Skip to content

Commit a0a3a67

Browse files
authored
Merge pull request #2174 from kleros/refactor/active-dk
Refactor the Dispute Kit checks: active dispute and isJumped
2 parents 536e61c + f9fd944 commit a0a3a67

File tree

8 files changed

+119
-135
lines changed

8 files changed

+119
-135
lines changed

contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol

Lines changed: 49 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
2424
struct Dispute {
2525
Round[] rounds; // Rounds of the dispute. 0 is the default round, and [1, ..n] are the appeal rounds.
2626
uint256 numberOfChoices; // The number of choices jurors have when voting. This does not include choice `0` which is reserved for "refuse to arbitrate".
27-
bool jumped; // True if dispute jumped to a parent dispute kit and won't be handled by this DK anymore.
2827
mapping(uint256 => uint256) coreRoundIDToLocal; // Maps id of the round in the core contract to the index of the round of related local dispute.
2928
bytes extraData; // Extradata for the dispute.
3029
uint256[10] __gap; // Reserved slots for future upgrades.
@@ -54,6 +53,11 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
5453
uint256[10] __gap; // Reserved slots for future upgrades.
5554
}
5655

56+
struct Active {
57+
bool dispute; // True if at least one round in the dispute has been active on this Dispute Kit. False if the dispute is unknown to this Dispute Kit.
58+
bool currentRound; // True if the dispute's current round is active on this Dispute Kit. False if the dispute has jumped to another Dispute Kit.
59+
}
60+
5761
// ************************************* //
5862
// * Storage * //
5963
// ************************************* //
@@ -67,7 +71,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
6771
Dispute[] public disputes; // Array of the locally created disputes.
6872
mapping(uint256 => uint256) public coreDisputeIDToLocal; // Maps the dispute ID in Kleros Core to the local dispute ID.
6973
bool public singleDrawPerJuror; // Whether each juror can only draw once per dispute, false by default.
70-
mapping(uint256 coreDisputeID => bool) public coreDisputeIDToActive; // True if this dispute kit is active for this core dispute ID.
74+
mapping(uint256 coreDisputeID => Active) public coreDisputeIDToActive; // Active status of the dispute and the current round.
7175
address public wNative; // The wrapped native token for safeSend().
7276
uint256 public jumpDisputeKitID; // The ID of the dispute kit in Kleros Core disputeKits array that the dispute should switch to after the court jump, in case the new court doesn't support this dispute kit.
7377

@@ -106,17 +110,10 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
106110

107111
/// @notice To be emitted when the contributed funds are withdrawn.
108112
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
109-
/// @param _coreRoundID The identifier of the round in the Arbitrator contract.
110113
/// @param _choice The choice that is being funded.
111114
/// @param _contributor The address of the contributor.
112115
/// @param _amount The amount withdrawn.
113-
event Withdrawal(
114-
uint256 indexed _coreDisputeID,
115-
uint256 indexed _coreRoundID,
116-
uint256 _choice,
117-
address indexed _contributor,
118-
uint256 _amount
119-
);
116+
event Withdrawal(uint256 indexed _coreDisputeID, uint256 _choice, address indexed _contributor, uint256 _amount);
120117

121118
/// @notice To be emitted when a choice is fully funded for an appeal.
122119
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
@@ -138,8 +135,9 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
138135
_;
139136
}
140137

141-
modifier notJumped(uint256 _coreDisputeID) {
142-
if (disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped) revert DisputeJumpedToParentDK();
138+
modifier isActive(uint256 _coreDisputeID) {
139+
if (!coreDisputeIDToActive[_coreDisputeID].dispute) revert DisputeUnknownInThisDisputeKit();
140+
if (!coreDisputeIDToActive[_coreDisputeID].currentRound) revert DisputeJumpedToAnotherDisputeKit();
143141
_;
144142
}
145143

@@ -208,25 +206,26 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
208206
) external override onlyByCore {
209207
uint256 localDisputeID;
210208
Dispute storage dispute;
211-
// Check if this dk wasn't already active before. Can happen if DK1 jumps to DK2 and then back to DK1.
212-
if (coreDisputeIDToActive[_coreDisputeID]) {
209+
Active storage active = coreDisputeIDToActive[_coreDisputeID];
210+
if (active.dispute) {
211+
// The dispute has already been created in this DK in a previous round. E.g. if DK1 jumps to DK2 and then back to DK1.
213212
localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
214213
dispute = disputes[localDisputeID];
215-
dispute.jumped = false;
216214
} else {
215+
// The dispute has not been created in this DK yet.
217216
localDisputeID = disputes.length;
218217
dispute = disputes.push();
219218
coreDisputeIDToLocal[_coreDisputeID] = localDisputeID;
220-
coreDisputeIDToActive[_coreDisputeID] = true;
221219
}
222220

221+
active.dispute = true;
222+
active.currentRound = true;
223223
dispute.numberOfChoices = _numberOfChoices;
224224
dispute.extraData = _extraData;
225-
// New round in the Core should be created before the dispute creation in DK.
226-
dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID) - 1] = dispute.rounds.length;
227225

