Skip to content

Commit 14fedbb

Browse files
committed
fix: minStake check after setStakePenalty should return the remaining PNK to the juror
1 parent 4cc634f commit 14fedbb

File tree

8 files changed

+148
-30
lines changed

8 files changed

+148
-30
lines changed

contracts/src/arbitration/KlerosCoreBase.sol

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -464,16 +464,16 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
464464
/// @param _newStake The new stake.
465465
/// Note that the existing delayed stake will be nullified as non-relevant.
466466
function setStake(uint96 _courtID, uint256 _newStake) external virtual whenNotPaused {
467-
_setStake(msg.sender, _courtID, _newStake, OnError.Revert);
467+
_setStake(msg.sender, _courtID, _newStake, false, OnError.Revert);
468468
}
469469

470-
/// @dev Sets the stake of a specified account in a court, typically to apply a delayed stake or unstake inactive jurors.
470+
/// @dev Sets the stake of a specified account in a court without delaying stake changes, typically to apply a delayed stake or unstake inactive jurors.
471471
/// @param _account The account whose stake is being set.
472472
/// @param _courtID The ID of the court.
473473
/// @param _newStake The new stake.
474474
function setStakeBySortitionModule(address _account, uint96 _courtID, uint256 _newStake) external {
475475
if (msg.sender != address(sortitionModule)) revert SortitionModuleOnly();
476-
_setStake(_account, _courtID, _newStake, OnError.Return);
476+
_setStake(_account, _courtID, _newStake, true, OnError.Return);
477477
}
478478

479479
/// @dev Transfers PNK to the juror by SortitionModule.
@@ -796,10 +796,10 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
796796

797797
if (pnkBalance == 0 || !disputeKit.isVoteActive(_params.disputeID, _params.round, _params.repartition)) {
798798
// The juror is inactive or their balance is can't cover penalties anymore, unstake them from all courts.
799-
sortitionModule.setJurorInactive(account);
799+
sortitionModule.unstakeByCoreFromAllCourts(account);
800800
} else if (newCourtStake < courts[penalizedInCourtID].minStake) {
801801
// The juror's balance fell below the court minStake, unstake them from the court.
802-
sortitionModule.setStake(account, penalizedInCourtID, 0, 0, 0);
802+
sortitionModule.unstakeByCore(account, penalizedInCourtID);
803803
}
804804

