Skip to content

Commit 4d75947

Browse files
committed
fix: dispute manager refactor (TRST-M07 / TRST-M08)
Signed-off-by: Tomás Migone <[email protected]>
1 parent 0c0d090 commit 4d75947

File tree

11 files changed

+409
-199
lines changed

11 files changed

+409
-199
lines changed

packages/subgraph-service/contracts/DisputeManager.sol

Lines changed: 84 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ contract DisputeManager is
5959
// Maximum value for fisherman reward cut in PPM
6060
uint32 public constant MAX_FISHERMAN_REWARD_CUT = 500000;
6161

62+
// Minimum value for dispute deposit
63+
uint256 public constant MIN_DISPUTE_DEPOSIT = 1e18; // 1 GRT
64+
6265
// -- Modifiers --
6366

6467
/**
@@ -140,7 +143,7 @@ contract DisputeManager is
140143
*/
141144
function createIndexingDispute(address allocationId, bytes32 poi) external override returns (bytes32) {
142145
// Get funds from fisherman
143-
_pullFishermanDeposit();
146+
_graphToken().pullTokens(msg.sender, disputeDeposit);
144147

145148
// Create a dispute
146149
return _createIndexingDisputeWithAllocation(msg.sender, disputeDeposit, allocationId, poi);
@@ -158,7 +161,7 @@ contract DisputeManager is
158161
*/
159162
function createQueryDispute(bytes calldata attestationData) external override returns (bytes32) {
160163
// Get funds from fisherman
161-
_pullFishermanDeposit();
164+
_graphToken().pullTokens(msg.sender, disputeDeposit);
162165

163166
// Create a dispute
164167
return
@@ -174,10 +177,14 @@ contract DisputeManager is
174177
* @notice Create query disputes for two conflicting attestations.
175178
* A conflicting attestation is a proof presented by two different indexers
176179
* where for the same request on a subgraph the response is different.
177-
* For this type of dispute the fisherman is not required to present a deposit
178-
* as one of the attestation is considered to be right.
179180
* Two linked disputes will be created and if the arbitrator resolve one, the other
180-
* one will be automatically resolved.
181+
* one will be automatically resolved. Note that:
182+
* - it's not possible to reject a conflicting query dispute as by definition at least one
183+
* of the attestations is incorrect.
184+
* - if both attestations are proven to be incorrect, the arbitrator can slash the indexer twice.
185+
* Requirements:
186+
* - fisherman must have previously approved this contract to pull `disputeDeposit` amount
187+
* of tokens from their balance.
181188
* @param attestationData1 First attestation data submitted
182189
* @param attestationData2 Second attestation data submitted
183190
* @return DisputeId1, DisputeId2
@@ -205,10 +212,23 @@ contract DisputeManager is
205212
)
206213
);
207214

215+
// Get funds from fisherman
216+
_graphToken().pullTokens(msg.sender, disputeDeposit);
217+
208218
// Create the disputes
209219
// The deposit is zero for conflicting attestations
210-
bytes32 dId1 = _createQueryDisputeWithAttestation(fisherman, 0, attestation1, attestationData1);
211-
bytes32 dId2 = _createQueryDisputeWithAttestation(fisherman, 0, attestation2, attestationData2);
220+
bytes32 dId1 = _createQueryDisputeWithAttestation(
221+
fisherman,
222+
disputeDeposit / 2,
223+
attestation1,
224+
attestationData1
225+
);
226+
bytes32 dId2 = _createQueryDisputeWithAttestation(
227+
fisherman,
228+
disputeDeposit / 2,
229+
attestation2,
230+
attestationData2
231+
);
212232

