Skip to content

Commit 51976ec

Browse files
authored
Merge pull request #1078 from graphprotocol/tmigone/post-trust-changes-payments
2 parents 3dc3140 + 20396ba commit 51976ec

File tree

17 files changed

+513
-206
lines changed

17 files changed

+513
-206
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ bin/
3434
.vscode
3535

3636
# Coverage and other reports
37+
coverage/
3738
reports/
3839
coverage.json
40+
lcov.info
3941

4042
# Local test files
4143
addresses-local.json

packages/horizon/contracts/interfaces/IGraphPayments.sol

+5-10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface IGraphPayments {
2020

2121
/**
2222
* @notice Emitted when a payment is collected
23+
* @param paymentType The type of payment as defined in {IGraphPayments}
2324
* @param payer The address of the payer
2425
* @param receiver The address of the receiver
2526
* @param dataService The address of the data service
@@ -30,8 +31,9 @@ interface IGraphPayments {
3031
* @param tokensReceiver Amount of tokens for the receiver
3132
*/
3233
event GraphPaymentCollected(
34+
PaymentTypes indexed paymentType,
3335
address indexed payer,
34-
address indexed receiver,
36+
address receiver,
3537
address indexed dataService,
3638
uint256 tokens,
3739
uint256 tokensProtocol,
@@ -40,14 +42,6 @@ interface IGraphPayments {
4042
uint256 tokensReceiver
4143
);
4244

43-
/**
44-
* @notice Thrown when the calculated amount of tokens to be paid out to all parties is
45-
* not the same as the amount of tokens being collected
46-
* @param tokens The amount of tokens being collected
47-
* @param tokensCalculated The sum of all the tokens to be paid out
48-
*/
49-
error GraphPaymentsBadAccounting(uint256 tokens, uint256 tokensCalculated);
50-
5145
/**
5246
* @notice Thrown when the protocol payment cut is invalid
5347
* @param protocolPaymentCut The protocol payment cut
@@ -63,9 +57,10 @@ interface IGraphPayments {
6357
/**
6458
* @notice Collects funds from a payer.
6559
* It will pay cuts to all relevant parties and forward the rest to the receiver.
60+
* Note that the collected amount can be zero.
6661
* @param paymentType The type of payment as defined in {IGraphPayments}
6762
* @param receiver The address of the receiver
68-
* @param tokens The amount of tokens being collected
63+
* @param tokens The amount of tokens being collected.
6964
* @param dataService The address of the data service
7065
* @param dataServiceCut The data service cut in PPM
7166
*/

packages/horizon/contracts/interfaces/IPaymentsEscrow.sol

+29-4
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,18 @@ interface IPaymentsEscrow {
3737
/**
3838
* @notice Emitted when a payer cancels an escrow thawing
3939
* @param payer The address of the payer
40+
* @param collector The address of the collector
4041
* @param receiver The address of the receiver
42+
* @param tokensThawing The amount of tokens that were being thawed
43+
* @param thawEndTimestamp The timestamp at which the thawing period was ending
4144
*/
42-
event CancelThaw(address indexed payer, address indexed receiver);
45+
event CancelThaw(
46+
address indexed payer,
47+
address indexed collector,
48+
address indexed receiver,
49+
uint256 tokensThawing,
50+
uint256 thawEndTimestamp
51+
);
4352

4453
/**
4554
* @notice Emitted when a payer thaws funds from the escrow for a payer-collector-receiver tuple
@@ -68,12 +77,19 @@ interface IPaymentsEscrow {
6877

6978
/**
7079
* @notice Emitted when a collector collects funds from the escrow for a payer-collector-receiver tuple
80+
* @param paymentType The type of payment being collected as defined in the {IGraphPayments} interface
7181
* @param payer The address of the payer
7282
* @param collector The address of the collector
7383
* @param receiver The address of the receiver
7484
* @param tokens The amount of tokens collected
7585
*/
76-
event EscrowCollected(address indexed payer, address indexed collector, address indexed receiver, uint256 tokens);
86+
event EscrowCollected(
87+
IGraphPayments.PaymentTypes indexed paymentType,
88+
address indexed payer,
89+
address indexed collector,
90+
address receiver,
91+
uint256 tokens
92+
);
7793

7894
// -- Errors --
7995

@@ -145,20 +161,29 @@ interface IPaymentsEscrow {
145161
/**
146162
* @notice Thaw a specific amount of escrow from a payer-collector-receiver's escrow account.
147163
* The payer is the transaction caller.
148-
* If `tokens` is zero and funds were already thawing it will cancel the thawing.
149164
* Note that repeated calls to this function will overwrite the previous thawing amount
150165
* and reset the thawing period.
151166
* @dev Requirements:
152167
* - `tokens` must be less than or equal to the available balance
153168
*
154-
* Emits a {Thaw} event. If `tokens` is zero it will emit a {CancelThaw} event.
169+
* Emits a {Thaw} event.
155170
*
156171
* @param collector The address of the collector
157172
* @param receiver The address of the receiver
158173
* @param tokens The amount of tokens to thaw
159174
*/
160175
function thaw(address collector, address receiver, uint256 tokens) external;
161176

177+
/**
178+
* @notice Cancels the thawing of escrow from a payer-collector-receiver's escrow account.
179+
* @param collector The address of the collector
180+
* @param receiver The address of the receiver
181+
* @dev Requirements:
182+
* - The payer must be thawing funds
183+
* Emits a {CancelThaw} event.
184+
*/
185+
function cancelThaw(address collector, address receiver) external;
186+
162187
/**
163188
* @notice Withdraws all thawed escrow from a payer-collector-receiver's escrow account.
164189
* The payer is the transaction caller.

packages/horizon/contracts/interfaces/ITAPCollector.sol

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ interface ITAPCollector is IPaymentsCollector {
2929
bytes32 collectionId;
3030
// The address of the payer the RAV was issued by
3131
address payer;
32-
// The address of the data service the RAV was issued to
33-
address dataService;
3432
// The address of the service provider the RAV was issued to
3533
address serviceProvider;
34+
// The address of the data service the RAV was issued to
35+
address dataService;
3636
// The RAV timestamp, indicating the latest TAP Receipt in the RAV
3737
uint64 timestampNs;
3838
// Total amount owed to the service provider since the beginning of the

packages/horizon/contracts/payments/GraphPayments.sol

+12-8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pragma solidity 0.8.27;
33

44
import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol";
55
import { IGraphPayments } from "../interfaces/IGraphPayments.sol";
6+
import { IHorizonStakingTypes } from "../interfaces/internal/IHorizonStakingTypes.sol";
67

78
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
89
import { MulticallUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol";
@@ -22,6 +23,8 @@ import { GraphDirectory } from "../utilities/GraphDirectory.sol";
2223
contract GraphPayments is Initializable, MulticallUpgradeable, GraphDirectory, IGraphPayments {
2324
using TokenUtils for IGraphToken;
2425
using PPMMath for uint256;
26+
27+
/// @notice Protocol payment cut in PPM
2528
uint256 public immutable PROTOCOL_PAYMENT_CUT;
2629

2730
/**
@@ -70,14 +73,14 @@ contract GraphPayments is Initializable, MulticallUpgradeable, GraphDirectory, I
7073
uint256 tokensDataService = tokensRemaining.mulPPMRoundUp(dataServiceCut);
7174
tokensRemaining = tokensRemaining - tokensDataService;
7275

73-
uint256 tokensDelegationPool = tokensRemaining.mulPPMRoundUp(
74-
_graphStaking().getDelegationFeeCut(receiver, dataService, paymentType)
75-
);
76-
tokensRemaining = tokensRemaining - tokensDelegationPool;
77-
78-
// Ensure accounting is correct
79-
uint256 tokensTotal = tokensProtocol + tokensDataService + tokensDelegationPool + tokensRemaining;
80-
require(tokens == tokensTotal, GraphPaymentsBadAccounting(tokens, tokensTotal));
76+
uint256 tokensDelegationPool = 0;
77+
IHorizonStakingTypes.DelegationPool memory pool = _graphStaking().getDelegationPool(receiver, dataService);
78+
if (pool.shares > 0) {
79+
tokensDelegationPool = tokensRemaining.mulPPMRoundUp(
80+
_graphStaking().getDelegationFeeCut(receiver, dataService, paymentType)
81+
);
82+
tokensRemaining = tokensRemaining - tokensDelegationPool;
83+
}
8184

8285
// Pay all parties
8386
_graphToken().burnTokens(tokensProtocol);
@@ -92,6 +95,7 @@ contract GraphPayments is Initializable, MulticallUpgradeable, GraphDirectory, I
9295
_graphToken().pushTokens(receiver, tokensRemaining);
9396

9497
emit GraphPaymentCollected(
98+
paymentType,
9599
msg.sender,
96100
receiver,
97101
dataService,

packages/horizon/contracts/payments/PaymentsEscrow.sol

+32-25
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,21 @@ import { GraphDirectory } from "../utilities/GraphDirectory.sol";
2323
contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory, IPaymentsEscrow {
2424
using TokenUtils for IGraphToken;
2525

26-
/// @notice Escrow account details for payer-collector-receiver tuples
27-
mapping(address payer => mapping(address collector => mapping(address receiver => IPaymentsEscrow.EscrowAccount escrowAccount)))
28-
public escrowAccounts;
29-
3026
/// @notice The maximum thawing period (in seconds) for both escrow withdrawal and collector revocation
3127
/// @dev This is a precautionary measure to avoid inadvertedly locking funds for too long
3228
uint256 public constant MAX_WAIT_PERIOD = 90 days;
3329

3430
/// @notice Thawing period in seconds for escrow funds withdrawal
3531
uint256 public immutable WITHDRAW_ESCROW_THAWING_PERIOD;
3632

33+
/// @notice Escrow account details for payer-collector-receiver tuples
34+
mapping(address payer => mapping(address collector => mapping(address receiver => IPaymentsEscrow.EscrowAccount escrowAccount)))
35+
public escrowAccounts;
36+
37+
/**
38+
* @notice Modifier to prevent function execution when contract is paused
39+
* @dev Reverts if the controller indicates the contract is paused
40+
*/
3741
modifier notPaused() {
3842
require(!_graphController().paused(), PaymentsEscrowIsPaused());
3943
_;
@@ -78,19 +82,9 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory,
7882
* @notice See {IPaymentsEscrow-thaw}
7983
*/
8084
function thaw(address collector, address receiver, uint256 tokens) external override notPaused {
81-
EscrowAccount storage account = escrowAccounts[msg.sender][collector][receiver];
82-
83-
// if amount thawing is zero and requested amount is zero this is an invalid request.
84-
// otherwise if amount thawing is greater than zero and requested amount is zero this
85-
// is a cancel thaw request.
86-
if (tokens == 0) {
87-
require(account.tokensThawing != 0, PaymentsEscrowNotThawing());
88-
account.tokensThawing = 0;
89-
account.thawEndTimestamp = 0;
90-
emit CancelThaw(msg.sender, receiver);
91-
return;
92-
}
85+
require(tokens > 0, PaymentsEscrowInvalidZeroTokens());
9386

87+
EscrowAccount storage account = escrowAccounts[msg.sender][collector][receiver];
9488
require(account.balance >= tokens, PaymentsEscrowInsufficientBalance(account.balance, tokens));
9589

9690
account.tokensThawing = tokens;
@@ -99,6 +93,21 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory,
9993
emit Thaw(msg.sender, collector, receiver, tokens, account.thawEndTimestamp);
10094
}
10195

96+
/**
97+
* @notice See {IPaymentsEscrow-cancelThaw}
98+
*/
99+
function cancelThaw(address collector, address receiver) external override notPaused {
100+
EscrowAccount storage account = escrowAccounts[msg.sender][collector][receiver];
101+
require(account.tokensThawing != 0, PaymentsEscrowNotThawing());
102+
103+
uint256 tokensThawing = account.tokensThawing;
104+
uint256 thawEndTimestamp = account.thawEndTimestamp;
105+
account.tokensThawing = 0;
106+
account.thawEndTimestamp = 0;
107+
108+
emit CancelThaw(msg.sender, collector, receiver, tokensThawing, thawEndTimestamp);
109+
}
110+
102111
/**
103112
* @notice See {IPaymentsEscrow-withdraw}
104113
*/
@@ -138,29 +147,27 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory,
138147
// Reduce amount from account balance
139148
account.balance -= tokens;
140149

141-
uint256 balanceBefore = _graphToken().balanceOf(address(this));
150+
uint256 escrowBalanceBefore = _graphToken().balanceOf(address(this));
142151

143152
_graphToken().approve(address(_graphPayments()), tokens);
144153
_graphPayments().collect(paymentType, receiver, tokens, dataService, dataServiceCut);
145154

146-
uint256 balanceAfter = _graphToken().balanceOf(address(this));
155+
// Verify that the escrow balance is consistent with the collected tokens
156+
uint256 escrowBalanceAfter = _graphToken().balanceOf(address(this));
147157
require(
148-
balanceBefore == tokens + balanceAfter,
149-
PaymentsEscrowInconsistentCollection(balanceBefore, balanceAfter, tokens)
158+
escrowBalanceBefore == tokens + escrowBalanceAfter,
159+
PaymentsEscrowInconsistentCollection(escrowBalanceBefore, escrowBalanceAfter, tokens)
150160
);
151161

152-
emit EscrowCollected(payer, msg.sender, receiver, tokens);
162+
emit EscrowCollected(paymentType, payer, msg.sender, receiver, tokens);
153163
}
154164

155165
/**
156166
* @notice See {IPaymentsEscrow-getBalance}
157167
*/
158168
function getBalance(address payer, address collector, address receiver) external view override returns (uint256) {
159169
EscrowAccount storage account = escrowAccounts[payer][collector][receiver];
160-
if (account.balance <= account.tokensThawing) {
161-
return 0;
162-
}
163-
return account.balance - account.tokensThawing;
170+
return account.balance > account.tokensThawing ? account.balance - account.tokensThawing : 0;
164171
}
165172

166173
/**

packages/horizon/contracts/payments/collectors/TAPCollector.sol

+4-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ contract TAPCollector is EIP712, GraphDirectory, ITAPCollector {
2929
/// @notice The EIP712 typehash for the ReceiptAggregateVoucher struct
3030
bytes32 private constant EIP712_RAV_TYPEHASH =
3131
keccak256(
32-
"ReceiptAggregateVoucher(address payer,address dataService,address serviceProvider,uint64 timestampNs,uint128 valueAggregate,bytes metadata)"
32+
"ReceiptAggregateVoucher(address payer,address serviceProvider,address dataService,uint64 timestampNs,uint128 valueAggregate,bytes metadata)"
3333
);
3434

3535
/// @notice Authorization details for payer-signer pairs
@@ -166,8 +166,9 @@ contract TAPCollector is EIP712, GraphDirectory, ITAPCollector {
166166
bytes memory _data,
167167
uint256 _tokensToCollect
168168
) private returns (uint256) {
169-
// Ensure caller is the RAV data service
170169
(SignedRAV memory signedRAV, uint256 dataServiceCut) = abi.decode(_data, (SignedRAV, uint256));
170+
171+
// Ensure caller is the RAV data service
171172
require(
172173
signedRAV.rav.dataService == msg.sender,
173174
TAPCollectorCallerNotDataService(msg.sender, signedRAV.rav.dataService)
@@ -259,8 +260,8 @@ contract TAPCollector is EIP712, GraphDirectory, ITAPCollector {
259260
abi.encode(
260261
EIP712_RAV_TYPEHASH,
261262
_rav.payer,
262-
_rav.dataService,
263263
_rav.serviceProvider,
264+
_rav.dataService,
264265
_rav.timestampNs,
265266
_rav.valueAggregate,
266267
keccak256(_rav.metadata)

0 commit comments

Comments
 (0)