Skip to content

Commit 25d22e4

Browse files
committed
fix: added new function for accept dispute in conflict that includes tokensSlashRelated (TRST-M08)
1 parent 85de5bd commit 25d22e4

File tree

7 files changed

+281
-68
lines changed

7 files changed

+281
-68
lines changed

packages/subgraph-service/contracts/DisputeManager.sol

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -243,27 +243,46 @@ contract DisputeManager is
243243
* This function will revert if the indexer is not slashable, whether because it does not have
244244
* any stake available or the slashing percentage is configured to be zero. In those cases
245245
* a dispute must be resolved using drawDispute or rejectDispute.
246+
* This function will also revert if the dispute is in conflict, to accept a conflicting dispute
247+
* use acceptDisputeConflict.
246248
* @dev Accept a dispute with Id `disputeId`
247249
* @param disputeId Id of the dispute to be accepted
248-
* @param tokensSlash Amount of tokens to slash from the indexer
249-
* @param acceptDisputeInConflict If the dispute is in conflict, accept the conflicting dispute. Otherwise
250-
* it will be drawn automatically. Ignored if the dispute is not in conflict.
250+
* @param tokensSlash Amount of tokens to slash from the indexer for the first dispute
251251
*/
252252
function acceptDispute(
253+
bytes32 disputeId,
254+
uint256 tokensSlash
255+
) external override onlyArbitrator onlyPendingDispute(disputeId) {
256+
require(!_isDisputeInConflict(disputes[disputeId]), DisputeManagerDisputeInConflict(disputeId));
257+
Dispute storage dispute = disputes[disputeId];
258+
_acceptDispute(disputeId, dispute, tokensSlash);
259+
}
260+
261+
/**
262+
* @notice The arbitrator accepts a conflicting dispute as being valid.
263+
* This function will revert if the indexer is not slashable, whether because it does not have
264+
* any stake available or the slashing percentage is configured to be zero. In those cases
265+
* a dispute must be resolved using drawDispute.
266+
* @param disputeId Id of the dispute to be accepted
267+
* @param tokensSlash Amount of tokens to slash from the indexer for the first dispute
268+
* @param acceptDisputeInConflict Accept the conflicting dispute. Otherwise it will be drawn automatically
269+
* @param tokensSlashRelated Amount of tokens to slash from the indexer for the related dispute in case
270+
* acceptDisputeInConflict is true, otherwise it will be ignored
271+
*/
272+
function acceptDisputeConflict(
253273
bytes32 disputeId,
254274
uint256 tokensSlash,
255-
bool acceptDisputeInConflict
275+
bool acceptDisputeInConflict,
276+
uint256 tokensSlashRelated
256277
) external override onlyArbitrator onlyPendingDispute(disputeId) {
278+
require(_isDisputeInConflict(disputes[disputeId]), DisputeManagerDisputeNotInConflict(disputeId));
257279
Dispute storage dispute = disputes[disputeId];
258280
_acceptDispute(disputeId, dispute, tokensSlash);
259281

260-
if (_isDisputeInConflict(dispute)) {
261-
Dispute storage relatedDispute = disputes[dispute.relatedDisputeId];
262-
if (acceptDisputeInConflict) {
263-
_acceptDispute(dispute.relatedDisputeId, relatedDispute, tokensSlash);
264-
} else {
265-
_drawDispute(dispute.relatedDisputeId, relatedDispute);
266-
}
282+
if (acceptDisputeInConflict) {
283+
_acceptDispute(dispute.relatedDisputeId, disputes[dispute.relatedDisputeId], tokensSlashRelated);
284+
} else {
285+
_drawDispute(dispute.relatedDisputeId, disputes[dispute.relatedDisputeId]);
267286
}
268287
}
269288

packages/subgraph-service/contracts/interfaces/IDisputeManager.sol

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ interface IDisputeManager {
170170
error DisputeManagerDisputeAlreadyCreated(bytes32 disputeId);
171171
error DisputeManagerDisputePeriodNotFinished();
172172
error DisputeManagerDisputeInConflict(bytes32 disputeId);
173+
error DisputeManagerDisputeNotInConflict(bytes32 disputeId);
173174
error DisputeManagerMustAcceptRelatedDispute(bytes32 disputeId, bytes32 relatedDisputeId);
174175
error DisputeManagerIndexerNotFound(address allocationId);
175176
error DisputeManagerNonMatchingSubgraphDeployment(bytes32 subgraphDeploymentId1, bytes32 subgraphDeploymentId2);
@@ -204,7 +205,14 @@ interface IDisputeManager {
204205

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

207-
function acceptDispute(bytes32 disputeId, uint256 tokensSlash, bool acceptDisputeInConflict) external;
208+
function acceptDispute(bytes32 disputeId, uint256 tokensSlash) external;
209+
210+
function acceptDisputeConflict(
211+
bytes32 disputeId,
212+
uint256 tokensSlash,
213+
bool acceptDisputeInConflict,
214+
uint256 tokensSlashRelated
215+
) external;
208216

209217
function rejectDispute(bytes32 disputeId) external;
210218

packages/subgraph-service/test/disputeManager/DisputeManager.t.sol

Lines changed: 132 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -323,11 +323,8 @@ contract DisputeManagerTest is SubgraphServiceSharedTest {
323323
return (_disputeId1, _disputeId2);
324324
}
325325

326-
function _acceptDispute(bytes32 _disputeId, uint256 _tokensSlash, bool _acceptRelatedDispute) internal {
326+
function _acceptDispute(bytes32 _disputeId, uint256 _tokensSlash) internal {
327327
IDisputeManager.Dispute memory dispute = _getDispute(_disputeId);
328-
bool isDisputeInConflict = dispute.relatedDisputeId != bytes32(0);
329-
IDisputeManager.Dispute memory relatedDispute;
330-
if (isDisputeInConflict) relatedDispute = _getDispute(dispute.relatedDisputeId);
331328
address fisherman = dispute.fisherman;
332329
uint256 fishermanPreviousBalance = token.balanceOf(fisherman);
333330
uint256 indexerTokensAvailable = staking.getProviderTokensAvailable(dispute.indexer, address(subgraphService));
@@ -343,33 +340,13 @@ contract DisputeManagerTest is SubgraphServiceSharedTest {
343340
dispute.deposit + fishermanReward
344341
);
345342

346-
if (isDisputeInConflict) {
347-
if (_acceptRelatedDispute) {
348-
emit IDisputeManager.DisputeAccepted(
349-
dispute.relatedDisputeId,
350-
relatedDispute.indexer,
351-
relatedDispute.fisherman,
352-
relatedDispute.deposit
353-
);
354-
} else {
355-
emit IDisputeManager.DisputeRejected(
356-
dispute.relatedDisputeId,
357-
relatedDispute.indexer,
358-
relatedDispute.fisherman,
359-
relatedDispute.deposit
360-
);
361-
}
362-
}
363-
364343
// Accept the dispute
365-
disputeManager.acceptDispute(_disputeId, _tokensSlash, _acceptRelatedDispute);
344+
disputeManager.acceptDispute(_disputeId, _tokensSlash);
366345

367346
// Check fisherman's got their reward and their deposit (if any) back
368347
uint256 fishermanExpectedBalance = fishermanPreviousBalance +
369348
fishermanReward +
370-
disputeDeposit +
371-
(isDisputeInConflict ? relatedDispute.deposit : 0) +
372-
((isDisputeInConflict && _acceptRelatedDispute) ? fishermanReward : 0);
349+
disputeDeposit;
373350
assertEq(
374351
token.balanceOf(fisherman),
375352
fishermanExpectedBalance,
@@ -378,11 +355,10 @@ contract DisputeManagerTest is SubgraphServiceSharedTest {
378355

379356
// Check indexer was slashed by the correct amount
380357
uint256 expectedIndexerTokensAvailable;
381-
uint256 tokensToSlash = (isDisputeInConflict && _acceptRelatedDispute) ? _tokensSlash * 2 : _tokensSlash;
382-
if (tokensToSlash > indexerTokensAvailable) {
358+
if (_tokensSlash > indexerTokensAvailable) {
383359
expectedIndexerTokensAvailable = 0;
384360
} else {
385-
expectedIndexerTokensAvailable = indexerTokensAvailable - tokensToSlash;
361+
expectedIndexerTokensAvailable = indexerTokensAvailable - _tokensSlash;
386362
}
387363
assertEq(
388364
staking.getProviderTokensAvailable(dispute.indexer, address(subgraphService)),
@@ -397,18 +373,137 @@ contract DisputeManagerTest is SubgraphServiceSharedTest {
397373
uint8(IDisputeManager.DisputeStatus.Accepted),
398374
"Dispute status should be accepted"
399375
);
376+
}
400377

401-
// If there's a related dispute, check it
402-
if (isDisputeInConflict) {
403-
relatedDispute = _getDispute(dispute.relatedDisputeId);
378+
struct FishermanParams {
379+
address fisherman;
380+
uint256 previousBalance;
381+
uint256 disputeDeposit;
382+
uint256 relatedDisputeDeposit;
383+
uint256 rewardPercentage;
384+
uint256 rewardFirstDispute;
385+
uint256 rewardRelatedDispute;
386+
uint256 totalReward;
387+
uint256 expectedBalance;
388+
}
389+
390+
function _acceptDisputeConflict(bytes32 _disputeId, uint256 _tokensSlash, bool _acceptRelatedDispute, uint256 _tokensRelatedSlash) internal {
391+
IDisputeManager.Dispute memory dispute = _getDispute(_disputeId);
392+
IDisputeManager.Dispute memory relatedDispute = _getDispute(dispute.relatedDisputeId);
393+
uint256 indexerTokensAvailable = staking.getProviderTokensAvailable(dispute.indexer, address(subgraphService));
394+
uint256 relatedIndexerTokensAvailable = staking.getProviderTokensAvailable(relatedDispute.indexer, address(subgraphService));
395+
396+
FishermanParams memory params;
397+
params.fisherman = dispute.fisherman;
398+
params.previousBalance = token.balanceOf(params.fisherman);
399+
params.disputeDeposit = dispute.deposit;
400+
params.relatedDisputeDeposit = relatedDispute.deposit;
401+
params.rewardPercentage = disputeManager.fishermanRewardCut();
402+
params.rewardFirstDispute = _tokensSlash.mulPPM(params.rewardPercentage);
403+
params.rewardRelatedDispute = (_acceptRelatedDispute) ? _tokensRelatedSlash.mulPPM(params.rewardPercentage) : 0;
404+
params.totalReward = params.rewardFirstDispute + params.rewardRelatedDispute;
405+
406+
vm.expectEmit(address(disputeManager));
407+
emit IDisputeManager.DisputeAccepted(
408+
_disputeId,
409+
dispute.indexer,
410+
params.fisherman,
411+
params.disputeDeposit + params.rewardFirstDispute
412+
);
413+
414+
if (_acceptRelatedDispute) {
415+
emit IDisputeManager.DisputeAccepted(
416+
dispute.relatedDisputeId,
417+
relatedDispute.indexer,
418+
relatedDispute.fisherman,
419+
relatedDispute.deposit + params.rewardRelatedDispute
420+
);
421+
} else {
422+
emit IDisputeManager.DisputeDrawn(
423+
dispute.relatedDisputeId,
424+
relatedDispute.indexer,
425+
relatedDispute.fisherman,
426+
relatedDispute.deposit
427+
);
428+
}
429+
430+
// Accept the dispute
431+
disputeManager.acceptDisputeConflict(_disputeId, _tokensSlash, _acceptRelatedDispute, _tokensRelatedSlash);
432+
433+
// Check fisherman's got their reward and their deposit back
434+
params.expectedBalance = params.previousBalance +
435+
params.totalReward +
436+
params.disputeDeposit +
437+
params.relatedDisputeDeposit;
438+
assertEq(
439+
token.balanceOf(params.fisherman),
440+
params.expectedBalance,
441+
"Fisherman should get their reward and deposit back"
442+
);
443+
444+
// If both disputes are for the same indexer, check that the indexer was slashed by the correct amount
445+
if (dispute.indexer == relatedDispute.indexer) {
446+
uint256 tokensToSlash = (_acceptRelatedDispute) ? _tokensSlash + _tokensRelatedSlash : _tokensSlash;
447+
uint256 expectedIndexerTokensAvailable;
448+
if (tokensToSlash > indexerTokensAvailable) {
449+
expectedIndexerTokensAvailable = 0;
450+
} else {
451+
expectedIndexerTokensAvailable = indexerTokensAvailable - tokensToSlash;
452+
}
404453
assertEq(
405-
uint8(relatedDispute.status),
406-
_acceptRelatedDispute
407-
? uint8(IDisputeManager.DisputeStatus.Accepted)
408-
: uint8(IDisputeManager.DisputeStatus.Drawn),
409-
"Related dispute status should be drawn"
454+
staking.getProviderTokensAvailable(dispute.indexer, address(subgraphService)),
455+
expectedIndexerTokensAvailable,
456+
"Indexer should be slashed by the correct amount"
410457
);
458+
} else {
459+
// Check indexer for first dispute was slashed by the correct amount
460+
uint256 expectedIndexerTokensAvailable;
461+
uint256 tokensToSlash = (_acceptRelatedDispute) ? _tokensSlash : _tokensSlash;
462+
if (tokensToSlash > indexerTokensAvailable) {
463+
expectedIndexerTokensAvailable = 0;
464+
} else {
465+
expectedIndexerTokensAvailable = indexerTokensAvailable - tokensToSlash;
466+
}
467+
assertEq(
468+
staking.getProviderTokensAvailable(dispute.indexer, address(subgraphService)),
469+
expectedIndexerTokensAvailable,
470+
"Indexer should be slashed by the correct amount"
471+
);
472+
473+
// Check indexer for related dispute was slashed by the correct amount if it was accepted
474+
if (_acceptRelatedDispute) {
475+
uint256 expectedRelatedIndexerTokensAvailable;
476+
if (_tokensRelatedSlash > relatedIndexerTokensAvailable) {
477+
expectedRelatedIndexerTokensAvailable = 0;
478+
} else {
479+
expectedRelatedIndexerTokensAvailable = relatedIndexerTokensAvailable - _tokensRelatedSlash;
480+
}
481+
assertEq(
482+
staking.getProviderTokensAvailable(relatedDispute.indexer, address(subgraphService)),
483+
expectedRelatedIndexerTokensAvailable,
484+
"Indexer should be slashed by the correct amount"
485+
);
486+
}
411487
}
488+
489+
490+
// Check dispute status
491+
dispute = _getDispute(_disputeId);
492+
assertEq(
493+
uint8(dispute.status),
494+
uint8(IDisputeManager.DisputeStatus.Accepted),
495+
"Dispute status should be accepted"
496+
);
497+
498+
// If there's a related dispute, check it
499+
relatedDispute = _getDispute(dispute.relatedDisputeId);
500+
assertEq(
501+
uint8(relatedDispute.status),
502+
_acceptRelatedDispute
503+
? uint8(IDisputeManager.DisputeStatus.Accepted)
504+
: uint8(IDisputeManager.DisputeStatus.Drawn),
505+
"Related dispute status should be drawn"
506+
);
412507
}
413508

414509
function _drawDispute(bytes32 _disputeId) internal {

packages/subgraph-service/test/disputeManager/disputes/disputes.t.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ contract DisputeManagerDisputeTest is DisputeManagerTest {
2727
IDisputeManager.DisputeManagerInvalidDispute.selector,
2828
disputeID
2929
));
30-
disputeManager.acceptDispute(disputeID, tokensSlash, false);
30+
disputeManager.acceptDispute(disputeID, tokensSlash);
3131
}
3232

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

4646
function test_Dispute_Reject_RevertIf_DisputeDoesNotExist(

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ contract DisputeManagerIndexingAcceptDisputeTest is DisputeManagerTest {
2424
bytes32 disputeID = _createIndexingDispute(allocationID, bytes32("POI1"));
2525

2626
resetPrank(users.arbitrator);
27-
_acceptDispute(disputeID, tokensSlash, false);
27+
_acceptDispute(disputeID, tokensSlash);
2828
}
2929

3030
function test_Indexing_Accept_Dispute_RevertWhen_SubgraphServiceNotSet(
@@ -41,7 +41,7 @@ contract DisputeManagerIndexingAcceptDisputeTest is DisputeManagerTest {
4141
_setStorage_SubgraphService(address(0));
4242

4343
vm.expectRevert(abi.encodeWithSelector(IDisputeManager.DisputeManagerSubgraphServiceNotSet.selector));
44-
disputeManager.acceptDispute(disputeID, tokensSlash, false);
44+
disputeManager.acceptDispute(disputeID, tokensSlash);
4545
}
4646

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

5656
resetPrank(users.arbitrator);
57-
_acceptDispute(disputeID, tokensSlash, true);
57+
_acceptDispute(disputeID, tokensSlash);
5858
}
5959

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

7575
function test_Indexing_Accept_RevertWhen_SlashingOverMaxSlashPercentage(
@@ -89,6 +89,6 @@ contract DisputeManagerIndexingAcceptDisputeTest is DisputeManagerTest {
8989
maxTokensToSlash
9090
);
9191
vm.expectRevert(expectedError);
92-
disputeManager.acceptDispute(disputeID, tokensSlash, false);
92+
disputeManager.acceptDispute(disputeID, tokensSlash);
9393
}
9494
}

0 commit comments

Comments
 (0)