228-
Round storage round = dispute.rounds.push();
229-
round.tied = true;
226+
// KlerosCore.Round must have been already created.
227+
dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID) - 1] = dispute.rounds.length;
228+
dispute.rounds.push().tied = true;
230229

231230
emit DisputeCreation(_coreDisputeID, _numberOfChoices, _extraData);
232231
}
@@ -235,7 +234,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
235234
function draw(
236235
uint256 _coreDisputeID,
237236
uint256 _nonce
238-
) external override onlyByCore notJumped(_coreDisputeID) returns (address drawnAddress, uint96 fromSubcourtID) {
237+
) external override onlyByCore isActive(_coreDisputeID) returns (address drawnAddress, uint96 fromSubcourtID) {
239238
uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
240239
Dispute storage dispute = disputes[localDisputeID];
241240
uint256 localRoundID = dispute.rounds.length - 1;
@@ -274,11 +273,10 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
274273
uint256 _coreDisputeID,
275274
uint256[] calldata _voteIDs,
276275
bytes32 _commit
277-
) internal notJumped(_coreDisputeID) {
276+
) internal isActive(_coreDisputeID) {
278277
(, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID);
279278
if (period != KlerosCore.Period.commit) revert NotCommitPeriod();
280279
if (_commit == bytes32(0)) revert EmptyCommit();
281-
if (!coreDisputeIDToActive[_coreDisputeID]) revert NotActiveForCoreDisputeID();
282280

283281
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
284282
Round storage round = dispute.rounds[dispute.rounds.length - 1];
@@ -321,11 +319,10 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
321319
uint256 _salt,
322320
string memory _justification,
323321
address _juror
324-
) internal notJumped(_coreDisputeID) {
322+
) internal isActive(_coreDisputeID) {
325323
(, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID);
326324
if (period != KlerosCore.Period.vote) revert NotVotePeriod();
327325
if (_voteIDs.length == 0) revert EmptyVoteIDs();
328-
if (!coreDisputeIDToActive[_coreDisputeID]) revert NotActiveForCoreDisputeID();
329326

330327
uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
331328
Dispute storage dispute = disputes[localDisputeID];
@@ -372,10 +369,9 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
372369
/// Note that the surplus deposit will be reimbursed.
373370
/// @param _coreDisputeID Index of the dispute in Kleros Core.
374371
/// @param _choice A choice that receives funding.
375-
function fundAppeal(uint256 _coreDisputeID, uint256 _choice) external payable notJumped(_coreDisputeID) {
372+
function fundAppeal(uint256 _coreDisputeID, uint256 _choice) external payable isActive(_coreDisputeID) {
376373
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
377374
if (_choice > dispute.numberOfChoices) revert ChoiceOutOfBounds();
378-
if (!coreDisputeIDToActive[_coreDisputeID]) revert NotActiveForCoreDisputeID();
379375

380376
(uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_coreDisputeID);
381377
if (block.timestamp < appealPeriodStart || block.timestamp >= appealPeriodEnd) revert NotAppealPeriod();
@@ -425,7 +421,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
425421

426422
if (core.isDisputeKitJumping(_coreDisputeID)) {
427423
// Don't create a new round in case of a jump, and remove local dispute from the flow.
428-
dispute.jumped = true;
424+
coreDisputeIDToActive[_coreDisputeID].currentRound = false;
429425
} else {
430426
// Don't subtract 1 from length since both round arrays haven't been updated yet.
431427
dispute.coreRoundIDToLocal[coreRoundID + 1] = dispute.rounds.length;
@@ -441,48 +437,50 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
441437

442438
/// @notice Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved.
443439
/// @dev Withdrawals are not possible if the core contract is paused.
440+
/// @dev It can be called after the dispute has jumped to another dispute kit.
444441
/// @param _coreDisputeID Index of the dispute in Kleros Core contract.
445442
/// @param _beneficiary The address whose rewards to withdraw.
446-
/// @param _coreRoundID The round in the Kleros Core contract the caller wants to withdraw from.
447443
/// @param _choice The ruling option that the caller wants to withdraw from.
448444
/// @return amount The withdrawn amount.
449445
function withdrawFeesAndRewards(
450446
uint256 _coreDisputeID,
451447
address payable _beneficiary,
452-
uint256 _coreRoundID,
453448
uint256 _choice
454449
) external returns (uint256 amount) {
455450
(, , , bool isRuled, ) = core.disputes(_coreDisputeID);
456451
if (!isRuled) revert DisputeNotResolved();
457452
if (core.paused()) revert CoreIsPaused();
458-
if (!coreDisputeIDToActive[_coreDisputeID]) revert NotActiveForCoreDisputeID();
453+
if (!coreDisputeIDToActive[_coreDisputeID].dispute) revert DisputeUnknownInThisDisputeKit();
459454

460455
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
461-
Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]];
462456
(uint256 finalRuling, , ) = core.currentRuling(_coreDisputeID);
463457

464-
if (!round.hasPaid[_choice]) {
465-
// Allow to reimburse if funding was unsuccessful for this ruling option.
466-
amount = round.contributions[_beneficiary][_choice];
467-
} else {
468-
// Funding was successful for this ruling option.
469-
if (_choice == finalRuling) {
470-
// This ruling option is the ultimate winner.
471-
amount = round.paidFees[_choice] > 0
472-
? (round.contributions[_beneficiary][_choice] * round.feeRewards) / round.paidFees[_choice]
473-
: 0;
474-
} else if (!round.hasPaid[finalRuling]) {
475-
// The ultimate winner was not funded in this round. In this case funded ruling option(s) are reimbursed.
476-
amount =
477-
(round.contributions[_beneficiary][_choice] * round.feeRewards) /
478-
(round.paidFees[round.fundedChoices[0]] + round.paidFees[round.fundedChoices[1]]);
458+
for (uint256 i = 0; i < dispute.rounds.length; i++) {
459+
Round storage round = dispute.rounds[i];
460+
461+
if (!round.hasPaid[_choice]) {
462+
// Allow to reimburse if funding was unsuccessful for this ruling option.
463+
amount += round.contributions[_beneficiary][_choice];
464+
} else {
465+
// Funding was successful for this ruling option.
466+
if (_choice == finalRuling) {
467+
// This ruling option is the ultimate winner.
468+
amount += round.paidFees[_choice] > 0
469+
? (round.contributions[_beneficiary][_choice] * round.feeRewards) / round.paidFees[_choice]
470+
: 0;
471+
} else if (!round.hasPaid[finalRuling]) {
472+
// The ultimate winner was not funded in this round. In this case funded ruling option(s) are reimbursed.
473+
amount +=
474+
(round.contributions[_beneficiary][_choice] * round.feeRewards) /
475+
(round.paidFees[round.fundedChoices[0]] + round.paidFees[round.fundedChoices[1]]);
476+
}
479477
}
478+
round.contributions[_beneficiary][_choice] = 0;
480479
}
481-
round.contributions[_beneficiary][_choice] = 0;
482480

483481
if (amount != 0) {
484482
_beneficiary.safeSend(amount, wNative);
485-
emit Withdrawal(_coreDisputeID, _coreRoundID, _choice, _beneficiary, amount);
483+
emit Withdrawal(_coreDisputeID, _choice, _beneficiary, amount);
486484
}
487485
}
488486

@@ -756,11 +754,11 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
756754

757755
error OwnerOnly();
758756
error KlerosCoreOnly();
759-
error DisputeJumpedToParentDK();
757+
error DisputeJumpedToAnotherDisputeKit();
758+
error DisputeUnknownInThisDisputeKit();
760759
error UnsuccessfulCall();
761760
error NotCommitPeriod();
762761
error EmptyCommit();
763-
error NotActiveForCoreDisputeID();
764762
error JurorHasToOwnTheVote();
765763
error NotVotePeriod();
766764
error EmptyVoteIDs();

contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
118118
bytes32 _recoveryCommit,
119119
bytes32 _identity,
120120
bytes calldata _encryptedVote
121-
) external notJumped(_coreDisputeID) {
121+
) external {
122122
if (_recoveryCommit == bytes32(0)) revert EmptyRecoveryCommit();
123123

124124
uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
@@ -128,7 +128,7 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
128128
recoveryCommitments[localDisputeID][localRoundID][_voteIDs[i]] = _recoveryCommit;
129129
}
130130

131-
// `_castCommit()` ensures that the caller owns the vote
131+
// `_castCommit()` ensures that the caller owns the vote and that dispute is active
132132
_castCommit(_coreDisputeID, _voteIDs, _commit);
133133
emit CommitCastShutter(_coreDisputeID, msg.sender, _commit, _recoveryCommit, _identity, _encryptedVote);
134134
}

contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ contract DisputeKitShutter is DisputeKitClassicBase {
102102
bytes32 _recoveryCommit,
103103
bytes32 _identity,
104104
bytes calldata _encryptedVote
105-
) external notJumped(_coreDisputeID) {
105+
) external {
106106
if (_recoveryCommit == bytes32(0)) revert EmptyRecoveryCommit();
107107

108108
uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
@@ -112,7 +112,7 @@ contract DisputeKitShutter is DisputeKitClassicBase {
112112
recoveryCommitments[localDisputeID][localRoundID][_voteIDs[i]] = _recoveryCommit;
113113
}
114114

115-
// `_castCommit()` ensures that the caller owns the vote
115+
// `_castCommit()` ensures that the caller owns the vote and that dispute is active
116116
_castCommit(_coreDisputeID, _voteIDs, _commit);
117117
emit CommitCastShutter(_coreDisputeID, msg.sender, _commit, _recoveryCommit, _identity, _encryptedVote);
118118
}

contracts/src/arbitration/interfaces/IDisputeKit.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ interface IDisputeKit {
3232

3333
/// @notice Creates a local dispute and maps it to the dispute ID in the Core contract.
3434
/// @dev Access restricted to Kleros Core only.
35+
/// @dev The new `KlerosCore.Round` must be created before calling this function.
3536
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit.
3637
/// @param _numberOfChoices Number of choices of the dispute
3738
/// @param _extraData Additional info about the dispute, for possible use in future dispute kits.

0 commit comments

Comments
 (0)