Skip to content

Commit

Permalink
fix: added new function for accept dispute in conflict that includes …
Browse files Browse the repository at this point in the history
…tokensSlashRelated (TRST-M08)
  • Loading branch information
Maikol committed Dec 13, 2024
1 parent 85de5bd commit 25d22e4
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 68 deletions.
41 changes: 30 additions & 11 deletions packages/subgraph-service/contracts/DisputeManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -243,27 +243,46 @@ contract DisputeManager is
* This function will revert if the indexer is not slashable, whether because it does not have
* any stake available or the slashing percentage is configured to be zero. In those cases
* a dispute must be resolved using drawDispute or rejectDispute.
* This function will also revert if the dispute is in conflict, to accept a conflicting dispute
* use acceptDisputeConflict.
* @dev Accept a dispute with Id `disputeId`
* @param disputeId Id of the dispute to be accepted
* @param tokensSlash Amount of tokens to slash from the indexer
* @param acceptDisputeInConflict If the dispute is in conflict, accept the conflicting dispute. Otherwise
* it will be drawn automatically. Ignored if the dispute is not in conflict.
* @param tokensSlash Amount of tokens to slash from the indexer for the first dispute
*/
function acceptDispute(
bytes32 disputeId,
uint256 tokensSlash
) external override onlyArbitrator onlyPendingDispute(disputeId) {
require(!_isDisputeInConflict(disputes[disputeId]), DisputeManagerDisputeInConflict(disputeId));
Dispute storage dispute = disputes[disputeId];
_acceptDispute(disputeId, dispute, tokensSlash);
}