805805
if (_params.repartition == _params.numberOfVotesInRound - 1 && _params.coherentCount == 0) {
@@ -1138,9 +1138,16 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
11381138
/// @param _account The account to set the stake for.
11391139
/// @param _courtID The ID of the court to set the stake for.
11401140
/// @param _newStake The new stake.
1141+
/// @param _noDelay True if the stake change should not be delayed.
11411142
/// @param _onError Whether to revert or return false on error.
11421143
/// @return Whether the stake was successfully set or not.
1143-
function _setStake(address _account, uint96 _courtID, uint256 _newStake, OnError _onError) internal returns (bool) {
1144+
function _setStake(
1145+
address _account,
1146+
uint96 _courtID,
1147+
uint256 _newStake,
1148+
bool _noDelay,
1149+
OnError _onError
1150+
) internal returns (bool) {
11441151
if (_courtID == FORKING_COURT || _courtID >= courts.length) {
11451152
_stakingFailed(_onError, StakingResult.CannotStakeInThisCourt); // Staking directly into the forking court is not allowed.
11461153
return false;
@@ -1152,7 +1159,8 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
11521159
(uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult) = sortitionModule.validateStake(
11531160
_account,
11541161
_courtID,
1155-
_newStake
1162+
_newStake,
1163+
_noDelay
11561164
);
11571165
if (stakingResult != StakingResult.Successful && stakingResult != StakingResult.Delayed) {
11581166
_stakingFailed(_onError, stakingResult);

contracts/src/arbitration/KlerosCoreNeo.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ contract KlerosCoreNeo is KlerosCoreBase {
108108
/// Note that the existing delayed stake will be nullified as non-relevant.
109109
function setStake(uint96 _courtID, uint256 _newStake) external override whenNotPaused {
110110
if (jurorNft.balanceOf(msg.sender) == 0) revert NotEligibleForStaking();
111-
super._setStake(msg.sender, _courtID, _newStake, OnError.Revert);
111+
super._setStake(msg.sender, _courtID, _newStake, false, OnError.Revert);
112112
}
113113

114114
// ************************************* //

contracts/src/arbitration/SortitionModuleBase.sol

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,21 +232,24 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
232232
/// @param _account The address of the juror.
233233
/// @param _courtID The ID of the court.
234234
/// @param _newStake The new stake.
235+
/// @param _noDelay True if the stake change should not be delayed.
235236
/// @return pnkDeposit The amount of PNK to be deposited.
236237
/// @return pnkWithdrawal The amount of PNK to be withdrawn.
237238
/// @return stakingResult The result of the staking operation.
238239
function validateStake(
239240
address _account,
240241
uint96 _courtID,
241-
uint256 _newStake
242+
uint256 _newStake,
243+
bool _noDelay
242244
) external override onlyByCore returns (uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult) {
243-
(pnkDeposit, pnkWithdrawal, stakingResult) = _validateStake(_account, _courtID, _newStake);
245+
(pnkDeposit, pnkWithdrawal, stakingResult) = _validateStake(_account, _courtID, _newStake, _noDelay);
244246
}
245247

246248
function _validateStake(
247249
address _account,
248250
uint96 _courtID,
249-
uint256 _newStake
251+
uint256 _newStake,
252+
bool _noDelay
250253
) internal virtual returns (uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult) {
251254
Juror storage juror = jurors[_account];
252255
uint256 currentStake = stakeOf(_account, _courtID);
@@ -260,7 +263,7 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
260263
return (0, 0, StakingResult.CannotStakeZeroWhenNoStake); // Forbid staking 0 amount when current stake is 0 to avoid flaky behaviour.
261264
}
262265

263-
if (phase != Phase.staking) {
266+
if (phase != Phase.staking && !_noDelay) {
264267
// Store the stake change as delayed, to be applied when the phase switches back to Staking.
265268
DelayedStake storage delayedStake = delayedStakes[++delayedStakeWriteIndex];
266269
delayedStake.account = _account;
@@ -433,13 +436,25 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
433436
/// `k` is the minimum number of children per node of one of these courts' sortition sum tree,
434437
/// and `j` is the maximum number of jurors that ever staked in one of these courts simultaneously.
435438
/// @param _account The juror to unstake.
436-
function setJurorInactive(address _account) external override onlyByCore {
439+
function unstakeByCoreFromAllCourts(address _account) external override onlyByCore {
437440
uint96[] memory courtIDs = getJurorCourtIDs(_account);
438441
for (uint256 j = courtIDs.length; j > 0; j--) {
439442
core.setStakeBySortitionModule(_account, courtIDs[j - 1], 0);
440443
}
441444
}
442445

446+
/// @dev Unstakes the inactive juror from a specific court.
447+
/// `O(n * (p * log_k(j)) )` where
448+
/// `n` is the number of courts the juror has staked in,
449+
/// `p` is the depth of the court tree,
450+
/// `k` is the minimum number of children per node of one of these courts' sortition sum tree,
451+
/// and `j` is the maximum number of jurors that ever staked in one of these courts simultaneously.
452+
/// @param _account The juror to unstake.
453+
/// @param _courtID The ID of the court.
454+
function unstakeByCore(address _account, uint96 _courtID) external override onlyByCore {
455+
core.setStakeBySortitionModule(_account, _courtID, 0);
456+
}
457+
443458
/// @dev Gives back the locked PNKs in case the juror fully unstaked earlier.
444459
/// Note that since locked and staked PNK are async it is possible for the juror to have positive staked PNK balance
445460
/// while having 0 stake in courts and 0 locked tokens (eg. when the juror fully unstaked during dispute and later got his tokens unlocked).

contracts/src/arbitration/SortitionModuleNeo.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ contract SortitionModuleNeo is SortitionModuleBase {
7777
function _validateStake(
7878
address _account,
7979
uint96 _courtID,
80-
uint256 _newStake
80+
uint256 _newStake,
81+
bool _noDelay
8182
) internal override onlyByCore returns (uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult) {
8283
uint256 currentStake = stakeOf(_account, _courtID);
8384
bool stakeIncrease = _newStake > currentStake;
@@ -98,6 +99,6 @@ contract SortitionModuleNeo is SortitionModuleBase {
9899
totalStaked -= stakeChange;
99100
}
100101
}
101-
(pnkDeposit, pnkWithdrawal, stakingResult) = super._validateStake(_account, _courtID, _newStake);
102+
(pnkDeposit, pnkWithdrawal, stakingResult) = super._validateStake(_account, _courtID, _newStake, _noDelay);
102103
}
103104
}

contracts/src/arbitration/interfaces/ISortitionModule.sol

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ interface ISortitionModule {
1818
function validateStake(
1919
address _account,
2020
uint96 _courtID,
21-
uint256 _newStake
21+
uint256 _newStake,
22+
bool _noDelay
2223
) external returns (uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult);
2324

2425
function setStake(
@@ -37,7 +38,9 @@ interface ISortitionModule {
3738

3839
function setStakeReward(address _account, uint96 _courtID, uint256 _reward) external returns (bool success);
3940

40-
function setJurorInactive(address _account) external;
41+
function unstakeByCoreFromAllCourts(address _account) external;
42+
43+
function unstakeByCore(address _account, uint96 _courtID) external;
4144

4245
function lockStake(address _account, uint256 _relativeAmount) external;
4346

contracts/src/arbitration/university/KlerosCoreUniversity.sol

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable {
452452
/// @param _newStake The new stake.
453453
/// Note that the existing delayed stake will be nullified as non-relevant.
454454
function setStake(uint96 _courtID, uint256 _newStake) external {
455-
_setStake(msg.sender, _courtID, _newStake, OnError.Revert);
455+
_setStake(msg.sender, _courtID, _newStake, false, OnError.Revert);
456456
}
457457

458458
/// @dev Sets the stake of a specified account in a court, typically to apply a delayed stake or unstake inactive jurors.
@@ -461,7 +461,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable {
461461
/// @param _newStake The new stake.
462462
function setStakeBySortitionModule(address _account, uint96 _courtID, uint256 _newStake) external {
463463
if (msg.sender != address(sortitionModule)) revert SortitionModuleOnly();
464-
_setStake(_account, _courtID, _newStake, OnError.Return);
464+
_setStake(_account, _courtID, _newStake, true, OnError.Return);
465465
}
466466

467467
/// @dev Transfers PNK to the juror by SortitionModule.
@@ -791,7 +791,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable {
791791

792792
if (pnkBalance == 0 || !disputeKit.isVoteActive(_params.disputeID, _params.round, _params.repartition)) {
793793
// The juror is inactive or their balance is can't cover penalties anymore, unstake them from all courts.
794-
sortitionModule.setJurorInactive(account);
794+
sortitionModule.unstakeByCoreFromAllCourts(account);
795795
} else if (newCourtStake < courts[penalizedInCourtID].minStake) {
796796
// The juror's balance fell below the court minStake, unstake them from the court.
797797
sortitionModule.setStake(account, penalizedInCourtID, 0, 0, 0);
@@ -1080,9 +1080,16 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable {
10801080
/// @param _account The account to set the stake for.
10811081
/// @param _courtID The ID of the court to set the stake for.
10821082
/// @param _newStake The new stake.
1083+
/// @param _noDelay True if the stake change should not be delayed.
10831084
/// @param _onError Whether to revert or return false on error.
10841085
/// @return Whether the stake was successfully set or not.
1085-
function _setStake(address _account, uint96 _courtID, uint256 _newStake, OnError _onError) internal returns (bool) {
1086+
function _setStake(
1087+
address _account,
1088+
uint96 _courtID,
1089+
uint256 _newStake,
1090+
bool _noDelay,
1091+
OnError _onError
1092+
) internal returns (bool) {
10861093
if (_courtID == FORKING_COURT || _courtID >= courts.length) {
10871094
_stakingFailed(_onError, StakingResult.CannotStakeInThisCourt); // Staking directly into the forking court is not allowed.
10881095
return false;
@@ -1094,7 +1101,8 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable {
10941101
(uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult) = sortitionModule.validateStake(
10951102
_account,
10961103
_courtID,
1097-
_newStake
1104+
_newStake,
1105+
_noDelay
10981106
);
10991107
if (stakingResult != StakingResult.Successful) {
11001108
_stakingFailed(_onError, stakingResult);

contracts/src/arbitration/university/SortitionModuleUniversity.sol

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ contract SortitionModuleUniversity is ISortitionModuleUniversity, UUPSProxiable,
139139
function validateStake(
140140
address _account,
141141
uint96 _courtID,
142-
uint256 _newStake
142+
uint256 _newStake,
143+
bool /*_noDelay*/
143144
)
144145
external
145146
view
@@ -302,13 +303,25 @@ contract SortitionModuleUniversity is ISortitionModuleUniversity, UUPSProxiable,
302303
/// `k` is the minimum number of children per node of one of these courts' sortition sum tree,
303304
/// and `j` is the maximum number of jurors that ever staked in one of these courts simultaneously.
304305
/// @param _account The juror to unstake.
305-
function setJurorInactive(address _account) external override onlyByCore {
306+
function unstakeByCoreFromAllCourts(address _account) external override onlyByCore {
306307
uint96[] memory courtIDs = getJurorCourtIDs(_account);
307308
for (uint256 j = courtIDs.length; j > 0; j--) {
308309
core.setStakeBySortitionModule(_account, courtIDs[j - 1], 0);
309310
}
310311
}
311312

313+
/// @dev Unstakes the inactive juror from a specific court.
314+
/// `O(n * (p * log_k(j)) )` where
315+
/// `n` is the number of courts the juror has staked in,
316+
/// `p` is the depth of the court tree,
317+
/// `k` is the minimum number of children per node of one of these courts' sortition sum tree,
318+
/// and `j` is the maximum number of jurors that ever staked in one of these courts simultaneously.
319+
/// @param _account The juror to unstake.
320+
/// @param _courtID The ID of the court.
321+
function unstakeByCore(address _account, uint96 _courtID) external override onlyByCore {
322+
core.setStakeBySortitionModule(_account, _courtID, 0);
323+
}
324+
312325
/// @dev Gives back the locked PNKs in case the juror fully unstaked earlier.
313326
/// Note that since locked and staked PNK are async it is possible for the juror to have positive staked PNK balance
314327
/// while having 0 stake in courts and 0 locked tokens (eg. when the juror fully unstaked during dispute and later got his tokens unlocked).

0 commit comments

Comments
 (0)