213233
// Store the linked disputes to be resolved
214234
disputes[dId1].relatedDisputeId = dId2;
@@ -228,54 +248,59 @@ contract DisputeManager is
228248
* @dev Accept a dispute with Id `disputeId`
229249
* @param disputeId Id of the dispute to be accepted
230250
* @param tokensSlash Amount of tokens to slash from the indexer
251+
* @param acceptDisputeInConflict If the dispute is in conflict, accept the conflicting dispute. Otherwise
252+
* it will be drawn automatically. Ignored if the dispute is not in conflict.
231253
*/
232254
function acceptDispute(
233255
bytes32 disputeId,
234-
uint256 tokensSlash
256+
uint256 tokensSlash,
257+
bool acceptDisputeInConflict
235258
) external override onlyArbitrator onlyPendingDispute(disputeId) {
236259
Dispute storage dispute = disputes[disputeId];
237-
238-
// store the dispute status
239-
dispute.status = IDisputeManager.DisputeStatus.Accepted;
240-
241-
// Slash
242-
uint256 tokensToReward = _slashIndexer(dispute.indexer, tokensSlash, dispute.stakeSnapshot);
243-
244-
// Give the fisherman their reward and their deposit back
245-
_graphToken().pushTokens(dispute.fisherman, tokensToReward + dispute.deposit);
260+
_acceptDispute(disputeId, dispute, tokensSlash);
246261

247262
if (_isDisputeInConflict(dispute)) {
248-
rejectDispute(dispute.relatedDisputeId);
263+
Dispute storage relatedDispute = disputes[dispute.relatedDisputeId];
264+
if (acceptDisputeInConflict) {
265+
_acceptDispute(dispute.relatedDisputeId, relatedDispute, tokensSlash);
266+
} else {
267+
_drawDispute(dispute.relatedDisputeId, relatedDispute);
268+
}
249269
}
270+
}
250271

251-
emit DisputeAccepted(disputeId, dispute.indexer, dispute.fisherman, dispute.deposit + tokensToReward);
272+
/**
273+
* @notice The arbitrator rejects a dispute as being invalid.
274+
* Note that conflicting query disputes cannot be rejected.
275+
* @dev Reject a dispute with Id `disputeId`
276+
* @param disputeId Id of the dispute to be rejected
277+
*/
278+
function rejectDispute(bytes32 disputeId) external override onlyArbitrator onlyPendingDispute(disputeId) {
279+
Dispute storage dispute = disputes[disputeId];
280+
require(!_isDisputeInConflict(dispute), DisputeManagerDisputeInConflict(disputeId));
281+
_rejectDispute(disputeId, dispute);
252282
}
253283

