Skip to content

Commit f0f2d1e

Browse files
committed
refactor: moved the juror stakes accounting from KlerosCore to SortitionModule
1 parent 7e0d1bd commit f0f2d1e

File tree

8 files changed

+306
-263
lines changed

8 files changed

+306
-263
lines changed

contracts/src/arbitration/KlerosCore.sol

Lines changed: 27 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)