diff --git a/contracts/src/arbitration/KlerosCoreBase.sol b/contracts/src/arbitration/KlerosCoreBase.sol index 6a435489b..b5d1e1c72 100644 --- a/contracts/src/arbitration/KlerosCoreBase.sol +++ b/contracts/src/arbitration/KlerosCoreBase.sol @@ -463,22 +463,16 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable /// @param _newStake The new stake. /// Note that the existing delayed stake will be nullified as non-relevant. function setStake(uint96 _courtID, uint256 _newStake) external virtual whenNotPaused { - _setStake(msg.sender, _courtID, _newStake, false, OnError.Revert); + _setStake(msg.sender, _courtID, _newStake, OnError.Revert); } /// @dev Sets the stake of a specified account in a court, typically to apply a delayed stake or unstake inactive jurors. /// @param _account The account whose stake is being set. /// @param _courtID The ID of the court. /// @param _newStake The new stake. - /// @param _alreadyTransferred Whether the PNKs have already been transferred to the contract. - function setStakeBySortitionModule( - address _account, - uint96 _courtID, - uint256 _newStake, - bool _alreadyTransferred - ) external { + function setStakeBySortitionModule(address _account, uint96 _courtID, uint256 _newStake) external { if (msg.sender != address(sortitionModule)) revert SortitionModuleOnly(); - _setStake(_account, _courtID, _newStake, _alreadyTransferred, OnError.Return); + _setStake(_account, _courtID, _newStake, OnError.Return); } /// @inheritdoc IArbitratorV2 @@ -609,7 +603,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable if (drawnAddress == address(0)) { continue; } - sortitionModule.lockStake(drawnAddress, round.pnkAtStakePerJuror); + sortitionModule.lockStake(drawnAddress, dispute.courtID, round.pnkAtStakePerJuror); emit Draw(drawnAddress, _disputeID, currentRound, round.drawnJurors.length); round.drawnJurors.push(drawnAddress); if (round.drawnJurors.length == round.nbVotes) { @@ -774,28 +768,34 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable // Fully coherent jurors won't be penalized. uint256 penalty = (round.pnkAtStakePerJuror * (ALPHA_DIVISOR - degreeOfCoherence)) / ALPHA_DIVISOR; - _params.pnkPenaltiesInRound += penalty; // Unlock the PNKs affected by the penalty address account = round.drawnJurors[_params.repartition]; - sortitionModule.unlockStake(account, penalty); + sortitionModule.unlockStake(account, dispute.courtID, penalty); // Apply the penalty to the staked PNKs. - sortitionModule.penalizeStake(account, penalty); + (uint256 pnkBalance, uint256 availablePenalty) = sortitionModule.penalizeStake( + account, + dispute.courtID, + penalty + ); + _params.pnkPenaltiesInRound += availablePenalty; emit TokenAndETHShift( account, _params.disputeID, _params.round, degreeOfCoherence, - -int256(penalty), + -int256(availablePenalty), 0, round.feeToken ); - if (!disputeKit.isVoteActive(_params.disputeID, _params.round, _params.repartition)) { - // The juror is inactive, unstake them. + // Unstake the juror from all courts if he was inactive or his balance can't cover penalties anymore. + if (pnkBalance == 0 || !disputeKit.isVoteActive(_params.disputeID, _params.round, _params.repartition)) { sortitionModule.setJurorInactive(account); } + + // Transfer any residual dispute fees to the governor. It may happen due to partial coherence of the jurors. if (_params.repartition == _params.numberOfVotesInRound - 1 && _params.coherentCount == 0) { // No one was coherent, send the rewards to the governor. if (round.feeToken == NATIVE_CURRENCY) { @@ -842,12 +842,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable uint256 pnkLocked = (round.pnkAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR; // Release the rest of the PNKs of the juror for this round. - sortitionModule.unlockStake(account, pnkLocked); - - // Give back the locked PNKs in case the juror fully unstaked earlier. - if (!sortitionModule.isJurorStaked(account)) { - pinakion.safeTransfer(account, pnkLocked); - } + sortitionModule.unlockStake(account, dispute.courtID, pnkLocked); // Transfer the rewards uint256 pnkReward = ((_params.pnkPenaltiesInRound / _params.coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR; @@ -1074,16 +1069,9 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable /// @param _account The account to set the stake for. /// @param _courtID The ID of the court to set the stake for. /// @param _newStake The new stake. - /// @param _alreadyTransferred Whether the PNKs were already transferred to/from the staking contract. /// @param _onError Whether to revert or return false on error. /// @return Whether the stake was successfully set or not. - function _setStake( - address _account, - uint96 _courtID, - uint256 _newStake, - bool _alreadyTransferred, - OnError _onError - ) internal returns (bool) { + function _setStake(address _account, uint96 _courtID, uint256 _newStake, OnError _onError) internal returns (bool) { if (_courtID == FORKING_COURT || _courtID >= courts.length) { _stakingFailed(_onError, StakingResult.CannotStakeInThisCourt); // Staking directly into the forking court is not allowed. return false; @@ -1092,15 +1080,17 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable _stakingFailed(_onError, StakingResult.CannotStakeLessThanMinStake); // Staking less than the minimum stake is not allowed. return false; } - (uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult) = sortitionModule.setStake( - _account, - _courtID, - _newStake, - _alreadyTransferred - ); - if (stakingResult != StakingResult.Successful) { + ( + uint256 pnkDeposit, + uint256 pnkWithdrawal, + uint256 actualNewStake, + StakingResult stakingResult + ) = sortitionModule.validateStake(_account, _courtID, _newStake); + if (stakingResult != StakingResult.Successful && stakingResult != StakingResult.Delayed) { _stakingFailed(_onError, stakingResult); return false; + } else if (stakingResult == StakingResult.Delayed) { + return true; } if (pnkDeposit > 0) { if (!pinakion.safeTransferFrom(_account, address(this), pnkDeposit)) { @@ -1114,6 +1104,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable return false; } } + sortitionModule.setStake(_account, _courtID, actualNewStake); return true; } diff --git a/contracts/src/arbitration/KlerosCoreNeo.sol b/contracts/src/arbitration/KlerosCoreNeo.sol index 988e42a53..49019252b 100644 --- a/contracts/src/arbitration/KlerosCoreNeo.sol +++ b/contracts/src/arbitration/KlerosCoreNeo.sol @@ -105,7 +105,7 @@ contract KlerosCoreNeo is KlerosCoreBase { /// Note that the existing delayed stake will be nullified as non-relevant. function setStake(uint96 _courtID, uint256 _newStake) external override whenNotPaused { if (jurorNft.balanceOf(msg.sender) == 0) revert NotEligibleForStaking(); - super._setStake(msg.sender, _courtID, _newStake, false, OnError.Revert); + super._setStake(msg.sender, _courtID, _newStake, OnError.Revert); } // ************************************* // diff --git a/contracts/src/arbitration/SortitionModuleBase.sol b/contracts/src/arbitration/SortitionModuleBase.sol index edb10edf1..81e372973 100644 --- a/contracts/src/arbitration/SortitionModuleBase.sol +++ b/contracts/src/arbitration/SortitionModuleBase.sol @@ -25,13 +25,6 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr // * Enums / Structs * // // ************************************* // - enum PreStakeHookResult { - ok, // Correct phase. All checks are passed. - stakeDelayedAlreadyTransferred, // Wrong phase but stake is increased, so transfer the tokens without updating the drawing chance. - stakeDelayedNotTransferred, // Wrong phase and stake is decreased. Delay the token transfer and drawing chance update. - failed // Checks didn't pass. Do no changes. - } - struct SortitionSumTree { uint256 K; // The maximum number of children per node. // We use this to keep track of vacant positions in the tree after removing a leaf. This is for keeping the tree as balanced as possible without spending gas on moving nodes around. @@ -46,13 +39,14 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr address account; // The address of the juror. uint96 courtID; // The ID of the court. uint256 stake; // The new stake. - bool alreadyTransferred; // True if tokens were already transferred before delayed stake's execution. + bool alreadyTransferred; // DEPRECATED. True if tokens were already transferred before delayed stake's execution. } struct Juror { 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`. uint256 stakedPnk; // The juror's total amount of tokens staked in subcourts. Reflects actual pnk balance. - uint256 lockedPnk; // The juror's total amount of tokens locked in disputes. Can reflect actual pnk balance when stakedPnk are fully withdrawn. + uint256 lockedPnk; // The juror's total amount of tokens locked in disputes. + mapping(uint96 courtID => uint256 stake) lockedInCourt; // // The juror's tokens locked in particular courts. } // ************************************* // @@ -75,7 +69,7 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr mapping(bytes32 treeHash => SortitionSumTree) sortitionSumTrees; // The mapping trees by keys. mapping(address account => Juror) public jurors; // The jurors. mapping(uint256 => DelayedStake) public delayedStakes; // Stores the stakes that were changed during Drawing phase, to update them when the phase is switched to Staking. - mapping(address jurorAccount => mapping(uint96 courtId => uint256)) public latestDelayedStakeIndex; // Maps the juror to its latest delayed stake. If there is already a delayed stake for this juror then it'll be replaced. latestDelayedStakeIndex[juror][courtID]. + mapping(address jurorAccount => mapping(uint96 courtId => uint256)) public latestDelayedStakeIndex; // DEPRECATED. Maps the juror to its latest delayed stake. If there is already a delayed stake for this juror then it'll be replaced. latestDelayedStakeIndex[juror][courtID]. // ************************************* // // * Events * // @@ -88,30 +82,41 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr /// @param _amountAllCourts The amount of tokens staked in all courts. event StakeSet(address indexed _address, uint256 _courtID, uint256 _amount, uint256 _amountAllCourts); - /// @notice Emitted when a juror's stake is delayed and tokens are not transferred yet. + /// @notice DEPRECATED Emitted when a juror's stake is delayed and tokens are not transferred yet. /// @param _address The address of the juror. /// @param _courtID The ID of the court. /// @param _amount The amount of tokens staked in the court. event StakeDelayedNotTransferred(address indexed _address, uint256 _courtID, uint256 _amount); - /// @notice Emitted when a juror's stake is delayed and tokens are already deposited. + /// @notice DEPRECATED Emitted when a juror's stake is delayed and tokens are already deposited. /// @param _address The address of the juror. /// @param _courtID The ID of the court. /// @param _amount The amount of tokens staked in the court. event StakeDelayedAlreadyTransferredDeposited(address indexed _address, uint256 _courtID, uint256 _amount); - /// @notice Emitted when a juror's stake is delayed and tokens are already withdrawn. + /// @notice DEPRECATED Emitted when a juror's stake is delayed and tokens are already withdrawn. /// @param _address The address of the juror. /// @param _courtID The ID of the court. /// @param _amount The amount of tokens withdrawn. event StakeDelayedAlreadyTransferredWithdrawn(address indexed _address, uint96 indexed _courtID, uint256 _amount); + /// @notice Emitted when a juror's stake is delayed. + /// @param _address The address of the juror. + /// @param _courtID The ID of the court. + /// @param _amount The amount of tokens staked in the court. + event StakeDelayed(address indexed _address, uint96 indexed _courtID, uint256 _amount); + /// @notice Emitted when a juror's stake is locked. /// @param _address The address of the juror. /// @param _relativeAmount The amount of tokens locked. /// @param _unlock Whether the stake is locked or unlocked. event StakeLocked(address indexed _address, uint256 _relativeAmount, bool _unlock); + /// @dev Emitted when leftover PNK is withdrawn. + /// @param _account The account of the juror withdrawing PNK. + /// @param _amount The amount of PNK withdrawn. + event LeftoverPNKWithdrawn(address indexed _account, uint256 _amount); + // ************************************* // // * Constructor * // // ************************************* // @@ -148,6 +153,14 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr _; } + modifier onlyByCoreOrThis() { + require( + address(core) == msg.sender || address(this) == msg.sender, + "Access not allowed: KlerosCore or SortitionModule only." + ); + _; + } + // ************************************* // // * Governance * // // ************************************* // @@ -237,18 +250,8 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr for (uint256 i = delayedStakeReadIndex; i < newDelayedStakeReadIndex; i++) { DelayedStake storage delayedStake = delayedStakes[i]; - // Delayed stake could've been manually removed already. In this case simply move on to the next item. - if (delayedStake.account != address(0)) { - // Nullify the index so the delayed stake won't get deleted before its own execution. - delete latestDelayedStakeIndex[delayedStake.account][delayedStake.courtID]; - core.setStakeBySortitionModule( - delayedStake.account, - delayedStake.courtID, - delayedStake.stake, - delayedStake.alreadyTransferred - ); - delete delayedStakes[i]; - } + core.setStakeBySortitionModule(delayedStake.account, delayedStake.courtID, delayedStake.stake); + delete delayedStakes[i]; } delayedStakeReadIndex = newDelayedStakeReadIndex; } @@ -274,66 +277,98 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr /// @param _account The address of the juror. /// @param _courtID The ID of the court. /// @param _newStake The new stake. - /// @param _alreadyTransferred True if the tokens were already transferred from juror. Only relevant for delayed stakes. /// @return pnkDeposit The amount of PNK to be deposited. /// @return pnkWithdrawal The amount of PNK to be withdrawn. + /// @return actualNewStake The actual new stake. /// @return stakingResult The result of the staking operation. - function setStake( + function validateStake( address _account, uint96 _courtID, - uint256 _newStake, - bool _alreadyTransferred - ) external override onlyByCore returns (uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult) { - (pnkDeposit, pnkWithdrawal, stakingResult) = _setStake(_account, _courtID, _newStake, _alreadyTransferred); + uint256 _newStake + ) + external + override + onlyByCore + returns (uint256 pnkDeposit, uint256 pnkWithdrawal, uint256 actualNewStake, StakingResult stakingResult) + { + (pnkDeposit, pnkWithdrawal, actualNewStake, stakingResult) = _validateStake(_account, _courtID, _newStake); } /// @dev Sets the specified juror's stake in a court. - /// Note: no state changes should be made when returning `succeeded` = false, otherwise delayed stakes might break invariants. - function _setStake( + /// Note: no state changes should be made when returning stakingResult != Successful, otherwise delayed stakes might break invariants. + function _validateStake( address _account, uint96 _courtID, - uint256 _newStake, - bool _alreadyTransferred - ) internal virtual returns (uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult) { + uint256 _newStake + ) + internal + virtual + returns (uint256 pnkDeposit, uint256 pnkWithdrawal, uint256 actualNewStake, StakingResult stakingResult) + { Juror storage juror = jurors[_account]; uint256 currentStake = stakeOf(_account, _courtID); uint256 nbCourts = juror.courtIDs.length; if (currentStake == 0 && nbCourts >= MAX_STAKE_PATHS) { - return (0, 0, StakingResult.CannotStakeInMoreCourts); // Prevent staking beyond MAX_STAKE_PATHS but unstaking is always allowed. + return (0, 0, currentStake, StakingResult.CannotStakeInMoreCourts); // Prevent staking beyond MAX_STAKE_PATHS but unstaking is always allowed. } if (currentStake == 0 && _newStake == 0) { - return (0, 0, StakingResult.CannotStakeZeroWhenNoStake); // Forbid staking 0 amount when current stake is 0 to avoid flaky behaviour. + return (0, 0, currentStake, StakingResult.CannotStakeZeroWhenNoStake); // Forbid staking 0 amount when current stake is 0 to avoid flaky behaviour. } - pnkWithdrawal = _deleteDelayedStake(_courtID, _account); if (phase != Phase.staking) { // Store the stake change as delayed, to be applied when the phase switches back to Staking. DelayedStake storage delayedStake = delayedStakes[++delayedStakeWriteIndex]; delayedStake.account = _account; delayedStake.courtID = _courtID; delayedStake.stake = _newStake; - latestDelayedStakeIndex[_account][_courtID] = delayedStakeWriteIndex; - if (_newStake > currentStake) { - // PNK deposit: tokens are transferred now. - delayedStake.alreadyTransferred = true; - pnkDeposit = _increaseStake(juror, _courtID, _newStake, currentStake); - emit StakeDelayedAlreadyTransferredDeposited(_account, _courtID, _newStake); - } else { - // PNK withdrawal: tokens are not transferred yet. - emit StakeDelayedNotTransferred(_account, _courtID, _newStake); - } - return (pnkDeposit, pnkWithdrawal, StakingResult.Successful); + emit StakeDelayed(_account, _courtID, _newStake); + return (0, 0, currentStake, StakingResult.Delayed); } - // Current phase is Staking: set normal stakes or delayed stakes (which may have been already transferred). + // Current phase is Staking: set normal stakes or delayed stakes. + actualNewStake = _newStake; if (_newStake >= currentStake) { - if (!_alreadyTransferred) { - pnkDeposit = _increaseStake(juror, _courtID, _newStake, currentStake); + pnkDeposit = _newStake - currentStake; + } else { + pnkWithdrawal = currentStake - _newStake; + // Ensure locked tokens remain in the contract. They can only be released during Execution. + uint256 possibleWithdrawal = currentStake > juror.lockedInCourt[_courtID] + ? currentStake - juror.lockedInCourt[_courtID] + : 0; + if (pnkWithdrawal > possibleWithdrawal) { + pnkWithdrawal = possibleWithdrawal; + actualNewStake = currentStake - pnkWithdrawal; + } + } + return (pnkDeposit, pnkWithdrawal, actualNewStake, StakingResult.Successful); + } + + /// @dev Called by KC at the end of setStake flow. + function setStake(address _account, uint96 _courtID, uint256 _newStake) public override onlyByCoreOrThis { + Juror storage juror = jurors[_account]; + uint256 currentStake = stakeOf(_account, _courtID); + + if (_newStake == currentStake) return; + + if (_newStake > currentStake) { + juror.stakedPnk += _newStake - currentStake; + if (currentStake == 0) { + juror.courtIDs.push(_courtID); } } else { - pnkWithdrawal += _decreaseStake(juror, _courtID, _newStake, currentStake); + juror.stakedPnk -= currentStake - _newStake; + if (_newStake == 0) { + // Cleanup + for (uint256 i = juror.courtIDs.length; i > 0; i--) { + if (juror.courtIDs[i - 1] == _courtID) { + juror.courtIDs[i - 1] = juror.courtIDs[juror.courtIDs.length - 1]; + juror.courtIDs.pop(); + break; + } + } + } } // Update the sortition sum tree. @@ -350,114 +385,32 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr } } emit StakeSet(_account, _courtID, _newStake, juror.stakedPnk); - return (pnkDeposit, pnkWithdrawal, StakingResult.Successful); - } - - /// @dev Checks if there is already a delayed stake. In this case consider it irrelevant and remove it. - /// @param _courtID ID of the court. - /// @param _juror Juror whose stake to check. - function _deleteDelayedStake(uint96 _courtID, address _juror) internal returns (uint256 actualAmountToWithdraw) { - uint256 latestIndex = latestDelayedStakeIndex[_juror][_courtID]; - if (latestIndex != 0) { - DelayedStake storage delayedStake = delayedStakes[latestIndex]; - if (delayedStake.alreadyTransferred) { - // Sortition stake represents the stake value that was last updated during Staking phase. - uint256 sortitionStake = stakeOf(_juror, _courtID); - - // Withdraw the tokens that were added with the latest delayed stake. - uint256 amountToWithdraw = delayedStake.stake - sortitionStake; - actualAmountToWithdraw = amountToWithdraw; - Juror storage juror = jurors[_juror]; - if (juror.stakedPnk <= actualAmountToWithdraw) { - actualAmountToWithdraw = juror.stakedPnk; - } - - // StakePnk can become lower because of penalty. - juror.stakedPnk -= actualAmountToWithdraw; - emit StakeDelayedAlreadyTransferredWithdrawn(_juror, _courtID, amountToWithdraw); - - if (sortitionStake == 0) { - // Cleanup: delete the court otherwise it will be duplicated after staking. - for (uint256 i = juror.courtIDs.length; i > 0; i--) { - if (juror.courtIDs[i - 1] == _courtID) { - juror.courtIDs[i - 1] = juror.courtIDs[juror.courtIDs.length - 1]; - juror.courtIDs.pop(); - break; - } - } - } - } - delete delayedStakes[latestIndex]; - delete latestDelayedStakeIndex[_juror][_courtID]; - } } - function _increaseStake( - Juror storage juror, - uint96 _courtID, - uint256 _newStake, - uint256 _currentStake - ) internal returns (uint256 transferredAmount) { - // Stake increase - // When stakedPnk becomes lower than lockedPnk count the locked tokens in when transferring tokens from juror. - // (E.g. stakedPnk = 0, lockedPnk = 150) which can happen if the juror unstaked fully while having some tokens locked. - uint256 previouslyLocked = (juror.lockedPnk >= juror.stakedPnk) ? juror.lockedPnk - juror.stakedPnk : 0; // underflow guard - transferredAmount = (_newStake >= _currentStake + previouslyLocked) // underflow guard - ? _newStake - _currentStake - previouslyLocked - : 0; - if (_currentStake == 0) { - juror.courtIDs.push(_courtID); - } - // stakedPnk can become async with _currentStake (e.g. after penalty). - juror.stakedPnk = (juror.stakedPnk >= _currentStake) ? juror.stakedPnk - _currentStake + _newStake : _newStake; - } - - function _decreaseStake( - Juror storage juror, - uint96 _courtID, - uint256 _newStake, - uint256 _currentStake - ) internal returns (uint256 transferredAmount) { - // Stakes can be partially delayed only when stake is increased. - // Stake decrease: make sure locked tokens always stay in the contract. They can only be released during Execution. - if (juror.stakedPnk >= _currentStake - _newStake + juror.lockedPnk) { - // We have enough pnk staked to afford withdrawal while keeping locked tokens. - transferredAmount = _currentStake - _newStake; - } else if (juror.stakedPnk >= juror.lockedPnk) { - // Can't afford withdrawing the current stake fully. Take whatever is available while keeping locked tokens. - transferredAmount = juror.stakedPnk - juror.lockedPnk; - } - if (_newStake == 0) { - // Cleanup - for (uint256 i = juror.courtIDs.length; i > 0; i--) { - if (juror.courtIDs[i - 1] == _courtID) { - juror.courtIDs[i - 1] = juror.courtIDs[juror.courtIDs.length - 1]; - juror.courtIDs.pop(); - break; - } - } - } - // stakedPnk can become async with _currentStake (e.g. after penalty). - juror.stakedPnk = (juror.stakedPnk >= _currentStake) ? juror.stakedPnk - _currentStake + _newStake : _newStake; - } - - function lockStake(address _account, uint256 _relativeAmount) external override onlyByCore { + function lockStake(address _account, uint96 _courtID, uint256 _relativeAmount) external override onlyByCore { jurors[_account].lockedPnk += _relativeAmount; + jurors[_account].lockedInCourt[_courtID] += _relativeAmount; emit StakeLocked(_account, _relativeAmount, false); } - function unlockStake(address _account, uint256 _relativeAmount) external override onlyByCore { - jurors[_account].lockedPnk -= _relativeAmount; + function unlockStake(address _account, uint96 _courtID, uint256 _relativeAmount) external override onlyByCore { + Juror storage juror = jurors[_account]; + juror.lockedPnk -= _relativeAmount; + juror.lockedInCourt[_courtID] -= _relativeAmount; emit StakeLocked(_account, _relativeAmount, true); } - function penalizeStake(address _account, uint256 _relativeAmount) external override onlyByCore { + function penalizeStake( + address _account, + uint96 _courtID, + uint256 _relativeAmount + ) external override onlyByCore returns (uint256 pnkBalance, uint256 availablePenalty) { Juror storage juror = jurors[_account]; - if (juror.stakedPnk >= _relativeAmount) { - juror.stakedPnk -= _relativeAmount; - } else { - juror.stakedPnk = 0; // stakedPnk might become lower after manual unstaking, but lockedPnk will always cover the difference. - } + uint256 currentStake = stakeOf(_account, _courtID); + availablePenalty = currentStake >= _relativeAmount ? _relativeAmount : currentStake; + this.setStake(_account, _courtID, currentStake - availablePenalty); + pnkBalance = juror.stakedPnk; + return (pnkBalance, availablePenalty); } /// @dev Unstakes the inactive juror from all courts. @@ -470,7 +423,7 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr function setJurorInactive(address _account) external override onlyByCore { uint96[] memory courtIDs = getJurorCourtIDs(_account); for (uint256 j = courtIDs.length; j > 0; j--) { - core.setStakeBySortitionModule(_account, courtIDs[j - 1], 0, false); + core.setStakeBySortitionModule(_account, courtIDs[j - 1], 0); } } diff --git a/contracts/src/arbitration/SortitionModuleNeo.sol b/contracts/src/arbitration/SortitionModuleNeo.sol index 2e60307d2..10cdd878b 100644 --- a/contracts/src/arbitration/SortitionModuleNeo.sol +++ b/contracts/src/arbitration/SortitionModuleNeo.sol @@ -84,22 +84,26 @@ contract SortitionModuleNeo is SortitionModuleBase { // * State Modifiers * // // ************************************* // - function _setStake( + function _validateStake( address _account, uint96 _courtID, - uint256 _newStake, - bool _alreadyTransferred - ) internal override onlyByCore returns (uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult) { + uint256 _newStake + ) + internal + override + onlyByCore + returns (uint256 pnkDeposit, uint256 pnkWithdrawal, uint256 actualNewStake, StakingResult stakingResult) + { uint256 currentStake = stakeOf(_account, _courtID); bool stakeIncrease = _newStake > currentStake; uint256 stakeChange = stakeIncrease ? _newStake - currentStake : currentStake - _newStake; Juror storage juror = jurors[_account]; - if (stakeIncrease && !_alreadyTransferred) { + if (stakeIncrease) { if (juror.stakedPnk + stakeChange > maxStakePerJuror) { - return (0, 0, StakingResult.CannotStakeMoreThanMaxStakePerJuror); + return (0, 0, currentStake, StakingResult.CannotStakeMoreThanMaxStakePerJuror); } if (totalStaked + stakeChange > maxTotalStaked) { - return (0, 0, StakingResult.CannotStakeMoreThanMaxTotalStaked); + return (0, 0, currentStake, StakingResult.CannotStakeMoreThanMaxTotalStaked); } } if (phase == Phase.staking) { @@ -109,11 +113,10 @@ contract SortitionModuleNeo is SortitionModuleBase { totalStaked -= stakeChange; } } - (pnkDeposit, pnkWithdrawal, stakingResult) = super._setStake( + (pnkDeposit, pnkWithdrawal, actualNewStake, stakingResult) = super._validateStake( _account, _courtID, - _newStake, - _alreadyTransferred + _newStake ); } } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol index e3ed491eb..6707bd91b 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -610,7 +610,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi /// Note that we don't check the minStake requirement here because of the implicit staking in parent courts. /// minStake is checked directly during staking process however it's possible for the juror to get drawn /// while having < minStake if it is later increased by governance. - /// This issue is expected and harmless since we check for insolvency anyway. + /// This issue is expected and harmless. /// @param _round The round in which the juror is being drawn. /// @param _coreDisputeID ID of the dispute in the core contract. /// @param _juror Chosen address. @@ -620,19 +620,13 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi uint256 _coreDisputeID, address _juror ) internal view virtual returns (bool result) { - (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); - uint256 lockedAmountPerJuror = core.getPnkAtStakePerJuror( - _coreDisputeID, - core.getNumberOfRounds(_coreDisputeID) - 1 - ); - (uint256 totalStaked, uint256 totalLocked, , ) = core.sortitionModule().getJurorBalance(_juror, courtID); - result = totalStaked >= totalLocked + lockedAmountPerJuror; - if (singleDrawPerJuror) { uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID]; Dispute storage dispute = disputes[localDisputeID]; uint256 localRoundID = dispute.rounds.length - 1; - result = result && !alreadyDrawn[localDisputeID][localRoundID][_juror]; + result = !alreadyDrawn[localDisputeID][localRoundID][_juror]; + } else { + result = true; } } } diff --git a/contracts/src/arbitration/interfaces/ISortitionModule.sol b/contracts/src/arbitration/interfaces/ISortitionModule.sol index c68490222..97b447263 100644 --- a/contracts/src/arbitration/interfaces/ISortitionModule.sol +++ b/contracts/src/arbitration/interfaces/ISortitionModule.sol @@ -14,20 +14,25 @@ interface ISortitionModule { function createTree(bytes32 _key, bytes memory _extraData) external; - function setStake( + function validateStake( address _account, uint96 _courtID, - uint256 _newStake, - bool _alreadyTransferred - ) external returns (uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult); + uint256 _newStake + ) external returns (uint256 pnkDeposit, uint256 pnkWithdrawal, uint256 actualNewStake, StakingResult stakingResult); + + function setStake(address _account, uint96 _courtID, uint256 _newStake) external; function setJurorInactive(address _account) external; - function lockStake(address _account, uint256 _relativeAmount) external; + function lockStake(address _account, uint96 _courtID, uint256 _relativeAmount) external; - function unlockStake(address _account, uint256 _relativeAmount) external; + function unlockStake(address _account, uint96 _courtID, uint256 _relativeAmount) external; - function penalizeStake(address _account, uint256 _relativeAmount) external; + function penalizeStake( + address _account, + uint96 _courtID, + uint256 _relativeAmount + ) external returns (uint256 pnkBalance, uint256 availablePenalty); function notifyRandomNumber(uint256 _drawnNumber) external; diff --git a/contracts/src/arbitration/university/KlerosCoreUniversity.sol b/contracts/src/arbitration/university/KlerosCoreUniversity.sol index e7182a809..c19abe222 100644 --- a/contracts/src/arbitration/university/KlerosCoreUniversity.sol +++ b/contracts/src/arbitration/university/KlerosCoreUniversity.sol @@ -456,22 +456,16 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { /// @param _newStake The new stake. /// Note that the existing delayed stake will be nullified as non-relevant. function setStake(uint96 _courtID, uint256 _newStake) external { - _setStake(msg.sender, _courtID, _newStake, false, OnError.Revert); + _setStake(msg.sender, _courtID, _newStake, OnError.Revert); } /// @dev Sets the stake of a specified account in a court, typically to apply a delayed stake or unstake inactive jurors. /// @param _account The account whose stake is being set. /// @param _courtID The ID of the court. /// @param _newStake The new stake. - /// @param _alreadyTransferred Whether the PNKs have already been transferred to the contract. - function setStakeBySortitionModule( - address _account, - uint96 _courtID, - uint256 _newStake, - bool _alreadyTransferred - ) external { + function setStakeBySortitionModule(address _account, uint96 _courtID, uint256 _newStake) external { if (msg.sender != address(sortitionModule)) revert SortitionModuleOnly(); - _setStake(_account, _courtID, _newStake, _alreadyTransferred, OnError.Return); + _setStake(_account, _courtID, _newStake, OnError.Return); } /// @inheritdoc IArbitratorV2 @@ -598,7 +592,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { if (drawnAddress == address(0)) { revert NoJurorDrawn(); } - sortitionModule.lockStake(drawnAddress, round.pnkAtStakePerJuror); + sortitionModule.lockStake(drawnAddress, dispute.courtID, round.pnkAtStakePerJuror); emit Draw(drawnAddress, _disputeID, currentRound, round.drawnJurors.length); round.drawnJurors.push(drawnAddress); if (round.drawnJurors.length == round.nbVotes) { @@ -766,10 +760,10 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { // Unlock the PNKs affected by the penalty address account = round.drawnJurors[_params.repartition]; - sortitionModule.unlockStake(account, penalty); + sortitionModule.unlockStake(account, dispute.courtID, penalty); // Apply the penalty to the staked PNKs. - sortitionModule.penalizeStake(account, penalty); + sortitionModule.penalizeStake(account, dispute.courtID, penalty); emit TokenAndETHShift( account, _params.disputeID, @@ -830,7 +824,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { uint256 pnkLocked = (round.pnkAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR; // Release the rest of the PNKs of the juror for this round. - sortitionModule.unlockStake(account, pnkLocked); + sortitionModule.unlockStake(account, dispute.courtID, pnkLocked); // Give back the locked PNKs in case the juror fully unstaked earlier. if (!sortitionModule.isJurorStaked(account)) { @@ -1042,16 +1036,9 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { /// @param _account The account to set the stake for. /// @param _courtID The ID of the court to set the stake for. /// @param _newStake The new stake. - /// @param _alreadyTransferred Whether the PNKs were already transferred to/from the staking contract. /// @param _onError Whether to revert or return false on error. /// @return Whether the stake was successfully set or not. - function _setStake( - address _account, - uint96 _courtID, - uint256 _newStake, - bool _alreadyTransferred, - OnError _onError - ) internal returns (bool) { + function _setStake(address _account, uint96 _courtID, uint256 _newStake, OnError _onError) internal returns (bool) { if (_courtID == FORKING_COURT || _courtID > courts.length) { _stakingFailed(_onError, StakingResult.CannotStakeInThisCourt); // Staking directly into the forking court is not allowed. return false; @@ -1060,15 +1047,17 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { _stakingFailed(_onError, StakingResult.CannotStakeLessThanMinStake); // Staking less than the minimum stake is not allowed. return false; } - (uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult) = sortitionModule.setStake( - _account, - _courtID, - _newStake, - _alreadyTransferred - ); - if (stakingResult != StakingResult.Successful) { + ( + uint256 pnkDeposit, + uint256 pnkWithdrawal, + uint256 actualNewStake, + StakingResult stakingResult + ) = sortitionModule.validateStake(_account, _courtID, _newStake); + if (stakingResult != StakingResult.Successful && stakingResult != StakingResult.Delayed) { _stakingFailed(_onError, stakingResult); return false; + } else if (stakingResult == StakingResult.Delayed) { + return true; } if (pnkDeposit > 0) { if (!pinakion.safeTransferFrom(_account, address(this), pnkDeposit)) { @@ -1082,6 +1071,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { return false; } } + sortitionModule.setStake(_account, _courtID, actualNewStake); return true; } diff --git a/contracts/src/arbitration/university/SortitionModuleUniversity.sol b/contracts/src/arbitration/university/SortitionModuleUniversity.sol index b178c8b75..2a8c14f3b 100644 --- a/contracts/src/arbitration/university/SortitionModuleUniversity.sol +++ b/contracts/src/arbitration/university/SortitionModuleUniversity.sol @@ -31,6 +31,7 @@ contract SortitionModuleUniversity is ISortitionModuleUniversity, UUPSProxiable, 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`. uint256 stakedPnk; // The juror's total amount of tokens staked in subcourts. Reflects actual pnk balance. uint256 lockedPnk; // The juror's total amount of tokens locked in disputes. Can reflect actual pnk balance when stakedPnk are fully withdrawn. + mapping(uint96 courtID => uint256 stake) lockedInCourt; // The juror's tokens locked in particular courts. } // ************************************* // @@ -135,28 +136,30 @@ contract SortitionModuleUniversity is ISortitionModuleUniversity, UUPSProxiable, /// @param _account The address of the juror. /// @param _courtID The ID of the court. /// @param _newStake The new stake. - /// @param _alreadyTransferred True if the tokens were already transferred from juror. Only relevant for delayed stakes. /// @return pnkDeposit The amount of PNK to be deposited. /// @return pnkWithdrawal The amount of PNK to be withdrawn. + /// @return actualNewStake The actual new stake. /// @return stakingResult The result of the staking operation. - function setStake( + function validateStake( address _account, uint96 _courtID, - uint256 _newStake, - bool _alreadyTransferred - ) external override onlyByCore returns (uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult) { + uint256 _newStake + ) + external + override + onlyByCore + returns (uint256 pnkDeposit, uint256 pnkWithdrawal, uint256 actualNewStake, StakingResult stakingResult) + { Juror storage juror = jurors[_account]; uint256 currentStake = _stakeOf(_account, _courtID); uint256 nbCourts = juror.courtIDs.length; if (_newStake == 0 && (nbCourts >= MAX_STAKE_PATHS || currentStake == 0)) { - return (0, 0, StakingResult.CannotStakeInMoreCourts); // Prevent staking beyond MAX_STAKE_PATHS but unstaking is always allowed. + return (0, 0, currentStake, StakingResult.CannotStakeInMoreCourts); // Prevent staking beyond MAX_STAKE_PATHS but unstaking is always allowed. } if (_newStake >= currentStake) { - if (!_alreadyTransferred) { - pnkDeposit = _increaseStake(juror, _courtID, _newStake, currentStake); - } + pnkDeposit = _increaseStake(juror, _courtID, _newStake, currentStake); } else { pnkWithdrawal += _decreaseStake(juror, _courtID, _newStake, currentStake); } @@ -174,7 +177,11 @@ contract SortitionModuleUniversity is ISortitionModuleUniversity, UUPSProxiable, } } emit StakeSet(_account, _courtID, _newStake, juror.stakedPnk); - return (pnkDeposit, pnkWithdrawal, StakingResult.Successful); + return (pnkDeposit, pnkWithdrawal, _newStake, StakingResult.Successful); + } + + function setStake(address _account, uint96 _courtID, uint256 _newStake) external override onlyByCore { + // TODO: split validateStake } function _increaseStake( @@ -225,17 +232,23 @@ contract SortitionModuleUniversity is ISortitionModuleUniversity, UUPSProxiable, juror.stakedPnk = (juror.stakedPnk >= _currentStake) ? juror.stakedPnk - _currentStake + _newStake : _newStake; } - function lockStake(address _account, uint256 _relativeAmount) external override onlyByCore { + function lockStake(address _account, uint96 _courtID, uint256 _relativeAmount) external override onlyByCore { jurors[_account].lockedPnk += _relativeAmount; + jurors[_account].lockedInCourt[_courtID] += _relativeAmount; emit StakeLocked(_account, _relativeAmount, false); } - function unlockStake(address _account, uint256 _relativeAmount) external override onlyByCore { + function unlockStake(address _account, uint96 _courtID, uint256 _relativeAmount) external override onlyByCore { jurors[_account].lockedPnk -= _relativeAmount; + jurors[_account].lockedInCourt[_courtID] -= _relativeAmount; emit StakeLocked(_account, _relativeAmount, true); } - function penalizeStake(address _account, uint256 _relativeAmount) external override onlyByCore { + function penalizeStake( + address _account, + uint96 _courtID, + uint256 _relativeAmount + ) external override onlyByCore returns (uint256 pnkBalance, uint256 availablePenalty) { Juror storage juror = jurors[_account]; if (juror.stakedPnk >= _relativeAmount) { juror.stakedPnk -= _relativeAmount; @@ -254,7 +267,7 @@ contract SortitionModuleUniversity is ISortitionModuleUniversity, UUPSProxiable, function setJurorInactive(address _account) external override onlyByCore { uint96[] memory courtIDs = getJurorCourtIDs(_account); for (uint256 j = courtIDs.length; j > 0; j--) { - core.setStakeBySortitionModule(_account, courtIDs[j - 1], 0, false); + core.setStakeBySortitionModule(_account, courtIDs[j - 1], 0); } } diff --git a/contracts/src/libraries/Constants.sol b/contracts/src/libraries/Constants.sol index f393b4792..f2101461b 100644 --- a/contracts/src/libraries/Constants.sol +++ b/contracts/src/libraries/Constants.sol @@ -27,6 +27,7 @@ enum OnError { enum StakingResult { Successful, + Delayed, StakingTransferFailed, UnstakingTransferFailed, CannotStakeInMoreCourts, diff --git a/contracts/test/foundry/KlerosCore.t.sol b/contracts/test/foundry/KlerosCore.t.sol index 291b969d0..293f5739c 100644 --- a/contracts/test/foundry/KlerosCore.t.sol +++ b/contracts/test/foundry/KlerosCore.t.sol @@ -1010,7 +1010,7 @@ contract KlerosCoreTest is Test { vm.prank(staker1); vm.expectEmit(true, true, true, true); - emit SortitionModuleBase.StakeDelayedAlreadyTransferredDeposited(staker1, GENERAL_COURT, 1500); + emit SortitionModuleBase.StakeDelayed(staker1, GENERAL_COURT, 1500); core.setStake(GENERAL_COURT, 1500); uint256 delayedStakeId = sortitionModule.delayedStakeWriteIndex(); @@ -1022,11 +1022,11 @@ contract KlerosCoreTest is Test { assertEq(account, staker1, "Wrong staker account"); assertEq(courtID, GENERAL_COURT, "Wrong court id"); assertEq(stake, 1500, "Wrong amount staked in court"); - assertEq(alreadyTransferred, true, "Should be flagged as transferred"); + assertEq(alreadyTransferred, false, "Should be flagged as transferred"); (uint256 totalStaked, uint256 totalLocked, uint256 stakedInCourt, uint256 nbCourts) = sortitionModule .getJurorBalance(staker1, GENERAL_COURT); - assertEq(totalStaked, 1500, "Wrong amount total staked"); + assertEq(totalStaked, 1000, "Wrong amount total staked"); assertEq(totalLocked, 0, "Wrong amount locked"); assertEq(stakedInCourt, 1000, "Amount staked in court should not change until delayed stake is executed"); assertEq(nbCourts, 1, "Wrong number of courts"); @@ -1036,9 +1036,8 @@ contract KlerosCoreTest is Test { assertEq(courts[0], GENERAL_COURT, "Wrong court id"); assertEq(sortitionModule.isJurorStaked(staker1), true, "Juror should be staked"); - assertEq(pinakion.balanceOf(address(core)), 1500, "Wrong token balance of the core"); - assertEq(pinakion.balanceOf(staker1), 999999999999998500, "Wrong token balance of staker1"); - assertEq(pinakion.allowance(staker1, address(core)), 999999999999998500, "Wrong allowance amount"); + assertEq(pinakion.balanceOf(address(core)), 1000, "Wrong token balance of the core"); + assertEq(pinakion.balanceOf(staker1), 999999999999999000, "Wrong token balance of staker1"); } function test_setStake_decreaseDrawingPhase() public { @@ -1057,7 +1056,7 @@ contract KlerosCoreTest is Test { vm.prank(staker1); vm.expectEmit(true, true, true, true); - emit SortitionModuleBase.StakeDelayedNotTransferred(staker1, GENERAL_COURT, 1800); + emit SortitionModuleBase.StakeDelayed(staker1, GENERAL_COURT, 1800); core.setStake(GENERAL_COURT, 1800); (uint256 totalStaked, , uint256 stakedInCourt, ) = sortitionModule.getJurorBalance(staker1, GENERAL_COURT); @@ -1092,31 +1091,30 @@ contract KlerosCoreTest is Test { assertEq(pinakion.balanceOf(address(core)), 10000, "Wrong token balance of the core"); assertEq(pinakion.balanceOf(staker1), 999999999999990000, "Wrong token balance of staker1"); - // Unstake to check that locked tokens will remain + // Unstake to check that locked tokens won't be withdrawn vm.prank(staker1); core.setStake(GENERAL_COURT, 0); (totalStaked, totalLocked, stakedInCourt, nbCourts) = sortitionModule.getJurorBalance(staker1, GENERAL_COURT); - assertEq(totalStaked, 0, "Wrong amount total staked"); + assertEq(totalStaked, 3000, "Wrong amount total staked"); assertEq(totalLocked, 3000, "Wrong amount locked"); - assertEq(stakedInCourt, 0, "Wrong amount staked in court"); - assertEq(nbCourts, 0, "Wrong amount staked in court"); + assertEq(stakedInCourt, 3000, "Wrong amount staked in court"); + assertEq(nbCourts, 1, "Wrong amount staked in court"); assertEq(pinakion.balanceOf(address(core)), 3000, "Wrong token balance of the core"); assertEq(pinakion.balanceOf(staker1), 999999999999997000, "Wrong token balance of staker1"); - // Stake again to see that locked tokens will count when increasing the stake. We check that the court won't take the full stake - // but only the remaining part. + // Stake again to check the behaviour. vm.prank(staker1); core.setStake(GENERAL_COURT, 5000); (totalStaked, totalLocked, stakedInCourt, nbCourts) = sortitionModule.getJurorBalance(staker1, GENERAL_COURT); - assertEq(totalStaked, 5000, "Wrong amount total staked"); + assertEq(totalStaked, 5000, "Wrong amount total staked"); // 5000 were added to the previous 3000. assertEq(totalLocked, 3000, "Wrong amount locked"); assertEq(stakedInCourt, 5000, "Wrong amount staked in court"); assertEq(nbCourts, 1, "Wrong amount staked in court"); - assertEq(pinakion.balanceOf(address(core)), 5000, "Locked tokens should stay in the core"); + assertEq(pinakion.balanceOf(address(core)), 5000, "Wrong amount of tokens in Core"); assertEq(pinakion.balanceOf(staker1), 999999999999995000, "Wrong token balance of staker1"); } @@ -1141,18 +1139,26 @@ contract KlerosCoreTest is Test { vm.expectRevert(bytes("Should be in Staking phase.")); sortitionModule.executeDelayedStakes(5); + // Create delayed stake vm.prank(staker1); + vm.expectEmit(true, true, true, true); + emit SortitionModuleBase.StakeDelayed(staker1, GENERAL_COURT, 1500); core.setStake(GENERAL_COURT, 1500); - assertEq(pinakion.balanceOf(address(core)), 11500, "Wrong token balance of the core"); - assertEq(pinakion.balanceOf(staker1), 999999999999998500, "Wrong token balance of staker1"); + assertEq(pinakion.balanceOf(address(core)), 10000, "Wrong token balance of the core"); // Balance should not increase because the stake was delayed + assertEq(pinakion.balanceOf(staker1), 1 ether, "Wrong token balance of staker1"); + + // Create delayed stake for another staker vm.prank(staker2); + vm.expectEmit(true, true, true, true); + emit SortitionModuleBase.StakeDelayed(staker2, GENERAL_COURT, 0); core.setStake(GENERAL_COURT, 0); assertEq(pinakion.balanceOf(staker2), 999999999999990000, "Wrong token balance of staker2"); // Balance should not change since wrong phase + // Create another delayed stake for staker1 on top of it to check the execution vm.prank(staker1); vm.expectEmit(true, true, true, true); - emit SortitionModuleBase.StakeDelayedAlreadyTransferredWithdrawn(staker1, GENERAL_COURT, 1500); + emit SortitionModuleBase.StakeDelayed(staker1, GENERAL_COURT, 1800); core.setStake(GENERAL_COURT, 1800); assertEq(sortitionModule.delayedStakeWriteIndex(), 3, "Wrong delayedStakeWriteIndex"); @@ -1160,42 +1166,47 @@ contract KlerosCoreTest is Test { (address account, uint96 courtID, uint256 stake, bool alreadyTransferred) = sortitionModule.delayedStakes(1); - // First delayed stake should be nullified - assertEq(account, address(0), "Wrong staker account after delayed stake deletion"); - assertEq(courtID, 0, "Court id should be nullified"); - assertEq(stake, 0, "No amount to stake"); + // Check each delayed stake + assertEq(account, staker1, "Wrong staker account for the first delayed stake"); + assertEq(courtID, GENERAL_COURT, "Wrong court ID"); + assertEq(stake, 1500, "Wrong staking amount"); assertEq(alreadyTransferred, false, "Should be false"); (account, courtID, stake, alreadyTransferred) = sortitionModule.delayedStakes(2); assertEq(account, staker2, "Wrong staker2 account"); assertEq(courtID, GENERAL_COURT, "Wrong court id for staker2"); assertEq(stake, 0, "Wrong amount for delayed stake of staker2"); - assertEq(alreadyTransferred, false, "Should be false for staker2"); + assertEq(alreadyTransferred, false, "Should be false"); (account, courtID, stake, alreadyTransferred) = sortitionModule.delayedStakes(3); assertEq(account, staker1, "Wrong staker1 account"); assertEq(courtID, GENERAL_COURT, "Wrong court id for staker1"); assertEq(stake, 1800, "Wrong amount for delayed stake of staker1"); - assertEq(alreadyTransferred, true, "Should be true for staker1"); + assertEq(alreadyTransferred, false, "Should be false"); - assertEq(pinakion.balanceOf(address(core)), 11800, "Wrong token balance of the core"); - assertEq(pinakion.balanceOf(staker1), 999999999999998200, "Wrong token balance of staker1"); + // So far the only amount transferred was 10000 by staker2. Staker 1 has two delayed stakes, for 1500 and 1800 pnk. + assertEq(pinakion.balanceOf(address(core)), 10000, "Wrong token balance of the core"); + assertEq(pinakion.balanceOf(staker1), 1 ether, "Wrong token balance of staker1"); assertEq(pinakion.balanceOf(staker2), 999999999999990000, "Wrong token balance of staker2"); (uint256 totalStaked, uint256 totalLocked, uint256 stakedInCourt, uint256 nbCourts) = sortitionModule - .getJurorBalance(staker1, GENERAL_COURT); // Only check the first staker since he has consecutive delayed stakes - assertEq(totalStaked, 1800, "Wrong amount total staked"); + .getJurorBalance(staker1, GENERAL_COURT); // Only check the first staker to check how consecutive delayed stakes are handled. + // Balances shouldn't be updated yet. + assertEq(totalStaked, 0, "Wrong amount total staked"); assertEq(totalLocked, 0, "Wrong amount locked"); assertEq(stakedInCourt, 0, "Wrong amount staked in court"); - assertEq(nbCourts, 1, "Wrong amount staked in court"); + assertEq(nbCourts, 0, "Wrong number of courts"); vm.warp(block.timestamp + minStakingTime); sortitionModule.passPhase(); // Staking. Delayed stakes can be executed now vm.prank(address(core)); pinakion.transfer(governor, 10000); // Dispose of the tokens of 2nd staker to make the execution fail for the 2nd delayed stake - assertEq(pinakion.balanceOf(address(core)), 1800, "Wrong token balance of the core"); + assertEq(pinakion.balanceOf(address(core)), 0, "Wrong token balance of the core"); + // 2 events should be emitted but the 2nd stake supersedes the first one in the end. + vm.expectEmit(true, true, true, true); + emit SortitionModuleBase.StakeSet(staker1, GENERAL_COURT, 1500, 1500); vm.expectEmit(true, true, true, true); emit SortitionModuleBase.StakeSet(staker1, GENERAL_COURT, 1800, 1800); sortitionModule.executeDelayedStakes(20); // Deliberately ask for more iterations than needed @@ -1225,59 +1236,11 @@ contract KlerosCoreTest is Test { assertEq(pinakion.balanceOf(staker2), 999999999999990000, "Wrong token balance of staker2"); } - function test_deleteDelayedStake() public { - // Check that the delayed stake gets deleted without execution if the juror changed his stake in staking phase before its execution. - vm.prank(staker1); - core.setStake(GENERAL_COURT, 1000); - - vm.prank(disputer); - arbitrable.createDispute{value: feeForJuror * DEFAULT_NB_OF_JURORS}("Action"); - vm.warp(block.timestamp + minStakingTime); - sortitionModule.passPhase(); // Generating - vm.roll(block.number + rngLookahead + 1); - sortitionModule.passPhase(); // Drawing phase - - vm.prank(staker1); - core.setStake(GENERAL_COURT, 1500); // Create delayed stake - - (uint256 totalStaked, , uint256 stakedInCourt, ) = sortitionModule.getJurorBalance(staker1, GENERAL_COURT); - assertEq(totalStaked, 1500, "Wrong amount total staked"); - assertEq(stakedInCourt, 1000, "Wrong amount staked in court"); - assertEq(pinakion.balanceOf(staker1), 999999999999998500, "Wrong token balance of the staker1"); - assertEq(pinakion.balanceOf(address(core)), 1500, "Wrong token balance of the core"); - - (address account, uint96 courtID, uint256 stake, bool alreadyTransferred) = sortitionModule.delayedStakes(1); - assertEq(account, staker1, "Wrong account"); - assertEq(courtID, GENERAL_COURT, "Wrong court id"); - assertEq(stake, 1500, "Wrong amount for delayed stake"); - assertEq(alreadyTransferred, true, "Should be true"); - - vm.warp(block.timestamp + maxDrawingTime); - sortitionModule.passPhase(); // Staking phase - - vm.prank(staker1); - core.setStake(GENERAL_COURT, 1700); // Set stake 2nd time, this time in staking phase to see that the delayed stake will be nullified. - - (totalStaked, , stakedInCourt, ) = sortitionModule.getJurorBalance(staker1, GENERAL_COURT); - assertEq(totalStaked, 1700, "Wrong amount total staked"); - assertEq(stakedInCourt, 1700, "Wrong amount staked in court"); - assertEq(pinakion.balanceOf(staker1), 999999999999998300, "Wrong token balance of the staker1"); - assertEq(pinakion.balanceOf(address(core)), 1700, "Wrong token balance of the core"); - - sortitionModule.executeDelayedStakes(1); - (account, courtID, stake, alreadyTransferred) = sortitionModule.delayedStakes(1); - // Check that delayed stake is deleted - assertEq(account, address(0), "Wrong staker account after delayed stake deletion"); - assertEq(courtID, 0, "Court id should be nullified"); - assertEq(stake, 0, "No amount to stake"); - assertEq(alreadyTransferred, false, "Should be false"); - } - function test_setStakeBySortitionModule() public { // Note that functionality of this function was checked during delayed stakes execution vm.expectRevert(KlerosCoreBase.SortitionModuleOnly.selector); vm.prank(governor); - core.setStakeBySortitionModule(staker1, GENERAL_COURT, 1000, false); + core.setStakeBySortitionModule(staker1, GENERAL_COURT, 1000); } function test_setStake_snapshotProxyCheck() public { @@ -1463,43 +1426,17 @@ contract KlerosCoreTest is Test { vm.expectEmit(true, true, true, true); emit KlerosCoreBase.Draw(staker1, disputeID, roundID, 0); // VoteID = 0 - core.draw(disputeID, DEFAULT_NB_OF_JURORS); // Do 3 iterations, but the current stake will only allow 1. + core.draw(disputeID, DEFAULT_NB_OF_JURORS); // Do 3 iterations and see that the juror will get drawn 3 times despite low stake. (uint256 totalStaked, uint256 totalLocked, uint256 stakedInCourt, ) = sortitionModule.getJurorBalance( staker1, GENERAL_COURT ); assertEq(totalStaked, 1500, "Wrong amount total staked"); - assertEq(totalLocked, 1000, "Wrong amount locked"); // 1000 per draw - assertEq(stakedInCourt, 1500, "Wrong amount staked in court"); - assertEq(sortitionModule.disputesWithoutJurors(), 1, "Wrong disputesWithoutJurors count"); - - KlerosCoreBase.Round memory round = core.getRoundInfo(disputeID, 0); - assertEq(round.drawIterations, 3, "Wrong drawIterations number"); - - vm.prank(staker1); - core.setStake(GENERAL_COURT, 3000); // Set stake to the minimal amount to cover the full dispute. The stake will be updated in Drawing phase since it's an increase. - - vm.expectEmit(true, true, true, true); - emit SortitionModuleBase.StakeLocked(staker1, 1000, false); - vm.expectEmit(true, true, true, true); - emit KlerosCoreBase.Draw(staker1, disputeID, roundID, 1); // VoteID = 1 - vm.expectEmit(true, true, true, true); - emit SortitionModuleBase.StakeLocked(staker1, 1000, false); - vm.expectEmit(true, true, true, true); - emit KlerosCoreBase.Draw(staker1, disputeID, roundID, 2); // VoteID = 2 - - core.draw(disputeID, DEFAULT_NB_OF_JURORS); - - (totalStaked, totalLocked, stakedInCourt, ) = sortitionModule.getJurorBalance(staker1, GENERAL_COURT); - assertEq(totalStaked, 3000, "Wrong amount total staked"); - assertEq(totalLocked, 3000, "Wrong amount locked"); // 1000 per draw and the juror was drawn 3 times + assertEq(totalLocked, 3000, "Wrong amount locked"); // 1000 per draw assertEq(stakedInCourt, 1500, "Wrong amount staked in court"); assertEq(sortitionModule.disputesWithoutJurors(), 0, "Wrong disputesWithoutJurors count"); - round = core.getRoundInfo(disputeID, roundID); - assertEq(round.drawIterations, 5, "Wrong drawIterations number"); // It's 5 because we needed only 2 iterations to draw the rest of the jurors - for (uint256 i = 0; i < DEFAULT_NB_OF_JURORS; i++) { (address account, bytes32 commit, uint256 choice, bool voted) = disputeKit.getVoteInfo(0, 0, i); assertEq(account, staker1, "Wrong drawn account"); @@ -2471,9 +2408,13 @@ contract KlerosCoreTest is Test { core.setStake(GENERAL_COURT, 20000); vm.prank(staker1); core.setStake(newCourtID, 20000); - (, , , uint256 nbCourts) = sortitionModule.getJurorBalance(staker1, GENERAL_COURT); + (uint256 totalStaked, uint256 totalLocked, uint256 stakedInCourt, uint256 nbCourts) = sortitionModule + .getJurorBalance(staker1, GENERAL_COURT); assertEq(nbCourts, 2, "Wrong number of courts"); + assertEq(pinakion.balanceOf(address(core)), 40000, "Wrong token balance of the core"); + assertEq(pinakion.balanceOf(staker1), 999999999999960000, "Wrong token balance of staker1"); + vm.prank(disputer); arbitrable.createDispute{value: feeForJuror * DEFAULT_NB_OF_JURORS}("Action"); vm.warp(block.timestamp + minStakingTime); @@ -2496,26 +2437,89 @@ contract KlerosCoreTest is Test { uint256 governorTokenBalance = pinakion.balanceOf(governor); + // Note that these events are emitted only after the first iteration of execute() therefore the juror has been penalized only for 1000 PNK her. + vm.expectEmit(true, true, true, true); + emit SortitionModuleBase.StakeSet(staker1, newCourtID, 0, 19000); // Starting with 40000 we first nullify the stake and remove 20000 and then remove penalty once since there was only first iteration (40000 - 20000 - 1000) vm.expectEmit(true, true, true, true); - emit SortitionModuleBase.StakeSet(staker1, newCourtID, 0, 19000); + emit SortitionModuleBase.StakeSet(staker1, GENERAL_COURT, 2000, 2000); // 2000 PNK should remain in balance to cover penalties since the first 1000 of locked pnk was already unlocked + vm.expectEmit(true, true, true, true); + emit SortitionModuleBase.StakeSet(staker1, GENERAL_COURT, 1000, 1000); vm.expectEmit(true, true, true, true); emit SortitionModuleBase.StakeSet(staker1, GENERAL_COURT, 0, 0); core.execute(disputeID, 0, 3); assertEq(pinakion.balanceOf(address(core)), 0, "Wrong token balance of the core"); - assertEq(pinakion.balanceOf(staker1), 999999999999997000, "Wrong token balance of staker1"); + assertEq(pinakion.balanceOf(staker1), 999999999999997000, "Wrong token balance of staker1"); // 3000 locked PNK was withheld by the contract and given to governor. assertEq(pinakion.balanceOf(governor), governorTokenBalance + 3000, "Wrong token balance of governor"); (, , , nbCourts) = sortitionModule.getJurorBalance(staker1, GENERAL_COURT); assertEq(nbCourts, 0, "Should unstake from all courts"); } - function test_execute_RewardUnstaked() public { - // Reward the juror who fully unstaked earlier. Return the locked tokens + function test_execute_UnstakeInsolvent() public { uint256 disputeID = 0; vm.prank(staker1); - core.setStake(GENERAL_COURT, 20000); + core.setStake(GENERAL_COURT, 1000); + + assertEq(pinakion.balanceOf(address(core)), 1000, "Wrong token balance of the core"); + assertEq(pinakion.balanceOf(staker1), 999999999999999000, "Wrong token balance of staker1"); + + vm.prank(disputer); + arbitrable.createDispute{value: feeForJuror * DEFAULT_NB_OF_JURORS}("Action"); + vm.warp(block.timestamp + minStakingTime); + sortitionModule.passPhase(); // Generating + vm.roll(block.number + rngLookahead + 1); + sortitionModule.passPhase(); // Drawing phase + + core.draw(disputeID, DEFAULT_NB_OF_JURORS); + + (uint256 totalStaked, uint256 totalLocked, , uint256 nbCourts) = sortitionModule.getJurorBalance( + staker1, + GENERAL_COURT + ); + assertEq(totalStaked, 1000, "Wrong totalStaked"); + assertEq(totalLocked, 3000, "totalLocked should exceed totalStaked"); // Juror only staked 1000 but was drawn 3x of minStake (3000 locked) + assertEq(nbCourts, 1, "Wrong number of courts"); + + sortitionModule.passPhase(); // Staking phase. Change to staking so we don't have to deal with delayed stakes. + + vm.warp(block.timestamp + timesPerPeriod[0]); + core.passPeriod(disputeID); // Vote + + uint256[] memory voteIDs = new uint256[](1); + voteIDs[0] = 0; + vm.prank(staker1); + disputeKit.castVote(disputeID, voteIDs, 1, 0, "XYZ"); // 1 incoherent vote should make the juror insolvent + + voteIDs = new uint256[](2); + voteIDs[0] = 1; + voteIDs[1] = 2; + vm.prank(staker1); + disputeKit.castVote(disputeID, voteIDs, 2, 0, "XYZ"); + + core.passPeriod(disputeID); // Appeal + + vm.warp(block.timestamp + timesPerPeriod[3]); + core.passPeriod(disputeID); // Execution + + vm.expectEmit(true, true, true, true); + emit SortitionModuleBase.StakeSet(staker1, GENERAL_COURT, 0, 0); // Juror should have no stake left and should be unstaked from the court automatically. + core.execute(disputeID, 0, 6); + + assertEq(pinakion.balanceOf(address(core)), 0, "Wrong token balance of the core"); + assertEq(pinakion.balanceOf(staker1), 1 ether, "Wrong token balance of staker1"); // The juror should have his penalty back as a reward + + (, , , nbCourts) = sortitionModule.getJurorBalance(staker1, GENERAL_COURT); + assertEq(nbCourts, 0, "Should unstake from all courts"); + } + + function test_execute_withdrawLeftoverPNK() public { + // Return the previously locked tokens + uint256 disputeID = 0; + + vm.prank(staker1); + core.setStake(GENERAL_COURT, 1000); vm.prank(disputer); arbitrable.createDispute{value: feeForJuror * DEFAULT_NB_OF_JURORS}("Action"); vm.warp(block.timestamp + minStakingTime); @@ -2543,27 +2547,28 @@ contract KlerosCoreTest is Test { core.passPeriod(disputeID); // Execution vm.prank(staker1); - core.setStake(GENERAL_COURT, 0); + core.setStake(GENERAL_COURT, 0); // Set stake to 0 to check if it will be withdrawn later. (uint256 totalStaked, uint256 totalLocked, uint256 stakedInCourt, uint256 nbCourts) = sortitionModule .getJurorBalance(staker1, GENERAL_COURT); - assertEq(totalStaked, 0, "Should be unstaked"); + assertEq(totalStaked, 1000, "Wrong amount staked"); assertEq(totalLocked, 3000, "Wrong amount locked"); - assertEq(stakedInCourt, 0, "Should be unstaked"); - assertEq(nbCourts, 0, "Should be 0 courts"); + assertEq(stakedInCourt, 1000, "Should be unstaked"); + assertEq(nbCourts, 1, "Should be 1 courts"); - assertEq(pinakion.balanceOf(address(core)), 3000, "Wrong token balance of the core"); - assertEq(pinakion.balanceOf(staker1), 999999999999997000, "Wrong token balance of staker1"); + assertEq(pinakion.balanceOf(address(core)), 1000, "Wrong token balance of the core"); + assertEq(pinakion.balanceOf(staker1), 999999999999999000, "Wrong token balance of staker1"); core.execute(disputeID, 0, 6); - KlerosCoreBase.Round memory round = core.getRoundInfo(disputeID, 0); - assertEq(round.pnkPenalties, 0, "Wrong pnkPenalties"); - assertEq(round.sumFeeRewardPaid, 0.09 ether, "Wrong sumFeeRewardPaid"); - assertEq(round.sumPnkRewardPaid, 0, "Wrong sumPnkRewardPaid"); // No penalty so no rewards in pnk + (totalStaked, totalLocked, stakedInCourt, nbCourts) = sortitionModule.getJurorBalance(staker1, GENERAL_COURT); + assertEq(totalStaked, 1000, "Wrong amount staked"); + assertEq(totalLocked, 0, "Should be fully unlocked"); + assertEq(stakedInCourt, 1000, "Should be unstaked"); + assertEq(nbCourts, 1, "Should be 1 courts"); - assertEq(pinakion.balanceOf(address(core)), 0, "Wrong token balance of the core"); - assertEq(pinakion.balanceOf(staker1), 1 ether, "Wrong token balance of staker1"); + assertEq(pinakion.balanceOf(address(core)), 1000, "Wrong token balance of the core"); + assertEq(pinakion.balanceOf(staker1), 999999999999999000, "Wrong token balance of staker1"); } function test_execute_feeToken() public { diff --git a/subgraph/core-neo/abi-migrations/SortitionModuleNeo.json b/subgraph/core-neo/abi-migrations/SortitionModuleNeo.json index b65294575..f7a756723 100644 --- a/subgraph/core-neo/abi-migrations/SortitionModuleNeo.json +++ b/subgraph/core-neo/abi-migrations/SortitionModuleNeo.json @@ -60,34 +60,15 @@ "name": "Initialized", "type": "event" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "enum ISortitionModule.Phase", - "name": "_phase", - "type": "uint8" - } - ], - "name": "NewPhase", - "type": "event" - }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", - "name": "_address", + "name": "_account", "type": "address" }, - { - "indexed": false, - "internalType": "uint256", - "name": "_courtID", - "type": "uint256" - }, { "indexed": false, "internalType": "uint256", @@ -95,32 +76,20 @@ "type": "uint256" } ], - "name": "StakeDelayedAlreadyTransferredDeposited", + "name": "LeftoverPNKWithdrawn", "type": "event" }, { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_address", - "type": "address" - }, { "indexed": false, - "internalType": "uint256", - "name": "_courtID", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_amount", - "type": "uint256" + "internalType": "enum ISortitionModule.Phase", + "name": "_phase", + "type": "uint8" } ], - "name": "StakeDelayedAlreadyTransferred", + "name": "NewPhase", "type": "event" }, { @@ -145,32 +114,7 @@ "type": "uint256" } ], - "name": "StakeDelayedAlreadyTransferredWithdrawn", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_address", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_courtID", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "StakeDelayedNotTransferred", + "name": "StakeDelayed", "type": "event" }, { @@ -449,11 +393,6 @@ "internalType": "uint256", "name": "stake", "type": "uint256" - }, - { - "internalType": "bool", - "name": "alreadyTransferred", - "type": "bool" } ], "stateMutability": "view", @@ -696,30 +635,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "jurorAccount", - "type": "address" - }, - { - "internalType": "uint96", - "name": "courtId", - "type": "uint96" - } - ], - "name": "latestDelayedStakeIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -824,7 +739,18 @@ } ], "name": "penalizeStake", - "outputs": [], + "outputs": [ + { + "internalType": "uint256", + "name": "pnkBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "availablePenalty", + "type": "uint256" + } + ], "stateMutability": "nonpayable", "type": "function" }, @@ -956,7 +882,7 @@ }, { "internalType": "bool", - "name": "_alreadyTransferred", + "name": "", "type": "bool" } ], @@ -972,6 +898,11 @@ "name": "pnkWithdrawal", "type": "uint256" }, + { + "internalType": "uint256", + "name": "oldStake", + "type": "uint256" + }, { "internalType": "enum StakingResult", "name": "stakingResult", @@ -1090,6 +1021,19 @@ ], "stateMutability": "view", "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + } + ], + "name": "withdrawLeftoverPNK", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" } ] } diff --git a/subgraph/core-neo/subgraph.yaml b/subgraph/core-neo/subgraph.yaml index 69e4b7d01..10a37fb5b 100644 --- a/subgraph/core-neo/subgraph.yaml +++ b/subgraph/core-neo/subgraph.yaml @@ -158,15 +158,8 @@ dataSources: # FIX: temporarily point to abi with event addition file: ./abi-migrations/SortitionModuleNeo.json eventHandlers: - - event: StakeDelayedAlreadyTransferredDeposited(indexed address,uint256,uint256) - handler: handleStakeDelayedAlreadyTransferredDeposited - # FIX: temporarily indexing old event name - - event: StakeDelayedAlreadyTransferred(indexed address,uint256,uint256) - handler: handleStakeDelayedAlreadyTransferred - - event: StakeDelayedAlreadyTransferredWithdrawn(indexed address,indexed uint96,uint256) - handler: handleStakeDelayedAlreadyTransferredWithdrawn - - event: StakeDelayedNotTransferred(indexed address,uint256,uint256) - handler: handleStakeDelayedNotTransferred + - event: StakeDelayed(indexed address,indexed uint96,uint256) + handler: handleStakeDelayed - event: StakeLocked(indexed address,uint256,bool) handler: handleStakeLocked - event: StakeSet(indexed address,uint256,uint256,uint256) diff --git a/subgraph/core/abi-migrations/SortitionModule.json b/subgraph/core/abi-migrations/SortitionModule.json index d9ba2ca9e..859f4ae9f 100644 --- a/subgraph/core/abi-migrations/SortitionModule.json +++ b/subgraph/core/abi-migrations/SortitionModule.json @@ -1,12 +1,9 @@ { "abi": [ { - "stateMutability": "payable", - "type": "fallback" - }, - { - "stateMutability": "payable", - "type": "receive" + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" }, { "inputs": [], @@ -63,34 +60,15 @@ "name": "Initialized", "type": "event" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "enum ISortitionModule.Phase", - "name": "_phase", - "type": "uint8" - } - ], - "name": "NewPhase", - "type": "event" - }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", - "name": "_address", + "name": "_account", "type": "address" }, - { - "indexed": false, - "internalType": "uint256", - "name": "_courtID", - "type": "uint256" - }, { "indexed": false, "internalType": "uint256", @@ -98,32 +76,20 @@ "type": "uint256" } ], - "name": "StakeDelayedAlreadyTransferredDeposited", + "name": "LeftoverPNKWithdrawn", "type": "event" }, { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_address", - "type": "address" - }, { "indexed": false, - "internalType": "uint256", - "name": "_courtID", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_amount", - "type": "uint256" + "internalType": "enum ISortitionModule.Phase", + "name": "_phase", + "type": "uint8" } ], - "name": "StakeDelayedAlreadyTransferred", + "name": "NewPhase", "type": "event" }, { @@ -148,32 +114,7 @@ "type": "uint256" } ], - "name": "StakeDelayedAlreadyTransferredWithdrawn", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_address", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_courtID", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "StakeDelayedNotTransferred", + "name": "StakeDelayed", "type": "event" }, { @@ -426,11 +367,6 @@ "internalType": "uint256", "name": "stake", "type": "uint256" - }, - { - "internalType": "bool", - "name": "alreadyTransferred", - "type": "bool" } ], "stateMutability": "view", @@ -663,30 +599,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "jurorAccount", - "type": "address" - }, - { - "internalType": "uint96", - "name": "courtId", - "type": "uint96" - } - ], - "name": "latestDelayedStakeIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -765,7 +677,18 @@ } ], "name": "penalizeStake", - "outputs": [], + "outputs": [ + { + "internalType": "uint256", + "name": "pnkBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "availablePenalty", + "type": "uint256" + } + ], "stateMutability": "nonpayable", "type": "function" }, @@ -897,7 +820,7 @@ }, { "internalType": "bool", - "name": "_alreadyTransferred", + "name": "", "type": "bool" } ], @@ -913,6 +836,11 @@ "name": "pnkWithdrawal", "type": "uint256" }, + { + "internalType": "uint256", + "name": "oldStake", + "type": "uint256" + }, { "internalType": "enum StakingResult", "name": "stakingResult", @@ -1023,17 +951,14 @@ "inputs": [ { "internalType": "address", - "name": "_implementation", + "name": "_account", "type": "address" - }, - { - "internalType": "bytes", - "name": "_data", - "type": "bytes" } ], + "name": "withdrawLeftoverPNK", + "outputs": [], "stateMutability": "nonpayable", - "type": "constructor" + "type": "function" } ] } diff --git a/subgraph/core/src/SortitionModule.ts b/subgraph/core/src/SortitionModule.ts index 4d4f8c895..672a994ba 100644 --- a/subgraph/core/src/SortitionModule.ts +++ b/subgraph/core/src/SortitionModule.ts @@ -1,31 +1,10 @@ -import { - SortitionModule, - StakeDelayedAlreadyTransferred, - StakeDelayedAlreadyTransferredDeposited, - StakeDelayedAlreadyTransferredWithdrawn, - StakeDelayedNotTransferred, - StakeLocked, - StakeSet, -} from "../generated/SortitionModule/SortitionModule"; +import { SortitionModule, StakeDelayed, StakeLocked, StakeSet } from "../generated/SortitionModule/SortitionModule"; import { updateJurorDelayedStake, updateJurorStake } from "./entities/JurorTokensPerCourt"; import { ensureUser } from "./entities/User"; import { ZERO } from "./utils"; -// FIX: temporarily adding this handler for old event name "StakeDelayedAlreadyTransferred", delete when deploying new fresh-address contract. -export function handleStakeDelayedAlreadyTransferred(event: StakeDelayedAlreadyTransferred): void { - updateJurorDelayedStake(event.params._address.toHexString(), event.params._courtID.toString(), event.params._amount); -} - -export function handleStakeDelayedAlreadyTransferredDeposited(event: StakeDelayedAlreadyTransferredDeposited): void { - updateJurorDelayedStake(event.params._address.toHexString(), event.params._courtID.toString(), event.params._amount); -} - -export function handleStakeDelayedAlreadyTransferredWithdrawn(event: StakeDelayedAlreadyTransferredWithdrawn): void { - updateJurorDelayedStake(event.params._address.toHexString(), event.params._courtID.toString(), event.params._amount); -} - -export function handleStakeDelayedNotTransferred(event: StakeDelayedNotTransferred): void { +export function handleStakeDelayed(event: StakeDelayed): void { updateJurorDelayedStake(event.params._address.toHexString(), event.params._courtID.toString(), event.params._amount); } diff --git a/subgraph/core/subgraph.yaml b/subgraph/core/subgraph.yaml index d3aff9873..e8c0cd3f6 100644 --- a/subgraph/core/subgraph.yaml +++ b/subgraph/core/subgraph.yaml @@ -159,15 +159,8 @@ dataSources: # FIX: temporarily point to abi with event addition file: ./abi-migrations/SortitionModule.json eventHandlers: - - event: StakeDelayedAlreadyTransferredDeposited(indexed address,uint256,uint256) - handler: handleStakeDelayedAlreadyTransferredDeposited - # FIX: temporarily indexing old event name - - event: StakeDelayedAlreadyTransferred(indexed address,uint256,uint256) - handler: handleStakeDelayedAlreadyTransferred - - event: StakeDelayedAlreadyTransferredWithdrawn(indexed address,indexed uint96,uint256) - handler: handleStakeDelayedAlreadyTransferredWithdrawn - - event: StakeDelayedNotTransferred(indexed address,uint256,uint256) - handler: handleStakeDelayedNotTransferred + - event: StakeDelayed(indexed address,indexed uint96,uint256) + handler: handleStakeDelayed - event: StakeLocked(indexed address,uint256,bool) handler: handleStakeLocked - event: StakeSet(indexed address,uint256,uint256,uint256) diff --git a/subgraph/temp-older-events-addition.txt b/subgraph/temp-older-events-addition.txt index abf0ca62f..f049dcc70 100644 --- a/subgraph/temp-older-events-addition.txt +++ b/subgraph/temp-older-events-addition.txt @@ -92,7 +92,7 @@ -// Goes in SortitionModule.json: rename of StakeDelayedAlreadyTransferred => StakeDelayedAlreadyTransferredDeposited +// NOT NEEDED ANYMORE: Goes in SortitionModule.json: rename of StakeDelayedAlreadyTransferred => StakeDelayedAlreadyTransferredDeposited { "anonymous": false, "inputs": [