@@ -68,8 +68,9 @@ contract KlerosCore is IArbitratorV2 {
68
68
69
69
struct Juror {
70
70
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]`.
71
+ uint256 stakedPnk; // The juror's total amount of tokens staked in subcourts. Reflects actual pnk balance.
72
+ uint256 lockedPnk; // The juror's total amount of tokens locked in disputes. Can reflect actual pnk balance when stakedPnk are fully withdrawn.
73
+ mapping (uint96 => uint256 ) stakedPnkByCourt; // The amount of PNKs the juror has staked in the court in the form `stakedPnkByCourt[courtID]`.
73
74
}
74
75
75
76
struct DisputeKitNode {
@@ -126,7 +127,7 @@ contract KlerosCore is IArbitratorV2 {
126
127
// ************************************* //
127
128
128
129
event StakeSet (address indexed _address , uint256 _courtID , uint256 _amount );
129
- event StakeDelayed (address indexed _address , uint256 _courtID , uint256 _amount , uint256 _penalty );
130
+ event StakeDelayed (address indexed _address , uint256 _courtID , uint256 _amount );
130
131
event NewPeriod (uint256 indexed _disputeID , Period _period );
131
132
event AppealPossible (uint256 indexed _disputeID , IArbitrableV2 indexed _arbitrable );
132
133
event AppealDecision (uint256 indexed _disputeID , IArbitrableV2 indexed _arbitrable );
@@ -483,12 +484,12 @@ contract KlerosCore is IArbitratorV2 {
483
484
/// @param _courtID The ID of the court.
484
485
/// @param _stake The new stake.
485
486
function setStake (uint96 _courtID , uint256 _stake ) external {
486
- if (! _setStakeForAccount (msg .sender , _courtID, _stake, 0 )) revert StakingFailed ();
487
+ if (! _setStakeForAccount (msg .sender , _courtID, _stake)) revert StakingFailed ();
487
488
}
488
489
489
- function setStakeBySortitionModule (address _account , uint96 _courtID , uint256 _stake , uint256 _penalty ) external {
490
+ function setStakeBySortitionModule (address _account , uint96 _courtID , uint256 _stake ) external {
490
491
if (msg .sender != address (sortitionModule)) revert WrongCaller ();
491
- _setStakeForAccount (_account, _courtID, _stake, _penalty );
492
+ _setStakeForAccount (_account, _courtID, _stake);
492
493
}
493
494
494
495
/// @inheritdoc IArbitratorV2
@@ -614,7 +615,7 @@ contract KlerosCore is IArbitratorV2 {
614
615
for (uint256 i = startIndex; i < endIndex; i++ ) {
615
616
address drawnAddress = disputeKit.draw (_disputeID);
616
617
if (drawnAddress != address (0 )) {
617
- jurors[drawnAddress].lockedPnk[dispute.courtID] += round.pnkAtStakePerJuror;
618
+ jurors[drawnAddress].lockedPnk += round.pnkAtStakePerJuror;
618
619
emit Draw (drawnAddress, _disputeID, currentRound, round.drawnJurors.length );
619
620
round.drawnJurors.push (drawnAddress);
620
621
@@ -763,16 +764,12 @@ contract KlerosCore is IArbitratorV2 {
763
764
764
765
// Unlock the PNKs affected by the penalty
765
766
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);
767
+ jurors[account].lockedPnk -= penalty;
768
+
769
+ // Apply the penalty to the staked PNKs if there ara any.
770
+ // Note that lockedPnk will always cover penalty while stakedPnk can become lower after manual unstaking.
771
+ if (jurors[account].stakedPnk >= penalty) {
772
+ jurors[account].stakedPnk -= penalty;
776
773
}
777
774
emit TokenAndETHShift (
778
775
account,
@@ -832,10 +829,10 @@ contract KlerosCore is IArbitratorV2 {
832
829
uint256 pnkLocked = (round.pnkAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR;
833
830
834
831
// Release the rest of the PNKs of the juror for this round.
835
- jurors[account].lockedPnk[dispute.courtID] -= pnkLocked;
832
+ jurors[account].lockedPnk -= pnkLocked;
836
833
837
834
// Give back the locked PNKs in case the juror fully unstaked earlier.
838
- if (jurors[account].stakedPnk[dispute.courtID] == 0 ) {
835
+ if (jurors[account].stakedPnk == 0 ) {
839
836
pinakion.safeTransfer (account, pnkLocked);
840
837
}
841
838
@@ -1014,10 +1011,11 @@ contract KlerosCore is IArbitratorV2 {
1014
1011
function getJurorBalance (
1015
1012
address _juror ,
1016
1013
uint96 _courtID
1017
- ) external view returns (uint256 staked , uint256 locked , uint256 nbCourts ) {
1014
+ ) external view returns (uint256 totalStaked , uint256 totalLocked , uint256 stakedInCourt , uint256 nbCourts ) {
1018
1015
Juror storage juror = jurors[_juror];
1019
- staked = juror.stakedPnk[_courtID];
1020
- locked = juror.lockedPnk[_courtID];
1016
+ totalStaked = juror.stakedPnk;
1017
+ totalLocked = juror.lockedPnk;
1018
+ stakedInCourt = juror.stakedPnkByCourt[_courtID];
1021
1019
nbCourts = juror.courtIDs.length ;
1022
1020
}
1023
1021
@@ -1110,35 +1108,33 @@ contract KlerosCore is IArbitratorV2 {
1110
1108
/// @param _account The address of the juror.
1111
1109
/// @param _courtID The ID of the court.
1112
1110
/// @param _stake The new stake.
1113
- /// @param _penalty Penalized amount won't be transferred back to juror when the stake is lowered.
1114
1111
/// @return succeeded True if the call succeeded, false otherwise.
1115
- function _setStakeForAccount (
1116
- address _account ,
1117
- uint96 _courtID ,
1118
- uint256 _stake ,
1119
- uint256 _penalty
1120
- ) internal returns (bool succeeded ) {
1112
+ function _setStakeForAccount (address _account , uint96 _courtID , uint256 _stake ) internal returns (bool succeeded ) {
1121
1113
if (_courtID == FORKING_COURT || _courtID > courts.length ) return false ;
1122
1114
1123
1115
Juror storage juror = jurors[_account];
1124
- uint256 currentStake = juror.stakedPnk [_courtID];
1116
+ uint256 currentStake = juror.stakedPnkByCourt [_courtID];
1125
1117
1126
1118
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 ;
1119
+ if (_stake < courts[_courtID].minStake) return false ;
1129
1120
}
1130
1121
1131
- ISortitionModule.preStakeHookResult result = sortitionModule.preStakeHook (_account, _courtID, _stake, _penalty );
1122
+ ISortitionModule.preStakeHookResult result = sortitionModule.preStakeHook (_account, _courtID, _stake);
1132
1123
if (result == ISortitionModule.preStakeHookResult.failed) {
1133
1124
return false ;
1134
1125
} else if (result == ISortitionModule.preStakeHookResult.delayed) {
1135
- emit StakeDelayed (_account, _courtID, _stake, _penalty );
1126
+ emit StakeDelayed (_account, _courtID, _stake);
1136
1127
return true ;
1137
1128
}
1138
1129
1139
1130
uint256 transferredAmount;
1140
1131
if (_stake >= currentStake) {
1141
- transferredAmount = _stake - currentStake;
1132
+ // When stakedPnk becomes lower than lockedPnk count the locked tokens in when transferring tokens from juror.
1133
+ // (E.g. stakedPnk = 0, lockedPnk = 150) which can happen if the juror unstaked fully while having some tokens locked.
1134
+ uint256 previouslyLocked = (juror.lockedPnk >= juror.stakedPnk) ? juror.lockedPnk - juror.stakedPnk : 0 ;
1135
+ transferredAmount = (_stake >= currentStake + previouslyLocked)
1136
+ ? _stake - currentStake - previouslyLocked
1137
+ : 0 ;
1142
1138
if (transferredAmount > 0 ) {
1143
1139
if (pinakion.safeTransferFrom (_account, address (this ), transferredAmount)) {
1144
1140
if (currentStake == 0 ) {
@@ -1150,8 +1146,14 @@ contract KlerosCore is IArbitratorV2 {
1150
1146
}
1151
1147
} else {
1152
1148
if (_stake == 0 ) {
1153
- // Keep locked PNKs in the contract and release them after dispute is executed.
1154
- transferredAmount = currentStake - juror.lockedPnk[_courtID] - _penalty;
1149
+ // Make sure locked tokens always stay in the contract. They can only be released during Execution.
1150
+ if (juror.stakedPnk >= currentStake + juror.lockedPnk) {
1151
+ // We have enough pnk staked to afford withdrawal of the current stake while keeping locked tokens.
1152
+ transferredAmount = currentStake;
1153
+ } else if (juror.stakedPnk >= juror.lockedPnk) {
1154
+ // Can't afford withdrawing the current stake fully. Take whatever is available while keeping locked tokens.
1155
+ transferredAmount = juror.stakedPnk - juror.lockedPnk;
1156
+ }
1155
1157
if (transferredAmount > 0 ) {
1156
1158
if (pinakion.safeTransfer (_account, transferredAmount)) {
1157
1159
for (uint256 i = juror.courtIDs.length ; i > 0 ; i-- ) {
@@ -1166,7 +1168,13 @@ contract KlerosCore is IArbitratorV2 {
1166
1168
}
1167
1169
}
1168
1170
} else {
1169
- transferredAmount = currentStake - _stake - _penalty;
1171
+ if (juror.stakedPnk >= currentStake - _stake + juror.lockedPnk) {
1172
+ // We have enough pnk staked to afford withdrawal while keeping locked tokens.
1173
+ transferredAmount = currentStake - _stake;
1174
+ } else if (juror.stakedPnk >= juror.lockedPnk) {
1175
+ // Can't afford withdrawing the current stake fully. Take whatever is available while keeping locked tokens.
1176
+ transferredAmount = juror.stakedPnk - juror.lockedPnk;
1177
+ }
1170
1178
if (transferredAmount > 0 ) {
1171
1179
if (! pinakion.safeTransfer (_account, transferredAmount)) {
1172
1180
return false ;
@@ -1175,8 +1183,9 @@ contract KlerosCore is IArbitratorV2 {
1175
1183
}
1176
1184
}
1177
1185
1178
- // Update juror's records.
1179
- juror.stakedPnk[_courtID] = _stake;
1186
+ // Note that stakedPnk can become async with currentStake (e.g. after penalty).
1187
+ juror.stakedPnk = (juror.stakedPnk >= currentStake) ? juror.stakedPnk - currentStake + _stake : _stake;
1188
+ juror.stakedPnkByCourt[_courtID] = _stake;
1180
1189
1181
1190
sortitionModule.setStake (_account, _courtID, _stake);
1182
1191
emit StakeSet (_account, _courtID, _stake);
0 commit comments