@@ -70,13 +70,6 @@ contract KlerosCore is IArbitratorV2, UUPSProxiable, Initializable {
7070 uint256 drawIterations; // The number of iterations passed drawing the jurors for this round.
7171 }
7272
73- struct Juror {
74- 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`.
75- uint256 stakedPnk; // The juror's total amount of tokens staked in subcourts. Reflects actual pnk balance.
76- uint256 lockedPnk; // The juror's total amount of tokens locked in disputes. Can reflect actual pnk balance when stakedPnk are fully withdrawn.
77- mapping (uint96 => uint256 ) stakedPnkByCourt; // The amount of PNKs the juror has staked in the court in the form `stakedPnkByCourt[courtID]`.
78- }
79-
8073 // Workaround "stack too deep" errors
8174 struct ExecuteParams {
8275 uint256 disputeID; // The ID of the dispute to execute.
@@ -107,21 +100,12 @@ contract KlerosCore is IArbitratorV2, UUPSProxiable, Initializable {
107100 Court[] public courts; // The courts.
108101 IDisputeKit[] public disputeKits; // Array of dispute kits.
109102 Dispute[] public disputes; // The disputes.
110- mapping (address => Juror) internal jurors; // The jurors.
111103 mapping (IERC20 => CurrencyRate) public currencyRates; // The price of each token in ETH.
112104
113105 // ************************************* //
114106 // * Events * //
115107 // ************************************* //
116108
117- event StakeSet (address indexed _address , uint256 _courtID , uint256 _amount );
118- event StakeDelayedNotTransferred (address indexed _address , uint256 _courtID , uint256 _amount );
119- event StakeDelayedAlreadyTransferred (address indexed _address , uint256 _courtID , uint256 _amount );
120- event StakeDelayedAlreadyTransferredWithdrawn (
121- uint96 indexed _courtID ,
122- address indexed _account ,
123- uint256 _withdrawnAmount
124- );
125109 event NewPeriod (uint256 indexed _disputeID , Period _period );
126110 event AppealPossible (uint256 indexed _disputeID , IArbitrableV2 indexed _arbitrable );
127111 event AppealDecision (uint256 indexed _disputeID , IArbitrableV2 indexed _arbitrable );
@@ -464,7 +448,7 @@ contract KlerosCore is IArbitratorV2, UUPSProxiable, Initializable {
464448 /// @param _newStake The new stake.
465449 /// Note that the existing delayed stake will be nullified as non-relevant.
466450 function setStake (uint96 _courtID , uint256 _newStake ) external {
467- if ( ! _setStakeForAccount ( msg .sender , _courtID, _newStake, false )) revert StakingFailed ( );
451+ _setStake ( msg .sender , _courtID, _newStake, false );
468452 }
469453
470454 function setStakeBySortitionModule (
@@ -474,31 +458,7 @@ contract KlerosCore is IArbitratorV2, UUPSProxiable, Initializable {
474458 bool _alreadyTransferred
475459 ) external {
476460 if (msg .sender != address (sortitionModule)) revert SortitionModuleOnly ();
477- _setStakeForAccount (_account, _courtID, _newStake, _alreadyTransferred);
478- }
479-
480- function withdrawPartiallyDelayedStake (uint96 _courtID , address _juror , uint256 _amountToWithdraw ) external {
481- if (msg .sender != address (sortitionModule)) revert SortitionModuleOnly ();
482- uint256 actualAmount = _amountToWithdraw;
483- Juror storage juror = jurors[_juror];
484- if (juror.stakedPnk <= actualAmount) {
485- actualAmount = juror.stakedPnk;
486- }
487- require (pinakion.safeTransfer (_juror, actualAmount));
488- // StakePnk can become lower because of penalty, thus we adjust the amount for it. stakedPnkByCourt can't be penalized so subtract the default amount.
489- juror.stakedPnk -= actualAmount;
490- juror.stakedPnkByCourt[_courtID] -= _amountToWithdraw;
491- emit StakeDelayedAlreadyTransferredWithdrawn (_courtID, _juror, _amountToWithdraw);
492- // Note that if we don't delete court here it'll be duplicated after staking.
493- if (juror.stakedPnkByCourt[_courtID] == 0 ) {
494- for (uint256 i = juror.courtIDs.length ; i > 0 ; i-- ) {
495- if (juror.courtIDs[i - 1 ] == _courtID) {
496- juror.courtIDs[i - 1 ] = juror.courtIDs[juror.courtIDs.length - 1 ];
497- juror.courtIDs.pop ();
498- break ;
499- }
500- }
501- }
461+ _setStake (_account, _courtID, _newStake, _alreadyTransferred);
502462 }
503463
504464 /// @inheritdoc IArbitratorV2
@@ -625,7 +585,7 @@ contract KlerosCore is IArbitratorV2, UUPSProxiable, Initializable {
625585 if (drawnAddress == address (0 )) {
626586 continue ;
627587 }
628- jurors[drawnAddress].lockedPnk += round.pnkAtStakePerJuror;
588+ sortitionModule. lockStake (drawnAddress, round.pnkAtStakePerJuror) ;
629589 emit Draw (drawnAddress, _disputeID, currentRound, round.drawnJurors.length );
630590 round.drawnJurors.push (drawnAddress);
631591 if (round.drawnJurors.length == round.nbVotes) {
@@ -764,15 +724,10 @@ contract KlerosCore is IArbitratorV2, UUPSProxiable, Initializable {
764724
765725 // Unlock the PNKs affected by the penalty
766726 address account = round.drawnJurors[_params.repartition];
767- jurors[account].lockedPnk -= penalty;
727+ sortitionModule. unlockStake (account, penalty) ;
768728
769729 // Apply the penalty to the staked PNKs.
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;
773- } else {
774- jurors[account].stakedPnk = 0 ;
775- }
730+ sortitionModule.penalizeStake (account, penalty);
776731 emit TokenAndETHShift (
777732 account,
778733 _params.disputeID,
@@ -831,10 +786,10 @@ contract KlerosCore is IArbitratorV2, UUPSProxiable, Initializable {
831786 uint256 pnkLocked = (round.pnkAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR;
832787
833788 // Release the rest of the PNKs of the juror for this round.
834- jurors[account].lockedPnk -= pnkLocked;
789+ sortitionModule. unlockStake (account, pnkLocked) ;
835790
836791 // Give back the locked PNKs in case the juror fully unstaked earlier.
837- if (jurors[account].stakedPnk == 0 ) {
792+ if (! sortitionModule. isJurorStaked (account) ) {
838793 pinakion.safeTransfer (account, pnkLocked);
839794 }
840795
@@ -980,17 +935,6 @@ contract KlerosCore is IArbitratorV2, UUPSProxiable, Initializable {
980935 return disputes[_disputeID].rounds.length ;
981936 }
982937
983- function getJurorBalance (
984- address _juror ,
985- uint96 _courtID
986- ) external view returns (uint256 totalStaked , uint256 totalLocked , uint256 stakedInCourt , uint256 nbCourts ) {
987- Juror storage juror = jurors[_juror];
988- totalStaked = juror.stakedPnk;
989- totalLocked = juror.lockedPnk;
990- stakedInCourt = juror.stakedPnkByCourt[_courtID];
991- nbCourts = juror.courtIDs.length ;
992- }
993-
994938 function isSupported (uint96 _courtID , uint256 _disputeKitID ) external view returns (bool ) {
995939 return courts[_courtID].supportedDisputeKits[_disputeKitID];
996940 }
@@ -1033,12 +977,6 @@ contract KlerosCore is IArbitratorV2, UUPSProxiable, Initializable {
1033977 return disputeKits.length ;
1034978 }
1035979
1036- /// @dev Gets the court identifiers where a specific `_juror` has staked.
1037- /// @param _juror The address of the juror.
1038- function getJurorCourtIDs (address _juror ) public view returns (uint96 [] memory ) {
1039- return jurors[_juror].courtIDs;
1040- }
1041-
1042980 function convertEthToTokenAmount (IERC20 _toToken , uint256 _amountInEth ) public view returns (uint256 ) {
1043981 return (_amountInEth * 10 ** currencyRates[_toToken].rateDecimals) / currencyRates[_toToken].rateInEth;
1044982 }
@@ -1056,104 +994,34 @@ contract KlerosCore is IArbitratorV2, UUPSProxiable, Initializable {
1056994 emit DisputeKitEnabled (_courtID, _disputeKitID, _enable);
1057995 }
1058996
1059- /// @dev Sets the specified juror's stake in a court.
1060- /// `O(n + p * log_k(j))` where
1061- /// `n` is the number of courts the juror has staked in,
1062- /// `p` is the depth of the court tree,
1063- /// `k` is the minimum number of children per node of one of these courts' sortition sum tree,
1064- /// and `j` is the maximum number of jurors that ever staked in one of these courts simultaneously.
1065- /// @param _account The address of the juror.
1066- /// @param _courtID The ID of the court.
1067- /// @param _newStake The new stake.
1068- /// @param _alreadyTransferred True if the tokens were already transferred from juror. Only relevant for delayed stakes.
1069- /// @return succeeded True if the call succeeded, false otherwise.
1070- function _setStakeForAccount (
997+ function _setStake (
1071998 address _account ,
1072999 uint96 _courtID ,
10731000 uint256 _newStake ,
10741001 bool _alreadyTransferred
1075- ) internal returns (bool succeeded ) {
1076- if (_courtID == Constants.FORKING_COURT || _courtID > courts.length ) return false ;
1077-
1078- Juror storage juror = jurors[_account];
1079- uint256 currentStake = juror.stakedPnkByCourt[_courtID];
1080-
1081- if (_newStake != 0 ) {
1082- if (_newStake < courts[_courtID].minStake) return false ;
1083- } else if (currentStake == 0 ) {
1084- return false ;
1002+ ) internal returns (bool success ) {
1003+ if (_courtID == Constants.FORKING_COURT || _courtID > courts.length ) {
1004+ return false ; // Staking directly into the forking court is not allowed.
10851005 }
1086-
1087- ISortitionModule.PreStakeHookResult result = sortitionModule.preStakeHook (_account, _courtID, _newStake);
1088- if (result == ISortitionModule.PreStakeHookResult.failed) {
1089- return false ;
1090- } else if (result == ISortitionModule.PreStakeHookResult.stakeDelayedNotTransferred) {
1091- emit StakeDelayedNotTransferred (_account, _courtID, _newStake);
1092- return true ;
1006+ if (_newStake != 0 && _newStake < courts[_courtID].minStake) {
1007+ return false ; // Staking less than the minimum stake is not allowed.
10931008 }
1094-
1095- uint256 transferredAmount;
1096- if (_newStake >= currentStake) {
1097- if (! _alreadyTransferred) {
1098- // Stake increase
1099- // When stakedPnk becomes lower than lockedPnk count the locked tokens in when transferring tokens from juror.
1100- // (E.g. stakedPnk = 0, lockedPnk = 150) which can happen if the juror unstaked fully while having some tokens locked.
1101- uint256 previouslyLocked = (juror.lockedPnk >= juror.stakedPnk) ? juror.lockedPnk - juror.stakedPnk : 0 ; // underflow guard
1102- transferredAmount = (_newStake >= currentStake + previouslyLocked) // underflow guard
1103- ? _newStake - currentStake - previouslyLocked
1104- : 0 ;
1105- if (transferredAmount > 0 ) {
1106- // Note we don't return false after incorrect transfer because when stake is increased the transfer is done immediately, thus it can't disrupt delayed stakes' queue.
1107- pinakion.safeTransferFrom (_account, address (this ), transferredAmount);
1108- }
1109- if (currentStake == 0 ) {
1110- juror.courtIDs.push (_courtID);
1111- }
1112- }
1113- } else {
1114- // Note that stakes can be partially delayed only when stake is increased.
1115- // Stake decrease: make sure locked tokens always stay in the contract. They can only be released during Execution.
1116- if (juror.stakedPnk >= currentStake - _newStake + juror.lockedPnk) {
1117- // We have enough pnk staked to afford withdrawal while keeping locked tokens.
1118- transferredAmount = currentStake - _newStake;
1119- } else if (juror.stakedPnk >= juror.lockedPnk) {
1120- // Can't afford withdrawing the current stake fully. Take whatever is available while keeping locked tokens.
1121- transferredAmount = juror.stakedPnk - juror.lockedPnk;
1122- }
1123- if (transferredAmount > 0 ) {
1124- if (! pinakion.safeTransfer (_account, transferredAmount)) {
1125- return false ;
1126- }
1127- }
1128- if (_newStake == 0 ) {
1129- for (uint256 i = juror.courtIDs.length ; i > 0 ; i-- ) {
1130- if (juror.courtIDs[i - 1 ] == _courtID) {
1131- juror.courtIDs[i - 1 ] = juror.courtIDs[juror.courtIDs.length - 1 ];
1132- juror.courtIDs.pop ();
1133- break ;
1134- }
1135- }
1009+ (uint256 pnkDeposit , uint256 pnkWithdrawal , bool sortitionSuccess ) = sortitionModule.setStake (
1010+ _account,
1011+ _courtID,
1012+ _newStake,
1013+ _alreadyTransferred
1014+ );
1015+ if (pnkDeposit > 0 && pnkWithdrawal > 0 ) revert StakingFailed ();
1016+ if (pnkDeposit > 0 ) {
1017+ // Note we don't return false after incorrect transfer because when stake is increased the transfer is done immediately, thus it can't disrupt delayed stakes' queue.
1018+ pinakion.safeTransferFrom (_account, address (this ), pnkDeposit);
1019+ } else if (pnkWithdrawal > 0 ) {
1020+ if (! pinakion.safeTransfer (_account, pnkWithdrawal)) {
1021+ return false ;
11361022 }
11371023 }
1138-
1139- // Note that stakedPnk can become async with currentStake (e.g. after penalty).
1140- // Also note that these values were already updated if the stake was only partially delayed.
1141- if (! _alreadyTransferred) {
1142- juror.stakedPnk = (juror.stakedPnk >= currentStake)
1143- ? juror.stakedPnk - currentStake + _newStake
1144- : _newStake;
1145- juror.stakedPnkByCourt[_courtID] = _newStake;
1146- }
1147-
1148- // Transfer the tokens but don't update sortition module.
1149- if (result == ISortitionModule.PreStakeHookResult.stakeDelayedAlreadyTransferred) {
1150- emit StakeDelayedAlreadyTransferred (_account, _courtID, _newStake);
1151- return true ;
1152- }
1153-
1154- sortitionModule.setStake (_account, _courtID, _newStake);
1155- emit StakeSet (_account, _courtID, _newStake);
1156- return true ;
1024+ return sortitionSuccess;
11571025 }
11581026
11591027 /// @dev Gets a court ID, the minimum number of jurors and an ID of a dispute kit from a specified extra data bytes array.
0 commit comments