Skip to content

Commit 9cad8de

Browse files
Merge pull request #34 from unknownunknown1/master
feat(KlerosCore): staking implementation
2 parents 36a8feb + 423c6e9 commit 9cad8de

File tree

1 file changed

+120
-53
lines changed

1 file changed

+120
-53
lines changed

contracts/src/arbitration/KlerosCore.sol

Lines changed: 120 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,21 @@ contract KlerosCore is IArbitrator {
5252
IDisputeKit disputeKit; // ID of the dispute kit that this dispute was assigned to.
5353
Period period; // The current period of the dispute.
5454
bool ruled; // True if the ruling has been executed, false otherwise.
55-
uint256 currentRound; // The index of the current appeal round. Note that 0 represents the default dispute round. Former votes.length - 1.
5655
uint256 lastPeriodChange; // The last time the period was changed.
5756
uint256 nbVotes; // The total number of votes the dispute can possibly have in the current round. Former votes[_appeal].length.
58-
mapping(uint256 => address[]) drawnJurors; // Addresses of the drawn jurors in the form `drawnJurors[_appeal]`.
5957
Round[] rounds;
6058
}
6159

6260
struct Round {
63-
uint256 tokensAtStakePerJuror; // The amount of tokens at stake for each juror in the form `tokensAtStakePerJuror[appeal]`.
64-
uint256 totalFeesForJurors; // The total juror fees paid in the form `totalFeesForJurors[appeal]`.
65-
uint256 repartitions; // A counter of vote reward repartitions made in each round in the form `repartitionsInEachRound[appeal]`.
66-
uint256 penalties; // The amount of tokens collected from penalties in each round in the form `penaltiesInEachRound[appeal]`.
61+
uint256 tokensAtStakePerJuror; // The amount of tokens at stake for each juror in this round.
62+
uint256 totalFeesForJurors; // The total juror fees paid in this round.
63+
uint256 repartitions; // A counter of reward repartitions made in this round.
64+
uint256 penalties; // The amount of tokens collected from penalties in this round.
65+
address[] drawnJurors; // Addresses of the jurors that were drawn in this round.
6766
}
6867

