-
Notifications
You must be signed in to change notification settings - Fork 47
/
Copy pathDisputeKitClassic.sol
617 lines (543 loc) · 29.3 KB
/
DisputeKitClassic.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
// SPDX-License-Identifier: MIT
/// @custom:authors: [@unknownunknown1, @jaybuidl]
/// @custom:reviewers: []
/// @custom:auditors: []
/// @custom:bounties: []
/// @custom:deployments: []
pragma solidity 0.8.24;
import "../KlerosCore.sol";
import "../interfaces/IDisputeKit.sol";
import "../../proxy/UUPSProxiable.sol";
import "../../proxy/Initializable.sol";
/// @title DisputeKitClassic
/// Dispute kit implementation of the Kleros v1 features including:
/// - a drawing system: proportional to staked PNK,
/// - a vote aggregation system: plurality,
/// - an incentive system: equal split between coherent votes,
/// - an appeal system: fund 2 choices only, vote on any choice.
contract DisputeKitClassic is IDisputeKit, Initializable, UUPSProxiable {
// ************************************* //
// * Structs * //
// ************************************* //
struct Dispute {
Round[] rounds; // Rounds of the dispute. 0 is the default round, and [1, ..n] are the appeal rounds.
uint256 numberOfChoices; // The number of choices jurors have when voting. This does not include choice `0` which is reserved for "refuse to arbitrate".
bool jumped; // True if dispute jumped to a parent dispute kit and won't be handled by this DK anymore.
mapping(uint256 => uint256) coreRoundIDToLocal; // Maps id of the round in the core contract to the index of the round of related local dispute.
bytes extraData; // Extradata for the dispute.
}
struct Round {
Vote[] votes; // Former votes[_appeal][].
uint256 winningChoice; // The choice with the most votes. Note that in the case of a tie, it is the choice that reached the tied number of votes first.
mapping(uint256 => uint256) counts; // The sum of votes for each choice in the form `counts[choice]`.
bool tied; // True if there is a tie, false otherwise.
uint256 totalVoted; // Former uint[_appeal] votesInEachRound.
uint256 totalCommitted; // Former commitsInRound.
mapping(uint256 choiceId => uint256) paidFees; // Tracks the fees paid for each choice in this round.
mapping(uint256 choiceId => bool) hasPaid; // True if this choice was fully funded, false otherwise.
mapping(address account => mapping(uint256 choiceId => uint256)) contributions; // Maps contributors to their contributions for each choice.
uint256 feeRewards; // Sum of reimbursable appeal fees available to the parties that made contributions to the ruling that ultimately wins a dispute.
uint256[] fundedChoices; // Stores the choices that are fully funded.
uint256 nbVotes; // Maximal number of votes this dispute can get.
}
struct Vote {
address account; // The address of the juror.
bytes32 commit; // The commit of the juror. For courts with hidden votes.
uint256 choice; // The choice of the juror.
bool voted; // True if the vote has been cast.
}
// ************************************* //
// * Storage * //
// ************************************* //
uint256 public constant WINNER_STAKE_MULTIPLIER = 10000; // Multiplier of the appeal cost that the winner has to pay as fee stake for a round in basis points. Default is 1x of appeal fee.
uint256 public constant LOSER_STAKE_MULTIPLIER = 20000; // Multiplier of the appeal cost that the loser has to pay as fee stake for a round in basis points. Default is 2x of appeal fee.
uint256 public constant LOSER_APPEAL_PERIOD_MULTIPLIER = 5000; // Multiplier of the appeal period for the choice that wasn't voted for in the previous round, in basis points. Default is 1/2 of original appeal period.
uint256 public constant ONE_BASIS_POINT = 10000; // One basis point, for scaling.
address public governor; // The governor of the contract.
KlerosCore public core; // The Kleros Core arbitrator
Dispute[] public disputes; // Array of the locally created disputes.
mapping(uint256 => uint256) public coreDisputeIDToLocal; // Maps the dispute ID in Kleros Core to the local dispute ID.
// ************************************* //
// * Events * //
// ************************************* //
/// @dev To be emitted when a dispute is created.
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
/// @param _numberOfChoices The number of choices available in the dispute.
/// @param _extraData The extra data for the dispute.
event DisputeCreation(uint256 indexed _coreDisputeID, uint256 _numberOfChoices, bytes _extraData);
/// @dev To be emitted when a vote commitment is cast.
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
/// @param _juror The address of the juror casting the vote commitment.
/// @param _voteIDs The identifiers of the votes in the dispute.
/// @param _commit The commitment of the juror.
event CommitCast(uint256 indexed _coreDisputeID, address indexed _juror, uint256[] _voteIDs, bytes32 _commit);
/// @dev To be emitted when a funding contribution is made.
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
/// @param _coreRoundID The identifier of the round in the Arbitrator contract.
/// @param _choice The choice that is being funded.
/// @param _contributor The address of the contributor.
/// @param _amount The amount contributed.
event Contribution(
uint256 indexed _coreDisputeID,
uint256 indexed _coreRoundID,
uint256 _choice,
address indexed _contributor,
uint256 _amount
);
/// @dev To be emitted when the contributed funds are withdrawn.
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
/// @param _coreRoundID The identifier of the round in the Arbitrator contract.
/// @param _choice The choice that is being funded.
/// @param _contributor The address of the contributor.
/// @param _amount The amount withdrawn.
event Withdrawal(
uint256 indexed _coreDisputeID,
uint256 indexed _coreRoundID,
uint256 _choice,
address indexed _contributor,
uint256 _amount
);
/// @dev To be emitted when a choice is fully funded for an appeal.
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
/// @param _coreRoundID The identifier of the round in the Arbitrator contract.
/// @param _choice The choice that is being funded.
event ChoiceFunded(uint256 indexed _coreDisputeID, uint256 indexed _coreRoundID, uint256 indexed _choice);
// ************************************* //
// * Modifiers * //
// ************************************* //
modifier onlyByGovernor() {
require(governor == msg.sender, "Access not allowed: Governor only.");
_;
}
modifier onlyByCore() {
require(address(core) == msg.sender, "Access not allowed: KlerosCore only.");
_;
}
modifier notJumped(uint256 _coreDisputeID) {
require(!disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped, "Dispute jumped to a parent DK!");
_;
}
// ************************************* //
// * Constructor * //
// ************************************* //
/// @dev Constructor, initializing the implementation to reduce attack surface.
constructor() {
_disableInitializers();
}
/// @dev Initializer.
/// @param _governor The governor's address.
/// @param _core The KlerosCore arbitrator.
function initialize(address _governor, KlerosCore _core) external reinitializer(1) {
governor = _governor;
core = _core;
}
// ************************ //
// * Governance * //
// ************************ //
/// @dev Access Control to perform implementation upgrades (UUPS Proxiable)
/// Only the governor can perform upgrades (`onlyByGovernor`)
function _authorizeUpgrade(address) internal view override onlyByGovernor {
// NOP
}
/// @dev Allows the governor to call anything on behalf of the contract.
/// @param _destination The destination of the call.
/// @param _amount The value sent with the call.
/// @param _data The data sent with the call.
function executeGovernorProposal(
address _destination,
uint256 _amount,
bytes memory _data
) external onlyByGovernor {
(bool success, ) = _destination.call{value: _amount}(_data);
require(success, "Unsuccessful call");
}
/// @dev Changes the `governor` storage variable.
/// @param _governor The new value for the `governor` storage variable.
function changeGovernor(address payable _governor) external onlyByGovernor {
governor = _governor;
}
/// @dev Changes the `core` storage variable.
/// @param _core The new value for the `core` storage variable.
function changeCore(address _core) external onlyByGovernor {
core = KlerosCore(_core);
}
// ************************************* //
// * State Modifiers * //
// ************************************* //
/// @dev Creates a local dispute and maps it to the dispute ID in the Core contract.
/// Note: Access restricted to Kleros Core only.
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @param _numberOfChoices Number of choices of the dispute
/// @param _extraData Additional info about the dispute, for possible use in future dispute kits.
/// @param _nbVotes Number of votes for this dispute.
function createDispute(
uint256 _coreDisputeID,
uint256 _numberOfChoices,
bytes calldata _extraData,
uint256 _nbVotes
) external override onlyByCore {
uint256 localDisputeID = disputes.length;
Dispute storage dispute = disputes.push();
dispute.numberOfChoices = _numberOfChoices;
dispute.extraData = _extraData;
// New round in the Core should be created before the dispute creation in DK.
dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID) - 1] = dispute.rounds.length;
Round storage round = dispute.rounds.push();
round.nbVotes = _nbVotes;
round.tied = true;
coreDisputeIDToLocal[_coreDisputeID] = localDisputeID;
emit DisputeCreation(_coreDisputeID, _numberOfChoices, _extraData);
}
/// @dev Draws the juror from the sortition tree. The drawn address is picked up by Kleros Core.
/// Note: Access restricted to Kleros Core only.
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @param _nonce Nonce of the drawing iteration.
/// @return drawnAddress The drawn address.
function draw(
uint256 _coreDisputeID,
uint256 _nonce
) external override onlyByCore notJumped(_coreDisputeID) returns (address drawnAddress) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage round = dispute.rounds[dispute.rounds.length - 1];
ISortitionModule sortitionModule = core.sortitionModule();
(uint96 courtID, , , , ) = core.disputes(_coreDisputeID);
bytes32 key = bytes32(uint256(courtID)); // Get the ID of the tree.
drawnAddress = sortitionModule.draw(key, _coreDisputeID, _nonce);
if (_postDrawCheck(_coreDisputeID, drawnAddress)) {
round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false}));
} else {
drawnAddress = address(0);
}
}
/// @dev Sets the caller's commit for the specified votes. It can be called multiple times during the
/// commit period, each call overrides the commits of the previous one.
/// `O(n)` where
/// `n` is the number of votes.
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @param _voteIDs The IDs of the votes.
/// @param _commit The commit. Note that justification string is a part of the commit.
function castCommit(
uint256 _coreDisputeID,
uint256[] calldata _voteIDs,
bytes32 _commit
) external notJumped(_coreDisputeID) {
(, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID);
require(period == KlerosCoreBase.Period.commit, "The dispute should be in Commit period.");
require(_commit != bytes32(0), "Empty commit.");
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage round = dispute.rounds[dispute.rounds.length - 1];
for (uint256 i = 0; i < _voteIDs.length; i++) {
require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote.");
round.votes[_voteIDs[i]].commit = _commit;
}
round.totalCommitted += _voteIDs.length;
emit CommitCast(_coreDisputeID, msg.sender, _voteIDs, _commit);
}
/// @dev Sets the caller's choices for the specified votes.
/// `O(n)` where
/// `n` is the number of votes.
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @param _voteIDs The IDs of the votes.
/// @param _choice The choice.
/// @param _salt The salt for the commit if the votes were hidden.
/// @param _justification Justification of the choice.
function castVote(
uint256 _coreDisputeID,
uint256[] calldata _voteIDs,
uint256 _choice,
uint256 _salt,
string memory _justification
) external notJumped(_coreDisputeID) {
(, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID);
require(period == KlerosCoreBase.Period.vote, "The dispute should be in Vote period.");
require(_voteIDs.length > 0, "No voteID provided");
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
require(_choice <= dispute.numberOfChoices, "Choice out of bounds");
Round storage round = dispute.rounds[dispute.rounds.length - 1];
(uint96 courtID, , , , ) = core.disputes(_coreDisputeID);
(, bool hiddenVotes, , , , , ) = core.courts(courtID);
// Save the votes.
for (uint256 i = 0; i < _voteIDs.length; i++) {
require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote.");
require(
!hiddenVotes || round.votes[_voteIDs[i]].commit == keccak256(abi.encodePacked(_choice, _salt)),
"The commit must match the choice in courts with hidden votes."
);
require(!round.votes[_voteIDs[i]].voted, "Vote already cast.");
round.votes[_voteIDs[i]].choice = _choice;
round.votes[_voteIDs[i]].voted = true;
}
round.totalVoted += _voteIDs.length;
round.counts[_choice] += _voteIDs.length;
if (_choice == round.winningChoice) {
if (round.tied) round.tied = false;
} else {
// Voted for another choice.
if (round.counts[_choice] == round.counts[round.winningChoice]) {
// Tie.
if (!round.tied) round.tied = true;
} else if (round.counts[_choice] > round.counts[round.winningChoice]) {
// New winner.
round.winningChoice = _choice;
round.tied = false;
}
}
emit VoteCast(_coreDisputeID, msg.sender, _voteIDs, _choice, _justification);
}
/// @dev Manages contributions, and appeals a dispute if at least two choices are fully funded.
/// Note that the surplus deposit will be reimbursed.
/// @param _coreDisputeID Index of the dispute in Kleros Core.
/// @param _choice A choice that receives funding.
function fundAppeal(uint256 _coreDisputeID, uint256 _choice) external payable notJumped(_coreDisputeID) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund.");
(uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_coreDisputeID);
require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over.");
uint256 multiplier;
(uint256 ruling, , ) = this.currentRuling(_coreDisputeID);
if (ruling == _choice) {
multiplier = WINNER_STAKE_MULTIPLIER;
} else {
require(
block.timestamp - appealPeriodStart <
((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / ONE_BASIS_POINT,
"Appeal period is over for loser"
);
multiplier = LOSER_STAKE_MULTIPLIER;
}
Round storage round = dispute.rounds[dispute.rounds.length - 1];
uint256 coreRoundID = core.getNumberOfRounds(_coreDisputeID) - 1;
require(!round.hasPaid[_choice], "Appeal fee is already paid.");
uint256 appealCost = core.appealCost(_coreDisputeID);
uint256 totalCost = appealCost + (appealCost * multiplier) / ONE_BASIS_POINT;
// Take up to the amount necessary to fund the current round at the current costs.
uint256 contribution;
if (totalCost > round.paidFees[_choice]) {
contribution = totalCost - round.paidFees[_choice] > msg.value // Overflows and underflows will be managed on the compiler level.
? msg.value
: totalCost - round.paidFees[_choice];
emit Contribution(_coreDisputeID, coreRoundID, _choice, msg.sender, contribution);
}
round.contributions[msg.sender][_choice] += contribution;
round.paidFees[_choice] += contribution;
if (round.paidFees[_choice] >= totalCost) {
round.feeRewards += round.paidFees[_choice];
round.fundedChoices.push(_choice);
round.hasPaid[_choice] = true;
emit ChoiceFunded(_coreDisputeID, coreRoundID, _choice);
}
if (round.fundedChoices.length > 1) {
// At least two sides are fully funded.
round.feeRewards = round.feeRewards - appealCost;
if (core.isDisputeKitJumping(_coreDisputeID)) {
// Don't create a new round in case of a jump, and remove local dispute from the flow.
dispute.jumped = true;
} else {
// Don't subtract 1 from length since both round arrays haven't been updated yet.
dispute.coreRoundIDToLocal[coreRoundID + 1] = dispute.rounds.length;
Round storage newRound = dispute.rounds.push();
newRound.nbVotes = core.getNumberOfVotes(_coreDisputeID);
newRound.tied = true;
}
core.appeal{value: appealCost}(_coreDisputeID, dispute.numberOfChoices, dispute.extraData);
}
if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution);
}
/// @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved.
/// Note that withdrawals are not possible if the core contract is paused.
/// @param _coreDisputeID Index of the dispute in Kleros Core contract.
/// @param _beneficiary The address whose rewards to withdraw.
/// @param _coreRoundID The round in the Kleros Core contract the caller wants to withdraw from.
/// @param _choice The ruling option that the caller wants to withdraw from.
/// @return amount The withdrawn amount.
function withdrawFeesAndRewards(
uint256 _coreDisputeID,
address payable _beneficiary,
uint256 _coreRoundID,
uint256 _choice
) external returns (uint256 amount) {
(, , , bool isRuled, ) = core.disputes(_coreDisputeID);
require(isRuled, "Dispute should be resolved.");
require(!core.paused(), "Core is paused");
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]];
(uint256 finalRuling, , ) = core.currentRuling(_coreDisputeID);
if (!round.hasPaid[_choice]) {
// Allow to reimburse if funding was unsuccessful for this ruling option.
amount = round.contributions[_beneficiary][_choice];
} else {
// Funding was successful for this ruling option.
if (_choice == finalRuling) {
// This ruling option is the ultimate winner.
amount = round.paidFees[_choice] > 0
? (round.contributions[_beneficiary][_choice] * round.feeRewards) / round.paidFees[_choice]
: 0;
} else if (!round.hasPaid[finalRuling]) {
// The ultimate winner was not funded in this round. In this case funded ruling option(s) are reimbursed.
amount =
(round.contributions[_beneficiary][_choice] * round.feeRewards) /
(round.paidFees[round.fundedChoices[0]] + round.paidFees[round.fundedChoices[1]]);
}
}
round.contributions[_beneficiary][_choice] = 0;
if (amount != 0) {
_beneficiary.send(amount); // Deliberate use of send to prevent reverting fallback. It's the user's responsibility to accept ETH.
emit Withdrawal(_coreDisputeID, _coreRoundID, _choice, _beneficiary, amount);
}
}
// ************************************* //
// * Public Views * //
// ************************************* //
function getFundedChoices(uint256 _coreDisputeID) public view returns (uint256[] memory fundedChoices) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage lastRound = dispute.rounds[dispute.rounds.length - 1];
return lastRound.fundedChoices;
}
/// @dev Gets the current ruling of a specified dispute.
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @return ruling The current ruling.
/// @return tied Whether it's a tie or not.
/// @return overridden Whether the ruling was overridden by appeal funding or not.
function currentRuling(
uint256 _coreDisputeID
) external view override returns (uint256 ruling, bool tied, bool overridden) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage round = dispute.rounds[dispute.rounds.length - 1];
tied = round.tied;
ruling = tied ? 0 : round.winningChoice;
(, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID);
// Override the final ruling if only one side funded the appeals.
if (period == KlerosCoreBase.Period.execution) {
uint256[] memory fundedChoices = getFundedChoices(_coreDisputeID);
if (fundedChoices.length == 1) {
ruling = fundedChoices[0];
tied = false;
overridden = true;
}
}
}
/// @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the reward.
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit.
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit.
/// @param _voteID The ID of the vote.
/// @return The degree of coherence in basis points.
function getDegreeOfCoherence(
uint256 _coreDisputeID,
uint256 _coreRoundID,
uint256 _voteID,
uint256 /* _feePerJuror */,
uint256 /* _pnkAtStakePerJuror */
) external view override returns (uint256) {
// In this contract this degree can be either 0 or 1, but in other dispute kits this value can be something in between.
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID];
(uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID);
if (vote.voted && (vote.choice == winningChoice || tied)) {
return ONE_BASIS_POINT;
} else {
return 0;
}
}
/// @dev Gets the number of jurors who are eligible to a reward in this round.
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit.
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit.
/// @return The number of coherent jurors.
function getCoherentCount(uint256 _coreDisputeID, uint256 _coreRoundID) external view override returns (uint256) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage currentRound = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]];
(uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID);
if (currentRound.totalVoted == 0 || (!tied && currentRound.counts[winningChoice] == 0)) {
return 0;
} else if (tied) {
return currentRound.totalVoted;
} else {
return currentRound.counts[winningChoice];
}
}
/// @dev Returns true if all of the jurors have cast their commits for the last round.
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @return Whether all of the jurors have cast their commits for the last round.
function areCommitsAllCast(uint256 _coreDisputeID) external view override returns (bool) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage round = dispute.rounds[dispute.rounds.length - 1];
return round.totalCommitted == round.votes.length;
}
/// @dev Returns true if all of the jurors have cast their votes for the last round.
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @return Whether all of the jurors have cast their votes for the last round.
function areVotesAllCast(uint256 _coreDisputeID) external view override returns (bool) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage round = dispute.rounds[dispute.rounds.length - 1];
return round.totalVoted == round.votes.length;
}
/// @dev Returns true if the specified voter was active in this round.
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit.
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit.
/// @param _voteID The ID of the voter.
/// @return Whether the voter was active or not.
function isVoteActive(
uint256 _coreDisputeID,
uint256 _coreRoundID,
uint256 _voteID
) external view override returns (bool) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID];
return vote.voted;
}
function getRoundInfo(
uint256 _coreDisputeID,
uint256 _coreRoundID,
uint256 _choice
)
external
view
override
returns (
uint256 winningChoice,
bool tied,
uint256 totalVoted,
uint256 totalCommited,
uint256 nbVoters,
uint256 choiceCount
)
{
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]];
return (
round.winningChoice,
round.tied,
round.totalVoted,
round.totalCommitted,
round.votes.length,
round.counts[_choice]
);
}
function getVoteInfo(
uint256 _coreDisputeID,
uint256 _coreRoundID,
uint256 _voteID
) external view override returns (address account, bytes32 commit, uint256 choice, bool voted) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID];
return (vote.account, vote.commit, vote.choice, vote.voted);
}
// ************************************* //
// * Internal * //
// ************************************* //
/// @dev Checks that the chosen address satisfies certain conditions for being drawn.
/// @param _coreDisputeID ID of the dispute in the core contract.
/// @param _juror Chosen address.
/// @return Whether the address can be drawn or not.
/// Note that we don't check the minStake requirement here because of the implicit staking in parent courts.
/// minStake is checked directly during staking process however it's possible for the juror to get drawn
/// while having < minStake if it is later increased by governance.
/// This issue is expected and harmless since we check for insolvency anyway.
function _postDrawCheck(uint256 _coreDisputeID, address _juror) internal view returns (bool) {
(uint96 courtID, , , , ) = core.disputes(_coreDisputeID);
uint256 lockedAmountPerJuror = core
.getRoundInfo(_coreDisputeID, core.getNumberOfRounds(_coreDisputeID) - 1)
.pnkAtStakePerJuror;
(uint256 totalStaked, uint256 totalLocked, , ) = core.sortitionModule().getJurorBalance(_juror, courtID);
return totalStaked >= totalLocked + lockedAmountPerJuror;
}
}