/**
* @notice The arbitrator accepts a conflicting dispute as being valid.
* This function will revert if the indexer is not slashable, whether because it does not have
* any stake available or the slashing percentage is configured to be zero. In those cases
* a dispute must be resolved using drawDispute.
* @param disputeId Id of the dispute to be accepted
* @param tokensSlash Amount of tokens to slash from the indexer for the first dispute
* @param acceptDisputeInConflict Accept the conflicting dispute. Otherwise it will be drawn automatically
* @param tokensSlashRelated Amount of tokens to slash from the indexer for the related dispute in case
* acceptDisputeInConflict is true, otherwise it will be ignored
*/
function acceptDisputeConflict(
bytes32 disputeId,
uint256 tokensSlash,
bool acceptDisputeInConflict
bool acceptDisputeInConflict,
uint256 tokensSlashRelated
) external override onlyArbitrator onlyPendingDispute(disputeId) {
require(_isDisputeInConflict(disputes[disputeId]), DisputeManagerDisputeNotInConflict(disputeId));
Dispute storage dispute = disputes[disputeId];
_acceptDispute(disputeId, dispute, tokensSlash);

if (_isDisputeInConflict(dispute)) {
Dispute storage relatedDispute = disputes[dispute.relatedDisputeId];
if (acceptDisputeInConflict) {
_acceptDispute(dispute.relatedDisputeId, relatedDispute, tokensSlash);
} else {
_drawDispute(dispute.relatedDisputeId, relatedDispute);
}
if (acceptDisputeInConflict) {
_acceptDispute(dispute.relatedDisputeId, disputes[dispute.relatedDisputeId], tokensSlashRelated);
} else {
_drawDispute(dispute.relatedDisputeId, disputes[dispute.relatedDisputeId]);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ interface IDisputeManager {
error DisputeManagerDisputeAlreadyCreated(bytes32 disputeId);
error DisputeManagerDisputePeriodNotFinished();
error DisputeManagerDisputeInConflict(bytes32 disputeId);
error DisputeManagerDisputeNotInConflict(bytes32 disputeId);
error DisputeManagerMustAcceptRelatedDispute(bytes32 disputeId, bytes32 relatedDisputeId);
error DisputeManagerIndexerNotFound(address allocationId);
error DisputeManagerNonMatchingSubgraphDeployment(bytes32 subgraphDeploymentId1, bytes32 subgraphDeploymentId2);
Expand Down Expand Up @@ -204,7 +205,14 @@ interface IDisputeManager {

function createIndexingDispute(address allocationId, bytes32 poi) external returns (bytes32);

function acceptDispute(bytes32 disputeId, uint256 tokensSlash, bool acceptDisputeInConflict) external;
function acceptDispute(bytes32 disputeId, uint256 tokensSlash) external;

function acceptDisputeConflict(
bytes32 disputeId,
uint256 tokensSlash,
bool acceptDisputeInConflict,
uint256 tokensSlashRelated
) external;

function rejectDispute(bytes32 disputeId) external;

Expand Down
169 changes: 132 additions & 37 deletions packages/subgraph-service/test/disputeManager/DisputeManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -323,11 +323,8 @@ contract DisputeManagerTest is SubgraphServiceSharedTest {
return (_disputeId1, _disputeId2);
}

function _acceptDispute(bytes32 _disputeId, uint256 _tokensSlash, bool _acceptRelatedDispute) internal {
function _acceptDispute(bytes32 _disputeId, uint256 _tokensSlash) internal {
IDisputeManager.Dispute memory dispute = _getDispute(_disputeId);
bool isDisputeInConflict = dispute.relatedDisputeId != bytes32(0);
IDisputeManager.Dispute memory relatedDispute;
if (isDisputeInConflict) relatedDispute = _getDispute(dispute.relatedDisputeId);
address fisherman = dispute.fisherman;
uint256 fishermanPreviousBalance = token.balanceOf(fisherman);
uint256 indexerTokensAvailable = staking.getProviderTokensAvailable(dispute.indexer, address(subgraphService));
Expand All @@ -343,33 +340,13 @@ contract DisputeManagerTest is SubgraphServiceSharedTest {
dispute.deposit + fishermanReward
);

if (isDisputeInConflict) {
if (_acceptRelatedDispute) {
emit IDisputeManager.DisputeAccepted(
dispute.relatedDisputeId,
relatedDispute.indexer,
relatedDispute.fisherman,
relatedDispute.deposit
);
} else {
emit IDisputeManager.DisputeRejected(
dispute.relatedDisputeId,
relatedDispute.indexer,
relatedDispute.fisherman,
relatedDispute.deposit
);
}
}

// Accept the dispute
disputeManager.acceptDispute(_disputeId, _tokensSlash, _acceptRelatedDispute);
disputeManager.acceptDispute(_disputeId, _tokensSlash);

// Check fisherman's got their reward and their deposit (if any) back
uint256 fishermanExpectedBalance = fishermanPreviousBalance +
fishermanReward +
disputeDeposit +
(isDisputeInConflict ? relatedDispute.deposit : 0) +
((isDisputeInConflict && _acceptRelatedDispute) ? fishermanReward : 0);
disputeDeposit;
assertEq(
token.balanceOf(fisherman),
fishermanExpectedBalance,
Expand All @@ -378,11 +355,10 @@ contract DisputeManagerTest is SubgraphServiceSharedTest {

// Check indexer was slashed by the correct amount
uint256 expectedIndexerTokensAvailable;
uint256 tokensToSlash = (isDisputeInConflict && _acceptRelatedDispute) ? _tokensSlash * 2 : _tokensSlash;
if (tokensToSlash > indexerTokensAvailable) {
if (_tokensSlash > indexerTokensAvailable) {
expectedIndexerTokensAvailable = 0;
} else {
expectedIndexerTokensAvailable = indexerTokensAvailable - tokensToSlash;
expectedIndexerTokensAvailable = indexerTokensAvailable - _tokensSlash;
}
assertEq(
staking.getProviderTokensAvailable(dispute.indexer, address(subgraphService)),
Expand All @@ -397,18 +373,137 @@ contract DisputeManagerTest is SubgraphServiceSharedTest {
uint8(IDisputeManager.DisputeStatus.Accepted),
"Dispute status should be accepted"
);
}

// If there's a related dispute, check it
if (isDisputeInConflict) {
relatedDispute = _getDispute(dispute.relatedDisputeId);
struct FishermanParams {
address fisherman;
uint256 previousBalance;
uint256 disputeDeposit;
uint256 relatedDisputeDeposit;
uint256 rewardPercentage;
uint256 rewardFirstDispute;
uint256 rewardRelatedDispute;
uint256 totalReward;
uint256 expectedBalance;
}

function _acceptDisputeConflict(bytes32 _disputeId, uint256 _tokensSlash, bool _acceptRelatedDispute, uint256 _tokensRelatedSlash) internal {
IDisputeManager.Dispute memory dispute = _getDispute(_disputeId);
IDisputeManager.Dispute memory relatedDispute = _getDispute(dispute.relatedDisputeId);
uint256 indexerTokensAvailable = staking.getProviderTokensAvailable(dispute.indexer, address(subgraphService));
uint256 relatedIndexerTokensAvailable = staking.getProviderTokensAvailable(relatedDispute.indexer, address(subgraphService));

FishermanParams memory params;
params.fisherman = dispute.fisherman;
params.previousBalance = token.balanceOf(params.fisherman);
params.disputeDeposit = dispute.deposit;
params.relatedDisputeDeposit = relatedDispute.deposit;
params.rewardPercentage = disputeManager.fishermanRewardCut();
params.rewardFirstDispute = _tokensSlash.mulPPM(params.rewardPercentage);
params.rewardRelatedDispute = (_acceptRelatedDispute) ? _tokensRelatedSlash.mulPPM(params.rewardPercentage) : 0;
params.totalReward = params.rewardFirstDispute + params.rewardRelatedDispute;

vm.expectEmit(address(disputeManager));
emit IDisputeManager.DisputeAccepted(
_disputeId,
dispute.indexer,
params.fisherman,
params.disputeDeposit + params.rewardFirstDispute
);

if (_acceptRelatedDispute) {
emit IDisputeManager.DisputeAccepted(
dispute.relatedDisputeId,
relatedDispute.indexer,
relatedDispute.fisherman,
relatedDispute.deposit + params.rewardRelatedDispute
);
} else {
emit IDisputeManager.DisputeDrawn(
dispute.relatedDisputeId,
relatedDispute.indexer,
relatedDispute.fisherman,
relatedDispute.deposit
);
}

// Accept the dispute
disputeManager.acceptDisputeConflict(_disputeId, _tokensSlash, _acceptRelatedDispute, _tokensRelatedSlash);

// Check fisherman's got their reward and their deposit back
params.expectedBalance = params.previousBalance +
params.totalReward +
params.disputeDeposit +
params.relatedDisputeDeposit;
assertEq(
token.balanceOf(params.fisherman),
params.expectedBalance,
"Fisherman should get their reward and deposit back"
);

// If both disputes are for the same indexer, check that the indexer was slashed by the correct amount
if (dispute.indexer == relatedDispute.indexer) {
uint256 tokensToSlash = (_acceptRelatedDispute) ? _tokensSlash + _tokensRelatedSlash : _tokensSlash;
uint256 expectedIndexerTokensAvailable;
if (tokensToSlash > indexerTokensAvailable) {
expectedIndexerTokensAvailable = 0;
} else {
expectedIndexerTokensAvailable = indexerTokensAvailable - tokensToSlash;
}
assertEq(
uint8(relatedDispute.status),
_acceptRelatedDispute
? uint8(IDisputeManager.DisputeStatus.Accepted)
: uint8(IDisputeManager.DisputeStatus.Drawn),
"Related dispute status should be drawn"
staking.getProviderTokensAvailable(dispute.indexer, address(subgraphService)),
expectedIndexerTokensAvailable,
"Indexer should be slashed by the correct amount"
);
} else {
// Check indexer for first dispute was slashed by the correct amount
uint256 expectedIndexerTokensAvailable;
uint256 tokensToSlash = (_acceptRelatedDispute) ? _tokensSlash : _tokensSlash;
if (tokensToSlash > indexerTokensAvailable) {
expectedIndexerTokensAvailable = 0;
} else {
expectedIndexerTokensAvailable = indexerTokensAvailable - tokensToSlash;
}
assertEq(
staking.getProviderTokensAvailable(dispute.indexer, address(subgraphService)),
expectedIndexerTokensAvailable,
"Indexer should be slashed by the correct amount"
);

// Check indexer for related dispute was slashed by the correct amount if it was accepted
if (_acceptRelatedDispute) {
uint256 expectedRelatedIndexerTokensAvailable;
if (_tokensRelatedSlash > relatedIndexerTokensAvailable) {
expectedRelatedIndexerTokensAvailable = 0;
} else {
expectedRelatedIndexerTokensAvailable = relatedIndexerTokensAvailable - _tokensRelatedSlash;
}
assertEq(
staking.getProviderTokensAvailable(relatedDispute.indexer, address(subgraphService)),
expectedRelatedIndexerTokensAvailable,
"Indexer should be slashed by the correct amount"
);
}
}


// Check dispute status
dispute = _getDispute(_disputeId);
assertEq(
uint8(dispute.status),
uint8(IDisputeManager.DisputeStatus.Accepted),
"Dispute status should be accepted"
);

// If there's a related dispute, check it
relatedDispute = _getDispute(dispute.relatedDisputeId);
assertEq(
uint8(relatedDispute.status),
_acceptRelatedDispute
? uint8(IDisputeManager.DisputeStatus.Accepted)
: uint8(IDisputeManager.DisputeStatus.Drawn),
"Related dispute status should be drawn"
);
}

function _drawDispute(bytes32 _disputeId) internal {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ contract DisputeManagerDisputeTest is DisputeManagerTest {
IDisputeManager.DisputeManagerInvalidDispute.selector,
disputeID
));
disputeManager.acceptDispute(disputeID, tokensSlash, false);
disputeManager.acceptDispute(disputeID, tokensSlash);
}

function test_Dispute_Accept_RevertIf_SlashZeroTokens(
Expand All @@ -40,7 +40,7 @@ contract DisputeManagerDisputeTest is DisputeManagerTest {
resetPrank(users.arbitrator);
uint256 maxTokensToSlash = uint256(maxSlashingPercentage).mulPPM(tokens);
vm.expectRevert(abi.encodeWithSelector(IDisputeManager.DisputeManagerInvalidTokensSlash.selector, 0, maxTokensToSlash));
disputeManager.acceptDispute(disputeID, 0, false);
disputeManager.acceptDispute(disputeID, 0);
}

function test_Dispute_Reject_RevertIf_DisputeDoesNotExist(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract DisputeManagerIndexingAcceptDisputeTest is DisputeManagerTest {
bytes32 disputeID = _createIndexingDispute(allocationID, bytes32("POI1"));

resetPrank(users.arbitrator);
_acceptDispute(disputeID, tokensSlash, false);
_acceptDispute(disputeID, tokensSlash);
}

function test_Indexing_Accept_Dispute_RevertWhen_SubgraphServiceNotSet(
Expand All @@ -41,7 +41,7 @@ contract DisputeManagerIndexingAcceptDisputeTest is DisputeManagerTest {
_setStorage_SubgraphService(address(0));

vm.expectRevert(abi.encodeWithSelector(IDisputeManager.DisputeManagerSubgraphServiceNotSet.selector));
disputeManager.acceptDispute(disputeID, tokensSlash, false);
disputeManager.acceptDispute(disputeID, tokensSlash);
}

function test_Indexing_Accept_Dispute_OptParam(
Expand All @@ -54,7 +54,7 @@ contract DisputeManagerIndexingAcceptDisputeTest is DisputeManagerTest {
bytes32 disputeID = _createIndexingDispute(allocationID, bytes32("POI1"));

resetPrank(users.arbitrator);
_acceptDispute(disputeID, tokensSlash, true);
_acceptDispute(disputeID, tokensSlash);
}

function test_Indexing_Accept_RevertIf_CallerIsNotArbitrator(
Expand All @@ -69,7 +69,7 @@ contract DisputeManagerIndexingAcceptDisputeTest is DisputeManagerTest {
// attempt to accept dispute as fisherman
resetPrank(users.fisherman);
vm.expectRevert(abi.encodeWithSelector(IDisputeManager.DisputeManagerNotArbitrator.selector));
disputeManager.acceptDispute(disputeID, tokensSlash, false);
disputeManager.acceptDispute(disputeID, tokensSlash);
}

function test_Indexing_Accept_RevertWhen_SlashingOverMaxSlashPercentage(
Expand All @@ -89,6 +89,6 @@ contract DisputeManagerIndexingAcceptDisputeTest is DisputeManagerTest {
maxTokensToSlash
);
vm.expectRevert(expectedError);
disputeManager.acceptDispute(disputeID, tokensSlash, false);
disputeManager.acceptDispute(disputeID, tokensSlash);
}
}
Loading

0 comments on commit 25d22e4

Please sign in to comment.