6968
struct Juror {
7069
uint96[] subcourtIDs; // The IDs of subcourts where the juror's stake path ends. A stake path is a path from the forking court to a court the juror directly staked in using `_setStake`.
71-
// TODO: Relations of staked and locked tokens (currently unfinished).
7270
mapping(uint96 => uint256) stakedTokens; // The number of tokens the juror has staked in the subcourt in the form `stakedTokens[subcourtID]`.
7371
mapping(uint96 => uint256) lockedTokens; // The number of tokens the juror has locked in the subcourt in the form `lockedTokens[subcourtID]`.
7472
}
@@ -90,7 +88,7 @@ contract KlerosCore is IArbitrator {
9088
Court[] public courts; // The subcourts.
9189

9290
//TODO: disputeKits forest.
93-
IDisputeKit[] public disputeKits; // All supported dispute kits.
91+
mapping(uint256 => IDisputeKit) public disputeKits; // All supported dispute kits.
9492

9593
Dispute[] public disputes; // The disputes.
9694
mapping(address => Juror) internal jurors; // The jurors.
@@ -150,7 +148,7 @@ contract KlerosCore is IArbitrator {
150148
governor = _governor;
151149
pinakion = _pinakion;
152150
jurorProsecutionModule = _jurorProsecutionModule;
153-
disputeKits.push(_disputeKit);
151+
disputeKits[0] = _disputeKit;
154152

155153
// Create the Forking court.
156154
courts.push(
@@ -210,12 +208,12 @@ contract KlerosCore is IArbitrator {
210208

211209
/** @dev Add a new supported dispute kit module to the court.
212210
* @param _disputeKitAddress The address of the dispute kit contract.
211+
* @param _disputeKitID The ID assigned to the added dispute kit.
213212
*/
214-
function addNewDisputeKit(IDisputeKit _disputeKitAddress) external onlyByGovernor {
215-
// TODO: the dispute kit data structure. For now keep it a simple array.
213+
function addNewDisputeKit(IDisputeKit _disputeKitAddress, uint8 _disputeKitID) external onlyByGovernor {
214+
// TODO: the dispute kit data structure. For now keep it a simple mapping.
216215
// Also note that in current state this function doesn't take into account that the added address is actually new.
217-
require(disputeKits.length <= 256); // Can't be more than 256 because the IDs are used in a bitfield.
218-
disputeKits.push(_disputeKitAddress);
216+
disputeKits[_disputeKitID] = _disputeKitAddress;
219217
}
220218

221219
/** @dev Creates a subcourt under a specified parent court.
@@ -227,6 +225,7 @@ contract KlerosCore is IArbitrator {
227225
* @param _jurorsForCourtJump The `jurorsForCourtJump` property value of the subcourt.
228226
* @param _timesPerPeriod The `timesPerPeriod` property value of the subcourt.
229227
* @param _sortitionSumTreeK The number of children per node of the subcourt's sortition sum tree.
228+
* @param _supportedDisputeKits Bitfield that contains the IDs of the dispute kits that this court will support.
230229
*/
231230
function createSubcourt(
232231
uint96 _parent,
@@ -236,7 +235,8 @@ contract KlerosCore is IArbitrator {
236235
uint256 _feeForJuror,
237236
uint256 _jurorsForCourtJump,
238237
uint256[4] memory _timesPerPeriod,
239-
uint256 _sortitionSumTreeK
238+
uint256 _sortitionSumTreeK,
239+
uint256 _supportedDisputeKits
240240
) external onlyByGovernor {
241241
require(
242242
courts[_parent].minStake <= _minStake,
@@ -255,7 +255,7 @@ contract KlerosCore is IArbitrator {
255255
feeForJuror: _feeForJuror,
256256
jurorsForCourtJump: _jurorsForCourtJump,
257257
timesPerPeriod: _timesPerPeriod,
258-
supportedDisputeKits: 1
258+
supportedDisputeKits: _supportedDisputeKits
259259
})
260260
);
261261

@@ -317,7 +317,7 @@ contract KlerosCore is IArbitrator {
317317

318318
/** @dev Adds/removes particular dispute kits to a subcourt's bitfield of supported dispute kits.
319319
* @param _subcourtID The ID of the subcourt.
320-
* @param _disputeKitIDs IDs of dispute kits in the disputeKits array, which support should be added/removed.
320+
* @param _disputeKitIDs IDs of dispute kits which support should be added/removed.
321321
* @param _enable Whether add or remove the dispute kits from the subcourt.
322322
*/
323323
function setDisputeKits(
@@ -346,7 +346,7 @@ contract KlerosCore is IArbitrator {
346346
* @param _stake The new stake.
347347
*/
348348
function setStake(uint96 _subcourtID, uint256 _stake) external {
349-
setStakeForAccount(msg.sender, _subcourtID, _stake);
349+
require(setStakeForAccount(msg.sender, _subcourtID, _stake, 0), "Staking failed");
350350
}
351351

352352
/** @dev Creates a dispute. Must be called by the arbitrable contract.
@@ -396,17 +396,17 @@ contract KlerosCore is IArbitrator {
396396
*/
397397
function passPeriod(uint256 _disputeID) external {
398398
Dispute storage dispute = disputes[_disputeID];
399+
400+
uint256 currentRound = dispute.rounds.length - 1;
401+
Round storage round = dispute.rounds[currentRound];
399402
if (dispute.period == Period.evidence) {
400403
require(
401-
dispute.currentRound > 0 ||
404+
currentRound > 0 ||
402405
block.timestamp - dispute.lastPeriodChange >=
403406
courts[dispute.subcourtID].timesPerPeriod[uint256(dispute.period)],
404407
"The evidence period time has not passed yet and it is not an appeal."
405408
);
406-
require(
407-
dispute.drawnJurors[dispute.currentRound].length == dispute.nbVotes,
408-
"The dispute has not finished drawing yet."
409-
);
409+
require(round.drawnJurors.length == dispute.nbVotes, "The dispute has not finished drawing yet.");
410410
dispute.period = courts[dispute.subcourtID].hiddenVotes ? Period.commit : Period.vote;
411411
} else if (dispute.period == Period.commit) {
412412
// In case the jurors finished casting commits beforehand the dispute kit should call passPeriod() by itself.
@@ -448,20 +448,26 @@ contract KlerosCore is IArbitrator {
448448
*/
449449
function draw(uint256 _disputeID, uint256 _iterations) external {
450450
Dispute storage dispute = disputes[_disputeID];
451+
uint256 currentRound = dispute.rounds.length - 1;
452+
Round storage round = dispute.rounds[currentRound];
451453
require(dispute.period == Period.evidence, "Should be evidence period.");
454+
452455
IDisputeKit disputeKit = dispute.disputeKit;
453-
uint256 startIndex = dispute.drawnJurors[dispute.currentRound].length;
456+
uint256 startIndex = round.drawnJurors.length;
454457
uint256 endIndex = startIndex + _iterations <= dispute.nbVotes ? startIndex + _iterations : dispute.nbVotes;
455458

456459
for (uint256 i = startIndex; i < endIndex; i++) {
457460
address drawnAddress = disputeKit.draw(_disputeID);
458461
if (drawnAddress != address(0)) {
459462
// In case no one has staked at the court yet.
460-
jurors[drawnAddress].lockedTokens[dispute.subcourtID] += dispute
461-
.rounds[dispute.currentRound]
462-
.tokensAtStakePerJuror;
463-
dispute.drawnJurors[dispute.currentRound].push(drawnAddress);
464-
emit Draw(drawnAddress, _disputeID, dispute.currentRound, i);
463+
jurors[drawnAddress].lockedTokens[dispute.subcourtID] += round.tokensAtStakePerJuror;
464+
require(
465+
jurors[drawnAddress].stakedTokens[dispute.subcourtID] >=
466+
jurors[drawnAddress].lockedTokens[dispute.subcourtID],
467+
"Locked amount shouldn't exceed staked amount."
468+
);
469+
round.drawnJurors.push(drawnAddress);
470+
emit Draw(drawnAddress, _disputeID, currentRound, i);
465471
}
466472
}
467473
}
@@ -493,8 +499,6 @@ contract KlerosCore is IArbitrator {
493499
ALPHA_DIVISOR;
494500
extraRound.totalFeesForJurors = msg.value;
495501

496-
dispute.currentRound++;
497-
498502
emit AppealDecision(_disputeID, dispute.arbitrated);
499503
emit NewPeriod(_disputeID, Period.evidence);
500504
}
@@ -515,7 +519,7 @@ contract KlerosCore is IArbitrator {
515519
uint256 end = dispute.rounds[_appeal].repartitions + _iterations;
516520
uint256 penaltiesInRoundCache = dispute.rounds[_appeal].penalties; // For saving gas.
517521

518-
uint256 numberOfVotesInRound = dispute.drawnJurors[_appeal].length;
522+
uint256 numberOfVotesInRound = dispute.rounds[_appeal].drawnJurors.length;
519523
uint256 coherentCount = dispute.disputeKit.getCoherentCount(_disputeID, _appeal); // Total number of jurors that are eligible to a reward in this round.
520524

521525
address account; // Address of the juror.
@@ -539,14 +543,25 @@ contract KlerosCore is IArbitrator {
539543
(ALPHA_DIVISOR - degreeOfCoherence)) / ALPHA_DIVISOR; // Fully coherent jurors won't be penalized.
540544
penaltiesInRoundCache += penalty;
541545

542-
account = dispute.drawnJurors[_appeal][i];
546+
account = dispute.rounds[_appeal].drawnJurors[i];
543547
jurors[account].lockedTokens[dispute.subcourtID] -= penalty; // Release this part of locked tokens.
544-
// TODO: properly update staked tokens in case of penalty.
548+
549+
// Can only update the stake if it is able to cover the minStake and penalty, otherwise unstake from the court.
550+
if (jurors[account].stakedTokens[dispute.subcourtID] >= courts[dispute.subcourtID].minStake + penalty) {
551+
setStakeForAccount(
552+
account,
553+
dispute.subcourtID,
554+
jurors[account].stakedTokens[dispute.subcourtID] - penalty,
555+
penalty
556+
);
557+
} else if (jurors[account].stakedTokens[dispute.subcourtID] != 0) {
558+
setStakeForAccount(account, dispute.subcourtID, 0, penalty);
559+
}
545560

546561
// Unstake the juror if he lost due to inactivity.
547562
if (!dispute.disputeKit.isVoteActive(_disputeID, _appeal, i)) {
548563
for (uint256 j = 0; j < jurors[account].subcourtIDs.length; j++)
549-
setStakeForAccount(account, jurors[account].subcourtIDs[j], 0);
564+
setStakeForAccount(account, jurors[account].subcourtIDs[j], 0, 0);
550565
}
551566
emit TokenAndETHShift(account, _disputeID, -int256(penalty), 0);
552567

@@ -565,12 +580,19 @@ contract KlerosCore is IArbitrator {
565580
i % numberOfVotesInRound
566581
);
567582
if (degreeOfCoherence > ALPHA_DIVISOR) degreeOfCoherence = ALPHA_DIVISOR;
568-
account = dispute.drawnJurors[_appeal][i % 2];
583+
account = dispute.rounds[_appeal].drawnJurors[i % numberOfVotesInRound];
569584
// Release the rest of the tokens of the juror for this round.
570585
jurors[account].lockedTokens[dispute.subcourtID] -=
571586
(dispute.rounds[_appeal].tokensAtStakePerJuror * degreeOfCoherence) /
572587
ALPHA_DIVISOR;
573-
// TODO: properly update staked tokens in case of reward.
588+
589+
if (jurors[account].stakedTokens[dispute.subcourtID] == 0) {
590+
// Give back the locked tokens in case the juror fully unstaked earlier.
591+
pinakion.transfer(
592+
account,
593+
(dispute.rounds[_appeal].tokensAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR
594+
);
595+
}
574596

575597
uint256 tokenReward = ((penaltiesInRoundCache / coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR;
576598
uint256 ETHReward = ((dispute.rounds[_appeal].totalFeesForJurors / coherentCount) * degreeOfCoherence) /
@@ -656,8 +678,41 @@ contract KlerosCore is IArbitrator {
656678
return disputeKit.currentRuling(_disputeID);
657679
}
658680

659-
function getDispute(uint256 _disputeID) external view returns (Round[] memory) {
660-
return disputes[_disputeID].rounds;
681+
function getRoundInfo(uint256 _disputeID, uint256 _round)
682+
external
683+
view
684+
returns (
685+
uint256 tokensAtStakePerJuror,
686+
uint256 totalFeesForJurors,
687+
uint256 repartitions,
688+
uint256 penalties,
689+
address[] memory drawnJurors
690+
)
691+
{
692+
Dispute storage dispute = disputes[_disputeID];
693+
Round storage round = dispute.rounds[_round];
694+
return (
695+
round.tokensAtStakePerJuror,
696+
round.totalFeesForJurors,
697+
round.repartitions,
698+
round.penalties,
699+
round.drawnJurors
700+
);
701+
}
702+
703+
function getNumberOfRounds(uint256 _disputeID) external view returns (uint256) {
704+
Dispute storage dispute = disputes[_disputeID];
705+
return dispute.rounds.length;
706+
}
707+
708+
function getJurorBalance(address _juror, uint96 _subcourtID)
709+
external
710+
view
711+
returns (uint256 staked, uint256 locked)
712+
{
713+
Juror storage juror = jurors[_juror];
714+
staked = juror.stakedTokens[_subcourtID];
715+
locked = juror.lockedTokens[_subcourtID];
661716
}
662717

663718
// ************************************* //
@@ -712,20 +767,24 @@ contract KlerosCore is IArbitrator {
712767
* @param _account The address of the juror.
713768
* @param _subcourtID The ID of the subcourt.
714769
* @param _stake The new stake.
770+
* @param _penalty Penalized amount won't be transferred back to juror when the stake is lowered.
771+
* @return succeeded True if the call succeeded, false otherwise.
715772
*/
716773
function setStakeForAccount(
717774
address _account,
718775
uint96 _subcourtID,
719-
uint256 _stake
720-
) internal {
776+
uint256 _stake,
777+
uint256 _penalty
778+
) internal returns (bool succeeded) {
721779
Juror storage juror = jurors[_account];
722780
bytes32 stakePathID = accountAndSubcourtIDToStakePathID(_account, _subcourtID);
723781
uint256 currentStake = sortitionSumTrees.stakeOf(bytes32(uint256(_subcourtID)), stakePathID);
724782

725783
if (_stake != 0) {
726-
require(_stake >= courts[_subcourtID].minStake, "Should stake at least minimal amount");
784+
// Check against locked tokens in case the min stake was lowered.
785+
if (_stake < courts[_subcourtID].minStake || _stake < juror.lockedTokens[_subcourtID]) return false;
727786
if (currentStake == 0) {
728-
require(juror.subcourtIDs.length < MAX_STAKE_PATHS, "Max stake paths reached");
787+
if (juror.subcourtIDs.length >= MAX_STAKE_PATHS) return false;
729788
juror.subcourtIDs.push(_subcourtID);
730789
}
731790
} else {
@@ -751,20 +810,28 @@ contract KlerosCore is IArbitrator {
751810
else currentSubcourtID = courts[currentSubcourtID].parent;
752811
}
753812

813+
emit StakeSet(_account, _subcourtID, _stake, newTotalStake);
814+
815+
uint256 transferredAmount;
754816
if (_stake >= currentStake) {
755-
require(
756-
pinakion.transferFrom(_account, address(this), _stake - currentStake),
757-
"Sender doesn't have enough tokens"
758-
);
817+
transferredAmount = _stake - currentStake;
818+
if (transferredAmount > 0) {
819+
if (!pinakion.transferFrom(_account, address(this), transferredAmount)) return false;
820+
}
821+
} else if (_stake == 0) {
822+
// Keep locked tokens in the contract and release them after dispute is executed.
823+
transferredAmount = currentStake - juror.lockedTokens[_subcourtID] - _penalty;
824+
if (transferredAmount > 0) {
825+
if (!pinakion.transfer(_account, transferredAmount)) return false;
826+
}
759827
} else {
760-
// Keep locked tokens in the contract and release them after dispute is executed. Note that the current flow of staking is unfinished.
761-
require(
762-
pinakion.transfer(_account, currentStake - _stake - juror.lockedTokens[_subcourtID]),
763-
"The transfer failed"
764-
);
828+
transferredAmount = currentStake - _stake - _penalty;
829+
if (transferredAmount > 0) {
830+
if (!pinakion.transfer(_account, transferredAmount)) return false;
831+
}
765832
}
766833

767-
emit StakeSet(_account, _subcourtID, _stake, newTotalStake);
834+
return true;
768835
}
769836

770837
/** @dev Gets a subcourt ID, the minimum number of jurors and an ID of a dispute kit from a specified extra data bytes array.
@@ -793,7 +860,7 @@ contract KlerosCore is IArbitrator {
793860
}
794861
if (subcourtID >= courts.length) subcourtID = 0;
795862
if (minJurors == 0) minJurors = MIN_JURORS;
796-
if (disputeKitID >= disputeKits.length) disputeKitID = 0;
863+
if (disputeKits[disputeKitID] == IDisputeKit(address(0))) disputeKitID = 0;
797864
} else {
798865
subcourtID = 0;
799866
minJurors = MIN_JURORS;

0 commit comments

Comments
 (0)