254284
/**
255285
* @notice The arbitrator draws dispute.
286+
* Note that drawing a conflicting query dispute should not be possible however it is allowed
287+
* to give arbitrators greater flexibility when resolving disputes.
256288
* @dev Ignore a dispute with Id `disputeId`
257289
* @param disputeId Id of the dispute to be disregarded
258290
*/
259291
function drawDispute(bytes32 disputeId) external override onlyArbitrator onlyPendingDispute(disputeId) {
260292
Dispute storage dispute = disputes[disputeId];
293+
_drawDispute(disputeId, dispute);
261294

262-
// Return deposit to the fisherman if any
263-
if (dispute.deposit > 0) {
264-
_graphToken().pushTokens(dispute.fisherman, dispute.deposit);
295+
if (_isDisputeInConflict(dispute)) {
296+
_drawDispute(dispute.relatedDisputeId, disputes[dispute.relatedDisputeId]);
265297
}
266-
267-
// resolve related dispute if any
268-
_drawDisputeInConflict(dispute);
269-
270-
// store dispute status
271-
dispute.status = IDisputeManager.DisputeStatus.Drawn;
272-
273-
emit DisputeDrawn(disputeId, dispute.indexer, dispute.fisherman, dispute.deposit);
274298
}
275299

276300
/**
277301
* @notice Once the dispute period ends, if the dispute status remains Pending,
278302
* the fisherman can cancel the dispute and get back their initial deposit.
303+
* Note that cancelling a conflicting query dispute will also cancel the related dispute.
279304
* @dev Cancel a dispute with Id `disputeId`
280305
* @param disputeId Id of the dispute to be cancelled
281306
*/
@@ -284,19 +309,11 @@ contract DisputeManager is
284309

285310
// Check if dispute period has finished
286311
require(dispute.createdAt + disputePeriod < block.timestamp, DisputeManagerDisputePeriodNotFinished());
312+
_cancelDispute(disputeId, dispute);
287313

288-
// Return deposit to the fisherman if any
289-
if (dispute.deposit > 0) {
290-
_graphToken().pushTokens(dispute.fisherman, dispute.deposit);
314+
if (_isDisputeInConflict(dispute)) {
315+
_cancelDispute(dispute.relatedDisputeId, disputes[dispute.relatedDisputeId]);
291316
}
292-
293-
// resolve related dispute if any
294-
_cancelDisputeInConflict(dispute);
295-
296-
// store dispute status
297-
dispute.status = IDisputeManager.DisputeStatus.Cancelled;
298-
299-
emit DisputeCancelled(disputeId, dispute.indexer, dispute.fisherman, dispute.deposit);
300317
}
301318

302319
/**
@@ -401,29 +418,6 @@ contract DisputeManager is
401418
return Attestation.areConflicting(attestation1, attestation2);
402419
}
403420

404-
/**
405-
* @notice The arbitrator rejects a dispute as being invalid.
406-
* @dev Reject a dispute with Id `disputeId`
407-
* @param disputeId Id of the dispute to be rejected
408-
*/
409-
function rejectDispute(bytes32 disputeId) public override onlyArbitrator onlyPendingDispute(disputeId) {
410-
Dispute storage dispute = disputes[disputeId];
411-
412-
// store dispute status
413-
dispute.status = IDisputeManager.DisputeStatus.Rejected;
414-
415-
// For conflicting disputes, the related dispute must be accepted
416-
require(
417-
!_isDisputeInConflict(dispute),
418-
DisputeManagerMustAcceptRelatedDispute(disputeId, dispute.relatedDisputeId)
419-
);
420-
421-
// Burn the fisherman's deposit
422-
_graphToken().burnTokens(dispute.deposit);
423-
424-
emit DisputeRejected(disputeId, dispute.indexer, dispute.fisherman, dispute.deposit);
425-
}
426-
427421
/**
428422
* @notice Returns the indexer that signed an attestation.
429423
* @param attestation Attestation
@@ -561,42 +555,33 @@ contract DisputeManager is
561555
return disputeId;
562556
}
563557

564-
/**
565-
* @notice Draw the conflicting dispute if there is any for the one passed to this function.
566-
* @param _dispute Dispute
567-
* @return True if resolved
568-
*/
569-
function _drawDisputeInConflict(Dispute memory _dispute) private returns (bool) {
570-
if (_isDisputeInConflict(_dispute)) {
571-
bytes32 relatedDisputeId = _dispute.relatedDisputeId;
572-
Dispute storage relatedDispute = disputes[relatedDisputeId];
573-
relatedDispute.status = IDisputeManager.DisputeStatus.Drawn;
574-
return true;
575-
}
576-
return false;
558+
function _acceptDispute(bytes32 _disputeId, Dispute storage _dispute, uint256 _tokensSlashed) private {
559+
uint256 tokensToReward = _slashIndexer(_dispute.indexer, _tokensSlashed, _dispute.stakeSnapshot);
560+
_dispute.status = IDisputeManager.DisputeStatus.Accepted;
561+
_graphToken().pushTokens(_dispute.fisherman, tokensToReward + _dispute.deposit);
562+
563+
emit DisputeAccepted(_disputeId, _dispute.indexer, _dispute.fisherman, _dispute.deposit + tokensToReward);
577564
}
578565

579-
/**
580-
* @notice Cancel the conflicting dispute if there is any for the one passed to this function.
581-
* @param _dispute Dispute
582-
* @return True if cancelled
583-
*/
584-
function _cancelDisputeInConflict(Dispute memory _dispute) private returns (bool) {
585-
if (_isDisputeInConflict(_dispute)) {
586-
bytes32 relatedDisputeId = _dispute.relatedDisputeId;
587-
Dispute storage relatedDispute = disputes[relatedDisputeId];
588-
relatedDispute.status = IDisputeManager.DisputeStatus.Cancelled;
589-
return true;
590-
}
591-
return false;
566+
function _rejectDispute(bytes32 _disputeId, Dispute storage _dispute) private {
567+
_dispute.status = IDisputeManager.DisputeStatus.Rejected;
568+
_graphToken().burnTokens(_dispute.deposit);
569+
570+
emit DisputeRejected(_disputeId, _dispute.indexer, _dispute.fisherman, _dispute.deposit);
592571
}
593572

594-
/**
595-
* @notice Pull `disputeDeposit` from fisherman account.
596-
*/
597-
function _pullFishermanDeposit() private {
598-
// Transfer tokens to deposit from fisherman to this contract
599-
_graphToken().pullTokens(msg.sender, disputeDeposit);
573+
function _drawDispute(bytes32 _disputeId, Dispute storage _dispute) private {
574+
_dispute.status = IDisputeManager.DisputeStatus.Drawn;
575+
_graphToken().pushTokens(_dispute.fisherman, _dispute.deposit);
576+
577+
emit DisputeDrawn(_disputeId, _dispute.indexer, _dispute.fisherman, _dispute.deposit);
578+
}
579+
580+
function _cancelDispute(bytes32 _disputeId, Dispute storage _dispute) private {
581+
_dispute.status = IDisputeManager.DisputeStatus.Cancelled;
582+
_graphToken().pushTokens(_dispute.fisherman, _dispute.deposit);
583+
584+
emit DisputeCancelled(_disputeId, _dispute.indexer, _dispute.fisherman, _dispute.deposit);
600585
}
601586

602587
/**
@@ -658,7 +643,7 @@ contract DisputeManager is
658643
* @param _disputeDeposit The dispute deposit in Graph Tokens
659644
*/
660645
function _setDisputeDeposit(uint256 _disputeDeposit) private {
661-
require(_disputeDeposit != 0, DisputeManagerInvalidDisputeDeposit(_disputeDeposit));
646+
require(_disputeDeposit >= MIN_DISPUTE_DEPOSIT, DisputeManagerInvalidDisputeDeposit(_disputeDeposit));
662647
disputeDeposit = _disputeDeposit;
663648
emit DisputeDepositSet(_disputeDeposit);
664649
}
@@ -704,10 +689,8 @@ contract DisputeManager is
704689
* @param _dispute Dispute
705690
* @return True conflicting attestation dispute
706691
*/
707-
function _isDisputeInConflict(Dispute memory _dispute) private view returns (bool) {
708-
bytes32 relatedId = _dispute.relatedDisputeId;
709-
// this is so the check returns false when rejecting the related dispute.
710-
return relatedId != 0 && disputes[relatedId].status == IDisputeManager.DisputeStatus.Pending;
692+
function _isDisputeInConflict(Dispute storage _dispute) private view returns (bool) {
693+
return _dispute.relatedDisputeId != bytes32(0);
711694
}
712695

713696
/**

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ interface IDisputeManager {
169169
error DisputeManagerDisputeNotPending(IDisputeManager.DisputeStatus status);
170170
error DisputeManagerDisputeAlreadyCreated(bytes32 disputeId);
171171
error DisputeManagerDisputePeriodNotFinished();
172+
error DisputeManagerDisputeInConflict(bytes32 disputeId);
172173
error DisputeManagerMustAcceptRelatedDispute(bytes32 disputeId, bytes32 relatedDisputeId);
173174
error DisputeManagerIndexerNotFound(address allocationId);
174175
error DisputeManagerNonMatchingSubgraphDeployment(bytes32 subgraphDeploymentId1, bytes32 subgraphDeploymentId2);
@@ -202,7 +203,7 @@ interface IDisputeManager {
202203

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

205-
function acceptDispute(bytes32 disputeId, uint256 tokensSlash) external;
206+
function acceptDispute(bytes32 disputeId, uint256 tokensSlash, bool acceptDisputeInConflict) external;
206207

207208
function rejectDispute(bytes32 disputeId) external;
208209

0 commit comments

Comments
 (0)