Skip to content

Commit c059f74

Browse files
f: add integration test for collection
1 parent d5887b1 commit c059f74

File tree

5 files changed

+208
-50
lines changed

5 files changed

+208
-50
lines changed

packages/horizon/test/payments/recurring-collector/RecurringCollector.t.sol

+5-21
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ import { IPaymentsCollector } from "../../../contracts/interfaces/IPaymentsColle
88
import { IRecurringCollector } from "../../../contracts/interfaces/IRecurringCollector.sol";
99
import { RecurringCollector } from "../../../contracts/payments/collectors/RecurringCollector.sol";
1010

11-
import { AuthorizableHelper } from "../../utilities/Authorizable.t.sol";
1211
import { Bounder } from "../../utils/Bounder.t.sol";
1312
import { RecurringCollectorControllerMock } from "./RecurringCollectorControllerMock.t.sol";
1413
import { PaymentsEscrowMock } from "./PaymentsEscrowMock.t.sol";
14+
import { RecurringCollectorHelper } from "./RecurringCollectorHelper.t.sol";
1515

1616
contract RecurringCollectorTest is Test, Bounder {
1717
RecurringCollector private _recurringCollector;
18-
AuthorizableHelper private _authHelper;
1918
PaymentsEscrowMock private _paymentsEscrow;
19+
RecurringCollectorHelper private _recurringCollectorHelper;
2020

2121
function setUp() public {
2222
_paymentsEscrow = new PaymentsEscrowMock();
@@ -26,7 +26,7 @@ contract RecurringCollectorTest is Test, Bounder {
2626
address(new RecurringCollectorControllerMock(address(_paymentsEscrow))),
2727
1
2828
);
29-
_authHelper = new AuthorizableHelper(_recurringCollector, 1);
29+
_recurringCollectorHelper = new RecurringCollectorHelper(_recurringCollector);
3030
}
3131

3232
/*
@@ -404,9 +404,9 @@ contract RecurringCollectorTest is Test, Bounder {
404404
uint256 _signerKey
405405
) private returns (IRecurringCollector.SignedRCV memory) {
406406
vm.assume(_rcv.payer != address(0));
407-
_authHelper.authorizeSignerWithChecks(_rcv.payer, _signerKey);
407+
_recurringCollectorHelper.authorizeSignerWithChecks(_rcv.payer, _signerKey);
408408
_rcv.acceptDeadline = boundTimestampMin(_rcv.acceptDeadline, block.timestamp + 1);
409-
IRecurringCollector.SignedRCV memory signedRCV = _generateSignedRCV(_recurringCollector, _rcv, _signerKey);
409+
IRecurringCollector.SignedRCV memory signedRCV = _recurringCollectorHelper.generateSignedRCV(_rcv, _signerKey);
410410

411411
vm.prank(_rcv.dataService);
412412
_recurringCollector.accept(signedRCV);
@@ -478,22 +478,6 @@ contract RecurringCollectorTest is Test, Bounder {
478478
return (data, collectionSeconds, tokens);
479479
}
480480

481-
function _generateSignedRCV(
482-
IRecurringCollector _collector,
483-
IRecurringCollector.RecurrentCollectionVoucher memory _rcv,
484-
uint256 _signerPrivateKey
485-
) private view returns (IRecurringCollector.SignedRCV memory) {
486-
bytes32 messageHash = _collector.encodeRCV(_rcv);
487-
(uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerPrivateKey, messageHash);
488-
bytes memory signature = abi.encodePacked(r, s, v);
489-
IRecurringCollector.SignedRCV memory signedRCV = IRecurringCollector.SignedRCV({
490-
rcv: _rcv,
491-
signature: signature
492-
});
493-
494-
return signedRCV;
495-
}
496-
497481
function _sensibleRCV(
498482
IRecurringCollector.RecurrentCollectionVoucher memory _rcv
499483
) private pure returns (IRecurringCollector.RecurrentCollectionVoucher memory) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.27;
3+
4+
import { IRecurringCollector } from "../../../contracts/interfaces/IRecurringCollector.sol";
5+
import { RecurringCollector } from "../../../contracts/payments/collectors/RecurringCollector.sol";
6+
import { AuthorizableHelper } from "../../utilities/Authorizable.t.sol";
7+
8+
contract RecurringCollectorHelper is AuthorizableHelper {
9+
RecurringCollector public collector;
10+
11+
constructor(
12+
RecurringCollector collector_
13+
) AuthorizableHelper(collector_, collector_.REVOKE_AUTHORIZATION_THAWING_PERIOD()) {
14+
collector = collector_;
15+
}
16+
17+
function generateSignedRCV(
18+
IRecurringCollector.RecurrentCollectionVoucher memory rcv,
19+
uint256 signerPrivateKey
20+
) public view returns (IRecurringCollector.SignedRCV memory) {
21+
bytes32 messageHash = collector.encodeRCV(rcv);
22+
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, messageHash);
23+
bytes memory signature = abi.encodePacked(r, s, v);
24+
IRecurringCollector.SignedRCV memory signedRCV = IRecurringCollector.SignedRCV({
25+
rcv: rcv,
26+
signature: signature
27+
});
28+
29+
return signedRCV;
30+
}
31+
}

packages/subgraph-service/test/subgraphService/SubgraphService.t.sol

+12-29
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ contract SubgraphServiceTest is SubgraphServiceSharedTest {
202202
uint256 paymentCollected = 0;
203203
address allocationId;
204204
IndexingRewardsData memory indexingRewardsData;
205-
CollectPaymentData memory collectPaymentDataBefore = _collectPaymentDataBefore(_indexer);
205+
CollectPaymentData memory collectPaymentDataBefore = _collectPaymentData(_indexer);
206206

207207
if (_paymentType == IGraphPayments.PaymentTypes.QueryFee) {
208208
paymentCollected = _handleQueryFeeCollection(_indexer, _data);
@@ -216,7 +216,7 @@ contract SubgraphServiceTest is SubgraphServiceSharedTest {
216216
// collect rewards
217217
subgraphService.collect(_indexer, _paymentType, _data);
218218

219-
CollectPaymentData memory collectPaymentDataAfter = _collectPaymentDataAfter(_indexer);
219+
CollectPaymentData memory collectPaymentDataAfter = _collectPaymentData(_indexer);
220220

221221
if (_paymentType == IGraphPayments.PaymentTypes.QueryFee) {
222222
_verifyQueryFeeCollection(
@@ -237,40 +237,23 @@ contract SubgraphServiceTest is SubgraphServiceSharedTest {
237237
}
238238
}
239239

240-
function _collectPaymentDataBefore(address _indexer) private view returns (CollectPaymentData memory) {
240+
function _collectPaymentData(
241+
address _indexer
242+
) internal view returns (CollectPaymentData memory collectPaymentData) {
241243
address rewardsDestination = subgraphService.rewardsDestination(_indexer);
242-
CollectPaymentData memory collectPaymentDataBefore;
243-
collectPaymentDataBefore.rewardsDestinationBalance = token.balanceOf(rewardsDestination);
244-
collectPaymentDataBefore.indexerProvisionBalance = staking.getProviderTokensAvailable(
244+
collectPaymentData.rewardsDestinationBalance = token.balanceOf(rewardsDestination);
245+
collectPaymentData.indexerProvisionBalance = staking.getProviderTokensAvailable(
245246
_indexer,
246247
address(subgraphService)
247248
);
248-
collectPaymentDataBefore.delegationPoolBalance = staking.getDelegatedTokensAvailable(
249+
collectPaymentData.delegationPoolBalance = staking.getDelegatedTokensAvailable(
249250
_indexer,
250251
address(subgraphService)
251252
);
252-
collectPaymentDataBefore.indexerBalance = token.balanceOf(_indexer);
253-
collectPaymentDataBefore.curationBalance = token.balanceOf(address(curation));
254-
collectPaymentDataBefore.lockedTokens = subgraphService.feesProvisionTracker(_indexer);
255-
return collectPaymentDataBefore;
256-
}
257-
258-
function _collectPaymentDataAfter(address _indexer) private view returns (CollectPaymentData memory) {
259-
CollectPaymentData memory collectPaymentDataAfter;
260-
address rewardsDestination = subgraphService.rewardsDestination(_indexer);
261-
collectPaymentDataAfter.rewardsDestinationBalance = token.balanceOf(rewardsDestination);
262-
collectPaymentDataAfter.indexerProvisionBalance = staking.getProviderTokensAvailable(
263-
_indexer,
264-
address(subgraphService)
265-
);
266-
collectPaymentDataAfter.delegationPoolBalance = staking.getDelegatedTokensAvailable(
267-
_indexer,
268-
address(subgraphService)
269-
);
270-
collectPaymentDataAfter.indexerBalance = token.balanceOf(_indexer);
271-
collectPaymentDataAfter.curationBalance = token.balanceOf(address(curation));
272-
collectPaymentDataAfter.lockedTokens = subgraphService.feesProvisionTracker(_indexer);
273-
return collectPaymentDataAfter;
253+
collectPaymentData.indexerBalance = token.balanceOf(_indexer);
254+
collectPaymentData.curationBalance = token.balanceOf(address(curation));
255+
collectPaymentData.lockedTokens = subgraphService.feesProvisionTracker(_indexer);
256+
return collectPaymentData;
274257
}
275258

276259
function _handleQueryFeeCollection(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.27;
3+
4+
import { IRecurringCollector } from "@graphprotocol/horizon/contracts/interfaces/IRecurringCollector.sol";
5+
import { IGraphPayments } from "@graphprotocol/horizon/contracts/interfaces/IGraphPayments.sol";
6+
import { PPMMath } from "@graphprotocol/horizon/contracts/libraries/PPMMath.sol";
7+
import { ISubgraphService } from "../../../contracts/interfaces/ISubgraphService.sol";
8+
9+
import { SubgraphServiceIndexingAgreementSharedTest } from "./shared.t.sol";
10+
11+
import { RecurringCollectorHelper } from "@graphprotocol/horizon/test/payments/recurring-collector/RecurringCollectorHelper.t.sol";
12+
13+
contract SubgraphServiceIndexingAgreementCancelTest is SubgraphServiceIndexingAgreementSharedTest {
14+
using PPMMath for uint256;
15+
16+
struct TestState {
17+
uint256 escrowBalance;
18+
uint256 indexerBalance;
19+
uint256 indexerTokensLocked;
20+
}
21+
22+
RecurringCollectorHelper private _recurringCollectorHelper;
23+
24+
function setUp() public override {
25+
super.setUp();
26+
27+
_recurringCollectorHelper = new RecurringCollectorHelper(recurringCollector);
28+
}
29+
30+
/*
31+
* TESTS
32+
*/
33+
34+
/* solhint-disable graph/func-name-mixedcase */
35+
function test_SubgraphService_CollectIndexingFee_Integration(
36+
SetupTestIndexerParams calldata fuzzyParams,
37+
IRecurringCollector.RecurrentCollectionVoucher memory fuzzyRCV,
38+
uint256 unboundedSignerPrivateKey,
39+
uint256 fuzzyTokensCollected
40+
) public {
41+
uint256 expectedTotalTokensCollected = bound(fuzzyTokensCollected, 1000, 1_000_000);
42+
uint256 expectedTokensLocked = stakeToFeesRatio * expectedTotalTokensCollected;
43+
uint256 expectedProtocolTokensBurnt = expectedTotalTokensCollected.mulPPMRoundUp(
44+
graphPayments.PROTOCOL_PAYMENT_CUT()
45+
);
46+
uint256 expectedIndexerTokensCollected = expectedTotalTokensCollected - expectedProtocolTokensBurnt;
47+
48+
TestIndexerParams memory params = _setupIndexer(fuzzyParams, expectedTokensLocked);
49+
50+
uint256 signerPrivateKey = boundKey(unboundedSignerPrivateKey);
51+
_setupPayerWithEscrow(fuzzyRCV.payer, signerPrivateKey, params.indexer, expectedTotalTokensCollected);
52+
53+
uint256 agreementTokensPerSecond = 1;
54+
// Create the Indexing Agreement
55+
fuzzyRCV.acceptDeadline = block.timestamp; // accept now
56+
fuzzyRCV.duration = type(uint256).max; // no expiration
57+
fuzzyRCV.maxInitialTokens = 0; // no initial payment
58+
fuzzyRCV.maxOngoingTokensPerSecond = type(uint32).max; // unlimited tokens per second
59+
fuzzyRCV.minSecondsPerCollection = 1; // 1 second between collections
60+
fuzzyRCV.maxSecondsPerCollection = type(uint32).max; // no maximum time between collections
61+
fuzzyRCV.serviceProvider = params.indexer; // service provider is the indexer
62+
fuzzyRCV.dataService = address(subgraphService); // data service is the subgraph service
63+
fuzzyRCV.metadata = abi.encode(
64+
ISubgraphService.RCVMetadata({
65+
tokensPerSecond: agreementTokensPerSecond,
66+
tokensPerEntityPerSecond: 0, // no payment for entities
67+
subgraphDeploymentId: params.subgraphDeploymentId
68+
})
69+
);
70+
71+
ISubgraphService.IndexingAgreementKey memory key = ISubgraphService.IndexingAgreementKey({
72+
payer: fuzzyRCV.payer,
73+
indexer: params.indexer,
74+
agreementId: fuzzyRCV.agreementId
75+
});
76+
77+
resetPrank(params.indexer);
78+
79+
// Accept the Indexing Agreement
80+
subgraphService.acceptIndexingAgreement(
81+
params.allocationId,
82+
_recurringCollectorHelper.generateSignedRCV(fuzzyRCV, signerPrivateKey)
83+
);
84+
85+
// Skip ahead to collection point
86+
skip(expectedTotalTokensCollected / agreementTokensPerSecond);
87+
88+
TestState memory beforeCollect = _getState(fuzzyRCV.payer, params.indexer);
89+
uint256 tokensCollected = subgraphService.collect(
90+
params.indexer,
91+
IGraphPayments.PaymentTypes.IndexingFee,
92+
abi.encode(key, 1, keccak256(abi.encodePacked("poi")))
93+
);
94+
TestState memory afterCollect = _getState(fuzzyRCV.payer, params.indexer);
95+
96+
uint256 indexerTokensCollected = afterCollect.indexerBalance - beforeCollect.indexerBalance;
97+
uint256 protocolTokensBurnt = tokensCollected - indexerTokensCollected;
98+
99+
assertEq(
100+
afterCollect.escrowBalance,
101+
beforeCollect.escrowBalance - tokensCollected,
102+
"Escrow balance should be reduced by the amount collected"
103+
);
104+
assertEq(tokensCollected, expectedTotalTokensCollected, "Total tokens collected should match");
105+
assertEq(expectedProtocolTokensBurnt, protocolTokensBurnt, "Protocol tokens burnt should match");
106+
assertEq(indexerTokensCollected, expectedIndexerTokensCollected, "Indexer tokens collected should match");
107+
assertEq(
108+
afterCollect.indexerTokensLocked,
109+
beforeCollect.indexerTokensLocked + expectedTokensLocked,
110+
"Locked tokens should match"
111+
);
112+
}
113+
114+
/* solhint-enable graph/func-name-mixedcase */
115+
116+
function _setupIndexer(
117+
SetupTestIndexerParams calldata _fuzzyParams,
118+
uint256 _tokensToAddToProvision
119+
) private returns (TestIndexerParams memory) {
120+
TestIndexerParams memory params = _setupTestIndexer(_fuzzyParams);
121+
deal({ token: address(token), to: params.indexer, give: _tokensToAddToProvision });
122+
vm.startPrank(params.indexer);
123+
_addToProvision(params.indexer, _tokensToAddToProvision);
124+
vm.stopPrank();
125+
126+
return params;
127+
}
128+
129+
function _setupPayerWithEscrow(
130+
address _payer,
131+
uint256 _signerPrivateKey,
132+
address _indexer,
133+
uint256 _escrowTokens
134+
) private {
135+
_recurringCollectorHelper.authorizeSignerWithChecks(_payer, _signerPrivateKey);
136+
137+
deal({ token: address(token), to: _payer, give: _escrowTokens });
138+
vm.startPrank(_payer);
139+
_escrow(_escrowTokens, _indexer);
140+
vm.stopPrank();
141+
}
142+
143+
function _escrow(uint256 _tokens, address _indexer) private {
144+
token.approve(address(escrow), _tokens);
145+
escrow.deposit(address(recurringCollector), _indexer, _tokens);
146+
}
147+
148+
function _getState(address _payer, address _indexer) private view returns (TestState memory) {
149+
CollectPaymentData memory collect = _collectPaymentData(_indexer);
150+
151+
return
152+
TestState({
153+
escrowBalance: escrow.getBalance(_payer, address(recurringCollector), _indexer),
154+
indexerBalance: collect.indexerBalance,
155+
indexerTokensLocked: collect.lockedTokens
156+
});
157+
}
158+
}

packages/subgraph-service/test/subgraphService/indexing-agreement/shared.t.sol

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { SubgraphServiceTest } from "../SubgraphService.t.sol";
1111
contract SubgraphServiceIndexingAgreementSharedTest is SubgraphServiceTest, Bounder {
1212
struct SetupTestIndexerParams {
1313
address indexer;
14+
string indexerLabel;
1415
uint256 unboundedTokens;
1516
uint256 unboundedAllocationPrivateKey;
1617
bytes32 subgraphDeploymentId;
@@ -104,6 +105,7 @@ contract SubgraphServiceIndexingAgreementSharedTest is SubgraphServiceTest, Boun
104105
}
105106

106107
function _setupTestIndexer(SetupTestIndexerParams calldata _params) internal returns (TestIndexerParams memory) {
108+
vm.label(_params.indexer, string.concat("indexer-", _params.indexerLabel));
107109
vm.assume(_isSafeSubgraphServiceCaller(_params.indexer) && !_registeredIndexers[_params.indexer]);
108110
_registeredIndexers[_params.indexer] = true;
109111

0 commit comments

Comments
 (0)