@@ -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 ();
0 commit comments