@@ -127,6 +127,7 @@ contract KlerosCore is IArbitratorV2 {
127
127
128
128
event StakeSet (address indexed _address , uint256 _courtID , uint256 _amount );
129
129
event StakeDelayed (address indexed _address , uint256 _courtID , uint256 _amount , uint256 _penalty );
130
+ event StakePartiallyDelayed (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 );
@@ -482,12 +483,28 @@ contract KlerosCore is IArbitratorV2 {
482
483
/// @param _courtID The ID of the court.
483
484
/// @param _stake The new stake.
484
485
function setStake (uint96 _courtID , uint256 _stake ) external {
485
- if (! _setStakeForAccount (msg .sender , _courtID, _stake, 0 )) revert StakingFailed ();
486
+ if (! _setStakeForAccount (msg .sender , _courtID, _stake, 0 , false )) revert StakingFailed ();
486
487
}
487
488
488
- function setStakeBySortitionModule (address _account , uint96 _courtID , uint256 _stake , uint256 _penalty ) external {
489
+ function withdrawDelayedStake (uint256 _delayedStakeIndex ) external {
490
+ // All necessary checks are done in sortition module.
491
+ (uint256 amountToWithdraw , uint96 courtID ) = sortitionModule.removeDelayedStake (_delayedStakeIndex, msg .sender );
492
+ if (jurors[msg .sender ].stakedPnk[courtID] <= amountToWithdraw) {
493
+ amountToWithdraw = jurors[msg .sender ].stakedPnk[courtID];
494
+ }
495
+ require (pinakion.safeTransfer (msg .sender , amountToWithdraw));
496
+ jurors[msg .sender ].stakedPnk[courtID] -= amountToWithdraw;
497
+ }
498
+
499
+ function setStakeBySortitionModule (
500
+ address _account ,
501
+ uint96 _courtID ,
502
+ uint256 _stake ,
503
+ uint256 _penalty ,
504
+ bool _alreadyTransferred
505
+ ) external {
489
506
if (msg .sender != address (sortitionModule)) revert WrongCaller ();
490
- _setStakeForAccount (_account, _courtID, _stake, _penalty);
507
+ _setStakeForAccount (_account, _courtID, _stake, _penalty, _alreadyTransferred );
491
508
}
492
509
493
510
/// @inheritdoc IArbitratorV2
@@ -768,10 +785,11 @@ contract KlerosCore is IArbitratorV2 {
768
785
if (jurors[account].stakedPnk[dispute.courtID] >= courts[dispute.courtID].minStake + penalty) {
769
786
// The juror still has enough staked PNKs after penalty for this court.
770
787
uint256 newStake = jurors[account].stakedPnk[dispute.courtID] - penalty;
771
- _setStakeForAccount (account, dispute.courtID, newStake, penalty);
788
+ // `alreadyTransferred` flag can be true only after manual stake increase, which can't happen during penalty.
789
+ _setStakeForAccount (account, dispute.courtID, newStake, penalty, false );
772
790
} else if (jurors[account].stakedPnk[dispute.courtID] != 0 ) {
773
791
// The juror does not have enough staked PNKs after penalty for this court, unstake them.
774
- _setStakeForAccount (account, dispute.courtID, 0 , penalty);
792
+ _setStakeForAccount (account, dispute.courtID, 0 , penalty, false );
775
793
}
776
794
emit TokenAndETHShift (
777
795
account,
@@ -1110,12 +1128,14 @@ contract KlerosCore is IArbitratorV2 {
1110
1128
/// @param _courtID The ID of the court.
1111
1129
/// @param _stake The new stake.
1112
1130
/// @param _penalty Penalized amount won't be transferred back to juror when the stake is lowered.
1131
+ /// @param _alreadyTransferred True if the tokens were already transferred. Only relevant for delayed stake execution.
1113
1132
/// @return succeeded True if the call succeeded, false otherwise.
1114
1133
function _setStakeForAccount (
1115
1134
address _account ,
1116
1135
uint96 _courtID ,
1117
1136
uint256 _stake ,
1118
- uint256 _penalty
1137
+ uint256 _penalty ,
1138
+ bool _alreadyTransferred
1119
1139
) internal returns (bool succeeded ) {
1120
1140
if (_courtID == FORKING_COURT || _courtID > courts.length ) return false ;
1121
1141
@@ -1135,47 +1155,56 @@ contract KlerosCore is IArbitratorV2 {
1135
1155
return true ;
1136
1156
}
1137
1157
1138
- uint256 transferredAmount;
1139
- if (_stake >= currentStake) {
1140
- transferredAmount = _stake - currentStake;
1141
- if (transferredAmount > 0 ) {
1142
- if (pinakion.safeTransferFrom (_account, address (this ), transferredAmount)) {
1143
- if (currentStake == 0 ) {
1144
- juror.courtIDs.push (_courtID);
1145
- }
1146
- } else {
1147
- return false ;
1148
- }
1149
- }
1150
- } else {
1151
- if (_stake == 0 ) {
1152
- // Keep locked PNKs in the contract and release them after dispute is executed.
1153
- transferredAmount = currentStake - juror.lockedPnk[_courtID] - _penalty;
1158
+ // Don't transfer the tokens and only update the drawing chance if the transfer was already done.
1159
+ if (! _alreadyTransferred) {
1160
+ uint256 transferredAmount;
1161
+ if (_stake >= currentStake) {
1162
+ transferredAmount = _stake - currentStake;
1154
1163
if (transferredAmount > 0 ) {
1155
- if (pinakion.safeTransfer (_account, transferredAmount)) {
1156
- for (uint256 i = juror.courtIDs.length ; i > 0 ; i-- ) {
1157
- if (juror.courtIDs[i - 1 ] == _courtID) {
1158
- juror.courtIDs[i - 1 ] = juror.courtIDs[juror.courtIDs.length - 1 ];
1159
- juror.courtIDs.pop ();
1160
- break ;
1161
- }
1164
+ if (pinakion.safeTransferFrom (_account, address (this ), transferredAmount)) {
1165
+ if (currentStake == 0 ) {
1166
+ juror.courtIDs.push (_courtID);
1162
1167
}
1163
1168
} else {
1164
1169
return false ;
1165
1170
}
1166
1171
}
1167
1172
} else {
1168
- transferredAmount = currentStake - _stake - _penalty;
1169
- if (transferredAmount > 0 ) {
1170
- if (! pinakion.safeTransfer (_account, transferredAmount)) {
1171
- return false ;
1173
+ if (_stake == 0 ) {
1174
+ // Keep locked PNKs in the contract and release them after dispute is executed.
1175
+ transferredAmount = currentStake - juror.lockedPnk[_courtID] - _penalty;
1176
+ if (transferredAmount > 0 ) {
1177
+ if (pinakion.safeTransfer (_account, transferredAmount)) {
1178
+ for (uint256 i = juror.courtIDs.length ; i > 0 ; i-- ) {
1179
+ if (juror.courtIDs[i - 1 ] == _courtID) {
1180
+ juror.courtIDs[i - 1 ] = juror.courtIDs[juror.courtIDs.length - 1 ];
1181
+ juror.courtIDs.pop ();
1182
+ break ;
1183
+ }
1184
+ }
1185
+ } else {
1186
+ return false ;
1187
+ }
1188
+ }
1189
+ } else {
1190
+ transferredAmount = currentStake - _stake - _penalty;
1191
+ if (transferredAmount > 0 ) {
1192
+ if (! pinakion.safeTransfer (_account, transferredAmount)) {
1193
+ return false ;
1194
+ }
1172
1195
}
1173
1196
}
1174
1197
}
1198
+
1199
+ // Update juror's records.
1200
+ juror.stakedPnk[_courtID] = _stake;
1175
1201
}
1176
1202
1177
- // Update juror's records.
1178
- juror.stakedPnk[_courtID] = _stake;
1203
+ // Transfer the tokens but don't update sortition module.
1204
+ if (result == ISortitionModule.preStakeHookResult.partiallyDelayed) {
1205
+ emit StakePartiallyDelayed (_account, _courtID, _stake);
1206
+ return true ;
1207
+ }
1179
1208
1180
1209
sortitionModule.setStake (_account, _courtID, _stake);
1181
1210
emit StakeSet (_account, _courtID, _stake);
0 commit comments