Skip to content

Commit c0e83cf

Browse files
committed
fix: allow payments to be routed to a provided address
Signed-off-by: Tomás Migone <[email protected]>
1 parent 4a3cf2c commit c0e83cf

21 files changed

+440
-169
lines changed

packages/horizon/contracts/interfaces/IGraphPayments.sol

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ interface IGraphPayments {
3131
* @param tokensDataService Amount of tokens for the data service
3232
* @param tokensDelegationPool Amount of tokens for delegators
3333
* @param tokensReceiver Amount of tokens for the receiver
34+
* @param receiverDestination The address where the receiver's payment cut is sent.
3435
*/
3536
event GraphPaymentCollected(
3637
PaymentTypes indexed paymentType,
@@ -41,7 +42,8 @@ interface IGraphPayments {
4142
uint256 tokensProtocol,
4243
uint256 tokensDataService,
4344
uint256 tokensDelegationPool,
44-
uint256 tokensReceiver
45+
uint256 tokensReceiver,
46+
address receiverDestination
4547
);
4648

4749
/**
@@ -63,19 +65,25 @@ interface IGraphPayments {
6365

6466
/**
6567
* @notice Collects funds from a payer.
66-
* It will pay cuts to all relevant parties and forward the rest to the receiver.
68+
* It will pay cuts to all relevant parties and forward the rest to the receiver destination address. If the
69+
* destination address is zero the funds are automatically staked to the receiver. Note that the receiver
70+
* destination address can be set to the receiver address to collect funds on the receiver without re-staking.
71+
*
6772
* Note that the collected amount can be zero.
73+
*
6874
* @param paymentType The type of payment as defined in {IGraphPayments}
6975
* @param receiver The address of the receiver
7076
* @param tokens The amount of tokens being collected.
7177
* @param dataService The address of the data service
7278
* @param dataServiceCut The data service cut in PPM
79+
* @param receiverDestination The address where the receiver's payment cut is sent.
7380
*/
7481
function collect(
7582
PaymentTypes paymentType,
7683
address receiver,
7784
uint256 tokens,
7885
address dataService,
79-
uint256 dataServiceCut
86+
uint256 dataServiceCut,
87+
address receiverDestination
8088
) external;
8189
}

packages/horizon/contracts/interfaces/IGraphTallyCollector.sol

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,10 @@ interface IGraphTallyCollector is IPaymentsCollector {
107107
* - The amount of tokens to collect must be less than or equal to the total amount of tokens in the RAV minus
108108
* the tokens already collected.
109109
* @param paymentType The payment type to collect
110-
* @param data Additional data required for the payment collection
110+
* @param data Additional data required for the payment collection. Encoded as follows:
111+
* - SignedRAV `signedRAV`: The signed RAV
112+
* - uint256 `dataServiceCut`: The data service cut in PPM
113+
* - address `receiverDestination`: The address where the receiver's payment should be sent.
111114
* @param tokensToCollect The amount of tokens to collect
112115
* @return The amount of tokens collected
113116
*/

packages/horizon/contracts/interfaces/IPaymentsEscrow.sol

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,15 @@ interface IPaymentsEscrow {
8686
* @param collector The address of the collector
8787
* @param receiver The address of the receiver
8888
* @param tokens The amount of tokens collected
89+
* @param receiverDestination The address where the receiver's payment should be sent.
8990
*/
9091
event EscrowCollected(
9192
IGraphPayments.PaymentTypes indexed paymentType,
9293
address indexed payer,
9394
address indexed collector,
9495
address receiver,
95-
uint256 tokens
96+
uint256 tokens,
97+
address receiverDestination
9698
);
9799

98100
// -- Errors --
@@ -221,14 +223,16 @@ interface IPaymentsEscrow {
221223
* @param tokens The amount of tokens to collect
222224
* @param dataService The address of the data service
223225
* @param dataServiceCut The data service cut in PPM that {GraphPayments} should send
226+
* @param receiverDestination The address where the receiver's payment should be sent.
224227
*/
225228
function collect(
226229
IGraphPayments.PaymentTypes paymentType,
227230
address payer,
228231
address receiver,
229232
uint256 tokens,
230233
address dataService,
231-
uint256 dataServiceCut
234+
uint256 dataServiceCut,
235+
address receiverDestination
232236
) external;
233237

234238
/**

packages/horizon/contracts/payments/GraphPayments.sol

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ contract GraphPayments is Initializable, MulticallUpgradeable, GraphDirectory, I
5151
address receiver,
5252
uint256 tokens,
5353
address dataService,
54-
uint256 dataServiceCut
54+
uint256 dataServiceCut,
55+
address receiverDestination
5556
) external {
5657
require(PPMMath.isValidPPM(dataServiceCut), GraphPaymentsInvalidCut(dataServiceCut));
5758

@@ -88,7 +89,14 @@ contract GraphPayments is Initializable, MulticallUpgradeable, GraphDirectory, I
8889
_graphStaking().addToDelegationPool(receiver, dataService, tokensDelegationPool);
8990
}
9091

91-
_graphToken().pushTokens(receiver, tokensRemaining);
92+
if (tokensRemaining > 0) {
93+
if (receiverDestination == address(0)) {
94+
_graphToken().approve(address(_graphStaking()), tokensRemaining);
95+
_graphStaking().stakeTo(receiver, tokensRemaining);
96+
} else {
97+
_graphToken().pushTokens(receiverDestination, tokensRemaining);
98+
}
99+
}
92100

93101
emit GraphPaymentCollected(
94102
paymentType,
@@ -99,7 +107,8 @@ contract GraphPayments is Initializable, MulticallUpgradeable, GraphDirectory, I
99107
tokensProtocol,
100108
tokensDataService,
101109
tokensDelegationPool,
102-
tokensRemaining
110+
tokensRemaining,
111+
receiverDestination
103112
);
104113
}
105114
}

packages/horizon/contracts/payments/PaymentsEscrow.sol

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory,
125125
address receiver,
126126
uint256 tokens,
127127
address dataService,
128-
uint256 dataServiceCut
128+
uint256 dataServiceCut,
129+
address receiverDestination
129130
) external override notPaused {
130131
// Check if there are enough funds in the escrow account
131132
EscrowAccount storage account = escrowAccounts[payer][msg.sender][receiver];
@@ -137,7 +138,7 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory,
137138
uint256 escrowBalanceBefore = _graphToken().balanceOf(address(this));
138139

139140
_graphToken().approve(address(_graphPayments()), tokens);
140-
_graphPayments().collect(paymentType, receiver, tokens, dataService, dataServiceCut);
141+
_graphPayments().collect(paymentType, receiver, tokens, dataService, dataServiceCut, receiverDestination);
141142

142143
// Verify that the escrow balance is consistent with the collected tokens
143144
uint256 escrowBalanceAfter = _graphToken().balanceOf(address(this));
@@ -146,7 +147,7 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory,
146147
PaymentsEscrowInconsistentCollection(escrowBalanceBefore, escrowBalanceAfter, tokens)
147148
);
148149

149-
emit EscrowCollected(paymentType, payer, msg.sender, receiver, tokens);
150+
emit EscrowCollected(paymentType, payer, msg.sender, receiver, tokens, receiverDestination);
150151
}
151152

152153
/// @inheritdoc IPaymentsEscrow

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ contract GraphTallyCollector is EIP712, GraphDirectory, Authorizable, IGraphTall
5858
* - Signer of the RAV must be authorized to sign for the payer.
5959
* - Service provider must have an active provision with the data service to collect payments.
6060
* @notice REVERT: This function may revert if ECDSA.recover fails, check ECDSA library for details.
61+
* @param paymentType The payment type to collect
62+
* @param data Additional data required for the payment collection. Encoded as follows:
63+
* - SignedRAV `signedRAV`: The signed RAV
64+
* - uint256 `dataServiceCut`: The data service cut in PPM
65+
* - address `receiverDestination`: The address where the receiver's payment should be sent.
66+
* @return The amount of tokens collected
6167
*/
6268
/// @inheritdoc IPaymentsCollector
6369
function collect(IGraphPayments.PaymentTypes paymentType, bytes calldata data) external override returns (uint256) {
@@ -96,7 +102,7 @@ contract GraphTallyCollector is EIP712, GraphDirectory, Authorizable, IGraphTall
96102
bytes calldata _data,
97103
uint256 _tokensToCollect
98104
) private returns (uint256) {
99-
(SignedRAV memory signedRAV, uint256 dataServiceCut) = abi.decode(_data, (SignedRAV, uint256));
105+
(SignedRAV memory signedRAV, uint256 dataServiceCut, address receiverDestination) = abi.decode(_data, (SignedRAV, uint256, address));
100106

101107
// Ensure caller is the RAV data service
102108
require(
@@ -123,10 +129,9 @@ contract GraphTallyCollector is EIP712, GraphDirectory, Authorizable, IGraphTall
123129
}
124130

125131
uint256 tokensToCollect = 0;
126-
address payer = signedRAV.rav.payer;
127132
{
128133
uint256 tokensRAV = signedRAV.rav.valueAggregate;
129-
uint256 tokensAlreadyCollected = tokensCollected[dataService][collectionId][receiver][payer];
134+
uint256 tokensAlreadyCollected = tokensCollected[dataService][collectionId][receiver][signedRAV.rav.payer];
130135
require(
131136
tokensRAV > tokensAlreadyCollected,
132137
GraphTallyCollectorInconsistentRAVTokens(tokensRAV, tokensAlreadyCollected)
@@ -147,16 +152,16 @@ contract GraphTallyCollector is EIP712, GraphDirectory, Authorizable, IGraphTall
147152
}
148153

149154
if (tokensToCollect > 0) {
150-
tokensCollected[dataService][collectionId][receiver][payer] += tokensToCollect;
151-
_graphPaymentsEscrow().collect(_paymentType, payer, receiver, tokensToCollect, dataService, dataServiceCut);
155+
tokensCollected[dataService][collectionId][receiver][signedRAV.rav.payer] += tokensToCollect;
156+
_graphPaymentsEscrow().collect(_paymentType, signedRAV.rav.payer, receiver, tokensToCollect, dataService, dataServiceCut, receiverDestination);
152157
}
153158

154-
emit PaymentCollected(_paymentType, collectionId, payer, receiver, dataService, tokensToCollect);
159+
emit PaymentCollected(_paymentType, collectionId, signedRAV.rav.payer, receiver, dataService, tokensToCollect);
155160

156161
// This event is emitted to allow reconstructing RAV history with onchain data.
157162
emit RAVCollected(
158163
collectionId,
159-
payer,
164+
signedRAV.rav.payer,
160165
receiver,
161166
dataService,
162167
signedRAV.rav.timestampNs,

packages/horizon/test/escrow/GraphEscrow.t.sol

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,21 @@ contract GraphEscrowTest is HorizonStakingSharedTest, PaymentsEscrowSharedTest {
113113
uint256 payerEscrowBalance;
114114
}
115115

116+
struct CollectTokensData {
117+
uint256 tokensProtocol;
118+
uint256 tokensDataService;
119+
uint256 tokensDelegation;
120+
uint256 receiverExpectedPayment;
121+
}
122+
116123
function _collectEscrow(
117124
IGraphPayments.PaymentTypes _paymentType,
118125
address _payer,
119126
address _receiver,
120127
uint256 _tokens,
121128
address _dataService,
122-
uint256 _dataServiceCut
129+
uint256 _dataServiceCut,
130+
address _paymentsDestination
123131
) internal {
124132
(, address _collector, ) = vm.readCallers();
125133

@@ -132,31 +140,49 @@ contract GraphEscrowTest is HorizonStakingSharedTest, PaymentsEscrowSharedTest {
132140
dataServiceBalance: token.balanceOf(_dataService),
133141
payerEscrowBalance: 0
134142
});
143+
CollectTokensData memory collectTokensData = CollectTokensData({
144+
tokensProtocol: 0,
145+
tokensDataService: 0,
146+
tokensDelegation: 0,
147+
receiverExpectedPayment: 0
148+
});
135149

136150
{
137151
(uint256 payerEscrowBalance, , ) = escrow.escrowAccounts(_payer, _collector, _receiver);
138152
previousBalances.payerEscrowBalance = payerEscrowBalance;
139153
}
140154

141155
vm.expectEmit(address(escrow));
142-
emit IPaymentsEscrow.EscrowCollected(_paymentType, _payer, _collector, _receiver, _tokens);
143-
escrow.collect(_paymentType, _payer, _receiver, _tokens, _dataService, _dataServiceCut);
156+
emit IPaymentsEscrow.EscrowCollected(
157+
_paymentType,
158+
_payer,
159+
_collector,
160+
_receiver,
161+
_tokens,
162+
_paymentsDestination
163+
);
164+
escrow.collect(_paymentType, _payer, _receiver, _tokens, _dataService, _dataServiceCut, _paymentsDestination);
144165

145166
// Calculate cuts
146167
// this is nasty but stack is indeed too deep
147-
uint256 tokensDataService = (_tokens - _tokens.mulPPMRoundUp(payments.PROTOCOL_PAYMENT_CUT())).mulPPMRoundUp(
168+
collectTokensData.tokensProtocol = _tokens.mulPPMRoundUp(payments.PROTOCOL_PAYMENT_CUT());
169+
collectTokensData.tokensDataService = (_tokens - collectTokensData.tokensProtocol).mulPPMRoundUp(
148170
_dataServiceCut
149171
);
150-
uint256 tokensDelegation = 0;
172+
151173
IHorizonStakingTypes.DelegationPool memory pool = staking.getDelegationPool(_receiver, _dataService);
152174
if (pool.shares > 0) {
153-
tokensDelegation = (_tokens - _tokens.mulPPMRoundUp(payments.PROTOCOL_PAYMENT_CUT()) - tokensDataService)
154-
.mulPPMRoundUp(staking.getDelegationFeeCut(_receiver, _dataService, _paymentType));
175+
collectTokensData.tokensDelegation = (_tokens -
176+
collectTokensData.tokensProtocol -
177+
collectTokensData.tokensDataService).mulPPMRoundUp(
178+
staking.getDelegationFeeCut(_receiver, _dataService, _paymentType)
179+
);
155180
}
156-
uint256 receiverExpectedPayment = _tokens -
157-
_tokens.mulPPMRoundUp(payments.PROTOCOL_PAYMENT_CUT()) -
158-
tokensDataService -
159-
tokensDelegation;
181+
collectTokensData.receiverExpectedPayment =
182+
_tokens -
183+
collectTokensData.tokensProtocol -
184+
collectTokensData.tokensDataService -
185+
collectTokensData.tokensDelegation;
160186

161187
// After balances
162188
CollectPaymentData memory afterBalances = CollectPaymentData({
@@ -173,11 +199,11 @@ contract GraphEscrowTest is HorizonStakingSharedTest, PaymentsEscrowSharedTest {
173199
}
174200

175201
// Check receiver balance after payment
176-
assertEq(afterBalances.receiverBalance - previousBalances.receiverBalance, receiverExpectedPayment);
202+
assertEq(afterBalances.receiverBalance - previousBalances.receiverBalance, collectTokensData.receiverExpectedPayment);
177203
assertEq(token.balanceOf(address(payments)), 0);
178204

179205
// Check delegation pool balance after payment
180-
assertEq(afterBalances.delegationPoolBalance - previousBalances.delegationPoolBalance, tokensDelegation);
206+
assertEq(afterBalances.delegationPoolBalance - previousBalances.delegationPoolBalance, collectTokensData.tokensDelegation);
181207

182208
// Check that the escrow account has been updated
183209
assertEq(previousBalances.escrowBalance, afterBalances.escrowBalance + _tokens);
@@ -186,7 +212,7 @@ contract GraphEscrowTest is HorizonStakingSharedTest, PaymentsEscrowSharedTest {
186212
assertEq(previousBalances.paymentsBalance, afterBalances.paymentsBalance);
187213

188214
// Check data service balance after payment
189-
assertEq(afterBalances.dataServiceBalance - previousBalances.dataServiceBalance, tokensDataService);
215+
assertEq(afterBalances.dataServiceBalance - previousBalances.dataServiceBalance, collectTokensData.tokensDataService);
190216

191217
// Check payers escrow balance after payment
192218
assertEq(previousBalances.payerEscrowBalance - _tokens, afterBalances.payerEscrowBalance);

packages/horizon/test/escrow/collect.t.sol

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ contract GraphEscrowCollectTest is GraphEscrowTest {
4646
users.indexer,
4747
tokensToCollect,
4848
subgraphDataServiceAddress,
49-
dataServiceCut
49+
dataServiceCut,
50+
users.indexer
5051
);
5152
}
5253

@@ -71,7 +72,8 @@ contract GraphEscrowCollectTest is GraphEscrowTest {
7172
users.indexer,
7273
tokens,
7374
subgraphDataServiceAddress,
74-
dataServiceCut
75+
dataServiceCut,
76+
users.indexer
7577
);
7678
}
7779

@@ -95,7 +97,8 @@ contract GraphEscrowCollectTest is GraphEscrowTest {
9597
users.indexer,
9698
amount,
9799
subgraphDataServiceAddress,
98-
0
100+
0,
101+
users.indexer
99102
);
100103
vm.stopPrank();
101104
}
@@ -126,16 +129,8 @@ contract GraphEscrowCollectTest is GraphEscrowTest {
126129
users.indexer,
127130
firstCollect,
128131
subgraphDataServiceAddress,
129-
0
132+
0,
133+
users.indexer
130134
);
131-
132-
// _collectEscrow(
133-
// IGraphPayments.PaymentTypes.QueryFee,
134-
// users.gateway,
135-
// users.indexer,
136-
// secondCollect,
137-
// subgraphDataServiceAddress,
138-
// 0
139-
// );
140135
}
141136
}

packages/horizon/test/escrow/getters.t.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ contract GraphEscrowGettersTest is GraphEscrowTest {
5757
users.indexer,
5858
amountCollected,
5959
subgraphDataServiceAddress,
60-
0
60+
0,
61+
users.indexer
6162
);
6263

6364
// balance should always be 0 since thawing funds > available funds

packages/horizon/test/escrow/paused.t.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ contract GraphEscrowPausedTest is GraphEscrowTest {
6666
users.indexer,
6767
tokens,
6868
subgraphDataServiceAddress,
69-
tokensDataService
69+
tokensDataService,
70+
users.indexer
7071
);
7172
}
7273
}

packages/horizon/test/escrow/withdraw.t.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ contract GraphEscrowWithdrawTest is GraphEscrowTest {
6161
users.indexer,
6262
amountCollected,
6363
subgraphDataServiceAddress,
64-
0
64+
0,
65+
users.indexer
6566
);
6667

6768
// Advance time to simulate the thawing period

0 commit comments

Comments
 (0)