@@ -127,6 +127,7 @@ contract KlerosCore is IArbitratorV2 {
127127
128128 event StakeSet (address indexed _address , uint256 _courtID , uint256 _amount );
129129 event StakeDelayed (address indexed _address , uint256 _courtID , uint256 _amount , uint256 _penalty );
130+ event StakePartiallyDelayed (address indexed _address , uint256 _courtID , uint256 _amount );
130131 event NewPeriod (uint256 indexed _disputeID , Period _period );
131132 event AppealPossible (uint256 indexed _disputeID , IArbitrableV2 indexed _arbitrable );
132133 event AppealDecision (uint256 indexed _disputeID , IArbitrableV2 indexed _arbitrable );
@@ -482,12 +483,28 @@ contract KlerosCore is IArbitratorV2 {
482483 /// @param _courtID The ID of the court.
483484 /// @param _stake The new stake.
484485 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 ();
486487 }
487488
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 {
489506 if (msg .sender != address (sortitionModule)) revert WrongCaller ();
490- _setStakeForAccount (_account, _courtID, _stake, _penalty);
507+ _setStakeForAccount (_account, _courtID, _stake, _penalty, _alreadyTransferred );
491508 }
492509
493510 /// @inheritdoc IArbitratorV2
@@ -768,10 +785,11 @@ contract KlerosCore is IArbitratorV2 {
768785 if (jurors[account].stakedPnk[dispute.courtID] >= courts[dispute.courtID].minStake + penalty) {
769786 // The juror still has enough staked PNKs after penalty for this court.
770787 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 );
772790 } else if (jurors[account].stakedPnk[dispute.courtID] != 0 ) {
773791 // 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 );
775793 }
776794 emit TokenAndETHShift (
777795 account,
@@ -1110,12 +1128,14 @@ contract KlerosCore is IArbitratorV2 {
11101128 /// @param _courtID The ID of the court.
11111129 /// @param _stake The new stake.
11121130 /// @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.
11131132 /// @return succeeded True if the call succeeded, false otherwise.
11141133 function _setStakeForAccount (
11151134 address _account ,
11161135 uint96 _courtID ,
11171136 uint256 _stake ,
1118- uint256 _penalty
1137+ uint256 _penalty ,
1138+ bool _alreadyTransferred
11191139 ) internal returns (bool succeeded ) {
11201140 if (_courtID == FORKING_COURT || _courtID > courts.length ) return false ;
11211141
@@ -1135,47 +1155,56 @@ contract KlerosCore is IArbitratorV2 {
11351155 return true ;
11361156 }
11371157
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;
11541163 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);
11621167 }
11631168 } else {
11641169 return false ;
11651170 }
11661171 }
11671172 } 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+ }
11721195 }
11731196 }
11741197 }
1198+
1199+ // Update juror's records.
1200+ juror.stakedPnk[_courtID] = _stake;
11751201 }
11761202
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+ }
11791208
11801209 sortitionModule.setStake (_account, _courtID, _stake);
11811210 emit StakeSet (_account, _courtID, _stake);
0 commit comments