Skip to content

Commit 8edeec4

Browse files
fix: tied choices coherence M-07
1 parent 053ea20 commit 8edeec4

File tree

2 files changed

+83
-5
lines changed

2 files changed

+83
-5
lines changed

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
4444
uint256 feeRewards; // Sum of reimbursable appeal fees available to the parties that made contributions to the ruling that ultimately wins a dispute.
4545
uint256[] fundedChoices; // Stores the choices that are fully funded.
4646
mapping(address drawnAddress => bool) alreadyDrawn; // True if the address has already been drawn, false by default.
47-
uint256[10] __gap; // Reserved slots for future upgrades.
47+
uint256 winningChoiceCount; // Number of votes for the winning choice.
48+
uint256 numberOfWinningChoices; // Number of choices that are currently top voted.
49+
uint256[8] __gap; // Reserved slots for future upgrades.
4850
}
4951

5052
struct Vote {
@@ -378,15 +380,20 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
378380
round.counts[_choice] += _voteIDs.length;
379381
if (_choice == round.winningChoice) {
380382
if (round.tied) round.tied = false;
383+
round.winningChoiceCount = round.counts[_choice];
384+
round.numberOfWinningChoices = 1;
381385
} else {
382386
// Voted for another choice.
383387
if (round.counts[_choice] == round.counts[round.winningChoice]) {
384388
// Tie.
385389
if (!round.tied) round.tied = true;
390+
round.numberOfWinningChoices++;
386391
} else if (round.counts[_choice] > round.counts[round.winningChoice]) {
387392
// New winner.
388393
round.winningChoice = _choice;
389394
round.tied = false;
395+
round.winningChoiceCount = round.counts[_choice];
396+
round.numberOfWinningChoices = 1;
390397
}
391398
}
392399
emit VoteCast(_coreDisputeID, _juror, _voteIDs, _choice, _justification);
@@ -588,10 +595,13 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
588595
) internal view returns (uint256 coherence) {
589596
// In this contract this degree can be either 0 or 1, but in other dispute kits this value can be something in between.
590597
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
591-
Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID];
592-
(uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID);
598+
uint256 currentRoundID = dispute.coreRoundIDToLocal[_coreRoundID];
599+
Round storage currentRound = dispute.rounds[currentRoundID];
600+
Vote storage vote = dispute.rounds[currentRoundID].votes[_voteID];
601+
(uint256 winningChoice, , ) = core.currentRuling(_coreDisputeID);
593602

