@@ -467,6 +467,289 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase {
467467 assertEq (account, staker1, "Wrong drawn account in the classic DK " );
468468 }
469469
470+ function test_appeal_recurringDK () public {
471+ // Test the behaviour when dispute jumps from DK3 to DK2 and then back to DK3 again.
472+
473+ // Setup: create 2 more courts to facilitate appeal jump. Create 2 more DK.
474+ // Set General Court as parent to court2, and court2 as parent to court3. dk2 as jump DK for dk3, and dk3 as jump DK for dk2.
475+ // Ensure DK2 is supported by Court2 and DK3 is supported by court3.
476+ // Preemptively add DK3 support for General court.
477+
478+ // Initial dispute starts with Court3, DK3.
479+ // Jumps to Court2, DK2.
480+ // Then jumps to General Court, DK3.
481+ uint256 disputeID = 0 ;
482+
483+ uint96 courtID2 = 2 ;
484+ uint96 courtID3 = 3 ;
485+
486+ uint256 dkID2 = 2 ;
487+ uint256 dkID3 = 3 ;
488+
489+ DisputeKitClassic dkLogic = new DisputeKitClassic ();
490+
491+ bytes memory initDataDk2 = abi.encodeWithSignature (
492+ "initialize(address,address,address,uint256) " ,
493+ owner,
494+ address (core),
495+ address (wNative),
496+ dkID3
497+ );
498+ UUPSProxy proxyDk2 = new UUPSProxy (address (dkLogic), initDataDk2);
499+ DisputeKitClassic disputeKit2 = DisputeKitClassic (address (proxyDk2));
500+
501+ bytes memory initDataDk3 = abi.encodeWithSignature (
502+ "initialize(address,address,address,uint256) " ,
503+ owner,
504+ address (core),
505+ address (wNative),
506+ dkID2
507+ );
508+ UUPSProxy proxyDk3 = new UUPSProxy (address (dkLogic), initDataDk3);
509+ DisputeKitClassic disputeKit3 = DisputeKitClassic (address (proxyDk3));
510+
511+ vm.prank (owner);
512+ core.addNewDisputeKit (disputeKit2);
513+ vm.prank (owner);
514+ core.addNewDisputeKit (disputeKit3);
515+
516+ uint256 [] memory supportedDK = new uint256 [](2 );
517+ supportedDK[0 ] = DISPUTE_KIT_CLASSIC;
518+ supportedDK[1 ] = dkID2;
519+ vm.prank (owner);
520+ core.createCourt (
521+ GENERAL_COURT,
522+ hiddenVotes,
523+ minStake,
524+ alpha,
525+ feeForJuror,
526+ 7 , // jurors for jump. Minimal number to ensure jump after the first appeal
527+ [uint256 (60 ), uint256 (120 ), uint256 (180 ), uint256 (240 )], // Times per period
528+ sortitionExtraData,
529+ supportedDK
530+ );
531+ assertEq (core.isSupported (courtID2, dkID2), true , "dkID2 should be supported by Court2 " );
532+
533+ (uint96 courtParent , , , , , uint256 courtJurorsForCourtJump ) = core.courts (courtID2);
534+ assertEq (courtParent, GENERAL_COURT, "Wrong court parent for court2 " );
535+ assertEq (courtJurorsForCourtJump, 7 , "Wrong jurors for jump value for court2 " );
536+
537+ supportedDK = new uint256 [](2 );
538+ supportedDK[0 ] = DISPUTE_KIT_CLASSIC;
539+ supportedDK[1 ] = dkID3;
540+ vm.prank (owner);
541+ core.createCourt (
542+ courtID2,
543+ hiddenVotes,
544+ minStake,
545+ alpha,
546+ feeForJuror,
547+ 3 , // jurors for jump. Minimal number to ensure jump after the first appeal
548+ [uint256 (60 ), uint256 (120 ), uint256 (180 ), uint256 (240 )], // Times per period
549+ sortitionExtraData,
550+ supportedDK
551+ );
552+ assertEq (core.isSupported (courtID3, dkID3), true , "dkID3 should be supported by Court3 " );
553+
554+ (courtParent, , , , , courtJurorsForCourtJump) = core.courts (courtID3);
555+ assertEq (courtParent, courtID2, "Wrong court parent for court3 " );
556+ assertEq (courtJurorsForCourtJump, 3 , "Wrong jurors for jump value for court3 " );
557+
558+ vm.prank (owner);
559+ supportedDK[0 ] = DISPUTE_KIT_CLASSIC;
560+ supportedDK[1 ] = dkID3;
561+ core.enableDisputeKits (GENERAL_COURT, supportedDK, true );
562+ assertEq (core.isSupported (GENERAL_COURT, dkID3), true , "dkID3 should be supported by GENERAL_COURT " );
563+
564+ bytes memory newExtraData = abi.encodePacked (uint256 (courtID3), DEFAULT_NB_OF_JURORS, dkID3);
565+ arbitrable.changeArbitratorExtraData (newExtraData);
566+
567+ vm.prank (staker1);
568+ core.setStake (courtID3, 20000 );
569+ vm.prank (disputer);
570+ arbitrable.createDispute {value: feeForJuror * DEFAULT_NB_OF_JURORS}("Action " );
571+ vm.warp (block .timestamp + minStakingTime);
572+ sortitionModule.passPhase (); // Generating
573+ vm.warp (block .timestamp + rngLookahead);
574+ sortitionModule.passPhase (); // Drawing phase
575+
576+ // Round1 //
577+
578+ KlerosCore.Round memory round = core.getRoundInfo (disputeID, 0 );
579+ assertEq (round.disputeKitID, dkID3, "Wrong DK ID " );
580+
581+ assertEq (disputeKit3.coreDisputeIDToActive (disputeID), true , "Should be true for dk3 " );
582+ assertEq (disputeKit3.coreDisputeIDToLocal (disputeID), 0 , "Wrong local dispute ID to core dispute ID " );
583+ assertEq (disputeKit3.getNumberOfRounds (0 ), 1 , "Wrong number of rounds dk3 " ); // local dispute id
584+ (, uint256 localRoundID ) = disputeKit3.getLocalDisputeRoundID (disputeID, 0 );
585+ assertEq (localRoundID, 0 , "Wrong local round ID dk3 " );
586+
587+ (, bool jumped , ) = disputeKit3.disputes (0 );
588+ assertEq (jumped, false , "jumped should be false in dk3 " );
589+
590+ core.draw (disputeID, DEFAULT_NB_OF_JURORS);
591+ vm.warp (block .timestamp + timesPerPeriod[0 ]);
592+ core.passPeriod (disputeID); // Vote
593+
594+ uint256 [] memory voteIDs = new uint256 [](3 );
595+ voteIDs[0 ] = 0 ;
596+ voteIDs[1 ] = 1 ;
597+ voteIDs[2 ] = 2 ;
598+
599+ vm.prank (staker1);
600+ disputeKit3.castVote (disputeID, voteIDs, 2 , 0 , "XYZ " );
601+
602+ core.passPeriod (disputeID); // Appeal
603+
604+ vm.prank (crowdfunder1);
605+ disputeKit3.fundAppeal {value: 0.63 ether }(disputeID, 1 );
606+
607+ assertEq (core.isDisputeKitJumping (disputeID), true , "Should be jumping " );
608+
609+ vm.expectEmit (true , true , true , true );
610+ emit KlerosCore.CourtJump (disputeID, 1 , courtID3, courtID2);
611+ vm.expectEmit (true , true , true , true );
612+ emit KlerosCore.DisputeKitJump (disputeID, 1 , dkID3, dkID2);
613+ vm.expectEmit (true , true , true , true );
614+ emit DisputeKitClassicBase.DisputeCreation (disputeID, 2 , newExtraData);
615+ vm.expectEmit (true , true , true , true );
616+ emit KlerosCore.AppealDecision (disputeID, arbitrable);
617+ vm.expectEmit (true , true , true , true );
618+ emit KlerosCore.NewPeriod (disputeID, KlerosCore.Period.evidence);
619+ vm.prank (crowdfunder2);
620+ disputeKit3.fundAppeal {value: 0.42 ether }(disputeID, 2 );
621+
622+ // Round2 //
623+
624+ (, jumped, ) = disputeKit3.disputes (0 );
625+ assertEq (jumped, true , "jumped should be true in dk3 " );
626+ assertEq (
627+ (disputeKit3.getFundedChoices (disputeID)).length ,
628+ 2 ,
629+ "No fresh round created so the number of funded choices should be 2 "
630+ );
631+
632+ assertEq (disputeKit3.coreDisputeIDToActive (disputeID), true , "Should still be true for dk3 " );
633+ assertEq (disputeKit3.coreDisputeIDToLocal (disputeID), 0 , "core to local ID should not change for dk3 " );
634+ assertEq (disputeKit3.getNumberOfRounds (0 ), 1 , "Wrong number of rounds dk3 " ); // local dispute id
635+ (, localRoundID) = disputeKit3.getLocalDisputeRoundID (disputeID, 0 );
636+ assertEq (localRoundID, 0 , "Local round ID should not change dk3 " );
637+
638+ round = core.getRoundInfo (disputeID, 1 );
639+ assertEq (round.disputeKitID, dkID2, "Wrong DK ID " );
640+ assertEq (sortitionModule.disputesWithoutJurors (), 1 , "Wrong disputesWithoutJurors count " );
641+ (uint96 courtID , , , , ) = core.disputes (disputeID);
642+ assertEq (courtID, courtID2, "Wrong court ID after jump " );
643+
644+ (, jumped, ) = disputeKit2.disputes (0 );
645+ assertEq (jumped, false , "jumped should be false in the DK that dispute jumped to " );
646+
647+ assertEq (disputeKit2.coreDisputeIDToActive (disputeID), true , "Should be true for dk2 " );
648+ assertEq (disputeKit2.coreDisputeIDToLocal (disputeID), 0 , "Wrong local dispute ID to core dispute ID dk2 " );
649+ assertEq (disputeKit2.getNumberOfRounds (0 ), 1 , "Wrong number of rounds dk2 " ); // local dispute id
650+ (, localRoundID) = disputeKit2.getLocalDisputeRoundID (disputeID, 1 );
651+ assertEq (localRoundID, 0 , "Wrong local round ID for dk2 " );
652+
653+ vm.prank (address (core));
654+ vm.expectRevert (DisputeKitClassicBase.DisputeJumpedToParentDK.selector );
655+ disputeKit3.draw (disputeID, 1 );
656+
657+ core.draw (disputeID, 7 ); // New round requires 7 jurors
658+ vm.warp (block .timestamp + timesPerPeriod[0 ]);
659+ core.passPeriod (disputeID); // Vote
660+
661+ voteIDs = new uint256 [](7 );
662+ for (uint256 i = 0 ; i < voteIDs.length ; i++ ) {
663+ voteIDs[i] = i;
664+ }
665+
666+ vm.prank (staker1);
667+ vm.expectRevert (DisputeKitClassicBase.DisputeJumpedToParentDK.selector );
668+ disputeKit3.castVote (disputeID, voteIDs, 2 , 0 , "XYZ " );
669+
670+ vm.prank (staker1);
671+ disputeKit2.castVote (disputeID, voteIDs, 2 , 0 , "XYZ " );
672+
673+ core.passPeriod (disputeID); // Appeal
674+
675+ vm.prank (crowdfunder1);
676+ vm.expectRevert (DisputeKitClassicBase.DisputeJumpedToParentDK.selector );
677+ disputeKit3.fundAppeal {value: 1.35 ether }(disputeID, 1 );
678+
679+ assertEq (core.isDisputeKitJumping (disputeID), true , "Should be jumping " );
680+
681+ vm.prank (crowdfunder1);
682+ // appealCost is 0.45. (0.03 * 15)
683+ disputeKit2.fundAppeal {value: 1.35 ether }(disputeID, 1 ); // 0.45 + (0.45 * 20000/10000).
684+
685+ vm.expectEmit (true , true , true , true );
686+ emit KlerosCore.CourtJump (disputeID, 2 , courtID2, GENERAL_COURT);
687+ vm.expectEmit (true , true , true , true );
688+ emit KlerosCore.DisputeKitJump (disputeID, 2 , dkID2, dkID3);
689+ vm.prank (crowdfunder2);
690+ disputeKit2.fundAppeal {value: 0.9 ether }(disputeID, 2 ); // 0.45 + (0.45 * 10000/10000).
691+
692+ // Round3 //
693+
694+ (, jumped, ) = disputeKit2.disputes (0 );
695+ assertEq (jumped, true , "jumped should be true in dk2 " );
696+ assertEq (
697+ (disputeKit2.getFundedChoices (disputeID)).length ,
698+ 2 ,
699+ "No fresh round created so the number of funded choices should be 2 for dk2 "
700+ );
701+ assertEq (
702+ disputeKit3.getFundedChoices (disputeID).length ,
703+ 0 ,
704+ "Should be 0 funded choices in dk3 because fresh round "
705+ );
706+
707+ assertEq (disputeKit3.coreDisputeIDToActive (disputeID), true , "Should be true for dk3 round3 " );
708+ assertEq (disputeKit3.coreDisputeIDToLocal (disputeID), 0 , "core to local ID should stay the same for dk3 " );
709+ assertEq (disputeKit3.getNumberOfRounds (0 ), 2 , "Wrong number of rounds dk3 round3 " ); // local dispute id
710+ (, localRoundID) = disputeKit3.getLocalDisputeRoundID (disputeID, 2 );
711+ assertEq (localRoundID, 1 , "Wrong local round id for dk3 round3 " );
712+
713+ round = core.getRoundInfo (disputeID, 2 );
714+ assertEq (round.disputeKitID, dkID3, "Wrong DK ID " );
715+ assertEq (sortitionModule.disputesWithoutJurors (), 1 , "Wrong disputesWithoutJurors count " );
716+ (courtID, , , , ) = core.disputes (disputeID);
717+ assertEq (courtID, GENERAL_COURT, "Wrong court ID after jump " );
718+
719+ (, jumped, ) = disputeKit3.disputes (0 ); // local dispute id
720+ assertEq (jumped, false , "jumped should be false in the DK that dispute jumped to " );
721+
722+ assertEq (disputeKit2.coreDisputeIDToActive (disputeID), true , "Should be true for dk2 round3 " );
723+ assertEq (
724+ disputeKit2.coreDisputeIDToLocal (disputeID),
725+ 0 ,
726+ "Wrong local dispute ID to core dispute ID dk2 round3 "
727+ );
728+ assertEq (disputeKit2.getNumberOfRounds (0 ), 1 , "Wrong number of rounds dk2 round3 " ); // local dispute id
729+ (, localRoundID) = disputeKit2.getLocalDisputeRoundID (disputeID, 1 );
730+ assertEq (localRoundID, 0 , "Wrong local round ID for dk2 round3 " );
731+
732+ vm.prank (address (core));
733+ vm.expectRevert (DisputeKitClassicBase.DisputeJumpedToParentDK.selector );
734+ disputeKit2.draw (disputeID, 1 );
735+
736+ core.draw (disputeID, 15 ); // New round requires 15 jurors
737+ vm.warp (block .timestamp + timesPerPeriod[0 ]);
738+ core.passPeriod (disputeID); // Vote
739+
740+ voteIDs = new uint256 [](15 );
741+ for (uint256 i = 0 ; i < voteIDs.length ; i++ ) {
742+ voteIDs[i] = i;
743+ }
744+
745+ vm.prank (staker1);
746+ vm.expectRevert (DisputeKitClassicBase.DisputeJumpedToParentDK.selector );
747+ disputeKit2.castVote (disputeID, voteIDs, 2 , 0 , "XYZ " );
748+
749+ vm.prank (staker1);
750+ disputeKit3.castVote (disputeID, voteIDs, 2 , 0 , "XYZ " );
751+ }
752+
470753 function test_appeal_quickPassPeriod () public {
471754 uint256 disputeID = 0 ;
472755
0 commit comments