@@ -59,6 +59,9 @@ contract DisputeManager is
59
59
// Maximum value for fisherman reward cut in PPM
60
60
uint32 public constant MAX_FISHERMAN_REWARD_CUT = 500000 ;
61
61
62
+ // Minimum value for dispute deposit
63
+ uint256 public constant MIN_DISPUTE_DEPOSIT = 1e18 ; // 1 GRT
64
+
62
65
// -- Modifiers --
63
66
64
67
/**
@@ -140,7 +143,7 @@ contract DisputeManager is
140
143
*/
141
144
function createIndexingDispute (address allocationId , bytes32 poi ) external override returns (bytes32 ) {
142
145
// Get funds from fisherman
143
- _pullFishermanDeposit ( );
146
+ _graphToken (). pullTokens ( msg . sender , disputeDeposit );
144
147
145
148
// Create a dispute
146
149
return _createIndexingDisputeWithAllocation (msg .sender , disputeDeposit, allocationId, poi);
@@ -158,7 +161,7 @@ contract DisputeManager is
158
161
*/
159
162
function createQueryDispute (bytes calldata attestationData ) external override returns (bytes32 ) {
160
163
// Get funds from fisherman
161
- _pullFishermanDeposit ( );
164
+ _graphToken (). pullTokens ( msg . sender , disputeDeposit );
162
165
163
166
// Create a dispute
164
167
return
@@ -174,10 +177,14 @@ contract DisputeManager is
174
177
* @notice Create query disputes for two conflicting attestations.
175
178
* A conflicting attestation is a proof presented by two different indexers
176
179
* 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.
179
180
* 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.
181
188
* @param attestationData1 First attestation data submitted
182
189
* @param attestationData2 Second attestation data submitted
183
190
* @return DisputeId1, DisputeId2
@@ -205,10 +212,23 @@ contract DisputeManager is
205
212
)
206
213
);
207
214
215
+ // Get funds from fisherman
216
+ _graphToken ().pullTokens (msg .sender , disputeDeposit);
217
+
208
218
// Create the disputes
209
219
// 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
+ );
212
232
213
233
// Store the linked disputes to be resolved
214
234
disputes[dId1].relatedDisputeId = dId2;
@@ -228,54 +248,59 @@ contract DisputeManager is
228
248
* @dev Accept a dispute with Id `disputeId`
229
249
* @param disputeId Id of the dispute to be accepted
230
250
* @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.
231
253
*/
232
254
function acceptDispute (
233
255
bytes32 disputeId ,
234
- uint256 tokensSlash
256
+ uint256 tokensSlash ,
257
+ bool acceptDisputeInConflict
235
258
) external override onlyArbitrator onlyPendingDispute (disputeId) {
236
259
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);
246
261
247
262
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
+ }
249
269
}
270
+ }
250
271
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);
252
282
}
253
283
254
284
/**
255
285
* @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.
256
288
* @dev Ignore a dispute with Id `disputeId`
257
289
* @param disputeId Id of the dispute to be disregarded
258
290
*/
259
291
function drawDispute (bytes32 disputeId ) external override onlyArbitrator onlyPendingDispute (disputeId) {
260
292
Dispute storage dispute = disputes[disputeId];
293
+ _drawDispute (disputeId, dispute);
261
294
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]);
265
297
}
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);
274
298
}
275
299
276
300
/**
277
301
* @notice Once the dispute period ends, if the dispute status remains Pending,
278
302
* 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.
279
304
* @dev Cancel a dispute with Id `disputeId`
280
305
* @param disputeId Id of the dispute to be cancelled
281
306
*/
@@ -284,19 +309,11 @@ contract DisputeManager is
284
309
285
310
// Check if dispute period has finished
286
311
require (dispute.createdAt + disputePeriod < block .timestamp , DisputeManagerDisputePeriodNotFinished ());
312
+ _cancelDispute (disputeId, dispute);
287
313
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]);
291
316
}
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);
300
317
}
301
318
302
319
/**
@@ -401,29 +418,6 @@ contract DisputeManager is
401
418
return Attestation.areConflicting (attestation1, attestation2);
402
419
}
403
420
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
-
427
421
/**
428
422
* @notice Returns the indexer that signed an attestation.
429
423
* @param attestation Attestation
@@ -561,42 +555,33 @@ contract DisputeManager is
561
555
return disputeId;
562
556
}
563
557
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);
577
564
}
578
565
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);
592
571
}
593
572
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);
600
585
}
601
586
602
587
/**
@@ -658,7 +643,7 @@ contract DisputeManager is
658
643
* @param _disputeDeposit The dispute deposit in Graph Tokens
659
644
*/
660
645
function _setDisputeDeposit (uint256 _disputeDeposit ) private {
661
- require (_disputeDeposit != 0 , DisputeManagerInvalidDisputeDeposit (_disputeDeposit));
646
+ require (_disputeDeposit >= MIN_DISPUTE_DEPOSIT , DisputeManagerInvalidDisputeDeposit (_disputeDeposit));
662
647
disputeDeposit = _disputeDeposit;
663
648
emit DisputeDepositSet (_disputeDeposit);
664
649
}
@@ -704,10 +689,8 @@ contract DisputeManager is
704
689
* @param _dispute Dispute
705
690
* @return True conflicting attestation dispute
706
691
*/
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 );
711
694
}
712
695
713
696
/**
0 commit comments