Skip to content

Commit 475f6dd

Browse files
committed
feat(merge): web-staging
2 parents d3a54c7 + 0dd543c commit 475f6dd

File tree

82 files changed

+1474
-612
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+1474
-612
lines changed

contracts/src/arbitration/KlerosCore.sol

Lines changed: 83 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,14 @@ contract KlerosCore is IArbitratorV2 {
6464
uint256 sumFeeRewardPaid; // Total sum of arbitration fees paid to coherent jurors as a reward in this round.
6565
uint256 sumPnkRewardPaid; // Total sum of PNK paid to coherent jurors as a reward in this round.
6666
IERC20 feeToken; // The token used for paying fees in this round.
67+
uint256 drawIterations; // The number of iterations passed drawing the jurors for this round.
6768
}
6869

6970
struct Juror {
7071
uint96[] courtIDs; // The IDs of courts 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`.
71-
mapping(uint96 => uint256) stakedPnk; // The amount of PNKs the juror has staked in the court in the form `stakedPnk[courtID]`.
72-
mapping(uint96 => uint256) lockedPnk; // The amount of PNKs the juror has locked in the court in the form `lockedPnk[courtID]`.
72+
uint256 stakedPnk; // The juror's total amount of tokens staked in subcourts. Reflects actual pnk balance.
73+
uint256 lockedPnk; // The juror's total amount of tokens locked in disputes. Can reflect actual pnk balance when stakedPnk are fully withdrawn.
74+
mapping(uint96 => uint256) stakedPnkByCourt; // The amount of PNKs the juror has staked in the court in the form `stakedPnkByCourt[courtID]`.
7375
}
7476

