@@ -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
@@ -206,28 +204,37 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
206204 bytes calldata _extraData ,
207205 uint256 /*_nbVotes*/
208206 ) external override onlyByCore {
209- uint256 localDisputeID = disputes.length ;
210- Dispute storage dispute = disputes.push ();
207+ uint256 localDisputeID;
208+ Dispute storage dispute;
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.
212+ localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
213+ dispute = disputes[localDisputeID];
214+ } else {
215+ // The dispute has not been created in this DK yet.
216+ localDisputeID = disputes.length ;
217+ dispute = disputes.push ();
218+ coreDisputeIDToLocal[_coreDisputeID] = localDisputeID;
219+ }
220+
221+ active.dispute = true ;
222+ active.currentRound = true ;
211223 dispute.numberOfChoices = _numberOfChoices;
212224 dispute.extraData = _extraData;
213- dispute.jumped = false ; // Possibly true if this DK has jumped in a previous round.
214225
215- // New round in the Core should be created before the dispute creation in DK .
226+ // KlerosCore.Round must have been already created.
216227 dispute.coreRoundIDToLocal[core.getNumberOfRounds (_coreDisputeID) - 1 ] = dispute.rounds.length ;
228+ dispute.rounds.push ().tied = true ;
217229
218- Round storage round = dispute.rounds.push ();
219- round.tied = true ;
220-
221- coreDisputeIDToLocal[_coreDisputeID] = localDisputeID;
222- coreDisputeIDToActive[_coreDisputeID] = true ;
223230 emit DisputeCreation (_coreDisputeID, _numberOfChoices, _extraData);
224231 }
225232
226233 /// @inheritdoc IDisputeKit
227234 function draw (
228235 uint256 _coreDisputeID ,
229236 uint256 _nonce
230- ) external override onlyByCore notJumped (_coreDisputeID) returns (address drawnAddress , uint96 fromSubcourtID ) {
237+ ) external override onlyByCore isActive (_coreDisputeID) returns (address drawnAddress , uint96 fromSubcourtID ) {
231238 uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
232239 Dispute storage dispute = disputes[localDisputeID];
233240 uint256 localRoundID = dispute.rounds.length - 1 ;
@@ -266,11 +273,10 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
266273 uint256 _coreDisputeID ,
267274 uint256 [] calldata _voteIDs ,
268275 bytes32 _commit
269- ) internal notJumped (_coreDisputeID) {
276+ ) internal isActive (_coreDisputeID) {
270277 (, , KlerosCore.Period period , , ) = core.disputes (_coreDisputeID);
271278 if (period != KlerosCore.Period.commit) revert NotCommitPeriod ();
272279 if (_commit == bytes32 (0 )) revert EmptyCommit ();
273- if (! coreDisputeIDToActive[_coreDisputeID]) revert NotActiveForCoreDisputeID ();
274280
275281 Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
276282 Round storage round = dispute.rounds[dispute.rounds.length - 1 ];
@@ -313,11 +319,10 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
313319 uint256 _salt ,
314320 string memory _justification ,
315321 address _juror
316- ) internal notJumped (_coreDisputeID) {
322+ ) internal isActive (_coreDisputeID) {
317323 (, , KlerosCore.Period period , , ) = core.disputes (_coreDisputeID);
318324 if (period != KlerosCore.Period.vote) revert NotVotePeriod ();
319325 if (_voteIDs.length == 0 ) revert EmptyVoteIDs ();
320- if (! coreDisputeIDToActive[_coreDisputeID]) revert NotActiveForCoreDisputeID ();
321326
322327 uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
323328 Dispute storage dispute = disputes[localDisputeID];
@@ -364,10 +369,9 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
364369 /// Note that the surplus deposit will be reimbursed.
365370 /// @param _coreDisputeID Index of the dispute in Kleros Core.
366371 /// @param _choice A choice that receives funding.
367- function fundAppeal (uint256 _coreDisputeID , uint256 _choice ) external payable notJumped (_coreDisputeID) {
372+ function fundAppeal (uint256 _coreDisputeID , uint256 _choice ) external payable isActive (_coreDisputeID) {
368373 Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
369374 if (_choice > dispute.numberOfChoices) revert ChoiceOutOfBounds ();
370- if (! coreDisputeIDToActive[_coreDisputeID]) revert NotActiveForCoreDisputeID ();
371375
372376 (uint256 appealPeriodStart , uint256 appealPeriodEnd ) = core.appealPeriod (_coreDisputeID);
373377 if (block .timestamp < appealPeriodStart || block .timestamp >= appealPeriodEnd) revert NotAppealPeriod ();
@@ -417,7 +421,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
417421
418422 if (core.isDisputeKitJumping (_coreDisputeID)) {
419423 // Don't create a new round in case of a jump, and remove local dispute from the flow.
420- dispute.jumped = true ;
424+ coreDisputeIDToActive[_coreDisputeID].currentRound = false ;
421425 } else {
422426 // Don't subtract 1 from length since both round arrays haven't been updated yet.
423427 dispute.coreRoundIDToLocal[coreRoundID + 1 ] = dispute.rounds.length ;
@@ -433,48 +437,50 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
433437
434438 /// @notice Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved.
435439 /// @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.
436441 /// @param _coreDisputeID Index of the dispute in Kleros Core contract.
437442 /// @param _beneficiary The address whose rewards to withdraw.
438- /// @param _coreRoundID The round in the Kleros Core contract the caller wants to withdraw from.
439443 /// @param _choice The ruling option that the caller wants to withdraw from.
440444 /// @return amount The withdrawn amount.
441445 function withdrawFeesAndRewards (
442446 uint256 _coreDisputeID ,
443447 address payable _beneficiary ,
444- uint256 _coreRoundID ,
445448 uint256 _choice
446449 ) external returns (uint256 amount ) {
447450 (, , , bool isRuled , ) = core.disputes (_coreDisputeID);
448451 if (! isRuled) revert DisputeNotResolved ();
449452 if (core.paused ()) revert CoreIsPaused ();
450- if (! coreDisputeIDToActive[_coreDisputeID]) revert NotActiveForCoreDisputeID ();
453+ if (! coreDisputeIDToActive[_coreDisputeID].dispute ) revert DisputeUnknownInThisDisputeKit ();
451454
452455 Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
453- Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]];
454456 (uint256 finalRuling , , ) = core.currentRuling (_coreDisputeID);
455457
456- if (! round.hasPaid[_choice]) {
457- // Allow to reimburse if funding was unsuccessful for this ruling option.
458- amount = round.contributions[_beneficiary][_choice];
459- } else {
460- // Funding was successful for this ruling option.
461- if (_choice == finalRuling) {
462- // This ruling option is the ultimate winner.
463- amount = round.paidFees[_choice] > 0
464- ? (round.contributions[_beneficiary][_choice] * round.feeRewards) / round.paidFees[_choice]
465- : 0 ;
466- } else if (! round.hasPaid[finalRuling]) {
467- // The ultimate winner was not funded in this round. In this case funded ruling option(s) are reimbursed.
468- amount =
469- (round.contributions[_beneficiary][_choice] * round.feeRewards) /
470- (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+ }
471477 }
478+ round.contributions[_beneficiary][_choice] = 0 ;
472479 }
473- round.contributions[_beneficiary][_choice] = 0 ;
474480
475481 if (amount != 0 ) {
476482 _beneficiary.safeSend (amount, wNative);
477- emit Withdrawal (_coreDisputeID, _coreRoundID, _choice, _beneficiary, amount);
483+ emit Withdrawal (_coreDisputeID, _choice, _beneficiary, amount);
478484 }
479485 }
480486
@@ -748,11 +754,11 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
748754
749755 error OwnerOnly ();
750756 error KlerosCoreOnly ();
751- error DisputeJumpedToParentDK ();
757+ error DisputeJumpedToAnotherDisputeKit ();
758+ error DisputeUnknownInThisDisputeKit ();
752759 error UnsuccessfulCall ();
753760 error NotCommitPeriod ();
754761 error EmptyCommit ();
755- error NotActiveForCoreDisputeID ();
756762 error JurorHasToOwnTheVote ();
757763 error NotVotePeriod ();
758764 error EmptyVoteIDs ();
0 commit comments