Skip to content

Commit 9423bfa

Browse files
fix: [TRST-L-9] Cancel agreement if over-allocated
1 parent 087f300 commit 9423bfa

File tree

6 files changed

+74
-15
lines changed

6 files changed

+74
-15
lines changed

packages/subgraph-service/contracts/SubgraphService.sol

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -574,10 +574,10 @@ contract SubgraphService is
574574
* @notice Internal function to handle closing an allocation
575575
* @dev This function is called when an allocation is closed, either by the indexer or by a third party
576576
* @param _allocationId The id of the allocation being closed
577-
* @param _stale Whether the allocation is stale or not
577+
* @param _forceClosed Whether the allocation was force closed
578578
*/
579-
function _onCloseAllocation(address _allocationId, bool _stale) internal {
580-
IndexingAgreement._getStorageManager().onCloseAllocation(_allocationId, _stale);
579+
function _onCloseAllocation(address _allocationId, bool _forceClosed) internal {
580+
IndexingAgreement._getStorageManager().onCloseAllocation(_allocationId, _forceClosed);
581581
}
582582

583583
/**
@@ -738,7 +738,20 @@ contract SubgraphService is
738738
_allocations.get(allocationId).indexer == _indexer,
739739
SubgraphServiceAllocationNotAuthorized(_indexer, allocationId)
740740
);
741-
return _presentPOI(allocationId, poi_, poiMetadata_, _delegationRatio, paymentsDestination[_indexer]);
741+
742+
(uint256 paymentCollected, bool allocationForceClosed) = _presentPOI(
743+
allocationId,
744+
poi_,
745+
poiMetadata_,
746+
_delegationRatio,
747+
paymentsDestination[_indexer]
748+
);
749+
750+
if (allocationForceClosed) {
751+
_onCloseAllocation(allocationId, true);
752+
}
753+
754+
return paymentCollected;
742755
}
743756

744757
/**

packages/subgraph-service/contracts/libraries/AllocationHandler.sol

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,14 +281,15 @@ library AllocationHandler {
281281
* @param allocationProvisionTracker The mapping of indexers to their locked tokens
282282
* @param _subgraphAllocatedTokens The mapping of subgraph deployment ids to their allocated tokens
283283
* @param params The parameters for the POI presentation
284-
* @return The amount of tokens collected
284+
* @return tokensCollected The amount of tokens collected
285+
* @return allocationForceClosed True if the allocation was automatically closed due to over-allocation, false otherwise
285286
*/
286287
function presentPOI(
287288
mapping(address allocationId => Allocation.State allocation) storage _allocations,
288289
mapping(address indexer => uint256 tokens) storage allocationProvisionTracker,
289290
mapping(bytes32 subgraphDeploymentId => uint256 tokens) storage _subgraphAllocatedTokens,
290291
PresentParams memory params
291-
) external returns (uint256) {
292+
) external returns (uint256, bool) {
292293
Allocation.State memory allocation = _allocations.get(params._allocationId);
293294
require(allocation.isOpen(), AllocationHandler.AllocationHandlerAllocationClosed(params._allocationId));
294295

@@ -358,6 +359,7 @@ library AllocationHandler {
358359
);
359360

360361
// Check if the indexer is over-allocated and force close the allocation if necessary
362+
bool allocationForceClosed;
361363
if (
362364
_isOverAllocated(
363365
allocationProvisionTracker,
@@ -366,6 +368,7 @@ library AllocationHandler {
366368
params._delegationRatio
367369
)
368370
) {
371+
allocationForceClosed = true;
369372
_closeAllocation(
370373
_allocations,
371374
allocationProvisionTracker,
@@ -376,7 +379,7 @@ library AllocationHandler {
376379
);
377380
}
378381

379-
return tokensRewards;
382+
return (tokensRewards, allocationForceClosed);
380383
}
381384

382385
/**

packages/subgraph-service/contracts/libraries/IndexingAgreement.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,10 +450,10 @@ library IndexingAgreement {
450450
*
451451
* @param self The indexing agreement storage manager
452452
* @param _allocationId The allocation ID
453-
* @param stale Whether the allocation is stale or not
453+
* @param forceClosed Whether the allocation was force closed
454454
*
455455
*/
456-
function onCloseAllocation(StorageManager storage self, address _allocationId, bool stale) external {
456+
function onCloseAllocation(StorageManager storage self, address _allocationId, bool forceClosed) external {
457457
bytes16 agreementId = self.allocationToActiveAgreementId[_allocationId];
458458
if (agreementId == bytes16(0)) {
459459
return;
@@ -469,7 +469,7 @@ library IndexingAgreement {
469469
agreementId,
470470
wrapper.agreement,
471471
wrapper.collectorAgreement,
472-
stale
472+
forceClosed
473473
? IRecurringCollector.CancelAgreementBy.ThirdParty
474474
: IRecurringCollector.CancelAgreementBy.ServiceProvider
475475
);

packages/subgraph-service/contracts/utilities/AllocationManager.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,16 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca
131131
* @param _poiMetadata The metadata associated with the POI. The data and encoding format is for off-chain components to define, this function will only emit the value in an event as-is.
132132
* @param _delegationRatio The delegation ratio to consider when locking tokens
133133
* @param _paymentsDestination The address where indexing rewards should be sent
134-
* @return The amount of tokens collected
134+
* @return tokensCollected The amount of tokens collected
135+
* @return allocationForceClosed True if the allocation was automatically closed due to over-allocation, false otherwise
135136
*/
136137
function _presentPOI(
137138
address _allocationId,
138139
bytes32 _poi,
139140
bytes memory _poiMetadata,
140141
uint32 _delegationRatio,
141142
address _paymentsDestination
142-
) internal returns (uint256) {
143+
) internal returns (uint256, bool) {
143144
return
144145
AllocationHandler.presentPOI(
145146
_allocations,

packages/subgraph-service/test/unit/shared/HorizonStakingShared.t.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ abstract contract HorizonStakingSharedTest is SubgraphBaseTest {
3838
staking.addToProvision(_indexer, address(subgraphService), _tokens);
3939
}
4040

41+
function _removeFromProvision(address _indexer, uint256 _tokens) internal {
42+
staking.thaw(_indexer, address(subgraphService), _tokens);
43+
skip(staking.getProvision(_indexer, address(subgraphService)).thawingPeriod + 1);
44+
staking.deprovision(_indexer, address(subgraphService), 0);
45+
}
46+
4147
function _delegate(address _indexer, address _verifier, uint256 _tokens, uint256 _minSharesOut) internal {
4248
staking.delegate(_indexer, _verifier, _tokens, _minSharesOut);
4349
}

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

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,35 @@ contract SubgraphServiceIndexingAgreementIntegrationTest is SubgraphServiceIndex
101101
_sharedAssert(beforeCollect, afterCollect, expectedTokens, tokensCollected);
102102
}
103103

104+
function test_SubgraphService_CollectIndexingRewards_CancelsAgreementWhenOverAllocated_Integration(
105+
Seed memory seed
106+
) public {
107+
// Setup context and indexer with active agreement
108+
Context storage ctx = _newCtx(seed);
109+
IndexerState memory indexerState = _withIndexer(ctx);
110+
(, bytes16 agreementId) = _withAcceptedIndexingAgreement(ctx, indexerState);
111+
112+
// Reduce indexer's provision to force over-allocation after collecting rewards
113+
uint256 extraTokens = indexerState.tokens - minimumProvisionTokens;
114+
vm.assume(extraTokens > 0);
115+
_removeTokensFromProvision(indexerState, extraTokens);
116+
117+
// Verify indexer will be over-allocated after presenting POI
118+
assertTrue(subgraphService.isOverAllocated(indexerState.addr));
119+
120+
// Collect indexing rewards - this should trigger allocation closure and agreement cancellation
121+
bytes memory collectData = abi.encode(indexerState.allocationId, bytes32("poi"), bytes("metadata"));
122+
resetPrank(indexerState.addr);
123+
subgraphService.collect(indexerState.addr, IGraphPayments.PaymentTypes.IndexingRewards, collectData);
124+
125+
// Verify the indexing agreement was properly cancelled
126+
IndexingAgreement.AgreementWrapper memory agreement = subgraphService.getIndexingAgreement(agreementId);
127+
assertEq(
128+
uint8(agreement.collectorAgreement.state),
129+
uint8(IRecurringCollector.AgreementState.CanceledByServiceProvider)
130+
);
131+
}
132+
104133
/* solhint-enable graph/func-name-mixedcase */
105134

106135
function _sharedSetup(
@@ -195,10 +224,17 @@ contract SubgraphServiceIndexingAgreementIntegrationTest is SubgraphServiceIndex
195224
);
196225
}
197226

198-
function _addTokensToProvision(IndexerState memory _indexerState, uint256 _tokensToAddToProvision) private {
199-
deal({ token: address(token), to: _indexerState.addr, give: _tokensToAddToProvision });
227+
function _addTokensToProvision(IndexerState memory _indexerState, uint256 _tokens) private {
228+
deal({ token: address(token), to: _indexerState.addr, give: _tokens });
229+
vm.startPrank(_indexerState.addr);
230+
_addToProvision(_indexerState.addr, _tokens);
231+
vm.stopPrank();
232+
}
233+
234+
function _removeTokensFromProvision(IndexerState memory _indexerState, uint256 _tokens) private {
235+
deal({ token: address(token), to: _indexerState.addr, give: _tokens });
200236
vm.startPrank(_indexerState.addr);
201-
_addToProvision(_indexerState.addr, _tokensToAddToProvision);
237+
_removeFromProvision(_indexerState.addr, _tokens);
202238
vm.stopPrank();
203239
}
204240

0 commit comments

Comments
 (0)