7577
struct DisputeKitNode {
@@ -126,7 +128,7 @@ contract KlerosCore is IArbitratorV2 {
126128
// ************************************* //
127129

128130
event StakeSet(address indexed _address, uint256 _courtID, uint256 _amount);
129-
event StakeDelayed(address indexed _address, uint256 _courtID, uint256 _amount, uint256 _penalty);
131+
event StakeDelayed(address indexed _address, uint256 _courtID, uint256 _amount);
130132
event NewPeriod(uint256 indexed _disputeID, Period _period);
131133
event AppealPossible(uint256 indexed _disputeID, IArbitrableV2 indexed _arbitrable);
132134
event AppealDecision(uint256 indexed _disputeID, IArbitrableV2 indexed _arbitrable);
@@ -481,14 +483,14 @@ contract KlerosCore is IArbitratorV2 {
481483

482484
/// @dev Sets the caller's stake in a court.
483485
/// @param _courtID The ID of the court.
484-
/// @param _stake The new stake.
485-
function setStake(uint96 _courtID, uint256 _stake) external {
486-
if (!_setStakeForAccount(msg.sender, _courtID, _stake, 0)) revert StakingFailed();
486+
/// @param _newStake The new stake.
487+
function setStake(uint96 _courtID, uint256 _newStake) external {
488+
if (!_setStakeForAccount(msg.sender, _courtID, _newStake)) revert StakingFailed();
487489
}
488490

489-
function setStakeBySortitionModule(address _account, uint96 _courtID, uint256 _stake, uint256 _penalty) external {
491+
function setStakeBySortitionModule(address _account, uint96 _courtID, uint256 _newStake) external {
490492
if (msg.sender != address(sortitionModule)) revert WrongCaller();
491-
_setStakeForAccount(_account, _courtID, _stake, _penalty);
493+
_setStakeForAccount(_account, _courtID, _newStake);
492494
}
493495

494496
/// @inheritdoc IArbitratorV2
@@ -608,21 +610,21 @@ contract KlerosCore is IArbitratorV2 {
608610

609611
IDisputeKit disputeKit = disputeKitNodes[round.disputeKitID].disputeKit;
610612

611-
uint256 startIndex = round.drawnJurors.length;
612-
uint256 endIndex = startIndex + _iterations <= round.nbVotes ? startIndex + _iterations : round.nbVotes;
613-
614-
for (uint256 i = startIndex; i < endIndex; i++) {
615-
address drawnAddress = disputeKit.draw(_disputeID);
616-
if (drawnAddress != address(0)) {
617-
jurors[drawnAddress].lockedPnk[dispute.courtID] += round.pnkAtStakePerJuror;
618-
emit Draw(drawnAddress, _disputeID, currentRound, round.drawnJurors.length);
619-
round.drawnJurors.push(drawnAddress);
620-
621-
if (round.drawnJurors.length == round.nbVotes) {
622-
sortitionModule.postDrawHook(_disputeID, currentRound);
623-
}
613+
uint256 startIndex = round.drawIterations; // for gas: less storage reads
614+
uint256 i;
615+
while (i < _iterations && round.drawnJurors.length < round.nbVotes) {
616+
address drawnAddress = disputeKit.draw(_disputeID, startIndex + i++);
617+
if (drawnAddress == address(0)) {
618+
continue;
619+
}
620+
jurors[drawnAddress].lockedPnk += round.pnkAtStakePerJuror;
621+
emit Draw(drawnAddress, _disputeID, currentRound, round.drawnJurors.length);
622+
round.drawnJurors.push(drawnAddress);
623+
if (round.drawnJurors.length == round.nbVotes) {
624+
sortitionModule.postDrawHook(_disputeID, currentRound);
624625
}
625626
}
627+
round.drawIterations += i;
626628
}
627629

628630
/// @dev Appeals the ruling of a specified dispute.
@@ -763,16 +765,14 @@ contract KlerosCore is IArbitratorV2 {
763765

764766
// Unlock the PNKs affected by the penalty
765767
address account = round.drawnJurors[_params.repartition];
766-
jurors[account].lockedPnk[dispute.courtID] -= penalty;
767-
768-
// Apply the penalty to the staked PNKs
769-
if (jurors[account].stakedPnk[dispute.courtID] >= courts[dispute.courtID].minStake + penalty) {
770-
// The juror still has enough staked PNKs after penalty for this court.
771-
uint256 newStake = jurors[account].stakedPnk[dispute.courtID] - penalty;
772-
_setStakeForAccount(account, dispute.courtID, newStake, penalty);
773-
} else if (jurors[account].stakedPnk[dispute.courtID] != 0) {
774-
// The juror does not have enough staked PNKs after penalty for this court, unstake them.
775-
_setStakeForAccount(account, dispute.courtID, 0, penalty);
768+
jurors[account].lockedPnk -= penalty;
769+
770+
// Apply the penalty to the staked PNKs.
771+
// Note that lockedPnk will always cover penalty while stakedPnk can become lower after manual unstaking.
772+
if (jurors[account].stakedPnk >= penalty) {
773+
jurors[account].stakedPnk -= penalty;
774+
} else {
775+
jurors[account].stakedPnk = 0;
776776
}
777777
emit TokenAndETHShift(
778778
account,
@@ -832,10 +832,10 @@ contract KlerosCore is IArbitratorV2 {
832832
uint256 pnkLocked = (round.pnkAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR;
833833

834834
// Release the rest of the PNKs of the juror for this round.
835-
jurors[account].lockedPnk[dispute.courtID] -= pnkLocked;
835+
jurors[account].lockedPnk -= pnkLocked;
836836

837837
// Give back the locked PNKs in case the juror fully unstaked earlier.
838-
if (jurors[account].stakedPnk[dispute.courtID] == 0) {
838+
if (jurors[account].stakedPnk == 0) {
839839
pinakion.safeTransfer(account, pnkLocked);
840840
}
841841

@@ -973,38 +973,8 @@ contract KlerosCore is IArbitratorV2 {
973973
(ruling, tied, overridden) = disputeKit.currentRuling(_disputeID);
974974
}
975975

976-
function getRoundInfo(
977-
uint256 _disputeID,
978-
uint256 _round
979-
)
980-
external
981-
view
982-
returns (
983-
uint256 disputeKitID,
984-
uint256 pnkAtStakePerJuror,
985-
uint256 totalFeesForJurors,
986-
uint256 nbVotes,
987-
uint256 repartitions,
988-
uint256 pnkPenalties,
989-
address[] memory drawnJurors,
990-
uint256 sumFeeRewardPaid,
991-
uint256 sumPnkRewardPaid,
992-
IERC20 feeToken
993-
)
994-
{
995-
Round storage round = disputes[_disputeID].rounds[_round];
996-
return (
997-
round.disputeKitID,
998-
round.pnkAtStakePerJuror,
999-
round.totalFeesForJurors,
1000-
round.nbVotes,
1001-
round.repartitions,
1002-
round.pnkPenalties,
1003-
round.drawnJurors,
1004-
round.sumFeeRewardPaid,
1005-
round.sumPnkRewardPaid,
1006-
round.feeToken
1007-
);
976+
function getRoundInfo(uint256 _disputeID, uint256 _round) external view returns (Round memory) {
977+
return disputes[_disputeID].rounds[_round];
1008978
}
1009979

1010980
function getNumberOfRounds(uint256 _disputeID) external view returns (uint256) {
@@ -1014,10 +984,11 @@ contract KlerosCore is IArbitratorV2 {
1014984
function getJurorBalance(
1015985
address _juror,
1016986
uint96 _courtID
1017-
) external view returns (uint256 staked, uint256 locked, uint256 nbCourts) {
987+
) external view returns (uint256 totalStaked, uint256 totalLocked, uint256 stakedInCourt, uint256 nbCourts) {
1018988
Juror storage juror = jurors[_juror];
1019-
staked = juror.stakedPnk[_courtID];
1020-
locked = juror.lockedPnk[_courtID];
989+
totalStaked = juror.stakedPnk;
990+
totalLocked = juror.lockedPnk;
991+
stakedInCourt = juror.stakedPnkByCourt[_courtID];
1021992
nbCourts = juror.courtIDs.length;
1022993
}
1023994

@@ -1109,77 +1080,80 @@ contract KlerosCore is IArbitratorV2 {
11091080
/// and `j` is the maximum number of jurors that ever staked in one of these courts simultaneously.
11101081
/// @param _account The address of the juror.
11111082
/// @param _courtID The ID of the court.
1112-
/// @param _stake The new stake.
1113-
/// @param _penalty Penalized amount won't be transferred back to juror when the stake is lowered.
1083+
/// @param _newStake The new stake.
11141084
/// @return succeeded True if the call succeeded, false otherwise.
11151085
function _setStakeForAccount(
11161086
address _account,
11171087
uint96 _courtID,
1118-
uint256 _stake,
1119-
uint256 _penalty
1088+
uint256 _newStake
11201089
) internal returns (bool succeeded) {
11211090
if (_courtID == FORKING_COURT || _courtID > courts.length) return false;
11221091

11231092
Juror storage juror = jurors[_account];
1124-
uint256 currentStake = juror.stakedPnk[_courtID];
1093+
uint256 currentStake = juror.stakedPnkByCourt[_courtID];
11251094

1126-
if (_stake != 0) {
1127-
// Check against locked PNKs in case the min stake was lowered.
1128-
if (_stake < courts[_courtID].minStake || _stake < juror.lockedPnk[_courtID]) return false;
1095+
if (_newStake != 0) {
1096+
if (_newStake < courts[_courtID].minStake) return false;
1097+
} else if (currentStake == 0) {
1098+
return false;
11291099
}
11301100

1131-
ISortitionModule.preStakeHookResult result = sortitionModule.preStakeHook(_account, _courtID, _stake, _penalty);
1101+
ISortitionModule.preStakeHookResult result = sortitionModule.preStakeHook(_account, _courtID, _newStake);
11321102
if (result == ISortitionModule.preStakeHookResult.failed) {
11331103
return false;
11341104
} else if (result == ISortitionModule.preStakeHookResult.delayed) {
1135-
emit StakeDelayed(_account, _courtID, _stake, _penalty);
1105+
emit StakeDelayed(_account, _courtID, _newStake);
11361106
return true;
11371107
}
11381108

11391109
uint256 transferredAmount;
1140-
if (_stake >= currentStake) {
1141-
transferredAmount = _stake - currentStake;
1110+
if (_newStake >= currentStake) {
1111+
// Stake increase
1112+
// When stakedPnk becomes lower than lockedPnk count the locked tokens in when transferring tokens from juror.
1113+
// (E.g. stakedPnk = 0, lockedPnk = 150) which can happen if the juror unstaked fully while having some tokens locked.
1114+
uint256 previouslyLocked = (juror.lockedPnk >= juror.stakedPnk) ? juror.lockedPnk - juror.stakedPnk : 0; // underflow guard
1115+
transferredAmount = (_newStake >= currentStake + previouslyLocked) // underflow guard
1116+
? _newStake - currentStake - previouslyLocked
1117+
: 0;
11421118
if (transferredAmount > 0) {
1143-
if (pinakion.safeTransferFrom(_account, address(this), transferredAmount)) {
1144-
if (currentStake == 0) {
1145-
juror.courtIDs.push(_courtID);
1146-
}
1147-
} else {
1119+
if (!pinakion.safeTransferFrom(_account, address(this), transferredAmount)) {
11481120
return false;
11491121
}
11501122
}
1123+
if (currentStake == 0) {
1124+
juror.courtIDs.push(_courtID);
1125+
}
11511126
} else {
1152-
if (_stake == 0) {
1153-
// Keep locked PNKs in the contract and release them after dispute is executed.
1154-
transferredAmount = currentStake - juror.lockedPnk[_courtID] - _penalty;
1155-
if (transferredAmount > 0) {
1156-
if (pinakion.safeTransfer(_account, transferredAmount)) {
1157-
for (uint256 i = juror.courtIDs.length; i > 0; i--) {
1158-
if (juror.courtIDs[i - 1] == _courtID) {
1159-
juror.courtIDs[i - 1] = juror.courtIDs[juror.courtIDs.length - 1];
1160-
juror.courtIDs.pop();
1161-
break;
1162-
}
1163-
}
1164-
} else {
1165-
return false;
1166-
}
1127+
// Stake decrease: make sure locked tokens always stay in the contract. They can only be released during Execution.
1128+
if (juror.stakedPnk >= currentStake - _newStake + juror.lockedPnk) {
1129+
// We have enough pnk staked to afford withdrawal while keeping locked tokens.
1130+
transferredAmount = currentStake - _newStake;
1131+
} else if (juror.stakedPnk >= juror.lockedPnk) {
1132+
// Can't afford withdrawing the current stake fully. Take whatever is available while keeping locked tokens.
1133+
transferredAmount = juror.stakedPnk - juror.lockedPnk;
1134+
}
1135+
if (transferredAmount > 0) {
1136+
if (!pinakion.safeTransfer(_account, transferredAmount)) {
1137+
return false;
11671138
}
1168-
} else {
1169-
transferredAmount = currentStake - _stake - _penalty;
1170-
if (transferredAmount > 0) {
1171-
if (!pinakion.safeTransfer(_account, transferredAmount)) {
1172-
return false;
1139+
}
1140+
if (_newStake == 0) {
1141+
for (uint256 i = juror.courtIDs.length; i > 0; i--) {
1142+
if (juror.courtIDs[i - 1] == _courtID) {
1143+
juror.courtIDs[i - 1] = juror.courtIDs[juror.courtIDs.length - 1];
1144+
juror.courtIDs.pop();
1145+
break;
11731146
}
11741147
}
11751148
}
11761149
}
11771150

1178-
// Update juror's records.
1179-
juror.stakedPnk[_courtID] = _stake;
1151+
// Note that stakedPnk can become async with currentStake (e.g. after penalty).
1152+
juror.stakedPnk = (juror.stakedPnk >= currentStake) ? juror.stakedPnk - currentStake + _newStake : _newStake;
1153+
juror.stakedPnkByCourt[_courtID] = _newStake;
11801154

1181-
sortitionModule.setStake(_account, _courtID, _stake);
1182-
emit StakeSet(_account, _courtID, _stake);
1155+
sortitionModule.setStake(_account, _courtID, _newStake);
1156+
emit StakeSet(_account, _courtID, _newStake);
11831157
return true;
11841158
}
11851159

contracts/src/arbitration/SortitionModule.sol

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ contract SortitionModule is ISortitionModule {
3636
address account; // The address of the juror.
3737
uint96 courtID; // The ID of the court.
3838
uint256 stake; // The new stake.
39-
uint256 penalty; // Penalty value, in case the stake was set during execution.
4039
}
4140

4241
// ************************************* //
@@ -185,12 +184,7 @@ contract SortitionModule is ISortitionModule {
185184

186185
for (uint256 i = delayedStakeReadIndex; i < newDelayedStakeReadIndex; i++) {
187186
DelayedStake storage delayedStake = delayedStakes[i];
188-
core.setStakeBySortitionModule(
189-
delayedStake.account,
190-
delayedStake.courtID,
191-
delayedStake.stake,
192-
delayedStake.penalty
193-
);
187+
core.setStakeBySortitionModule(delayedStake.account, delayedStake.courtID, delayedStake.stake);
194188
delete delayedStakes[i];
195189
}
196190
delayedStakeReadIndex = newDelayedStakeReadIndex;
@@ -199,10 +193,9 @@ contract SortitionModule is ISortitionModule {
199193
function preStakeHook(
200194
address _account,
201195
uint96 _courtID,
202-
uint256 _stake,
203-
uint256 _penalty
196+
uint256 _stake
204197
) external override onlyByCore returns (preStakeHookResult) {
205-
(uint256 currentStake, , uint256 nbCourts) = core.getJurorBalance(_account, _courtID);
198+
(, , uint256 currentStake, uint256 nbCourts) = core.getJurorBalance(_account, _courtID);
206199
if (currentStake == 0 && nbCourts >= MAX_STAKE_PATHS) {
207200
// Prevent staking beyond MAX_STAKE_PATHS but unstaking is always allowed.
208201
return preStakeHookResult.failed;
@@ -211,8 +204,7 @@ contract SortitionModule is ISortitionModule {
211204
delayedStakes[++delayedStakeWriteIndex] = DelayedStake({
212205
account: _account,
213206
courtID: _courtID,
214-
stake: _stake,
215-
penalty: _penalty
207+
stake: _stake
216208
});
217209
return preStakeHookResult.delayed;
218210
}
@@ -264,7 +256,7 @@ contract SortitionModule is ISortitionModule {
264256
function setJurorInactive(address _account) external override onlyByCore {
265257
uint96[] memory courtIDs = core.getJurorCourtIDs(_account);
266258
for (uint256 j = courtIDs.length; j > 0; j--) {
267-
core.setStakeBySortitionModule(_account, courtIDs[j - 1], 0, 0);
259+
core.setStakeBySortitionModule(_account, courtIDs[j - 1], 0);
268260
}
269261
}
270262

@@ -276,15 +268,15 @@ contract SortitionModule is ISortitionModule {
276268
/// Note that this function reverts if the sum of all values in the tree is 0.
277269
/// @param _key The key of the tree.
278270
/// @param _coreDisputeID Index of the dispute in Kleros Core.
279-
/// @param _voteID ID of the voter.
271+
/// @param _nonce Nonce to hash with random number.
280272
/// @return drawnAddress The drawn address.
281273
/// `O(k * log_k(n))` where
282274
/// `k` is the maximum number of children per node in the tree,
283275
/// and `n` is the maximum number of nodes ever appended.
284276
function draw(
285277
bytes32 _key,
286278
uint256 _coreDisputeID,
287-
uint256 _voteID
279+
uint256 _nonce
288280
) public view override returns (address drawnAddress) {
289281
require(phase == Phase.drawing, "Wrong phase.");
290282
SortitionSumTree storage tree = sortitionSumTrees[_key];
@@ -293,7 +285,7 @@ contract SortitionModule is ISortitionModule {
293285
return address(0); // No jurors staked.
294286
}
295287

296-
uint256 currentDrawnNumber = uint256(keccak256(abi.encodePacked(randomNumber, _coreDisputeID, _voteID))) %
288+
uint256 currentDrawnNumber = uint256(keccak256(abi.encodePacked(randomNumber, _coreDisputeID, _nonce))) %
297289
tree.nodes[0];
298290

299291
// While it still has children

0 commit comments

Comments
 (0)