594-
if (vote.voted && (vote.choice == winningChoice || tied)) {
603+
if (vote.voted && (currentRound.counts[vote.choice] == currentRound.winningChoiceCount)) {
604+
// If several choices match the winning choice count they all become coherent.
595605
return ONE_BASIS_POINT;
596606
} else {
597607
return 0;
@@ -607,7 +617,8 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
607617
if (currentRound.totalVoted == 0 || (!tied && currentRound.counts[winningChoice] == 0)) {
608618
return 0;
609619
} else if (tied) {
610-
return currentRound.totalVoted;
620+
// All votes casted for top choices are considered coherent
621+
return currentRound.numberOfWinningChoices * currentRound.winningChoiceCount;
611622
} else {
612623
return currentRound.counts[winningChoice];
613624
}

contracts/test/foundry/KlerosCore_Execution.t.sol

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,73 @@ contract KlerosCore_ExecutionTest is KlerosCore_TestBase {
153153
assertEq(pinakion.balanceOf(staker2), 999999999999980000, "Wrong token balance of staker2");
154154
}
155155

156+
function test_execute_multipleWinners() public {
157+
uint256 disputeID = 0;
158+
uint256 numberOfOptions = 5; // 5 choices, 2 will be winners, the rest - losers
159+
160+
arbitrable.changeNumberOfRulingOptions(numberOfOptions);
161+
162+
vm.prank(staker1);
163+
core.setStake(GENERAL_COURT, 10000);
164+
vm.prank(disputer);
165+
arbitrable.createDispute{value: feeForJuror * 5}("Action"); // 5 jurors, with future votes distribution 2-2-1-0-0
166+
vm.warp(block.timestamp + minStakingTime);
167+
sortitionModule.passPhase(); // Generating
168+
vm.warp(block.timestamp + rngLookahead);
169+
sortitionModule.passPhase(); // Drawing phase
170+
171+
core.draw(disputeID, 5);
172+
173+
vm.warp(block.timestamp + timesPerPeriod[0]);
174+
core.passPeriod(disputeID); // Vote
175+
176+
uint256[] memory voteIDs = new uint256[](2);
177+
voteIDs[0] = 0;
178+
voteIDs[1] = 1;
179+
vm.prank(staker1);
180+
disputeKit.castVote(disputeID, voteIDs, 1, 0, "XYZ"); // Cast first 2 votes for 1st choice
181+
182+
voteIDs = new uint256[](2);
183+
voteIDs[0] = 2;
184+
voteIDs[1] = 3;
185+
vm.prank(staker1);
186+
disputeKit.castVote(disputeID, voteIDs, 2, 0, "XYZ"); // Cast next 2 votes for 2nd choice
187+
188+
voteIDs = new uint256[](1);
189+
voteIDs[0] = 4;
190+
vm.prank(staker1);
191+
disputeKit.castVote(disputeID, voteIDs, 3, 0, "XYZ"); // Cast the last vote for 3rd choice.
192+
core.passPeriod(disputeID); // Appeal
193+
194+
vm.warp(block.timestamp + timesPerPeriod[3]);
195+
core.passPeriod(disputeID); // Execution
196+
197+
assertEq(disputeKit.getCoherentCount(disputeID, 0), 4, "Wrong coherent count"); // Votes casted for first 2 choices are coherent
198+
199+
uint256 pnkCoherence;
200+
uint256 feeCoherence;
201+
// dispute, round, voteID, feeForJuror (not used in classic DK), pnkPerJuror (not used in classic DK)
202+
(pnkCoherence, feeCoherence) = disputeKit.getDegreeOfCoherenceReward(disputeID, 0, 0, 0, 0);
203+
assertEq(pnkCoherence, 10000, "Wrong reward pnk coherence 0 vote ID");
204+
assertEq(feeCoherence, 10000, "Wrong reward fee coherence 0 vote ID");
205+
206+
(pnkCoherence, feeCoherence) = disputeKit.getDegreeOfCoherenceReward(disputeID, 0, 1, 0, 0);
207+
assertEq(pnkCoherence, 10000, "Wrong reward pnk coherence 1 vote ID");
208+
assertEq(feeCoherence, 10000, "Wrong reward fee coherence 1 vote ID");
209+
210+
(pnkCoherence, feeCoherence) = disputeKit.getDegreeOfCoherenceReward(disputeID, 0, 2, 0, 0);
211+
assertEq(pnkCoherence, 10000, "Wrong reward pnk coherence 2 vote ID");
212+
assertEq(feeCoherence, 10000, "Wrong reward fee coherence 2 vote ID");
213+
214+
(pnkCoherence, feeCoherence) = disputeKit.getDegreeOfCoherenceReward(disputeID, 0, 3, 0, 0);
215+
assertEq(pnkCoherence, 10000, "Wrong reward pnk coherence 3 vote ID");
216+
assertEq(feeCoherence, 10000, "Wrong reward fee coherence 3 vote ID");
217+
218+
(pnkCoherence, feeCoherence) = disputeKit.getDegreeOfCoherenceReward(disputeID, 0, 4, 0, 0);
219+
assertEq(pnkCoherence, 0, "Wrong reward pnk coherence 4 vote ID");
220+
assertEq(feeCoherence, 0, "Wrong reward fee coherence 4 vote ID");
221+
}
222+
156223
function test_execute_maxStakeCheck() public {
157224
uint256 disputeID = 0;
158225

0 commit comments

Comments
 (0)