Skip to content

Commit a76a0d6

Browse files
authored
feat: separate escrow accounts for each collector (#1058)
* feat: separate escrow accounts for each collector Signed-off-by: Tomás Migone <[email protected]> * feat: escrow account per collector Signed-off-by: Tomás Migone <[email protected]> --------- Signed-off-by: Tomás Migone <[email protected]>
1 parent e40c8fe commit a76a0d6

File tree

9 files changed

+91
-73
lines changed

9 files changed

+91
-73
lines changed

packages/horizon/contracts/interfaces/IPaymentsEscrow.sol

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ import { IGraphPayments } from "./IGraphPayments.sol";
1515
* collector contract which implements the {IPaymentsCollector} interface.
1616
*/
1717
interface IPaymentsEscrow {
18-
/// @notice Escrow account for a payer-receiver pair
18+
/// @notice Escrow account for a payer-collector-receiver tuple
1919
struct EscrowAccount {
20-
// Total token balance for the payer-receiver pair
20+
// Total token balance for the payer-collector-receiver tuple
2121
uint256 balance;
2222
// Amount of tokens currently being thawed
2323
uint256 tokensThawing;
@@ -70,12 +70,13 @@ interface IPaymentsEscrow {
7070
event RevokeCollector(address indexed payer, address indexed collector);
7171

7272
/**
73-
* @notice Emitted when a payer deposits funds into the escrow for a payer-receiver pair
73+
* @notice Emitted when a payer deposits funds into the escrow for a payer-collector-receiver tuple
7474
* @param payer The address of the payer
75+
* @param collector The address of the collector
7576
* @param receiver The address of the receiver
7677
* @param tokens The amount of tokens deposited
7778
*/
78-
event Deposit(address indexed payer, address indexed receiver, uint256 tokens);
79+
event Deposit(address indexed payer, address indexed collector, address indexed receiver, uint256 tokens);
7980

8081
/**
8182
* @notice Emitted when a payer cancels an escrow thawing
@@ -85,29 +86,38 @@ interface IPaymentsEscrow {
8586
event CancelThaw(address indexed payer, address indexed receiver);
8687

8788
/**
88-
* @notice Emitted when a payer thaws funds from the escrow for a payer-receiver pair
89+
* @notice Emitted when a payer thaws funds from the escrow for a payer-collector-receiver tuple
8990
* @param payer The address of the payer
91+
* @param collector The address of the collector
9092
* @param receiver The address of the receiver
9193
* @param tokens The amount of tokens being thawed
9294
* @param thawEndTimestamp The timestamp at which the thawing period ends
9395
*/
94-
event Thaw(address indexed payer, address indexed receiver, uint256 tokens, uint256 thawEndTimestamp);
96+
event Thaw(
97+
address indexed payer,
98+
address indexed collector,
99+
address indexed receiver,
100+
uint256 tokens,
101+
uint256 thawEndTimestamp
102+
);
95103

96104
/**
97-
* @notice Emitted when a payer withdraws funds from the escrow for a payer-receiver pair
105+
* @notice Emitted when a payer withdraws funds from the escrow for a payer-collector-receiver tuple
98106
* @param payer The address of the payer
107+
* @param collector The address of the collector
99108
* @param receiver The address of the receiver
100109
* @param tokens The amount of tokens withdrawn
101110
*/
102-
event Withdraw(address indexed payer, address indexed receiver, uint256 tokens);
111+
event Withdraw(address indexed payer, address indexed collector, address indexed receiver, uint256 tokens);
103112

104113
/**
105-
* @notice Emitted when a collector collects funds from the escrow for a payer-receiver pair
114+
* @notice Emitted when a collector collects funds from the escrow for a payer-collector-receiver tuple
106115
* @param payer The address of the payer
116+
* @param collector The address of the collector
107117
* @param receiver The address of the receiver
108118
* @param tokens The amount of tokens collected
109119
*/
110-
event EscrowCollected(address indexed payer, address indexed receiver, uint256 tokens);
120+
event EscrowCollected(address indexed payer, address indexed collector, address indexed receiver, uint256 tokens);
111121

112122
// -- Errors --
113123

@@ -211,26 +221,28 @@ interface IPaymentsEscrow {
211221
function revokeCollector(address collector) external;
212222

213223
/**
214-
* @notice Deposits funds into the escrow for a payer-receiver pair, where
224+
* @notice Deposits funds into the escrow for a payer-collector-receiver tuple, where
215225
* the payer is the transaction caller.
216226
* @dev Emits a {Deposit} event
227+
* @param collector The address of the collector
217228
* @param receiver The address of the receiver
218229
* @param tokens The amount of tokens to deposit
219230
*/
220-
function deposit(address receiver, uint256 tokens) external;
231+
function deposit(address collector, address receiver, uint256 tokens) external;
221232

222233
/**
223-
* @notice Deposits funds into the escrow for a payer-receiver pair, where
234+
* @notice Deposits funds into the escrow for a payer-collector-receiver tuple, where
224235
* the payer can be specified.
225236
* @dev Emits a {Deposit} event
226237
* @param payer The address of the payer
238+
* @param collector The address of the collector
227239
* @param receiver The address of the receiver
228240
* @param tokens The amount of tokens to deposit
229241
*/
230-
function depositTo(address payer, address receiver, uint256 tokens) external;
242+
function depositTo(address payer, address collector, address receiver, uint256 tokens) external;
231243

232244
/**
233-
* @notice Thaw a specific amount of escrow from a payer-receiver's escrow account.
245+
* @notice Thaw a specific amount of escrow from a payer-collector-receiver's escrow account.
234246
* The payer is the transaction caller.
235247
* If `tokens` is zero and funds were already thawing it will cancel the thawing.
236248
* Note that repeated calls to this function will overwrite the previous thawing amount
@@ -240,13 +252,14 @@ interface IPaymentsEscrow {
240252
*
241253
* Emits a {Thaw} event. If `tokens` is zero it will emit a {CancelThaw} event.
242254
*
255+
* @param collector The address of the collector
243256
* @param receiver The address of the receiver
244257
* @param tokens The amount of tokens to thaw
245258
*/
246-
function thaw(address receiver, uint256 tokens) external;
259+
function thaw(address collector, address receiver, uint256 tokens) external;
247260

248261
/**
249-
* @notice Withdraws all thawed escrow from a payer-receiver's escrow account.
262+
* @notice Withdraws all thawed escrow from a payer-collector-receiver's escrow account.
250263
* The payer is the transaction caller.
251264
* Note that the withdrawn funds might be less than the thawed amount if there were
252265
* payment collections in the meantime.
@@ -255,12 +268,13 @@ interface IPaymentsEscrow {
255268
*
256269
* Emits a {Withdraw} event
257270
*
271+
* @param collector The address of the collector
258272
* @param receiver The address of the receiver
259273
*/
260-
function withdraw(address receiver) external;
274+
function withdraw(address collector, address receiver) external;
261275

262276
/**
263-
* @notice Collects funds from the payer-receiver's escrow and sends them to {GraphPayments} for
277+
* @notice Collects funds from the payer-collector-receiver's escrow and sends them to {GraphPayments} for
264278
* distribution using the Graph Horizon Payments protocol.
265279
* The function will revert if there are not enough funds in the escrow.
266280
* @dev Requirements:
@@ -272,22 +286,23 @@ interface IPaymentsEscrow {
272286
* @param payer The address of the payer
273287
* @param receiver The address of the receiver
274288
* @param tokens The amount of tokens to collect
275-
* @param collector The address of the collector
289+
* @param dataService The address of the data service
276290
* @param tokensDataService The amount of tokens that {GraphPayments} should send to the data service
277291
*/
278292
function collect(
279293
IGraphPayments.PaymentTypes paymentType,
280294
address payer,
281295
address receiver,
282296
uint256 tokens,
283-
address collector,
297+
address dataService,
284298
uint256 tokensDataService
285299
) external;
286300

287301
/**
288-
* @notice Get the balance of a payer-receiver pair
302+
* @notice Get the balance of a payer-collector-receiver tuple
289303
* @param payer The address of the payer
304+
* @param collector The address of the collector
290305
* @param receiver The address of the receiver
291306
*/
292-
function getBalance(address payer, address receiver) external view returns (uint256);
307+
function getBalance(address payer, address collector, address receiver) external view returns (uint256);
293308
}

packages/horizon/contracts/payments/PaymentsEscrow.sol

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory,
2727
mapping(address payer => mapping(address collector => IPaymentsEscrow.Collector collectorDetails))
2828
public authorizedCollectors;
2929

30-
/// @notice Escrow account details for payer-receiver pairs
31-
mapping(address payer => mapping(address receiver => IPaymentsEscrow.EscrowAccount escrowAccount))
30+
/// @notice Escrow account details for payer-collector-receiver tuples
31+
mapping(address payer => mapping(address collector => mapping(address receiver => IPaymentsEscrow.EscrowAccount escrowAccount)))
3232
public escrowAccounts;
3333

34-
/// @notice The maximum thawing period (in seconds) for both escrow withdrawal and signer revocation
34+
/// @notice The maximum thawing period (in seconds) for both escrow withdrawal and collector revocation
3535
/// @dev This is a precautionary measure to avoid inadvertedly locking funds for too long
3636
uint256 public constant MAX_WAIT_PERIOD = 90 days;
3737

@@ -126,22 +126,22 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory,
126126
/**
127127
* @notice See {IPaymentsEscrow-deposit}
128128
*/
129-
function deposit(address receiver, uint256 tokens) external override notPaused {
130-
_deposit(msg.sender, receiver, tokens);
129+
function deposit(address collector, address receiver, uint256 tokens) external override notPaused {
130+
_deposit(msg.sender, collector, receiver, tokens);
131131
}
132132

133133
/**
134134
* @notice See {IPaymentsEscrow-depositTo}
135135
*/
136-
function depositTo(address payer, address receiver, uint256 tokens) external override notPaused {
137-
_deposit(payer, receiver, tokens);
136+
function depositTo(address payer, address collector, address receiver, uint256 tokens) external override notPaused {
137+
_deposit(payer, collector, receiver, tokens);
138138
}
139139

140140
/**
141141
* @notice See {IPaymentsEscrow-thaw}
142142
*/
143-
function thaw(address receiver, uint256 tokens) external override notPaused {
144-
EscrowAccount storage account = escrowAccounts[msg.sender][receiver];
143+
function thaw(address collector, address receiver, uint256 tokens) external override notPaused {
144+
EscrowAccount storage account = escrowAccounts[msg.sender][collector][receiver];
145145

146146
// if amount thawing is zero and requested amount is zero this is an invalid request.
147147
// otherwise if amount thawing is greater than zero and requested amount is zero this
@@ -159,14 +159,14 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory,
159159
account.tokensThawing = tokens;
160160
account.thawEndTimestamp = block.timestamp + WITHDRAW_ESCROW_THAWING_PERIOD;
161161

162-
emit Thaw(msg.sender, receiver, tokens, account.thawEndTimestamp);
162+
emit Thaw(msg.sender, collector, receiver, tokens, account.thawEndTimestamp);
163163
}
164164

165165
/**
166166
* @notice See {IPaymentsEscrow-withdraw}
167167
*/
168-
function withdraw(address receiver) external override notPaused {
169-
EscrowAccount storage account = escrowAccounts[msg.sender][receiver];
168+
function withdraw(address collector, address receiver) external override notPaused {
169+
EscrowAccount storage account = escrowAccounts[msg.sender][collector][receiver];
170170
require(account.thawEndTimestamp != 0, PaymentsEscrowNotThawing());
171171
require(
172172
account.thawEndTimestamp < block.timestamp,
@@ -180,7 +180,7 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory,
180180
account.tokensThawing = 0;
181181
account.thawEndTimestamp = 0;
182182
_graphToken().pushTokens(msg.sender, tokens);
183-
emit Withdraw(msg.sender, receiver, tokens);
183+
emit Withdraw(msg.sender, collector, receiver, tokens);
184184
}
185185

186186
/**
@@ -195,15 +195,18 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory,
195195
uint256 tokensDataService
196196
) external override notPaused {
197197
// Check if collector is authorized and has enough funds
198-
Collector storage collector = authorizedCollectors[payer][msg.sender];
199-
require(collector.allowance >= tokens, PaymentsEscrowInsufficientAllowance(collector.allowance, tokens));
198+
Collector storage collectorDetails = authorizedCollectors[payer][msg.sender];
199+
require(
200+
collectorDetails.allowance >= tokens,
201+
PaymentsEscrowInsufficientAllowance(collectorDetails.allowance, tokens)
202+
);
200203

201204
// Check if there are enough funds in the escrow account
202-
EscrowAccount storage account = escrowAccounts[payer][receiver];
205+
EscrowAccount storage account = escrowAccounts[payer][msg.sender][receiver];
203206
require(account.balance >= tokens, PaymentsEscrowInsufficientBalance(account.balance, tokens));
204207

205208
// Reduce amount from approved collector and account balance
206-
collector.allowance -= tokens;
209+
collectorDetails.allowance -= tokens;
207210
account.balance -= tokens;
208211

209212
uint256 balanceBefore = _graphToken().balanceOf(address(this));
@@ -217,14 +220,14 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory,
217220
PaymentsEscrowInconsistentCollection(balanceBefore, balanceAfter, tokens)
218221
);
219222

220-
emit EscrowCollected(payer, receiver, tokens);
223+
emit EscrowCollected(payer, msg.sender, receiver, tokens);
221224
}
222225

223226
/**
224227
* @notice See {IPaymentsEscrow-getBalance}
225228
*/
226-
function getBalance(address payer, address receiver) external view override returns (uint256) {
227-
EscrowAccount storage account = escrowAccounts[payer][receiver];
229+
function getBalance(address payer, address collector, address receiver) external view override returns (uint256) {
230+
EscrowAccount storage account = escrowAccounts[payer][collector][receiver];
228231
return account.balance - account.tokensThawing;
229232
}
230233

@@ -234,9 +237,9 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory,
234237
* @param _receiver The address of the receiver
235238
* @param _tokens The amount of tokens to deposit
236239
*/
237-
function _deposit(address _payer, address _receiver, uint256 _tokens) private {
238-
escrowAccounts[_payer][_receiver].balance += _tokens;
240+
function _deposit(address _payer, address _collector, address _receiver, uint256 _tokens) private {
241+
escrowAccounts[_payer][_collector][_receiver].balance += _tokens;
239242
_graphToken().pullTokens(msg.sender, _tokens);
240-
emit Deposit(_payer, _receiver, _tokens);
243+
emit Deposit(_payer, _collector, _receiver, _tokens);
241244
}
242245
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ contract GraphEscrowTest is HorizonStakingSharedTest {
3939
vm.assume(thawAmount > 0);
4040
vm.assume(amount > thawAmount);
4141
_depositTokens(amount);
42-
escrow.thaw(users.indexer, thawAmount);
42+
escrow.thaw(users.verifier, users.indexer, thawAmount);
4343
_;
4444
}
4545

@@ -49,7 +49,7 @@ contract GraphEscrowTest is HorizonStakingSharedTest {
4949

5050
function _depositTokens(uint256 tokens) internal {
5151
token.approve(address(escrow), tokens);
52-
escrow.deposit(users.indexer, tokens);
52+
escrow.deposit(users.verifier, users.indexer, tokens);
5353
}
5454

5555
function _approveEscrow(uint256 tokens) internal {

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ contract GraphEscrowCollectTest is GraphEscrowTest {
2727
address _dataService,
2828
uint256 _tokensDataService
2929
) private {
30+
(, address _collector, ) = vm.readCallers();
31+
3032
// Previous balances
31-
(uint256 previousPayerEscrowBalance,,) = escrow.escrowAccounts(_payer, _receiver);
33+
(uint256 previousPayerEscrowBalance,,) = escrow.escrowAccounts(_payer, _collector, _receiver);
3234
CollectPaymentData memory previousBalances = CollectPaymentData({
3335
escrowBalance: token.balanceOf(address(escrow)),
3436
paymentsBalance: token.balanceOf(address(payments)),
@@ -41,7 +43,7 @@ contract GraphEscrowCollectTest is GraphEscrowTest {
4143
});
4244

4345
vm.expectEmit(address(escrow));
44-
emit IPaymentsEscrow.EscrowCollected(_payer, _receiver, _tokens);
46+
emit IPaymentsEscrow.EscrowCollected(_payer, _collector, _receiver, _tokens);
4547
escrow.collect(_paymentType, _payer, _receiver, _tokens, _dataService, _tokensDataService);
4648

4749
// Calculate cuts
@@ -51,11 +53,9 @@ contract GraphEscrowCollectTest is GraphEscrowTest {
5153
_dataService,
5254
_paymentType
5355
);
54-
uint256 tokensProtocol = _tokens * protocolPaymentCut / MAX_PPM;
55-
uint256 tokensDelegation = _tokens * delegatorCut / MAX_PPM;
5656

5757
// After balances
58-
(uint256 afterPayerEscrowBalance,,) = escrow.escrowAccounts(_payer, _receiver);
58+
(uint256 afterPayerEscrowBalance,,) = escrow.escrowAccounts(_payer, _collector, _receiver);
5959
CollectPaymentData memory afterBalances = CollectPaymentData({
6060
escrowBalance: token.balanceOf(address(escrow)),
6161
paymentsBalance: token.balanceOf(address(payments)),
@@ -68,12 +68,12 @@ contract GraphEscrowCollectTest is GraphEscrowTest {
6868
});
6969

7070
// Check receiver balance after payment
71-
uint256 receiverExpectedPayment = _tokens - _tokensDataService - tokensProtocol - tokensDelegation;
71+
uint256 receiverExpectedPayment = _tokens - _tokensDataService - _tokens * protocolPaymentCut / MAX_PPM - _tokens * delegatorCut / MAX_PPM;
7272
assertEq(afterBalances.receiverBalance - previousBalances.receiverBalance, receiverExpectedPayment);
7373
assertEq(token.balanceOf(address(payments)), 0);
7474

7575
// Check delegation pool balance after payment
76-
assertEq(afterBalances.delegationPoolBalance - previousBalances.delegationPoolBalance, tokensDelegation);
76+
assertEq(afterBalances.delegationPoolBalance - previousBalances.delegationPoolBalance, _tokens * delegatorCut / MAX_PPM);
7777

7878
// Check that the escrow account has been updated
7979
assertEq(previousBalances.escrowBalance, afterBalances.escrowBalance + _tokens);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ contract GraphEscrowDepositTest is GraphEscrowTest {
1212
*/
1313

1414
function testDeposit_Tokens(uint256 amount) public useGateway useDeposit(amount) {
15-
(uint256 indexerEscrowBalance,,) = escrow.escrowAccounts(users.gateway, users.indexer);
15+
(uint256 indexerEscrowBalance,,) = escrow.escrowAccounts(users.gateway, users.verifier, users.indexer);
1616
assertEq(indexerEscrowBalance, amount);
1717
}
1818
}

0 commit comments

Comments
 (0)