Skip to content

Commit d89440a

Browse files
committed
fix: consider delegation when deciding if an indexer is disputable
Signed-off-by: Tomás Migone <tomas@edgeandnode.com>
1 parent 97dceb1 commit d89440a

File tree

3 files changed

+57
-22
lines changed

3 files changed

+57
-22
lines changed

packages/subgraph-service/contracts/DisputeManager.sol

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -344,11 +344,7 @@ contract DisputeManager is
344344

345345
/// @inheritdoc IDisputeManager
346346
function getStakeSnapshot(address indexer) external view override returns (uint256) {
347-
IHorizonStaking.Provision memory provision = _graphStaking().getProvision(
348-
indexer,
349-
address(_getSubgraphService())
350-
);
351-
return _getStakeSnapshot(indexer, provision.tokens);
347+
return _getStakeSnapshot(indexer);
352348
}
353349

354350
/// @inheritdoc IDisputeManager
@@ -398,13 +394,6 @@ contract DisputeManager is
398394
// Get the indexer that signed the attestation
399395
address indexer = getAttestationIndexer(_attestation);
400396

401-
// The indexer is disputable
402-
IHorizonStaking.Provision memory provision = _graphStaking().getProvision(
403-
indexer,
404-
address(_getSubgraphService())
405-
);
406-
require(provision.tokens != 0, DisputeManagerZeroTokens());
407-
408397
// Create a disputeId
409398
bytes32 disputeId = keccak256(
410399
abi.encodePacked(
@@ -419,8 +408,11 @@ contract DisputeManager is
419408
// Only one dispute at a time
420409
require(!isDisputeCreated(disputeId), DisputeManagerDisputeAlreadyCreated(disputeId));
421410

411+
// The indexer is disputable
412+
uint256 stakeSnapshot = _getStakeSnapshot(indexer);
413+
require(stakeSnapshot != 0, DisputeManagerZeroTokens());
414+
422415
// Store dispute
423-
uint256 stakeSnapshot = _getStakeSnapshot(indexer, provision.tokens);
424416
uint256 cancellableAt = block.timestamp + disputePeriod;
425417
disputes[disputeId] = Dispute(
426418
indexer,
@@ -477,11 +469,10 @@ contract DisputeManager is
477469
require(indexer != address(0), DisputeManagerIndexerNotFound(_allocationId));
478470

479471
// The indexer must be disputable
480-
IHorizonStaking.Provision memory provision = _graphStaking().getProvision(indexer, address(subgraphService_));
481-
require(provision.tokens != 0, DisputeManagerZeroTokens());
472+
uint256 stakeSnapshot = _getStakeSnapshot(indexer);
473+
require(stakeSnapshot != 0, DisputeManagerZeroTokens());
482474

483475
// Store dispute
484-
uint256 stakeSnapshot = _getStakeSnapshot(indexer, provision.tokens);
485476
uint256 cancellableAt = block.timestamp + disputePeriod;
486477
disputes[disputeId] = Dispute(
487478
alloc.indexer,
@@ -691,18 +682,19 @@ contract DisputeManager is
691682
* @dev A few considerations:
692683
* - We include both indexer and delegators stake.
693684
* - Thawing stake is not excluded from the snapshot.
694-
* - Delegators stake is capped at the delegation ratio to prevent delegators from inflating the snapshot
695-
* to increase the indexer slash amount.
696685
*
697686
* Note that the snapshot can be inflated by delegators front-running the dispute creation with a delegation
698687
* to the indexer. Given the snapshot is a cap, the dispute outcome is uncertain and considering the cost of capital
699688
* and slashing risk, this is not a concern.
700689
* @param _indexer Indexer address
701-
* @param _indexerStake Indexer's stake
702690
* @return Total stake snapshot
703691
*/
704-
function _getStakeSnapshot(address _indexer, uint256 _indexerStake) private view returns (uint256) {
705-
uint256 delegatorsStake = _graphStaking().getDelegationPool(_indexer, address(_getSubgraphService())).tokens;
706-
return _indexerStake + delegatorsStake;
692+
function _getStakeSnapshot(address _indexer) private view returns (uint256) {
693+
address subgraphService = address(_getSubgraphService());
694+
695+
IHorizonStaking.Provision memory provision = _graphStaking().getProvision(_indexer, subgraphService);
696+
uint256 delegatorsStake = _graphStaking().getDelegationPool(_indexer, subgraphService).tokens;
697+
698+
return provision.tokens + delegatorsStake;
707699
}
708700
}

packages/subgraph-service/test/unit/disputeManager/disputes/indexing/create.t.sol

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,24 @@ contract DisputeManagerIndexingCreateDisputeTest is DisputeManagerTest {
157157
vm.expectRevert(abi.encodeWithSelector(IDisputeManager.DisputeManagerZeroTokens.selector));
158158
disputeManager.createIndexingDispute(allocationID, bytes32("POI1"), block.number);
159159
}
160+
161+
function test_Indexing_Create_DontRevertIf_IndexerIsBelowStake_WithDelegation(uint256 tokens, uint256 delegationTokens) public useIndexer useAllocation(tokens) {
162+
// Close allocation
163+
bytes memory data = abi.encode(allocationID);
164+
_stopService(users.indexer, data);
165+
// Thaw, deprovision and unstake
166+
address subgraphDataServiceAddress = address(subgraphService);
167+
_thawDeprovisionAndUnstake(users.indexer, subgraphDataServiceAddress, tokens);
168+
169+
delegationTokens = bound(delegationTokens, 1 ether, 100_000_000 ether);
170+
171+
resetPrank(users.delegator);
172+
token.approve(address(staking), delegationTokens);
173+
staking.delegate(users.indexer, address(subgraphService), delegationTokens, 0);
174+
175+
// create dispute
176+
resetPrank(users.fisherman);
177+
token.approve(address(disputeManager), tokens);
178+
_createIndexingDispute(allocationID, bytes32("POI1"), block.number);
179+
}
160180
}

packages/subgraph-service/test/unit/disputeManager/disputes/query/create.t.sol

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,27 @@ contract DisputeManagerQueryCreateDisputeTest is DisputeManagerTest {
155155
vm.expectRevert(abi.encodeWithSelector(IDisputeManager.DisputeManagerZeroTokens.selector));
156156
disputeManager.createQueryDispute(attestationData);
157157
}
158+
159+
function test_Query_Create_DontRevertIf_IndexerIsBelowStake_WithDelegation(uint256 tokens, uint256 delegationTokens) public useIndexer useAllocation(tokens) {
160+
// Close allocation
161+
bytes memory data = abi.encode(allocationID);
162+
_stopService(users.indexer, data);
163+
// Thaw, deprovision and unstake
164+
address subgraphDataServiceAddress = address(subgraphService);
165+
_thawDeprovisionAndUnstake(users.indexer, subgraphDataServiceAddress, tokens);
166+
167+
delegationTokens = bound(delegationTokens, 1 ether, 100_000_000 ether);
168+
169+
resetPrank(users.delegator);
170+
token.approve(address(staking), delegationTokens);
171+
staking.delegate(users.indexer, address(subgraphService), delegationTokens, 0);
172+
173+
// create dispute
174+
resetPrank(users.fisherman);
175+
token.approve(address(disputeManager), tokens);
176+
177+
Attestation.Receipt memory receipt = _createAttestationReceipt(requestCID, responseCID, subgraphDeploymentId);
178+
bytes memory attestationData = _createAtestationData(receipt, allocationIDPrivateKey);
179+
_createQueryDispute(attestationData);
180+
}
158181
}

0 commit comments

Comments
 (0)