diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 19e8e1eab..f6068fc6e 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -36,13 +36,17 @@ interface IERC721 { * until transfers become available in Ethereum 2.0. * Whitepaper: https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf * -* NOTE: the code below assumes moderate amount of node operators, e.g. up to 50. +* NOTE: the code below assumes moderate amount of node operators, e.g. up to 200. * * Since balances of all token holders change when the amount of total pooled Ether * changes, this token cannot fully implement ERC20 standard: it only emits `Transfer` * events upon explicit transfer between holders. In contrast, when Lido oracle reports * rewards, no Transfer events are generated: doing so would require emitting an event * for each token holder and thus running an unbounded loop. +* +* At the moment withdrawals are not possible in the beacon chain and there's no workaround. +* Pool will be upgraded to an actual implementation when withdrawals are enabled +* (Phase 1.5 or 2 of Eth2 launch, likely late 2022 or 2023). */ contract Lido is ILido, StETH, AragonApp { using SafeMath for uint256; @@ -54,7 +58,7 @@ contract Lido is ILido, StETH, AragonApp { bytes32 constant public PAUSE_ROLE = keccak256("PAUSE_ROLE"); bytes32 constant public RESUME_ROLE = keccak256("RESUME_ROLE"); bytes32 constant public STAKING_PAUSE_ROLE = keccak256("STAKING_PAUSE_ROLE"); - bytes32 constant public STAKING_RESUME_ROLE = keccak256("STAKING_RESUME_ROLE"); + bytes32 constant public STAKING_CONTROL_ROLE = keccak256("STAKING_CONTROL_ROLE"); bytes32 constant public MANAGE_FEE = keccak256("MANAGE_FEE"); bytes32 constant public MANAGE_WITHDRAWAL_KEY = keccak256("MANAGE_WITHDRAWAL_KEY"); bytes32 constant public MANAGE_PROTOCOL_CONTRACTS_ROLE = keccak256("MANAGE_PROTOCOL_CONTRACTS_ROLE"); @@ -101,7 +105,7 @@ contract Lido is ILido, StETH, AragonApp { bytes32 internal constant BEACON_VALIDATORS_POSITION = keccak256("lido.Lido.beaconValidators"); /// @dev percent in basis points of total pooled ether allowed to withdraw from LidoExecutionLayerRewardsVault per LidoOracle report - bytes32 internal constant EL_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION = keccak256("lido.Lido.ELRewardsWithdrawalLimitPoints"); + bytes32 internal constant EL_REWARDS_WITHDRAWAL_LIMIT_POSITION = keccak256("lido.Lido.ELRewardsWithdrawalLimit"); /// @dev Just a counter of total amount of execution layer rewards received by Lido contract /// Not used in the logic @@ -115,8 +119,8 @@ contract Lido is ILido, StETH, AragonApp { * @param _depositContract official ETH2 Deposit contract * @param _oracle oracle contract * @param _operators instance of Node Operators Registry - * @param _treasury contract which accumulates treasury fee - * @param _insuranceFund contract which accumulates insurance fee + * @param _treasury treasury contract + * @param _insuranceFund insurance fund contract */ function initialize( IDepositContract _depositContract, @@ -132,17 +136,11 @@ contract Lido is ILido, StETH, AragonApp { _setProtocolContracts(_oracle, _treasury, _insuranceFund); - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().resumeStakingWithNewLimit( - 0, 0 // STAKING IS UNLIMITED - ) - ); - initialized(); } /** - * @notice Stops accepting new Ether to the protocol. + * @notice Stops accepting new Ether to the protocol * * @dev While accepting new Ether is stopped, calls to the `submit` function, * as well as to the default payable function, will revert. @@ -157,10 +155,21 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Resumes accepting new Ether to the protocol (if `pauseStaking` was called previously) - * and updates the staking rate limit. + * NB: Staking could be rate-limited by imposing a limit on the stake amount + * at each moment in time, see `setStakingLimit()` and `removeStakingLimit()` + * + * @dev Preserves staking limit if it was set previously * - * Staking could be rate-limited by imposing a limit on the stake amount - * at each moment in time. + * Emits `StakingResumed` event + */ + function resumeStaking() external { + _auth(STAKING_CONTROL_ROLE); + + _resumeStaking(); + } + + /** + * @notice Sets the staking rate limit * * ▲ Stake limit * │..... ..... ........ ... .... ... Stake limit = max @@ -170,28 +179,43 @@ contract Lido is ILido, StETH, AragonApp { * │──────────────────────────────────────────────────> Time * │ ^ ^ ^ ^^^ ^ ^ ^ ^^^ ^ Stake events * - * NB: To resume without limits pass zero arg values. * @dev Reverts if: + * - `_maxStakeLimit` == 0 * - `_maxStakeLimit` >= 2^96 * - `_maxStakeLimit` < `_stakeLimitIncreasePerBlock` * - `_maxStakeLimit` / `_stakeLimitIncreasePerBlock` >= 2^32 (only if `_stakeLimitIncreasePerBlock` != 0) - * Emits `StakeResumed` event + * + * Emits `StakingLimitSet` event + * * @param _maxStakeLimit max stake limit value * @param _stakeLimitIncreasePerBlock stake limit increase per single block */ - function resumeStaking( - uint256 _maxStakeLimit, - uint256 _stakeLimitIncreasePerBlock - ) external { - _auth(STAKING_RESUME_ROLE); + function setStakingLimit(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) external { + _auth(STAKING_CONTROL_ROLE); STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().resumeStakingWithNewLimit( - _maxStakeLimit, _stakeLimitIncreasePerBlock + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakingLimit( + _maxStakeLimit, + _stakeLimitIncreasePerBlock ) ); - emit StakingResumed(_maxStakeLimit, _stakeLimitIncreasePerBlock); + emit StakingLimitSet(_maxStakeLimit, _stakeLimitIncreasePerBlock); + } + + /** + * @notice Removes the staking rate limit + * + * Emits `StakingLimitRemoved` event + */ + function removeStakingLimit() external { + _auth(STAKING_CONTROL_ROLE); + + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().removeStakingLimit() + ); + + emit StakingLimitRemoved(); } /** @@ -207,29 +231,25 @@ contract Lido is ILido, StETH, AragonApp { * - 2^256 - 1 if staking is unlimited; * - 0 if staking is paused or if limit is exhausted. */ - function getCurrentStakeLimit() external view returns (uint256) { - StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); - - if (stakeLimitData.isStakingPaused()) { - return 0; - } - if (!stakeLimitData.isStakingLimitApplied()) { - return uint256(-1); - } - - return stakeLimitData.calculateCurrentStakeLimit(); + function getCurrentStakeLimit() public view returns (uint256) { + return _getCurrentStakeLimit(STAKE_LIMIT_POSITION.getStorageStakeLimitStruct()); } /** - * @notice Returns internal info about stake limit + * @notice Returns full info about current stake limit params and state * @dev Might be used for the advanced integration requests. - * @return - * `maxStakeLimit` max stake limit - * `maxStakeLimitGrowthBlocks` blocks needed to restore max stake limit from the fully exhausted state - * `prevStakeLimit` previously reached stake limit - * `prevStakeBlockNumber` previously seen block number - */ - function getStakeLimitInternalInfo() external view returns ( + * @return isStakingPaused staking pause state (equivalent to return of isStakingPaused()) + * @return isStakingLimitSet whether the stake limit is set + * @return currentStakeLimit current stake limit (equivalent to return of getCurrentStakeLimit()) + * @return maxStakeLimit max stake limit + * @return maxStakeLimitGrowthBlocks blocks needed to restore max stake limit from the fully exhausted state + * @return prevStakeLimit previously reached stake limit + * @return prevStakeBlockNumber previously seen block number + */ + function getStakeLimitFullInfo() external view returns ( + bool isStakingPaused, + bool isStakingLimitSet, + uint256 currentStakeLimit, uint256 maxStakeLimit, uint256 maxStakeLimitGrowthBlocks, uint256 prevStakeLimit, @@ -237,6 +257,11 @@ contract Lido is ILido, StETH, AragonApp { ) { StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); + isStakingPaused = stakeLimitData.isStakingPaused(); + isStakingLimitSet = stakeLimitData.isStakingLimitSet(); + + currentStakeLimit = _getCurrentStakeLimit(stakeLimitData); + maxStakeLimit = stakeLimitData.maxStakeLimit; maxStakeLimitGrowthBlocks = stakeLimitData.maxStakeLimitGrowthBlocks; prevStakeLimit = stakeLimitData.prevStakeLimit; @@ -267,8 +292,8 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice A payable function for execution layer rewards. Can be called only by ExecutionLayerRewardsVault contract - * @dev We need a separate payable function because funds received by default payable function - * are considered as funds submitted for minting stETH + * @dev We need a dedicated function because funds received by the default payable function + * are treated as a user deposit */ function receiveELRewards() external payable { require(msg.sender == EL_REWARDS_VAULT_POSITION.getStorageAddress()); @@ -325,6 +350,7 @@ contract Lido is ILido, StETH, AragonApp { _auth(RESUME_ROLE); _resume(); + _resumeStaking(); } /** @@ -375,14 +401,14 @@ contract Lido is ILido, StETH, AragonApp { * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). * * @dev Oracle contract specified here is allowed to make - * periodical updates of beacon states + * periodical updates of beacon stats * by calling pushBeacon. Treasury contract specified here is used - * to accumulate the protocol treasury fee.Insurance fund contract + * to accumulate the protocol treasury fee. Insurance fund contract * specified here is used to accumulate the protocol insurance fee. * * @param _oracle oracle contract - * @param _treasury treasury contract which accumulates treasury fee - * @param _insuranceFund insurance fund contract which accumulates insurance fee + * @param _treasury treasury contract + * @param _insuranceFund insurance fund contract */ function setProtocolContracts( address _oracle, @@ -397,8 +423,7 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Set credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched to `_withdrawalCredentials` * @dev Note that setWithdrawalCredentials discards all unused signing keys as the signatures are invalidated. - * @param _withdrawalCredentials hash of withdrawal multisignature key as accepted by - * the deposit_contract.deposit function + * @param _withdrawalCredentials withdrawal credentials field as defined in the Ethereum PoS consensus specs */ function setWithdrawalCredentials(bytes32 _withdrawalCredentials) external { _auth(MANAGE_WITHDRAWAL_KEY); @@ -422,29 +447,18 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @dev Sets limit to amount of ETH to withdraw from execution layer rewards vault per LidoOracle report + * @dev Sets limit on amount of ETH to withdraw from execution layer rewards vault per LidoOracle report * @param _limitPoints limit in basis points to amount of ETH to withdraw per LidoOracle report */ function setELRewardsWithdrawalLimit(uint16 _limitPoints) external { _auth(SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE); - _setBPValue(EL_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION, _limitPoints); + _setBPValue(EL_REWARDS_WITHDRAWAL_LIMIT_POSITION, _limitPoints); emit ELRewardsWithdrawalLimitSet(_limitPoints); } /** - * @notice Issues withdrawal request. Not implemented. - * @param _amount Amount of StETH to withdraw - * @param _pubkeyHash Receiving address - */ - function withdraw(uint256 _amount, bytes32 _pubkeyHash) external whenNotStopped { /* solhint-disable-line no-unused-vars */ - //will be upgraded to an actual implementation when withdrawals are enabled (Phase 1.5 or 2 of Eth2 launch, likely late 2022 or 2023). - //at the moment withdrawals are not possible in the beacon chain and there's no workaround - revert("NOT_IMPLEMENTED_YET"); - } - - /** - * @notice Updates beacon states, collects rewards from LidoExecutionLayerRewardsVault and distributes all rewards if beacon balance increased + * @notice Updates beacon stats, collects rewards from LidoExecutionLayerRewardsVault and distributes all rewards if beacon balance increased * @dev periodically called by the Oracle contract * @param _beaconValidators number of Lido's keys in the beacon state * @param _beaconBalance summarized balance of Lido-controlled keys in wei @@ -481,7 +495,7 @@ contract Lido is ILido, StETH, AragonApp { if (executionLayerRewardsVaultAddress != address(0)) { executionLayerRewards = ILidoExecutionLayerRewardsVault(executionLayerRewardsVaultAddress).withdrawRewards( - (_getTotalPooledEther() * EL_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION.getStorageUint256()) / TOTAL_BASIS_POINTS + (_getTotalPooledEther() * EL_REWARDS_WITHDRAWAL_LIMIT_POSITION.getStorageUint256()) / TOTAL_BASIS_POINTS ); if (executionLayerRewards != 0) { @@ -494,7 +508,7 @@ contract Lido is ILido, StETH, AragonApp { // See ADR #3 for details: https://research.lido.fi/t/rewards-distribution-after-the-merge-architecture-decision-record/1535 if (_beaconBalance > rewardBase) { uint256 rewards = _beaconBalance.sub(rewardBase); - distributeRewards(rewards.add(executionLayerRewards)); + distributeFee(rewards.add(executionLayerRewards)); } } @@ -522,23 +536,6 @@ contract Lido is ILido, StETH, AragonApp { emit RecoverToVault(vault, _token, balance); } - /** - * @notice Send NTFs to recovery Vault - * @param _token Token to be sent to recovery vault - * @param _tokenId Token Id - */ - function transferERC721ToVault(address _token, uint256 _tokenId) external { - require(_token != address(0), "ZERO_ADDRESS"); - require(allowRecoverability(_token), "RECOVER_DISALLOWED"); - - address vault = getRecoveryVault(); - require(vault != address(0), "RECOVER_VAULT_ZERO"); - - IERC721(_token).transferFrom(address(this), vault, _tokenId); - - emit RecoverERC721ToVault(vault, _token, _tokenId); - } - /** * @notice Returns staking rewards fee rate */ @@ -574,17 +571,17 @@ contract Lido is ILido, StETH, AragonApp { * @notice Get the amount of Ether temporary buffered on this contract balance * @dev Buffered balance is kept on the contract from the moment the funds are received from user * until the moment they are actually sent to the official Deposit contract. - * @return uint256 of buffered funds in wei + * @return amount of buffered funds in wei */ function getBufferedEther() external view returns (uint256) { return _getBufferedEther(); } /** - * @notice Get total amount of execution level rewards collected to Lido contract + * @notice Get total amount of execution layer rewards collected to Lido contract * @dev Ether got through LidoExecutionLayerRewardsVault is kept on this contract's balance the same way * as other buffered Ether is kept (until it gets deposited) - * @return uint256 of funds received as execution layer rewards (in wei) + * @return amount of funds received as execution layer rewards (in wei) */ function getTotalELRewardsCollected() external view returns (uint256) { return TOTAL_EL_REWARDS_COLLECTED_POSITION.getStorageUint256(); @@ -592,10 +589,10 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Get limit in basis points to amount of ETH to withdraw per LidoOracle report - * @return uint256 limit in basis points to amount of ETH to withdraw per LidoOracle report + * @return limit in basis points to amount of ETH to withdraw per LidoOracle report */ - function getELRewardsWithdrawalLimitPoints() external view returns (uint256) { - return EL_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION.getStorageUint256(); + function getELRewardsWithdrawalLimit() external view returns (uint256) { + return EL_REWARDS_WITHDRAWAL_LIMIT_POSITION.getStorageUint256(); } /** @@ -680,7 +677,7 @@ contract Lido is ILido, StETH, AragonApp { StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); require(!stakeLimitData.isStakingPaused(), "STAKING_PAUSED"); - if (stakeLimitData.isStakingLimitApplied()) { + if (stakeLimitData.isStakingLimitSet()) { uint256 currentStakeLimit = stakeLimitData.calculateCurrentStakeLimit(); require(msg.value <= currentStakeLimit, "STAKE_LIMIT"); @@ -797,10 +794,10 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @dev Distributes rewards by minting and distributing corresponding amount of liquid tokens. + * @dev Distributes fee by minting and distributing corresponding amount of liquid tokens. * @param _totalRewards Total rewards accrued on the Ethereum 2.0 side in wei */ - function distributeRewards(uint256 _totalRewards) internal { + function distributeFee(uint256 _totalRewards) internal { // We need to take a defined percentage of the reported reward as a fee, and we do // this by minting new token shares and assigning them to the fee recipients (see // StETH docs for the explanation of the shares mechanics). The staking rewards fee @@ -974,11 +971,32 @@ contract Lido is ILido, StETH, AragonApp { } function _pauseStaking() internal { - StakeLimitState.Data memory zeroState; - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct(zeroState); + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(true) + ); + emit StakingPaused(); } + function _resumeStaking() internal { + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(false) + ); + + emit StakingResumed(); + } + + function _getCurrentStakeLimit(StakeLimitState.Data memory _stakeLimitData) internal view returns(uint256) { + if (_stakeLimitData.isStakingPaused()) { + return 0; + } + if (!_stakeLimitData.isStakingLimitSet()) { + return uint256(-1); + } + + return _stakeLimitData.calculateCurrentStakeLimit(); + } + /** * @dev Size-efficient analog of the `auth(_role)` modifier * @param _role Permission name diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index 8cef5b3d5..970c83658 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -97,15 +97,17 @@ contract StETH is IERC20, Pausable { * * @dev Reports simultaneously burnt shares amount * and corresponding stETH amount. - * The stETH amount is calculated just before the burning incurred rebase. + * The stETH amount is calculated twice: before and after the burning incurred rebase. * * @param account holder of the burnt shares - * @param amount amount of burnt shares (expressed in stETH just before burning invocation) - * @param sharesAmount amount of burnt shares (expressed in shares themselves) + * @param preRebaseTokenAmount amount of stETH the burnt shares corresponded to before the burn + * @param postRebaseTokenAmount amount of stETH the burnt shares corresponded to after the burn + * @param sharesAmount amount of burnt shares */ event SharesBurnt( address indexed account, - uint256 amount, + uint256 preRebaseTokenAmount, + uint256 postRebaseTokenAmount, uint256 sharesAmount ); @@ -458,14 +460,17 @@ contract StETH is IERC20, Pausable { uint256 accountShares = shares[_account]; require(_sharesAmount <= accountShares, "BURN_AMOUNT_EXCEEDS_BALANCE"); - uint256 amount = getPooledEthByShares(_sharesAmount); - emit SharesBurnt(_account, amount, _sharesAmount); + uint256 preRebaseTokenAmount = getPooledEthByShares(_sharesAmount); newTotalShares = _getTotalShares().sub(_sharesAmount); TOTAL_SHARES_POSITION.setStorageUint256(newTotalShares); shares[_account] = accountShares.sub(_sharesAmount); + uint256 postRebaseTokenAmount = getPooledEthByShares(_sharesAmount); + + emit SharesBurnt(_account, preRebaseTokenAmount, postRebaseTokenAmount, _sharesAmount); + // Notice: we're not emitting a Transfer event to the zero address here since shares burn // works by redistributing the amount of tokens corresponding to the burned shares between // all other token holders. The total supply of the token doesn't change as the result. diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 5e20606c3..17b5427cc 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -13,6 +13,10 @@ pragma solidity 0.4.24; * and stakes it via the deposit_contract.sol contract. It doesn't hold ether on it's balance, * only a small portion (buffer) of it. * It also mints new tokens for rewards generated at the ETH 2.0 side. + * + * At the moment withdrawals are not possible in the beacon chain and there's no workaround. + * Pool will be upgraded to an actual implementation when withdrawals are enabled + * (Phase 1.5 or 2 of Eth2 launch, likely late 2022 or 2023). */ interface ILido { function totalSupply() external view returns (uint256); @@ -29,7 +33,7 @@ interface ILido { function resume() external; /** - * @notice Stops accepting new Ether to the protocol. + * @notice Stops accepting new Ether to the protocol * * @dev While accepting new Ether is stopped, calls to the `submit` function, * as well as to the default payable function, will revert. @@ -40,12 +44,37 @@ interface ILido { /** * @notice Resumes accepting new Ether to the protocol (if `pauseStaking` was called previously) - * and updates the staking rate limit. - * NB: To resume without limits pass zero arg values. + * NB: Staking could be rate-limited by imposing a limit on the stake amount + * at each moment in time, see `setStakingLimit()` and `removeStakingLimit()` + * + * @dev Preserves staking limit if it was set previously + * + * Emits `StakingResumed` event + */ + function resumeStaking() external; + + /** + * @notice Sets the staking rate limit + * + * @dev Reverts if: + * - `_maxStakeLimit` == 0 + * - `_maxStakeLimit` >= 2^96 + * - `_maxStakeLimit` < `_stakeLimitIncreasePerBlock` + * - `_maxStakeLimit` / `_stakeLimitIncreasePerBlock` >= 2^32 (only if `_stakeLimitIncreasePerBlock` != 0) + * + * Emits `StakingLimitSet` event + * * @param _maxStakeLimit max stake limit value * @param _stakeLimitIncreasePerBlock stake limit increase per single block */ - function resumeStaking(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) external; + function setStakingLimit(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) external; + + /** + * @notice Removes the staking rate limit + * + * Emits `StakingLimitRemoved` event + */ + function removeStakingLimit() external; /** * @notice Check staking state: whether it's paused or not @@ -61,15 +90,20 @@ interface ILido { function getCurrentStakeLimit() external view returns (uint256); /** - * @notice Returns internal info about stake limit + * @notice Returns full info about current stake limit params and state * @dev Might be used for the advanced integration requests. - * @return - * `maxStakeLimit` max stake limit - * `maxStakeLimitGrowthBlocks` blocks needed to restore max stake limit from the fully exhausted state - * `prevStakeLimit` previously reached stake limit - * `prevStakeBlockNumber` previously seen block number + * @return isStakingPaused staking pause state (equivalent to return of isStakingPaused()) + * @return isStakingLimitSet whether the stake limit is set + * @return currentStakeLimit current stake limit (equivalent to return of getCurrentStakeLimit()) + * @return maxStakeLimit max stake limit + * @return maxStakeLimitGrowthBlocks blocks needed to restore max stake limit from the fully exhausted state + * @return prevStakeLimit previously reached stake limit + * @return prevStakeBlockNumber previously seen block number */ - function getStakeLimitInternalInfo() external view returns ( + function getStakeLimitFullInfo() external view returns ( + bool isStakingPaused, + bool isStakingLimitSet, + uint256 currentStakeLimit, uint256 maxStakeLimit, uint256 maxStakeLimitGrowthBlocks, uint256 prevStakeLimit, @@ -78,14 +112,17 @@ interface ILido { event Stopped(); event Resumed(); + event StakingPaused(); - event StakingResumed(uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock); + event StakingResumed(); + event StakingLimitSet(uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock); + event StakingLimitRemoved(); /** * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). * @param _oracle oracle contract - * @param _treasury treasury contract which accumulates treasury fee - * @param _insuranceFund insurance fund contract which accumulates insurance fee + * @param _treasury treasury contract + * @param _insuranceFund insurance fund contract */ function setProtocolContracts( address _oracle, @@ -137,8 +174,8 @@ interface ILido { /** * @notice A payable function supposed to be called only by LidoExecutionLayerRewardsVault contract - * @dev We need a separate function because funds received by default payable function - * are considered as funds submitted by a user for staking + * @dev We need a dedicated function because funds received by the default payable function + * are treated as a user deposit */ function receiveELRewards() external payable; @@ -146,7 +183,7 @@ interface ILido { event ELRewardsReceived(uint256 amount); /** - * @dev Sets limit to amount of ETH to withdraw from execution layer rewards vault per LidoOracle report + * @dev Sets limit on amount of ETH to withdraw from execution layer rewards vault per LidoOracle report * @param _limitPoints limit in basis points to amount of ETH to withdraw per LidoOracle report */ function setELRewardsWithdrawalLimit(uint16 _limitPoints) external; @@ -157,8 +194,7 @@ interface ILido { /** * @notice Set credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched to `_withdrawalCredentials` * @dev Note that setWithdrawalCredentials discards all unused signing keys as the signatures are invalidated. - * @param _withdrawalCredentials hash of withdrawal multisignature key as accepted by - * the deposit_contract.deposit function + * @param _withdrawalCredentials withdrawal credentials field as defined in the Ethereum PoS consensus specs */ function setWithdrawalCredentials(bytes32 _withdrawalCredentials) external; @@ -170,9 +206,9 @@ interface ILido { event WithdrawalCredentialsSet(bytes32 withdrawalCredentials); /** - * @dev Sets the address of LidoExecutionLayerRewardsVault contract - * @param _executionLayerRewardsVault Execution layer rewards vault contract address - */ + * @dev Sets the address of LidoExecutionLayerRewardsVault contract + * @param _executionLayerRewardsVault Execution layer rewards vault contract address + */ function setELRewardsVault(address _executionLayerRewardsVault) external; // The `executionLayerRewardsVault` was set as the execution layer rewards vault for Lido @@ -200,13 +236,6 @@ interface ILido { // The `amount` of ether was sent to the deposit_contract.deposit function event Unbuffered(uint256 amount); - /** - * @notice Issues withdrawal request. Large withdrawals will be processed only after the phase 2 launch. - * @param _amount Amount of StETH to burn - * @param _pubkeyHash Receiving address - */ - function withdraw(uint256 _amount, bytes32 _pubkeyHash) external; - // Requested withdrawal of `etherAmount` to `pubkeyHash` on the ETH 2.0 side, `tokenAmount` burned by `sender`, // `sentFromBuffer` was sent on the current Ethereum side. event Withdrawal(address indexed sender, uint256 tokenAmount, uint256 sentFromBuffer, @@ -232,7 +261,4 @@ interface ILido { * @return beaconBalance - total amount of Beacon-side Ether (sum of all the balances of Lido validators) */ function getBeaconStat() external view returns (uint256 depositedValidators, uint256 beaconValidators, uint256 beaconBalance); - - // Requested ERC721 recovery from the `Lido` to the designated `recoveryVault` vault. - event RecoverERC721ToVault(address indexed vault, address indexed token, uint256 tokenId); } diff --git a/contracts/0.4.24/interfaces/ILidoExecutionLayerRewardsVault.sol b/contracts/0.4.24/interfaces/ILidoExecutionLayerRewardsVault.sol index 6f53da857..701643ef6 100644 --- a/contracts/0.4.24/interfaces/ILidoExecutionLayerRewardsVault.sol +++ b/contracts/0.4.24/interfaces/ILidoExecutionLayerRewardsVault.sol @@ -10,7 +10,7 @@ interface ILidoExecutionLayerRewardsVault { /** * @notice Withdraw all accumulated execution layer rewards to Lido contract * @param _maxAmount Max amount of ETH to withdraw - * @return amount uint256 of funds received as execution layer rewards (in wei) + * @return amount of funds received as execution layer rewards (in wei) */ function withdrawRewards(uint256 _maxAmount) external returns (uint256 amount); } diff --git a/contracts/0.4.24/lib/StakeLimitUtils.sol b/contracts/0.4.24/lib/StakeLimitUtils.sol index 171084184..f9353e255 100644 --- a/contracts/0.4.24/lib/StakeLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeLimitUtils.sol @@ -27,7 +27,7 @@ import "@aragon/os/contracts/common/UnstructuredStorage.sol"; // 32 bits 96 bits 96 bits // // -// - the "staking paused" state is encoded by all fields being zero, +// - the "staking paused" state is encoded by `prevStakeBlockNumber` being zero, // - the "staking unlimited" state is encoded by `maxStakeLimit` being zero and `prevStakeBlockNumber` being non-zero. // @@ -64,13 +64,13 @@ library StakeLimitUnstructuredStorage { * @dev Read stake limit state from the unstructured storage position * @param _position storage offset */ - function getStorageStakeLimitStruct(bytes32 _position) internal view returns (StakeLimitState.Data memory ret) { + function getStorageStakeLimitStruct(bytes32 _position) internal view returns (StakeLimitState.Data memory stakeLimit) { uint256 slotValue = _position.getStorageUint256(); - ret.prevStakeBlockNumber = uint32(slotValue >> PREV_STAKE_BLOCK_NUMBER_OFFSET); - ret.prevStakeLimit = uint96(slotValue >> PREV_STAKE_LIMIT_OFFSET); - ret.maxStakeLimitGrowthBlocks = uint32(slotValue >> MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET); - ret.maxStakeLimit = uint96(slotValue >> MAX_STAKE_LIMIT_OFFSET); + stakeLimit.prevStakeBlockNumber = uint32(slotValue >> PREV_STAKE_BLOCK_NUMBER_OFFSET); + stakeLimit.prevStakeLimit = uint96(slotValue >> PREV_STAKE_LIMIT_OFFSET); + stakeLimit.maxStakeLimitGrowthBlocks = uint32(slotValue >> MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET); + stakeLimit.maxStakeLimit = uint96(slotValue >> MAX_STAKE_LIMIT_OFFSET); } /** @@ -108,34 +108,32 @@ library StakeLimitUtils { } /** - * @notice check if staking is on pause (i.e. every byte in the slot has a zero value) + * @notice check if staking is on pause */ function isStakingPaused(StakeLimitState.Data memory _data) internal pure returns(bool) { - return ( - _data.maxStakeLimit - | _data.maxStakeLimitGrowthBlocks - | _data.prevStakeBlockNumber - | _data.prevStakeLimit - ) == 0; + return _data.prevStakeBlockNumber == 0; } /** - * @notice check if staking limit is applied (otherwise staking is unlimited) + * @notice check if staking limit is set (otherwise staking is unlimited) */ - function isStakingLimitApplied(StakeLimitState.Data memory _data) internal pure returns(bool) { + function isStakingLimitSet(StakeLimitState.Data memory _data) internal pure returns(bool) { return _data.maxStakeLimit != 0; } /** - * @notice prepare stake limit repr to resume staking with the desired limits + * @notice update stake limit repr with the desired limits + * @dev input `_data` param is mutated and the func returns effectively the same pointer + * @param _data stake limit state struct * @param _maxStakeLimit stake limit max value * @param _stakeLimitIncreasePerBlock stake limit increase (restoration) per block */ - function resumeStakingWithNewLimit( + function setStakingLimit( StakeLimitState.Data memory _data, uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock ) internal view returns (StakeLimitState.Data memory) { + require(_maxStakeLimit != 0, "ZERO_MAX_STAKE_LIMIT"); require(_maxStakeLimit <= uint96(-1), "TOO_LARGE_MAX_STAKE_LIMIT"); require(_maxStakeLimit >= _stakeLimitIncreasePerBlock, "TOO_LARGE_LIMIT_INCREASE"); require( @@ -153,23 +151,58 @@ library StakeLimitUtils { _data.maxStakeLimitGrowthBlocks = _stakeLimitIncreasePerBlock != 0 ? uint32(_maxStakeLimit / _stakeLimitIncreasePerBlock) : 0; _data.maxStakeLimit = uint96(_maxStakeLimit); - _data.prevStakeBlockNumber = uint32(block.number); + + if (_data.prevStakeBlockNumber != 0) { + _data.prevStakeBlockNumber = uint32(block.number); + } + + return _data; + } + + /** + * @notice update stake limit repr to remove the limit + * @dev input `_data` param is mutated and the func returns effectively the same pointer + * @param _data stake limit state struct + */ + function removeStakingLimit( + StakeLimitState.Data memory _data + ) internal view returns (StakeLimitState.Data memory) { + _data.maxStakeLimit = 0; return _data; } /** * @notice update stake limit repr after submitting user's eth + * @dev input `_data` param is mutated and the func returns effectively the same pointer + * @param _data stake limit state struct + * @param _newPrevStakeLimit new value for the `prevStakeLimit` field */ function updatePrevStakeLimit( StakeLimitState.Data memory _data, - uint256 _newPrevLimit + uint256 _newPrevStakeLimit ) internal view returns (StakeLimitState.Data memory) { - assert(_newPrevLimit <= uint96(-1)); + assert(_newPrevStakeLimit <= uint96(-1)); + assert(_data.prevStakeBlockNumber != 0); - _data.prevStakeLimit = uint96(_newPrevLimit); + _data.prevStakeLimit = uint96(_newPrevStakeLimit); _data.prevStakeBlockNumber = uint32(block.number); return _data; } + + /** + * @notice set stake limit pause state (on or off) + * @dev input `_data` param is mutated and the func returns effectively the same pointer + * @param _data stake limit state struct + * @param _isPaused pause state flag + */ + function setStakeLimitPauseState( + StakeLimitState.Data memory _data, + bool _isPaused + ) internal view returns (StakeLimitState.Data memory) { + _data.prevStakeBlockNumber = uint32(_isPaused ? 0 : block.number); + + return _data; + } } diff --git a/contracts/0.4.24/template/LidoTemplate.sol b/contracts/0.4.24/template/LidoTemplate.sol index 07c6708d9..5f8fa4138 100644 --- a/contracts/0.4.24/template/LidoTemplate.sol +++ b/contracts/0.4.24/template/LidoTemplate.sol @@ -656,7 +656,7 @@ contract LidoTemplate is IsContract { perms[4] = _state.lido.BURN_ROLE(); perms[5] = _state.lido.RESUME_ROLE(); perms[6] = _state.lido.STAKING_PAUSE_ROLE(); - perms[7] = _state.lido.STAKING_RESUME_ROLE(); + perms[7] = _state.lido.STAKING_CONTROL_ROLE(); perms[8] = _state.lido.SET_EL_REWARDS_VAULT_ROLE(); perms[9] = _state.lido.SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE(); diff --git a/contracts/0.4.24/test_helpers/LidoMock.sol b/contracts/0.4.24/test_helpers/LidoMock.sol index 8aa191157..be7dc3844 100644 --- a/contracts/0.4.24/test_helpers/LidoMock.sol +++ b/contracts/0.4.24/test_helpers/LidoMock.sol @@ -28,6 +28,7 @@ contract LidoMock is Lido { ); _resume(); + _resumeStaking(); } /** diff --git a/contracts/0.4.24/test_helpers/LidoPushableMock.sol b/contracts/0.4.24/test_helpers/LidoPushableMock.sol index 35c3d5c17..704b3d346 100644 --- a/contracts/0.4.24/test_helpers/LidoPushableMock.sol +++ b/contracts/0.4.24/test_helpers/LidoPushableMock.sol @@ -14,7 +14,7 @@ import "./VaultMock.sol"; contract LidoPushableMock is Lido { uint256 public totalRewards; - bool public distributeRewardsCalled; + bool public distributeFeeCalled; function initialize( IDepositContract depositContract, @@ -57,13 +57,13 @@ contract LidoPushableMock is Lido { initialized(); } - function resetDistributeRewards() public { + function resetDistributeFee() public { totalRewards = 0; - distributeRewardsCalled = false; + distributeFeeCalled = false; } - function distributeRewards(uint256 _totalRewards) internal { + function distributeFee(uint256 _totalRewards) internal { totalRewards = _totalRewards; - distributeRewardsCalled = true; + distributeFeeCalled = true; } } diff --git a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol index c39ffef7a..74bbfa0f9 100644 --- a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol +++ b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol @@ -14,12 +14,13 @@ contract StakeLimitUtilsMock { bytes32 internal constant STAKE_LIMIT_POSITION = keccak256("abcdef"); - function getStorageStakeLimit() public view returns ( + function getStorageStakeLimit(uint256 _slotValue) public view returns ( uint32 prevStakeBlockNumber, uint96 prevStakeLimit, uint32 maxStakeLimitGrowthBlocks, uint96 maxStakeLimit ) { + STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); StakeLimitState.Data memory data = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); prevStakeBlockNumber = data.prevStakeBlockNumber; @@ -33,7 +34,7 @@ contract StakeLimitUtilsMock { uint96 _prevStakeLimit, uint32 _maxStakeLimitGrowthBlocks, uint96 _maxStakeLimit - ) public returns (uint256 ret) { + ) public view returns (uint256 ret) { StakeLimitState.Data memory data; data.prevStakeBlockNumber = _prevStakeBlockNumber; data.prevStakeLimit = _prevStakeLimit; @@ -41,33 +42,54 @@ contract StakeLimitUtilsMock { data.maxStakeLimit = _maxStakeLimit; STAKE_LIMIT_POSITION.setStorageStakeLimitStruct(data); - return STAKE_LIMIT_POSITION.getStorageUint256(); } - function calculateCurrentStakeLimit() public view returns(uint256 limit) { + function calculateCurrentStakeLimit(uint256 _slotValue) public view returns(uint256 limit) { + STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().calculateCurrentStakeLimit(); } function isStakingPaused(uint256 _slotValue) public view returns(bool) { + STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingPaused(); } - function isStakingLimitApplied(uint256 _slotValue) public view returns(bool) { - return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingLimitApplied(); + function isStakingLimitSet(uint256 _slotValue) public view returns(bool) { + STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); + return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingLimitSet(); } - function resumeStakingWithNewLimit(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) public view { + function setStakingLimit(uint256 _slotValue, uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) public view { + STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().resumeStakingWithNewLimit( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakingLimit( _maxStakeLimit, _stakeLimitIncreasePerBlock ) ); } - function updatePrevStakeLimit(uint256 _newPrevLimit) internal view returns (StakeLimitState.Data memory) { + function removeStakingLimit(uint256 _slotValue) public view returns(uint256) { + STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().removeStakingLimit() + ); + return STAKE_LIMIT_POSITION.getStorageUint256(); + } + + function updatePrevStakeLimit(uint256 _slotValue, uint256 _newPrevLimit) public view returns(uint256) { + STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().updatePrevStakeLimit(_newPrevLimit) ); + return STAKE_LIMIT_POSITION.getStorageUint256(); + } + + function setStakeLimitPauseState(uint256 _slotValue, bool _isPaused) public view returns(uint256) { + STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(_isPaused) + ); + return STAKE_LIMIT_POSITION.getStorageUint256(); } } diff --git a/contracts/0.8.9/LidoExecutionLayerRewardsVault.sol b/contracts/0.8.9/LidoExecutionLayerRewardsVault.sol index d6bc219f6..2d1250be2 100644 --- a/contracts/0.8.9/LidoExecutionLayerRewardsVault.sol +++ b/contracts/0.8.9/LidoExecutionLayerRewardsVault.sol @@ -11,20 +11,17 @@ import "@openzeppelin/contracts-v4.4/token/ERC20/utils/SafeERC20.sol"; interface ILido { /** - * @notice A payable function supposed to be called only by LidoExecLayerRewardsVault contract - * @dev We need a separate function because funds received by default payable function - * will go through entire deposit algorithm - */ + * @notice A payable function supposed to be called only by LidoExecLayerRewardsVault contract + * @dev We need a dedicated function because funds received by the default payable function + * are treated as a user deposit + */ function receiveELRewards() external payable; } /** -* @title A vault for temporary storage of execution layer rewards (MEV and tx priority fee) -* -* These vault replenishments happen continuously through a day, while withdrawals -* happen much less often, only on LidoOracle beacon balance reports -*/ + * @title A vault for temporary storage of execution layer rewards (MEV and tx priority fee) + */ contract LidoExecutionLayerRewardsVault { using SafeERC20 for IERC20; @@ -32,7 +29,7 @@ contract LidoExecutionLayerRewardsVault { address public immutable TREASURY; /** - * Emitted when the ERC20 `token` recovered (e.g. transferred) + * Emitted when the ERC20 `token` recovered (i.e. transferred) * to the Lido treasury address by `requestedBy` sender. */ event ERC20Recovered( @@ -42,7 +39,7 @@ contract LidoExecutionLayerRewardsVault { ); /** - * Emitted when the ERC721-compatible `token` (NFT) recovered (e.g. transferred) + * Emitted when the ERC721-compatible `token` (NFT) recovered (i.e. transferred) * to the Lido treasury address by `requestedBy` sender. */ event ERC721Recovered( @@ -51,6 +48,13 @@ contract LidoExecutionLayerRewardsVault { uint256 tokenId ); + /** + * Emitted when the vault received ETH + */ + event ETHReceived( + uint256 amount + ); + /** * Ctor * @@ -66,19 +70,19 @@ contract LidoExecutionLayerRewardsVault { } /** - * @notice Allows the contract to receive ETH - * @dev execution layer rewards may be sent as plain ETH transfers - */ + * @notice Allows the contract to receive ETH + * @dev execution layer rewards may be sent as plain ETH transfers + */ receive() external payable { - // no-op + emit ETHReceived(msg.value); } /** - * @notice Withdraw all accumulated rewards to Lido contract - * @dev Can be called only by the Lido contract - * @param _maxAmount Max amount of ETH to withdraw - * @return amount uint256 of funds received as execution layer rewards (in wei) - */ + * @notice Withdraw all accumulated rewards to Lido contract + * @dev Can be called only by the Lido contract + * @param _maxAmount Max amount of ETH to withdraw + * @return amount of funds received as execution layer rewards (in wei) + */ function withdrawRewards(uint256 _maxAmount) external returns (uint256 amount) { require(msg.sender == LIDO, "ONLY_LIDO_CAN_WITHDRAW"); diff --git a/contracts/0.8.9/SelfOwnedStETHBurner.sol b/contracts/0.8.9/SelfOwnedStETHBurner.sol index e39949170..1894f77cc 100644 --- a/contracts/0.8.9/SelfOwnedStETHBurner.sol +++ b/contracts/0.8.9/SelfOwnedStETHBurner.sol @@ -120,7 +120,7 @@ contract SelfOwnedStETHBurner is ISelfOwnedStETHBurner, IBeaconReportReceiver, E ); /** - * Emitted when the excessive stETH `amount` (corresponding to `sharesAmount` shares) recovered (e.g. transferred) + * Emitted when the excessive stETH `amount` (corresponding to `sharesAmount` shares) recovered (i.e. transferred) * to the Lido treasure address by `requestedBy` sender. */ event ExcessStETHRecovered( @@ -130,7 +130,7 @@ contract SelfOwnedStETHBurner is ISelfOwnedStETHBurner, IBeaconReportReceiver, E ); /** - * Emitted when the ERC20 `token` recovered (e.g. transferred) + * Emitted when the ERC20 `token` recovered (i.e. transferred) * to the Lido treasure address by `requestedBy` sender. */ event ERC20Recovered( @@ -140,7 +140,7 @@ contract SelfOwnedStETHBurner is ISelfOwnedStETHBurner, IBeaconReportReceiver, E ); /** - * Emitted when the ERC721-compatible `token` (NFT) recovered (e.g. transferred) + * Emitted when the ERC721-compatible `token` (NFT) recovered (i.e. transferred) * to the Lido treasure address by `requestedBy` sender. */ event ERC721Recovered( diff --git a/scripts/multisig/12-check-dao.js b/scripts/multisig/12-check-dao.js index f1a6a53f5..e11f3f191 100644 --- a/scripts/multisig/12-check-dao.js +++ b/scripts/multisig/12-check-dao.js @@ -575,7 +575,7 @@ async function assertDaoPermissions({ kernel, lido, oracle, nopsRegistry, agent, 'SET_TREASURY', 'SET_INSURANCE_FUND', 'STAKING_PAUSE_ROLE', - 'STAKING_RESUME_ROLE', + 'STAKING_CONTROL_ROLE', 'SET_EL_REWARDS_VAULT_ROLE', 'SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE' ], diff --git a/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js b/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js index 6082c7e1a..cf06ca6f5 100644 --- a/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js +++ b/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js @@ -87,7 +87,7 @@ async function createVoting({ web3, artifacts }) { const grantSetELRewardsWithdrawalLimitRoleCallData = await createGrantRoleForLidoAppCallData('SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE') const grantResumeRoleCallData = await createGrantRoleForLidoAppCallData('RESUME_ROLE') const grantStakingPauseRoleCallData = await createGrantRoleForLidoAppCallData('STAKING_PAUSE_ROLE') - const grantStakingResumeRoleCallData = await createGrantRoleForLidoAppCallData('STAKING_RESUME_ROLE') + const grantStakingControlRoleCallData = await createGrantRoleForLidoAppCallData('STAKING_CONTROL_ROLE') const grantManageProtocolContractsRoleCallData = await createGrantRoleForLidoAppCallData('MANAGE_PROTOCOL_CONTRACTS_ROLE') const setELRewardsVaultCallData = { @@ -120,7 +120,7 @@ async function createVoting({ web3, artifacts }) { grantSetELRewardsWithdrawalLimitRoleCallData, grantResumeRoleCallData, grantStakingPauseRoleCallData, - grantStakingResumeRoleCallData, + grantStakingControlRoleCallData, grantManageProtocolContractsRoleCallData, setELRewardsVaultCallData, setELRewardsWithdrawalLimitCallData, @@ -148,7 +148,7 @@ async function createVoting({ web3, artifacts }) { 9) Grant role SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE to voting 10) Grant role RESUME_ROLE to voting 11) Grant role STAKING_PAUSE_ROLE to voting -12) Grant role STAKING_RESUME_ROLE to voting +12) Grant role STAKING_CONTROL_ROLE to voting 13) Grant role MANAGE_PROTOCOL_CONTRACTS_ROLE to voting 14) Set deployed LidoExecutionLayerRewardsVault to Lido contract 15) Set Execution Layer rewards withdrawal limit to ${elRewardsWithdrawalLimitPoints} basis points diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 1e2427658..e32487be8 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -93,7 +93,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) from: appManager }) await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.STAKING_RESUME_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.STAKING_CONTROL_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.MANAGE_SIGNING_KEYS(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.ADD_NODE_OPERATOR_ROLE(), appManager, { from: appManager }) @@ -270,7 +270,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it('Attempt to set invalid execution layer rewards withdrawal limit', async () => { - const initialValue = await app.getELRewardsWithdrawalLimitPoints() + const initialValue = await app.getELRewardsWithdrawalLimit() assertEvent(await app.setELRewardsWithdrawalLimit(1, { from: voting }), 'ELRewardsWithdrawalLimitSet', { expectedArgs: { limitPoints: 1 } @@ -561,40 +561,58 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(5), referral: ZERO_ADDRESS } }) }) - const verifyStakeLimitState = async (expectedMaxStakeLimit, expectedLimitIncrease, expectedCurrentStakeLimit) => { + const verifyStakeLimitState = async ( + expectedMaxStakeLimit, + expectedLimitIncrease, + expectedCurrentStakeLimit, + expectedIsStakingPaused, + expectedIsStakingLimited + ) => { currentStakeLimit = await app.getCurrentStakeLimit() assertBn(currentStakeLimit, expectedCurrentStakeLimit) - ;({ maxStakeLimit, maxStakeLimitGrowthBlocks, prevStakeLimit, prevStakeBlockNumber } = await app.getStakeLimitInternalInfo()) + isStakingPaused = await app.isStakingPaused() + assert.equal(isStakingPaused, expectedIsStakingPaused) + ;({ + isStakingPaused, + isStakingLimitSet, + currentStakeLimit, + maxStakeLimit, + maxStakeLimitGrowthBlocks, + prevStakeLimit, + prevStakeBlockNumber + } = await app.getStakeLimitFullInfo()) + + assertBn(currentStakeLimit, expectedCurrentStakeLimit) assertBn(maxStakeLimit, expectedMaxStakeLimit) - assertBn(maxStakeLimitGrowthBlocks, expectedLimitIncrease > 0 ? expectedMaxStakeLimit / expectedLimitIncrease : 0) + assert.equal(isStakingPaused, expectedIsStakingPaused) + assert.equal(isStakingLimitSet, expectedIsStakingLimited) + + if (isStakingLimitSet) { + assertBn(maxStakeLimitGrowthBlocks, expectedLimitIncrease > 0 ? expectedMaxStakeLimit / expectedLimitIncrease : 0) + } } it('staking pause & unlimited resume works', async () => { let receipt const MAX_UINT256 = bn(2).pow(bn(256)).sub(bn(1)) - await verifyStakeLimitState(bn(0), bn(0), MAX_UINT256) + await verifyStakeLimitState(bn(0), bn(0), MAX_UINT256, false, false) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) await assertRevert(app.pauseStaking(), 'APP_AUTH_FAILED') receipt = await app.pauseStaking({ from: voting }) assertEvent(receipt, 'StakingPaused') - - assert.equal(await app.isStakingPaused(), true) - verifyStakeLimitState(bn(0), bn(0), bn(0)) + verifyStakeLimitState(bn(0), bn(0), bn(0), true, false) await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(2) }), `STAKING_PAUSED`) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }), `STAKING_PAUSED`) - const disableStakeLimit = 0 - await assertRevert(app.resumeStaking(disableStakeLimit, disableStakeLimit), 'APP_AUTH_FAILED') - receipt = await app.resumeStaking(disableStakeLimit, disableStakeLimit, { from: voting }) + await assertRevert(app.resumeStaking(), 'APP_AUTH_FAILED') + receipt = await app.resumeStaking({ from: voting }) assertEvent(receipt, 'StakingResumed') - assert.equal(await app.isStakingPaused(), false) - - await verifyStakeLimitState(bn(0), bn(0), MAX_UINT256) + await verifyStakeLimitState(bn(0), bn(0), MAX_UINT256, false, false) await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(1.1) }) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(1.4) }) @@ -614,50 +632,52 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) const expectedMaxStakeLimit = ETH(3) const limitIncreasePerBlock = bn(expectedMaxStakeLimit).div(bn(blocksToReachMaxStakeLimit)) // 1 * 10**16 - receipt = await app.resumeStaking(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) - assertEvent(receipt, 'StakingResumed', { + receipt = await app.resumeStaking({ from: voting }) + assertEvent(receipt, 'StakingResumed') + receipt = await app.setStakingLimit(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingLimitSet', { expectedArgs: { maxStakeLimit: expectedMaxStakeLimit, stakeLimitIncreasePerBlock: limitIncreasePerBlock } }) - assert.equal(await app.isStakingPaused(), false) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit, false, true) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(1)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(1), false, true) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.5) }), `STAKE_LIMIT`) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, bn(ETH(1)).add(limitIncreasePerBlock)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, bn(ETH(1)).add(limitIncreasePerBlock), false, true) // expect to grow for another 1.5 ETH since last submit // every revert produces new block, so we need to account that block await mineNBlocks(blocksToReachMaxStakeLimit / 2 - 1) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(2.5)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(2.5), false, true) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.6) }), `STAKE_LIMIT`) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.5) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2.5), referral: ZERO_ADDRESS } }) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, limitIncreasePerBlock.muln(2)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, limitIncreasePerBlock.muln(2), false, true) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(0.1) }), `STAKE_LIMIT`) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, limitIncreasePerBlock.muln(3)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, limitIncreasePerBlock.muln(3), false, true) // once again, we are subtracting blocks number induced by revert checks await mineNBlocks(blocksToReachMaxStakeLimit / 3 - 4) receipt = await app.submit(ZERO_ADDRESS, { from: user1, value: ETH(1) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user1, amount: ETH(1), referral: ZERO_ADDRESS } }) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0), false, true) // check that limit is restored completely await mineNBlocks(blocksToReachMaxStakeLimit) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit, false, true) // check that limit is capped by maxLimit value and doesn't grow infinitely await mineNBlocks(10) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit, false, true) - await assertRevert(app.resumeStaking(ETH(1), ETH(1.1), { from: voting }), `TOO_LARGE_LIMIT_INCREASE`) - await assertRevert(app.resumeStaking(ETH(1), bn(10), { from: voting }), `TOO_SMALL_LIMIT_INCREASE`) + await assertRevert(app.setStakingLimit(ETH(0), ETH(0), { from: voting }), `ZERO_MAX_STAKE_LIMIT`) + await assertRevert(app.setStakingLimit(ETH(1), ETH(1.1), { from: voting }), `TOO_LARGE_LIMIT_INCREASE`) + await assertRevert(app.setStakingLimit(ETH(1), bn(10), { from: voting }), `TOO_SMALL_LIMIT_INCREASE`) }) it('resume staking with an one-shot limit works', async () => { @@ -666,26 +686,27 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) const expectedMaxStakeLimit = ETH(7) const limitIncreasePerBlock = 0 - receipt = await app.resumeStaking(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) - assertEvent(receipt, 'StakingResumed', { + receipt = await app.resumeStaking({ from: voting }) + assertEvent(receipt, 'StakingResumed') + receipt = await app.setStakingLimit(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingLimitSet', { expectedArgs: { maxStakeLimit: expectedMaxStakeLimit, stakeLimitIncreasePerBlock: limitIncreasePerBlock } }) - assert.equal(await app.isStakingPaused(), false) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit, false, true) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(5) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(5), referral: ZERO_ADDRESS } }) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(2)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(2), false, true) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0), false, true) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(0.1) }), `STAKE_LIMIT`) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0), false, true) await mineNBlocks(100) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0), false, true) }) it('resume with various changing limits work', async () => { @@ -694,44 +715,48 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) const expectedMaxStakeLimit = ETH(9) const limitIncreasePerBlock = bn(expectedMaxStakeLimit).divn(100) - receipt = await app.resumeStaking(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) - assertEvent(receipt, 'StakingResumed', { + receipt = await app.resumeStaking({ from: voting }) + assertEvent(receipt, 'StakingResumed') + receipt = await app.setStakingLimit(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingLimitSet', { expectedArgs: { maxStakeLimit: expectedMaxStakeLimit, stakeLimitIncreasePerBlock: limitIncreasePerBlock } }) - assert.equal(await app.isStakingPaused(), false) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit, false, true) const smallerExpectedMaxStakeLimit = ETH(5) const smallerLimitIncreasePerBlock = bn(smallerExpectedMaxStakeLimit).divn(200) - receipt = await app.resumeStaking(smallerExpectedMaxStakeLimit, smallerLimitIncreasePerBlock, { from: voting }) - assertEvent(receipt, 'StakingResumed', { + receipt = await app.setStakingLimit(smallerExpectedMaxStakeLimit, smallerLimitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingLimitSet', { expectedArgs: { maxStakeLimit: smallerExpectedMaxStakeLimit, stakeLimitIncreasePerBlock: smallerLimitIncreasePerBlock } }) - assert.equal(await app.isStakingPaused(), false) - await verifyStakeLimitState(smallerExpectedMaxStakeLimit, smallerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit) + await verifyStakeLimitState(smallerExpectedMaxStakeLimit, smallerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit, false, true) const largerExpectedMaxStakeLimit = ETH(10) const largerLimitIncreasePerBlock = bn(largerExpectedMaxStakeLimit).divn(1000) - receipt = await app.resumeStaking(largerExpectedMaxStakeLimit, largerLimitIncreasePerBlock, { from: voting }) - assertEvent(receipt, 'StakingResumed', { + receipt = await app.setStakingLimit(largerExpectedMaxStakeLimit, largerLimitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingLimitSet', { expectedArgs: { maxStakeLimit: largerExpectedMaxStakeLimit, stakeLimitIncreasePerBlock: largerLimitIncreasePerBlock } }) - assert.equal(await app.isStakingPaused(), false) - await verifyStakeLimitState(largerExpectedMaxStakeLimit, largerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit) + await verifyStakeLimitState(largerExpectedMaxStakeLimit, largerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit, false, true) + + receipt = await app.removeStakingLimit({ from: voting }) + assertEvent(receipt, 'StakingLimitRemoved') + + await verifyStakeLimitState(0, 0, bn(2).pow(bn(256)).sub(bn(1)), false, false) }) it('reverts when trying to call unknown function', async () => { @@ -804,41 +829,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(await app.getBufferedEther(), ETH(5)) }) - it('withdrawal method reverts', async () => { - await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) - await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) - - await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) - await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) - - await app.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) - await operators.addSigningKeys( - 0, - 6, - hexConcat( - pad('0x010203', 48), - pad('0x010204', 48), - pad('0x010205', 48), - pad('0x010206', 48), - pad('0x010207', 48), - pad('0x010208', 48) - ), - hexConcat(pad('0x01', 96), pad('0x01', 96), pad('0x01', 96), pad('0x01', 96), pad('0x01', 96), pad('0x01', 96)), - { from: voting } - ) - - await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(1) }) - await app.methods['depositBufferedEther()']({ from: depositor }) - assertBn(await app.getTotalPooledEther(), ETH(1)) - assertBn(await app.totalSupply(), tokens(1)) - assertBn(await app.getBufferedEther(), ETH(1)) - - await checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) - - await assertRevert(app.withdraw(tokens(1), pad('0x1000', 32), { from: nobody }), 'NOT_IMPLEMENTED_YET') - await assertRevert(app.withdraw(tokens(1), pad('0x1000', 32), { from: user1 }), 'NOT_IMPLEMENTED_YET') - }) - it('handleOracleReport works', async () => { await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) @@ -976,8 +966,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await assertRevert(app.resume({ from: user2 }), 'APP_AUTH_FAILED') await app.resume({ from: voting }) - await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(4) }), 'STAKING_PAUSED') - await app.resumeStaking(0, 0, { from: voting }) await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(4) }) await app.methods['depositBufferedEther()']({ from: depositor }) @@ -1393,18 +1381,34 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await assertRevert(app.burnShares(user1, ETH(1), { from: nobody }), 'APP_AUTH_FAILED') // voting can burn shares of any user - const expectedAmount = await app.getPooledEthByShares(ETH(0.5)) + const expectedPreTokenAmount = await app.getPooledEthByShares(ETH(0.5)) let receipt = await app.burnShares(user1, ETH(0.5), { from: voting }) - assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: ETH(0.5) } }) + const expectedPostTokenAmount = await app.getPooledEthByShares(ETH(0.5)) + assertEvent(receipt, 'SharesBurnt', { + expectedArgs: { + account: user1, + preRebaseTokenAmount: expectedPreTokenAmount, + postRebaseTokenAmount: expectedPostTokenAmount, + sharesAmount: ETH(0.5) + } + }) - const expectedDoubledAmount = await app.getPooledEthByShares(ETH(0.5)) + const expectedPreDoubledAmount = await app.getPooledEthByShares(ETH(0.5)) receipt = await app.burnShares(user1, ETH(0.5), { from: voting }) - assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: expectedDoubledAmount, sharesAmount: ETH(0.5) } }) + const expectedPostDoubledAmount = await app.getPooledEthByShares(ETH(0.5)) + assertEvent(receipt, 'SharesBurnt', { + expectedArgs: { + account: user1, + preRebaseTokenAmount: expectedPreDoubledAmount, + postRebaseTokenAmount: expectedPostDoubledAmount, + sharesAmount: ETH(0.5) + } + }) - assertBn(expectedAmount.mul(bn(2)), expectedDoubledAmount) + assertBn(expectedPreTokenAmount.mul(bn(2)), expectedPreDoubledAmount) assertBn(tokens(0), await app.getPooledEthByShares(ETH(0.5))) - // user1 has zero shares afteralls + // user1 has zero shares after all assertBn(await app.sharesOf(user1), tokens(0)) // voting can't continue burning if user already has no shares @@ -1460,18 +1464,12 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) context('recovery vault', () => { - let nftToken - beforeEach(async () => { await anyToken.mint(app.address, 100) - - nftToken = await ERC721Mock.new() - await nftToken.mint(app.address, 777) }) it('reverts when vault is not set', async () => { await assertRevert(app.transferToVault(anyToken.address, { from: nobody }), 'RECOVER_VAULT_ZERO') - await assertRevert(app.transferERC721ToVault(nftToken.address, 777, { from: nobody }), 'RECOVER_VAULT_ZERO') }) context('recovery works with vault mock deployed', () => { @@ -1494,17 +1492,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'RecoverToVault', { expectedArgs: { vault: vault.address, token: anyToken.address, amount: 100 } }) }) - it('recovery with nft tokens works and emits event', async () => { - await assertRevert(app.transferERC721ToVault(ZERO_ADDRESS, 777, { from: nobody })) - - assert.equal(await nftToken.ownerOf(777), app.address) - - const receipt = await app.transferERC721ToVault(nftToken.address, 777, { from: nobody }) - assertEvent(receipt, 'RecoverERC721ToVault', { expectedArgs: { vault: vault.address, token: nftToken.address, tokenId: 777 } }) - - assert.equal(await nftToken.ownerOf(777), vault.address) - }) - it('recovery with unaccounted ether works and emits event', async () => { await app.makeUnaccountedEther({ from: user1, value: ETH(10) }) const receipt = await app.transferToVault(ZERO_ADDRESS, { from: nobody }) diff --git a/test/0.4.24/lidoHandleOracleReport.test.js b/test/0.4.24/lidoHandleOracleReport.test.js index c8de0f421..747dd30c9 100644 --- a/test/0.4.24/lidoHandleOracleReport.test.js +++ b/test/0.4.24/lidoHandleOracleReport.test.js @@ -53,7 +53,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await app.getBufferedEther(), ETH(0)) assertBn(await app.getTotalPooledEther(), ETH(0)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -62,7 +62,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await app.getBufferedEther(), ETH(0)) assertBn(await app.getTotalPooledEther(), ETH(0)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) }) @@ -80,7 +80,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await app.getBufferedEther(), ETH(12)) assertBn(await app.getTotalPooledEther(), ETH(12)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -89,7 +89,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await app.getBufferedEther(), ETH(12)) assertBn(await app.getTotalPooledEther(), ETH(12)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) }) @@ -113,7 +113,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await app.getBufferedEther(), ETH(3)) assertBn(await app.getTotalPooledEther(), ETH(35)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -122,7 +122,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await app.getBufferedEther(), ETH(3)) assertBn(await app.getTotalPooledEther(), ETH(35)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -131,7 +131,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(31) }) assertBn(await app.getBufferedEther(), ETH(3)) assertBn(await app.getTotalPooledEther(), ETH(34)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -140,7 +140,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(32) }) assertBn(await app.getBufferedEther(), ETH(3)) assertBn(await app.getTotalPooledEther(), ETH(35)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) }) @@ -164,7 +164,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 2, beaconValidators: 1, beaconBalance: ETH(0) }) assertBn(await app.getBufferedEther(), ETH(5)) assertBn(await app.getTotalPooledEther(), ETH(37)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -173,7 +173,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 2, beaconValidators: 1, beaconBalance: ETH(1) }) assertBn(await app.getBufferedEther(), ETH(5)) assertBn(await app.getTotalPooledEther(), ETH(38)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -182,7 +182,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 2, beaconValidators: 2, beaconBalance: ETH(62) }) assertBn(await app.getBufferedEther(), ETH(5)) assertBn(await app.getTotalPooledEther(), ETH(67)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -191,7 +191,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 2, beaconValidators: 1, beaconBalance: ETH(31) }) assertBn(await app.getBufferedEther(), ETH(5)) assertBn(await app.getTotalPooledEther(), ETH(68)) - assert.equal(await app.distributeRewardsCalled(), true) + assert.equal(await app.distributeFeeCalled(), true) assertBn(await app.totalRewards(), ETH(1)) }) @@ -200,7 +200,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 2, beaconValidators: 2, beaconBalance: ETH(63) }) assertBn(await app.getBufferedEther(), ETH(5)) assertBn(await app.getTotalPooledEther(), ETH(68)) - assert.equal(await app.distributeRewardsCalled(), true) + assert.equal(await app.distributeFeeCalled(), true) assertBn(await app.totalRewards(), ETH(1)) }) @@ -209,7 +209,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 2, beaconValidators: 1, beaconBalance: ETH(30) }) assertBn(await app.getBufferedEther(), ETH(5)) assertBn(await app.getTotalPooledEther(), ETH(67)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) }) @@ -232,7 +232,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 5, beaconValidators: 4, beaconBalance: ETH(1) }) assertBn(await app.getBufferedEther(), ETH(0)) assertBn(await app.getTotalPooledEther(), ETH(33)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) }) diff --git a/test/0.4.24/staking-limit.test.js b/test/0.4.24/staking-limit.test.js index ad0f4a297..6946ed06a 100644 --- a/test/0.4.24/staking-limit.test.js +++ b/test/0.4.24/staking-limit.test.js @@ -28,11 +28,11 @@ const ETH = (value) => web3.utils.toWei(value + '', 'ether') // 32 bits 96 bits 96 bits // // -// - the "staking paused" state is encoded by all fields being zero, +// - the "staking paused" state is encoded by `prevStakeBlockNumber` being zero, // - the "staking unlimited" state is encoded by `maxStakeLimit` being zero and `prevStakeBlockNumber` being non-zero. // -contract.skip('StakingLimits', () => { +contract('StakingLimits', ([account1]) => { let limits before('deploy base app', async () => { @@ -40,90 +40,119 @@ contract.skip('StakingLimits', () => { }) it('encode zeros', async () => { - const slot = await limits.encodeStakeLimitSlot(0, 0, 0, 0) + const slot = await limits.setStorageStakeLimitStruct(0, 0, 0, 0) assertBn(slot, 0) - const decodedSlot = await limits.decodeStakeLimitSlot(slot) - assertBn(decodedSlot.maxStakeLimit, 0) - assertBn(decodedSlot.stakeLimitIncPerBlock, 0) - assertBn(decodedSlot.prevStakeLimit, 0) + const decodedSlot = await limits.getStorageStakeLimit(slot) assertBn(decodedSlot.prevStakeBlockNumber, 0) - }) - - it('check 0 slot', async () => { - const slot = 0 - assertBn(slot, 0) - - const decodedSlot = await limits.decodeStakeLimitSlot(slot) - assertBn(decodedSlot.maxStakeLimit, 0) - assertBn(decodedSlot.stakeLimitIncPerBlock, 0) assertBn(decodedSlot.prevStakeLimit, 0) - assertBn(decodedSlot.prevStakeBlockNumber, 0) + assertBn(decodedSlot.maxStakeLimitGrowthBlocks, 0) + assertBn(decodedSlot.maxStakeLimit, 0) }) - it('check staking pause', async () => { - const slot = 0 + it('check staking pause at start', async () => { + const slot = await limits.setStorageStakeLimitStruct(0, 0, 0, 0) const paused = await limits.isStakingPaused(slot) - assert.equal(paused, true, 'limits not paused') + assert.equal(paused, true, 'staking not paused') + }) - const maxStakeLimit = 10 - const slot2 = await limits.encodeStakeLimitSlot(maxStakeLimit, 0, 0, 0) + it('check staking pause with block number', async () => { + const prevStakeBlockNumber = 10 + const slot2 = await limits.setStorageStakeLimitStruct(prevStakeBlockNumber, 0, 0, 0) const paused2 = await limits.isStakingPaused(slot2) - assert.equal(paused2, false, 'limits not limited') + assert.equal(paused2, false, 'staking paused') + }) + + it('check staking pause/unpause', async () => { + let paused + const slot = await limits.setStorageStakeLimitStruct(1, 1, 1, 1) + paused = await limits.isStakingPaused(slot) + assert.equal(paused, false, 'staking paused') + + const slot2 = await limits.setStakeLimitPauseState(slot, true) + paused = await limits.isStakingPaused(slot2) + assert.equal(paused, true, 'staking not paused') + + const slot3 = await limits.setStakeLimitPauseState(slot, false) + paused = await limits.isStakingPaused(slot3) + assert.equal(paused, false, 'staking paused') }) it('check staking rate limit', async () => { - const slot = 0 - const limited = await limits.isStakingLimitApplied(slot) + let limited + const slot = await limits.setStorageStakeLimitStruct(0, 0, 0, 0) + limited = await limits.isStakingLimitSet(slot) - assert.equal(limited, false, 'limits not limited') + assert.equal(limited, false, 'limits are limited') const maxStakeLimit = 10 - const slot2 = await limits.encodeStakeLimitSlot(maxStakeLimit, 0, 0, 0) - const limited2 = await limits.isStakingLimitApplied(slot2) + const slot2 = await limits.setStorageStakeLimitStruct(0, 0, 0, maxStakeLimit) + limited = await limits.isStakingLimitSet(slot2) + assert.equal(limited, true, 'limits are not limited') - assert.equal(limited2, true, 'limits not limited') + const slot3 = await limits.removeStakingLimit(slot2) + limited = await limits.isStakingLimitSet(slot3) + assert.equal(limited, false, 'limits are limited') }) it('stake limit increase > max stake', async () => { - await limits.encodeStakeLimitSlot(5, 0, 0, 0) - await limits.encodeStakeLimitSlot(5, 5, 0, 0) - - assertRevert(limits.encodeStakeLimitSlot(5, 6, 0, 0), `TOO_LARGE_LIMIT_INCREASE`) + let maxStakeLimit = 5 + let maxStakeLimitIncreasePerBlock = 0 + const slot = await limits.setStorageStakeLimitStruct(0, 0, 0, 0) + await limits.setStakingLimit(slot, maxStakeLimit, maxStakeLimitIncreasePerBlock) + + maxStakeLimit = 5 + maxStakeLimitIncreasePerBlock = 5 + await limits.setStakingLimit(slot, maxStakeLimit, maxStakeLimitIncreasePerBlock) + + maxStakeLimit = 5 + maxStakeLimitGrowthBlocks = 6 + assertRevert(limits.setStakingLimit(slot, maxStakeLimit, maxStakeLimitGrowthBlocks), 'TOO_LARGE_LIMIT_INCREASE') }) it('stake limit reverts on large values', async () => { - assertRevert(limits.encodeStakeLimitSlot(toBN(2).pow(toBN(96)), 1, 1, 1), `TOO_LARGE_MAX_STAKE_LIMIT`) - assertRevert(limits.encodeStakeLimitSlot(1, 1, toBN(2).pow(toBN(96), 1), 1), `TOO_LARGE_PREV_STAKE_LIMIT`) - assertRevert(limits.encodeStakeLimitSlot(1, 1, 1, toBN(2).pow(toBN(32), 1)), `TOO_LARGE_BLOCK_NUMBER`) + let maxStakeLimit = toBN(2).pow(toBN(96)) + let maxStakeLimitIncreasePerBlock = 1 + const slot = await limits.setStorageStakeLimitStruct(0, 0, 0, 0) + assertRevert(limits.setStakingLimit(slot, maxStakeLimit, maxStakeLimitIncreasePerBlock), 'TOO_LARGE_MAX_STAKE_LIMIT') + + maxStakeLimit = toBN(2).mul(toBN(10).pow(toBN(18))) + maxStakeLimitIncreasePerBlock = toBN(10) + assertRevert(limits.setStakingLimit(slot, maxStakeLimit, maxStakeLimitIncreasePerBlock), `TOO_SMALL_LIMIT_INCREASE`) }) it('check update calculate stake limit with different blocks', async () => { const block = await web3.eth.getBlock('latest') - const slot = await limits.encodeStakeLimitSlot(100, 50, 0, block.number) + const maxStakeLimit = 100 + const increasePerBlock = 50 + const maxStakeLimitGrowthBlocks = maxStakeLimit / increasePerBlock - const currentStakeLimit = await limits.calculateCurrentStakeLimit(slot) - assertBn(currentStakeLimit, 0) + const slot = await limits.setStorageStakeLimitStruct(block.number, 0, maxStakeLimitGrowthBlocks, maxStakeLimit) + + const currentStakeLimit2 = await limits.calculateCurrentStakeLimit(slot) + assertBn(currentStakeLimit2, 0) const block2 = await waitBlocks(1) assert.equal(block2.number, block.number + 1) - const currentStakeLimit2 = await limits.calculateCurrentStakeLimit(slot) - assertBn(currentStakeLimit2, 50) + const currentStakeLimit3 = await limits.calculateCurrentStakeLimit(slot) + assertBn(currentStakeLimit3, 50) const block3 = await waitBlocks(3) assert.equal(block3.number, block.number + 1 + 3) - const currentStakeLimit3 = await limits.calculateCurrentStakeLimit(slot) - assertBn(currentStakeLimit3, 100) + const currentStakeLimit4 = await limits.calculateCurrentStakeLimit(slot) + assertBn(currentStakeLimit4, 100) }) it('check update stake limit', async () => { - const maxLimit = 100 - const incPerBlock = 50 const block = await web3.eth.getBlock('latest') - const slot = await limits.encodeStakeLimitSlot(maxLimit, incPerBlock, 0, block.number) - const decodedSlot = await limits.decodeStakeLimitSlot(slot) + const maxStakeLimit = 100 + const increasePerBlock = 50 + const maxStakeLimitGrowthBlocks = maxStakeLimit / increasePerBlock + + const slot = await limits.setStorageStakeLimitStruct(block.number, 0, maxStakeLimitGrowthBlocks, maxStakeLimit) + const decodedSlot = await limits.getStorageStakeLimit(slot) assert.equal(decodedSlot.prevStakeBlockNumber, block.number) assert.equal(decodedSlot.prevStakeLimit, 0) @@ -131,81 +160,51 @@ contract.skip('StakingLimits', () => { assert.equal(block2.number, block.number + 3) const currentStakeLimit2 = await limits.calculateCurrentStakeLimit(slot) - assertBn(currentStakeLimit2, maxLimit) + assertBn(currentStakeLimit2, maxStakeLimit) const deposit = 87 const newSlot = await limits.updatePrevStakeLimit(slot, currentStakeLimit2 - deposit) - const decodedNewSlot = await limits.decodeStakeLimitSlot(newSlot) + const decodedNewSlot = await limits.getStorageStakeLimit(newSlot) assert.equal(decodedNewSlot.prevStakeBlockNumber, block2.number) assert.equal(decodedNewSlot.prevStakeLimit, 13) // checking staking recovery await waitBlocks(1) const currentStakeLimit3 = await limits.calculateCurrentStakeLimit(newSlot) - assertBn(currentStakeLimit3, 13 + incPerBlock) + assertBn(currentStakeLimit3, 13 + increasePerBlock) await waitBlocks(1) const currentStakeLimit4 = await limits.calculateCurrentStakeLimit(newSlot) - assertBn(currentStakeLimit4, maxLimit) + assertBn(currentStakeLimit4, maxStakeLimit) }) it('max values', async () => { const block = await web3.eth.getBlock('latest') - const maxLimit = toBN(2).pow(toBN(96)).sub(toBN(1)) // uint96 - let minIncPerBlock = 1 // uint96 - const maxPrevStakeLimit = toBN(2).pow(toBN(96)).sub(toBN(1)) // uint96 - const maxBlock = toBN(2).pow(toBN(32)).sub(toBN(1)) // uint32 - assertRevert(limits.encodeStakeLimitSlot(maxLimit, minIncPerBlock, maxPrevStakeLimit, maxBlock), `TOO_SMALL_LIMIT_INCREASE`) + const max32 = toBN(2).pow(toBN(32)).sub(toBN(1)) // uint32 + const max96 = toBN(2).pow(toBN(96)).sub(toBN(1)) // uint96 - minIncPerBlock = maxLimit.div(toBN(2).pow(toBN(32)).sub(toBN(1))) + const maxStakeLimit = max96 // uint96 + const maxStakeLimitGrowthBlocks = max32 + const maxPrevStakeLimit = max96 // uint96 + const maxBlock = max32 // uint32 - minIncPerBlockForRevert = minIncPerBlock.div(toBN(2)) // reverts - assertRevert(limits.encodeStakeLimitSlot(maxLimit, minIncPerBlockForRevert, maxPrevStakeLimit, maxBlock), `TOO_SMALL_LIMIT_INCREASE`) + // check that we CAN set max value - const maxSlot = await limits.encodeStakeLimitSlot(maxLimit, minIncPerBlock, maxPrevStakeLimit, maxBlock) + const maxSlot = await limits.setStorageStakeLimitStruct(maxBlock, maxPrevStakeLimit, maxStakeLimitGrowthBlocks, maxStakeLimit) const maxUint256 = toBN(2).pow(toBN(256)).sub(toBN(1)) assertBn(maxSlot, maxUint256) - const decodedRaw = await limits.decodeStakeLimitSlot(maxSlot) - - // console.log(decodedRaw) + const decodedRaw = await limits.getStorageStakeLimit(maxSlot) - const maxStakeLimit = decodedRaw.maxStakeLimit - const stakeLimitIncPerBlock = decodedRaw.stakeLimitIncPerBlock - const prevStakeLimit = decodedRaw.prevStakeLimit - const prevStakeBlockNumber = decodedRaw.prevStakeBlockNumber + const decodedMaxLimit = decodedRaw.maxStakeLimit + const decodedMaxStakeLimitGrowthBlocks = decodedRaw.maxStakeLimitGrowthBlocks + const decodedPrevStakeLimit = decodedRaw.prevStakeLimit + const decodedPrevStakeBlockNumber = decodedRaw.prevStakeBlockNumber - const growthBlock = maxSlot.shrn(128) - // console.log(maxSlot.toString(2)) - console.log({ - stakeLimitIncPerBlock: stakeLimitIncPerBlock.toString(), - maxBlock: maxBlock.toString(), - maxStakeLimit: maxStakeLimit.toString(), - growthBlock: growthBlock.toTwos(32).toString(2) - }) - - assertBn(maxStakeLimit, maxLimit) - assertBn(stakeLimitIncPerBlock, minIncPerBlock) - assertBn(prevStakeLimit, maxPrevStakeLimit) - assertBn(prevStakeBlockNumber, maxBlock) + assertBn(decodedMaxLimit, max96) + assertBn(decodedMaxStakeLimitGrowthBlocks, max32) + assertBn(decodedPrevStakeLimit, max96) + assertBn(decodedPrevStakeBlockNumber, max32) }) }) - -function pad32(num) { - return pz(num, 32) -} - -function pad96(num) { - return pz(num, 96) -} - -function pad256(num) { - return pz(num, 256) -} - -function pz(num, size) { - var s = num + '' - while (s.length < size) s = '0' + s - return s -} diff --git a/test/0.4.24/steth.test.js b/test/0.4.24/steth.test.js index 4ccf0455e..be35c12e0 100644 --- a/test/0.4.24/steth.test.js +++ b/test/0.4.24/steth.test.js @@ -370,7 +370,14 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { it('burning zero value works', async () => { const receipt = await stEth.burnShares(user1, tokens(0)) - assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: tokens(0), sharesAmount: tokens(0) } }) + assertEvent(receipt, 'SharesBurnt', { + expectedArgs: { + account: user1, + preRebaseTokenAmount: tokens(0), + postRebaseTokenAmount: tokens(0), + sharesAmount: tokens(0) + } + }) assertBn(await stEth.totalSupply(), tokens(300)) assertBn(await stEth.balanceOf(user1), tokens(100)) @@ -392,9 +399,17 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { totalSupply.mul(totalShares.sub(user1Shares)).div(totalSupply.sub(user1Balance).add(bn(tokens(10)))) ) - const expectedAmount = await stEth.getPooledEthByShares(sharesToBurn) + const expectedPreTokenAmount = await stEth.getPooledEthByShares(sharesToBurn) const receipt = await stEth.burnShares(user1, sharesToBurn) - assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: sharesToBurn } }) + const expectedPostTokenAmount = await stEth.getPooledEthByShares(sharesToBurn) + assertEvent(receipt, 'SharesBurnt', { + expectedArgs: { + account: user1, + preRebaseTokenAmount: expectedPreTokenAmount, + postRebaseTokenAmount: expectedPostTokenAmount, + sharesAmount: sharesToBurn + } + }) assertBn(await stEth.totalSupply(), tokens(300)) assertBn(await stEth.balanceOf(user1), bn(tokens(90)).subn(1)) // expected round error @@ -418,9 +433,17 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { totalSupply.mul(totalShares.sub(user1Shares)).div(totalSupply.sub(user1Balance).add(bn(tokens(50)))) ) - const expectedAmount = await stEth.getPooledEthByShares(sharesToBurn) + const expectedPreTokenAmount = await stEth.getPooledEthByShares(sharesToBurn) const receipt = await stEth.burnShares(user1, sharesToBurn) - assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: sharesToBurn } }) + const expectedPostTokenAmount = await stEth.getPooledEthByShares(sharesToBurn) + assertEvent(receipt, 'SharesBurnt', { + expectedArgs: { + account: user1, + preRebaseTokenAmount: expectedPreTokenAmount, + postRebaseTokenAmount: expectedPostTokenAmount, + sharesAmount: sharesToBurn + } + }) assertBn(await stEth.balanceOf(user1), tokens(50)) diff --git a/test/deposit.test.js b/test/deposit.test.js index 295acb8ad..4e0f1f717 100644 --- a/test/deposit.test.js +++ b/test/deposit.test.js @@ -88,7 +88,7 @@ contract('Lido with official deposit contract', ([appManager, voting, user1, use from: appManager }) await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.STAKING_RESUME_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.STAKING_CONTROL_ROLE(), appManager, { from: appManager }) // await acl.createPermission(app.address, token.address, await token.MINT_ROLE(), appManager, { from: appManager }) // await acl.createPermission(app.address, token.address, await token.BURN_ROLE(), appManager, { from: appManager }) diff --git a/test/scenario/execution_layer_rewards_after_the_merge.js b/test/scenario/execution_layer_rewards_after_the_merge.js index 3cfcd7a0c..d48fe46f0 100644 --- a/test/scenario/execution_layer_rewards_after_the_merge.js +++ b/test/scenario/execution_layer_rewards_after_the_merge.js @@ -764,7 +764,7 @@ contract('Lido: merge acceptance', (addresses) => { let lastBeaconBalance = toE18(85) await pool.setELRewardsWithdrawalLimit(getELRewardsWithdrawalLimitFromEpoch(epoch), { from: voting }) - let elRewardsWithdrawalLimitPoints = toNum(await pool.getELRewardsWithdrawalLimitPoints()) + let elRewardsWithdrawalLimitPoints = toNum(await pool.getELRewardsWithdrawalLimit()) let elRewardsVaultBalance = toNum(await web3.eth.getBalance(elRewardsVault.address)) let totalPooledEther = toNum(await pool.getTotalPooledEther()) let bufferedEther = toNum(await pool.getBufferedEther()) @@ -776,7 +776,7 @@ contract('Lido: merge acceptance', (addresses) => { while (elRewardsVaultBalance > 0) { const elRewardsWithdrawalLimit = getELRewardsWithdrawalLimitFromEpoch(epoch) await pool.setELRewardsWithdrawalLimit(elRewardsWithdrawalLimit, { from: voting }) - elRewardsWithdrawalLimitPoints = toNum(await pool.getELRewardsWithdrawalLimitPoints()) + elRewardsWithdrawalLimitPoints = toNum(await pool.getELRewardsWithdrawalLimit()) const maxELRewardsAmountPerWithdrawal = Math.floor( ((totalPooledEther + beaconBalanceInc) * elRewardsWithdrawalLimitPoints) / TOTAL_BASIS_POINTS diff --git a/test/scenario/helpers/deploy.js b/test/scenario/helpers/deploy.js index ba809d5b7..b6334e503 100644 --- a/test/scenario/helpers/deploy.js +++ b/test/scenario/helpers/deploy.js @@ -73,7 +73,7 @@ async function deployDaoAndPool(appManager, voting) { POOL_BURN_ROLE, DEPOSIT_ROLE, STAKING_PAUSE_ROLE, - STAKING_RESUME_ROLE, + STAKING_CONTROL_ROLE, SET_EL_REWARDS_VAULT_ROLE, SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE, NODE_OPERATOR_REGISTRY_MANAGE_SIGNING_KEYS, @@ -91,7 +91,7 @@ async function deployDaoAndPool(appManager, voting) { pool.BURN_ROLE(), pool.DEPOSIT_ROLE(), pool.STAKING_PAUSE_ROLE(), - pool.STAKING_RESUME_ROLE(), + pool.STAKING_CONTROL_ROLE(), pool.SET_EL_REWARDS_VAULT_ROLE(), pool.SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE(), nodeOperatorRegistry.MANAGE_SIGNING_KEYS(), @@ -111,7 +111,7 @@ async function deployDaoAndPool(appManager, voting) { acl.createPermission(voting, pool.address, POOL_MANAGE_WITHDRAWAL_KEY, appManager, { from: appManager }), acl.createPermission(voting, pool.address, POOL_BURN_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, STAKING_PAUSE_ROLE, appManager, { from: appManager }), - acl.createPermission(voting, pool.address, STAKING_RESUME_ROLE, appManager, { from: appManager }), + acl.createPermission(voting, pool.address, STAKING_CONTROL_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, SET_EL_REWARDS_VAULT_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE, appManager, { from: appManager }),