-
Notifications
You must be signed in to change notification settings - Fork 46
/
Copy pathKlerosCore.sol
1303 lines (1153 loc) · 58.5 KB
/
KlerosCore.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
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// SPDX-License-Identifier: MIT
/**
* @authors: [@unknownunknown1, @jaybuidl]
* @reviewers: []
* @auditors: []
* @bounties: []
* @deployments: []
*/
pragma solidity ^0.8;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./IArbitrator.sol";
import "./IDisputeKit.sol";
import {SortitionSumTreeFactory} from "../data-structures/SortitionSumTreeFactory.sol";
/**
* @title KlerosCore
* Core arbitrator contract for Kleros v2.
* Note that this contract trusts the token and the dispute kit contracts.
*/
contract KlerosCore is IArbitrator {
using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees.
// ************************************* //
// * Enums / Structs * //
// ************************************* //
enum Phase {
staking, // Stake can be updated during this phase.
freezing // Phase during which the dispute kits can undergo the drawing process. Staking is not allowed during this phase.
}
enum Period {
evidence, // Evidence can be submitted. This is also when drawing has to take place.
commit, // Jurors commit a hashed vote. This is skipped for courts without hidden votes.
vote, // Jurors reveal/cast their vote depending on whether the court has hidden votes or not.
appeal, // The dispute can be appealed.
execution // Tokens are redistributed and the ruling is executed.
}
struct Court {
uint96 parent; // The parent court.
bool hiddenVotes; // Whether to use commit and reveal or not.
uint256[] children; // List of child courts.
uint256 minStake; // Minimum tokens needed to stake in the court.
uint256 alpha; // Basis point of tokens that are lost when incoherent.
uint256 feeForJuror; // Arbitration fee paid per juror.
uint256 jurorsForCourtJump; // The appeal after the one that reaches this number of jurors will go to the parent court if any.
uint256[4] timesPerPeriod; // The time allotted to each dispute period in the form `timesPerPeriod[period]`.
mapping(uint256 => bool) supportedDisputeKits; // True if DK with this ID is supported by the court.
}
struct Dispute {
uint96 subcourtID; // The ID of the subcourt the dispute is in.
IArbitrable arbitrated; // The arbitrable contract.
Period period; // The current period of the dispute.
bool ruled; // True if the ruling has been executed, false otherwise.
uint256 lastPeriodChange; // The last time the period was changed.
Round[] rounds;
}
struct Round {
uint256 disputeKitID; // Index of the dispute kit in the array.
uint256 tokensAtStakePerJuror; // The amount of tokens at stake for each juror in this round.
uint256 totalFeesForJurors; // The total juror fees paid in this round.
uint256 nbVotes; // The total number of votes the dispute can possibly have in the current round. Former votes[_round].length.
uint256 repartitions; // A counter of reward repartitions made in this round.
uint256 penalties; // The amount of tokens collected from penalties in this round.
address[] drawnJurors; // Addresses of the jurors that were drawn in this round.
}
struct Juror {
uint96[] subcourtIDs; // The IDs of subcourts where the juror's stake path ends. A stake path is a path from the general court to a court the juror directly staked in using `_setStake`.
mapping(uint96 => uint256) stakedTokens; // The number of tokens the juror has staked in the subcourt in the form `stakedTokens[subcourtID]`.
mapping(uint96 => uint256) lockedTokens; // The number of tokens the juror has locked in the subcourt in the form `lockedTokens[subcourtID]`.
}
struct DisputeKitNode {
uint256 parent; // Index of the parent dispute kit. If it's 0 then this DK is a root.
uint256[] children; // List of child dispute kits.
IDisputeKit disputeKit; // The dispute kit implementation.
bool needsFreezing; // The dispute kit needs freezing.
uint256 depthLevel; // How far this DK is from the root. 0 for root DK.
}
struct DelayedStake {
address account; // The address of the juror.
uint96 subcourtID; // The ID of the subcourt.
uint256 stake; // The new stake.
uint256 penalty; // Penalty value, in case the stake was set during execution.
}
// ************************************* //
// * Storage * //
// ************************************* //
uint256 public constant FORKING_COURT = 0; // Index of the forking court.
uint256 public constant GENERAL_COURT = 1; // Index of the default (general) court.
uint256 public constant NULL_DISPUTE_KIT = 0; // Null pattern to indicate a top-level DK which has no parent.
uint256 public constant DISPUTE_KIT_CLASSIC_INDEX = 1; // Index of the default DK. 0 index is skipped.
uint256 public constant MAX_STAKE_PATHS = 4; // The maximum number of stake paths a juror can have.
uint256 public constant MIN_JURORS = 3; // The global default minimum number of jurors in a dispute.
uint256 public constant ALPHA_DIVISOR = 1e4; // The number to divide `Court.alpha` by.
uint256 public constant NON_PAYABLE_AMOUNT = (2**256 - 2) / 2; // An amount higher than the supply of ETH.
uint256 public constant SEARCH_ITERATIONS = 10; // Number of iterations to search for suitable parent court before jumping to the top court.
address public governor; // The governor of the contract.
IERC20 public pinakion; // The Pinakion token contract.
// TODO: interactions with jurorProsecutionModule.
address public jurorProsecutionModule; // The module for juror's prosecution.
Phase public phase; // The current phase.
uint256 public minStakingTime; // The time after which the phase can be switched to Freezing if there are open disputes.
uint256 public maxFreezingTime; // The time after which the phase can be switched back to Staking.
uint256 public lastPhaseChange; // The last time the phase was changed.
uint256 public freezeBlock; // Number of the block when Core was frozen.
Court[] public courts; // The subcourts.
DisputeKitNode[] public disputeKitNodes; // The list of DisputeKitNode, indexed by DisputeKitID.
uint256[] public disputesKitIDsThatNeedFreezing; // The disputeKitIDs that need switching to Freezing phase.
Dispute[] public disputes; // The disputes.
mapping(address => Juror) internal jurors; // The jurors.
SortitionSumTreeFactory.SortitionSumTrees internal sortitionSumTrees; // The sortition sum trees.
mapping(uint256 => DelayedStake) public delayedStakes; // Stores the stakes that were changed during Freezing phase, to update them when the phase is switched to Staking.
uint256 public delayedStakeWriteIndex; // The index of the last `delayedStake` item that was written to the array. 0 index is skipped.
uint256 public delayedStakeReadIndex = 1; // The index of the next `delayedStake` item that should be processed. Starts at 1 because 0 index is skipped.
// ************************************* //
// * Events * //
// ************************************* //
event NewPhase(Phase _phase);
event StakeSet(address indexed _address, uint256 _subcourtID, uint256 _amount, uint256 _newTotalStake);
event NewPeriod(uint256 indexed _disputeID, Period _period);
event AppealPossible(uint256 indexed _disputeID, IArbitrable indexed _arbitrable);
event AppealDecision(uint256 indexed _disputeID, IArbitrable indexed _arbitrable);
event Draw(address indexed _address, uint256 indexed _disputeID, uint256 _roundID, uint256 _voteID);
event CourtJump(
uint256 indexed _disputeID,
uint256 indexed _roundID,
uint96 indexed _fromSubcourtID,
uint96 _toSubcourtID
);
event DisputeKitJump(
uint256 indexed _disputeID,
uint256 indexed _roundID,
uint256 indexed _fromDisputeKitID,
uint256 _toDisputeKitID
);
event TokenAndETHShift(
address indexed _account,
uint256 indexed _disputeID,
int256 _tokenAmount,
int256 _ethAmount
);
// ************************************* //
// * Function Modifiers * //
// ************************************* //
modifier onlyByGovernor() {
require(governor == msg.sender, "Access not allowed: Governor only.");
_;
}
/** @dev Constructor.
* @param _governor The governor's address.
* @param _pinakion The address of the token contract.
* @param _jurorProsecutionModule The address of the juror prosecution module.
* @param _disputeKit The address of the default dispute kit.
* @param _phaseTimeouts minStakingTime and maxFreezingTime respectively
* @param _hiddenVotes The `hiddenVotes` property value of the general court.
* @param _courtParameters Numeric parameters of General court (minStake, alpha, feeForJuror and jurorsForCourtJump respectively).
* @param _timesPerPeriod The `timesPerPeriod` property value of the general court.
* @param _sortitionSumTreeK The number of children per node of the general court's sortition sum tree.
*/
constructor(
address _governor,
IERC20 _pinakion,
address _jurorProsecutionModule,
IDisputeKit _disputeKit,
uint256[2] memory _phaseTimeouts,
bool _hiddenVotes,
uint256[4] memory _courtParameters,
uint256[4] memory _timesPerPeriod,
uint256 _sortitionSumTreeK
) {
governor = _governor;
pinakion = _pinakion;
jurorProsecutionModule = _jurorProsecutionModule;
disputeKitNodes.push(); // NULL_DISPUTE_KIT: an empty element at index 0 to indicate when a node has no parent.
disputeKitNodes.push(
DisputeKitNode({
parent: 0,
children: new uint256[](0),
disputeKit: _disputeKit,
needsFreezing: false,
depthLevel: 0
})
);
minStakingTime = _phaseTimeouts[0];
maxFreezingTime = _phaseTimeouts[1];
lastPhaseChange = block.timestamp;
// Create the Forking court.
courts.push();
// TODO: fill the properties for Forking court.
// Create the General court.
Court storage court = courts.push();
court.parent = 1; // TODO: Should the parent for General court be 0 or 1? In the former case the Forking court will become the top court after jumping.
court.children = new uint256[](0);
court.hiddenVotes = _hiddenVotes;
court.minStake = _courtParameters[0];
court.alpha = _courtParameters[1];
court.feeForJuror = _courtParameters[2];
court.jurorsForCourtJump = _courtParameters[3];
court.timesPerPeriod = _timesPerPeriod;
court.supportedDisputeKits[DISPUTE_KIT_CLASSIC_INDEX] = true;
// TODO: fill the properties for Forking court.
sortitionSumTrees.createTree(bytes32(FORKING_COURT), _sortitionSumTreeK);
sortitionSumTrees.createTree(bytes32(GENERAL_COURT), _sortitionSumTreeK);
}
// ************************ //
// * Governance * //
// ************************ //
/** @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 `pinakion` storage variable.
* @param _pinakion The new value for the `pinakion` storage variable.
*/
function changePinakion(IERC20 _pinakion) external onlyByGovernor {
pinakion = _pinakion;
}
/** @dev Changes the `jurorProsecutionModule` storage variable.
* @param _jurorProsecutionModule The new value for the `jurorProsecutionModule` storage variable.
*/
function changeJurorProsecutionModule(address _jurorProsecutionModule) external onlyByGovernor {
jurorProsecutionModule = _jurorProsecutionModule;
}
/** @dev Changes the `minStakingTime` storage variable.
* @param _minStakingTime The new value for the `minStakingTime` storage variable.
*/
function changeMinStakingTime(uint256 _minStakingTime) external onlyByGovernor {
minStakingTime = _minStakingTime;
}
/** @dev Changes the `maxFreezingTime` storage variable.
* @param _maxFreezingTime The new value for the `maxFreezingTime` storage variable.
*/
function changeMaxFreezingTime(uint256 _maxFreezingTime) external onlyByGovernor {
maxFreezingTime = _maxFreezingTime;
}
/** @dev Add a new supported dispute kit module to the court.
* @param _disputeKitAddress The address of the dispute kit contract.
* @param _parent The ID of the parent dispute kit. It is left empty when root DK is created.
* Note that the root DK must be supported by the general court.
*/
function addNewDisputeKit(IDisputeKit _disputeKitAddress, uint256 _parent) external onlyByGovernor {
uint256 disputeKitID = disputeKitNodes.length;
require(_parent < disputeKitID, "Parent doesn't exist");
uint256 depthLevel;
// Create new tree, which root should be supported by General court.
if (_parent == NULL_DISPUTE_KIT) {
courts[GENERAL_COURT].supportedDisputeKits[disputeKitID] = true;
} else {
depthLevel = disputeKitNodes[_parent].depthLevel + 1;
// It should be always possible to reach the root from the leaf with the defined number of search iterations.
require(depthLevel < SEARCH_ITERATIONS, "Depth level is at max");
}
disputeKitNodes.push(
DisputeKitNode({
parent: _parent,
children: new uint256[](0),
disputeKit: _disputeKitAddress,
needsFreezing: false,
depthLevel: depthLevel
})
);
disputeKitNodes[_parent].children.push(disputeKitID);
}
/** @dev Creates a subcourt under a specified parent court.
* @param _parent The `parent` property value of the subcourt.
* @param _hiddenVotes The `hiddenVotes` property value of the subcourt.
* @param _minStake The `minStake` property value of the subcourt.
* @param _alpha The `alpha` property value of the subcourt.
* @param _feeForJuror The `feeForJuror` property value of the subcourt.
* @param _jurorsForCourtJump The `jurorsForCourtJump` property value of the subcourt.
* @param _timesPerPeriod The `timesPerPeriod` property value of the subcourt.
* @param _sortitionSumTreeK The number of children per node of the subcourt's sortition sum tree.
* @param _supportedDisputeKits Indexes of dispute kits that this subcourt will support.
*/
function createSubcourt(
uint96 _parent,
bool _hiddenVotes,
uint256 _minStake,
uint256 _alpha,
uint256 _feeForJuror,
uint256 _jurorsForCourtJump,
uint256[4] memory _timesPerPeriod,
uint256 _sortitionSumTreeK,
uint256[] memory _supportedDisputeKits
) external onlyByGovernor {
require(
courts[_parent].minStake <= _minStake,
"A subcourt cannot be a child of a subcourt with a higher minimum stake."
);
require(_supportedDisputeKits.length > 0, "Must support at least one DK");
require(_parent != FORKING_COURT, "Can't have Forking court as a parent");
uint256 subcourtID = courts.length;
Court storage court = courts.push();
for (uint256 i = 0; i < _supportedDisputeKits.length; i++) {
require(
_supportedDisputeKits[i] > 0 && _supportedDisputeKits[i] < disputeKitNodes.length,
"Wrong DK index"
);
court.supportedDisputeKits[_supportedDisputeKits[i]] = true;
}
court.parent = _parent;
court.children = new uint256[](0);
court.hiddenVotes = _hiddenVotes;
court.minStake = _minStake;
court.alpha = _alpha;
court.feeForJuror = _feeForJuror;
court.jurorsForCourtJump = _jurorsForCourtJump;
court.timesPerPeriod = _timesPerPeriod;
sortitionSumTrees.createTree(bytes32(subcourtID), _sortitionSumTreeK);
// Update the parent.
courts[_parent].children.push(subcourtID);
}
/** @dev Changes the `minStake` property value of a specified subcourt. Don't set to a value lower than its parent's `minStake` property value.
* @param _subcourtID The ID of the subcourt.
* @param _minStake The new value for the `minStake` property value.
*/
function changeSubcourtMinStake(uint96 _subcourtID, uint256 _minStake) external onlyByGovernor {
require(_subcourtID == GENERAL_COURT || courts[courts[_subcourtID].parent].minStake <= _minStake);
for (uint256 i = 0; i < courts[_subcourtID].children.length; i++) {
require(
courts[courts[_subcourtID].children[i]].minStake >= _minStake,
"A subcourt cannot be the parent of a subcourt with a lower minimum stake."
);
}
courts[_subcourtID].minStake = _minStake;
}
/** @dev Changes the `alpha` property value of a specified subcourt.
* @param _subcourtID The ID of the subcourt.
* @param _alpha The new value for the `alpha` property value.
*/
function changeSubcourtAlpha(uint96 _subcourtID, uint256 _alpha) external onlyByGovernor {
courts[_subcourtID].alpha = _alpha;
}
/** @dev Changes the `feeForJuror` property value of a specified subcourt.
* @param _subcourtID The ID of the subcourt.
* @param _feeForJuror The new value for the `feeForJuror` property value.
*/
function changeSubcourtJurorFee(uint96 _subcourtID, uint256 _feeForJuror) external onlyByGovernor {
courts[_subcourtID].feeForJuror = _feeForJuror;
}
/** @dev Changes the `jurorsForCourtJump` property value of a specified subcourt.
* @param _subcourtID The ID of the subcourt.
* @param _jurorsForCourtJump The new value for the `jurorsForCourtJump` property value.
*/
function changeSubcourtJurorsForJump(uint96 _subcourtID, uint256 _jurorsForCourtJump) external onlyByGovernor {
courts[_subcourtID].jurorsForCourtJump = _jurorsForCourtJump;
}
/** @dev Changes the `hiddenVotes` property value of a specified subcourt.
* @param _subcourtID The ID of the subcourt.
* @param _hiddenVotes The new value for the `hiddenVotes` property value.
*/
function changeHiddenVotes(uint96 _subcourtID, bool _hiddenVotes) external onlyByGovernor {
courts[_subcourtID].hiddenVotes = _hiddenVotes;
}
/** @dev Changes the `timesPerPeriod` property value of a specified subcourt.
* @param _subcourtID The ID of the subcourt.
* @param _timesPerPeriod The new value for the `timesPerPeriod` property value.
*/
function changeSubcourtTimesPerPeriod(uint96 _subcourtID, uint256[4] memory _timesPerPeriod)
external
onlyByGovernor
{
courts[_subcourtID].timesPerPeriod = _timesPerPeriod;
}
/** @dev Adds/removes court's support for specified dispute kits..
* @param _subcourtID The ID of the subcourt.
* @param _disputeKitIDs The IDs of dispute kits which support should be added/removed.
* @param _enable Whether add or remove the dispute kits from the subcourt.
*/
function enableDisputeKits(
uint96 _subcourtID,
uint256[] memory _disputeKitIDs,
bool _enable
) external onlyByGovernor {
Court storage subcourt = courts[_subcourtID];
for (uint256 i = 0; i < _disputeKitIDs.length; i++) {
if (_enable) {
require(_disputeKitIDs[i] > 0 && _disputeKitIDs[i] < disputeKitNodes.length, "Wrong DK index");
subcourt.supportedDisputeKits[_disputeKitIDs[i]] = true;
} else {
require(
!(_subcourtID == GENERAL_COURT && disputeKitNodes[_disputeKitIDs[i]].parent == NULL_DISPUTE_KIT),
"Can't remove root DK support from the general court"
);
subcourt.supportedDisputeKits[_disputeKitIDs[i]] = false;
}
}
}
// ************************************* //
// * State Modifiers * //
// ************************************* //
/** @dev Sets the caller's stake in a subcourt.
* @param _subcourtID The ID of the subcourt.
* @param _stake The new stake.
*/
function setStake(uint96 _subcourtID, uint256 _stake) external {
require(setStakeForAccount(msg.sender, _subcourtID, _stake, 0), "Staking failed");
}
/** @dev Executes the next delayed stakes.
* @param _iterations The number of delayed stakes to execute.
*/
function executeDelayedStakes(uint256 _iterations) external {
require(phase == Phase.staking, "Should be in Staking phase.");
uint256 actualIterations = (delayedStakeReadIndex + _iterations) - 1 > delayedStakeWriteIndex
? (delayedStakeWriteIndex - delayedStakeReadIndex) + 1
: _iterations;
uint256 newDelayedStakeReadIndex = delayedStakeReadIndex + actualIterations;
for (uint256 i = delayedStakeReadIndex; i < newDelayedStakeReadIndex; i++) {
DelayedStake storage delayedStake = delayedStakes[i];
setStakeForAccount(delayedStake.account, delayedStake.subcourtID, delayedStake.stake, delayedStake.penalty);
delete delayedStakes[i];
}
delayedStakeReadIndex = newDelayedStakeReadIndex;
}
/** @dev Allows an active dispute kit contract to manually unstake the ineligible juror. The main purpose of this function is to remove
* jurors that might obstruct the drawing process.
* @param _disputeID The ID of the dispute.
* @param _account The address of the juror.
* @return True if unstaking was successful.
*/
function unstakeByDK(uint256 _disputeID, address _account) external returns (bool) {
Dispute storage dispute = disputes[_disputeID];
Round storage round = dispute.rounds[dispute.rounds.length - 1];
uint96 subcourtID = dispute.subcourtID;
require(msg.sender == address(disputeKitNodes[round.disputeKitID].disputeKit), "Can only be called by DK");
require(dispute.period == Period.evidence, "The dispute should be in Evidence period.");
Juror storage juror = jurors[_account];
bytes32 stakePathID = accountAndSubcourtIDToStakePathID(_account, subcourtID);
uint256 currentStake = sortitionSumTrees.stakeOf(bytes32(uint256(subcourtID)), stakePathID);
uint256 transferredAmount;
transferredAmount = currentStake - juror.lockedTokens[subcourtID];
if (transferredAmount > 0) {
if (safeTransfer(_account, transferredAmount)) {
for (uint256 i = 0; i < juror.subcourtIDs.length; i++) {
if (juror.subcourtIDs[i] == subcourtID) {
juror.subcourtIDs[i] = juror.subcourtIDs[juror.subcourtIDs.length - 1];
juror.subcourtIDs.pop();
break;
}
}
} else {
return false;
}
}
// Update juror's records.
juror.stakedTokens[subcourtID] = 0;
// Update subcourt parents.
bool finished = false;
uint256 currentSubcourtID = subcourtID;
while (!finished) {
sortitionSumTrees.set(bytes32(currentSubcourtID), 0, stakePathID);
if (currentSubcourtID == GENERAL_COURT) finished = true;
else currentSubcourtID = courts[currentSubcourtID].parent;
}
emit StakeSet(_account, subcourtID, 0, 0);
return true;
}
/** @dev Creates a dispute. Must be called by the arbitrable contract.
* @param _numberOfChoices Number of choices for the jurors to choose from.
* @param _extraData Additional info about the dispute. We use it to pass the ID of the dispute's subcourt (first 32 bytes),
* the minimum number of jurors required (next 32 bytes) and the ID of the specific dispute kit (last 32 bytes).
* @return disputeID The ID of the created dispute.
*/
function createDispute(uint256 _numberOfChoices, bytes memory _extraData)
external
payable
override
returns (uint256 disputeID)
{
require(msg.value >= arbitrationCost(_extraData), "Not enough ETH to cover arbitration cost.");
(uint96 subcourtID, , uint256 disputeKitID) = extraDataToSubcourtIDMinJurorsDisputeKit(_extraData);
require(
courts[subcourtID].supportedDisputeKits[disputeKitID],
"The dispute kit is not supported by this subcourt"
);
disputeID = disputes.length;
Dispute storage dispute = disputes.push();
dispute.subcourtID = subcourtID;
dispute.arbitrated = IArbitrable(msg.sender);
dispute.lastPeriodChange = block.timestamp;
IDisputeKit disputeKit = disputeKitNodes[disputeKitID].disputeKit;
Court storage court = courts[dispute.subcourtID];
Round storage round = dispute.rounds.push();
round.nbVotes = msg.value / court.feeForJuror;
round.disputeKitID = disputeKitID;
round.tokensAtStakePerJuror = (court.minStake * court.alpha) / ALPHA_DIVISOR;
round.totalFeesForJurors = msg.value;
if (!disputeKitNodes[disputeKitID].needsFreezing) {
// Ensures uniqueness in the disputesKitIDsThatNeedFreezing array.
disputeKitNodes[disputeKitID].needsFreezing = true;
disputesKitIDsThatNeedFreezing.push(disputeKitID);
}
disputeKit.createDispute(disputeID, _numberOfChoices, _extraData, round.nbVotes);
emit DisputeCreation(disputeID, IArbitrable(msg.sender));
}
/** @dev Switches the phases between Staking and Freezing, also signal the switch to the dispute kits.
*/
function passPhase() external {
if (phase == Phase.staking) {
require(block.timestamp - lastPhaseChange >= minStakingTime, "The minimal staking time has not passed yet");
require(disputesKitIDsThatNeedFreezing.length > 0, "There are no dispute kit which need freezing");
phase = Phase.freezing;
freezeBlock = block.number;
} else {
// phase == Phase.freezing
bool timeout = this.freezingPhaseTimeout();
for (int256 i = int256(disputesKitIDsThatNeedFreezing.length) - 1; i >= 0; --i) {
uint256 disputeKitID = disputesKitIDsThatNeedFreezing[uint256(i)];
IDisputeKit disputeKit = disputeKitNodes[disputesKitIDsThatNeedFreezing[uint256(i)]].disputeKit;
if (timeout && !disputeKit.isResolving()) {
// Force the dispute kit to be ready for Staking phase.
disputeKit.passPhase(); // Should not be called if already in Resolving phase, because it reverts.
require(disputeKit.isResolving(), "A dispute kit has not passed to Resolving phase");
} else {
// Check if the dispute kit is ready for Staking phase.
require(disputeKit.isResolving(), "A dispute kit has not passed to Resolving phase");
if (disputeKit.disputesWithoutJurors() == 0) {
// The dispute kit had time to finish drawing jurors for all its disputes.
disputeKitNodes[disputeKitID].needsFreezing = false;
if (i < int256(disputesKitIDsThatNeedFreezing.length) - 1) {
// This is not the last element so copy the last element to the current one, then pop.
disputesKitIDsThatNeedFreezing[uint256(i)] = disputesKitIDsThatNeedFreezing[
disputesKitIDsThatNeedFreezing.length - 1
];
}
disputesKitIDsThatNeedFreezing.pop();
}
}
}
phase = Phase.staking;
}
// Should not be reached if the phase is unchanged.
lastPhaseChange = block.timestamp;
emit NewPhase(phase);
}
/** @dev Passes the period of a specified dispute.
* @param _disputeID The ID of the dispute.
*/
function passPeriod(uint256 _disputeID) external {
Dispute storage dispute = disputes[_disputeID];
Court storage court = courts[dispute.subcourtID];
uint256 currentRound = dispute.rounds.length - 1;
Round storage round = dispute.rounds[currentRound];
if (dispute.period == Period.evidence) {
require(
currentRound > 0 ||
block.timestamp - dispute.lastPeriodChange >= court.timesPerPeriod[uint256(dispute.period)],
"The evidence period time has not passed yet and it is not an appeal."
);
require(round.drawnJurors.length == round.nbVotes, "The dispute has not finished drawing yet.");
dispute.period = court.hiddenVotes ? Period.commit : Period.vote;
} else if (dispute.period == Period.commit) {
require(
block.timestamp - dispute.lastPeriodChange >= court.timesPerPeriod[uint256(dispute.period)] ||
disputeKitNodes[round.disputeKitID].disputeKit.areCommitsAllCast(_disputeID),
"The commit period time has not passed yet."
);
dispute.period = Period.vote;
} else if (dispute.period == Period.vote) {
require(
block.timestamp - dispute.lastPeriodChange >= court.timesPerPeriod[uint256(dispute.period)] ||
disputeKitNodes[round.disputeKitID].disputeKit.areVotesAllCast(_disputeID),
"The vote period time has not passed yet"
);
dispute.period = Period.appeal;
emit AppealPossible(_disputeID, dispute.arbitrated);
} else if (dispute.period == Period.appeal) {
require(
block.timestamp - dispute.lastPeriodChange >= court.timesPerPeriod[uint256(dispute.period)],
"The appeal period time has not passed yet."
);
dispute.period = Period.execution;
} else if (dispute.period == Period.execution) {
revert("The dispute is already in the last period.");
}
dispute.lastPeriodChange = block.timestamp;
emit NewPeriod(_disputeID, dispute.period);
}
/** @dev Draws jurors for the dispute. Can be called in parts.
* @param _disputeID The ID of the dispute.
* @param _iterations The number of iterations to run.
*/
function draw(uint256 _disputeID, uint256 _iterations) external {
require(phase == Phase.freezing, "Wrong phase.");
Dispute storage dispute = disputes[_disputeID];
uint256 currentRound = dispute.rounds.length - 1;
Round storage round = dispute.rounds[currentRound];
require(dispute.period == Period.evidence, "Should be evidence period.");
IDisputeKit disputeKit = disputeKitNodes[round.disputeKitID].disputeKit;
uint256 startIndex = round.drawnJurors.length;
uint256 endIndex = startIndex + _iterations <= round.nbVotes ? startIndex + _iterations : round.nbVotes;
for (uint256 i = startIndex; i < endIndex; i++) {
address drawnAddress = disputeKit.draw(_disputeID);
if (drawnAddress != address(0)) {
// In case no one has staked at the court yet.
jurors[drawnAddress].lockedTokens[dispute.subcourtID] += round.tokensAtStakePerJuror;
round.drawnJurors.push(drawnAddress);
emit Draw(drawnAddress, _disputeID, currentRound, i);
}
}
}
/** @dev Appeals the ruling of a specified dispute.
* Note: Access restricted to the Dispute Kit for this `disputeID`.
* @param _disputeID The ID of the dispute.
* @param _numberOfChoices Number of choices for the dispute. Can be required during court jump.
* @param _extraData Extradata for the dispute. Can be required during court jump.
*/
function appeal(
uint256 _disputeID,
uint256 _numberOfChoices,
bytes memory _extraData
) external payable {
require(msg.value >= appealCost(_disputeID), "Not enough ETH to cover appeal cost.");
Dispute storage dispute = disputes[_disputeID];
require(dispute.period == Period.appeal, "Dispute is not appealable.");
Round storage round = dispute.rounds[dispute.rounds.length - 1];
require(
msg.sender == address(disputeKitNodes[round.disputeKitID].disputeKit),
"Access not allowed: Dispute Kit only."
);
uint96 newSubcourtID = dispute.subcourtID;
uint256 newDisputeKitID = round.disputeKitID;
// Warning: the extra round must be created before calling disputeKit.createDispute()
Round storage extraRound = dispute.rounds.push();
if (round.nbVotes >= courts[newDisputeKitID].jurorsForCourtJump) {
// Jump to parent subcourt.
newSubcourtID = courts[newSubcourtID].parent;
for (uint256 i = 0; i < SEARCH_ITERATIONS; i++) {
if (courts[newSubcourtID].supportedDisputeKits[newDisputeKitID]) {
break;
} else if (disputeKitNodes[newDisputeKitID].parent != NULL_DISPUTE_KIT) {
newDisputeKitID = disputeKitNodes[newDisputeKitID].parent;
} else {
// DK's parent has 0 index, that means we reached the root DK (0 depth level).
// Jump to the next parent court if the current court doesn't support any DK from this tree.
// Note that we don't reset newDisputeKitID in this case as, a precaution.
newSubcourtID = courts[newSubcourtID].parent;
}
}
// We didn't find a court that is compatible with DK from this tree, so we jump directly to the top court.
// Note that this can only happen when disputeKitID is at its root, and each root DK is supported by the top court by default.
if (!courts[newSubcourtID].supportedDisputeKits[newDisputeKitID]) {
newSubcourtID = uint96(GENERAL_COURT);
}
if (newSubcourtID != dispute.subcourtID) {
emit CourtJump(_disputeID, dispute.rounds.length - 1, dispute.subcourtID, newSubcourtID);
}
}
dispute.subcourtID = newSubcourtID;
dispute.period = Period.evidence;
dispute.lastPeriodChange = block.timestamp;
Court storage court = courts[newSubcourtID];
extraRound.nbVotes = msg.value / court.feeForJuror; // As many votes that can be afforded by the provided funds.
extraRound.tokensAtStakePerJuror = (court.minStake * court.alpha) / ALPHA_DIVISOR;
extraRound.totalFeesForJurors = msg.value;
extraRound.disputeKitID = newDisputeKitID;
if (!disputeKitNodes[newDisputeKitID].needsFreezing) {
// Ensures uniqueness in the disputesKitIDsThatNeedFreezing array.
disputeKitNodes[newDisputeKitID].needsFreezing = true;
disputesKitIDsThatNeedFreezing.push(newDisputeKitID);
}
// Dispute kit was changed, so create a dispute in the new DK contract.
if (extraRound.disputeKitID != round.disputeKitID) {
emit DisputeKitJump(_disputeID, dispute.rounds.length - 1, round.disputeKitID, extraRound.disputeKitID);
disputeKitNodes[extraRound.disputeKitID].disputeKit.createDispute(
_disputeID,
_numberOfChoices,
_extraData,
extraRound.nbVotes
);
}
emit AppealDecision(_disputeID, dispute.arbitrated);
emit NewPeriod(_disputeID, Period.evidence);
}
/** @dev Distribute tokens and ETH for the specific round of the dispute. Can be called in parts.
* @param _disputeID The ID of the dispute.
* @param _round The appeal round.
* @param _iterations The number of iterations to run.
*/
function execute(
uint256 _disputeID,
uint256 _round,
uint256 _iterations
) external {
Dispute storage dispute = disputes[_disputeID];
require(dispute.period == Period.execution, "Should be execution period.");
Round storage round = dispute.rounds[_round];
IDisputeKit disputeKit = disputeKitNodes[round.disputeKitID].disputeKit;
uint256 end = round.repartitions + _iterations;
uint256 penaltiesInRoundCache = round.penalties; // For saving gas.
uint256 numberOfVotesInRound = round.drawnJurors.length;
uint256 coherentCount = disputeKit.getCoherentCount(_disputeID, _round); // Total number of jurors that are eligible to a reward in this round.
address account; // Address of the juror.
uint256 degreeOfCoherence; // [0, 1] value that determines how coherent the juror was in this round, in basis points.
if (coherentCount == 0) {
// We loop over the votes once as there are no rewards because it is not a tie and no one in this round is coherent with the final outcome.
if (end > numberOfVotesInRound) end = numberOfVotesInRound;
} else {
// We loop over the votes twice, first to collect penalties, and second to distribute them as rewards along with arbitration fees.
if (end > numberOfVotesInRound * 2) end = numberOfVotesInRound * 2;
}
for (uint256 i = round.repartitions; i < end; i++) {
if (i < numberOfVotesInRound) {
// Penalty.
degreeOfCoherence = disputeKit.getDegreeOfCoherence(_disputeID, _round, i);
// Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit.
if (degreeOfCoherence > ALPHA_DIVISOR) {
degreeOfCoherence = ALPHA_DIVISOR;
}
// Fully coherent jurors won't be penalized.
uint256 penalty = (round.tokensAtStakePerJuror * (ALPHA_DIVISOR - degreeOfCoherence)) / ALPHA_DIVISOR;
penaltiesInRoundCache += penalty;
account = round.drawnJurors[i];
jurors[account].lockedTokens[dispute.subcourtID] -= penalty; // Release this part of locked tokens.
// Can only update the stake if it is able to cover the minStake and penalty, otherwise unstake from the court.
if (jurors[account].stakedTokens[dispute.subcourtID] >= courts[dispute.subcourtID].minStake + penalty) {
uint256 newStake = jurors[account].stakedTokens[dispute.subcourtID] - penalty;
setStakeForAccount(account, dispute.subcourtID, newStake, penalty);
} else if (jurors[account].stakedTokens[dispute.subcourtID] != 0) {
setStakeForAccount(account, dispute.subcourtID, 0, penalty);
}
// Unstake the juror if he lost due to inactivity.
if (!disputeKit.isVoteActive(_disputeID, _round, i)) {
for (uint256 j = 0; j < jurors[account].subcourtIDs.length; j++) {
setStakeForAccount(account, jurors[account].subcourtIDs[j], 0, 0);
}
}
emit TokenAndETHShift(account, _disputeID, -int256(penalty), 0);
if (i == numberOfVotesInRound - 1) {
if (coherentCount == 0) {
// No one was coherent. Send the rewards to governor.
payable(governor).send(round.totalFeesForJurors);
safeTransfer(governor, penaltiesInRoundCache);
}
}
} else {
// Reward.
degreeOfCoherence = disputeKit.getDegreeOfCoherence(_disputeID, _round, i % numberOfVotesInRound);
// Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit.
if (degreeOfCoherence > ALPHA_DIVISOR) {
degreeOfCoherence = ALPHA_DIVISOR;
}
account = round.drawnJurors[i % numberOfVotesInRound];
// Release the rest of the tokens of the juror for this round.
jurors[account].lockedTokens[dispute.subcourtID] -=
(round.tokensAtStakePerJuror * degreeOfCoherence) /
ALPHA_DIVISOR;
// Give back the locked tokens in case the juror fully unstaked earlier.
if (jurors[account].stakedTokens[dispute.subcourtID] == 0) {
uint256 tokenLocked = (round.tokensAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR;
safeTransfer(account, tokenLocked);
}
uint256 tokenReward = ((penaltiesInRoundCache / coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR;
uint256 ethReward = ((round.totalFeesForJurors / coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR;
safeTransfer(account, tokenReward);
payable(account).send(ethReward);
emit TokenAndETHShift(account, _disputeID, int256(tokenReward), int256(ethReward));
}
}
if (round.penalties != penaltiesInRoundCache) {
round.penalties = penaltiesInRoundCache;
}
round.repartitions = end;
}
/** @dev Executes a specified dispute's ruling. UNTRUSTED.
* @param _disputeID The ID of the dispute.
*/
function executeRuling(uint256 _disputeID) external {
Dispute storage dispute = disputes[_disputeID];
require(dispute.period == Period.execution, "Should be execution period.");
require(!dispute.ruled, "Ruling already executed.");
uint256 winningChoice = currentRuling(_disputeID);
dispute.ruled = true;
dispute.arbitrated.rule(_disputeID, winningChoice);
}
// ************************************* //
// * Public Views * //
// ************************************* //
/** @dev Gets the cost of arbitration in a specified subcourt.
* @param _extraData Additional info about the dispute. We use it to pass the ID of the subcourt to create the dispute in (first 32 bytes)
* and the minimum number of jurors required (next 32 bytes).
* @return cost The arbitration cost.
*/
function arbitrationCost(bytes memory _extraData) public view override returns (uint256 cost) {
(uint96 subcourtID, uint256 minJurors, ) = extraDataToSubcourtIDMinJurorsDisputeKit(_extraData);
cost = courts[subcourtID].feeForJuror * minJurors;
}
/** @dev Gets the cost of appealing a specified dispute.
* @param _disputeID The ID of the dispute.
* @return cost The appeal cost.
*/
function appealCost(uint256 _disputeID) public view returns (uint256 cost) {
Dispute storage dispute = disputes[_disputeID];
Round storage round = dispute.rounds[dispute.rounds.length - 1];
Court storage court = courts[dispute.subcourtID];
if (round.nbVotes >= court.jurorsForCourtJump) {
// Jump to parent subcourt.
if (dispute.subcourtID == GENERAL_COURT) {
// TODO: Handle the forking when appealed in General court.
cost = NON_PAYABLE_AMOUNT; // Get the cost of the parent subcourt.
} else {
cost = courts[court.parent].feeForJuror * ((round.nbVotes * 2) + 1);
}
} else {
// Stay in current subcourt.
cost = court.feeForJuror * ((round.nbVotes * 2) + 1);
}
}
/** @dev Gets the start and the end of a specified dispute's current appeal period.
* @param _disputeID The ID of the dispute.
* @return start The start of the appeal period.
* @return end The end of the appeal period.
*/
function appealPeriod(uint256 _disputeID) public view returns (uint256 start, uint256 end) {
Dispute storage dispute = disputes[_disputeID];
if (dispute.period == Period.appeal) {
start = dispute.lastPeriodChange;
end = dispute.lastPeriodChange + courts[dispute.subcourtID].timesPerPeriod[uint256(Period.appeal)];
} else {
start = 0;
end = 0;
}
}
/** @dev Gets the current ruling of a specified dispute.
* @param _disputeID The ID of the dispute.
* @return ruling The current ruling.
*/
function currentRuling(uint256 _disputeID) public view returns (uint256 ruling) {
Dispute storage dispute = disputes[_disputeID];
Round storage round = dispute.rounds[dispute.rounds.length - 1];
IDisputeKit disputeKit = disputeKitNodes[round.disputeKitID].disputeKit;
return disputeKit.currentRuling(_disputeID);
}
function getRoundInfo(uint256 _disputeID, uint256 _round)
external
view
returns (
uint256 tokensAtStakePerJuror,
uint256 totalFeesForJurors,
uint256 repartitions,
uint256 penalties,
address[] memory drawnJurors,
uint256 disputeKitID
)
{
Dispute storage dispute = disputes[_disputeID];
Round storage round = dispute.rounds[_round];
return (
round.tokensAtStakePerJuror,
round.totalFeesForJurors,
round.repartitions,
round.penalties,
round.drawnJurors,
round.disputeKitID
);
}
function getNumberOfRounds(uint256 _disputeID) external view returns (uint256) {
Dispute storage dispute = disputes[_disputeID];
return dispute.rounds.length;
}
function getJurorBalance(address _juror, uint96 _subcourtID)
external
view
returns (uint256 staked, uint256 locked)
{
Juror storage juror = jurors[_juror];
staked = juror.stakedTokens[_subcourtID];
locked = juror.lockedTokens[_subcourtID];
}
function isSupported(uint96 _subcourtID, uint256 _disputeKitID) external view returns (bool) {
return courts[_subcourtID].supportedDisputeKits[_disputeKitID];