Skip to content

Commit 00be694

Browse files
fix(DK): allow recurring DK
1 parent decd3e7 commit 00be694

File tree

2 files changed

+297
-6
lines changed

2 files changed

+297
-6
lines changed

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -206,20 +206,28 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
206206
bytes calldata _extraData,
207207
uint256 /*_nbVotes*/
208208
) external override onlyByCore {
209-
uint256 localDisputeID = disputes.length;
210-
Dispute storage dispute = disputes.push();
209+
uint256 localDisputeID;
210+
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]) {
213+
localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
214+
dispute = disputes[localDisputeID];
215+
dispute.jumped = false;
216+
} else {
217+
localDisputeID = disputes.length;
218+
dispute = disputes.push();
219+
coreDisputeIDToLocal[_coreDisputeID] = localDisputeID;
220+
coreDisputeIDToActive[_coreDisputeID] = true;
221+
}
222+
211223
dispute.numberOfChoices = _numberOfChoices;
212224
dispute.extraData = _extraData;
213-
dispute.jumped = false; // Possibly true if this DK has jumped in a previous round.
214-
215225
// New round in the Core should be created before the dispute creation in DK.
216226
dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID) - 1] = dispute.rounds.length;
217227

218228
Round storage round = dispute.rounds.push();
219229
round.tied = true;
220230

221-
coreDisputeIDToLocal[_coreDisputeID] = localDisputeID;
222-
coreDisputeIDToActive[_coreDisputeID] = true;
223231
emit DisputeCreation(_coreDisputeID, _numberOfChoices, _extraData);
224232
}
225233

contracts/test/foundry/KlerosCore_Appeals.t.sol

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)