@@ -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