diff --git a/.gitignore b/.gitignore index ae4edcfc0..239264963 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,7 @@ cli/vendor # OS relative .DS_Store + +# foundry artifacts +foundry/cache +foundry/out \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..0c2994f67 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "foundry/lib/forge-std"] + path = foundry/lib/forge-std + url = https://github.com/foundry-rs/forge-std + branch = v1.3.0 diff --git a/README.md b/README.md index ce9f98e40..edec88af6 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ The contract also works as a wrapper that accepts stETH tokens and mints wstETH * docker * node.js v12 * (optional) Lerna +* (optional) Foundry ### Installing Aragon & other deps @@ -239,6 +240,14 @@ so full branch coverage will never be reported until [solidity-coverage#219]: https://github.com/sc-forks/solidity-coverage/issues/269 +Run fuzzing tests with foundry: + +```bash +curl -L https://foundry.paradigm.xyz | bash +foundryup +forge test +``` + ## Deploying We have several ways to deploy lido smart-contracts and run DAO locally, you can find documents here: diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index b15c723ae..47061b896 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -81,7 +81,7 @@ interface IStakingRouter { uint256 _maxDepositsCount, uint256 _stakingModuleId, bytes _depositCalldata - ) external payable returns (uint256); + ) external payable; function getStakingRewardsDistribution() external @@ -101,6 +101,11 @@ interface IStakingRouter { function getTotalFeeE4Precision() external view returns (uint16 totalFee); function getStakingFeeAggregateDistributionE4Precision() external view returns (uint16 modulesFee, uint16 treasuryFee); + + function getStakingModuleMaxDepositsCount(uint256 _stakingModuleId, uint256 _depositableEther) + external + view + returns (uint256); } interface IWithdrawalQueue { @@ -160,7 +165,8 @@ contract Lido is Versioned, StETHPermit, AragonApp { uint256 private constant DEPOSIT_SIZE = 32 ether; uint256 public constant TOTAL_BASIS_POINTS = 10000; - /// @dev special value for the last finalizable withdrawal request id + /// @dev special value to not finalize withdrawal requests + /// see the `_lastFinalizableRequestId` arg for `handleOracleReport()` uint256 private constant DONT_FINALIZE_WITHDRAWALS = 0; /// @dev storage slot position for the Lido protocol contracts locator @@ -244,18 +250,25 @@ contract Lido is Versioned, StETHPermit, AragonApp { // The `amount` of ether was sent to the deposit_contract.deposit function event Unbuffered(uint256 amount); - // The amount of ETH sent from StakingRouter contract to Lido contract when deposit called - event StakingRouterDepositRemainderReceived(uint256 amount); - /** * @dev As AragonApp, Lido contract must be initialized with following variables: * NB: by default, staking and the whole Lido pool are in paused state + * + * The contract's balance must be non-zero to allow initial holder bootstrap. + * * @param _lidoLocator lido locator contract * @param _eip712StETH eip712 helper contract for StETH */ function initialize(address _lidoLocator, address _eip712StETH) - public onlyInit + public + payable + onlyInit { + uint256 amount = _bootstrapInitialHolder(); + BUFFERED_ETHER_POSITION.setStorageUint256(amount); + + emit Submitted(INITIAL_TOKEN_HOLDER, amount, 0); + _initialize_v2(_lidoLocator, _eip712StETH); initialized(); } @@ -284,15 +297,19 @@ contract Lido is Versioned, StETHPermit, AragonApp { * @notice A function to finalize upgrade to v2 (from v1). Can be called only once * @dev Value "1" in CONTRACT_VERSION_POSITION is skipped due to change in numbering * + * The initial protocol token holder must exist. + * * For more details see https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-10.md */ function finalizeUpgrade_v2(address _lidoLocator, address _eip712StETH) external { - require(hasInitialized(), "NOT_INITIALIZED"); _checkContractVersion(0); + require(hasInitialized(), "NOT_INITIALIZED"); require(_lidoLocator != address(0), "LIDO_LOCATOR_ZERO_ADDRESS"); require(_eip712StETH != address(0), "EIP712_STETH_ZERO_ADDRESS"); + require(_sharesOf(INITIAL_TOKEN_HOLDER) != 0, "INITIAL_HOLDER_EXISTS"); + _initialize_v2(_lidoLocator, _eip712StETH); } @@ -432,7 +449,7 @@ contract Lido is Versioned, StETHPermit, AragonApp { * accepts payments of any size. Submitted Ethers are stored in Buffer until someone calls * deposit() and pushes them to the Ethereum Deposit contract. */ - // solhint-disable-next-line + // solhint-disable-next-line no-complex-fallback function() external payable { // protection against accidental submissions by calling non-existent function require(msg.data.length == 0, "NON_EMPTY_DATA"); @@ -472,17 +489,6 @@ contract Lido is Versioned, StETHPermit, AragonApp { emit WithdrawalsReceived(msg.value); } - /** - * @notice A payable function for staking router deposits remainder. Can be called only by `StakingRouter` - * @dev We need a dedicated function because funds received by the default payable function - * are treated as a user deposit - */ - function receiveStakingRouterDepositRemainder() external payable { - require(msg.sender == getLidoLocator().stakingRouter()); - - emit StakingRouterDepositRemainderReceived(msg.value); - } - /** * @notice Stop pool routine operations */ @@ -550,10 +556,9 @@ contract Lido is Versioned, StETHPermit, AragonApp { * @param _lastFinalizableRequestId right boundary of requestId range if equals 0, no requests should be finalized * @param _simulatedShareRate share rate that was simulated by oracle when the report data created (1e27 precision) * - * NB: `_simulatedShareRate` should be calculated by the Oracle daemon - * invoking the method with static call and passing `_lastFinalizableRequestId` == `_simulatedShareRate` == 0 - * plugging the returned values to the following formula: - * `_simulatedShareRate = (postTotalPooledEther * 1e27) / postTotalShares` + * NB: `_simulatedShareRate` should be calculated off-chain by calling the method with `eth_call` JSON-RPC API + * while passing `_lastFinalizableRequestId` == `_simulatedShareRate` == 0, and plugging the returned values + * to the following formula: `_simulatedShareRate = (postTotalPooledEther * 1e27) / postTotalShares` * * @return postTotalPooledEther amount of ether in the protocol after report * @return postTotalShares amount of shares in the protocol after report @@ -677,14 +682,10 @@ contract Lido is Versioned, StETHPermit, AragonApp { * @dev Returns depositable ether amount. * Takes into account unfinalized stETH required by WithdrawalQueue */ - function getDepositableEther() public view returns (uint256 depositableEth) { - uint256 bufferedEth = _getBufferedEther(); + function getDepositableEther() public view returns (uint256) { + uint256 bufferedEther = _getBufferedEther(); uint256 withdrawalReserve = IWithdrawalQueue(getLidoLocator().withdrawalQueue()).unfinalizedStETH(); - - if (bufferedEth > withdrawalReserve) { - bufferedEth -= withdrawalReserve; - depositableEth = bufferedEth.div(DEPOSIT_SIZE).mul(DEPOSIT_SIZE); - } + return bufferedEther > withdrawalReserve ? bufferedEther - withdrawalReserve : 0; } /** @@ -697,36 +698,29 @@ contract Lido is Versioned, StETHPermit, AragonApp { ILidoLocator locator = getLidoLocator(); require(msg.sender == locator.depositSecurityModule(), "APP_AUTH_DSM_FAILED"); - require(_stakingModuleId <= uint24(-1), "STAKING_MODULE_ID_TOO_LARGE"); require(canDeposit(), "CAN_NOT_DEPOSIT"); - uint256 depositableEth = getDepositableEther(); - - if (depositableEth > 0) { - /// available ether amount for deposits (multiple of 32eth) - depositableEth = Math256.min(depositableEth, _maxDepositsCount.mul(DEPOSIT_SIZE)); - - uint256 unaccountedEth = _getUnaccountedEther(); - /// @dev transfer ether to SR and make deposit at the same time - /// @notice allow zero value of depositableEth, in this case SR will simply transfer the unaccounted ether to Lido contract - uint256 depositsCount = IStakingRouter(locator.stakingRouter()).deposit.value(depositableEth)( - _maxDepositsCount, - _stakingModuleId, - _depositCalldata - ); - - uint256 depositedAmount = depositsCount.mul(DEPOSIT_SIZE); - assert(depositedAmount <= depositableEth); - - if (depositsCount > 0) { - uint256 newDepositedValidators = DEPOSITED_VALIDATORS_POSITION.getStorageUint256().add(depositsCount); - DEPOSITED_VALIDATORS_POSITION.setStorageUint256(newDepositedValidators); - emit DepositedValidatorsChanged(newDepositedValidators); - - _markAsUnbuffered(depositedAmount); - assert(_getUnaccountedEther() == unaccountedEth); - } - } + IStakingRouter stakingRouter = IStakingRouter(locator.stakingRouter()); + uint256 depositsCount = Math256.min( + _maxDepositsCount, + stakingRouter.getStakingModuleMaxDepositsCount(_stakingModuleId, getDepositableEther()) + ); + if (depositsCount == 0) return; + + uint256 depositsValue = depositsCount.mul(DEPOSIT_SIZE); + /// @dev firstly update the local state of the contract to prevent a reentrancy attack, + /// even if the StakingRouter is a trusted contract. + BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().sub(depositsValue)); + emit Unbuffered(depositsValue); + + uint256 newDepositedValidators = DEPOSITED_VALIDATORS_POSITION.getStorageUint256().add(depositsCount); + DEPOSITED_VALIDATORS_POSITION.setStorageUint256(newDepositedValidators); + emit DepositedValidatorsChanged(newDepositedValidators); + + /// @dev transfer ether to StakingRouter and make a deposit at the same time. All the ether + /// sent to StakingRouter is counted as deposited. If StakingRouter can't deposit all + /// passed ether it MUST revert the whole transaction (never happens in normal circumstances) + stakingRouter.deposit.value(depositsValue)(depositsCount, _stakingModuleId, _depositCalldata); } /// DEPRECATED PUBLIC METHODS @@ -937,14 +931,7 @@ contract Lido is Versioned, StETHPermit, AragonApp { STAKING_STATE_POSITION.setStorageStakeLimitStruct(stakeLimitData.updatePrevStakeLimit(currentStakeLimit - msg.value)); } - uint256 sharesAmount; - if (_getTotalPooledEther() != 0 && _getTotalShares() != 0) { - sharesAmount = getSharesByPooledEth(msg.value); - } else { - // totalPooledEther is 0: for first-ever deposit - // assume that shares correspond to Ether 1-to-1 - sharesAmount = msg.value; - } + uint256 sharesAmount = getSharesByPooledEth(msg.value); _mintShares(msg.sender, sharesAmount); @@ -1095,16 +1082,6 @@ contract Lido is Versioned, StETHPermit, AragonApp { _emitTransferAfterMintingShares(treasury, treasuryReward); } - /** - * @dev Records a deposit to the deposit_contract.deposit function - * @param _amount Total amount deposited to the Consensus Layer side - */ - function _markAsUnbuffered(uint256 _amount) internal { - BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().sub(_amount)); - - emit Unbuffered(_amount); - } - /** * @dev Gets the amount of Ether temporary buffered on this contract balance */ @@ -1112,13 +1089,6 @@ contract Lido is Versioned, StETHPermit, AragonApp { return BUFFERED_ETHER_POSITION.getStorageUint256(); } - /** - * @dev Gets unaccounted (excess) Ether on this contract balance - */ - function _getUnaccountedEther() internal view returns (uint256) { - return address(this).balance.sub(_getBufferedEther()); - } - /// @dev Calculates and returns the total base balance (multiple of 32) of validators in transient state, /// i.e. submitted to the official Deposit contract but not yet visible in the CL state. /// @return transient balance in wei (1e-18 Ether) @@ -1127,7 +1097,7 @@ contract Lido is Versioned, StETHPermit, AragonApp { uint256 clValidators = CL_VALIDATORS_POSITION.getStorageUint256(); // clValidators can never be less than deposited ones. assert(depositedValidators >= clValidators); - return depositedValidators.sub(clValidators).mul(DEPOSIT_SIZE); + return (depositedValidators - clValidators).mul(DEPOSIT_SIZE); } /** @@ -1203,7 +1173,7 @@ contract Lido is Versioned, StETHPermit, AragonApp { * (i.e., postpone the extra rewards to be applied during the next rounds) * 5. Invoke finalization of the withdrawal requests * 6. Distribute protocol fee (treasury & node operators) - * 7. Burn excess shares (withdrawn stETH at least) + * 7. Burn excess shares within the allowed limit (can postpone some shares to be burnt later) * 8. Complete token rebase by informing observers (emit an event and call the external receivers if any) * 9. Sanity check for the provided simulated share rate */ @@ -1288,8 +1258,9 @@ contract Lido is Versioned, StETHPermit, AragonApp { ); // Step 7. - // Burn excess shares (withdrawn stETH at least) - uint256 burntWithdrawalQueueShares = _burnSharesLimited( + // Burn excess shares within the allowed limit (can postpone some shares to be burnt later) + // Return actually burnt shares of the current report's finalized withdrawal requests to use in sanity checks + uint256 burntCurrentWithdrawalShares = _burnSharesLimited( IBurner(_contracts.burner), _contracts.withdrawalQueue, reportContext.sharesToBurnFromWithdrawalQueue, @@ -1313,7 +1284,7 @@ contract Lido is Versioned, StETHPermit, AragonApp { postTotalPooledEther, postTotalShares, reportContext.etherToLockOnWithdrawalQueue, - burntWithdrawalQueueShares, + burntCurrentWithdrawalShares, _reportedData.simulatedShareRate ); } @@ -1377,17 +1348,20 @@ contract Lido is Versioned, StETHPermit, AragonApp { /* * @dev Perform burning of `stETH` shares via the dedicated `Burner` contract. * - * NB: some of the burning amount can be postponed for the next reports - * if positive token rebase smoothened. + * NB: some of the burning amount can be postponed for the next reports if positive token rebase smoothened. + * It's possible that underlying shares of the current oracle report's finalized withdrawals won't be burnt + * completely in a single oracle report round due to the provided `_sharesToBurnLimit` limit * - * @return burnt shares from withdrawals queue (when some requests finalized) + * @return shares actually burnt for the current oracle report's finalized withdrawals + * these shares are assigned to be burnt most recently, so the amount can be calculated deducting + * `postponedSharesToBurn` shares (if any) after the burn commitment & execution */ function _burnSharesLimited( IBurner _burner, address _withdrawalQueue, uint256 _sharesToBurnFromWithdrawalQueue, uint256 _sharesToBurnLimit - ) internal returns (uint256 burntWithdrawalsShares) { + ) internal returns (uint256 burntCurrentWithdrawalShares) { if (_sharesToBurnFromWithdrawalQueue > 0) { _burner.requestBurnShares(_withdrawalQueue, _sharesToBurnFromWithdrawalQueue); } @@ -1403,7 +1377,7 @@ contract Lido is Versioned, StETHPermit, AragonApp { (uint256 coverShares, uint256 nonCoverShares) = _burner.getSharesRequestedToBurn(); uint256 postponedSharesToBurn = coverShares.add(nonCoverShares); - burntWithdrawalsShares = + burntCurrentWithdrawalShares = postponedSharesToBurn < _sharesToBurnFromWithdrawalQueue ? _sharesToBurnFromWithdrawalQueue - postponedSharesToBurn : 0; } diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index 2b78eb9aa..0261fe377 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -50,6 +50,8 @@ contract StETH is IERC20, Pausable { using SafeMath for uint256; using UnstructuredStorage for bytes32; + address constant internal INITIAL_TOKEN_HOLDER = 0xdead; + /** * @dev StETH balances are dynamic and are calculated based on the accounts' shares * and the total amount of Ether controlled by the protocol. Account shares aren't @@ -295,28 +297,18 @@ contract StETH is IERC20, Pausable { * @return the amount of shares that corresponds to `_ethAmount` protocol-controlled Ether. */ function getSharesByPooledEth(uint256 _ethAmount) public view returns (uint256) { - uint256 totalPooledEther = _getTotalPooledEther(); - if (totalPooledEther == 0) { - return 0; - } else { - return _ethAmount - .mul(_getTotalShares()) - .div(totalPooledEther); - } + return _ethAmount + .mul(_getTotalShares()) + .div(_getTotalPooledEther()); } /** * @return the amount of Ether that corresponds to `_sharesAmount` token shares. */ function getPooledEthByShares(uint256 _sharesAmount) public view returns (uint256) { - uint256 totalShares = _getTotalShares(); - if (totalShares == 0) { - return 0; - } else { - return _sharesAmount - .mul(_getTotalPooledEther()) - .div(totalShares); - } + return _sharesAmount + .mul(_getTotalPooledEther()) + .div(_getTotalShares()); } /** @@ -396,7 +388,7 @@ contract StETH is IERC20, Pausable { * * Emits an `Approval` event. * - * NB: the method can be invoken even if the protocol paused. + * NB: the method can be invoked even if the protocol paused. * * Requirements: * @@ -509,4 +501,31 @@ contract StETH is IERC20, Pausable { // We're emitting `SharesBurnt` event to provide an explicit rebase log record nonetheless. } + + /** + * @notice Mints shares on behalf of 0xdead address, + * the shares amount is equal to the contract's balance. * + * + * Allows to get rid of zero checks for `totalShares` and `totalPooledEther` + * and overcome corner cases. + * + * @dev must be invoked before using the token + */ + function _bootstrapInitialHolder() internal returns (uint256) { + uint256 balance = address(this).balance; + require(balance != 0, "EMPTY_INIT_BALANCE"); + + if (_getTotalShares() == 0) { + // if protocol is empty bootstrap it with the contract's balance + // address(0xdead) is a holder for initial shares + _mintShares(INITIAL_TOKEN_HOLDER, balance); + + emit Transfer(0x0, INITIAL_TOKEN_HOLDER, balance); + emit TransferShares(0x0, INITIAL_TOKEN_HOLDER, balance); + + return balance; + } + + return 0; + } } diff --git a/contracts/0.4.24/StETHPermit.sol b/contracts/0.4.24/StETHPermit.sol index 092137285..6b7071746 100644 --- a/contracts/0.4.24/StETHPermit.sol +++ b/contracts/0.4.24/StETHPermit.sol @@ -7,7 +7,7 @@ pragma solidity 0.4.24; import {UnstructuredStorage} from "@aragon/os/contracts/common/UnstructuredStorage.sol"; import {ECDSA} from "../common/lib/ECDSA.sol"; -import {IEIP712} from "../common/interfaces/IEIP712.sol"; +import {IEIP712StETH} from "../common/interfaces/IEIP712StETH.sol"; import {StETH} from "./StETH.sol"; @@ -100,7 +100,7 @@ contract StETHPermit is IERC2612, StETH { abi.encode(PERMIT_TYPEHASH, _owner, _spender, _value, _useNonce(_owner), _deadline) ); - bytes32 hash = IEIP712(_getEIP712StETH()).hashTypedDataV4(structHash); + bytes32 hash = IEIP712StETH(getEIP712StETH()).hashTypedDataV4(address(this), structHash); address signer = ECDSA.recover(hash, _v, _r, _s); require(signer == _owner, "ERC20Permit: invalid signature"); @@ -124,7 +124,26 @@ contract StETHPermit is IERC2612, StETH { */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view returns (bytes32) { - return IEIP712(_getEIP712StETH()).domainSeparatorV4(); + return IEIP712StETH(getEIP712StETH()).domainSeparatorV4(address(this)); + } + + /** + * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712 + * signature. + * + * NB: compairing to the full-fledged ERC-5267 version: + * - `salt` and `extensions` are unused + * - `flags` is hex"0f" or 01111b + * + * @dev using shortened returns to reduce a bytecode size + */ + function eip712Domain() external view returns ( + string memory name, + string memory version, + uint256 chainId, + address verifyingContract + ) { + return IEIP712StETH(getEIP712StETH()).eip712Domain(address(this)); } /** @@ -140,7 +159,7 @@ contract StETHPermit is IERC2612, StETH { */ function _initializeEIP712StETH(address _eip712StETH) internal { require(_eip712StETH != address(0), "StETHPermit: zero eip712StETH"); - require(_getEIP712StETH() == address(0), "StETHPermit: eip712StETH already set"); + require(getEIP712StETH() == address(0), "StETHPermit: eip712StETH already set"); EIP712_STETH_POSITION.setStorageAddress(_eip712StETH); @@ -150,7 +169,7 @@ contract StETHPermit is IERC2612, StETH { /** * @dev Get EIP712 message utils contract */ - function _getEIP712StETH() internal view returns (address) { + function getEIP712StETH() public view returns (address) { return EIP712_STETH_POSITION.getStorageAddress(); } } diff --git a/contracts/0.4.24/lib/Math64.sol b/contracts/0.4.24/lib/Math64.sol deleted file mode 100644 index c95bd919b..000000000 --- a/contracts/0.4.24/lib/Math64.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Lido -// SPDX-License-Identifier: MIT - -// See contracts/COMPILERS.md -pragma solidity 0.4.24; - -library Math64 { - function max(uint64 a, uint64 b) internal pure returns (uint64) { - return a > b ? a : b; - } - - function min(uint64 a, uint64 b) internal pure returns (uint64) { - return a < b ? a : b; - } -} diff --git a/contracts/0.4.24/lib/SigningKeys.sol b/contracts/0.4.24/lib/SigningKeys.sol index b27d3a416..346dbcbea 100644 --- a/contracts/0.4.24/lib/SigningKeys.sol +++ b/contracts/0.4.24/lib/SigningKeys.sol @@ -6,165 +6,174 @@ pragma solidity 0.4.24; import {SafeMath} from "@aragon/os/contracts/lib/math/SafeMath.sol"; import {SafeMath64} from "@aragon/os/contracts/lib/math/SafeMath64.sol"; -import {MemUtils} from "../../common/lib/MemUtils.sol"; +/// @title Library for manage operator keys in storage +/// @author KRogLA library SigningKeys { using SafeMath for uint256; using SafeMath64 for uint64; - using SigningKeys for bytes32; uint64 internal constant PUBKEY_LENGTH = 48; uint64 internal constant SIGNATURE_LENGTH = 96; - uint256 internal constant UINT64_MAX = uint256(~uint64(0)); + uint256 internal constant UINT64_MAX = 0xFFFFFFFFFFFFFFFF; event SigningKeyAdded(uint256 indexed nodeOperatorId, bytes pubkey); event SigningKeyRemoved(uint256 indexed nodeOperatorId, bytes pubkey); - function isKeyEmpty(bytes memory _key) internal pure returns (bool) { - assert(_key.length == PUBKEY_LENGTH); - - uint256 k1; - uint256 k2; - assembly { - k1 := mload(add(_key, 0x20)) - k2 := mload(add(_key, 0x40)) - } - - return 0 == k1 && 0 == (k2 >> ((2 * 32 - PUBKEY_LENGTH) * 8)); - } - - function getKeyOffset(bytes32 _position, uint256 _nodeOperatorId, uint256 _keyIndex) - internal - pure - returns (uint256) - { + function getKeyOffset(bytes32 _position, uint256 _nodeOperatorId, uint256 _keyIndex) internal pure returns (uint256) { return uint256(keccak256(abi.encodePacked(_position, _nodeOperatorId, _keyIndex))); } - function addKeysSigs( + /// @dev store opeartor keys to storage + /// @param _position storage slot + /// @param _nodeOperatorId operator id + /// @param _startIndex start index + /// @param _keysCount keys count to load + /// @param _pubkeys kes buffer to read from + /// @param _signatures signatures buffer to read from + /// @return new total keys count + function saveKeysSigs( bytes32 _position, uint256 _nodeOperatorId, - uint256 _keysCount, uint256 _startIndex, - bytes _publicKeys, + uint256 _keysCount, + bytes _pubkeys, bytes _signatures ) internal returns (uint256) { - require(_keysCount != 0, "NO_KEYS"); - require(_keysCount <= UINT64_MAX, "KEYS_COUNT_TOO_LARGE"); - require(_publicKeys.length == _keysCount.mul(PUBKEY_LENGTH), "INVALID_LENGTH"); - require(_signatures.length == _keysCount.mul(SIGNATURE_LENGTH), "INVALID_LENGTH"); + require(_keysCount > 0 && _startIndex.add(_keysCount - 1) <= UINT64_MAX, "INVALID_KEYS_COUNT"); + require( + _pubkeys.length == _keysCount.mul(PUBKEY_LENGTH) && _signatures.length == _keysCount.mul(SIGNATURE_LENGTH), + "LENGTH_MISMATCH" + ); - (bytes memory key, bytes memory sig) = initKeySig(1); - for (uint256 i = 0; i < _keysCount; ++i) { - MemUtils.copyBytes(_publicKeys, key, i * PUBKEY_LENGTH, 0, PUBKEY_LENGTH); - require(!isKeyEmpty(key), "EMPTY_KEY"); - MemUtils.copyBytes(_signatures, sig, i * SIGNATURE_LENGTH, 0, SIGNATURE_LENGTH); + uint256 curOffset; + bool isEmpty; + bytes memory tmpKey = new bytes(48); - _position.storeKeySig(_nodeOperatorId, _startIndex, key, sig); - _startIndex = _startIndex.add(1); - emit SigningKeyAdded(_nodeOperatorId, key); - } - return _startIndex; - } - - function removeUnusedKeySig(bytes32 _position, uint256 _nodeOperatorId, uint256 _index, uint256 _lastIndex) - internal - returns (uint256) - { - (bytes memory removedKey,) = _position.loadKeySig(_nodeOperatorId, _index); + for (uint256 i; i < _keysCount;) { + curOffset = _position.getKeyOffset(_nodeOperatorId, _startIndex); + assembly { + let _ofs := add(add(_pubkeys, 0x20), mul(i, 48)) //PUBKEY_LENGTH = 48 + let _part1 := mload(_ofs) // bytes 0..31 + let _part2 := mload(add(_ofs, 0x10)) // bytes 16..47 + isEmpty := iszero(or(_part1, _part2)) + mstore(add(tmpKey, 0x30), _part2) // store 2nd part first + mstore(add(tmpKey, 0x20), _part1) // store 1st part with overwrite bytes 16-31 + } - if (_index < _lastIndex) { - (bytes memory key, bytes memory signature) = _position.loadKeySig(_nodeOperatorId, _lastIndex); - _position.storeKeySig(_nodeOperatorId, _index, key, signature); + require(!isEmpty, "EMPTY_KEY"); + assembly { + // store key + sstore(curOffset, mload(add(tmpKey, 0x20))) // store bytes 0..31 + sstore(add(curOffset, 1), shl(128, mload(add(tmpKey, 0x30)))) // store bytes 32..47 + // store signature + let _ofs := add(add(_signatures, 0x20), mul(i, 96)) //SIGNATURE_LENGTH = 96 + sstore(add(curOffset, 2), mload(_ofs)) + sstore(add(curOffset, 3), mload(add(_ofs, 0x20))) + sstore(add(curOffset, 4), mload(add(_ofs, 0x40))) + i := add(i, 1) + _startIndex := add(_startIndex, 1) + } + emit SigningKeyAdded(_nodeOperatorId, tmpKey); } - - _position.deleteKeySig(_nodeOperatorId, _lastIndex); - emit SigningKeyRemoved(_nodeOperatorId, removedKey); - - return _lastIndex; + return _startIndex; } - function storeKeySig( + /// @dev remove opeartor keys from storage + /// @param _position storage slot + /// @param _nodeOperatorId operator id + /// @param _startIndex start index + /// @param _keysCount keys count to load + /// @param _totalKeysCount current total keys count for operator + /// @return new _totalKeysCount + function removeKeysSigs( bytes32 _position, uint256 _nodeOperatorId, - uint256 _keyIndex, - bytes memory _pubkey, - bytes memory _signature - ) internal { - // assert(_pubkey.length == PUBKEY_LENGTH); - // assert(_signature.length == SIGNATURE_LENGTH); - - // key - uint256 offset = _position.getKeyOffset(_nodeOperatorId, _keyIndex); - uint256 keyExcessBits = (2 * 32 - PUBKEY_LENGTH) * 8; - assembly { - sstore(offset, mload(add(_pubkey, 0x20))) - sstore(add(offset, 1), shl(keyExcessBits, shr(keyExcessBits, mload(add(_pubkey, 0x40))))) - } - offset += 2; + uint256 _startIndex, + uint256 _keysCount, + uint256 _totalKeysCount + ) internal returns (uint256) { + require( + _keysCount > 0 && _startIndex.add(_keysCount) <= _totalKeysCount && _totalKeysCount <= UINT64_MAX, + "INVALID_KEYS_COUNT" + ); - // signature - for (uint256 i = 0; i < SIGNATURE_LENGTH; i += 32) { + uint256 curOffset; + uint256 lastOffset; + uint256 j; + bytes memory tmpKey = new bytes(48); + // removing from the last index + for (uint256 i = _startIndex + _keysCount; i > _startIndex;) { + curOffset = _position.getKeyOffset(_nodeOperatorId, i - 1); assembly { - sstore(offset, mload(add(_signature, add(0x20, i)))) + // read key + mstore(add(tmpKey, 0x30), shr(128, sload(add(curOffset, 1)))) // bytes 16..47 + mstore(add(tmpKey, 0x20), sload(curOffset)) // bytes 0..31 + } + if (i < _totalKeysCount) { + lastOffset = _position.getKeyOffset(_nodeOperatorId, _totalKeysCount - 1); + // move last key to deleted key index + for (j = 0; j < 5;) { + assembly { + sstore(add(curOffset, j), sload(add(lastOffset, j))) + j := add(j, 1) + } + } + curOffset = lastOffset; + } + // clear storage + for (j = 0; j < 5;) { + assembly { + sstore(add(curOffset, j), 0) + j := add(j, 1) + } } - offset++; - } - } - - function deleteKeySig(bytes32 _position, uint256 _nodeOperatorId, uint256 _keyIndex) internal { - uint256 offset = _position.getKeyOffset(_nodeOperatorId, _keyIndex); - for (uint256 i = 0; i < (PUBKEY_LENGTH + SIGNATURE_LENGTH) / 32 + 1; ++i) { assembly { - sstore(add(offset, i), 0) + _totalKeysCount := sub(_totalKeysCount, 1) + i := sub(i, 1) } + emit SigningKeyRemoved(_nodeOperatorId, tmpKey); } + return _totalKeysCount; } - function loadKeySigAndAppend( + /// @dev laod opeartor keys from storage + /// @param _position storage slot + /// @param _nodeOperatorId operator id + /// @param _startIndex start index + /// @param _keysCount keys count to load + /// @param _pubkeys preallocated kes buffer to read in + /// @param _signatures preallocated signatures buffer to read in + /// @param _bufOffset start offset in `_pubkeys`/`_signatures` buffer to place values (in number of keys) + function loadKeysSigs( bytes32 _position, uint256 _nodeOperatorId, - uint256 _keyIndex, - uint256 _offset, + uint256 _startIndex, + uint256 _keysCount, bytes memory _pubkeys, - bytes memory _signatures + bytes memory _signatures, + uint256 _bufOffset ) internal view { - (bytes memory pubkey, bytes memory signature) = _position.loadKeySig(_nodeOperatorId, _keyIndex); - MemUtils.copyBytes(pubkey, _pubkeys, _offset.mul(PUBKEY_LENGTH)); - MemUtils.copyBytes(signature, _signatures, _offset.mul(SIGNATURE_LENGTH)); - } - - function loadKeySig(bytes32 _position, uint256 _nodeOperatorId, uint256 _keyIndex) - internal - view - returns (bytes memory pubkey, bytes memory signature) - { - uint256 offset = _position.getKeyOffset(_nodeOperatorId, _keyIndex); - - // key - bytes memory tmpKey = MemUtils.unsafeAllocateBytes(64); - assembly { - mstore(add(tmpKey, 0x20), sload(offset)) - mstore(add(tmpKey, 0x40), sload(add(offset, 1))) - } - offset += 2; - pubkey = MemUtils.unsafeAllocateBytes(PUBKEY_LENGTH); - MemUtils.copyBytes(tmpKey, pubkey, 0, 0, PUBKEY_LENGTH); - // signature - signature = MemUtils.unsafeAllocateBytes(SIGNATURE_LENGTH); - for (uint256 i = 0; i < SIGNATURE_LENGTH; i += 32) { + uint256 curOffset; + for (uint256 i; i < _keysCount;) { + curOffset = _position.getKeyOffset(_nodeOperatorId, _startIndex + i); assembly { - mstore(add(signature, add(0x20, i)), sload(offset)) + // read key + let _ofs := add(add(_pubkeys, 0x20), mul(add(_bufOffset, i), 48)) //PUBKEY_LENGTH = 48 + mstore(add(_ofs, 0x10), shr(128, sload(add(curOffset, 1)))) // bytes 16..47 + mstore(_ofs, sload(curOffset)) // bytes 0..31 + // store signature + _ofs := add(add(_signatures, 0x20), mul(add(_bufOffset, i), 96)) //SIGNATURE_LENGTH = 96 + mstore(_ofs, sload(add(curOffset, 2))) + mstore(add(_ofs, 0x20), sload(add(curOffset, 3))) + mstore(add(_ofs, 0x40), sload(add(curOffset, 4))) + i := add(i, 1) } - offset++; } } - function initKeySig(uint256 _count) internal pure returns (bytes memory, bytes memory) { - return ( - MemUtils.unsafeAllocateBytes(_count.mul(PUBKEY_LENGTH)), - MemUtils.unsafeAllocateBytes(_count.mul(SIGNATURE_LENGTH)) - ); + function initKeysSigsBuf(uint256 _count) internal pure returns (bytes memory, bytes memory) { + return (new bytes(_count.mul(PUBKEY_LENGTH)), new bytes(_count.mul(SIGNATURE_LENGTH))); } } diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index c077181fc..b1c8b4eaf 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -9,9 +9,7 @@ import {SafeMath} from "@aragon/os/contracts/lib/math/SafeMath.sol"; import {SafeMath64} from "@aragon/os/contracts/lib/math/SafeMath64.sol"; import {UnstructuredStorage} from "@aragon/os/contracts/common/UnstructuredStorage.sol"; -import {Math64} from "../lib/Math64.sol"; import {Math256} from "../../common/lib/Math256.sol"; -import {MemUtils} from "../../common/lib/MemUtils.sol"; import {MinFirstAllocationStrategy} from "../../common/lib/MinFirstAllocationStrategy.sol"; import {ILidoLocator} from "../../common/interfaces/ILidoLocator.sol"; import {IBurner} from "../../common/interfaces/IBurner.sol"; @@ -46,7 +44,6 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { event NodeOperatorRewardAddressSet(uint256 indexed nodeOperatorId, address rewardAddress); event NodeOperatorTotalKeysTrimmed(uint256 indexed nodeOperatorId, uint64 totalKeysTrimmed); event KeysOpIndexSet(uint256 keysOpIndex); - event ContractVersionSet(uint256 version); event StakingModuleTypeSet(bytes32 moduleType); event RewardsDistributed(address indexed rewardAddress, uint256 sharesAmount); event LocatorContractSet(address locatorAddress); @@ -56,8 +53,13 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { event TotalSigningKeysCountChanged(uint256 indexed nodeOperatorId, uint256 totalValidatorsCount); event NonceChanged(uint256 nonce); - event StuckValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 stuckValidatorsCount); - event RefundedValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 RefundedValidatorsCount); + event StuckPenaltyDelayChanged(uint256 stuckPenaltyDelay); + event StuckPenaltyStateChanged( + uint256 indexed nodeOperatorId, + uint256 stuckValidatorsCount, + uint256 refundedValidatorsCount, + uint256 stuckPenaltyEndTimestamp + ); event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetValidatorsCount); event NodeOperatorPenalized(address indexed recipientAddress, uint256 sharesPenalizedAmount); @@ -82,6 +84,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { uint256 internal constant UINT64_MAX = 0xFFFFFFFFFFFFFFFF; // SigningKeysStats + /// @dev Operator's max validator keys count approved for deposit by the DAO uint8 internal constant VETTED_KEYS_COUNT_OFFSET = 0; /// @dev Number of keys in the EXITED state for this operator for all time uint8 internal constant EXITED_KEYS_COUNT_OFFSET = 1; @@ -91,16 +94,13 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { uint8 internal constant DEPOSITED_KEYS_COUNT_OFFSET = 3; // TargetValidatorsStats - /// @dev DAO target limit, used to check how many keys should go to exit - /// UINT64_MAX - unlimited - /// 0 - all deposited keys - /// N < deposited keys - (deposited-N) keys - /// deposited < N < vetted - use (N-deposited) as available + /// @dev Flag enable/disable limiting target active validators count for operator uint8 internal constant IS_TARGET_LIMIT_ACTIVE_OFFSET = 0; - /// @dev relative target active validators limit for operator, set by DAO, UINT64_MAX === 'no limit' - /// @notice stores value +1 based, so 0 is means target count is unlimited (i.e. = -1), - /// and 1 is means target count = 0 (i.e. all validators should be exited) + /// @dev relative target active validators limit for operator, set by DAO + /// @notice used to check how many keys should go to exit, 0 - means all deposited keys would be exited uint8 internal constant TARGET_VALIDATORS_COUNT_OFFSET = 1; + /// @dev actual operators's number of keys which could be deposited + uint8 internal constant MAX_VALIDATORS_COUNT_OFFSET = 2; // StuckPenaltyStats /// @dev stuck keys count from oracle report @@ -108,8 +108,22 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { /// @dev refunded keys count from dao uint8 internal constant REFUNDED_VALIDATORS_COUNT_OFFSET = 1; /// @dev extra penalty time after stuck keys resolved (refunded and/or exited) + /// @notice field is also used as flag for "half-cleaned" panlty status + /// Operator is PENALIZED if `STUCK_VALIDATORS_COUNT > REFUNDED_VALIDATORS_COUNT` or + /// `STUCK_VALIDATORS_COUNT <= REFUNDED_VALIDATORS_COUNT && STUCK_PENALTY_END_TIMESTAMP <= refund timastamp + STUCK_PENALTY_DELAY` + /// When operator refund all stuck validators and time has pass STUCK_PENALTY_DELAY, but STUCK_PENALTY_END_TIMESTAMP not zeroed, + /// then Operator can receive reawards but can't get new deposits until the new Oracle report or `clearNodeOperatorPenalty` is called. uint8 internal constant STUCK_PENALTY_END_TIMESTAMP_OFFSET = 2; + // Summary SigningKeysStats + uint8 internal constant SUMMARY_MAX_VALIDATORS_COUNT_OFFSET = 0; + /// @dev Number of keys in the EXITED state for this operator for all time + uint8 internal constant SUMMARY_EXITED_KEYS_COUNT_OFFSET = 1; + /// @dev Total number of keys of this operator for all time + uint8 internal constant SUMMARY_TOTAL_KEYS_COUNT_OFFSET = 2; + /// @dev Number of keys of this operator which were in DEPOSITED state for all time + uint8 internal constant SUMMARY_DEPOSITED_KEYS_COUNT_OFFSET = 3; + // // UNSTRUCTURED STORAGE POSITIONS // @@ -175,70 +189,108 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { Packed64x4.Packed targetValidatorsStats; } + struct NodeOperatorSummary { + Packed64x4.Packed summarySigningKeysStats; + } + // // STORAGE VARIABLES // /// @dev Mapping of all node operators. Mapping is used to be able to extend the struct. mapping(uint256 => NodeOperator) internal _nodeOperators; + NodeOperatorSummary internal _nodeOperatorSummary; // // METHODS // - function initialize(address _locator, bytes32 _type) public onlyInit { + function initialize(address _locator, bytes32 _type, uint256 _stuckPenaltyDelay) public onlyInit { // Initializations for v1 --> v2 - _initialize_v2(_locator, _type); + _initialize_v2(_locator, _type, _stuckPenaltyDelay); initialized(); } /// @notice A function to finalize upgrade to v2 (from v1). Can be called only once /// For more details see https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-10.md - function finalizeUpgrade_v2(address _locator, bytes32 _type) external { - require(hasInitialized() && !isPetrified(), "CONTRACT_NOT_INITIALIZED_OR_PETRIFIED"); + function finalizeUpgrade_v2(address _locator, bytes32 _type, uint256 _stuckPenaltyDelay) external { + require(hasInitialized(), "CONTRACT_NOT_INITIALIZED"); _checkContractVersion(0); - _initialize_v2(_locator, _type); + _initialize_v2(_locator, _type, _stuckPenaltyDelay); uint256 totalOperators = getNodeOperatorsCount(); Packed64x4.Packed memory signingKeysStats; - for (uint256 operatorId; operatorId < totalOperators; ++operatorId) { - signingKeysStats = _loadOperatorSigningKeysStats(operatorId); - uint64 vettedSigningKeysCountBefore = signingKeysStats.get(VETTED_KEYS_COUNT_OFFSET); - uint64 totalSigningKeysCount = signingKeysStats.get(TOTAL_KEYS_COUNT_OFFSET); - uint64 depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); + Packed64x4.Packed memory operatorTargetStats; + Packed64x4.Packed memory summarySigningKeysStats = _loadSummarySigningKeysStats(); + uint64 vettedSigningKeysCountBefore; + uint64 totalSigningKeysCount; + uint64 depositedSigningKeysCount; + for (uint256 nodeOperatorId; nodeOperatorId < totalOperators; ++nodeOperatorId) { + signingKeysStats = _loadOperatorSigningKeysStats(nodeOperatorId); + vettedSigningKeysCountBefore = signingKeysStats.get(VETTED_KEYS_COUNT_OFFSET); + totalSigningKeysCount = signingKeysStats.get(TOTAL_KEYS_COUNT_OFFSET); + depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); uint64 vettedSigningKeysCountAfter; - if (!_nodeOperators[operatorId].active) { + if (!_nodeOperators[nodeOperatorId].active) { // trim vetted signing keys count when node operator is not active vettedSigningKeysCountAfter = depositedSigningKeysCount; } else { - vettedSigningKeysCountAfter = - Math64.min(totalSigningKeysCount, Math64.max(depositedSigningKeysCount, vettedSigningKeysCountBefore)); + vettedSigningKeysCountAfter = uint64( + Math256.min( + totalSigningKeysCount, + Math256.max(uint256(depositedSigningKeysCount), uint256(vettedSigningKeysCountBefore)) + ) + ); } if (vettedSigningKeysCountBefore != vettedSigningKeysCountAfter) { signingKeysStats.set(VETTED_KEYS_COUNT_OFFSET, vettedSigningKeysCountAfter); - _saveOperatorSigningKeysStats(operatorId, signingKeysStats); - emit VettedSigningKeysCountChanged(operatorId, vettedSigningKeysCountAfter); + _saveOperatorSigningKeysStats(nodeOperatorId, signingKeysStats); + emit VettedSigningKeysCountChanged(nodeOperatorId, vettedSigningKeysCountAfter); } + + operatorTargetStats = _loadOperatorTargetValidatorsStats(nodeOperatorId); + operatorTargetStats.set(MAX_VALIDATORS_COUNT_OFFSET, vettedSigningKeysCountAfter); + _saveOperatorTargetValidatorsStats(nodeOperatorId, operatorTargetStats); + + summarySigningKeysStats.set( + SUMMARY_MAX_VALIDATORS_COUNT_OFFSET, + summarySigningKeysStats.get(SUMMARY_MAX_VALIDATORS_COUNT_OFFSET).add(vettedSigningKeysCountAfter) + ); + summarySigningKeysStats.set( + SUMMARY_DEPOSITED_KEYS_COUNT_OFFSET, + summarySigningKeysStats.get(SUMMARY_DEPOSITED_KEYS_COUNT_OFFSET).add(depositedSigningKeysCount) + ); + summarySigningKeysStats.set( + SUMMARY_EXITED_KEYS_COUNT_OFFSET, + summarySigningKeysStats.get(SUMMARY_EXITED_KEYS_COUNT_OFFSET).add( + signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET) + ) + ); + summarySigningKeysStats.set( + SUMMARY_TOTAL_KEYS_COUNT_OFFSET, + summarySigningKeysStats.get(SUMMARY_TOTAL_KEYS_COUNT_OFFSET).add(totalSigningKeysCount) + ); } + _saveSummarySigningKeysStats(summarySigningKeysStats); + _increaseValidatorsKeysNonce(); } - function _initialize_v2(address _locator, bytes32 _type) internal { + function _initialize_v2(address _locator, bytes32 _type, uint256 _stuckPenaltyDelay) internal { _onlyNonZeroAddress(_locator); LIDO_LOCATOR_POSITION.setStorageAddress(_locator); TYPE_POSITION.setStorageBytes32(_type); _setContractVersion(2); - _setStuckPenaltyDelay(2 days); + _setStuckPenaltyDelay(_stuckPenaltyDelay); // set unlimited allowance for burner from staking router // to burn stuck keys penalized shares IStETH(getLocator().lido()).approve(getLocator().burner(), ~uint256(0)); - emit ContractVersionSet(2); emit LocatorContractSet(_locator); emit StakingModuleTypeSet(_type); } @@ -310,6 +362,8 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); emit VettedSigningKeysCountChanged(_nodeOperatorId, depositedSigningKeysCount); + + _updateSummaryMaxValidatorsCount(_nodeOperatorId); } _increaseValidatorsKeysNonce(); } @@ -356,8 +410,11 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { uint64 depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); uint64 totalSigningKeysCount = signingKeysStats.get(TOTAL_KEYS_COUNT_OFFSET); - uint64 vettedSigningKeysCountAfter = - Math64.min(totalSigningKeysCount, Math64.max(_vettedSigningKeysCount, depositedSigningKeysCount)); + uint64 vettedSigningKeysCountAfter = uint64( + Math256.min( + totalSigningKeysCount, Math256.max(uint256(_vettedSigningKeysCount), uint256(depositedSigningKeysCount)) + ) + ); if (vettedSigningKeysCountAfter == vettedSigningKeysCountBefore) { return; @@ -367,43 +424,96 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); emit VettedSigningKeysCountChanged(_nodeOperatorId, vettedSigningKeysCountAfter); + + _updateSummaryMaxValidatorsCount(_nodeOperatorId); _increaseValidatorsKeysNonce(); } /// @notice Called by StakingRouter to signal that stETH rewards were minted for this module. - function handleRewardsMinted(uint256) external view { + function onRewardsMinted(uint256 /* _totalShares */) external view { _auth(STAKING_ROUTER_ROLE); // since we're pushing rewards to operators after exited validators counts are // updated (as opposed to pulling by node ops), we don't need any handling here - // see `onAllValidatorCountersUpdated()` + // see `onExitedAndStuckValidatorsCountsUpdated()` + } + + function _checkReportPayload(uint256 idsLength, uint256 countsLength) internal pure returns (uint256 count) { + count = idsLength / 8; + require(countsLength / 16 == count && idsLength % 8 == 0 && countsLength % 16 == 0, "INVALID_REPORT_DATA"); } /// @notice Called by StakingRouter to update the number of the validators of the given node /// operator that were requested to exit but failed to do so in the max allowed time /// - /// @param _nodeOperatorId Id of the node operator - /// @param _stuckValidatorsCount New number of stuck validators of the node operator - function updateStuckValidatorsCount(uint256 _nodeOperatorId, uint256 _stuckValidatorsCount) external { - _onlyExistedNodeOperator(_nodeOperatorId); + /// @param _nodeOperatorIds bytes packed array of the node operators id + /// @param _stuckValidatorsCounts bytes packed array of the new number of stuck validators for the node operators + function updateStuckValidatorsCount(bytes _nodeOperatorIds, bytes _stuckValidatorsCounts) external { _auth(STAKING_ROUTER_ROLE); + uint256 nodeOperatorsCount = _checkReportPayload(_nodeOperatorIds.length, _stuckValidatorsCounts.length); + uint256 totalNodeOperatorsCount = getNodeOperatorsCount(); - _updateStuckValidatorsCount(_nodeOperatorId, uint64(_stuckValidatorsCount)); + uint256 nodeOperatorId; + uint64 validatorsCount; + uint256 _nodeOperatorIdsOffset; + uint256 _stuckValidatorsCountsOffset; + + /// @dev calldata layout: + /// | func sig (4 bytes) | ABI-enc data | + /// + /// ABI-enc data: + /// + /// | 32 bytes | 32 bytes | 32 bytes | ... | 32 bytes | ...... | + /// | ids len offset | counts len offset | ids len | ids | counts len | counts | + assembly { + _nodeOperatorIdsOffset := add(calldataload(4), 36) // arg1 calldata offset + 4 (signature len) + 32 (length slot) + _stuckValidatorsCountsOffset := add(calldataload(36), 36) // arg2 calldata offset + 4 (signature len) + 32 (length slot)) + } + for (uint256 i; i < nodeOperatorsCount;) { + /// @solidity memory-safe-assembly + assembly { + nodeOperatorId := shr(192, calldataload(add(_nodeOperatorIdsOffset, mul(i, 8)))) + validatorsCount := shr(128, calldataload(add(_stuckValidatorsCountsOffset, mul(i, 16)))) + i := add(i, 1) + } + _requireValidRange(nodeOperatorId < totalNodeOperatorsCount); + _updateStuckValidatorsCount(nodeOperatorId, validatorsCount); + } } /// @notice Called by StakingRouter to update the number of the validators in the EXITED state /// for node operator with given id /// - /// @param _nodeOperatorId Id of the node operator - /// @param _exitedValidatorsCount New number of EXITED validators of the node operator - /// @return Total number of exited validators across all node operators. - function updateExitedValidatorsCount(uint256 _nodeOperatorId, uint256 _exitedValidatorsCount) + /// @param _nodeOperatorIds bytes packed array of the node operators id + /// @param _exitedValidatorsCounts bytes packed array of the new number of EXITED validators for the node operators + function updateExitedValidatorsCount( + bytes _nodeOperatorIds, + bytes _exitedValidatorsCounts + ) external - returns (uint256) { - _onlyExistedNodeOperator(_nodeOperatorId); _auth(STAKING_ROUTER_ROLE); - - _updateExitedValidatorsCount(_nodeOperatorId, uint64(_exitedValidatorsCount), false); + uint256 nodeOperatorsCount = _checkReportPayload(_nodeOperatorIds.length, _exitedValidatorsCounts.length); + uint256 totalNodeOperatorsCount = getNodeOperatorsCount(); + + uint256 nodeOperatorId; + uint64 validatorsCount; + uint256 _nodeOperatorIdsOffset; + uint256 _exitedValidatorsCountsOffset; + /// @dev see comments for `updateStuckValidatorsCount` + assembly { + _nodeOperatorIdsOffset := add(calldataload(4), 36) // arg1 calldata offset + 4 (signature len) + 32 (length slot) + _exitedValidatorsCountsOffset := add(calldataload(36), 36) // arg2 calldata offset + 4 (signature len) + 32 (length slot)) + } + for (uint256 i; i < nodeOperatorsCount;) { + /// @solidity memory-safe-assembly + assembly { + nodeOperatorId := shr(192, calldataload(add(_nodeOperatorIdsOffset, mul(i, 8)))) + validatorsCount := shr(128, calldataload(add(_exitedValidatorsCountsOffset, mul(i, 16)))) + i := add(i, 1) + } + _requireValidRange(nodeOperatorId < totalNodeOperatorsCount); + _updateExitedValidatorsCount(nodeOperatorId, validatorsCount, false); + } } /// @notice Updates the number of the refunded validators for node operator with the given id @@ -416,9 +526,14 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _updateRefundValidatorsKeysCount(_nodeOperatorId, uint64(_refundedValidatorsCount)); } - /// @notice Called by StakingRouter after oracle finishes updating validators counters for all node operators - function onAllValidatorCountersUpdated() external - { + /// @notice Called by StakingRouter after it finishes updating exited and stuck validators + /// counts for this module's node operators. + /// + /// Guaranteed to be called after an oracle report is applied, regardless of whether any node + /// operator in this module has actually received any updated counts as a result of the report + /// but given that the total number of exited validators returned from getStakingModuleSummary + /// is the same as StakingRouter expects based on the total count received from the oracle. + function onExitedAndStuckValidatorsCountsUpdated() external { _auth(STAKING_ROUTER_ROLE); // for the permissioned module, we're distributing rewards within oracle operation // since the number of node ops won't be high and thus gas costs are limited @@ -438,7 +553,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _onlyExistedNodeOperator(_nodeOperatorId); _auth(STAKING_ROUTER_ROLE); - _updateExitedValidatorsCount(_nodeOperatorId, uint64(_exitedValidatorsCount), true /* _allowDecrease */); + _updateExitedValidatorsCount(_nodeOperatorId, uint64(_exitedValidatorsCount), true /* _allowDecrease */ ); _updateStuckValidatorsCount(_nodeOperatorId, uint64(_stuckValidatorsCount)); } @@ -446,18 +561,28 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { internal { Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); - uint64 depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); - uint64 exitedValidatorsCount = signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET); + int64 totalExitedValidatorsDelta = + int64(_exitedValidatorsKeysCount) - int64(signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET)); - if (exitedValidatorsCount != _exitedValidatorsKeysCount) { - _requireValidRange(_exitedValidatorsKeysCount <= depositedSigningKeysCount); - if (_exitedValidatorsKeysCount < exitedValidatorsCount && !_allowDecrease) { + if (totalExitedValidatorsDelta != 0) { + _requireValidRange(_exitedValidatorsKeysCount <= signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET)); + if (totalExitedValidatorsDelta < 0 && !_allowDecrease) { revert("EXITED_VALIDATORS_COUNT_DECREASED"); } signingKeysStats.set(EXITED_KEYS_COUNT_OFFSET, _exitedValidatorsKeysCount); _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); emit ExitedSigningKeysCountChanged(_nodeOperatorId, _exitedValidatorsKeysCount); + + // upd totals + Packed64x4.Packed memory summarySigningKeysStats = _loadSummarySigningKeysStats(); + summarySigningKeysStats.set( + SUMMARY_EXITED_KEYS_COUNT_OFFSET, + uint64(int64(summarySigningKeysStats.get(SUMMARY_EXITED_KEYS_COUNT_OFFSET)) + totalExitedValidatorsDelta) + ); + _saveSummarySigningKeysStats(summarySigningKeysStats); + + _updateSummaryMaxValidatorsCount(_nodeOperatorId); } } @@ -475,6 +600,8 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _saveOperatorTargetValidatorsStats(_nodeOperatorId, operatorTargetStats); emit TargetValidatorsCountChanged(_nodeOperatorId, _targetLimit); + + _updateSummaryMaxValidatorsCount(_nodeOperatorId); } /** @@ -485,14 +612,24 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { if (_stuckValidatorsCount == stuckPenaltyStats.get(STUCK_VALIDATORS_COUNT_OFFSET)) return; Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); - _requireValidRange(_stuckValidatorsCount <= signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET)); + _requireValidRange( + _stuckValidatorsCount + <= signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET) - signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET) + ); stuckPenaltyStats.set(STUCK_VALIDATORS_COUNT_OFFSET, _stuckValidatorsCount); if (_stuckValidatorsCount <= stuckPenaltyStats.get(REFUNDED_VALIDATORS_COUNT_OFFSET)) { stuckPenaltyStats.set(STUCK_PENALTY_END_TIMESTAMP_OFFSET, uint64(block.timestamp + getStuckPenaltyDelay())); } _saveOperatorStuckPenaltyStats(_nodeOperatorId, stuckPenaltyStats); - emit StuckValidatorsCountChanged(_nodeOperatorId, _stuckValidatorsCount); + emit StuckPenaltyStateChanged( + _nodeOperatorId, + _stuckValidatorsCount, + stuckPenaltyStats.get(REFUNDED_VALIDATORS_COUNT_OFFSET), + stuckPenaltyStats.get(STUCK_PENALTY_END_TIMESTAMP_OFFSET) + ); + + _updateSummaryMaxValidatorsCount(_nodeOperatorId); } function _updateRefundValidatorsKeysCount(uint256 _nodeOperatorId, uint64 _refundedValidatorsCount) internal { @@ -507,7 +644,27 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { stuckPenaltyStats.set(STUCK_PENALTY_END_TIMESTAMP_OFFSET, uint64(block.timestamp + getStuckPenaltyDelay())); } _saveOperatorStuckPenaltyStats(_nodeOperatorId, stuckPenaltyStats); - emit RefundedValidatorsCountChanged(_nodeOperatorId, _refundedValidatorsCount); + emit StuckPenaltyStateChanged( + _nodeOperatorId, + stuckPenaltyStats.get(STUCK_VALIDATORS_COUNT_OFFSET), + _refundedValidatorsCount, + stuckPenaltyStats.get(STUCK_PENALTY_END_TIMESTAMP_OFFSET) + ); + _updateSummaryMaxValidatorsCount(_nodeOperatorId); + } + + // @dev Recalculate and update the max validoator count for operator and summary stats + function _updateSummaryMaxValidatorsCount(uint256 _nodeOperatorId) internal returns (int64 maxSigningKeysDelta) { + maxSigningKeysDelta = _applyNodeOperatorLimits(_nodeOperatorId); + if (maxSigningKeysDelta != 0) { + Packed64x4.Packed memory summarySigningKeysStats = _loadSummarySigningKeysStats(); + + summarySigningKeysStats.set( + SUMMARY_MAX_VALIDATORS_COUNT_OFFSET, + uint64(int64(summarySigningKeysStats.get(SUMMARY_MAX_VALIDATORS_COUNT_OFFSET)) + maxSigningKeysDelta) + ); + _saveSummarySigningKeysStats(summarySigningKeysStats); + } } /// @notice Invalidates all unused deposit data for all node operators @@ -527,8 +684,8 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { uint64 totalTrimmedKeysCount; Packed64x4.Packed memory signingKeysStats; - for (uint256 _nodeOperatorId = _indexFrom; _nodeOperatorId <= _indexTo; ++_nodeOperatorId) { - signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); + for (uint256 nodeOperatorId = _indexFrom; nodeOperatorId <= _indexTo; ++nodeOperatorId) { + signingKeysStats = _loadOperatorSigningKeysStats(nodeOperatorId); uint64 depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); trimmedKeysCount = signingKeysStats.get(TOTAL_KEYS_COUNT_OFFSET) - depositedSigningKeysCount; @@ -537,11 +694,13 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { signingKeysStats.set(TOTAL_KEYS_COUNT_OFFSET, depositedSigningKeysCount); signingKeysStats.set(VETTED_KEYS_COUNT_OFFSET, depositedSigningKeysCount); - _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); + _saveOperatorSigningKeysStats(nodeOperatorId, signingKeysStats); - emit TotalSigningKeysCountChanged(_nodeOperatorId, depositedSigningKeysCount); - emit VettedSigningKeysCountChanged(_nodeOperatorId, depositedSigningKeysCount); - emit NodeOperatorTotalKeysTrimmed(_nodeOperatorId, trimmedKeysCount); + _updateSummaryMaxValidatorsCount(nodeOperatorId); + + emit TotalSigningKeysCountChanged(nodeOperatorId, depositedSigningKeysCount); + emit VettedSigningKeysCountChanged(nodeOperatorId, depositedSigningKeysCount); + emit NodeOperatorTotalKeysTrimmed(nodeOperatorId, trimmedKeysCount); } if (totalTrimmedKeysCount > 0) { @@ -549,62 +708,76 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { } } - /// @notice Obtains up to _depositsCount deposit data to be used by StakingRouter - /// to deposit to the Ethereum Deposit contract - /// @dev the second param is optional staking module calldata - /// (not used for NodeOperatorsRegistry) - /// @param _depositsCount Desireable number of deposits to be done - /// @return depositsCount Actual deposits count might be done with returned data + /// @notice Obtains deposit data to be used by StakingRouter to deposit to the Ethereum Deposit + /// contract + /// @param _depositsCount Number of deposits to be done /// @return publicKeys Batch of the concatenated public validators keys /// @return signatures Batch of the concatenated deposit signatures for returned public keys - function obtainDepositData(uint256 _depositsCount, bytes /* _depositCalldata */) - external - returns ( - uint256 depositsCount, - bytes memory publicKeys, - bytes memory signatures - ) - { + function obtainDepositData( + uint256 _depositsCount, + bytes /* _depositCalldata */ + ) external returns (bytes memory publicKeys, bytes memory signatures) { _auth(STAKING_ROUTER_ROLE); - uint256[] memory nodeOperatorIds; - uint256[] memory activeKeysCountAfterAllocation; + if (_depositsCount == 0) return (new bytes(0), new bytes(0)); - (depositsCount, nodeOperatorIds, activeKeysCountAfterAllocation) = - _getSigningKeysAllocationData(_depositsCount); + ( + uint256 allocatedKeysCount, + uint256[] memory nodeOperatorIds, + uint256[] memory activeKeysCountAfterAllocation + ) = _getSigningKeysAllocationData(_depositsCount); - if (depositsCount == 0) { - return (0, new bytes(0), new bytes(0)); - } + require(allocatedKeysCount == _depositsCount, "INVALID_ALLOCATED_KEYS_COUNT"); - (publicKeys, signatures) = - _loadAllocatedSigningKeys(depositsCount, nodeOperatorIds, activeKeysCountAfterAllocation); + (publicKeys, signatures) = _loadAllocatedSigningKeys( + allocatedKeysCount, + nodeOperatorIds, + activeKeysCountAfterAllocation + ); _increaseValidatorsKeysNonce(); } - function _getNodeOperatorWithLimitApplied(uint256 _nodeOperatorId) + function _getNodeOperator(uint256 _nodeOperatorId) internal view - returns (uint64 vettedSigningKeysCount, uint64 exitedSigningKeysCount, uint64 depositedSigningKeysCount) + returns (uint64 maxSigningKeysCount, uint64 exitedSigningKeysCount, uint64 depositedSigningKeysCount) { Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); + Packed64x4.Packed memory operatorTargetStats = _loadOperatorTargetValidatorsStats(_nodeOperatorId); + exitedSigningKeysCount = signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET); depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); - vettedSigningKeysCount = depositedSigningKeysCount; + maxSigningKeysCount = operatorTargetStats.get(MAX_VALIDATORS_COUNT_OFFSET); + } + + function _applyNodeOperatorLimits(uint256 _nodeOperatorId) internal returns (int64 maxSigningKeysDelta) { + Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); + Packed64x4.Packed memory operatorTargetStats = _loadOperatorTargetValidatorsStats(_nodeOperatorId); + + uint64 exitedSigningKeysCount = signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET); + uint64 depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); + uint64 vettedSigningKeysCount = signingKeysStats.get(VETTED_KEYS_COUNT_OFFSET); - if (!isOperatorPenalized(_nodeOperatorId)) { - Packed64x4.Packed memory operatorTargetStats = _loadOperatorTargetValidatorsStats(_nodeOperatorId); + uint64 oldMaxSigningKeysCount = operatorTargetStats.get(MAX_VALIDATORS_COUNT_OFFSET); + uint64 newMaxSigningKeysCount = depositedSigningKeysCount; + if (isOperatorPenaltyCleared(_nodeOperatorId)) { if (operatorTargetStats.get(IS_TARGET_LIMIT_ACTIVE_OFFSET) == 0) { - vettedSigningKeysCount = signingKeysStats.get(VETTED_KEYS_COUNT_OFFSET); + newMaxSigningKeysCount = vettedSigningKeysCount; } else { - // correct vetted count according to target if target is enabled + // correct max count according to target if target is enabled uint64 targetLimit = exitedSigningKeysCount.add(operatorTargetStats.get(TARGET_VALIDATORS_COUNT_OFFSET)); if (targetLimit > depositedSigningKeysCount) { - vettedSigningKeysCount = Math64.min(signingKeysStats.get(VETTED_KEYS_COUNT_OFFSET), targetLimit); + newMaxSigningKeysCount = uint64(Math256.min(uint256(vettedSigningKeysCount), uint256(targetLimit))); } } - } // else vettedSigningKeysCount = depositedSigningKeysCount, so depositable keys count = 0 + } // else newMaxSigningKeysCount = depositedSigningKeysCount, so depositable keys count = 0 + + if (oldMaxSigningKeysCount != newMaxSigningKeysCount) { + operatorTargetStats.set(MAX_VALIDATORS_COUNT_OFFSET, newMaxSigningKeysCount); + _saveOperatorTargetValidatorsStats(_nodeOperatorId, operatorTargetStats); + maxSigningKeysDelta = int64(newMaxSigningKeysCount) - int64(oldMaxSigningKeysCount); + } } function _getSigningKeysAllocationData(uint256 _keysCount) @@ -619,20 +792,19 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { uint256 activeNodeOperatorIndex; uint256 nodeOperatorsCount = getNodeOperatorsCount(); - uint256 vettedSigningKeysCount; + uint256 maxSigningKeysCount; uint256 depositedSigningKeysCount; uint256 exitedSigningKeysCount; for (uint256 nodeOperatorId; nodeOperatorId < nodeOperatorsCount; ++nodeOperatorId) { - (vettedSigningKeysCount, exitedSigningKeysCount, depositedSigningKeysCount) = - _getNodeOperatorWithLimitApplied(nodeOperatorId); + (maxSigningKeysCount, exitedSigningKeysCount, depositedSigningKeysCount) = _getNodeOperator(nodeOperatorId); // the node operator has no available signing keys - if (depositedSigningKeysCount == vettedSigningKeysCount) continue; + if (depositedSigningKeysCount == maxSigningKeysCount) continue; nodeOperatorIds[activeNodeOperatorIndex] = nodeOperatorId; activeKeyCountsAfterAllocation[activeNodeOperatorIndex] = depositedSigningKeysCount - exitedSigningKeysCount; - activeKeysCapacities[activeNodeOperatorIndex] = vettedSigningKeysCount - exitedSigningKeysCount; + activeKeysCapacities[activeNodeOperatorIndex] = maxSigningKeysCount - exitedSigningKeysCount; ++activeNodeOperatorIndex; } @@ -650,6 +822,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { allocatedKeysCount = MinFirstAllocationStrategy.allocate(activeKeyCountsAfterAllocation, activeKeysCapacities, uint64(_keysCount)); + /// @dev method NEVER allocates more keys than was requested assert(allocatedKeysCount <= _keysCount); } @@ -658,12 +831,12 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { uint256[] memory _nodeOperatorIds, uint256[] memory _activeKeyCountsAfterAllocation ) internal returns (bytes memory pubkeys, bytes memory signatures) { - (pubkeys, signatures) = SigningKeys.initKeySig(_keysCountToLoad); + (pubkeys, signatures) = SigningKeys.initKeysSigsBuf(_keysCountToLoad); uint256 loadedKeysCount = 0; uint64 depositedSigningKeysCountBefore; uint64 depositedSigningKeysCountAfter; - uint256 keyIndex; + uint256 keysCount; Packed64x4.Packed memory signingKeysStats; for (uint256 i; i < _nodeOperatorIds.length; ++i) { signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorIds[i]); @@ -671,20 +844,28 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { depositedSigningKeysCountAfter = signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET) + uint64(_activeKeyCountsAfterAllocation[i]); - if (depositedSigningKeysCountBefore == depositedSigningKeysCountAfter) continue; + keysCount = depositedSigningKeysCountAfter.sub(depositedSigningKeysCountBefore); + if (keysCount == 0) continue; + + SIGNING_KEYS_MAPPING_NAME.loadKeysSigs( + _nodeOperatorIds[i], depositedSigningKeysCountBefore, keysCount, pubkeys, signatures, loadedKeysCount + ); + loadedKeysCount += keysCount; - for (keyIndex = depositedSigningKeysCountBefore; keyIndex < depositedSigningKeysCountAfter; ++keyIndex) { - SIGNING_KEYS_MAPPING_NAME.loadKeySigAndAppend( - _nodeOperatorIds[i], keyIndex, loadedKeysCount, pubkeys, signatures - ); - ++loadedKeysCount; - } emit DepositedSigningKeysCountChanged(_nodeOperatorIds[i], depositedSigningKeysCountAfter); signingKeysStats.set(DEPOSITED_KEYS_COUNT_OFFSET, depositedSigningKeysCountAfter); _saveOperatorSigningKeysStats(_nodeOperatorIds[i], signingKeysStats); + _updateSummaryMaxValidatorsCount(_nodeOperatorIds[i]); } assert(loadedKeysCount == _keysCountToLoad); + + Packed64x4.Packed memory summarySigningKeysStats = _loadSummarySigningKeysStats(); + summarySigningKeysStats.set( + SUMMARY_DEPOSITED_KEYS_COUNT_OFFSET, + summarySigningKeysStats.get(SUMMARY_DEPOSITED_KEYS_COUNT_OFFSET).add(uint64(loadedKeysCount)) + ); + _saveSummarySigningKeysStats(summarySigningKeysStats); } /// @notice Returns the node operator by id @@ -803,13 +984,20 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _requireValidRange(totalSigningKeysCount.add(_keysCount) <= UINT64_MAX); totalSigningKeysCount = - SIGNING_KEYS_MAPPING_NAME.addKeysSigs(_nodeOperatorId, _keysCount, totalSigningKeysCount, _publicKeys, _signatures); + SIGNING_KEYS_MAPPING_NAME.saveKeysSigs(_nodeOperatorId, totalSigningKeysCount, _keysCount, _publicKeys, _signatures); emit TotalSigningKeysCountChanged(_nodeOperatorId, totalSigningKeysCount); signingKeysStats.set(TOTAL_KEYS_COUNT_OFFSET, uint64(totalSigningKeysCount)); _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); + // upd totals + Packed64x4.Packed memory summarySigningKeysStats = _loadSummarySigningKeysStats(); + summarySigningKeysStats.set( + TOTAL_KEYS_COUNT_OFFSET, summarySigningKeysStats.get(SUMMARY_TOTAL_KEYS_COUNT_OFFSET).add(uint64(_keysCount)) + ); + _saveSummarySigningKeysStats(summarySigningKeysStats); + _increaseValidatorsKeysNonce(); } @@ -855,15 +1043,11 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); uint256 totalSigningKeysCount = signingKeysStats.get(TOTAL_KEYS_COUNT_OFFSET); - uint256 _toIndex = _fromIndex.add(_keysCount); - // comapring _toIndex <= totalSigningKeysCount is enough as totalSigningKeysCount is always less than MAX_UINT64 - _requireValidRange(_fromIndex >= signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET) && _toIndex <= totalSigningKeysCount); - - // removing from the last index to the highest one, so we won't get outside the array - for (uint256 i = _toIndex; i > _fromIndex; --i) { - totalSigningKeysCount = - SIGNING_KEYS_MAPPING_NAME.removeUnusedKeySig(_nodeOperatorId, i - 1, totalSigningKeysCount.sub(1)); - } + // comapring _fromIndex.add(_keysCount) <= totalSigningKeysCount is enough as totalSigningKeysCount is always less than MAX_UINT64 + _requireValidRange(_fromIndex >= signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET) && _fromIndex.add(_keysCount) <= totalSigningKeysCount); + + totalSigningKeysCount = + SIGNING_KEYS_MAPPING_NAME.removeKeysSigs(_nodeOperatorId, _fromIndex, _keysCount, totalSigningKeysCount); signingKeysStats.set(TOTAL_KEYS_COUNT_OFFSET, uint64(totalSigningKeysCount)); emit TotalSigningKeysCountChanged(_nodeOperatorId, totalSigningKeysCount); @@ -875,6 +1059,15 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { } _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); + // upd totals + Packed64x4.Packed memory summarySigningKeysStats = _loadSummarySigningKeysStats(); + summarySigningKeysStats.set( + SUMMARY_TOTAL_KEYS_COUNT_OFFSET, + summarySigningKeysStats.get(SUMMARY_TOTAL_KEYS_COUNT_OFFSET).sub(uint64(_keysCount)) + ); + _saveSummarySigningKeysStats(summarySigningKeysStats); + _updateSummaryMaxValidatorsCount(_nodeOperatorId); + _increaseValidatorsKeysNonce(); } @@ -927,11 +1120,11 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _requireValidRange(_offset.add(_limit) <= signingKeysStats.get(TOTAL_KEYS_COUNT_OFFSET)); uint256 depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); - (pubkeys, signatures) = SigningKeys.initKeySig(_limit); + (pubkeys, signatures) = SigningKeys.initKeysSigsBuf(_limit); used = new bool[](_limit); + SIGNING_KEYS_MAPPING_NAME.loadKeysSigs(_nodeOperatorId, _offset, _limit, pubkeys, signatures, 0); for (uint256 i; i < _limit; ++i) { - SIGNING_KEYS_MAPPING_NAME.loadKeySigAndAppend(_nodeOperatorId, _offset + i, i, pubkeys, signatures); used[i] = (_offset + i) < depositedSigningKeysCount; } } @@ -941,34 +1134,29 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { return TYPE_POSITION.getStorageBytes32(); } - function getStakingModuleSummary() external view returns ( - uint256 totalExitedValidators, - uint256 totalDepositedValidators, - uint256 depositableValidatorsCount - ) { - uint256 nodeOperatorsCount = getNodeOperatorsCount(); - - uint256 tmpTotalExitedValidators; - uint256 tmpTotalDepositedValidators; - uint256 tmpDepositableValidatorsCount; - for (uint256 i; i < nodeOperatorsCount; ++i) { - (tmpTotalExitedValidators, tmpTotalDepositedValidators, tmpDepositableValidatorsCount) = - _getNodeOperatorValidatorsSummary(i); - totalExitedValidators += tmpTotalExitedValidators; - totalDepositedValidators += tmpTotalDepositedValidators; - depositableValidatorsCount += tmpDepositableValidatorsCount; - } + function getStakingModuleSummary() + external + view + returns (uint256 totalExitedValidators, uint256 totalDepositedValidators, uint256 depositableValidatorsCount) + { + Packed64x4.Packed memory summarySigningKeysStats = _loadSummarySigningKeysStats(); + totalExitedValidators = summarySigningKeysStats.get(SUMMARY_EXITED_KEYS_COUNT_OFFSET); + totalDepositedValidators = summarySigningKeysStats.get(SUMMARY_DEPOSITED_KEYS_COUNT_OFFSET); + depositableValidatorsCount = summarySigningKeysStats.get(SUMMARY_MAX_VALIDATORS_COUNT_OFFSET) - totalDepositedValidators; } - function getNodeOperatorSummary(uint256 _nodeOperatorId) external view returns ( - bool isTargetLimitActive, - uint256 targetValidatorsCount, - uint256 stuckValidatorsCount, - uint256 refundedValidatorsCount, - uint256 stuckPenaltyEndTimestamp, - uint256 totalExitedValidators, - uint256 totalDepositedValidators, - uint256 depositableValidatorsCount + function getNodeOperatorSummary(uint256 _nodeOperatorId) + external + view + returns ( + bool isTargetLimitActive, + uint256 targetValidatorsCount, + uint256 stuckValidatorsCount, + uint256 refundedValidatorsCount, + uint256 stuckPenaltyEndTimestamp, + uint256 totalExitedValidators, + uint256 totalDepositedValidators, + uint256 depositableValidatorsCount ) { _onlyExistedNodeOperator(_nodeOperatorId); @@ -982,7 +1170,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { stuckPenaltyEndTimestamp = stuckPenaltyStats.get(STUCK_PENALTY_END_TIMESTAMP_OFFSET); (totalExitedValidators, totalDepositedValidators, depositableValidatorsCount) = - _getNodeOperatorValidatorsSummary(_nodeOperatorId); + _getNodeOperatorValidatorsSummary(_nodeOperatorId); } function _getNodeOperatorValidatorsSummary(uint256 _nodeOperatorId) internal view returns ( @@ -990,18 +1178,38 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { uint256 totalDepositedValidators, uint256 depositableValidatorsCount ) { - uint256 totalVettedValidators; - (totalVettedValidators, totalExitedValidators, totalDepositedValidators) = - _getNodeOperatorWithLimitApplied(_nodeOperatorId); - depositableValidatorsCount = totalVettedValidators - totalDepositedValidators; + uint256 totalMaxValidators; + (totalMaxValidators, totalExitedValidators, totalDepositedValidators) = _getNodeOperator(_nodeOperatorId); + + depositableValidatorsCount = totalMaxValidators - totalDepositedValidators; } - function isOperatorPenalized(uint256 _nodeOperatorId) public view returns (bool) { - Packed64x4.Packed memory stuckPenaltyStats = _loadOperatorStuckPenaltyStats(_nodeOperatorId); + function _isOperatorPenalized(Packed64x4.Packed memory stuckPenaltyStats) internal view returns (bool) { return stuckPenaltyStats.get(REFUNDED_VALIDATORS_COUNT_OFFSET) < stuckPenaltyStats.get(STUCK_VALIDATORS_COUNT_OFFSET) || block.timestamp <= stuckPenaltyStats.get(STUCK_PENALTY_END_TIMESTAMP_OFFSET); } + function isOperatorPenalized(uint256 _nodeOperatorId) public view returns (bool) { + Packed64x4.Packed memory stuckPenaltyStats = _loadOperatorStuckPenaltyStats(_nodeOperatorId); + return _isOperatorPenalized(stuckPenaltyStats); + } + + function isOperatorPenaltyCleared(uint256 _nodeOperatorId) public view returns (bool) { + Packed64x4.Packed memory stuckPenaltyStats = _loadOperatorStuckPenaltyStats(_nodeOperatorId); + return !_isOperatorPenalized(stuckPenaltyStats) && stuckPenaltyStats.get(STUCK_PENALTY_END_TIMESTAMP_OFFSET) == 0; + } + + function clearNodeOperatorPenalty(uint256 _nodeOperatorId) public returns (bool) { + Packed64x4.Packed memory stuckPenaltyStats = _loadOperatorStuckPenaltyStats(_nodeOperatorId); + require( + !_isOperatorPenalized(stuckPenaltyStats) && stuckPenaltyStats.get(STUCK_PENALTY_END_TIMESTAMP_OFFSET) != 0, + "CANT_CLEAR_PENALTY" + ); + stuckPenaltyStats.set(STUCK_PENALTY_END_TIMESTAMP_OFFSET, 0); + _saveOperatorStuckPenaltyStats(_nodeOperatorId, stuckPenaltyStats); + _updateSummaryMaxValidatorsCount(_nodeOperatorId); + } + /// @notice Returns total number of node operators function getNodeOperatorsCount() public view returns (uint256) { return TOTAL_OPERATORS_COUNT_POSITION.getStorageUint256(); @@ -1022,13 +1230,13 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { external view returns (uint256[] memory nodeOperatorIds) { - uint256 nodeOperatorsCount = getNodeOperatorsCount(); - if (_offset >= nodeOperatorsCount || _limit == 0) return; - nodeOperatorIds = new uint256[](Math256.min(_limit, nodeOperatorsCount - _offset)); - for (uint256 i = 0; i < nodeOperatorIds.length; ++i) { - nodeOperatorIds[i] = _offset + i; - } + uint256 nodeOperatorsCount = getNodeOperatorsCount(); + if (_offset >= nodeOperatorsCount || _limit == 0) return; + nodeOperatorIds = new uint256[](Math256.min(_limit, nodeOperatorsCount - _offset)); + for (uint256 i = 0; i < nodeOperatorIds.length; ++i) { + nodeOperatorIds[i] = _offset + i; } + } /// @notice Returns a counter that MUST change it's value when any of the following happens: /// 1. a node operator's deposit data is added @@ -1098,8 +1306,10 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _setStuckPenaltyDelay(_delay); } + /// @dev set new stuck penalty delay, duration in sec function _setStuckPenaltyDelay(uint256 _delay) internal { STUCK_PENALTY_DELAY_POSITION.setStorageUint256(_delay); + emit StuckPenaltyDelayChanged(_delay); } function _increaseValidatorsKeysNonce() internal { @@ -1110,6 +1320,14 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { emit NonceChanged(keysOpIndex); } + function _loadSummarySigningKeysStats() internal view returns (Packed64x4.Packed memory) { + return _nodeOperatorSummary.summarySigningKeysStats; + } + + function _saveSummarySigningKeysStats(Packed64x4.Packed memory _val) internal { + _nodeOperatorSummary.summarySigningKeysStats = _val; + } + function _loadOperatorTargetValidatorsStats(uint256 _nodeOperatorId) internal view returns (Packed64x4.Packed memory) { return _nodeOperators[_nodeOperatorId].targetValidatorsStats; } diff --git a/contracts/0.4.24/template/LidoTemplate.sol b/contracts/0.4.24/template/LidoTemplate.sol index 7045fd720..4ca6bb5eb 100644 --- a/contracts/0.4.24/template/LidoTemplate.sol +++ b/contracts/0.4.24/template/LidoTemplate.sol @@ -328,7 +328,7 @@ contract LidoTemplate is IsContract { ); - state.operators.initialize(state.lido, bytes32(0x1)); + state.operators.initialize(state.lido, bytes32(0x1), 2 days); // used for issuing vested tokens in the next step _createTokenManagerPermissionsForTemplate(state.acl, state.tokenManager); diff --git a/contracts/0.4.24/test_helpers/LidoMock.sol b/contracts/0.4.24/test_helpers/LidoMock.sol index ab834cc4e..b519b5cd0 100644 --- a/contracts/0.4.24/test_helpers/LidoMock.sol +++ b/contracts/0.4.24/test_helpers/LidoMock.sol @@ -18,6 +18,7 @@ contract LidoMock is Lido { address _eip712StETH ) public + payable { super.initialize( _lidoLocator, @@ -35,13 +36,6 @@ contract LidoMock is Lido { _resumeStaking(); } - /** - * @dev Gets unaccounted (excess) Ether on this contract balance - */ - function getUnaccountedEther() public view returns (uint256) { - return _getUnaccountedEther(); - } - /** * @dev Only for testing recovery vault */ diff --git a/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol b/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol index 44a6188a9..8f0912221 100644 --- a/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol +++ b/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol @@ -11,6 +11,14 @@ contract NodeOperatorsRegistryMock is NodeOperatorsRegistry { Packed64x4.Packed memory signingKeysStats = _nodeOperators[_nodeOperatorId].signingKeysStats; signingKeysStats.set(DEPOSITED_KEYS_COUNT_OFFSET, signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET) + _keysCount); _nodeOperators[_nodeOperatorId].signingKeysStats = signingKeysStats; + + Packed64x4.Packed memory totalSigningKeysStats = _loadSummarySigningKeysStats(); + totalSigningKeysStats.set( + DEPOSITED_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET).add(_keysCount) + ); + _saveSummarySigningKeysStats(totalSigningKeysStats); + + _updateSummaryMaxValidatorsCount(_nodeOperatorId); } function testing_markAllKeysDeposited() external { @@ -86,7 +94,18 @@ contract NodeOperatorsRegistryMock is NodeOperatorsRegistry { operator.signingKeysStats = signingKeysStats; + Packed64x4.Packed memory operatorTargetStats; + operatorTargetStats.set(MAX_VALIDATORS_COUNT_OFFSET, vettedSigningKeysCount); + operator.targetValidatorsStats = operatorTargetStats; + emit NodeOperatorAdded(id, _name, _rewardAddress, 0); + + Packed64x4.Packed memory totalSigningKeysStats = _loadSummarySigningKeysStats(); + totalSigningKeysStats.set(VETTED_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(VETTED_KEYS_COUNT_OFFSET).add(vettedSigningKeysCount)); + totalSigningKeysStats.set(DEPOSITED_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET).add(depositedSigningKeysCount)); + totalSigningKeysStats.set(EXITED_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(EXITED_KEYS_COUNT_OFFSET).add(exitedSigningKeysCount)); + totalSigningKeysStats.set(TOTAL_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(TOTAL_KEYS_COUNT_OFFSET).add(totalSigningKeysCount)); + _saveSummarySigningKeysStats(totalSigningKeysStats); } function testing_setNodeOperatorLimits( @@ -100,6 +119,7 @@ contract NodeOperatorsRegistryMock is NodeOperatorsRegistry { stuckPenaltyStats.set(REFUNDED_VALIDATORS_COUNT_OFFSET, refundedValidatorsCount); stuckPenaltyStats.set(STUCK_PENALTY_END_TIMESTAMP_OFFSET, stuckPenaltyEndAt); _nodeOperators[_nodeOperatorId].stuckPenaltyStats = stuckPenaltyStats; + _updateSummaryMaxValidatorsCount(_nodeOperatorId); } function testing_getTotalSigningKeysStats() @@ -133,6 +153,10 @@ contract NodeOperatorsRegistryMock is NodeOperatorsRegistry { ACTIVE_OPERATORS_COUNT_POSITION.setStorageUint256(0); KEYS_OP_INDEX_POSITION.setStorageUint256(0); + _nodeOperatorSummary = NodeOperatorSummary({ + summarySigningKeysStats: Packed64x4.Packed(0) + }); + Packed64x4.Packed memory tmp; for (uint256 i = 0; i < totalOperatorsCount; ++i) { _nodeOperators[i] = NodeOperator(false, address(0), new string(0), tmp, tmp, tmp); @@ -155,8 +179,8 @@ contract NodeOperatorsRegistryMock is NodeOperatorsRegistry { external returns (uint256 loadedValidatorsKeysCount, bytes memory publicKeys, bytes memory signatures) { - (loadedValidatorsKeysCount, publicKeys, signatures) = this.obtainDepositData(_keysToAllocate, new bytes(0)); - emit ValidatorsKeysLoaded(loadedValidatorsKeysCount, publicKeys, signatures); + (publicKeys, signatures) = this.obtainDepositData(_keysToAllocate, new bytes(0)); + emit ValidatorsKeysLoaded(publicKeys, signatures); } function testing_isNodeOperatorPenalized(uint256 operatorId) external view returns (bool) { @@ -170,13 +194,13 @@ contract NodeOperatorsRegistryMock is NodeOperatorsRegistry { return false; } - function testing_getCorrectedNodeOperator(uint256 operatorId) external view - returns (uint64 vettedSigningKeysCount, uint64 exitedSigningKeysCount, uint64 depositedSigningKeysCount) + function testing_getNodeOperator(uint256 operatorId) external view + returns (uint64 maxSigningKeysCount, uint64 exitedSigningKeysCount, uint64 depositedSigningKeysCount) { - return _getNodeOperatorWithLimitApplied(operatorId); + return _getNodeOperator(operatorId); } - event ValidatorsKeysLoaded(uint256 count, bytes publicKeys, bytes signatures); + event ValidatorsKeysLoaded(bytes publicKeys, bytes signatures); function testing__distributeRewards() external returns (uint256) { return _distributeRewards(); diff --git a/contracts/0.4.24/test_helpers/SiginigKyesMock.sol b/contracts/0.4.24/test_helpers/SiginigKyesMock.sol new file mode 100644 index 000000000..4b2da4ab8 --- /dev/null +++ b/contracts/0.4.24/test_helpers/SiginigKyesMock.sol @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +// See contracts/COMPILERS.md +pragma solidity 0.4.24; + +import {SigningKeys} from "../lib/SigningKeys.sol"; + +import "hardhat/console.sol"; + +contract SigningKeysMock { + using SigningKeys for bytes32; + + bytes32 public constant KEYSSIGS_POSITION = keccak256("KEYSSIGS_POSITION"); + + uint256[] public _nodeOperatorIds; + + constructor(uint256[] memory ids) public { + _nodeOperatorIds = ids; + } + + function getKeyOffset(uint256 _nodeOperatorId, uint256 _keyIndex) external pure returns (uint256) { + return KEYSSIGS_POSITION.getKeyOffset(_nodeOperatorId, _keyIndex); + } + + function saveKeysSigs( + uint256 _nodeOperatorId, + uint256 _startIndex, + uint256 _keysCount, + bytes _publicKeys, + bytes _signatures + ) external returns (uint256) { + return KEYSSIGS_POSITION.saveKeysSigs(_nodeOperatorId, _startIndex, _keysCount, _publicKeys, _signatures); + } + + function removeKeysSigs(uint256 _nodeOperatorId, uint256 _startIndex, uint256 _keysCount, uint256 _lastIndex) + external + returns (uint256) + { + return KEYSSIGS_POSITION.removeKeysSigs(_nodeOperatorId, _startIndex, _keysCount, _lastIndex); + } + + function loadKeysSigs(uint256 _nodeOperatorId, uint256 _startIndex, uint256 _keysCount) + external + view + returns (bytes memory pubkeys, bytes memory signatures) + { + (pubkeys, signatures) = SigningKeys.initKeysSigsBuf(_keysCount); + KEYSSIGS_POSITION.loadKeysSigs( + _nodeOperatorId, + _startIndex, + _keysCount, + pubkeys, + signatures, + 0 // key offset inside _pubkeys/_signatures buffers + ); + } + + function loadKeysSigsBatch(uint256[] _nodeOpIds, uint256[] _startIndexes, uint256[] _keysCounts) + external + view + returns (bytes memory pubkeys, bytes memory signatures) + { + require(_nodeOpIds.length == _startIndexes.length && _startIndexes.length == _keysCounts.length, "LENGTH_MISMATCH"); + uint256 totalKeysCount; + uint256 i; + for (i = 0; i < _nodeOpIds.length; ++i) { + totalKeysCount += _keysCounts[i]; + } + (pubkeys, signatures) = SigningKeys.initKeysSigsBuf(totalKeysCount); + uint256 loadedKeysCount; + for (i = 0; i < _nodeOpIds.length; ++i) { + KEYSSIGS_POSITION.loadKeysSigs( + _nodeOpIds[i], + _startIndexes[i], + _keysCounts[i], + pubkeys, + signatures, + loadedKeysCount // key offset inside _pubkeys/_signatures buffers + ); + loadedKeysCount += _keysCounts[i]; + } + } +} diff --git a/contracts/0.4.24/test_helpers/StETHMock.sol b/contracts/0.4.24/test_helpers/StETHMock.sol index 50eadebe2..612166c60 100644 --- a/contracts/0.4.24/test_helpers/StETHMock.sol +++ b/contracts/0.4.24/test_helpers/StETHMock.sol @@ -14,6 +14,7 @@ contract StETHMock is StETH { constructor() public payable{ _resume(); + _bootstrapInitialHolder(); } function _getTotalPooledEther() internal view returns (uint256) { diff --git a/contracts/0.8.9/BeaconChainDepositor.sol b/contracts/0.8.9/BeaconChainDepositor.sol index 7ec67f755..4bcd2f5f3 100644 --- a/contracts/0.8.9/BeaconChainDepositor.sol +++ b/contracts/0.8.9/BeaconChainDepositor.sol @@ -44,10 +44,12 @@ contract BeaconChainDepositor { bytes memory _publicKeysBatch, bytes memory _signaturesBatch ) internal { - require(_publicKeysBatch.length == PUBLIC_KEY_LENGTH * _keysCount, "INVALID_PUBLIC_KEYS_BATCH_LENGTH"); - require(_signaturesBatch.length == SIGNATURE_LENGTH * _keysCount, "INVALID_SIGNATURES_BATCH_LENGTH"); - - uint256 targetBalance = address(this).balance - (_keysCount * DEPOSIT_SIZE); + if (_publicKeysBatch.length != PUBLIC_KEY_LENGTH * _keysCount) { + revert InvalidPublicKeysBatchLength(_publicKeysBatch.length, PUBLIC_KEY_LENGTH * _keysCount); + } + if (_signaturesBatch.length != SIGNATURE_LENGTH * _keysCount) { + revert InvalidSignaturesBatchLength(_signaturesBatch.length, SIGNATURE_LENGTH * _keysCount); + } bytes memory publicKey = MemUtils.unsafeAllocateBytes(PUBLIC_KEY_LENGTH); bytes memory signature = MemUtils.unsafeAllocateBytes(SIGNATURE_LENGTH); @@ -64,8 +66,6 @@ contract BeaconChainDepositor { ++i; } } - - if (address(this).balance != targetBalance) revert NotExpectedBalance(); } /// @dev computes the deposit_root_hash required by official Beacon Deposit contract @@ -80,19 +80,20 @@ contract BeaconChainDepositor { bytes memory sigPart1 = MemUtils.unsafeAllocateBytes(64); bytes memory sigPart2 = MemUtils.unsafeAllocateBytes(SIGNATURE_LENGTH - 64); MemUtils.copyBytes(_signature, sigPart1, 0, 0, 64); - MemUtils.copyBytes(_signature, sigPart2, 64, 0,SIGNATURE_LENGTH - 64); + MemUtils.copyBytes(_signature, sigPart2, 64, 0, SIGNATURE_LENGTH - 64); bytes32 publicKeyRoot = sha256(abi.encodePacked(_publicKey, bytes16(0))); bytes32 signatureRoot = sha256(abi.encodePacked(sha256(abi.encodePacked(sigPart1)), sha256(abi.encodePacked(sigPart2, bytes32(0))))); return sha256( - abi.encodePacked( - sha256(abi.encodePacked(publicKeyRoot, _withdrawalCredentials)), - sha256(abi.encodePacked(DEPOSIT_SIZE_IN_GWEI_LE64, bytes24(0), signatureRoot)) - ) - ); + abi.encodePacked( + sha256(abi.encodePacked(publicKeyRoot, _withdrawalCredentials)), + sha256(abi.encodePacked(DEPOSIT_SIZE_IN_GWEI_LE64, bytes24(0), signatureRoot)) + ) + ); } error DepositContractZeroAddress(); - error NotExpectedBalance(); + error InvalidPublicKeysBatchLength(uint256 actual, uint256 expected); + error InvalidSignaturesBatchLength(uint256 actual, uint256 expected); } diff --git a/contracts/0.8.9/Burner.sol b/contracts/0.8.9/Burner.sol index 5cfd927f4..c4997062c 100644 --- a/contracts/0.8.9/Burner.sol +++ b/contracts/0.8.9/Burner.sol @@ -57,7 +57,6 @@ contract Burner is IBurner, AccessControlEnumerable { error ZeroRecoveryAmount(); error StETHRecoveryWrongFunc(); error ZeroBurnAmount(); - error NotEnoughExcessStETH(); error ZeroAddress(string field); bytes32 public constant REQUEST_BURN_MY_STETH_ROLE = keccak256("REQUEST_BURN_MY_STETH_ROLE"); diff --git a/contracts/0.8.9/EIP712StETH.sol b/contracts/0.8.9/EIP712StETH.sol index 5686fd57c..4e7055ef0 100644 --- a/contracts/0.8.9/EIP712StETH.sol +++ b/contracts/0.8.9/EIP712StETH.sol @@ -1,27 +1,88 @@ -// SPDX-FileCopyrightText: 2023 Lido -// SPDX-License-Identifier: GPL-3.0 +// SPDX-FileCopyrightText: 2023 OpenZeppelin, Lido +// SPDX-License-Identifier: MIT /* See contracts/COMPILERS.md */ pragma solidity 0.8.9; -import {EIP712} from "@openzeppelin/contracts-v4.4/utils/cryptography/draft-EIP712.sol"; +import {ECDSA} from "@openzeppelin/contracts-v4.4/utils/cryptography/ECDSA.sol"; -import {IEIP712} from "../common/interfaces/IEIP712.sol"; +import {IEIP712StETH} from "../common/interfaces/IEIP712StETH.sol"; /** - * Helper contract exposes OpenZeppelin's EIP712 message utils implementation. + * NOTE: The code below is taken from "@openzeppelin/contracts-v4.4/utils/cryptography/draft-EIP712.sol" + * With a main difference to store the stETH contract address internally and use it for signing. */ -contract EIP712StETH is IEIP712, EIP712 { + +/** + * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. + * + * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, + * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding + * they need in their contracts using a combination of `abi.encode` and `keccak256`. + * + * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding + * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA + * ({_hashTypedDataV4}). + * + * The implementation of the domain separator was designed to be as efficient as possible while still properly updating + * the chain id to protect against replay attacks on an eventual fork of the chain. + * + * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method + * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. + * + */ +contract EIP712StETH is IEIP712StETH { + /* solhint-disable var-name-mixedcase */ + // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to + // invalidate the cached domain separator if the chain id changes. + bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; + uint256 private immutable _CACHED_CHAIN_ID; + address private immutable _CACHED_STETH; + + bytes32 private immutable _HASHED_NAME; + bytes32 private immutable _HASHED_VERSION; + bytes32 private immutable _TYPE_HASH; + + error ZeroStETHAddress(); + /** * @dev Constructs specialized EIP712 instance for StETH token, version "2". */ - constructor() EIP712("Liquid staked Ether 2.0", "2") {} + constructor(address _stETH) { + if (_stETH == address(0)) { revert ZeroStETHAddress(); } + + bytes32 hashedName = keccak256(bytes("Liquid staked Ether 2.0")); + bytes32 hashedVersion = keccak256(bytes("2")); + bytes32 typeHash = keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ); + + _HASHED_NAME = hashedName; + _HASHED_VERSION = hashedVersion; + _CACHED_CHAIN_ID = block.chainid; + _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion, _stETH); + _CACHED_STETH = _stETH; + _TYPE_HASH = typeHash; + } /** * @dev Returns the domain separator for the current chain. */ - function domainSeparatorV4() external view override returns (bytes32) { - return _domainSeparatorV4(); + function domainSeparatorV4(address _stETH) public view override returns (bytes32) { + if (_stETH == _CACHED_STETH && block.chainid == _CACHED_CHAIN_ID) { + return _CACHED_DOMAIN_SEPARATOR; + } else { + return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION, _stETH); + } + } + + function _buildDomainSeparator( + bytes32 _typeHash, + bytes32 _nameHash, + bytes32 _versionHash, + address _stETH + ) private view returns (bytes32) { + return keccak256(abi.encode(_typeHash, _nameHash, _versionHash, block.chainid, _stETH)); } /** @@ -31,7 +92,7 @@ contract EIP712StETH is IEIP712, EIP712 { * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: * * ```solidity - * bytes32 digest = hashTypedDataV4(keccak256(abi.encode( + * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( * keccak256("Mail(address to,string contents)"), * mailTo, * keccak256(bytes(mailContents)) @@ -39,7 +100,25 @@ contract EIP712StETH is IEIP712, EIP712 { * address signer = ECDSA.recover(digest, signature); * ``` */ - function hashTypedDataV4(bytes32 _structHash) external view override returns (bytes32) { - return _hashTypedDataV4(_structHash); + function hashTypedDataV4(address _stETH, bytes32 _structHash) external view override returns (bytes32) { + return ECDSA.toTypedDataHash(domainSeparatorV4(_stETH), _structHash); + } + + /** + * @dev returns the fields and values that describe the domain separator + * used by stETH for EIP-712 signature. + */ + function eip712Domain(address _stETH) external view returns ( + string memory name, + string memory version, + uint256 chainId, + address verifyingContract + ) { + return ( + "Liquid staked Ether 2.0", + "2", + block.chainid, + _stETH + ); } } diff --git a/contracts/0.8.9/LidoLocator.sol b/contracts/0.8.9/LidoLocator.sol index 721875b00..07392a280 100644 --- a/contracts/0.8.9/LidoLocator.sol +++ b/contracts/0.8.9/LidoLocator.sol @@ -27,6 +27,7 @@ contract LidoLocator is ILidoLocator { address validatorsExitBusOracle; address withdrawalQueue; address withdrawalVault; + address oracleDaemonConfig; } error ZeroAddress(); @@ -44,6 +45,7 @@ contract LidoLocator is ILidoLocator { address public immutable validatorsExitBusOracle; address public immutable withdrawalQueue; address public immutable withdrawalVault; + address public immutable oracleDaemonConfig; /** * @notice declare service locations @@ -64,6 +66,7 @@ contract LidoLocator is ILidoLocator { validatorsExitBusOracle = _assertNonZero(_config.validatorsExitBusOracle); withdrawalQueue = _assertNonZero(_config.withdrawalQueue); withdrawalVault = _assertNonZero(_config.withdrawalVault); + oracleDaemonConfig = _assertNonZero(_config.oracleDaemonConfig); } function coreComponents() external view returns( diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 9686ee7a6..1ec148083 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -15,11 +15,6 @@ import {MinFirstAllocationStrategy} from "../common/lib/MinFirstAllocationStrate import {BeaconChainDepositor} from "./BeaconChainDepositor.sol"; import {Versioned} from "./utils/Versioned.sol"; -interface ILido { - function getDepositableEther() external view returns (uint256); - function receiveStakingRouterDepositRemainder() external payable; -} - contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Versioned { using UnstructuredStorage for bytes32; @@ -41,7 +36,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version error StakingModuleNotPaused(); error EmptyWithdrawalsCredentials(); error DirectETHTransfer(); - error InvalidReportData(); + error InvalidReportData(uint256 code); error ExitedValidatorsCountCannotDecrease(); error StakingModulesLimitExceeded(); error StakingModuleIdTooLarge(); @@ -54,6 +49,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 currentNodeOpExitedValidatorsCount, uint256 currentNodeOpStuckValidatorsCount ); + error InvalidDepositsValue(uint256 etherValue); + error StakingModuleAddressExists(); enum StakingModuleStatus { Active, // deposits and rewards allowed @@ -157,8 +154,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /** * @notice Return the Lido contract address */ - function getLido() public view returns (ILido) { - return ILido(LIDO_POSITION.getStorageAddress()); + function getLido() public view returns (address) { + return LIDO_POSITION.getStorageAddress(); } /** @@ -184,6 +181,14 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 newStakingModuleIndex = getStakingModulesCount(); if (newStakingModuleIndex >= 32) revert StakingModulesLimitExceeded(); + + for (uint256 i; i < newStakingModuleIndex; ) { + if (_stakingModuleAddress == _getStakingModuleByIndex(i).stakingModuleAddress) revert StakingModuleAddressExists(); + unchecked { + ++i; + } + } + StakingModule storage newStakingModule = _getStakingModuleByIndex(newStakingModuleIndex); uint24 newStakingModuleId = uint24(LAST_STAKING_MODULE_ID_POSITION.getStorageUint256()) + 1; @@ -258,7 +263,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version { for (uint256 i = 0; i < _stakingModuleIds.length; ) { address moduleAddr = _getStakingModuleById(_stakingModuleIds[i]).stakingModuleAddress; - IStakingModule(moduleAddr).handleRewardsMinted(_totalShares[i]); + IStakingModule(moduleAddr).onRewardsMinted(_totalShares[i]); unchecked { ++i; } } } @@ -272,6 +277,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version { for (uint256 i = 0; i < _stakingModuleIds.length; ) { StakingModule storage stakingModule = _getStakingModuleById(_stakingModuleIds[i]); + uint256 prevReportedExitedValidatorsCount = stakingModule.exitedValidatorsCount; if (_exitedValidatorsCounts[i] < prevReportedExitedValidatorsCount) { revert ExitedValidatorsCountCannotDecrease(); @@ -290,6 +296,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version prevReportedExitedValidatorsCount - totalExitedValidatorsCount ); } + stakingModule.exitedValidatorsCount = _exitedValidatorsCounts[i]; unchecked { ++i; } } @@ -303,61 +310,36 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version external onlyRole(REPORT_EXITED_VALIDATORS_ROLE) { - if (_nodeOperatorIds.length % 8 != 0 || _exitedValidatorsCounts.length % 16 != 0) { - revert InvalidReportData(); - } - - uint256 totalNodeOps = _nodeOperatorIds.length / 8; - if (_exitedValidatorsCounts.length / 16 != totalNodeOps) { - revert InvalidReportData(); - } - - StakingModule storage stakingModule = _getStakingModuleById(_stakingModuleId); - IStakingModule moduleContract = IStakingModule(stakingModule.stakingModuleAddress); - ( - uint256 prevExitedValidatorsCount, - /* uint256 totalDepositedValidators */, - /* uint256 depositableValidatorsCount */ - ) = moduleContract.getStakingModuleSummary(); - - for (uint256 i = 0; i < totalNodeOps; ) { - uint256 nodeOpId; - uint256 validatorsCount; - /// @solidity memory-safe-assembly - assembly { - nodeOpId := shr(192, calldataload(add(_nodeOperatorIds.offset, mul(i, 8)))) - validatorsCount := shr(128, calldataload(add(_exitedValidatorsCounts.offset, mul(i, 16)))) - i := add(i, 1) - } - moduleContract.updateExitedValidatorsCount(nodeOpId, validatorsCount); - } - - uint256 prevReportedExitedValidatorsCount = stakingModule.exitedValidatorsCount; - ( - uint256 newExitedValidatorsCount, - /* uint256 totalDepositedValidators */, - /* uint256 depositableValidatorsCount */ - ) = moduleContract.getStakingModuleSummary(); - if (prevExitedValidatorsCount < prevReportedExitedValidatorsCount && - newExitedValidatorsCount >= prevReportedExitedValidatorsCount - ) { - // oracle finished updating exited validators for all node ops - moduleContract.onAllValidatorCountersUpdated(); - } + address moduleAddr = _getStakingModuleById(_stakingModuleId).stakingModuleAddress; + _checkValidatorsByNodeOperatorReportData(_nodeOperatorIds, _exitedValidatorsCounts); + IStakingModule(moduleAddr).updateExitedValidatorsCount( + _nodeOperatorIds, + _exitedValidatorsCounts + ); } - struct ValidatorsCountCorrection { + struct ValidatorsCountsCorrection { + /// @notice The expected current number of exited validators of the module that is + /// being corrected. uint256 currentModuleExitedValidatorsCount; + /// @notice The expected current number of exited validators of the node operator + /// that is being corrected. uint256 currentNodeOperatorExitedValidatorsCount; + /// @notice The expected current number of stuck validators of the node operator + /// that is being corrected. uint256 currentNodeOperatorStuckValidatorsCount; + /// @notice The corrected number of exited validators of the module. uint256 newModuleExitedValidatorsCount; + /// @notice The corrected number of exited validators of the node operator. uint256 newNodeOperatorExitedValidatorsCount; + /// @notice The corrected number of stuck validators of the node operator. uint256 newNodeOperatorStuckValidatorsCount; } /** - * @notice Sets exited validators count for the given module and given node operator in that module - * without performing critical safety checks, e.g. that exited validators count cannot decrease. + * @notice Sets exited validators count for the given module and given node operator in that + * module without performing critical safety checks, e.g. that exited validators count cannot + * decrease. * * Should only be used by the DAO in extreme cases and with sufficient precautions to correct * invalid data reported by the oracle committee due to a bug in the oracle daemon. @@ -366,34 +348,19 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version * * @param _nodeOperatorId ID of the node operator. * - * @param _triggerUpdateFinish Whether to call `onAllValidatorCountersUpdated` on + * @param _triggerUpdateFinish Whether to call `onExitedAndStuckValidatorsCountsUpdated` on * the module after applying the corrections. * - * @param _correction.currentModuleExitedValidatorsCount The expected current number of exited - * validators of the module that is being corrected. - * - * @param _correction.currentNodeOperatorExitedValidatorsCount The expected current number of exited - * validators of the node operator that is being corrected. - * - * @param _correction.currentNodeOperatorStuckValidatorsCount The expected current number of stuck - * validators of the node operator that is being corrected. - * - * @param _correction.newModuleExitedValidatorsCount The corrected number of exited validators of the module. + * @param _correction See the docs for the `ValidatorsCountsCorrection` struct. * - * @param _correction.newNodeOperatorExitedValidatorsCount The corrected number of exited validators of the - * node operator. - * - * @param _correction.newNodeOperatorStuckValidatorsCount The corrected number of stuck validators of the - * node operator. - * - * Reverts if the current numbers of exited and stuck validators of the module and node operator don't - * match the supplied expected current values. + * Reverts if the current numbers of exited and stuck validators of the module and node operator + * don't match the supplied expected current values. */ function unsafeSetExitedValidatorsCount( uint256 _stakingModuleId, uint256 _nodeOperatorId, bool _triggerUpdateFinish, - ValidatorsCountCorrection memory _correction + ValidatorsCountsCorrection memory _correction ) external onlyRole(UNSAFE_SET_EXITED_VALIDATORS_ROLE) @@ -432,7 +399,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version ); if (_triggerUpdateFinish) { - IStakingModule(moduleAddr).onAllValidatorCountersUpdated(); + IStakingModule(moduleAddr).onExitedAndStuckValidatorsCountsUpdated(); } } @@ -444,27 +411,28 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version external onlyRole(REPORT_EXITED_VALIDATORS_ROLE) { - if (_nodeOperatorIds.length % 8 != 0 || _stuckValidatorsCounts.length % 16 != 0) { - revert InvalidReportData(); - } + address moduleAddr = _getStakingModuleById(_stakingModuleId).stakingModuleAddress; + _checkValidatorsByNodeOperatorReportData(_nodeOperatorIds, _stuckValidatorsCounts); + IStakingModule(moduleAddr).updateStuckValidatorsCount(_nodeOperatorIds, _stuckValidatorsCounts); + } - uint256 totalNodeOps = _nodeOperatorIds.length / 8; - if (_stuckValidatorsCounts.length / 16 != totalNodeOps) { - revert InvalidReportData(); - } + function onValidatorsCountsByNodeOperatorReportingFinished() + external + onlyRole(REPORT_EXITED_VALIDATORS_ROLE) + { + uint256 stakingModulesCount = getStakingModulesCount(); - address moduleAddr = _getStakingModuleById(_stakingModuleId).stakingModuleAddress; + for (uint256 i; i < stakingModulesCount; ) { + StakingModule storage stakingModule = _getStakingModuleByIndex(i); + IStakingModule moduleContract = IStakingModule(stakingModule.stakingModuleAddress); - for (uint256 i = 0; i < totalNodeOps; ) { - uint256 nodeOpId; - uint256 validatorsCount; - /// @solidity memory-safe-assembly - assembly { - nodeOpId := shr(192, calldataload(add(_nodeOperatorIds.offset, mul(i, 8)))) - validatorsCount := shr(128, calldataload(add(_stuckValidatorsCounts.offset, mul(i, 16)))) - i := add(i, 1) + (uint256 exitedValidatorsCount, , ) = moduleContract.getStakingModuleSummary(); + if (exitedValidatorsCount == stakingModule.exitedValidatorsCount) { + // oracle finished updating exited validators for all node ops + moduleContract.onExitedAndStuckValidatorsCountsUpdated(); } - IStakingModule(moduleAddr).updateStuckValidatorsCount(nodeOpId, validatorsCount); + + unchecked { ++i; } } } @@ -827,22 +795,25 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version activeValidatorsCount = totalDepositedValidatorsCount - totalExitedValidatorsCount; } - /** - * @dev calculate max count of deposits which staking module can provide data for based on the - * current Staking Router balance and buffered Ether amount - * - * @param _stakingModuleId id of the staking module to be deposited - * @return max number of deposits might be done using given staking module - */ - function getStakingModuleMaxDepositsCount(uint256 _stakingModuleId) public view + /// @dev calculate the max count of deposits which the staking module can provide data for based + /// on the passed `_maxDepositsValue` amount + /// @param _stakingModuleId id of the staking module to be deposited + /// @param _maxDepositsValue max amount of ether that might be used for deposits count calculation + /// @return max number of deposits might be done using the given staking module + function getStakingModuleMaxDepositsCount(uint256 _stakingModuleId, uint256 _maxDepositsValue) + public + view validStakingModuleId(_stakingModuleId) returns (uint256) { + ( + /* uint256 allocated */, + uint256[] memory newDepositsAllocation, + StakingModuleCache[] memory stakingModulesCache + ) = _getDepositsAllocation(_maxDepositsValue / DEPOSIT_SIZE); uint256 stakingModuleIndex = _getStakingModuleIndexById(_stakingModuleId); - uint256 depositsToAllocate = getLido().getDepositableEther() / DEPOSIT_SIZE; - (, uint256[] memory newDepositsAllocation, StakingModuleCache[] memory stakingModulesCache) - = _getDepositsAllocation(depositsToAllocate); - return newDepositsAllocation[stakingModuleIndex] - stakingModulesCache[stakingModuleIndex].activeValidatorsCount; + return + newDepositsAllocation[stakingModuleIndex] - stakingModulesCache[stakingModuleIndex].activeValidatorsCount; } /** @@ -981,59 +952,49 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /** * @dev Invokes a deposit call to the official Deposit contract - * @param _maxDepositsCount max deposits count + * @param _depositsCount number of deposits to make * @param _stakingModuleId id of the staking module to be deposited * @param _depositCalldata staking module calldata */ function deposit( - uint256 _maxDepositsCount, + uint256 _depositsCount, uint256 _stakingModuleId, bytes calldata _depositCalldata - ) external payable validStakingModuleId(_stakingModuleId) returns (uint256 depositsCount) { + ) external payable validStakingModuleId(_stakingModuleId) { if (msg.sender != LIDO_POSITION.getStorageAddress()) revert AppAuthLidoFailed(); - uint256 depositableEth = msg.value; - if (depositableEth == 0) { - _transferBalanceEthToLido(); - return 0; + uint256 depositsValue = msg.value; + if (depositsValue == 0 || depositsValue != _depositsCount * DEPOSIT_SIZE) { + revert InvalidDepositsValue(depositsValue); } bytes32 withdrawalCredentials = getWithdrawalCredentials(); if (withdrawalCredentials == 0) revert EmptyWithdrawalsCredentials(); - uint256 stakingModuleIndex = _getStakingModuleIndexById(_stakingModuleId); - StakingModule storage stakingModule = _getStakingModuleByIndex(stakingModuleIndex); - if (StakingModuleStatus(stakingModule.status) != StakingModuleStatus.Active) revert StakingModuleNotActive(); - - uint256 maxDepositsCount = Math256.min( - _maxDepositsCount, - getStakingModuleMaxDepositsCount(_stakingModuleId) - ); - - if (maxDepositsCount > 0) { - bytes memory publicKeysBatch; - bytes memory signaturesBatch; - (depositsCount, publicKeysBatch, signaturesBatch) = IStakingModule(stakingModule.stakingModuleAddress) - .obtainDepositData(maxDepositsCount, _depositCalldata); + StakingModule storage stakingModule = _getStakingModuleById(_stakingModuleId); + if (StakingModuleStatus(stakingModule.status) != StakingModuleStatus.Active) { + revert StakingModuleNotActive(); + } - if (depositsCount > 0) { - _makeBeaconChainDeposits32ETH(depositsCount, abi.encodePacked(withdrawalCredentials), publicKeysBatch, signaturesBatch); + (bytes memory publicKeysBatch, bytes memory signaturesBatch) = + IStakingModule(stakingModule.stakingModuleAddress) + .obtainDepositData(_depositsCount, _depositCalldata); - stakingModule.lastDepositAt = uint64(block.timestamp); - stakingModule.lastDepositBlock = block.number; + uint256 etherBalanceBeforeDeposits = address(this).balance; + _makeBeaconChainDeposits32ETH( + _depositsCount, + abi.encodePacked(withdrawalCredentials), + publicKeysBatch, + signaturesBatch + ); + uint256 etherBalanceAfterDeposits = address(this).balance; - emit StakingRouterETHDeposited(_stakingModuleId, depositsCount * DEPOSIT_SIZE); - } - } - _transferBalanceEthToLido(); - } + /// @dev all sent ETH must be deposited and self balance stay the same + assert(etherBalanceBeforeDeposits - etherBalanceAfterDeposits == depositsValue); - /// @dev transfer all remaining balance to Lido contract - function _transferBalanceEthToLido() internal { - uint256 balance = address(this).balance; - if (balance > 0) { - getLido().receiveStakingRouterDepositRemainder{value: balance}(); - } + stakingModule.lastDepositAt = uint64(block.timestamp); + stakingModule.lastDepositBlock = block.number; + emit StakingRouterETHDeposited(_stakingModuleId, depositsValue); } /** @@ -1062,6 +1023,22 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version return WITHDRAWAL_CREDENTIALS_POSITION.getStorageBytes32(); } + function _checkValidatorsByNodeOperatorReportData( + bytes calldata _nodeOperatorIds, + bytes calldata _validatorsCounts + ) internal { + if (_nodeOperatorIds.length % 8 != 0 || _validatorsCounts.length % 16 != 0) { + revert InvalidReportData(3); + } + uint256 nodeOperatorsCount = _nodeOperatorIds.length / 8; + if (_validatorsCounts.length / 16 != nodeOperatorsCount) { + revert InvalidReportData(2); + } + if (nodeOperatorsCount == 0) { + revert InvalidReportData(1); + } + } + /** * @dev load modules into a memory cache * diff --git a/contracts/0.8.9/WithdrawalQueue.sol b/contracts/0.8.9/WithdrawalQueue.sol index b39d57215..1f190a64d 100644 --- a/contracts/0.8.9/WithdrawalQueue.sol +++ b/contracts/0.8.9/WithdrawalQueue.sol @@ -8,6 +8,7 @@ import {WithdrawalQueueBase} from "./WithdrawalQueueBase.sol"; import {IERC20} from "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; import {IERC20Permit} from "@openzeppelin/contracts-v4.4/token/ERC20/extensions/draft-IERC20Permit.sol"; +import {EnumerableSet} from "@openzeppelin/contracts-v4.4/utils/structs/EnumerableSet.sol"; import {AccessControlEnumerable} from "./utils/access/AccessControlEnumerable.sol"; import {UnstructuredStorage} from "./lib/UnstructuredStorage.sol"; import {PausableUntil} from "./utils/PausableUntil.sol"; @@ -32,6 +33,7 @@ interface IWstETH is IERC20, IERC20Permit { /// @author folkyatina abstract contract WithdrawalQueue is AccessControlEnumerable, PausableUntil, WithdrawalQueueBase, Versioned { using UnstructuredStorage for bytes32; + using EnumerableSet for EnumerableSet.UintSet; /// Bunker mode activation timestamp bytes32 internal constant BUNKER_MODE_SINCE_TIMESTAMP_POSITION = @@ -65,14 +67,11 @@ abstract contract WithdrawalQueue is AccessControlEnumerable, PausableUntil, Wit event BunkerModeDisabled(); error AdminZeroAddress(); - error AlreadyInitialized(); - error Uninitialized(); - error Unimplemented(); error RequestAmountTooSmall(uint256 _amountOfStETH); error RequestAmountTooLarge(uint256 _amountOfStETH); error InvalidReportTimestamp(); - error LengthsMismatch(uint256 _expectedLength, uint256 _actualLength); error RequestIdsNotSorted(); + error ZeroRecipient(); /// @param _wstETH address of WstETH contract constructor(IWstETH _wstETH) { @@ -142,8 +141,7 @@ abstract contract WithdrawalQueue is AccessControlEnumerable, PausableUntil, Wit if (_owner == address(0)) _owner = msg.sender; requestIds = new uint256[](amounts.length); for (uint256 i = 0; i < amounts.length; ++i) { - uint256 amountOfWstETH = amounts[i]; - requestIds[i] = _requestWithdrawalWstETH(amountOfWstETH, _owner); + requestIds[i] = _requestWithdrawalWstETH(amounts[i], _owner); } } @@ -189,56 +187,94 @@ abstract contract WithdrawalQueue is AccessControlEnumerable, PausableUntil, Wit return requestWithdrawalsWstETH(_amounts, _owner); } - /// @notice return statuses for the bunch of requests - /// @param _requestIds list of withdrawal request ids and hints to claim - function getWithdrawalRequestStatuses(uint256[] calldata _requestIds) + /// @notice Returns all withdrawal requests that belongs to the `_owner` address + /// + /// WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + /// to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + /// this function has an unbounded cost, and using it as part of a state-changing function may render the function + /// uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + function getWithdrawalRequests(address _owner) external view returns (uint256[] memory requestsIds) { + return _getRequestsByOwner()[_owner].values(); + } + + /// @notice Returns statuses for the array of request ids + /// @param _requestIds array of withdrawal request ids + function getWithdrawalStatus(uint256[] calldata _requestIds) external view returns (WithdrawalRequestStatus[] memory statuses) { statuses = new WithdrawalRequestStatus[](_requestIds.length); for (uint256 i = 0; i < _requestIds.length; ++i) { - statuses[i] = getWithdrawalRequestStatus(_requestIds[i]); + statuses[i] = _getStatus(_requestIds[i]); } } - struct ClaimWithdrawalInput { - /// @notice id of the finalized requests to claim - uint256 requestId; - /// @notice rate index that should be used for claiming - uint256 hint; + /// @notice Returns array of claimable eth amounts that is locked for each request + /// @param _requestIds array of request ids to find claimable ether for + /// @param _hints checkpoint hint for each id. + /// Can be retrieved with `findCheckpointHints()` or `findCheckpointHintsUnbounded()` + function getClaimableEther(uint256[] calldata _requestIds, uint256[] calldata _hints) + external + view + returns (uint256[] memory claimableEthValues) + { + claimableEthValues = new uint256[](_requestIds.length); + for (uint256 i = 0; i < _requestIds.length; ++i) { + claimableEthValues[i] = _getClaimableEther(_requestIds[i], _hints[i]); + } } - /// @notice Claim withdrawals batch once finalized (claimable). - /// @param _claimWithdrawalInputs list of withdrawal request ids and hints to claim - function claimWithdrawals(ClaimWithdrawalInput[] calldata _claimWithdrawalInputs) external { - for (uint256 i = 0; i < _claimWithdrawalInputs.length; ++i) { - _claimWithdrawalTo(_claimWithdrawalInputs[i].requestId, _claimWithdrawalInputs[i].hint, msg.sender); - _emitTransfer(msg.sender, address(0), _claimWithdrawalInputs[i].requestId); + /// @notice Claim a batch of withdrawal requests once finalized (claimable) sending ether to `_recipient` + /// @param _requestIds array of request ids to claim + /// @param _hints checkpoint hint for each id. + /// Can be retrieved with `findCheckpointHints()` or `findCheckpointHintsUnbounded()` + /// @param _recipient address where claimed ether will be sent to + /// @dev + /// Reverts if recipient is equal to zero + /// Reverts if any requestId or hint in arguments are not valid + /// Reverts if any request is not finalized or already claimed + /// Reverts if msg sender is not an owner of the requests + function claimWithdrawalsTo(uint256[] calldata _requestIds, uint256[] calldata _hints, address _recipient) + external + { + if (_recipient == address(0)) revert ZeroRecipient(); + + for (uint256 i = 0; i < _requestIds.length; ++i) { + _claim(_requestIds[i], _hints[i], _recipient); + _emitTransfer(msg.sender, address(0), _requestIds[i]); } } - /// @notice Claim `_requestId` request and transfer locked ether to the owner - /// @param _requestId request id to claim - /// @param _hint hint for checkpoint index to avoid extensive search over the checkpointHistory. - /// Can be retrieved with `findCheckpointHint()` or `findCheckpointHintUnbounded()` - /// @param _recipient address where claimed ether will be sent to - function claimWithdrawalTo(uint256 _requestId, uint256 _hint, address _recipient) external { - _claimWithdrawalTo(_requestId, _hint, _recipient); - _emitTransfer(msg.sender, address(0), _requestId); + /// @notice Claim a batch of withdrawal requests once finalized (claimable) sending locked ether to the owner + /// @param _requestIds array of request ids to claim + /// @param _hints checkpoint hint for each id. + /// Can be retrieved with `findCheckpointHints()` or `findCheckpointHintsUnbounded()` + /// @dev + /// Reverts if any requestId or hint in arguments are not valid + /// Reverts if any request is not finalized or already claimed + /// Reverts if msg sender is not an owner of the requests + function claimWithdrawals(uint256[] calldata _requestIds, uint256[] calldata _hints) external { + for (uint256 i = 0; i < _requestIds.length; ++i) { + _claim(_requestIds[i], _hints[i], msg.sender); + _emitTransfer(msg.sender, address(0), _requestIds[i]); + } } - /// @notice Claim `_requestId` request and transfer locked ether to the owner + /// @notice Claim one`_requestId` request once finalized sending locked ether to the owner /// @param _requestId request id to claim - /// @dev will use `findCheckpointHintUnbounded()` to find a hint, what can lead to OOG - /// Prefer `claimWithdrawal(uint256 _requestId, uint256 _hint)` to save gas + /// @dev use unbounded loop to find a hint, which can lead to OOG + /// @dev + /// Reverts if requestId or hint are not valid + /// Reverts if request is not finalized or already claimed + /// Reverts if msg sender is not an owner of request function claimWithdrawal(uint256 _requestId) external { - _claimWithdrawalTo(_requestId, findCheckpointHintUnbounded(_requestId), msg.sender); + _claim(_requestId, _findCheckpointHint(_requestId, 1, getLastCheckpointIndex()), msg.sender); _emitTransfer(msg.sender, address(0), _requestId); } /// @notice Finds the list of hints for the given `_requestIds` searching among the checkpoints with indices - /// in the range `[_firstIndex, _lastIndex]` + /// in the range `[_firstIndex, _lastIndex]`. NB! Array of request ids should be sorted /// @param _requestIds ids of the requests sorted in the ascending order to get hints for /// @param _firstIndex left boundary of the search range /// @param _lastIndex right boundary of the search range @@ -252,16 +288,15 @@ abstract contract WithdrawalQueue is AccessControlEnumerable, PausableUntil, Wit uint256 prevRequestId = 0; for (uint256 i = 0; i < _requestIds.length; ++i) { if (_requestIds[i] < prevRequestId) revert RequestIdsNotSorted(); - hintIds[i] = findCheckpointHint(_requestIds[i], _firstIndex, _lastIndex); + hintIds[i] = _findCheckpointHint(_requestIds[i], _firstIndex, _lastIndex); _firstIndex = hintIds[i]; prevRequestId = _requestIds[i]; } } /// @notice Finds the list of hints for the given `_requestIds` searching among the checkpoints with indices - /// in the range `[1, lastCheckpointIndex]` + /// in the range `[1, lastCheckpointIndex]`. NB! Array of request ids should be sorted /// @dev WARNING! OOG is possible if used onchain. - /// See `findCheckpointHints(uint256[] calldata _requestIds, uint256 _firstIndex, uint256 _lastIndex)` for onchain use /// @param _requestIds ids of the requests sorted in the ascending order to get hints for function findCheckpointHintsUnbounded(uint256[] calldata _requestIds) public @@ -326,20 +361,19 @@ abstract contract WithdrawalQueue is AccessControlEnumerable, PausableUntil, Wit internal { _initializeQueue(); - _initializePausable(); + _pause(PAUSE_INFINITELY); _initializeContractVersionTo(1); _grantRole(DEFAULT_ADMIN_ROLE, _admin); - _grantRole(PAUSE_ROLE, _pauser); - _grantRole(RESUME_ROLE, _resumer); - _grantRole(FINALIZE_ROLE, _finalizer); - _grantRole(BUNKER_MODE_REPORT_ROLE, _bunkerReporter); + if (_pauser != address(0)) _grantRole(PAUSE_ROLE, _pauser); + if (_resumer != address(0)) _grantRole(RESUME_ROLE, _resumer); + if (_finalizer != address(0)) _grantRole(FINALIZE_ROLE, _finalizer); + if (_bunkerReporter != address(0)) _grantRole(BUNKER_MODE_REPORT_ROLE, _bunkerReporter); - RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(PAUSE_INFINITELY); // pause it explicitly BUNKER_MODE_SINCE_TIMESTAMP_POSITION.setStorageUint256(BUNKER_MODE_DISABLED_TIMESTAMP); - emit InitializedV1(_admin, _pauser, _resumer, _finalizer, msg.sender); + emit InitializedV1(_admin, _pauser, _resumer, _finalizer, _bunkerReporter); } function _requestWithdrawal(uint256 _amountOfStETH, address _owner) internal returns (uint256 requestId) { @@ -372,4 +406,16 @@ abstract contract WithdrawalQueue is AccessControlEnumerable, PausableUntil, Wit revert RequestAmountTooLarge(_amountOfStETH); } } + + /// @notice returns claimable ether under the request with _requestId. + function _getClaimableEther(uint256 _requestId, uint256 _hint) internal view returns (uint256) { + if (_requestId == 0 || _requestId > getLastRequestId()) revert InvalidRequestId(_requestId); + + if (_requestId > getLastFinalizedRequestId()) return 0; + + WithdrawalRequest storage request = _getQueue()[_requestId]; + if (request.claimed) return 0; + + return _calculateClaimableEther(request, _requestId, _hint); + } } diff --git a/contracts/0.8.9/WithdrawalQueueBase.sol b/contracts/0.8.9/WithdrawalQueueBase.sol index 10695fda8..74487cec4 100644 --- a/contracts/0.8.9/WithdrawalQueueBase.sol +++ b/contracts/0.8.9/WithdrawalQueueBase.sol @@ -49,7 +49,7 @@ abstract contract WithdrawalQueueBase { /// @notice sum of the all shares locked for withdrawal up to this request uint128 cumulativeShares; /// @notice address that can claim or transfer the request - address payable owner; + address owner; /// @notice block.timestamp when the request was created uint64 timestamp; /// @notice flag if the request was claimed @@ -65,6 +65,22 @@ abstract contract WithdrawalQueueBase { uint96 discountFactor; } + /// @notice output format struct for `_getWithdrawalStatus()` method + struct WithdrawalRequestStatus { + /// @notice stETH token amount that was locked on withdrawal queue for this request + uint256 amountOfStETH; + /// @notice amount of stETH shares locked on withdrawal queue for this request + uint256 amountOfShares; + /// @notice address that can claim or transfer this request + address owner; + /// @notice timestamp of when the request was created, in seconds + uint256 timestamp; + /// @notice true, if request is finalized + bool isFinalized; + /// @notice true, if request is claimed. Request is claimable if (isFinalized && !isClaimed) + bool isClaimed; + } + /// @dev Contains both stETH token amount and its corresponding shares amount event WithdrawalRequested( uint256 indexed requestId, @@ -87,8 +103,8 @@ abstract contract WithdrawalQueueBase { error NotOwner(address _sender, address _owner); error InvalidRequestId(uint256 _requestId); error InvalidRequestIdRange(uint256 startId, uint256 endId); + error RequestNotFoundOrNotFinalized(uint256 _requestId); error NotEnoughEther(); - error RequestNotFinalized(uint256 _requestId); error RequestAlreadyClaimed(uint256 _requestId); error InvalidHint(uint256 _hint); error CantSendValueRecipientMayHaveReverted(); @@ -124,113 +140,6 @@ abstract contract WithdrawalQueueBase { _getQueue()[getLastRequestId()].cumulativeStETH - _getQueue()[getLastFinalizedRequestId()].cumulativeStETH; } - /// @notice Returns all withdrawal requests that belongs to the `_owner` address - /// - /// WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - /// to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - /// this function has an unbounded cost, and using it as part of a state-changing function may render the function - /// uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - function getWithdrawalRequests(address _owner) external view returns (uint256[] memory requestsIds) { - return _getRequestsByOwner()[_owner].values(); - } - - /// @notice output format struct for `getWithdrawalRequestStatus()` - struct WithdrawalRequestStatus { - /// @notice stETH token amount that was locked on withdrawal queue for this request - uint256 amountOfStETH; - /// @notice amount of stETH shares locked on withdrawal queue for this request - uint256 amountOfShares; - /// @notice address that can claim or transfer this request - address owner; - /// @notice timestamp of when the request was created, in seconds - uint256 timestamp; - /// @notice true, if request is finalized - bool isFinalized; - /// @notice true, if request is claimed. Request is claimable if (isFinalized && !isClaimed) - bool isClaimed; - } - - /// @notice Returns status of the withdrawal request with `_requestId` id - function getWithdrawalRequestStatus(uint256 _requestId) - public - view - returns (WithdrawalRequestStatus memory status) - { - if (_requestId == 0 || _requestId > getLastRequestId()) revert InvalidRequestId(_requestId); - - WithdrawalRequest memory request = _getQueue()[_requestId]; - WithdrawalRequest memory previousRequest = _getQueue()[_requestId - 1]; - - status = WithdrawalRequestStatus( - request.cumulativeStETH - previousRequest.cumulativeStETH, - request.cumulativeShares - previousRequest.cumulativeShares, - request.owner, - request.timestamp, - _requestId <= getLastFinalizedRequestId(), - request.claimed - ); - } - - /// @notice View function to find a hint to pass it to `claimWithdrawal()`. - /// @dev WARNING! OOG is possible if used onchain, contains unbounded loop inside - /// See `findCheckpointHint(uint256 _requestId, uint256 _firstIndex, uint256 _lastIndex)` for onchain use - /// @param _requestId request id to be claimed with this hint - function findCheckpointHintUnbounded(uint256 _requestId) public view returns (uint256) { - return findCheckpointHint(_requestId, 1, getLastCheckpointIndex()); - } - - /// @notice View function to find a checkpoint hint for `claimWithdrawal()` - /// Search will be performed in the range of `[_firstIndex, _lastIndex]` - /// - /// NB!: Range search ought to be used to optimize gas cost. - /// You can utilize the following invariant: - /// `if (requestId2 > requestId1) than hint2 >= hint1`, - /// so you can search for `hint2` in the range starting from `hint1` - /// - /// @param _requestId request id we are searching the checkpoint for - /// @param _start index of the left boundary of the search range - /// @param _end index of the right boundary of the search range - /// - /// @return value that hints `claimWithdrawal` to find the discount for the request, - /// or 0 if hint not found in the range - function findCheckpointHint(uint256 _requestId, uint256 _start, uint256 _end) public view returns (uint256) { - if (_requestId == 0) revert InvalidRequestId(_requestId); - if (_start == 0) revert InvalidRequestIdRange(_start, _end); - uint256 lastCheckpointIndex = getLastCheckpointIndex(); - if (_end > lastCheckpointIndex) revert InvalidRequestIdRange(_start, _end); - if (_requestId > getLastFinalizedRequestId()) revert RequestNotFinalized(_requestId); - - if (_start > _end) return NOT_FOUND; // we have an empty range to search in, so return NOT_FOUND - - // Right boundary - if (_requestId >= _getCheckpoints()[_end].fromRequestId) { - // it's the last checkpoint, so it's valid - if (_end == lastCheckpointIndex) return _end; - // it fits right before the next checkpoint - if (_requestId < _getCheckpoints()[_end + 1].fromRequestId) return _end; - - return NOT_FOUND; - } - // Left boundary - if (_requestId < _getCheckpoints()[_start].fromRequestId) { - return NOT_FOUND; - } - - // Binary search - uint256 min = _start; - uint256 max = _end; - - while (max > min) { - uint256 mid = (max + min + 1) / 2; - if (_getCheckpoints()[mid].fromRequestId <= _requestId) { - min = mid; - } else { - max = mid - 1; - } - } - return min; - } - /// @notice Search for the latest request in the queue in the range of `[startId, endId]`, /// that has `request.timestamp <= maxTimestamp` /// @@ -397,7 +306,7 @@ abstract contract WithdrawalQueueBase { _amountOfETH, requestToFinalize.cumulativeShares - lastFinalizedRequest.cumulativeShares, block.timestamp - ); + ); } /// @dev creates a new `WithdrawalRequest` in the queue @@ -417,38 +326,123 @@ abstract contract WithdrawalQueueBase { _setLastRequestId(requestId); _getQueue()[requestId] = - WithdrawalRequest(cumulativeStETH, cumulativeShares, payable(_owner), uint64(block.timestamp), false); - _getRequestsByOwner()[_owner].add(requestId); + WithdrawalRequest(cumulativeStETH, cumulativeShares, _owner, uint64(block.timestamp), false); + assert(_getRequestsByOwner()[_owner].add(requestId)); emit WithdrawalRequested(requestId, msg.sender, _owner, _amountOfStETH, _amountOfShares); } - /// @notice Claim `_requestId` request and transfer related ether to the `_recipient`. Emits WithdrawalClaimed event - /// @param _requestId request id to claim - /// @param _hint hint for discount checkpoint index to avoid extensive search over the checkpoints. - /// Can be found with `findCheckpointHint()` or `findCheckpointHintUnbounded()` - /// @param _recipient address to send ether to. If `==address(0)` then will send to the owner. - function _claimWithdrawalTo(uint256 _requestId, uint256 _hint, address _recipient) internal { - if (_hint == 0) revert InvalidHint(_hint); + /// @notice Returns status of the withdrawal request with `_requestId` id + function _getStatus(uint256 _requestId) internal view returns (WithdrawalRequestStatus memory status) { + if (_requestId == 0 || _requestId > getLastRequestId()) revert InvalidRequestId(_requestId); + + WithdrawalRequest memory request = _getQueue()[_requestId]; + WithdrawalRequest memory previousRequest = _getQueue()[_requestId - 1]; + + status = WithdrawalRequestStatus( + request.cumulativeStETH - previousRequest.cumulativeStETH, + request.cumulativeShares - previousRequest.cumulativeShares, + request.owner, + request.timestamp, + _requestId <= getLastFinalizedRequestId(), + request.claimed + ); + } - if (_requestId == 0) revert InvalidRequestId(0); - if (_requestId > getLastFinalizedRequestId()) revert RequestNotFinalized(_requestId); + /// @notice View function to find a checkpoint hint for `claimWithdrawal()` + /// Search will be performed in the range of `[_firstIndex, _lastIndex]` + /// + /// NB!: Range search ought to be used to optimize gas cost. + /// You can utilize the following invariant: + /// `if (requestId2 > requestId1) than hint2 >= hint1`, + /// so you can search for `hint2` in the range starting from `hint1` + /// + /// @param _requestId request id we are searching the checkpoint for + /// @param _start index of the left boundary of the search range + /// @param _end index of the right boundary of the search range + /// + /// @return value that hints `claimWithdrawal` to find the discount for the request, + /// or 0 if hint not found in the range + function _findCheckpointHint(uint256 _requestId, uint256 _start, uint256 _end) internal view returns (uint256) { + if (_requestId == 0) revert InvalidRequestId(_requestId); + if (_start == 0) revert InvalidRequestIdRange(_start, _end); uint256 lastCheckpointIndex = getLastCheckpointIndex(); - if (_hint > lastCheckpointIndex) revert InvalidHint(_hint); + if (_end > lastCheckpointIndex) revert InvalidRequestIdRange(_start, _end); + if (_requestId > getLastFinalizedRequestId()) revert RequestNotFoundOrNotFinalized(_requestId); + + if (_start > _end) return NOT_FOUND; // we have an empty range to search in, so return NOT_FOUND + + // Right boundary + if (_requestId >= _getCheckpoints()[_end].fromRequestId) { + // it's the last checkpoint, so it's valid + if (_end == lastCheckpointIndex) return _end; + // it fits right before the next checkpoint + if (_requestId < _getCheckpoints()[_end + 1].fromRequestId) return _end; + + return NOT_FOUND; + } + // Left boundary + if (_requestId < _getCheckpoints()[_start].fromRequestId) { + return NOT_FOUND; + } + + // Binary search + uint256 min = _start; + uint256 max = _end - 1; + + while (max > min) { + uint256 mid = (max + min + 1) / 2; + if (_getCheckpoints()[mid].fromRequestId <= _requestId) { + min = mid; + } else { + max = mid - 1; + } + } + return min; + } + + /// @notice Claim `_requestId` request and transfer locked ether to `_recipient`. Emits WithdrawalClaimed event + /// @param _requestId request id to claim + /// @param _hint hint for discount checkpoint index to avoid extensive search over the checkpoints. + /// @param _recipient address to send ether to + function _claim(uint256 _requestId, uint256 _hint, address _recipient) internal { + if (_requestId == 0) revert InvalidRequestId(_requestId); + if (_requestId > getLastFinalizedRequestId()) revert RequestNotFoundOrNotFinalized(_requestId); WithdrawalRequest storage request = _getQueue()[_requestId]; + if (request.claimed) revert RequestAlreadyClaimed(_requestId); - if (msg.sender != request.owner) revert NotOwner(msg.sender, request.owner); - if (_recipient == address(0)) _recipient = request.owner; + if (request.owner != msg.sender) revert NotOwner(msg.sender, request.owner); request.claimed = true; + assert(_getRequestsByOwner()[request.owner].remove(_requestId)); + + uint256 ethWithDiscount = _calculateClaimableEther(request, _requestId, _hint); + + _setLockedEtherAmount(getLockedEtherAmount() - ethWithDiscount); + _sendValue(payable(_recipient), ethWithDiscount); + + emit WithdrawalClaimed(_requestId, msg.sender, _recipient, ethWithDiscount); + } + + /// @notice Calculates discounted ether value for `_requestId` using a provided `_hint`. Checks if hint is valid + /// @return claimableEther discounted eth for `_requestId`. Returns 0 if request is not claimable + function _calculateClaimableEther(WithdrawalRequest storage _request, uint256 _requestId, uint256 _hint) + internal + view + returns (uint256 claimableEther) + { + if (_hint == 0) revert InvalidHint(_hint); + + uint256 lastCheckpointIndex = getLastCheckpointIndex(); + if (_hint > lastCheckpointIndex) revert InvalidHint(_hint); DiscountCheckpoint memory hintCheckpoint = _getCheckpoints()[_hint]; - // ______(_______ + // ______(>______ // ^ hint if (_requestId < hintCheckpoint.fromRequestId) revert InvalidHint(_hint); if (_hint < lastCheckpointIndex) { - // ______(_______(_________ + // ______(>______(>________ // hint hint+1 ^ DiscountCheckpoint memory nextCheckpoint = _getCheckpoints()[_hint + 1]; if (nextCheckpoint.fromRequestId <= _requestId) { @@ -456,14 +450,8 @@ abstract contract WithdrawalQueueBase { } } - uint256 ethRequested = request.cumulativeStETH - _getQueue()[_requestId - 1].cumulativeStETH; - uint256 ethWithDiscount = ethRequested * hintCheckpoint.discountFactor / E27_PRECISION_BASE; - - _setLockedEtherAmount(getLockedEtherAmount() - ethWithDiscount); - - _sendValue(payable(_recipient), ethWithDiscount); - - emit WithdrawalClaimed(_requestId, msg.sender, _recipient, ethWithDiscount); + uint256 ethRequested = _request.cumulativeStETH - _getQueue()[_requestId - 1].cumulativeStETH; + return ethRequested * hintCheckpoint.discountFactor / E27_PRECISION_BASE; } // quazi-constructor @@ -471,11 +459,11 @@ abstract contract WithdrawalQueueBase { // setting dummy zero structs in checkpoints and queue beginning // to avoid uint underflows and related if-branches // 0-index is reserved as 'not_found' response in the interface everywhere - _getQueue()[0] = WithdrawalRequest(0, 0, payable(0), uint64(block.number), true); + _getQueue()[0] = WithdrawalRequest(0, 0, address(0), uint64(block.number), true); _getCheckpoints()[getLastCheckpointIndex()] = DiscountCheckpoint(0, 0); } - function _sendValue(address payable _recipient, uint256 _amount) internal { + function _sendValue(address _recipient, uint256 _amount) internal { if (address(this).balance < _amount) revert NotEnoughEther(); // solhint-disable-next-line diff --git a/contracts/0.8.9/WithdrawalRequestNFT.sol b/contracts/0.8.9/WithdrawalQueueERC721.sol similarity index 79% rename from contracts/0.8.9/WithdrawalRequestNFT.sol rename to contracts/0.8.9/WithdrawalQueueERC721.sol index b0d7930f1..e22d8201f 100644 --- a/contracts/0.8.9/WithdrawalRequestNFT.sol +++ b/contracts/0.8.9/WithdrawalQueueERC721.sol @@ -16,22 +16,36 @@ import {Strings} from "@openzeppelin/contracts-v4.4/utils/Strings.sol"; import {IWstETH, WithdrawalQueue} from "./WithdrawalQueue.sol"; import {AccessControlEnumerable} from "./utils/access/AccessControlEnumerable.sol"; import {UnstructuredRefStorage} from "./lib/UnstructuredRefStorage.sol"; +import {UnstructuredStorage} from "./lib/UnstructuredStorage.sol"; + +/** + * @title Interface defining INFTDescriptor to generate ERC721 tokenURI + */ +interface INFTDescriptor { + /** + * @notice Returns ERC721 tokenURI content + * @param _requestId is an id for particular withdrawal request + */ + function constructTokenURI(uint256 _requestId) external view returns (string memory); +} /// @title NFT implementation on top of {WithdrawalQueue} /// NFT is minted on every request and burned on claim /// /// @author psirex, folkyatina -contract WithdrawalRequestNFT is IERC721Metadata, WithdrawalQueue { +contract WithdrawalQueueERC721 is IERC721Metadata, WithdrawalQueue { using Address for address; using Strings for uint256; using EnumerableSet for EnumerableSet.UintSet; using UnstructuredRefStorage for bytes32; + using UnstructuredStorage for bytes32; - bytes32 internal constant TOKEN_APPROVALS_POSITION = keccak256("lido.WithdrawalRequestNFT.tokenApprovals"); - bytes32 internal constant OPERATOR_APPROVALS_POSITION = keccak256("lido.WithdrawalRequestNFT.operatorApprovals"); - bytes32 internal constant BASE_URI_POSITION = keccak256("lido.WithdrawalRequestNFT.baseUri"); + bytes32 internal constant TOKEN_APPROVALS_POSITION = keccak256("lido.WithdrawalQueueERC721.tokenApprovals"); + bytes32 internal constant OPERATOR_APPROVALS_POSITION = keccak256("lido.WithdrawalQueueERC721.operatorApprovals"); + bytes32 internal constant BASE_URI_POSITION = keccak256("lido.WithdrawalQueueERC721.baseUri"); + bytes32 internal constant NFT_DESCRIPTOR_ADDRESS_POSITION = keccak256("lido.WithdrawalQueueERC721.nftDescriptorAddress"); - bytes32 public constant SET_BASE_URI_ROLE = keccak256("SET_BASE_URI_ROLE"); + bytes32 public constant MANAGE_TOKEN_URI_ROLE = keccak256("MANAGE_TOKEN_URI_ROLE"); // @notion simple wrapper for base URI string // Solidity does not allow to store string in UnstructuredStorage @@ -40,6 +54,7 @@ contract WithdrawalRequestNFT is IERC721Metadata, WithdrawalQueue { } event BaseURISet(string baseURI); + event NftDescriptorAddressSet(address nftDescriptorAddress); error ApprovalToOwner(); error ApproveToCaller(); @@ -48,6 +63,7 @@ contract WithdrawalRequestNFT is IERC721Metadata, WithdrawalQueue { error TransferFromIncorrectOwner(address from, address realOwner); error TransferToZeroAddress(); error TransferFromZeroAddress(); + error TransferToThemselves(); error TransferToNonIERC721Receiver(address); error InvalidOwnerAddress(address); error StringTooLong(string str); @@ -71,7 +87,7 @@ contract WithdrawalRequestNFT is IERC721Metadata, WithdrawalQueue { public view virtual - override (IERC165, AccessControlEnumerable) + override(IERC165, AccessControlEnumerable) returns (bool) { return interfaceId == type(IERC721).interfaceId || interfaceId == type(IERC721Metadata).interfaceId @@ -89,11 +105,18 @@ contract WithdrawalRequestNFT is IERC721Metadata, WithdrawalQueue { } /// @dev See {IERC721Metadata-tokenURI}. + /// @dev If NFTDescriptor address isn't set the `baseURI` would be used for generating erc721 tokenURI. In case + /// NFTDescriptor address is set it would be used as a first-priority method. function tokenURI(uint256 _requestId) public view virtual override returns (string memory) { if (!_existsAndNotClaimed(_requestId)) revert InvalidRequestId(_requestId); - string memory baseURI = _getBaseURI().value; - return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, _requestId.toString())) : ""; + address nftDescriptorAddress = NFT_DESCRIPTOR_ADDRESS_POSITION.getStorageAddress(); + if (nftDescriptorAddress != address(0)) { + return INFTDescriptor(nftDescriptorAddress).constructTokenURI(_requestId); + } else { + string memory baseURI = _getBaseURI().value; + return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, _requestId.toString())) : ""; + } } /// @notice Base URI for computing {tokenURI}. If set, the resulting URI for each @@ -103,11 +126,26 @@ contract WithdrawalRequestNFT is IERC721Metadata, WithdrawalQueue { } /// @notice Sets the Base URI for computing {tokenURI} - function setBaseURI(string calldata _baseURI) external onlyRole(SET_BASE_URI_ROLE) { + /// @dev If NFTDescriptor address isn't set the `baseURI` would be used for generating erc721 tokenURI. In case + /// NFTDescriptor address is set it would be used as a first-priority method. + function setBaseURI(string calldata _baseURI) external onlyRole(MANAGE_TOKEN_URI_ROLE) { _getBaseURI().value = _baseURI; emit BaseURISet(_baseURI); } + /// @notice Address of NFTDescriptor contract that is responsible for tokenURI generation. + function getNFTDescriptorAddress() external view returns (address) { + return NFT_DESCRIPTOR_ADDRESS_POSITION.getStorageAddress(); + } + + /// @notice Sets the address of NFTDescriptor contract that is responsible for tokenURI generation. + /// @dev If NFTDescriptor address isn't set the `baseURI` would be used for generating erc721 tokenURI. In case + /// NFTDescriptor address is set it would be used as a first-priority method. + function setNFTDescriptorAddress(address _nftDescriptorAddress) external onlyRole(MANAGE_TOKEN_URI_ROLE) { + NFT_DESCRIPTOR_ADDRESS_POSITION.setStorageAddress(_nftDescriptorAddress); + emit NftDescriptorAddressSet(_nftDescriptorAddress); + } + /// @dev See {IERC721-balanceOf}. function balanceOf(address _owner) external view override returns (uint256) { if (_owner == address(0)) revert InvalidOwnerAddress(_owner); @@ -178,6 +216,7 @@ contract WithdrawalRequestNFT is IERC721Metadata, WithdrawalQueue { /// - `msg.sender` should be approved, or approved for all, or owner function _transfer(address _from, address _to, uint256 _requestId) internal { if (_to == address(0)) revert TransferToZeroAddress(); + if (_to == _from) revert TransferToThemselves(); if (_requestId == 0 || _requestId > getLastRequestId()) revert InvalidRequestId(_requestId); WithdrawalRequest storage request = _getQueue()[_requestId]; @@ -186,13 +225,15 @@ contract WithdrawalRequestNFT is IERC721Metadata, WithdrawalQueue { if (_from != request.owner) revert TransferFromIncorrectOwner(_from, request.owner); // here and below we are sure that `_from` is the owner of the request address msgSender = msg.sender; - if (!(_from == msgSender || isApprovedForAll(_from, msgSender) || _getTokenApprovals()[_requestId] == msgSender)) revert NotOwnerOrApproved(msgSender); + if ( + !(_from == msgSender || isApprovedForAll(_from, msgSender) || _getTokenApprovals()[_requestId] == msgSender) + ) revert NotOwnerOrApproved(msgSender); delete _getTokenApprovals()[_requestId]; request.owner = payable(_to); - _getRequestsByOwner()[_to].add(_requestId); - _getRequestsByOwner()[_from].remove(_requestId); + assert(_getRequestsByOwner()[_from].remove(_requestId)); + assert(_getRequestsByOwner()[_to].add(_requestId)); _emitTransfer(_from, _to, _requestId); } diff --git a/contracts/0.8.9/WithdrawalVault.sol b/contracts/0.8.9/WithdrawalVault.sol index 75d6902c9..c5485b785 100644 --- a/contracts/0.8.9/WithdrawalVault.sol +++ b/contracts/0.8.9/WithdrawalVault.sol @@ -65,7 +65,7 @@ contract WithdrawalVault is Versioned { } /** - * @notice Intialize the contract explicitly. + * @notice Initialize the contract explicitly. * Sets the contract version to '1'. */ function initialize() external { diff --git a/contracts/0.8.9/interfaces/IStakingModule.sol b/contracts/0.8.9/interfaces/IStakingModule.sol index 5ef430076..dee857212 100644 --- a/contracts/0.8.9/interfaces/IStakingModule.sol +++ b/contracts/0.8.9/interfaces/IStakingModule.sol @@ -79,23 +79,23 @@ interface IStakingModule { /// @notice Called by StakingRouter to signal that stETH rewards were minted for this module. /// @param _totalShares Amount of stETH shares that were minted to reward all node operators. - function handleRewardsMinted(uint256 _totalShares) external; + function onRewardsMinted(uint256 _totalShares) external; /// @notice Updates the number of the validators of the given node operator that were requested /// to exit but failed to do so in the max allowed time - /// @param _nodeOperatorId Id of the node operator - /// @param _stuckValidatorsCount New number of stuck validators of the node operator + /// @param _nodeOperatorIds bytes packed array of the node operators id + /// @param _stuckValidatorsCounts bytes packed array of the new number of STUCK validators for the node operators function updateStuckValidatorsCount( - uint256 _nodeOperatorId, - uint256 _stuckValidatorsCount + bytes calldata _nodeOperatorIds, + bytes calldata _stuckValidatorsCounts ) external; /// @notice Updates the number of the validators in the EXITED state for node operator with given id - /// @param _nodeOperatorId Id of the node operator - /// @param _exitedValidatorsCount New number of EXITED validators of the node operator + /// @param _nodeOperatorIds bytes packed array of the node operators id + /// @param _stuckValidatorsCounts bytes packed array of the new number of EXITED validators for the node operators function updateExitedValidatorsCount( - uint256 _nodeOperatorId, - uint256 _exitedValidatorsCount + bytes calldata _nodeOperatorIds, + bytes calldata _stuckValidatorsCounts ) external; /// @notice Updates the number of the refunded validators for node operator with the given id @@ -114,21 +114,25 @@ interface IStakingModule { uint256 _stuckValidatorsCount ) external; - /// @notice Obtains up to _depositsCount deposit data to be used by StakingRouter - /// to deposit to the Ethereum Deposit contract - /// @param _depositsCount Desireable number of deposits to be done + /// @notice Obtains deposit data to be used by StakingRouter to deposit to the Ethereum Deposit + /// contract + /// @dev The method MUST revert when the staking module has not enough deposit data items + /// @param _depositsCount Number of deposits to be done /// @param _calldata Staking module defined data encoded as bytes - /// @return depositsCount Actual deposits count might be done with returned data /// @return publicKeys Batch of the concatenated public validators keys /// @return signatures Batch of the concatenated deposit signatures for returned public keys - function obtainDepositData(uint256 _depositsCount, bytes calldata _calldata) external returns ( - uint256 depositsCount, - bytes memory publicKeys, - bytes memory signatures - ); - - /// @notice Called by StakingRouter after oracle finishes updating validators counters for all node operators - function onAllValidatorCountersUpdated() external; + function obtainDepositData(uint256 _depositsCount, bytes calldata _calldata) + external + returns (bytes memory publicKeys, bytes memory signatures); + + /// @notice Called by StakingRouter after it finishes updating exited and stuck validators + /// counts for this module's node operators. + /// + /// Guaranteed to be called after an oracle report is applied, regardless of whether any node + /// operator in this module has actually received any updated counts as a result of the report + /// but given that the total number of exited validators returned from getStakingModuleSummary + /// is the same as StakingRouter expects based on the total count received from the oracle. + function onExitedAndStuckValidatorsCountsUpdated() external; /// @notice Called by StakingRouter when withdrawal credentials are changed. /// @dev This method MUST discard all StakingModule's unused deposit data cause they become diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index c9bf7b61c..f6ef31d40 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -74,6 +74,8 @@ interface IStakingRouter { bytes calldata nodeOperatorIds, bytes calldata stuckValidatorsCounts ) external; + + function onValidatorsCountsByNodeOperatorReportingFinished() external; } @@ -98,7 +100,10 @@ contract AccountingOracle is BaseOracle { error CannotSubmitExtraDataBeforeMainData(); error ExtraDataAlreadyProcessed(); error ExtraDataListOnlySupportsSingleTx(); + error UnexpectedExtraDataHash(bytes32 consensusHash, bytes32 receivedHash); error UnexpectedExtraDataFormat(uint256 expectedFormat, uint256 receivedFormat); + error ExtraDataItemsCountCannotBeZeroForNonEmptyData(); + error ExtraDataHashCannotBeZeroForNonEmptyData(); error UnexpectedExtraDataItemsCount(uint256 expectedCount, uint256 receivedCount); error UnexpectedExtraDataIndex(uint256 expectedIndex, uint256 receivedIndex); error InvalidExtraDataItem(uint256 itemIndex); @@ -115,6 +120,7 @@ contract AccountingOracle is BaseOracle { struct ExtraDataProcessingState { uint64 refSlot; uint16 dataFormat; + bool submitted; uint64 itemsCount; uint64 itemsProcessed; uint256 lastSortingKey; @@ -136,7 +142,13 @@ contract AccountingOracle is BaseOracle { /// Initialization & admin functions /// - constructor(address lidoLocator, address lido, address legacyOracle, uint256 secondsPerSlot, uint256 genesisTime) + constructor( + address lidoLocator, + address lido, + address legacyOracle, + uint256 secondsPerSlot, + uint256 genesisTime + ) BaseOracle(secondsPerSlot, genesisTime) { if (lidoLocator == address(0)) revert LidoLocatorCannotBeZero(); @@ -168,17 +180,6 @@ contract AccountingOracle is BaseOracle { _initialize(admin, consensusContract, consensusVersion, lastProcessingRefSlot); } - function _initialize( - address admin, - address consensusContract, - uint256 consensusVersion, - uint256 lastProcessingRefSlot - ) internal { - _setupRole(DEFAULT_ADMIN_ROLE, admin); - - BaseOracle._initialize(consensusContract, consensusVersion, lastProcessingRefSlot); - } - /// /// Data provider interface /// @@ -320,21 +321,35 @@ contract AccountingOracle is BaseOracle { /// Extra data array can be passed in different formats, see below. /// - /// @dev Format of the extra data. Currently, only the EXTRA_DATA_FORMAT_LIST=0 is - /// supported. See the constant defining a specific extra data format for more info. + /// @dev Format of the extra data. + /// + /// Currently, only the EXTRA_DATA_FORMAT_EMPTY=0 and EXTRA_DATA_FORMAT_LIST=1 + /// formats are supported. See the constant defining a specific data format for + /// more info. + /// uint256 extraDataFormat; /// @dev Hash of the extra data. See the constant defining a specific extra data /// format for the info on how to calculate the hash. + /// + /// Must be set to a zero hash if the oracle report contains no extra data. + /// bytes32 extraDataHash; /// @dev Number of the extra data items. + /// + /// Must be set to zero if the oracle report contains no extra data. + /// uint256 extraDataItemsCount; } uint256 public constant EXTRA_DATA_TYPE_STUCK_VALIDATORS = 1; uint256 public constant EXTRA_DATA_TYPE_EXITED_VALIDATORS = 2; + /// @notice The extra data format used to signify that the oracle report contains no extra data. + /// + uint256 public constant EXTRA_DATA_FORMAT_EMPTY = 0; + /// @notice The list format for the extra data array. Used when all extra data processing /// fits into a single transaction. /// @@ -370,6 +385,13 @@ contract AccountingOracle is BaseOracle { _handleConsensusReportData(data, prevRefSlot); } + /// @notice Triggers the processing required when no extra data is present in the report, + /// i.e. when extra data format equals EXTRA_DATA_FORMAT_EMPTY. + /// + function submitReportExtraDataEmpty() external { + _submitReportExtraDataEmpty(); + } + /// @notice Submits report extra data in the EXTRA_DATA_FORMAT_LIST format for processing. /// /// @param items The extra data items list. See docs for the `EXTRA_DATA_FORMAT_LIST` @@ -387,17 +409,19 @@ contract AccountingOracle is BaseOracle { /// @notice Hash of the main report data. Zero bytes if consensus on the hash hasn't been /// reached yet for the current reporting frame. bytes32 mainDataHash; - /// @notice Whether main report data for the for the current reporting frame has been - /// already submitted. + /// @notice Whether the main report data for the current reporting frame has already been + /// submitted. bool mainDataSubmitted; - /// @notice Hash of the extra report data. Zero bytes if consensus on the main data hash - /// hasn't been reached yet, or if the main report data hasn't been submitted yet, for the - /// current reporting frame. Also zero bytes if the current reporting frame's data doesn't - /// contain any extra data. + /// @notice Hash of the extra report data. Should be ignored unless `mainDataSubmitted` + /// is true. bytes32 extraDataHash; - /// @notice Format of the extra report data for the current reporting frame. + /// @notice Format of the extra report data for the current reporting frame. Should be + /// ignored unless `mainDataSubmitted` is true. uint256 extraDataFormat; + /// @notice Whether any extra report data for the current reporting frame has been submitted. + bool extraDataSubmitted; /// @notice Total number of extra report data items for the current reporting frame. + /// Should be ignored unless `mainDataSubmitted` is true. uint256 extraDataItemsCount; /// @notice How many extra report data items are already submitted for the current /// reporting frame. @@ -425,12 +449,9 @@ contract AccountingOracle is BaseOracle { } ExtraDataProcessingState memory extraState = _storageExtraDataProcessingState().value; - if (extraState.dataHash == bytes32(0) || extraState.refSlot != processingRefSlot) { - return result; - } - result.extraDataHash = extraState.dataHash; result.extraDataFormat = extraState.dataFormat; + result.extraDataSubmitted = extraState.submitted; result.extraDataItemsCount = extraState.itemsCount; result.extraDataItemsSubmitted = extraState.itemsProcessed; } @@ -456,7 +477,8 @@ contract AccountingOracle is BaseOracle { /// /// 0. last reference slot of legacy oracle /// 1. last legacy oracle's consensus report arrives - /// 2. new oracle is deployed and enabled, legacy oracle is disabled and upgraded to compat code + /// 2. new oracle is deployed and enabled, legacy oracle is disabled and upgraded to + /// the compatibility implementation /// 3. first reference slot of the new oracle /// 4. first new oracle's consensus report arrives /// @@ -474,7 +496,7 @@ contract AccountingOracle is BaseOracle { uint256 genesisTime) = IConsensusContract(consensusContract).getChainConfig(); { - // check chain spec to match the prev. one (a block is used to reduce stack alloc) + // check chain spec to match the prev. one (a block is used to reduce stack allocation) (uint256 legacyEpochsPerFrame, uint256 legacySlotsPerEpoch, uint256 legacySecondsPerSlot, @@ -500,13 +522,26 @@ contract AccountingOracle is BaseOracle { return legacyProcessedEpoch * slotsPerEpoch; } + function _initialize( + address admin, + address consensusContract, + uint256 consensusVersion, + uint256 lastProcessingRefSlot + ) internal { + _setupRole(DEFAULT_ADMIN_ROLE, admin); + + BaseOracle._initialize(consensusContract, consensusVersion, lastProcessingRefSlot); + } + function _handleConsensusReport( ConsensusReport memory /* report */, uint256 /* prevSubmittedRefSlot */, uint256 prevProcessingRefSlot ) internal override { ExtraDataProcessingState memory state = _storageExtraDataProcessingState().value; - if (state.refSlot == prevProcessingRefSlot && state.itemsProcessed < state.itemsCount) { + if (state.refSlot == prevProcessingRefSlot && ( + !state.submitted || state.itemsProcessed < state.itemsCount + )) { emit WarnExtraDataIncompleteProcessing( prevProcessingRefSlot, state.itemsProcessed, @@ -523,8 +558,23 @@ contract AccountingOracle is BaseOracle { } function _handleConsensusReportData(ReportData calldata data, uint256 prevRefSlot) internal { - if (data.extraDataFormat != EXTRA_DATA_FORMAT_LIST) { - revert UnsupportedExtraDataFormat(data.extraDataFormat); + if (data.extraDataFormat == EXTRA_DATA_FORMAT_EMPTY) { + if (data.extraDataHash != bytes32(0)) { + revert UnexpectedExtraDataHash(bytes32(0), data.extraDataHash); + } + if (data.extraDataItemsCount != 0) { + revert UnexpectedExtraDataItemsCount(0, data.extraDataItemsCount); + } + } else { + if (data.extraDataFormat != EXTRA_DATA_FORMAT_LIST) { + revert UnsupportedExtraDataFormat(data.extraDataFormat); + } + if (data.extraDataItemsCount == 0) { + revert ExtraDataItemsCountCannotBeZeroForNonEmptyData(); + } + if (data.extraDataHash == bytes32(0)) { + revert ExtraDataHashCannotBeZeroForNonEmptyData(); + } } IOracleReportSanityChecker(LOCATOR.oracleReportSanityChecker()) @@ -567,6 +617,7 @@ contract AccountingOracle is BaseOracle { _storageExtraDataProcessingState().value = ExtraDataProcessingState({ refSlot: data.refSlot.toUint64(), dataFormat: data.extraDataFormat.toUint16(), + submitted: false, dataHash: data.extraDataHash, itemsCount: data.extraDataItemsCount.toUint16(), itemsProcessed: 0, @@ -623,6 +674,30 @@ contract AccountingOracle is BaseOracle { ); } + function _submitReportExtraDataEmpty() internal { + ExtraDataProcessingState memory procState = _storageExtraDataProcessingState().value; + _checkCanSubmitExtraData(procState, EXTRA_DATA_FORMAT_EMPTY); + if (procState.submitted) revert ExtraDataAlreadyProcessed(); + IStakingRouter(LOCATOR.stakingRouter()).onValidatorsCountsByNodeOperatorReportingFinished(); + _storageExtraDataProcessingState().value.submitted = true; + emit ExtraDataSubmitted(procState.refSlot, 0, 0); + } + + function _checkCanSubmitExtraData(ExtraDataProcessingState memory procState, uint256 format) + internal + { + _checkMsgSenderIsAllowedToSubmitData(); + _checkProcessingDeadline(); + + if (procState.refSlot != LAST_PROCESSING_REF_SLOT_POSITION.getStorageUint256()) { + revert CannotSubmitExtraDataBeforeMainData(); + } + + if (procState.dataFormat != format) { + revert UnexpectedExtraDataFormat(procState.dataFormat, format); + } + } + struct ExtraDataIterState { // volatile uint256 index; @@ -634,14 +709,8 @@ contract AccountingOracle is BaseOracle { } function _submitReportExtraDataList(bytes calldata items) internal { - _checkMsgSenderIsAllowedToSubmitData(); - _checkProcessingDeadline(); - ExtraDataProcessingState memory procState = _storageExtraDataProcessingState().value; - - if (procState.refSlot != LAST_PROCESSING_REF_SLOT_POSITION.getStorageUint256()) { - revert CannotSubmitExtraDataBeforeMainData(); - } + _checkCanSubmitExtraData(procState, EXTRA_DATA_FORMAT_LIST); if (procState.itemsProcessed == procState.itemsCount) { revert ExtraDataAlreadyProcessed(); @@ -651,13 +720,9 @@ contract AccountingOracle is BaseOracle { revert ExtraDataListOnlySupportsSingleTx(); } - if (procState.dataFormat != EXTRA_DATA_FORMAT_LIST) { - revert UnexpectedExtraDataFormat(procState.dataFormat, EXTRA_DATA_FORMAT_LIST); - } - bytes32 dataHash = keccak256(items); if (dataHash != procState.dataHash) { - revert UnexpectedDataHash(procState.dataHash, dataHash); + revert UnexpectedExtraDataHash(procState.dataHash, dataHash); } ExtraDataIterState memory iter = ExtraDataIterState({ @@ -675,10 +740,12 @@ contract AccountingOracle is BaseOracle { revert UnexpectedExtraDataItemsCount(procState.itemsCount, itemsProcessed); } - ExtraDataProcessingState storage _procState = _storageExtraDataProcessingState().value; - _procState.itemsProcessed = uint64(itemsProcessed); - _procState.lastSortingKey = iter.lastSortingKey; + procState.submitted = true; + procState.itemsProcessed = uint64(itemsProcessed); + procState.lastSortingKey = iter.lastSortingKey; + _storageExtraDataProcessingState().value = procState; + IStakingRouter(iter.stakingRouter).onValidatorsCountsByNodeOperatorReportingFinished(); emit ExtraDataSubmitted(procState.refSlot, itemsProcessed, itemsProcessed); } @@ -731,10 +798,9 @@ contract AccountingOracle is BaseOracle { dataOffset = iter.dataOffset; } - if (maxNodeOperatorsPerItem > 0) { - IOracleReportSanityChecker(LOCATOR.oracleReportSanityChecker()) - .checkNodeOperatorsPerExtraDataItemCount(maxNodeOperatorItemIndex, maxNodeOperatorsPerItem); - } + assert(maxNodeOperatorsPerItem > 0); + IOracleReportSanityChecker(LOCATOR.oracleReportSanityChecker()) + .checkNodeOperatorsPerExtraDataItemCount(maxNodeOperatorItemIndex, maxNodeOperatorsPerItem); } function _processExtraDataItem(bytes calldata data, ExtraDataIterState memory iter) internal returns (uint256) { @@ -743,7 +809,7 @@ contract AccountingOracle is BaseOracle { uint256 nodeOpsCount; uint256 firstNodeOpId; bytes calldata nodeOpIds; - bytes calldata valsCounts; + bytes calldata valuesCounts; if (dataOffset + 35 > data.length) { // has to fit at least moduleId (3 bytes), nodeOpsCount (8 bytes), @@ -762,9 +828,9 @@ contract AccountingOracle is BaseOracle { nodeOpIds.offset := add(data.offset, add(dataOffset, 11)) nodeOpIds.length := mul(nodeOpsCount, 8) firstNodeOpId := shr(192, calldataload(nodeOpIds.offset)) - valsCounts.offset := add(nodeOpIds.offset, nodeOpIds.length) - valsCounts.length := mul(nodeOpsCount, 16) - dataOffset := sub(add(valsCounts.offset, valsCounts.length), data.offset) + valuesCounts.offset := add(nodeOpIds.offset, nodeOpIds.length) + valuesCounts.length := mul(nodeOpsCount, 16) + dataOffset := sub(add(valuesCounts.offset, valuesCounts.length), data.offset) } if (moduleId == 0) { @@ -787,10 +853,10 @@ contract AccountingOracle is BaseOracle { if (iter.itemType == EXTRA_DATA_TYPE_STUCK_VALIDATORS) { IStakingRouter(iter.stakingRouter) - .reportStakingModuleStuckValidatorsCountByNodeOperator(moduleId, nodeOpIds, valsCounts); + .reportStakingModuleStuckValidatorsCountByNodeOperator(moduleId, nodeOpIds, valuesCounts); } else { IStakingRouter(iter.stakingRouter) - .reportStakingModuleExitedValidatorsCountByNodeOperator(moduleId, nodeOpIds, valsCounts); + .reportStakingModuleExitedValidatorsCountByNodeOperator(moduleId, nodeOpIds, valuesCounts); } iter.dataOffset = dataOffset; diff --git a/contracts/0.8.9/oracle/BaseOracle.sol b/contracts/0.8.9/oracle/BaseOracle.sol index ce36c002e..1292db0d2 100644 --- a/contracts/0.8.9/oracle/BaseOracle.sol +++ b/contracts/0.8.9/oracle/BaseOracle.sol @@ -42,6 +42,7 @@ abstract contract BaseOracle is IReportAsyncProcessor, AccessControlEnumerable, error RefSlotMustBeGreaterThanProcessingOne(uint256 refSlot, uint256 processingRefSlot); error RefSlotCannotDecrease(uint256 refSlot, uint256 prevRefSlot); error ProcessingDeadlineMissed(uint256 deadline); + error RefSlotAlreadyProcessing(); error UnexpectedRefSlot(uint256 consensusRefSlot, uint256 dataRefSlot); error UnexpectedConsensusVersion(uint256 expectedVersion, uint256 receivedVersion); error UnexpectedDataHash(bytes32 consensusHash, bytes32 receivedHash); @@ -267,9 +268,16 @@ abstract contract BaseOracle is IReportAsyncProcessor, AccessControlEnumerable, /// function _startProcessing() internal returns (uint256) { _checkProcessingDeadline(); + ConsensusReport memory report = _storageConsensusReport().value; + uint256 prevProcessingRefSlot = LAST_PROCESSING_REF_SLOT_POSITION.getStorageUint256(); + if (prevProcessingRefSlot == report.refSlot) { + revert RefSlotAlreadyProcessing(); + } + LAST_PROCESSING_REF_SLOT_POSITION.setStorageUint256(report.refSlot); + emit ProcessingStarted(report.refSlot, report.hash); return prevProcessingRefSlot; } diff --git a/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol b/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol index 9ac99f019..dd5abea1a 100644 --- a/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol +++ b/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol @@ -29,6 +29,12 @@ contract ValidatorsExitBusOracle is BaseOracle, PausableUntil { error UnexpectedRequestsDataLength(); error InvalidRequestsDataSortOrder(); error ArgumentOutOfBounds(); + error NodeOpValidatorIndexMustIncrease( + uint256 moduleId, + uint256 nodeOpId, + uint256 prevRequestedValidatorIndex, + uint256 requestedValidatorIndex + ); event ValidatorExitRequest( uint256 indexed stakingModuleId, @@ -92,15 +98,19 @@ contract ValidatorsExitBusOracle is BaseOracle, PausableUntil { function initialize( address admin, + address pauser, + address resumer, address consensusContract, uint256 consensusVersion, uint256 lastProcessingRefSlot ) external { if (admin == address(0)) revert AdminCannotBeZero(); _setupRole(DEFAULT_ADMIN_ROLE, admin); - _initializePausable(); + if (pauser != address(0)) _grantRole(PAUSE_ROLE, pauser); + if (resumer != address(0)) _grantRole(RESUME_ROLE, resumer); + + _pause(PAUSE_INFINITELY); _initialize(consensusContract, consensusVersion, lastProcessingRefSlot); - RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(PAUSE_INFINITELY); // pause it explicitly } /// @notice Resume accepting validator exit requests @@ -147,11 +157,14 @@ contract ValidatorsExitBusOracle is BaseOracle, PausableUntil { /// /// @dev Total number of validator exit requests in this report. Must not be greater - /// than limit checked in OracleReportSanityChecker.checkExitBusOracleReport + /// than limit checked in OracleReportSanityChecker.checkExitBusOracleReport. + /// + /// Cannot be zero: in the case there's no validator exit requests to submit, oracles + /// should skip submitting the report for the current reporting frame. uint256 requestsCount; /// @dev Format of the validator exit requests data. Currently, only the - /// DATA_FORMAT_LIST=0 is supported. + /// DATA_FORMAT_LIST=1 is supported. uint256 dataFormat; /// @dev Validator exit requests data. Can differ based on the data format, @@ -354,12 +367,9 @@ contract ValidatorsExitBusOracle is BaseOracle, PausableUntil { offsetPastEnd := add(offset, data.length) } - mapping(uint256 => RequestedValidator) storage _lastReqValidatorIndices = - _storageLastRequestedValidatorIndices(); - uint256 lastDataWithoutPubkey = 0; uint256 lastNodeOpKey = 0; - uint256 lastValIndex; + RequestedValidator memory lastRequestedVal; bytes calldata pubkey; assembly { @@ -386,7 +396,7 @@ contract ValidatorsExitBusOracle is BaseOracle, PausableUntil { revert InvalidRequestsDataSortOrder(); } - uint256 valIndex = uint64(dataWithoutPubkey); + uint64 valIndex = uint64(dataWithoutPubkey); uint256 nodeOpId = uint40(dataWithoutPubkey >> 64); uint256 moduleId = uint24(dataWithoutPubkey >> (64 + 40)); @@ -397,20 +407,29 @@ contract ValidatorsExitBusOracle is BaseOracle, PausableUntil { uint256 nodeOpKey = _computeNodeOpKey(moduleId, nodeOpId); if (nodeOpKey != lastNodeOpKey) { if (lastNodeOpKey != 0) { - _lastReqValidatorIndices[lastNodeOpKey] = - RequestedValidator(true, uint64(lastValIndex)); + _storageLastRequestedValidatorIndices()[lastNodeOpKey] = lastRequestedVal; } + lastRequestedVal = _storageLastRequestedValidatorIndices()[nodeOpKey]; lastNodeOpKey = nodeOpKey; } - lastValIndex = valIndex; + if (lastRequestedVal.requested && valIndex <= lastRequestedVal.index) { + revert NodeOpValidatorIndexMustIncrease( + moduleId, + nodeOpId, + lastRequestedVal.index, + valIndex + ); + } + + lastRequestedVal = RequestedValidator(true, valIndex); lastDataWithoutPubkey = dataWithoutPubkey; emit ValidatorExitRequest(moduleId, nodeOpId, valIndex, pubkey, timestamp); } if (lastNodeOpKey != 0) { - _lastReqValidatorIndices[lastNodeOpKey] = RequestedValidator(true, uint64(lastValIndex)); + _storageLastRequestedValidatorIndices()[lastNodeOpKey] = lastRequestedVal; } return lastDataWithoutPubkey; diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index ce741481b..f15e06e5d 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -43,10 +43,21 @@ struct LimitsList { /// @dev Represented in the Basis Points (100% == 10_000) uint256 annualBalanceIncreaseBPLimit; - /// @notice The max deviation of stETH.totalPooledEther() / stETH.totalShares() ratio since - /// the previous oracle report + /// @notice The max deviation of the provided `simulatedShareRate` + /// and the actual one within the currently processing oracle report /// @dev Represented in the Basis Points (100% == 10_000) - uint256 shareRateDeviationBPLimit; + uint256 simulatedShareRateDeviationBPLimit; + + /// @notice The max number of exit requests allowed in report to ValidatorsExitBusOracle + uint256 maxValidatorExitRequestsPerReport; + + /// @notice The max number of data list items reported to accounting oracle in extra data + /// @dev Must fit into uint16 (<= 65_535) + uint256 maxAccountingExtraDataListItemsCount; + + /// @notice The max number of node operators reported per extra data list item + /// @dev Must fit into uint16 (<= 65_535) + uint256 maxNodeOperatorsPerExtraDataItemCount; /// @notice The min time required to be passed from the creation of the request to be /// finalized till the time of the oracle report @@ -55,13 +66,6 @@ struct LimitsList { /// @notice The positive token rebase allowed per single LidoOracle report /// @dev uses 1e9 precision, e.g.: 1e6 - 0.1%; 1e9 - 100%, see `setMaxPositiveTokenRebase()` uint256 maxPositiveTokenRebase; - - /// @notice The max number of exit requests allowed in report to ValidatorsExitBusOracle - uint256 maxValidatorExitRequestsPerReport; - - /// @notice The max number of data list items reported to accounting oracle in extra data - /// @dev Must fit into uint16 (<= 65_535) - uint256 maxAccountingExtraDataListItemsCount; } /// @dev The packed version of the LimitsList struct to be effectively persisted in storage @@ -69,9 +73,10 @@ struct LimitsListPacked { uint16 churnValidatorsPerDayLimit; uint16 oneOffCLBalanceDecreaseBPLimit; uint16 annualBalanceIncreaseBPLimit; - uint16 shareRateDeviationBPLimit; + uint16 simulatedShareRateDeviationBPLimit; uint16 maxValidatorExitRequestsPerReport; uint16 maxAccountingExtraDataListItemsCount; + uint16 maxNodeOperatorsPerExtraDataItemCount; uint64 requestTimestampMargin; uint64 maxPositiveTokenRebase; } @@ -98,11 +103,13 @@ contract OracleReportSanityChecker is AccessControlEnumerable { keccak256("SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE"); bytes32 public constant MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE = keccak256("MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE"); + bytes32 public constant MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE = + keccak256("MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE"); + bytes32 public constant MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE = + keccak256("MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE"); bytes32 public constant REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE = keccak256("REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE"); bytes32 public constant MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE = keccak256("MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE"); - bytes32 public constant MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE = - keccak256("MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE"); uint256 private constant DEFAULT_TIME_ELAPSED = 1 hours; uint256 private constant DEFAULT_CL_BALANCE = 1 gwei; @@ -120,6 +127,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { address[] shareRateDeviationLimitManagers; address[] maxValidatorExitRequestsPerReportManagers; address[] maxAccountingExtraDataListItemsCountManagers; + address[] maxNodeOperatorsPerExtraDataItemCountManagers; address[] requestTimestampMarginManagers; address[] maxPositiveTokenRebaseManagers; } @@ -144,13 +152,15 @@ contract OracleReportSanityChecker is AccessControlEnumerable { _grantRole(ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE, _managersRoster.oneOffCLBalanceDecreaseLimitManagers); _grantRole(ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE, _managersRoster.annualBalanceIncreaseLimitManagers); - _grantRole(SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE, _managersRoster.shareRateDeviationLimitManagers); - _grantRole(REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE, _managersRoster.requestTimestampMarginManagers); _grantRole(MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE, _managersRoster.maxPositiveTokenRebaseManagers); _grantRole(MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE, _managersRoster.maxValidatorExitRequestsPerReportManagers); _grantRole(MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE, _managersRoster.maxAccountingExtraDataListItemsCountManagers); + _grantRole(MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE, + _managersRoster.maxNodeOperatorsPerExtraDataItemCountManagers); + _grantRole(SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE, _managersRoster.shareRateDeviationLimitManagers); + _grantRole(REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE, _managersRoster.requestTimestampMarginManagers); } /// @notice returns the address of the LidoLocator @@ -202,6 +212,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { external onlyRole(CHURN_VALIDATORS_PER_DAY_LIMIT_MANGER_ROLE) { + _checkLimitValue(_churnValidatorsPerDayLimit, type(uint16).max); LimitsList memory limitsList = _limits.unpack(); limitsList.churnValidatorsPerDayLimit = _churnValidatorsPerDayLimit; _updateLimits(limitsList); @@ -213,6 +224,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { external onlyRole(ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE) { + _checkLimitValue(_oneOffCLBalanceDecreaseBPLimit, MAX_BASIS_POINTS); LimitsList memory limitsList = _limits.unpack(); limitsList.oneOffCLBalanceDecreaseBPLimit = _oneOffCLBalanceDecreaseBPLimit; _updateLimits(limitsList); @@ -224,19 +236,21 @@ contract OracleReportSanityChecker is AccessControlEnumerable { external onlyRole(ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE) { + _checkLimitValue(_annualBalanceIncreaseBPLimit, MAX_BASIS_POINTS); LimitsList memory limitsList = _limits.unpack(); limitsList.annualBalanceIncreaseBPLimit = _annualBalanceIncreaseBPLimit; _updateLimits(limitsList); } - /// @notice Sets the new value for the shareRateDeviationBPLimit - /// @param _shareRateDeviationBPLimit new shareRateDeviationBPLimit value - function setShareRateDeviationBPLimit(uint256 _shareRateDeviationBPLimit) + /// @notice Sets the new value for the simulatedShareRateDeviationBPLimit + /// @param _simulatedShareRateDeviationBPLimit new simulatedShareRateDeviationBPLimit value + function setSimulatedShareRateDeviationBPLimit(uint256 _simulatedShareRateDeviationBPLimit) external onlyRole(SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE) { + _checkLimitValue(_simulatedShareRateDeviationBPLimit, MAX_BASIS_POINTS); LimitsList memory limitsList = _limits.unpack(); - limitsList.shareRateDeviationBPLimit = _shareRateDeviationBPLimit; + limitsList.simulatedShareRateDeviationBPLimit = _simulatedShareRateDeviationBPLimit; _updateLimits(limitsList); } @@ -246,6 +260,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { external onlyRole(MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE) { + _checkLimitValue(_maxValidatorExitRequestsPerReport, type(uint16).max); LimitsList memory limitsList = _limits.unpack(); limitsList.maxValidatorExitRequestsPerReport = _maxValidatorExitRequestsPerReport; _updateLimits(limitsList); @@ -257,6 +272,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { external onlyRole(REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE) { + _checkLimitValue(_requestTimestampMargin, type(uint64).max); LimitsList memory limitsList = _limits.unpack(); limitsList.requestTimestampMargin = _requestTimestampMargin; _updateLimits(limitsList); @@ -273,6 +289,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { external onlyRole(MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE) { + _checkLimitValue(_maxPositiveTokenRebase, type(uint64).max); LimitsList memory limitsList = _limits.unpack(); limitsList.maxPositiveTokenRebase = _maxPositiveTokenRebase; _updateLimits(limitsList); @@ -284,11 +301,24 @@ contract OracleReportSanityChecker is AccessControlEnumerable { external onlyRole(MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE) { + _checkLimitValue(_maxAccountingExtraDataListItemsCount, type(uint16).max); LimitsList memory limitsList = _limits.unpack(); limitsList.maxAccountingExtraDataListItemsCount = _maxAccountingExtraDataListItemsCount; _updateLimits(limitsList); } + /// @notice Sets the new value for the max maxNodeOperatorsPerExtraDataItemCount + /// @param _maxNodeOperatorsPerExtraDataItemCount new maxNodeOperatorsPerExtraDataItemCount value + function setMaxNodeOperatorsPerExtraDataItemCount(uint256 _maxNodeOperatorsPerExtraDataItemCount) + external + onlyRole(MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE) + { + _checkLimitValue(_maxNodeOperatorsPerExtraDataItemCount, type(uint16).max); + LimitsList memory limitsList = _limits.unpack(); + limitsList.maxNodeOperatorsPerExtraDataItemCount = _maxNodeOperatorsPerExtraDataItemCount; + _updateLimits(limitsList); + } + /// @notice Returns the allowed ETH amount that might be taken from the withdrawal vault and EL /// rewards vault during Lido's oracle report processing /// @param _preTotalPooledEther total amount of ETH controlled by the protocol @@ -404,7 +434,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { external view { - uint256 limit = _limits.unpack().maxAccountingExtraDataListItemsCount; + uint256 limit = _limits.unpack().maxNodeOperatorsPerExtraDataItemCount; if (_nodeOperatorsCount > limit) { revert TooManyNodeOpsPerExtraDataItem(_itemIndex, _nodeOperatorsCount); } @@ -442,9 +472,9 @@ contract OracleReportSanityChecker is AccessControlEnumerable { /// @notice Applies sanity checks to the simulated share rate for withdrawal requests finalization /// @param _postTotalPooledEther total pooled ether after report applied /// @param _postTotalShares total shares after report applied - /// @param _etherLockedOnWithdrawalQueue ether to lock on withdrawal queue - /// @param _sharesBurntFromWithdrawalQueue shares assigned to burn from withdrawal queue - /// @param _simulatedShareRate share rate provided with the oracle report (simulated via static call) + /// @param _etherLockedOnWithdrawalQueue ether locked on withdrawal queue for the current oracle report + /// @param _sharesBurntFromWithdrawalQueue shares burnt from withdrawal queue for the current oracle report + /// @param _simulatedShareRate share rate provided with the oracle report (simulated via off-chain "eth_call") function checkSimulatedShareRate( uint256 _postTotalPooledEther, uint256 _postTotalShares, @@ -455,9 +485,9 @@ contract OracleReportSanityChecker is AccessControlEnumerable { LimitsList memory limitsList = _limits.unpack(); // Pretending that withdrawals were not processed - // virtually return locked ether back to postTotalPooledEther - // virtually return burnt shares back to postTotalShares - _checkFinalizationShareRate( + // virtually return locked ether back to `_postTotalPooledEther` + // virtually return burnt just finalized withdrawals shares back to `_postTotalShares` + _checkSimulatedShareRate( limitsList, _postTotalPooledEther + _etherLockedOnWithdrawalQueue, _postTotalShares + _sharesBurntFromWithdrawalQueue, @@ -469,16 +499,18 @@ contract OracleReportSanityChecker is AccessControlEnumerable { uint256 _actualWithdrawalVaultBalance, uint256 _reportedWithdrawalVaultBalance ) internal pure { - if (_reportedWithdrawalVaultBalance > _actualWithdrawalVaultBalance) + if (_reportedWithdrawalVaultBalance > _actualWithdrawalVaultBalance) { revert IncorrectWithdrawalsVaultBalance(_actualWithdrawalVaultBalance); + } } function _checkELRewardsVaultBalance( uint256 _actualELRewardsVaultBalance, uint256 _reportedELRewardsVaultBalance ) internal pure { - if (_reportedELRewardsVaultBalance > _actualELRewardsVaultBalance) + if (_reportedELRewardsVaultBalance > _actualELRewardsVaultBalance) { revert IncorrectELRewardsVaultBalance(_actualELRewardsVaultBalance); + } } function _checkOneOffCLBalanceDecrease( @@ -489,8 +521,9 @@ contract OracleReportSanityChecker is AccessControlEnumerable { if (_preCLBalance <= _unifiedPostCLBalance) return; uint256 oneOffCLBalanceDecreaseBP = (MAX_BASIS_POINTS * (_preCLBalance - _unifiedPostCLBalance)) / _preCLBalance; - if (oneOffCLBalanceDecreaseBP > _limitsList.oneOffCLBalanceDecreaseBPLimit) + if (oneOffCLBalanceDecreaseBP > _limitsList.oneOffCLBalanceDecreaseBPLimit) { revert IncorrectCLBalanceDecrease(oneOffCLBalanceDecreaseBP); + } } function _checkAnnualBalancesIncrease( @@ -515,8 +548,10 @@ contract OracleReportSanityChecker is AccessControlEnumerable { uint256 annualBalanceIncrease = ((365 days * MAX_BASIS_POINTS * balanceIncrease) / _preCLBalance) / _timeElapsed; - if (annualBalanceIncrease > _limitsList.annualBalanceIncreaseBPLimit) + + if (annualBalanceIncrease > _limitsList.annualBalanceIncreaseBPLimit) { revert IncorrectCLBalanceIncrease(annualBalanceIncrease); + } } function _checkValidatorsChurnLimit( @@ -545,7 +580,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { revert IncorrectRequestFinalization(requestTimestampToFinalizeUpTo); } - function _checkFinalizationShareRate( + function _checkSimulatedShareRate( LimitsList memory _limitsList, uint256 _noWithdrawalsPostTotalPooledEther, uint256 _noWithdrawalsPostTotalShares, @@ -557,15 +592,17 @@ contract OracleReportSanityChecker is AccessControlEnumerable { if (actualShareRate == 0) { // can't finalize anything if the actual share rate is zero - revert IncorrectFinalizationShareRate(MAX_BASIS_POINTS); + revert IncorrectSimulatedShareRate(MAX_BASIS_POINTS); } - uint256 finalizationShareDiff = Math256.abs( + uint256 simulatedShareDiff = Math256.abs( SafeCast.toInt256(_simulatedShareRate) - SafeCast.toInt256(actualShareRate) ); - uint256 finalizationShareDeviation = (MAX_BASIS_POINTS * finalizationShareDiff) / actualShareRate; - if (finalizationShareDeviation > _limitsList.shareRateDeviationBPLimit) - revert IncorrectFinalizationShareRate(finalizationShareDeviation); + uint256 simulatedShareDeviation = (MAX_BASIS_POINTS * simulatedShareDiff) / actualShareRate; + + if (simulatedShareDeviation > _limitsList.simulatedShareRateDeviationBPLimit) { + revert IncorrectSimulatedShareRate(simulatedShareDeviation); + } } function _grantRole(bytes32 _role, address[] memory _accounts) internal { @@ -577,41 +614,61 @@ contract OracleReportSanityChecker is AccessControlEnumerable { function _updateLimits(LimitsList memory _newLimitsList) internal { LimitsList memory _oldLimitsList = _limits.unpack(); if (_oldLimitsList.churnValidatorsPerDayLimit != _newLimitsList.churnValidatorsPerDayLimit) { + _checkLimitValue(_newLimitsList.churnValidatorsPerDayLimit, type(uint16).max); emit ChurnValidatorsPerDayLimitSet(_newLimitsList.churnValidatorsPerDayLimit); } if (_oldLimitsList.oneOffCLBalanceDecreaseBPLimit != _newLimitsList.oneOffCLBalanceDecreaseBPLimit) { + _checkLimitValue(_newLimitsList.oneOffCLBalanceDecreaseBPLimit, type(uint16).max); emit OneOffCLBalanceDecreaseBPLimitSet(_newLimitsList.oneOffCLBalanceDecreaseBPLimit); } if (_oldLimitsList.annualBalanceIncreaseBPLimit != _newLimitsList.annualBalanceIncreaseBPLimit) { + _checkLimitValue(_newLimitsList.annualBalanceIncreaseBPLimit, type(uint16).max); emit AnnualBalanceIncreaseBPLimitSet(_newLimitsList.annualBalanceIncreaseBPLimit); } - if (_oldLimitsList.shareRateDeviationBPLimit != _newLimitsList.shareRateDeviationBPLimit) { - emit ShareRateDeviationBPLimitSet(_newLimitsList.shareRateDeviationBPLimit); - } - if (_oldLimitsList.requestTimestampMargin != _newLimitsList.requestTimestampMargin) { - emit RequestTimestampMarginSet(_newLimitsList.requestTimestampMargin); - } - if (_oldLimitsList.maxPositiveTokenRebase != _newLimitsList.maxPositiveTokenRebase) { - emit MaxPositiveTokenRebaseSet(_newLimitsList.maxPositiveTokenRebase); + if (_oldLimitsList.simulatedShareRateDeviationBPLimit != _newLimitsList.simulatedShareRateDeviationBPLimit) { + _checkLimitValue(_newLimitsList.simulatedShareRateDeviationBPLimit, type(uint16).max); + emit SimulatedShareRateDeviationBPLimitSet(_newLimitsList.simulatedShareRateDeviationBPLimit); } if (_oldLimitsList.maxValidatorExitRequestsPerReport != _newLimitsList.maxValidatorExitRequestsPerReport) { + _checkLimitValue(_newLimitsList.maxValidatorExitRequestsPerReport, type(uint16).max); emit MaxValidatorExitRequestsPerReportSet(_newLimitsList.maxValidatorExitRequestsPerReport); } if (_oldLimitsList.maxAccountingExtraDataListItemsCount != _newLimitsList.maxAccountingExtraDataListItemsCount) { + _checkLimitValue(_newLimitsList.maxAccountingExtraDataListItemsCount, type(uint16).max); emit MaxAccountingExtraDataListItemsCountSet(_newLimitsList.maxAccountingExtraDataListItemsCount); } + if (_oldLimitsList.maxNodeOperatorsPerExtraDataItemCount != _newLimitsList.maxNodeOperatorsPerExtraDataItemCount) { + _checkLimitValue(_newLimitsList.maxNodeOperatorsPerExtraDataItemCount, type(uint16).max); + emit MaxNodeOperatorsPerExtraDataItemCountSet(_newLimitsList.maxNodeOperatorsPerExtraDataItemCount); + } + if (_oldLimitsList.requestTimestampMargin != _newLimitsList.requestTimestampMargin) { + _checkLimitValue(_newLimitsList.requestTimestampMargin, type(uint64).max); + emit RequestTimestampMarginSet(_newLimitsList.requestTimestampMargin); + } + if (_oldLimitsList.maxPositiveTokenRebase != _newLimitsList.maxPositiveTokenRebase) { + _checkLimitValue(_newLimitsList.maxPositiveTokenRebase, type(uint64).max); + emit MaxPositiveTokenRebaseSet(_newLimitsList.maxPositiveTokenRebase); + } _limits = _newLimitsList.pack(); } + function _checkLimitValue(uint256 _value, uint256 _maxAllowedValue) internal pure { + if (_value > _maxAllowedValue) { + revert IncorrectLimitValue(_value, _maxAllowedValue); + } + } + event ChurnValidatorsPerDayLimitSet(uint256 churnValidatorsPerDayLimit); event OneOffCLBalanceDecreaseBPLimitSet(uint256 oneOffCLBalanceDecreaseBPLimit); event AnnualBalanceIncreaseBPLimitSet(uint256 annualBalanceIncreaseBPLimit); - event ShareRateDeviationBPLimitSet(uint256 shareRateDeviationBPLimit); - event RequestTimestampMarginSet(uint256 requestTimestampMargin); + event SimulatedShareRateDeviationBPLimitSet(uint256 simulatedShareRateDeviationBPLimit); event MaxPositiveTokenRebaseSet(uint256 maxPositiveTokenRebase); event MaxValidatorExitRequestsPerReportSet(uint256 maxValidatorExitRequestsPerReport); event MaxAccountingExtraDataListItemsCountSet(uint256 maxAccountingExtraDataListItemsCount); + event MaxNodeOperatorsPerExtraDataItemCountSet(uint256 maxNodeOperatorsPerExtraDataItemCount); + event RequestTimestampMarginSet(uint256 requestTimestampMargin); + error IncorrectLimitValue(uint256 value, uint256 maxAllowedValue); error IncorrectWithdrawalsVaultBalance(uint256 actualWithdrawalVaultBalance); error IncorrectELRewardsVaultBalance(uint256 actualELRewardsVaultBalance); error IncorrectCLBalanceDecrease(uint256 oneOffCLBalanceDecreaseBP); @@ -620,7 +677,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { error IncorrectNumberOfExitRequestsPerReport(uint256 maxRequestsCount); error IncorrectExitedValidators(uint256 churnLimit); error IncorrectRequestFinalization(uint256 requestCreationBlock); - error IncorrectFinalizationShareRate(uint256 finalizationShareDeviation); + error IncorrectSimulatedShareRate(uint256 simulatedShareDeviation); error MaxAccountingExtraDataItemsCountExceeded(uint256 maxItemsCount, uint256 receivedItemsCount); error ExitedValidatorsLimitExceeded(uint256 limitPerDay, uint256 exitedPerDay); error TooManyNodeOpsPerExtraDataItem(uint256 itemIndex, uint256 nodeOpsCount); @@ -631,11 +688,12 @@ library LimitsListPacker { res.churnValidatorsPerDayLimit = SafeCast.toUint16(_limitsList.churnValidatorsPerDayLimit); res.oneOffCLBalanceDecreaseBPLimit = _toBasisPoints(_limitsList.oneOffCLBalanceDecreaseBPLimit); res.annualBalanceIncreaseBPLimit = _toBasisPoints(_limitsList.annualBalanceIncreaseBPLimit); - res.shareRateDeviationBPLimit = _toBasisPoints(_limitsList.shareRateDeviationBPLimit); + res.simulatedShareRateDeviationBPLimit = _toBasisPoints(_limitsList.simulatedShareRateDeviationBPLimit); res.requestTimestampMargin = SafeCast.toUint64(_limitsList.requestTimestampMargin); res.maxPositiveTokenRebase = SafeCast.toUint64(_limitsList.maxPositiveTokenRebase); res.maxValidatorExitRequestsPerReport = SafeCast.toUint16(_limitsList.maxValidatorExitRequestsPerReport); res.maxAccountingExtraDataListItemsCount = SafeCast.toUint16(_limitsList.maxAccountingExtraDataListItemsCount); + res.maxNodeOperatorsPerExtraDataItemCount = SafeCast.toUint16(_limitsList.maxNodeOperatorsPerExtraDataItemCount); } function _toBasisPoints(uint256 _value) private pure returns (uint16) { @@ -649,10 +707,11 @@ library LimitsListUnpacker { res.churnValidatorsPerDayLimit = _limitsList.churnValidatorsPerDayLimit; res.oneOffCLBalanceDecreaseBPLimit = _limitsList.oneOffCLBalanceDecreaseBPLimit; res.annualBalanceIncreaseBPLimit = _limitsList.annualBalanceIncreaseBPLimit; - res.shareRateDeviationBPLimit = _limitsList.shareRateDeviationBPLimit; + res.simulatedShareRateDeviationBPLimit = _limitsList.simulatedShareRateDeviationBPLimit; res.requestTimestampMargin = _limitsList.requestTimestampMargin; res.maxPositiveTokenRebase = _limitsList.maxPositiveTokenRebase; res.maxValidatorExitRequestsPerReport = _limitsList.maxValidatorExitRequestsPerReport; res.maxAccountingExtraDataListItemsCount = _limitsList.maxAccountingExtraDataListItemsCount; + res.maxNodeOperatorsPerExtraDataItemCount = _limitsList.maxNodeOperatorsPerExtraDataItemCount; } } diff --git a/contracts/0.8.9/test_helpers/GenericStub.sol b/contracts/0.8.9/test_helpers/GenericStub.sol new file mode 100644 index 000000000..85d1957c3 --- /dev/null +++ b/contracts/0.8.9/test_helpers/GenericStub.sol @@ -0,0 +1,173 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.9; + +enum LogType { + LOG0, + LOG1, + LOG2, + LOG3, + LOG4 +} + +contract ETHForwarder { + constructor(address payable _recipient) payable { + selfdestruct(_recipient); + } +} + +contract GenericStub { + type MethodID is bytes4; + type InputHash is bytes32; + type Topic is bytes32; + + // InputHash private immutable WILDCARD_INPUT_HASH; + + struct Log { + LogType logType; + bytes data; + bytes32 t1; + bytes32 t2; + bytes32 t3; + bytes32 t4; + } + + struct ForwardETH { + address payable recipient; + uint256 value; + } + + struct MethodStub { + /// @notice msg.data used for call + bytes input; + /// @notice abi encoded data to be returned from the method + bytes output; + /// @notice events to emit during method execution + Log[] logs; + /// @notice optional ETH send on method execution + ForwardETH forwardETH; + /// @notice shall method ends with revert instead of return + bool isRevert; + /// @notice index of the state to set as current after stub call + /// @dev this value is one based + uint256 nextStateIndexOneBased; + } + + struct StubState { + /// @notice list of all stubs (order is not guaranteed) + MethodStub[] stubs; + /// @notice indices of stubs increased to 1 + mapping(bytes32 => uint256) indicesByIdOneBased; + } + + StubState[] private _states; + uint256 private _currentStateIndexOneBased = 1; + + constructor() { + _states.push(); + } + + function GenericStub__addStub(MethodStub memory _stub) external { + StubState storage currentState = _getState(_currentStateIndexOneBased - 1); + currentState.stubs.push(); + bytes32 stubId = keccak256(_stub.input); + uint256 newStubIndex = currentState.stubs.length - 1; + currentState.stubs[newStubIndex].input = _stub.input; + currentState.stubs[newStubIndex].output = _stub.output; + currentState.stubs[newStubIndex].forwardETH = _stub.forwardETH; + currentState.stubs[newStubIndex].isRevert = _stub.isRevert; + currentState.stubs[newStubIndex].nextStateIndexOneBased = _stub.nextStateIndexOneBased; + + for(uint256 i = 0; i < _stub.logs.length; ++i) { + currentState.stubs[newStubIndex].logs.push(_stub.logs[i]); + } + currentState.indicesByIdOneBased[stubId] = newStubIndex + 1; + } + + function GenericStub__addState() external { + _states.push(); + _currentStateIndexOneBased = _states.length; + } + + function GenericStub__setState(uint256 _stateIndex) external { + require(_stateIndex != 0, "INVALID_INDEX"); + if (_stateIndex > _states.length) { + revert GenericStub__StateIndexOutOfBounds(_stateIndex, _states.length); + } + _currentStateIndexOneBased = _stateIndex; + } + + fallback() external payable { + MethodStub memory stub = _getMethodStub(); + _forwardETH(stub.forwardETH); + _logEvents(stub.logs); + bytes memory output = stub.output; + uint256 outputLength = output.length; + if (stub.nextStateIndexOneBased != 0) { + _currentStateIndexOneBased = stub.nextStateIndexOneBased; + } + if (stub.isRevert) { + assembly { revert(add(output, 32), outputLength) } + } + assembly { return(add(output, 32), outputLength) } + } + + function _logEvents(Log[] memory _logs) internal { + for (uint256 i = 0; i < _logs.length; ++i) { + bytes32 t1 = _logs[i].t1; + bytes32 t2 = _logs[i].t2; + bytes32 t3 = _logs[i].t3; + bytes32 t4 = _logs[i].t4; + bytes memory data = _logs[i].data; + uint256 dataLength = data.length; + if (_logs[i].logType == LogType.LOG0) { + assembly { log0(add(data, 32), dataLength) } + } else if (_logs[i].logType == LogType.LOG1) { + assembly { log1(add(data, 32), dataLength, t1) } + } else if (_logs[i].logType == LogType.LOG2) { + assembly { log2(add(data, 32), dataLength, t1, t2) } + } else if (_logs[i].logType == LogType.LOG3) { + assembly { log3(add(data, 32), dataLength, t1, t2, t3) } + } else if (_logs[i].logType == LogType.LOG4) { + assembly { log4(add(data, 32), dataLength, t1, t2, t3, t4) } + } + } + } + + function _forwardETH(ForwardETH memory _ethForward) internal { + if (_ethForward.value == 0) return; + new ETHForwarder{value: _ethForward.value}(_ethForward.recipient); + emit GenericStub__ethSent(_ethForward.recipient, _ethForward.value); + } + + function _getMethodStub() internal view returns (MethodStub memory) { + StubState storage currentState = _getState(_currentStateIndexOneBased - 1); + bytes32 methodStubId = keccak256(msg.data); + bytes32 methodStubWildcardId = keccak256(msg.data[:4]); + + uint256 methodStubIndex = currentState.indicesByIdOneBased[methodStubId]; + uint256 methodStubWildcardIndex = currentState.indicesByIdOneBased[methodStubWildcardId]; + + if (methodStubIndex == 0 && methodStubWildcardIndex == 0) { + revert GenericStub__MethodStubIsNotDefined(msg.data); + } + + return methodStubIndex != 0 + ? currentState.stubs[methodStubIndex - 1] + : currentState.stubs[methodStubWildcardIndex - 1]; + } + + function _getState(uint256 _stateIndex) internal view returns (StubState storage) { + if (_stateIndex >= _states.length) { + revert GenericStub__StateIndexOutOfBounds(_stateIndex, _states.length); + } + return _states[_stateIndex]; + } + + event GenericStub__ethSent(address recipient, uint256 value); + + error GenericStub__StateIndexOutOfBounds(uint256 index, uint256 length); + error GenericStub__MethodStubIsNotDefined(bytes callData); + error GenericStub__ETHSendFailed(address recipient, uint256 value); +} \ No newline at end of file diff --git a/contracts/0.8.9/test_helpers/LidoLocatorMock.sol b/contracts/0.8.9/test_helpers/LidoLocatorMock.sol index 7d00ea62e..d4bd92f5a 100644 --- a/contracts/0.8.9/test_helpers/LidoLocatorMock.sol +++ b/contracts/0.8.9/test_helpers/LidoLocatorMock.sol @@ -21,6 +21,7 @@ contract LidoLocatorMock is ILidoLocator { address withdrawalQueue; address withdrawalVault; address postTokenRebaseReceiver; + address oracleDaemonConfig; } address public immutable lido; @@ -36,6 +37,7 @@ contract LidoLocatorMock is ILidoLocator { address public immutable withdrawalQueue; address public immutable withdrawalVault; address public immutable postTokenRebaseReceiver; + address public immutable oracleDaemonConfig; constructor ( ContractAddresses memory addresses @@ -53,6 +55,7 @@ contract LidoLocatorMock is ILidoLocator { withdrawalQueue = addresses.withdrawalQueue; withdrawalVault = addresses.withdrawalVault; postTokenRebaseReceiver = addresses.postTokenRebaseReceiver; + oracleDaemonConfig = addresses.oracleDaemonConfig; } function coreComponents() external view returns(address,address,address,address,address,address) { diff --git a/contracts/0.8.9/test_helpers/ModuleSolo.sol b/contracts/0.8.9/test_helpers/ModuleSolo.sol index 1bf96fbeb..3ccdc1b33 100644 --- a/contracts/0.8.9/test_helpers/ModuleSolo.sol +++ b/contracts/0.8.9/test_helpers/ModuleSolo.sol @@ -76,18 +76,21 @@ contract ModuleSolo is IStakingModule { function setNodeOperatorStakingLimit(uint256 _id, uint256 _stakingLimit) external {} - function handleRewardsMinted(uint256 _totalShares) external {} + function onRewardsMinted(uint256 _totalShares) external {} function updateStuckValidatorsCount( - uint256 _nodeOperatorId, - uint256 _stuckValidatorKeysCount + bytes calldata _nodeOperatorIds, + bytes calldata _stuckValidatorsCounts ) external {} - function updateExitedValidatorsCount(uint256, uint256) external {} + function updateExitedValidatorsCount( + bytes calldata _nodeOperatorIds, + bytes calldata _stuckValidatorsCounts + ) external {} function updateRefundedValidatorsCount(uint256 _nodeOperatorId, uint256 _refundedValidatorsCount) external {} - function onAllValidatorCountersUpdated() external {} + function onExitedAndStuckValidatorsCountsUpdated() external {} function unsafeUpdateValidatorsCount( uint256 /* _nodeOperatorId */, @@ -128,7 +131,6 @@ contract ModuleSolo is IStakingModule { external pure returns ( - uint256 depositsCount, bytes memory publicKeys, bytes memory signatures ) @@ -139,6 +141,6 @@ contract ModuleSolo is IStakingModule { MemUtils.copyBytes(_calldata, publicKeys, 0, 0, _depositsCount * PUBKEY_LENGTH); MemUtils.copyBytes(_calldata, signatures, _depositsCount * PUBKEY_LENGTH, 0, _depositsCount * PUBKEY_LENGTH); - return (_depositsCount, publicKeys, signatures); + return (publicKeys, signatures); } } diff --git a/contracts/0.8.9/test_helpers/PausableUntilPrivateExposed.sol b/contracts/0.8.9/test_helpers/PausableUntilPrivateExposed.sol new file mode 100644 index 000000000..acf1d7973 --- /dev/null +++ b/contracts/0.8.9/test_helpers/PausableUntilPrivateExposed.sol @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +/* See contracts/COMPILERS.md */ +pragma solidity 0.8.9; + +import {PausableUntil} from "../utils/PausableUntil.sol"; + +contract PausableUntilPrivateExposed is PausableUntil { + + function stubUnderModifierWhenPaused() external view whenPaused returns (uint256) { + return 42; + } + + function stubUnderModifierWhenResumed() external view whenResumed returns (uint256) { + return 42; + } + + function resume() external { + _resume(); + } + + function pause(uint256 _duration) external { + _pause(_duration); + } + +} diff --git a/contracts/0.8.9/test_helpers/StakingModuleMock.sol b/contracts/0.8.9/test_helpers/StakingModuleMock.sol index 7e4c07c2e..00d1227ad 100644 --- a/contracts/0.8.9/test_helpers/StakingModuleMock.sol +++ b/contracts/0.8.9/test_helpers/StakingModuleMock.sol @@ -7,6 +7,7 @@ pragma solidity 0.8.9; import {IStakingModule} from "../interfaces/IStakingModule.sol"; contract StakingModuleMock is IStakingModule { + uint256 internal _exitedValidatorsCount; uint256 private _activeValidatorsCount; uint256 private _availableValidatorsCount; uint256 private _nonce; @@ -26,7 +27,7 @@ contract StakingModuleMock is IStakingModule { uint256 totalDepositedValidators, uint256 depositableValidatorsCount ) { - totalExitedValidators = 0; + totalExitedValidators = _exitedValidatorsCount; totalDepositedValidators = _activeValidatorsCount; depositableValidatorsCount = _availableValidatorsCount; } @@ -61,18 +62,42 @@ contract StakingModuleMock is IStakingModule { view returns (uint256[] memory nodeOperatorIds) {} - function handleRewardsMinted(uint256 _totalShares) external {} + function onRewardsMinted(uint256 _totalShares) external {} + + struct Call_updateValidatorsCount { + bytes nodeOperatorIds; + bytes validatorsCounts; + uint256 callCount; + } + + Call_updateValidatorsCount public lastCall_updateStuckValidatorsCount; + Call_updateValidatorsCount public lastCall_updateExitedValidatorsCount; function updateStuckValidatorsCount( - uint256 _nodeOperatorId, - uint256 _stuckValidatorKeysCount - ) external {} + bytes calldata _nodeOperatorIds, + bytes calldata _stuckValidatorsCounts + ) external { + lastCall_updateStuckValidatorsCount.nodeOperatorIds = _nodeOperatorIds; + lastCall_updateStuckValidatorsCount.validatorsCounts = _stuckValidatorsCounts; + ++lastCall_updateStuckValidatorsCount.callCount; + } - function updateExitedValidatorsCount(uint256, uint256) external {} + function updateExitedValidatorsCount( + bytes calldata _nodeOperatorIds, + bytes calldata _exitedValidatorsCounts + ) external { + lastCall_updateExitedValidatorsCount.nodeOperatorIds = _nodeOperatorIds; + lastCall_updateExitedValidatorsCount.validatorsCounts = _exitedValidatorsCounts; + ++lastCall_updateExitedValidatorsCount.callCount; + } function updateRefundedValidatorsCount(uint256 _nodeOperatorId, uint256 _refundedValidatorsCount) external {} - function onAllValidatorCountersUpdated() external {} + uint256 public callCount_onExitedAndStuckValidatorsCountsUpdated; + + function onExitedAndStuckValidatorsCountsUpdated() external { + ++callCount_onExitedAndStuckValidatorsCountsUpdated; + } function unsafeUpdateValidatorsCount( uint256 /* _nodeOperatorId */, @@ -87,12 +112,15 @@ contract StakingModuleMock is IStakingModule { function obtainDepositData(uint256 _depositsCount, bytes calldata _calldata) external returns ( - uint256 depositsCount, bytes memory publicKeys, bytes memory signatures ) {} + function setTotalExitedValidatorsCount(uint256 newExitedValidatorsCount) external { + _exitedValidatorsCount = newExitedValidatorsCount; + } + function setActiveValidatorsCount(uint256 _newActiveValidatorsCount) external { _activeValidatorsCount = _newActiveValidatorsCount; } diff --git a/contracts/0.8.9/test_helpers/oracle/MockStakingRouterForAccountingOracle.sol b/contracts/0.8.9/test_helpers/oracle/MockStakingRouterForAccountingOracle.sol index 06e4bb0a2..0efb0d2f4 100644 --- a/contracts/0.8.9/test_helpers/oracle/MockStakingRouterForAccountingOracle.sol +++ b/contracts/0.8.9/test_helpers/oracle/MockStakingRouterForAccountingOracle.sol @@ -25,6 +25,8 @@ contract MockStakingRouterForAccountingOracle is IStakingRouter { ReportKeysByNodeOperatorCallData[] public calls_reportExitedKeysByNodeOperator; ReportKeysByNodeOperatorCallData[] public calls_reportStuckKeysByNodeOperator; + uint256 public totalCalls_onValidatorsCountsByNodeOperatorReportingFinished; + function setExitedKeysCountAcrossAllModules(uint256 count) external { _exitedKeysCountAcrossAllModules = count; @@ -80,4 +82,8 @@ contract MockStakingRouterForAccountingOracle is IStakingRouter { stakingModuleId, nodeOperatorIds, stuckKeysCounts )); } + + function onValidatorsCountsByNodeOperatorReportingFinished() external { + ++totalCalls_onValidatorsCountsByNodeOperatorReportingFinished; + } } diff --git a/contracts/0.8.9/test_helpers/oracle/ValidatorsExitBusTimeTravellable.sol b/contracts/0.8.9/test_helpers/oracle/ValidatorsExitBusTimeTravellable.sol index 28552ee23..a32fd4e55 100644 --- a/contracts/0.8.9/test_helpers/oracle/ValidatorsExitBusTimeTravellable.sol +++ b/contracts/0.8.9/test_helpers/oracle/ValidatorsExitBusTimeTravellable.sol @@ -30,4 +30,8 @@ contract ValidatorsExitBusTimeTravellable is ValidatorsExitBusOracle, ITimeProvi address consensus = CONSENSUS_CONTRACT_POSITION.getStorageAddress(); return ITimeProvider(consensus).getTime(); } + + function getDataProcessingState() external view returns (DataProcessingState memory) { + return _storageDataProcessingState().value; + } } diff --git a/contracts/0.8.9/utils/PausableUntil.sol b/contracts/0.8.9/utils/PausableUntil.sol index b4aff4e93..0fe8a8356 100644 --- a/contracts/0.8.9/utils/PausableUntil.sol +++ b/contracts/0.8.9/utils/PausableUntil.sol @@ -43,11 +43,15 @@ contract PausableUntil { return block.timestamp < RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256(); } - function _initializePausable() internal { - emit Paused(PAUSE_INFINITELY); + /// @notice Returns one of: + /// - PAUSE_INFINITELY if paused infinitely returns + /// - first second when get contract get resumed if paused for specific duration + /// - some timestamp in past if not paused + function getResumeSinceTimestamp() external view returns (uint256) { + return RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256(); } - function _resume() internal { + function _resume() internal whenPaused { RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(block.timestamp); emit Resumed(); diff --git a/contracts/common/interfaces/IEIP712.sol b/contracts/common/interfaces/IEIP712StETH.sol similarity index 63% rename from contracts/common/interfaces/IEIP712.sol rename to contracts/common/interfaces/IEIP712StETH.sol index 2916c245d..75ef5f2b8 100644 --- a/contracts/common/interfaces/IEIP712.sol +++ b/contracts/common/interfaces/IEIP712StETH.sol @@ -6,16 +6,16 @@ pragma solidity >=0.4.24 <0.9.0; /** - * @dev Helper interface of EIP712. + * @dev Helper interface of EIP712 StETH-dedicated helper. * * Has an access to the CHAIN_ID opcode and relies on immutables internally * Both are unavailable for Solidity 0.4.24. */ -interface IEIP712 { +interface IEIP712StETH { /** * @dev Returns the domain separator for the current chain. */ - function domainSeparatorV4() external view returns (bytes32); + function domainSeparatorV4(address _stETH) external view returns (bytes32); /** * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this @@ -32,5 +32,16 @@ interface IEIP712 { * address signer = ECDSA.recover(digest, signature); * ``` */ - function hashTypedDataV4(bytes32 _structHash) external view returns (bytes32); + function hashTypedDataV4(address _stETH, bytes32 _structHash) external view returns (bytes32); + + /** + * @dev returns the fields and values that describe the domain separator + * used by stETH for EIP-712 signature. + */ + function eip712Domain(address _stETH) external view returns ( + string memory name, + string memory version, + uint256 chainId, + address verifyingContract + ); } diff --git a/contracts/common/interfaces/ILidoLocator.sol b/contracts/common/interfaces/ILidoLocator.sol index 3b996b8b2..a2bdc764d 100644 --- a/contracts/common/interfaces/ILidoLocator.sol +++ b/contracts/common/interfaces/ILidoLocator.sol @@ -19,6 +19,7 @@ interface ILidoLocator { function withdrawalQueue() external view returns(address); function withdrawalVault() external view returns(address); function postTokenRebaseReceiver() external view returns(address); + function oracleDaemonConfig() external view returns(address); function coreComponents() external view returns( address elRewardsVault, address oracleReportSanityChecker, diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 000000000..8dfa20b7c --- /dev/null +++ b/foundry.toml @@ -0,0 +1,21 @@ +[profile.default] +# The source directory +src = 'contracts' + +# The artifact directory +out = 'foundry/out' + +# A list of paths to look for libraries in +libs = ['node_modules', 'foundry/lib'] + +# The test directory +test = 'test' + +# Whether to cache builds or not +cache = true + +# The cache directory if enabled +cache_path = 'foundry/cache' + +# Only run tests in contracts matching the specified glob pattern +match_path = '**/test/**/*.test.sol' diff --git a/foundry/lib/forge-std b/foundry/lib/forge-std new file mode 160000 index 000000000..662ae0d69 --- /dev/null +++ b/foundry/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 662ae0d6936654c5d1fb79fc15f521de28edb60e diff --git a/foundry/skip-sol-tests-compilation.js b/foundry/skip-sol-tests-compilation.js new file mode 100644 index 000000000..974d6ed1b --- /dev/null +++ b/foundry/skip-sol-tests-compilation.js @@ -0,0 +1,16 @@ +const minimatch = require('minimatch') +const { subtask } = require('hardhat/config') +const { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } = require('hardhat/builtin-tasks/task-names') + +/** + * Excludes Solidity test files from the Hardhat compilation. This step is required to avoid + * compilation errors when Hardhat can't find Foundry-specific solidity files. + * + * This function is used instead of the "hardhat-foundry" plugin because the last one is not + * compatible with the "solidity-coverage" plugin. After enabling "hardhat-foundry" coverage + * reports are always empty. + */ +subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction(async (_, __, runSuper) => { + const paths = await runSuper() + return paths.filter((path) => !minimatch(path, '**/test/**/*.sol')) +}) diff --git a/hardhat.config.js b/hardhat.config.js index e1fddf077..ee6a712ad 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -11,6 +11,7 @@ require('hardhat-gas-reporter') require('solidity-coverage') require('hardhat-contract-sizer') require('hardhat-ignore-warnings') +require('./foundry/skip-sol-tests-compilation') const NETWORK_NAME = getNetworkName() const ETH_ACCOUNT_NAME = process.env.ETH_ACCOUNT_NAME @@ -75,7 +76,8 @@ const getNetConfig = (networkName, ethAccountName) => { initialBaseFeePerGas: 0, allowUnlimitedContractSize: true, accounts: { - mnemonic: 'hardhat', + // default hardhat's node mnemonic + mnemonic: 'test test test test test test test test test test test junk', count: 20, accountsBalance: '100000000000000000000000', gasPrice: 0 diff --git a/lib/abi/AccountingOracle.json b/lib/abi/AccountingOracle.json index 13f5f9002..6def379da 100644 --- a/lib/abi/AccountingOracle.json +++ b/lib/abi/AccountingOracle.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"lidoLocator","type":"address"},{"internalType":"address","name":"lido","type":"address"},{"internalType":"address","name":"legacyOracle","type":"address"},{"internalType":"uint256","name":"secondsPerSlot","type":"uint256"},{"internalType":"uint256","name":"genesisTime","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressCannotBeSame","type":"error"},{"inputs":[],"name":"AddressCannotBeZero","type":"error"},{"inputs":[],"name":"AdminCannotBeZero","type":"error"},{"inputs":[],"name":"CannotSubmitExtraDataBeforeMainData","type":"error"},{"inputs":[],"name":"ExtraDataAlreadyProcessed","type":"error"},{"inputs":[],"name":"ExtraDataListOnlySupportsSingleTx","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"IncorrectOracleMigration","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[],"name":"InvalidExitedValidatorsData","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"name":"InvalidExtraDataItem","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"name":"InvalidExtraDataSortOrder","type":"error"},{"inputs":[],"name":"LegacyOracleCannotBeZero","type":"error"},{"inputs":[],"name":"LidoLocatorCannotBeZero","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"NumExitedValidatorsCannotDecrease","type":"error"},{"inputs":[],"name":"OnlyConsensusContractCanSubmitReport","type":"error"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"ProcessingDeadlineMissed","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotCannotBeLessThanProcessingOne","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"prevRefSlot","type":"uint256"}],"name":"RefSlotCannotDecrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotMustBeGreaterThanProcessingOne","type":"error"},{"inputs":[],"name":"SenderNotAllowed","type":"error"},{"inputs":[],"name":"UnexpectedChainConfig","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedVersion","type":"uint256"},{"internalType":"uint256","name":"receivedVersion","type":"uint256"}],"name":"UnexpectedConsensusVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedFormat","type":"uint256"},{"internalType":"uint256","name":"receivedFormat","type":"uint256"}],"name":"UnexpectedExtraDataFormat","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedIndex","type":"uint256"},{"internalType":"uint256","name":"receivedIndex","type":"uint256"}],"name":"UnexpectedExtraDataIndex","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedCount","type":"uint256"},{"internalType":"uint256","name":"receivedCount","type":"uint256"}],"name":"UnexpectedExtraDataItemsCount","type":"error"},{"inputs":[{"internalType":"uint256","name":"consensusRefSlot","type":"uint256"},{"internalType":"uint256","name":"dataRefSlot","type":"uint256"}],"name":"UnexpectedRefSlot","type":"error"},{"inputs":[{"internalType":"uint256","name":"format","type":"uint256"}],"name":"UnsupportedExtraDataFormat","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"},{"internalType":"uint256","name":"dataType","type":"uint256"}],"name":"UnsupportedExtraDataType","type":"error"},{"inputs":[],"name":"VersionCannotBeSame","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"},{"indexed":true,"internalType":"address","name":"prevAddr","type":"address"}],"name":"ConsensusHashContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"version","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"prevVersion","type":"uint256"}],"name":"ConsensusVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsProcessed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsCount","type":"uint256"}],"name":"ExtraDataSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"ProcessingStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"}],"name":"ReportSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"processedItemsCount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsCount","type":"uint256"}],"name":"WarnExtraDataIncompleteProcessing","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"}],"name":"WarnProcessingMissed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_FORMAT_LIST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_TYPE_EXITED_VALIDATORS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_TYPE_STUCK_VALIDATORS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GENESIS_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LEGACY_ORACLE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOCATOR","outputs":[{"internalType":"contract ILidoLocator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_CONTRACT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_VERSION_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECONDS_PER_SLOT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SUBMIT_DATA_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusReport","outputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bool","name":"processingStarted","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastProcessingRefSlot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getProcessingState","outputs":[{"components":[{"internalType":"uint256","name":"currentFrameRefSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bytes32","name":"mainDataHash","type":"bytes32"},{"internalType":"bool","name":"mainDataSubmitted","type":"bool"},{"internalType":"bytes32","name":"extraDataHash","type":"bytes32"},{"internalType":"uint256","name":"extraDataFormat","type":"uint256"},{"internalType":"uint256","name":"extraDataItemsCount","type":"uint256"},{"internalType":"uint256","name":"extraDataItemsSubmitted","type":"uint256"}],"internalType":"struct AccountingOracle.ProcessingState","name":"result","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"consensusContract","type":"address"},{"internalType":"uint256","name":"consensusVersion","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"consensusContract","type":"address"},{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"lastProcessingRefSlot","type":"uint256"}],"name":"initializeWithoutMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setConsensusContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"version","type":"uint256"}],"name":"setConsensusVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"reportHash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"submitConsensusReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"numValidators","type":"uint256"},{"internalType":"uint256","name":"clBalanceGwei","type":"uint256"},{"internalType":"uint256[]","name":"stakingModuleIdsWithNewlyExitedValidators","type":"uint256[]"},{"internalType":"uint256[]","name":"numExitedValidatorsByStakingModule","type":"uint256[]"},{"internalType":"uint256","name":"withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"lastFinalizableWithdrawalRequestId","type":"uint256"},{"internalType":"uint256","name":"simulatedShareRate","type":"uint256"},{"internalType":"bool","name":"isBunkerMode","type":"bool"},{"internalType":"uint256","name":"extraDataFormat","type":"uint256"},{"internalType":"bytes32","name":"extraDataHash","type":"bytes32"},{"internalType":"uint256","name":"extraDataItemsCount","type":"uint256"}],"internalType":"struct AccountingOracle.ReportData","name":"data","type":"tuple"},{"internalType":"uint256","name":"contractVersion","type":"uint256"}],"name":"submitReportData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"items","type":"bytes"}],"name":"submitReportExtraDataList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"lidoLocator","type":"address"},{"internalType":"address","name":"lido","type":"address"},{"internalType":"address","name":"legacyOracle","type":"address"},{"internalType":"uint256","name":"secondsPerSlot","type":"uint256"},{"internalType":"uint256","name":"genesisTime","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressCannotBeSame","type":"error"},{"inputs":[],"name":"AddressCannotBeZero","type":"error"},{"inputs":[],"name":"AdminCannotBeZero","type":"error"},{"inputs":[],"name":"CannotSubmitExtraDataBeforeMainData","type":"error"},{"inputs":[],"name":"ExtraDataAlreadyProcessed","type":"error"},{"inputs":[],"name":"ExtraDataHashCannotBeZeroForNonEmptyData","type":"error"},{"inputs":[],"name":"ExtraDataItemsCountCannotBeZeroForNonEmptyData","type":"error"},{"inputs":[],"name":"ExtraDataListOnlySupportsSingleTx","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"IncorrectOracleMigration","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[],"name":"InvalidExitedValidatorsData","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"name":"InvalidExtraDataItem","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"name":"InvalidExtraDataSortOrder","type":"error"},{"inputs":[],"name":"LegacyOracleCannotBeZero","type":"error"},{"inputs":[],"name":"LidoLocatorCannotBeZero","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"NumExitedValidatorsCannotDecrease","type":"error"},{"inputs":[],"name":"OnlyConsensusContractCanSubmitReport","type":"error"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"ProcessingDeadlineMissed","type":"error"},{"inputs":[],"name":"RefSlotAlreadyProcessing","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotCannotBeLessThanProcessingOne","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"prevRefSlot","type":"uint256"}],"name":"RefSlotCannotDecrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotMustBeGreaterThanProcessingOne","type":"error"},{"inputs":[],"name":"SenderNotAllowed","type":"error"},{"inputs":[],"name":"UnexpectedChainConfig","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedVersion","type":"uint256"},{"internalType":"uint256","name":"receivedVersion","type":"uint256"}],"name":"UnexpectedConsensusVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedFormat","type":"uint256"},{"internalType":"uint256","name":"receivedFormat","type":"uint256"}],"name":"UnexpectedExtraDataFormat","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedExtraDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedIndex","type":"uint256"},{"internalType":"uint256","name":"receivedIndex","type":"uint256"}],"name":"UnexpectedExtraDataIndex","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedCount","type":"uint256"},{"internalType":"uint256","name":"receivedCount","type":"uint256"}],"name":"UnexpectedExtraDataItemsCount","type":"error"},{"inputs":[{"internalType":"uint256","name":"consensusRefSlot","type":"uint256"},{"internalType":"uint256","name":"dataRefSlot","type":"uint256"}],"name":"UnexpectedRefSlot","type":"error"},{"inputs":[{"internalType":"uint256","name":"format","type":"uint256"}],"name":"UnsupportedExtraDataFormat","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"},{"internalType":"uint256","name":"dataType","type":"uint256"}],"name":"UnsupportedExtraDataType","type":"error"},{"inputs":[],"name":"VersionCannotBeSame","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"},{"indexed":true,"internalType":"address","name":"prevAddr","type":"address"}],"name":"ConsensusHashContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"version","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"prevVersion","type":"uint256"}],"name":"ConsensusVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsProcessed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsCount","type":"uint256"}],"name":"ExtraDataSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"ProcessingStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"}],"name":"ReportSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"processedItemsCount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsCount","type":"uint256"}],"name":"WarnExtraDataIncompleteProcessing","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"}],"name":"WarnProcessingMissed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_FORMAT_EMPTY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_FORMAT_LIST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_TYPE_EXITED_VALIDATORS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_TYPE_STUCK_VALIDATORS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GENESIS_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LEGACY_ORACLE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOCATOR","outputs":[{"internalType":"contract ILidoLocator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_CONTRACT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_VERSION_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECONDS_PER_SLOT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SUBMIT_DATA_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusReport","outputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bool","name":"processingStarted","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastProcessingRefSlot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getProcessingState","outputs":[{"components":[{"internalType":"uint256","name":"currentFrameRefSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bytes32","name":"mainDataHash","type":"bytes32"},{"internalType":"bool","name":"mainDataSubmitted","type":"bool"},{"internalType":"bytes32","name":"extraDataHash","type":"bytes32"},{"internalType":"uint256","name":"extraDataFormat","type":"uint256"},{"internalType":"bool","name":"extraDataSubmitted","type":"bool"},{"internalType":"uint256","name":"extraDataItemsCount","type":"uint256"},{"internalType":"uint256","name":"extraDataItemsSubmitted","type":"uint256"}],"internalType":"struct AccountingOracle.ProcessingState","name":"result","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"consensusContract","type":"address"},{"internalType":"uint256","name":"consensusVersion","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"consensusContract","type":"address"},{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"lastProcessingRefSlot","type":"uint256"}],"name":"initializeWithoutMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setConsensusContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"version","type":"uint256"}],"name":"setConsensusVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"reportHash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"submitConsensusReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"numValidators","type":"uint256"},{"internalType":"uint256","name":"clBalanceGwei","type":"uint256"},{"internalType":"uint256[]","name":"stakingModuleIdsWithNewlyExitedValidators","type":"uint256[]"},{"internalType":"uint256[]","name":"numExitedValidatorsByStakingModule","type":"uint256[]"},{"internalType":"uint256","name":"withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"lastFinalizableWithdrawalRequestId","type":"uint256"},{"internalType":"uint256","name":"simulatedShareRate","type":"uint256"},{"internalType":"bool","name":"isBunkerMode","type":"bool"},{"internalType":"uint256","name":"extraDataFormat","type":"uint256"},{"internalType":"bytes32","name":"extraDataHash","type":"bytes32"},{"internalType":"uint256","name":"extraDataItemsCount","type":"uint256"}],"internalType":"struct AccountingOracle.ReportData","name":"data","type":"tuple"},{"internalType":"uint256","name":"contractVersion","type":"uint256"}],"name":"submitReportData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"submitReportExtraDataEmpty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"items","type":"bytes"}],"name":"submitReportExtraDataList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/abi/BaseOracle.json b/lib/abi/BaseOracle.json index aea674172..e667ae05c 100644 --- a/lib/abi/BaseOracle.json +++ b/lib/abi/BaseOracle.json @@ -1 +1 @@ -[{"inputs":[],"name":"AddressCannotBeSame","type":"error"},{"inputs":[],"name":"AddressCannotBeZero","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"OnlyConsensusContractCanSubmitReport","type":"error"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"ProcessingDeadlineMissed","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotCannotBeLessThanProcessingOne","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"prevRefSlot","type":"uint256"}],"name":"RefSlotCannotDecrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotMustBeGreaterThanProcessingOne","type":"error"},{"inputs":[],"name":"UnexpectedChainConfig","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedVersion","type":"uint256"},{"internalType":"uint256","name":"receivedVersion","type":"uint256"}],"name":"UnexpectedConsensusVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"consensusRefSlot","type":"uint256"},{"internalType":"uint256","name":"dataRefSlot","type":"uint256"}],"name":"UnexpectedRefSlot","type":"error"},{"inputs":[],"name":"VersionCannotBeSame","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"},{"indexed":true,"internalType":"address","name":"prevAddr","type":"address"}],"name":"ConsensusHashContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"version","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"prevVersion","type":"uint256"}],"name":"ConsensusVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"ProcessingStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"}],"name":"ReportSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"}],"name":"WarnProcessingMissed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GENESIS_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_CONTRACT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_VERSION_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECONDS_PER_SLOT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusReport","outputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bool","name":"processingStarted","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastProcessingRefSlot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setConsensusContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"version","type":"uint256"}],"name":"setConsensusVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"reportHash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"submitConsensusReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[],"name":"AddressCannotBeSame","type":"error"},{"inputs":[],"name":"AddressCannotBeZero","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"OnlyConsensusContractCanSubmitReport","type":"error"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"ProcessingDeadlineMissed","type":"error"},{"inputs":[],"name":"RefSlotAlreadyProcessing","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotCannotBeLessThanProcessingOne","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"prevRefSlot","type":"uint256"}],"name":"RefSlotCannotDecrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotMustBeGreaterThanProcessingOne","type":"error"},{"inputs":[],"name":"UnexpectedChainConfig","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedVersion","type":"uint256"},{"internalType":"uint256","name":"receivedVersion","type":"uint256"}],"name":"UnexpectedConsensusVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"consensusRefSlot","type":"uint256"},{"internalType":"uint256","name":"dataRefSlot","type":"uint256"}],"name":"UnexpectedRefSlot","type":"error"},{"inputs":[],"name":"VersionCannotBeSame","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"},{"indexed":true,"internalType":"address","name":"prevAddr","type":"address"}],"name":"ConsensusHashContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"version","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"prevVersion","type":"uint256"}],"name":"ConsensusVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"ProcessingStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"}],"name":"ReportSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"}],"name":"WarnProcessingMissed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GENESIS_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_CONTRACT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_VERSION_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECONDS_PER_SLOT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusReport","outputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bool","name":"processingStarted","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastProcessingRefSlot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setConsensusContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"version","type":"uint256"}],"name":"setConsensusVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"reportHash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"submitConsensusReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/abi/BeaconChainDepositor.json b/lib/abi/BeaconChainDepositor.json index 94e81c706..9d8f718cd 100644 --- a/lib/abi/BeaconChainDepositor.json +++ b/lib/abi/BeaconChainDepositor.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[],"name":"NotExpectedBalance","type":"error"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidPublicKeysBatchLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidSignaturesBatchLength","type":"error"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/abi/Burner.json b/lib/abi/Burner.json index 07d05ab96..07cd2ab43 100644 --- a/lib/abi/Burner.json +++ b/lib/abi/Burner.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_treasury","type":"address"},{"internalType":"address","name":"_stETH","type":"address"},{"internalType":"uint256","name":"_totalCoverSharesBurnt","type":"uint256"},{"internalType":"uint256","name":"_totalNonCoverSharesBurnt","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AppAuthLidoFailed","type":"error"},{"inputs":[],"name":"DirectETHTransfer","type":"error"},{"inputs":[],"name":"NotEnoughExcessStETH","type":"error"},{"inputs":[],"name":"StETHRecoveryWrongFunc","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"ZeroBurnAmount","type":"error"},{"inputs":[],"name":"ZeroRecoveryAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"ExcessStETHRecovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bool","name":"isCover","type":"bool"},{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"StETHBurnRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bool","name":"isCover","type":"bool"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"StETHBurnt","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RECOVER_ASSETS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REQUEST_BURN_MY_STETH_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REQUEST_BURN_SHARES_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TREASURY","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_sharesToBurnLimit","type":"uint256"}],"name":"commitSharesToBurn","outputs":[{"internalType":"uint256","name":"sharesToBurnNow","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCoverSharesBurnt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExcessStETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNonCoverSharesBurnt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSharesRequestedToBurn","outputs":[{"internalType":"uint256","name":"coverShares","type":"uint256"},{"internalType":"uint256","name":"nonCoverShares","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"recoverERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"recoverExcessStETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stETHAmountToBurn","type":"uint256"}],"name":"requestBurnMyStETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stETHAmountToBurn","type":"uint256"}],"name":"requestBurnMyStETHForCover","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"uint256","name":"_sharesAmountToBurn","type":"uint256"}],"name":"requestBurnShares","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"uint256","name":"_sharesAmountToBurn","type":"uint256"}],"name":"requestBurnSharesForCover","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_treasury","type":"address"},{"internalType":"address","name":"_stETH","type":"address"},{"internalType":"uint256","name":"_totalCoverSharesBurnt","type":"uint256"},{"internalType":"uint256","name":"_totalNonCoverSharesBurnt","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AppAuthLidoFailed","type":"error"},{"inputs":[],"name":"DirectETHTransfer","type":"error"},{"inputs":[],"name":"StETHRecoveryWrongFunc","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"ZeroBurnAmount","type":"error"},{"inputs":[],"name":"ZeroRecoveryAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"ExcessStETHRecovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bool","name":"isCover","type":"bool"},{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"StETHBurnRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bool","name":"isCover","type":"bool"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"StETHBurnt","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RECOVER_ASSETS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REQUEST_BURN_MY_STETH_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REQUEST_BURN_SHARES_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TREASURY","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_sharesToBurnLimit","type":"uint256"}],"name":"commitSharesToBurn","outputs":[{"internalType":"uint256","name":"sharesToBurnNow","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCoverSharesBurnt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExcessStETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNonCoverSharesBurnt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSharesRequestedToBurn","outputs":[{"internalType":"uint256","name":"coverShares","type":"uint256"},{"internalType":"uint256","name":"nonCoverShares","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"recoverERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"recoverExcessStETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stETHAmountToBurn","type":"uint256"}],"name":"requestBurnMyStETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stETHAmountToBurn","type":"uint256"}],"name":"requestBurnMyStETHForCover","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"uint256","name":"_sharesAmountToBurn","type":"uint256"}],"name":"requestBurnShares","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"uint256","name":"_sharesAmountToBurn","type":"uint256"}],"name":"requestBurnSharesForCover","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/lib/abi/EIP712StETH.json b/lib/abi/EIP712StETH.json index b5fcf2fb2..d811a60f0 100644 --- a/lib/abi/EIP712StETH.json +++ b/lib/abi/EIP712StETH.json @@ -1 +1 @@ -[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"domainSeparatorV4","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_structHash","type":"bytes32"}],"name":"hashTypedDataV4","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_stETH","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ZeroStETHAddress","type":"error"},{"inputs":[{"internalType":"address","name":"_stETH","type":"address"}],"name":"domainSeparatorV4","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_stETH","type":"address"}],"name":"eip712Domain","outputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_stETH","type":"address"},{"internalType":"bytes32","name":"_structHash","type":"bytes32"}],"name":"hashTypedDataV4","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/abi/INFTDescriptor.json b/lib/abi/INFTDescriptor.json new file mode 100644 index 000000000..c42b80ece --- /dev/null +++ b/lib/abi/INFTDescriptor.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"constructTokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/abi/IStakingRouter.json b/lib/abi/IStakingRouter.json index 546692e99..4cdff3a03 100644 --- a/lib/abi/IStakingRouter.json +++ b/lib/abi/IStakingRouter.json @@ -1 +1 @@ -[{"inputs":[],"name":"getExitedValidatorsCountAcrossAllModules","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"moduleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[],"name":"getExitedValidatorsCountAcrossAllModules","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"onValidatorsCountsByNodeOperatorReportingFinished","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"moduleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/lib/abi/Lido.json b/lib/abi/Lido.json index 7d0de5b65..58ee3388a 100644 --- a/lib/abi/Lido.json +++ b/lib/abi/Lido.json @@ -1 +1 @@ -[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_CONTROL_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStakingPaused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"TOTAL_BASIS_POINTS","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_maxStakeLimit","type":"uint256"},{"name":"_stakeLimitIncreasePerBlock","type":"uint256"}],"name":"setStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"RESUME_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_lidoLocator","type":"address"},{"name":"_eip712StETH","type":"address"}],"name":"finalizeUpgrade_v2","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newDepositedValidators","type":"uint256"}],"name":"unsafeChangeDepositedValidators","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_lidoLocator","type":"address"},{"name":"_eip712StETH","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"receiveELRewards","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentStakeLimit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getStakeLimitFullInfo","outputs":[{"name":"isStakingPaused","type":"bool"},{"name":"isStakingLimitSet","type":"bool"},{"name":"currentStakeLimit","type":"uint256"},{"name":"maxStakeLimit","type":"uint256"},{"name":"maxStakeLimitGrowthBlocks","type":"uint256"},{"name":"prevStakeLimit","type":"uint256"},{"name":"prevStakeBlockNumber","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferSharesFrom","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"resumeStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"receiveWithdrawals","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"nonces","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getContractVersion","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDepositsCount","type":"uint256"},{"name":"_stakingModuleId","type":"uint256"},{"name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"UNSAFE_CHANGE_DEPOSITED_VALIDATORS_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_reportTimestamp","type":"uint256"},{"name":"_timeElapsed","type":"uint256"},{"name":"_clValidators","type":"uint256"},{"name":"_clBalance","type":"uint256"},{"name":"_withdrawalVaultBalance","type":"uint256"},{"name":"_elRewardsVaultBalance","type":"uint256"},{"name":"_lastFinalizableRequestId","type":"uint256"},{"name":"_simulatedShareRate","type":"uint256"}],"name":"handleOracleReport","outputs":[{"name":"postTotalPooledEther","type":"uint256"},{"name":"postTotalShares","type":"uint256"},{"name":"withdrawals","type":"uint256"},{"name":"elRewards","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"removeStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"receiveStakingRouterDepositRemainder","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"totalFee","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_deadline","type":"uint256"},{"name":"_v","type":"uint8"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLidoLocator","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"canDeposit","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getDepositableEther","outputs":[{"name":"depositableEth","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pauseStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTotalELRewardsCollected","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[],"name":"StakingPaused","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingResumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"maxStakeLimit","type":"uint256"},{"indexed":false,"name":"stakeLimitIncreasePerBlock","type":"uint256"}],"name":"StakingLimitSet","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingLimitRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"preCLValidators","type":"uint256"},{"indexed":false,"name":"postCLValidators","type":"uint256"}],"name":"CLValidatorsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"depositedValidators","type":"uint256"}],"name":"DepositedValidatorsChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"preCLBalance","type":"uint256"},{"indexed":false,"name":"postCLBalance","type":"uint256"},{"indexed":false,"name":"withdrawalsWithdrawn","type":"uint256"},{"indexed":false,"name":"executionLayerRewardsWithdrawn","type":"uint256"},{"indexed":false,"name":"postBufferedEther","type":"uint256"}],"name":"ETHDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"timeElapsed","type":"uint256"},{"indexed":false,"name":"preTotalShares","type":"uint256"},{"indexed":false,"name":"preTotalEther","type":"uint256"},{"indexed":false,"name":"postTotalShares","type":"uint256"},{"indexed":false,"name":"postTotalEther","type":"uint256"},{"indexed":false,"name":"sharesMintedAsFees","type":"uint256"}],"name":"TokenRebased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"lidoLocator","type":"address"}],"name":"LidoLocatorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"ELRewardsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"WithdrawalsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"StakingRouterDepositRemainderReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"eip712StETH","type":"address"}],"name":"EIP712StETHInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"preRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"postRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"SharesBurnt","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"}] \ No newline at end of file +[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_CONTROL_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStakingPaused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"TOTAL_BASIS_POINTS","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_maxStakeLimit","type":"uint256"},{"name":"_stakeLimitIncreasePerBlock","type":"uint256"}],"name":"setStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"RESUME_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_lidoLocator","type":"address"},{"name":"_eip712StETH","type":"address"}],"name":"finalizeUpgrade_v2","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newDepositedValidators","type":"uint256"}],"name":"unsafeChangeDepositedValidators","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_lidoLocator","type":"address"},{"name":"_eip712StETH","type":"address"}],"name":"initialize","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"receiveELRewards","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentStakeLimit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getStakeLimitFullInfo","outputs":[{"name":"isStakingPaused","type":"bool"},{"name":"isStakingLimitSet","type":"bool"},{"name":"currentStakeLimit","type":"uint256"},{"name":"maxStakeLimit","type":"uint256"},{"name":"maxStakeLimitGrowthBlocks","type":"uint256"},{"name":"prevStakeLimit","type":"uint256"},{"name":"prevStakeBlockNumber","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferSharesFrom","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"resumeStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"receiveWithdrawals","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"nonces","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"eip712Domain","outputs":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getContractVersion","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getEIP712StETH","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDepositsCount","type":"uint256"},{"name":"_stakingModuleId","type":"uint256"},{"name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"UNSAFE_CHANGE_DEPOSITED_VALIDATORS_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_reportTimestamp","type":"uint256"},{"name":"_timeElapsed","type":"uint256"},{"name":"_clValidators","type":"uint256"},{"name":"_clBalance","type":"uint256"},{"name":"_withdrawalVaultBalance","type":"uint256"},{"name":"_elRewardsVaultBalance","type":"uint256"},{"name":"_lastFinalizableRequestId","type":"uint256"},{"name":"_simulatedShareRate","type":"uint256"}],"name":"handleOracleReport","outputs":[{"name":"postTotalPooledEther","type":"uint256"},{"name":"postTotalShares","type":"uint256"},{"name":"withdrawals","type":"uint256"},{"name":"elRewards","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"removeStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"totalFee","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_deadline","type":"uint256"},{"name":"_v","type":"uint8"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLidoLocator","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"canDeposit","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getDepositableEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pauseStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTotalELRewardsCollected","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[],"name":"StakingPaused","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingResumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"maxStakeLimit","type":"uint256"},{"indexed":false,"name":"stakeLimitIncreasePerBlock","type":"uint256"}],"name":"StakingLimitSet","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingLimitRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"preCLValidators","type":"uint256"},{"indexed":false,"name":"postCLValidators","type":"uint256"}],"name":"CLValidatorsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"depositedValidators","type":"uint256"}],"name":"DepositedValidatorsChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"preCLBalance","type":"uint256"},{"indexed":false,"name":"postCLBalance","type":"uint256"},{"indexed":false,"name":"withdrawalsWithdrawn","type":"uint256"},{"indexed":false,"name":"executionLayerRewardsWithdrawn","type":"uint256"},{"indexed":false,"name":"postBufferedEther","type":"uint256"}],"name":"ETHDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"timeElapsed","type":"uint256"},{"indexed":false,"name":"preTotalShares","type":"uint256"},{"indexed":false,"name":"preTotalEther","type":"uint256"},{"indexed":false,"name":"postTotalShares","type":"uint256"},{"indexed":false,"name":"postTotalEther","type":"uint256"},{"indexed":false,"name":"sharesMintedAsFees","type":"uint256"}],"name":"TokenRebased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"lidoLocator","type":"address"}],"name":"LidoLocatorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"ELRewardsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"WithdrawalsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"eip712StETH","type":"address"}],"name":"EIP712StETHInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"preRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"postRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"SharesBurnt","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"}] \ No newline at end of file diff --git a/lib/abi/LidoLocator.json b/lib/abi/LidoLocator.json index a75b6f4bc..f2145f2d8 100644 --- a/lib/abi/LidoLocator.json +++ b/lib/abi/LidoLocator.json @@ -1 +1 @@ -[{"inputs":[{"components":[{"internalType":"address","name":"accountingOracle","type":"address"},{"internalType":"address","name":"depositSecurityModule","type":"address"},{"internalType":"address","name":"elRewardsVault","type":"address"},{"internalType":"address","name":"legacyOracle","type":"address"},{"internalType":"address","name":"lido","type":"address"},{"internalType":"address","name":"oracleReportSanityChecker","type":"address"},{"internalType":"address","name":"postTokenRebaseReceiver","type":"address"},{"internalType":"address","name":"burner","type":"address"},{"internalType":"address","name":"stakingRouter","type":"address"},{"internalType":"address","name":"treasury","type":"address"},{"internalType":"address","name":"validatorsExitBusOracle","type":"address"},{"internalType":"address","name":"withdrawalQueue","type":"address"},{"internalType":"address","name":"withdrawalVault","type":"address"}],"internalType":"struct LidoLocator.Config","name":"_config","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"accountingOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"burner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"coreComponents","outputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositSecurityModule","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"elRewardsVault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"legacyOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracleReportComponentsForLido","outputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracleReportSanityChecker","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"postTokenRebaseReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingRouter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"treasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"validatorsExitBusOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawalQueue","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawalVault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[{"components":[{"internalType":"address","name":"accountingOracle","type":"address"},{"internalType":"address","name":"depositSecurityModule","type":"address"},{"internalType":"address","name":"elRewardsVault","type":"address"},{"internalType":"address","name":"legacyOracle","type":"address"},{"internalType":"address","name":"lido","type":"address"},{"internalType":"address","name":"oracleReportSanityChecker","type":"address"},{"internalType":"address","name":"postTokenRebaseReceiver","type":"address"},{"internalType":"address","name":"burner","type":"address"},{"internalType":"address","name":"stakingRouter","type":"address"},{"internalType":"address","name":"treasury","type":"address"},{"internalType":"address","name":"validatorsExitBusOracle","type":"address"},{"internalType":"address","name":"withdrawalQueue","type":"address"},{"internalType":"address","name":"withdrawalVault","type":"address"},{"internalType":"address","name":"oracleDaemonConfig","type":"address"}],"internalType":"struct LidoLocator.Config","name":"_config","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"accountingOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"burner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"coreComponents","outputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositSecurityModule","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"elRewardsVault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"legacyOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracleDaemonConfig","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracleReportComponentsForLido","outputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracleReportSanityChecker","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"postTokenRebaseReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingRouter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"treasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"validatorsExitBusOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawalQueue","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawalVault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/abi/NodeOperatorsRegistry.json b/lib/abi/NodeOperatorsRegistry.json index a5a7660a1..6efc49363 100644 --- a/lib/abi/NodeOperatorsRegistry.json +++ b/lib/abi/NodeOperatorsRegistry.json @@ -1 +1 @@ -[{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_keysCount","type":"uint256"},{"name":"_publicKeys","type":"bytes"},{"name":"_signatures","type":"bytes"}],"name":"addSigningKeys","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getType","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_locator","type":"address"},{"name":"_type","type":"bytes32"}],"name":"finalizeUpgrade_v2","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_offset","type":"uint256"},{"name":"_limit","type":"uint256"}],"name":"getNodeOperatorIds","outputs":[{"name":"nodeOperatorIds","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_offset","type":"uint256"},{"name":"_limit","type":"uint256"}],"name":"getSigningKeys","outputs":[{"name":"pubkeys","type":"bytes"},{"name":"signatures","type":"bytes"},{"name":"used","type":"bool[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_fromIndex","type":"uint256"},{"name":"_keysCount","type":"uint256"}],"name":"removeSigningKeysOperatorBH","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorIsActive","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_name","type":"string"}],"name":"setNodeOperatorName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_totalRewardShares","type":"uint256"}],"name":"getRewardsDistribution","outputs":[{"name":"recipients","type":"address[]"},{"name":"shares","type":"uint256[]"},{"name":"penalized","type":"bool[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_indexFrom","type":"uint256"},{"name":"_indexTo","type":"uint256"}],"name":"invalidateReadyToDepositKeysRange","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_isTargetLimitActive","type":"bool"},{"name":"_targetLimit","type":"uint64"}],"name":"updateTargetValidatorsLimits","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_delay","type":"uint256"}],"name":"setStuckPenaltyDelay","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getStuckPenaltyDelay","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_index","type":"uint256"}],"name":"removeSigningKey","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_fromIndex","type":"uint256"},{"name":"_keysCount","type":"uint256"}],"name":"removeSigningKeys","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"isOperatorPenalized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"deactivateNodeOperator","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_ROUTER_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_keysCount","type":"uint256"},{"name":"_publicKeys","type":"bytes"},{"name":"_signatures","type":"bytes"}],"name":"addSigningKeysOperatorBH","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"onAllValidatorCountersUpdated","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getActiveNodeOperatorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_rewardAddress","type":"address"}],"name":"addNodeOperator","outputs":[{"name":"id","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getContractVersion","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"getUnusedSigningKeyCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_NODE_OPERATOR_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"onWithdrawalCredentialsChanged","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"activateNodeOperator","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_rewardAddress","type":"address"}],"name":"setNodeOperatorRewardAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_fullInfo","type":"bool"}],"name":"getNodeOperator","outputs":[{"name":"active","type":"bool"},{"name":"name","type":"string"},{"name":"rewardAddress","type":"address"},{"name":"stakingLimit","type":"uint64"},{"name":"stoppedValidators","type":"uint64"},{"name":"totalSigningKeys","type":"uint64"},{"name":"usedSigningKeys","type":"uint64"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getStakingModuleSummary","outputs":[{"name":"totalExitedValidators","type":"uint256"},{"name":"totalDepositedValidators","type":"uint256"},{"name":"depositableValidatorsCount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_refundedValidatorsCount","type":"uint256"}],"name":"updateRefundedValidatorsCount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNodeOperatorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_vettedSigningKeysCount","type":"uint64"}],"name":"setNodeOperatorStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorSummary","outputs":[{"name":"isTargetLimitActive","type":"bool"},{"name":"targetValidatorsCount","type":"uint256"},{"name":"stuckValidatorsCount","type":"uint256"},{"name":"refundedValidatorsCount","type":"uint256"},{"name":"stuckPenaltyEndTimestamp","type":"uint256"},{"name":"totalExitedValidators","type":"uint256"},{"name":"totalDepositedValidators","type":"uint256"},{"name":"depositableValidatorsCount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_index","type":"uint256"}],"name":"getSigningKey","outputs":[{"name":"key","type":"bytes"},{"name":"depositSignature","type":"bytes"},{"name":"used","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MAX_NODE_OPERATOR_NAME_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_locator","type":"address"},{"name":"_type","type":"bytes32"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_depositsCount","type":"uint256"},{"name":"","type":"bytes"}],"name":"obtainDepositData","outputs":[{"name":"depositsCount","type":"uint256"},{"name":"publicKeys","type":"bytes"},{"name":"signatures","type":"bytes"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getKeysOpIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNonce","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLocator","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_NODE_OPERATOR_LIMIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"getTotalSigningKeyCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_exitedValidatorsCount","type":"uint256"}],"name":"updateExitedValidatorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_stuckValidatorsCount","type":"uint256"}],"name":"updateStuckValidatorsCount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MAX_NODE_OPERATORS_COUNT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_index","type":"uint256"}],"name":"removeSigningKeyOperatorBH","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"handleRewardsMinted","outputs":[],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_exitedValidatorsCount","type":"uint256"},{"name":"_stuckValidatorsCount","type":"uint256"}],"name":"unsafeUpdateValidatorsCount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_SIGNING_KEYS","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"name","type":"string"},{"indexed":false,"name":"rewardAddress","type":"address"},{"indexed":false,"name":"stakingLimit","type":"uint64"}],"name":"NodeOperatorAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"active","type":"bool"}],"name":"NodeOperatorActiveSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"name","type":"string"}],"name":"NodeOperatorNameSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"rewardAddress","type":"address"}],"name":"NodeOperatorRewardAddressSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"totalKeysTrimmed","type":"uint64"}],"name":"NodeOperatorTotalKeysTrimmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"keysOpIndex","type":"uint256"}],"name":"KeysOpIndexSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"moduleType","type":"bytes32"}],"name":"StakingModuleTypeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"rewardAddress","type":"address"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"RewardsDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"locatorAddress","type":"address"}],"name":"LocatorContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"approvedValidatorsCount","type":"uint256"}],"name":"VettedSigningKeysCountChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"depositedValidatorsCount","type":"uint256"}],"name":"DepositedSigningKeysCountChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"exitedValidatorsCount","type":"uint256"}],"name":"ExitedSigningKeysCountChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"totalValidatorsCount","type":"uint256"}],"name":"TotalSigningKeysCountChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"nonce","type":"uint256"}],"name":"NonceChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"stuckValidatorsCount","type":"uint256"}],"name":"StuckValidatorsCountChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"RefundedValidatorsCount","type":"uint256"}],"name":"RefundedValidatorsCountChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"targetValidatorsCount","type":"uint256"}],"name":"TargetValidatorsCountChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"recipientAddress","type":"address"},{"indexed":false,"name":"sharesPenalizedAmount","type":"uint256"}],"name":"NodeOperatorPenalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"}] \ No newline at end of file +[{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_keysCount","type":"uint256"},{"name":"_publicKeys","type":"bytes"},{"name":"_signatures","type":"bytes"}],"name":"addSigningKeys","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getType","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"clearNodeOperatorPenalty","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_offset","type":"uint256"},{"name":"_limit","type":"uint256"}],"name":"getNodeOperatorIds","outputs":[{"name":"nodeOperatorIds","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_offset","type":"uint256"},{"name":"_limit","type":"uint256"}],"name":"getSigningKeys","outputs":[{"name":"pubkeys","type":"bytes"},{"name":"signatures","type":"bytes"},{"name":"used","type":"bool[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_fromIndex","type":"uint256"},{"name":"_keysCount","type":"uint256"}],"name":"removeSigningKeysOperatorBH","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorIsActive","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_name","type":"string"}],"name":"setNodeOperatorName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_totalRewardShares","type":"uint256"}],"name":"getRewardsDistribution","outputs":[{"name":"recipients","type":"address[]"},{"name":"shares","type":"uint256[]"},{"name":"penalized","type":"bool[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_indexFrom","type":"uint256"},{"name":"_indexTo","type":"uint256"}],"name":"invalidateReadyToDepositKeysRange","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_locator","type":"address"},{"name":"_type","type":"bytes32"},{"name":"_stuckPenaltyDelay","type":"uint256"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_isTargetLimitActive","type":"bool"},{"name":"_targetLimit","type":"uint64"}],"name":"updateTargetValidatorsLimits","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_delay","type":"uint256"}],"name":"setStuckPenaltyDelay","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getStuckPenaltyDelay","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_index","type":"uint256"}],"name":"removeSigningKey","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_fromIndex","type":"uint256"},{"name":"_keysCount","type":"uint256"}],"name":"removeSigningKeys","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"isOperatorPenalized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"deactivateNodeOperator","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_ROUTER_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_keysCount","type":"uint256"},{"name":"_publicKeys","type":"bytes"},{"name":"_signatures","type":"bytes"}],"name":"addSigningKeysOperatorBH","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getActiveNodeOperatorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_rewardAddress","type":"address"}],"name":"addNodeOperator","outputs":[{"name":"id","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getContractVersion","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"getUnusedSigningKeyCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"onRewardsMinted","outputs":[],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_NODE_OPERATOR_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"onWithdrawalCredentialsChanged","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"activateNodeOperator","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_rewardAddress","type":"address"}],"name":"setNodeOperatorRewardAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_fullInfo","type":"bool"}],"name":"getNodeOperator","outputs":[{"name":"active","type":"bool"},{"name":"name","type":"string"},{"name":"rewardAddress","type":"address"},{"name":"stakingLimit","type":"uint64"},{"name":"stoppedValidators","type":"uint64"},{"name":"totalSigningKeys","type":"uint64"},{"name":"usedSigningKeys","type":"uint64"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_locator","type":"address"},{"name":"_type","type":"bytes32"},{"name":"_stuckPenaltyDelay","type":"uint256"}],"name":"finalizeUpgrade_v2","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getStakingModuleSummary","outputs":[{"name":"totalExitedValidators","type":"uint256"},{"name":"totalDepositedValidators","type":"uint256"},{"name":"depositableValidatorsCount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorIds","type":"bytes"},{"name":"_exitedValidatorsCounts","type":"bytes"}],"name":"updateExitedValidatorsCount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorIds","type":"bytes"},{"name":"_stuckValidatorsCounts","type":"bytes"}],"name":"updateStuckValidatorsCount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_refundedValidatorsCount","type":"uint256"}],"name":"updateRefundedValidatorsCount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNodeOperatorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_vettedSigningKeysCount","type":"uint64"}],"name":"setNodeOperatorStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorSummary","outputs":[{"name":"isTargetLimitActive","type":"bool"},{"name":"targetValidatorsCount","type":"uint256"},{"name":"stuckValidatorsCount","type":"uint256"},{"name":"refundedValidatorsCount","type":"uint256"},{"name":"stuckPenaltyEndTimestamp","type":"uint256"},{"name":"totalExitedValidators","type":"uint256"},{"name":"totalDepositedValidators","type":"uint256"},{"name":"depositableValidatorsCount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_index","type":"uint256"}],"name":"getSigningKey","outputs":[{"name":"key","type":"bytes"},{"name":"depositSignature","type":"bytes"},{"name":"used","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MAX_NODE_OPERATOR_NAME_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_depositsCount","type":"uint256"},{"name":"","type":"bytes"}],"name":"obtainDepositData","outputs":[{"name":"publicKeys","type":"bytes"},{"name":"signatures","type":"bytes"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getKeysOpIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNonce","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLocator","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_NODE_OPERATOR_LIMIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"getTotalSigningKeyCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"onExitedAndStuckValidatorsCountsUpdated","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MAX_NODE_OPERATORS_COUNT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_index","type":"uint256"}],"name":"removeSigningKeyOperatorBH","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nodeOperatorId","type":"uint256"},{"name":"_exitedValidatorsCount","type":"uint256"},{"name":"_stuckValidatorsCount","type":"uint256"}],"name":"unsafeUpdateValidatorsCount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_SIGNING_KEYS","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_nodeOperatorId","type":"uint256"}],"name":"isOperatorPenaltyCleared","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"name","type":"string"},{"indexed":false,"name":"rewardAddress","type":"address"},{"indexed":false,"name":"stakingLimit","type":"uint64"}],"name":"NodeOperatorAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"active","type":"bool"}],"name":"NodeOperatorActiveSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"name","type":"string"}],"name":"NodeOperatorNameSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"rewardAddress","type":"address"}],"name":"NodeOperatorRewardAddressSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"totalKeysTrimmed","type":"uint64"}],"name":"NodeOperatorTotalKeysTrimmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"keysOpIndex","type":"uint256"}],"name":"KeysOpIndexSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"moduleType","type":"bytes32"}],"name":"StakingModuleTypeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"rewardAddress","type":"address"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"RewardsDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"locatorAddress","type":"address"}],"name":"LocatorContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"approvedValidatorsCount","type":"uint256"}],"name":"VettedSigningKeysCountChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"depositedValidatorsCount","type":"uint256"}],"name":"DepositedSigningKeysCountChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"exitedValidatorsCount","type":"uint256"}],"name":"ExitedSigningKeysCountChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"totalValidatorsCount","type":"uint256"}],"name":"TotalSigningKeysCountChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"nonce","type":"uint256"}],"name":"NonceChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"stuckPenaltyDelay","type":"uint256"}],"name":"StuckPenaltyDelayChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"stuckValidatorsCount","type":"uint256"},{"indexed":false,"name":"refundedValidatorsCount","type":"uint256"},{"indexed":false,"name":"stuckPenaltyEndTimestamp","type":"uint256"}],"name":"StuckPenaltyStateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"targetValidatorsCount","type":"uint256"}],"name":"TargetValidatorsCountChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"recipientAddress","type":"address"},{"indexed":false,"name":"sharesPenalizedAmount","type":"uint256"}],"name":"NodeOperatorPenalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"}] \ No newline at end of file diff --git a/lib/abi/OracleReportSanityChecker.json b/lib/abi/OracleReportSanityChecker.json index 0b666eab2..17a81a848 100644 --- a/lib/abi/OracleReportSanityChecker.json +++ b/lib/abi/OracleReportSanityChecker.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_lidoLocator","type":"address"},{"internalType":"address","name":"_admin","type":"address"},{"components":[{"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"},{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"shareRateDeviationBPLimit","type":"uint256"},{"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"},{"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"},{"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"},{"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"}],"internalType":"struct LimitsList","name":"_limitsList","type":"tuple"},{"components":[{"internalType":"address[]","name":"allLimitsManagers","type":"address[]"},{"internalType":"address[]","name":"churnValidatorsPerDayLimitManagers","type":"address[]"},{"internalType":"address[]","name":"oneOffCLBalanceDecreaseLimitManagers","type":"address[]"},{"internalType":"address[]","name":"annualBalanceIncreaseLimitManagers","type":"address[]"},{"internalType":"address[]","name":"shareRateDeviationLimitManagers","type":"address[]"},{"internalType":"address[]","name":"maxValidatorExitRequestsPerReportManagers","type":"address[]"},{"internalType":"address[]","name":"maxAccountingExtraDataListItemsCountManagers","type":"address[]"},{"internalType":"address[]","name":"requestTimestampMarginManagers","type":"address[]"},{"internalType":"address[]","name":"maxPositiveTokenRebaseManagers","type":"address[]"}],"internalType":"struct OracleReportSanityChecker.ManagersRoster","name":"_managersRoster","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"limitPerDay","type":"uint256"},{"internalType":"uint256","name":"exitedPerDay","type":"uint256"}],"name":"ExitedValidatorsLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"churnLimit","type":"uint256"}],"name":"IncorrectAppearedValidators","type":"error"},{"inputs":[{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBP","type":"uint256"}],"name":"IncorrectCLBalanceDecrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"annualBalanceDiff","type":"uint256"}],"name":"IncorrectCLBalanceIncrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"actualELRewardsVaultBalance","type":"uint256"}],"name":"IncorrectELRewardsVaultBalance","type":"error"},{"inputs":[{"internalType":"uint256","name":"churnLimit","type":"uint256"}],"name":"IncorrectExitedValidators","type":"error"},{"inputs":[{"internalType":"uint256","name":"finalizationShareDeviation","type":"uint256"}],"name":"IncorrectFinalizationShareRate","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxRequestsCount","type":"uint256"}],"name":"IncorrectNumberOfExitRequestsPerReport","type":"error"},{"inputs":[{"internalType":"uint256","name":"requestCreationBlock","type":"uint256"}],"name":"IncorrectRequestFinalization","type":"error"},{"inputs":[{"internalType":"uint256","name":"actualWithdrawalVaultBalance","type":"uint256"}],"name":"IncorrectWithdrawalsVaultBalance","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxItemsCount","type":"uint256"},{"internalType":"uint256","name":"receivedItemsCount","type":"uint256"}],"name":"MaxAccountingExtraDataItemsCountExceeded","type":"error"},{"inputs":[],"name":"TooHighTokenRebaseLimit","type":"error"},{"inputs":[],"name":"TooLowTokenRebaseLimit","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"},{"internalType":"uint256","name":"nodeOpsCount","type":"uint256"}],"name":"TooManyNodeOpsPerExtraDataItem","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"}],"name":"AnnualBalanceIncreaseBPLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"}],"name":"ChurnValidatorsPerDayLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"}],"name":"MaxAccountingExtraDataListItemsCountSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"}],"name":"MaxPositiveTokenRebaseSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"}],"name":"MaxValidatorExitRequestsPerReportSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"}],"name":"OneOffCLBalanceDecreaseBPLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"}],"name":"RequestTimestampMarginSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"shareRateDeviationBPLimit","type":"uint256"}],"name":"ShareRateDeviationBPLimitSet","type":"event"},{"inputs":[],"name":"ALL_LIMITS_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHURN_VALIDATORS_PER_DAY_LIMIT_MANGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_extraDataListItemsCount","type":"uint256"}],"name":"checkAccountingExtraDataListItemsCount","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_timeElapsed","type":"uint256"},{"internalType":"uint256","name":"_preCLBalance","type":"uint256"},{"internalType":"uint256","name":"_postCLBalance","type":"uint256"},{"internalType":"uint256","name":"_withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_preCLValidators","type":"uint256"},{"internalType":"uint256","name":"_postCLValidators","type":"uint256"}],"name":"checkAccountingOracleReport","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_exitRequestsCount","type":"uint256"}],"name":"checkExitBusOracleReport","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_exitedValidatorsCount","type":"uint256"}],"name":"checkExitedValidatorsRatePerDay","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_itemIndex","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorsCount","type":"uint256"}],"name":"checkNodeOperatorsPerExtraDataItemCount","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_postTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"_postTotalShares","type":"uint256"},{"internalType":"uint256","name":"_etherLockedOnWithdrawalQueue","type":"uint256"},{"internalType":"uint256","name":"_sharesBurntFromWithdrawalQueue","type":"uint256"},{"internalType":"uint256","name":"_simulatedShareRate","type":"uint256"}],"name":"checkSimulatedShareRate","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_lastFinalizableRequestId","type":"uint256"},{"internalType":"uint256","name":"_reportTimestamp","type":"uint256"}],"name":"checkWithdrawalQueueOracleReport","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLidoLocator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxPositiveTokenRebase","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOracleReportLimits","outputs":[{"components":[{"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"},{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"shareRateDeviationBPLimit","type":"uint256"},{"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"},{"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"},{"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"},{"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"}],"internalType":"struct LimitsList","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_annualBalanceIncreaseBPLimit","type":"uint256"}],"name":"setAnnualBalanceIncreaseBPLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_churnValidatorsPerDayLimit","type":"uint256"}],"name":"setChurnValidatorsPerDayLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxAccountingExtraDataListItemsCount","type":"uint256"}],"name":"setMaxAccountingExtraDataListItemsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxValidatorExitRequestsPerReport","type":"uint256"}],"name":"setMaxExitRequestsPerOracleReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxPositiveTokenRebase","type":"uint256"}],"name":"setMaxPositiveTokenRebase","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_oneOffCLBalanceDecreaseBPLimit","type":"uint256"}],"name":"setOneOffCLBalanceDecreaseBPLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"},{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"shareRateDeviationBPLimit","type":"uint256"},{"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"},{"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"},{"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"},{"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"}],"internalType":"struct LimitsList","name":"_limitsList","type":"tuple"}],"name":"setOracleReportLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestTimestampMargin","type":"uint256"}],"name":"setRequestTimestampMargin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_shareRateDeviationBPLimit","type":"uint256"}],"name":"setShareRateDeviationBPLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_preTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"_preTotalShares","type":"uint256"},{"internalType":"uint256","name":"_preCLBalance","type":"uint256"},{"internalType":"uint256","name":"_postCLBalance","type":"uint256"},{"internalType":"uint256","name":"_withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_etherToLockForWithdrawals","type":"uint256"}],"name":"smoothenTokenRebase","outputs":[{"internalType":"uint256","name":"withdrawals","type":"uint256"},{"internalType":"uint256","name":"elRewards","type":"uint256"},{"internalType":"uint256","name":"sharesToBurnLimit","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_lidoLocator","type":"address"},{"internalType":"address","name":"_admin","type":"address"},{"components":[{"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"},{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"simulatedShareRateDeviationBPLimit","type":"uint256"},{"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"},{"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"},{"internalType":"uint256","name":"maxNodeOperatorsPerExtraDataItemCount","type":"uint256"},{"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"},{"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"}],"internalType":"struct LimitsList","name":"_limitsList","type":"tuple"},{"components":[{"internalType":"address[]","name":"allLimitsManagers","type":"address[]"},{"internalType":"address[]","name":"churnValidatorsPerDayLimitManagers","type":"address[]"},{"internalType":"address[]","name":"oneOffCLBalanceDecreaseLimitManagers","type":"address[]"},{"internalType":"address[]","name":"annualBalanceIncreaseLimitManagers","type":"address[]"},{"internalType":"address[]","name":"shareRateDeviationLimitManagers","type":"address[]"},{"internalType":"address[]","name":"maxValidatorExitRequestsPerReportManagers","type":"address[]"},{"internalType":"address[]","name":"maxAccountingExtraDataListItemsCountManagers","type":"address[]"},{"internalType":"address[]","name":"maxNodeOperatorsPerExtraDataItemCountManagers","type":"address[]"},{"internalType":"address[]","name":"requestTimestampMarginManagers","type":"address[]"},{"internalType":"address[]","name":"maxPositiveTokenRebaseManagers","type":"address[]"}],"internalType":"struct OracleReportSanityChecker.ManagersRoster","name":"_managersRoster","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"limitPerDay","type":"uint256"},{"internalType":"uint256","name":"exitedPerDay","type":"uint256"}],"name":"ExitedValidatorsLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"churnLimit","type":"uint256"}],"name":"IncorrectAppearedValidators","type":"error"},{"inputs":[{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBP","type":"uint256"}],"name":"IncorrectCLBalanceDecrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"annualBalanceDiff","type":"uint256"}],"name":"IncorrectCLBalanceIncrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"actualELRewardsVaultBalance","type":"uint256"}],"name":"IncorrectELRewardsVaultBalance","type":"error"},{"inputs":[{"internalType":"uint256","name":"churnLimit","type":"uint256"}],"name":"IncorrectExitedValidators","type":"error"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"maxAllowedValue","type":"uint256"}],"name":"IncorrectLimitValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxRequestsCount","type":"uint256"}],"name":"IncorrectNumberOfExitRequestsPerReport","type":"error"},{"inputs":[{"internalType":"uint256","name":"requestCreationBlock","type":"uint256"}],"name":"IncorrectRequestFinalization","type":"error"},{"inputs":[{"internalType":"uint256","name":"simulatedShareDeviation","type":"uint256"}],"name":"IncorrectSimulatedShareRate","type":"error"},{"inputs":[{"internalType":"uint256","name":"actualWithdrawalVaultBalance","type":"uint256"}],"name":"IncorrectWithdrawalsVaultBalance","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxItemsCount","type":"uint256"},{"internalType":"uint256","name":"receivedItemsCount","type":"uint256"}],"name":"MaxAccountingExtraDataItemsCountExceeded","type":"error"},{"inputs":[],"name":"TooHighTokenRebaseLimit","type":"error"},{"inputs":[],"name":"TooLowTokenRebaseLimit","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"},{"internalType":"uint256","name":"nodeOpsCount","type":"uint256"}],"name":"TooManyNodeOpsPerExtraDataItem","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"}],"name":"AnnualBalanceIncreaseBPLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"}],"name":"ChurnValidatorsPerDayLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"}],"name":"MaxAccountingExtraDataListItemsCountSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxNodeOperatorsPerExtraDataItemCount","type":"uint256"}],"name":"MaxNodeOperatorsPerExtraDataItemCountSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"}],"name":"MaxPositiveTokenRebaseSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"}],"name":"MaxValidatorExitRequestsPerReportSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"}],"name":"OneOffCLBalanceDecreaseBPLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"}],"name":"RequestTimestampMarginSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"simulatedShareRateDeviationBPLimit","type":"uint256"}],"name":"SimulatedShareRateDeviationBPLimitSet","type":"event"},{"inputs":[],"name":"ALL_LIMITS_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHURN_VALIDATORS_PER_DAY_LIMIT_MANGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_extraDataListItemsCount","type":"uint256"}],"name":"checkAccountingExtraDataListItemsCount","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_timeElapsed","type":"uint256"},{"internalType":"uint256","name":"_preCLBalance","type":"uint256"},{"internalType":"uint256","name":"_postCLBalance","type":"uint256"},{"internalType":"uint256","name":"_withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_preCLValidators","type":"uint256"},{"internalType":"uint256","name":"_postCLValidators","type":"uint256"}],"name":"checkAccountingOracleReport","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_exitRequestsCount","type":"uint256"}],"name":"checkExitBusOracleReport","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_exitedValidatorsCount","type":"uint256"}],"name":"checkExitedValidatorsRatePerDay","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_itemIndex","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorsCount","type":"uint256"}],"name":"checkNodeOperatorsPerExtraDataItemCount","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_postTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"_postTotalShares","type":"uint256"},{"internalType":"uint256","name":"_etherLockedOnWithdrawalQueue","type":"uint256"},{"internalType":"uint256","name":"_sharesBurntFromWithdrawalQueue","type":"uint256"},{"internalType":"uint256","name":"_simulatedShareRate","type":"uint256"}],"name":"checkSimulatedShareRate","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_lastFinalizableRequestId","type":"uint256"},{"internalType":"uint256","name":"_reportTimestamp","type":"uint256"}],"name":"checkWithdrawalQueueOracleReport","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLidoLocator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxPositiveTokenRebase","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOracleReportLimits","outputs":[{"components":[{"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"},{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"simulatedShareRateDeviationBPLimit","type":"uint256"},{"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"},{"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"},{"internalType":"uint256","name":"maxNodeOperatorsPerExtraDataItemCount","type":"uint256"},{"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"},{"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"}],"internalType":"struct LimitsList","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_annualBalanceIncreaseBPLimit","type":"uint256"}],"name":"setAnnualBalanceIncreaseBPLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_churnValidatorsPerDayLimit","type":"uint256"}],"name":"setChurnValidatorsPerDayLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxAccountingExtraDataListItemsCount","type":"uint256"}],"name":"setMaxAccountingExtraDataListItemsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxValidatorExitRequestsPerReport","type":"uint256"}],"name":"setMaxExitRequestsPerOracleReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxNodeOperatorsPerExtraDataItemCount","type":"uint256"}],"name":"setMaxNodeOperatorsPerExtraDataItemCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxPositiveTokenRebase","type":"uint256"}],"name":"setMaxPositiveTokenRebase","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_oneOffCLBalanceDecreaseBPLimit","type":"uint256"}],"name":"setOneOffCLBalanceDecreaseBPLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"},{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"simulatedShareRateDeviationBPLimit","type":"uint256"},{"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"},{"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"},{"internalType":"uint256","name":"maxNodeOperatorsPerExtraDataItemCount","type":"uint256"},{"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"},{"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"}],"internalType":"struct LimitsList","name":"_limitsList","type":"tuple"}],"name":"setOracleReportLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestTimestampMargin","type":"uint256"}],"name":"setRequestTimestampMargin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_simulatedShareRateDeviationBPLimit","type":"uint256"}],"name":"setSimulatedShareRateDeviationBPLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_preTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"_preTotalShares","type":"uint256"},{"internalType":"uint256","name":"_preCLBalance","type":"uint256"},{"internalType":"uint256","name":"_postCLBalance","type":"uint256"},{"internalType":"uint256","name":"_withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_etherToLockForWithdrawals","type":"uint256"}],"name":"smoothenTokenRebase","outputs":[{"internalType":"uint256","name":"withdrawals","type":"uint256"},{"internalType":"uint256","name":"elRewards","type":"uint256"},{"internalType":"uint256","name":"sharesToBurnLimit","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/abi/StETHPermit.json b/lib/abi/StETHPermit.json index 29b2631b9..b3ae6e2cc 100644 --- a/lib/abi/StETHPermit.json +++ b/lib/abi/StETHPermit.json @@ -1 +1 @@ -[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferSharesFrom","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"nonces","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_deadline","type":"uint256"},{"name":"_v","type":"uint8"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"eip712StETH","type":"address"}],"name":"EIP712StETHInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"preRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"postRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"SharesBurnt","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}] \ No newline at end of file +[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferSharesFrom","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"nonces","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"eip712Domain","outputs":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getEIP712StETH","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_deadline","type":"uint256"},{"name":"_v","type":"uint8"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"eip712StETH","type":"address"}],"name":"EIP712StETHInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"preRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"postRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"SharesBurnt","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}] \ No newline at end of file diff --git a/lib/abi/StakingRouter.json b/lib/abi/StakingRouter.json index eadd0ae03..756ae6ed7 100644 --- a/lib/abi/StakingRouter.json +++ b/lib/abi/StakingRouter.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AppAuthLidoFailed","type":"error"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[],"name":"DirectETHTransfer","type":"error"},{"inputs":[],"name":"EmptyWithdrawalsCredentials","type":"error"},{"inputs":[],"name":"ExitedValidatorsCountCannotDecrease","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[],"name":"InvalidReportData","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"NotExpectedBalance","type":"error"},{"inputs":[],"name":"StakingModuleIdTooLarge","type":"error"},{"inputs":[],"name":"StakingModuleNotActive","type":"error"},{"inputs":[],"name":"StakingModuleNotPaused","type":"error"},{"inputs":[],"name":"StakingModuleStatusTheSame","type":"error"},{"inputs":[],"name":"StakingModuleUnregistered","type":"error"},{"inputs":[],"name":"StakingModuleWrongName","type":"error"},{"inputs":[],"name":"StakingModulesLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpStuckValidatorsCount","type":"uint256"}],"name":"UnexpectedCurrentValidatorsCount","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ValueOver100Percent","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"address","name":"stakingModule","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"address","name":"createdBy","type":"address"}],"name":"StakingModuleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unreportedExitedValidatorsCount","type":"uint256"}],"name":"StakingModuleExitedValidatorsIncompleteReporting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakingModuleFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"treasuryFee","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleFeesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"enum StakingRouter.StakingModuleStatus","name":"status","type":"uint8"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleStatusSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"targetShare","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleTargetShareSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakingRouterETHDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"withdrawalCredentials","type":"bytes32"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"WithdrawalCredentialsSet","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_PRECISION_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_WITHDRAWAL_CREDENTIALS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_REWARDS_MINTED_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_MANAGE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSAFE_SET_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"address","name":"_stakingModuleAddress","type":"address"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"addStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxDepositsCount","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"depositsCount","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getAllNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"}],"name":"getDepositsAllocation","outputs":[{"internalType":"uint256","name":"allocated","type":"uint256"},{"internalType":"uint256[]","name":"allocations","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExitedValidatorsCountAcrossAllModules","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLido","outputs":[{"internalType":"contract ILido","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256[]","name":"_nodeOperatorIds","type":"uint256[]"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorSummary","outputs":[{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistribution","outputs":[{"internalType":"uint96","name":"modulesFee","type":"uint96"},{"internalType":"uint96","name":"treasuryFee","type":"uint96"},{"internalType":"uint256","name":"basePrecision","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistributionE4Precision","outputs":[{"internalType":"uint16","name":"modulesFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModule","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleActiveValidatorsCount","outputs":[{"internalType":"uint256","name":"activeValidatorsCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"}],"name":"getStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModuleIds","outputs":[{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsDepositsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsStopped","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleLastDepositBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleMaxDepositsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleStatus","outputs":[{"internalType":"enum StakingRouter.StakingModuleStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleSummary","outputs":[{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModules","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule[]","name":"res","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModulesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingRewardsDistribution","outputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"},{"internalType":"uint96[]","name":"stakingModuleFees","type":"uint96[]"},{"internalType":"uint96","name":"totalFee","type":"uint96"},{"internalType":"uint256","name":"precisionPoints","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalFeeE4Precision","outputs":[{"internalType":"uint16","name":"totalFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_lido","type":"address"},{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"pauseStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_totalShares","type":"uint256[]"}],"name":"reportRewardsMinted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"resumeStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"enum StakingRouter.StakingModuleStatus","name":"_status","type":"uint8"}],"name":"setStakingModuleStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_triggerUpdateFinish","type":"bool"},{"components":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorStuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorStuckValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.ValidatorsCountCorrection","name":"_correction","type":"tuple"}],"name":"unsafeSetExitedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"uint256","name":"_refundedValidatorsCount","type":"uint256"}],"name":"updateRefundedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"updateStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AppAuthLidoFailed","type":"error"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[],"name":"DirectETHTransfer","type":"error"},{"inputs":[],"name":"EmptyWithdrawalsCredentials","type":"error"},{"inputs":[],"name":"ExitedValidatorsCountCannotDecrease","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"etherValue","type":"uint256"}],"name":"InvalidDepositsValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidPublicKeysBatchLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"InvalidReportData","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidSignaturesBatchLength","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"StakingModuleAddressExists","type":"error"},{"inputs":[],"name":"StakingModuleIdTooLarge","type":"error"},{"inputs":[],"name":"StakingModuleNotActive","type":"error"},{"inputs":[],"name":"StakingModuleNotPaused","type":"error"},{"inputs":[],"name":"StakingModuleStatusTheSame","type":"error"},{"inputs":[],"name":"StakingModuleUnregistered","type":"error"},{"inputs":[],"name":"StakingModuleWrongName","type":"error"},{"inputs":[],"name":"StakingModulesLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpStuckValidatorsCount","type":"uint256"}],"name":"UnexpectedCurrentValidatorsCount","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ValueOver100Percent","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"address","name":"stakingModule","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"address","name":"createdBy","type":"address"}],"name":"StakingModuleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unreportedExitedValidatorsCount","type":"uint256"}],"name":"StakingModuleExitedValidatorsIncompleteReporting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakingModuleFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"treasuryFee","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleFeesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"enum StakingRouter.StakingModuleStatus","name":"status","type":"uint8"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleStatusSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"targetShare","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleTargetShareSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakingRouterETHDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"withdrawalCredentials","type":"bytes32"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"WithdrawalCredentialsSet","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_PRECISION_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_WITHDRAWAL_CREDENTIALS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_REWARDS_MINTED_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_MANAGE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSAFE_SET_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"address","name":"_stakingModuleAddress","type":"address"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"addStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getAllNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"}],"name":"getDepositsAllocation","outputs":[{"internalType":"uint256","name":"allocated","type":"uint256"},{"internalType":"uint256[]","name":"allocations","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExitedValidatorsCountAcrossAllModules","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256[]","name":"_nodeOperatorIds","type":"uint256[]"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorSummary","outputs":[{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistribution","outputs":[{"internalType":"uint96","name":"modulesFee","type":"uint96"},{"internalType":"uint96","name":"treasuryFee","type":"uint96"},{"internalType":"uint256","name":"basePrecision","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistributionE4Precision","outputs":[{"internalType":"uint16","name":"modulesFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModule","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleActiveValidatorsCount","outputs":[{"internalType":"uint256","name":"activeValidatorsCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"}],"name":"getStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModuleIds","outputs":[{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsDepositsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsStopped","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleLastDepositBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_maxDepositsValue","type":"uint256"}],"name":"getStakingModuleMaxDepositsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleStatus","outputs":[{"internalType":"enum StakingRouter.StakingModuleStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleSummary","outputs":[{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModules","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule[]","name":"res","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModulesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingRewardsDistribution","outputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"},{"internalType":"uint96[]","name":"stakingModuleFees","type":"uint96[]"},{"internalType":"uint96","name":"totalFee","type":"uint96"},{"internalType":"uint256","name":"precisionPoints","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalFeeE4Precision","outputs":[{"internalType":"uint16","name":"totalFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_lido","type":"address"},{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"onValidatorsCountsByNodeOperatorReportingFinished","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"pauseStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_totalShares","type":"uint256[]"}],"name":"reportRewardsMinted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"resumeStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"enum StakingRouter.StakingModuleStatus","name":"_status","type":"uint8"}],"name":"setStakingModuleStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_triggerUpdateFinish","type":"bool"},{"components":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorStuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorStuckValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.ValidatorsCountsCorrection","name":"_correction","type":"tuple"}],"name":"unsafeSetExitedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"uint256","name":"_refundedValidatorsCount","type":"uint256"}],"name":"updateRefundedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"updateStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/lib/abi/ValidatorsExitBusOracle.json b/lib/abi/ValidatorsExitBusOracle.json index 40656d47b..d8dfe9d41 100644 --- a/lib/abi/ValidatorsExitBusOracle.json +++ b/lib/abi/ValidatorsExitBusOracle.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"uint256","name":"secondsPerSlot","type":"uint256"},{"internalType":"uint256","name":"genesisTime","type":"uint256"},{"internalType":"address","name":"lidoLocator","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressCannotBeSame","type":"error"},{"inputs":[],"name":"AddressCannotBeZero","type":"error"},{"inputs":[],"name":"AdminCannotBeZero","type":"error"},{"inputs":[],"name":"ArgumentOutOfBounds","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[],"name":"InvalidRequestsData","type":"error"},{"inputs":[],"name":"InvalidRequestsDataLength","type":"error"},{"inputs":[],"name":"InvalidRequestsDataSortOrder","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"OnlyConsensusContractCanSubmitReport","type":"error"},{"inputs":[],"name":"PausedExpected","type":"error"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"ProcessingDeadlineMissed","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotCannotBeLessThanProcessingOne","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"prevRefSlot","type":"uint256"}],"name":"RefSlotCannotDecrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotMustBeGreaterThanProcessingOne","type":"error"},{"inputs":[],"name":"ResumedExpected","type":"error"},{"inputs":[],"name":"SenderNotAllowed","type":"error"},{"inputs":[],"name":"UnexpectedChainConfig","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedVersion","type":"uint256"},{"internalType":"uint256","name":"receivedVersion","type":"uint256"}],"name":"UnexpectedConsensusVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"consensusRefSlot","type":"uint256"},{"internalType":"uint256","name":"dataRefSlot","type":"uint256"}],"name":"UnexpectedRefSlot","type":"error"},{"inputs":[],"name":"UnexpectedRequestsDataLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"format","type":"uint256"}],"name":"UnsupportedRequestsDataFormat","type":"error"},{"inputs":[],"name":"VersionCannotBeSame","type":"error"},{"inputs":[],"name":"ZeroPauseDuration","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"},{"indexed":true,"internalType":"address","name":"prevAddr","type":"address"}],"name":"ConsensusHashContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"version","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"prevVersion","type":"uint256"}],"name":"ConsensusVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"ProcessingStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"}],"name":"ReportSubmitted","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"nodeOperatorId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"validatorIndex","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"validatorPubkey","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"ValidatorExitRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"requestsProcessed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"requestsCount","type":"uint256"}],"name":"WarnDataIncompleteProcessing","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"}],"name":"WarnProcessingMissed","type":"event"},{"inputs":[],"name":"DATA_FORMAT_LIST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GENESIS_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_CONTRACT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_VERSION_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_INFINITELY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECONDS_PER_SLOT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SUBMIT_DATA_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusReport","outputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bool","name":"processingStarted","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastProcessingRefSlot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"moduleId","type":"uint256"},{"internalType":"uint256[]","name":"nodeOpIds","type":"uint256[]"}],"name":"getLastRequestedValidatorIndices","outputs":[{"internalType":"int256[]","name":"","type":"int256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getProcessingState","outputs":[{"components":[{"internalType":"uint256","name":"currentFrameRefSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bytes32","name":"dataHash","type":"bytes32"},{"internalType":"bool","name":"dataSubmitted","type":"bool"},{"internalType":"uint256","name":"dataFormat","type":"uint256"},{"internalType":"uint256","name":"requestsCount","type":"uint256"},{"internalType":"uint256","name":"requestsSubmitted","type":"uint256"}],"internalType":"struct ValidatorsExitBusOracle.ProcessingState","name":"result","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalRequestsProcessed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"consensusContract","type":"address"},{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"lastProcessingRefSlot","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resume","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setConsensusContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"version","type":"uint256"}],"name":"setConsensusVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"reportHash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"submitConsensusReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"requestsCount","type":"uint256"},{"internalType":"uint256","name":"dataFormat","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct ValidatorsExitBusOracle.ReportData","name":"data","type":"tuple"},{"internalType":"uint256","name":"contractVersion","type":"uint256"}],"name":"submitReportData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"uint256","name":"secondsPerSlot","type":"uint256"},{"internalType":"uint256","name":"genesisTime","type":"uint256"},{"internalType":"address","name":"lidoLocator","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressCannotBeSame","type":"error"},{"inputs":[],"name":"AddressCannotBeZero","type":"error"},{"inputs":[],"name":"AdminCannotBeZero","type":"error"},{"inputs":[],"name":"ArgumentOutOfBounds","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[],"name":"InvalidRequestsData","type":"error"},{"inputs":[],"name":"InvalidRequestsDataLength","type":"error"},{"inputs":[],"name":"InvalidRequestsDataSortOrder","type":"error"},{"inputs":[{"internalType":"uint256","name":"moduleId","type":"uint256"},{"internalType":"uint256","name":"nodeOpId","type":"uint256"},{"internalType":"uint256","name":"prevRequestedValidatorIndex","type":"uint256"},{"internalType":"uint256","name":"requestedValidatorIndex","type":"uint256"}],"name":"NodeOpValidatorIndexMustIncrease","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"OnlyConsensusContractCanSubmitReport","type":"error"},{"inputs":[],"name":"PausedExpected","type":"error"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"ProcessingDeadlineMissed","type":"error"},{"inputs":[],"name":"RefSlotAlreadyProcessing","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotCannotBeLessThanProcessingOne","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"prevRefSlot","type":"uint256"}],"name":"RefSlotCannotDecrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotMustBeGreaterThanProcessingOne","type":"error"},{"inputs":[],"name":"ResumedExpected","type":"error"},{"inputs":[],"name":"SenderNotAllowed","type":"error"},{"inputs":[],"name":"UnexpectedChainConfig","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedVersion","type":"uint256"},{"internalType":"uint256","name":"receivedVersion","type":"uint256"}],"name":"UnexpectedConsensusVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"consensusRefSlot","type":"uint256"},{"internalType":"uint256","name":"dataRefSlot","type":"uint256"}],"name":"UnexpectedRefSlot","type":"error"},{"inputs":[],"name":"UnexpectedRequestsDataLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"format","type":"uint256"}],"name":"UnsupportedRequestsDataFormat","type":"error"},{"inputs":[],"name":"VersionCannotBeSame","type":"error"},{"inputs":[],"name":"ZeroPauseDuration","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"},{"indexed":true,"internalType":"address","name":"prevAddr","type":"address"}],"name":"ConsensusHashContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"version","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"prevVersion","type":"uint256"}],"name":"ConsensusVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"ProcessingStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"}],"name":"ReportSubmitted","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"nodeOperatorId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"validatorIndex","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"validatorPubkey","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"ValidatorExitRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"requestsProcessed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"requestsCount","type":"uint256"}],"name":"WarnDataIncompleteProcessing","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"}],"name":"WarnProcessingMissed","type":"event"},{"inputs":[],"name":"DATA_FORMAT_LIST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GENESIS_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_CONTRACT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_VERSION_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_INFINITELY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECONDS_PER_SLOT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SUBMIT_DATA_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusReport","outputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bool","name":"processingStarted","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastProcessingRefSlot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"moduleId","type":"uint256"},{"internalType":"uint256[]","name":"nodeOpIds","type":"uint256[]"}],"name":"getLastRequestedValidatorIndices","outputs":[{"internalType":"int256[]","name":"","type":"int256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getProcessingState","outputs":[{"components":[{"internalType":"uint256","name":"currentFrameRefSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bytes32","name":"dataHash","type":"bytes32"},{"internalType":"bool","name":"dataSubmitted","type":"bool"},{"internalType":"uint256","name":"dataFormat","type":"uint256"},{"internalType":"uint256","name":"requestsCount","type":"uint256"},{"internalType":"uint256","name":"requestsSubmitted","type":"uint256"}],"internalType":"struct ValidatorsExitBusOracle.ProcessingState","name":"result","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getResumeSinceTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalRequestsProcessed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"pauser","type":"address"},{"internalType":"address","name":"resumer","type":"address"},{"internalType":"address","name":"consensusContract","type":"address"},{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"lastProcessingRefSlot","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resume","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setConsensusContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"version","type":"uint256"}],"name":"setConsensusVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"reportHash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"submitConsensusReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"requestsCount","type":"uint256"},{"internalType":"uint256","name":"dataFormat","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct ValidatorsExitBusOracle.ReportData","name":"data","type":"tuple"},{"internalType":"uint256","name":"contractVersion","type":"uint256"}],"name":"submitReportData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/abi/WithdrawalQueue.json b/lib/abi/WithdrawalQueue.json index e81ab351d..630bba300 100644 --- a/lib/abi/WithdrawalQueue.json +++ b/lib/abi/WithdrawalQueue.json @@ -1 +1 @@ -[{"inputs":[],"name":"AdminZeroAddress","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"CantSendValueRecipientMayHaveReverted","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"_hint","type":"uint256"}],"name":"InvalidHint","type":"error"},{"inputs":[],"name":"InvalidReportTimestamp","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"InvalidRequestId","type":"error"},{"inputs":[{"internalType":"uint256","name":"startId","type":"uint256"},{"internalType":"uint256","name":"endId","type":"uint256"}],"name":"InvalidRequestIdRange","type":"error"},{"inputs":[{"internalType":"uint256","name":"_expectedLength","type":"uint256"},{"internalType":"uint256","name":"_actualLength","type":"uint256"}],"name":"LengthsMismatch","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"NotEnoughEther","type":"error"},{"inputs":[{"internalType":"address","name":"_sender","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"name":"NotOwner","type":"error"},{"inputs":[],"name":"PausedExpected","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestAlreadyClaimed","type":"error"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"RequestAmountTooLarge","type":"error"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"RequestAmountTooSmall","type":"error"},{"inputs":[],"name":"RequestIdsNotSorted","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestNotFinalized","type":"error"},{"inputs":[],"name":"ResumedExpected","type":"error"},{"inputs":[{"internalType":"uint256","name":"sent","type":"uint256"},{"internalType":"uint256","name":"maxExpected","type":"uint256"}],"name":"TooMuchEtherToFinalize","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[],"name":"Unimplemented","type":"error"},{"inputs":[],"name":"Uninitialized","type":"error"},{"inputs":[],"name":"ZeroAmountOfETH","type":"error"},{"inputs":[],"name":"ZeroPauseDuration","type":"error"},{"inputs":[],"name":"ZeroShareRate","type":"error"},{"inputs":[],"name":"ZeroTimestamp","type":"error"},{"anonymous":false,"inputs":[],"name":"BunkerModeDisabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_sinceTimestamp","type":"uint256"}],"name":"BunkerModeEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_admin","type":"address"},{"indexed":false,"internalType":"address","name":"_pauser","type":"address"},{"indexed":false,"internalType":"address","name":"_resumer","type":"address"},{"indexed":false,"internalType":"address","name":"_finalizer","type":"address"},{"indexed":false,"internalType":"address","name":"_bunkerReporter","type":"address"}],"name":"InitializedV1","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"from","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"to","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfETHLocked","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesToBurn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"WithdrawalBatchFinalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfETH","type":"uint256"}],"name":"WithdrawalClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"requestor","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"WithdrawalRequested","type":"event"},{"inputs":[],"name":"BUNKER_MODE_DISABLED_TIMESTAMP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BUNKER_MODE_REPORT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"E27_PRECISION_BASE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FINALIZE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STETH_WITHDRAWAL_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_STETH_WITHDRAWAL_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_INFINITELY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STETH","outputs":[{"internalType":"contract IStETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WSTETH","outputs":[{"internalType":"contract IWstETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bunkerModeSinceTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"claimWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"},{"internalType":"uint256","name":"_hint","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"claimWithdrawalTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"uint256","name":"hint","type":"uint256"}],"internalType":"struct WithdrawalQueue.ClaimWithdrawalInput[]","name":"_claimWithdrawalInputs","type":"tuple[]"}],"name":"claimWithdrawals","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_nextFinalizedRequestId","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"}],"name":"finalizationBatch","outputs":[{"internalType":"uint256","name":"ethToLock","type":"uint256"},{"internalType":"uint256","name":"sharesToBurn","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_nextFinalizedRequestId","type":"uint256"}],"name":"finalize","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"},{"internalType":"uint256","name":"_start","type":"uint256"},{"internalType":"uint256","name":"_end","type":"uint256"}],"name":"findCheckpointHint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"findCheckpointHintUnbounded","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256","name":"_firstIndex","type":"uint256"},{"internalType":"uint256","name":"_lastIndex","type":"uint256"}],"name":"findCheckpointHints","outputs":[{"internalType":"uint256[]","name":"hintIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"}],"name":"findCheckpointHintsUnbounded","outputs":[{"internalType":"uint256[]","name":"hintIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethBudget","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"},{"internalType":"uint256","name":"_maxTimestamp","type":"uint256"}],"name":"findLastFinalizableRequestId","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethBudget","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"},{"internalType":"uint256","name":"_startId","type":"uint256"},{"internalType":"uint256","name":"_endId","type":"uint256"}],"name":"findLastFinalizableRequestIdByBudget","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxTimestamp","type":"uint256"},{"internalType":"uint256","name":"_startId","type":"uint256"},{"internalType":"uint256","name":"_endId","type":"uint256"}],"name":"findLastFinalizableRequestIdByTimestamp","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastCheckpointIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastFinalizedRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLockedEtherAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"getWithdrawalRequestStatus","outputs":[{"components":[{"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"internalType":"uint256","name":"amountOfShares","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bool","name":"isFinalized","type":"bool"},{"internalType":"bool","name":"isClaimed","type":"bool"}],"internalType":"struct WithdrawalQueueBase.WithdrawalRequestStatus","name":"status","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"}],"name":"getWithdrawalRequestStatuses","outputs":[{"components":[{"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"internalType":"uint256","name":"amountOfShares","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bool","name":"isFinalized","type":"bool"},{"internalType":"bool","name":"isClaimed","type":"bool"}],"internalType":"struct WithdrawalQueueBase.WithdrawalRequestStatus[]","name":"statuses","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"getWithdrawalRequests","outputs":[{"internalType":"uint256[]","name":"requestsIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_pauser","type":"address"},{"internalType":"address","name":"_resumer","type":"address"},{"internalType":"address","name":"_finalizer","type":"address"},{"internalType":"address","name":"_bunkerReporter","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isBunkerModeActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"}],"name":"requestWithdrawals","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct WithdrawalQueue.PermitInput","name":"_permit","type":"tuple"}],"name":"requestWithdrawalsWithPermit","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"}],"name":"requestWithdrawalsWstETH","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct WithdrawalQueue.PermitInput","name":"_permit","type":"tuple"}],"name":"requestWithdrawalsWstETHWithPermit","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resume","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unfinalizedRequestNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unfinalizedStETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"_isBunkerModeNow","type":"bool"},{"internalType":"uint256","name":"_sinceTimestamp","type":"uint256"}],"name":"updateBunkerMode","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[],"name":"AdminZeroAddress","type":"error"},{"inputs":[],"name":"CantSendValueRecipientMayHaveReverted","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"_hint","type":"uint256"}],"name":"InvalidHint","type":"error"},{"inputs":[],"name":"InvalidReportTimestamp","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"InvalidRequestId","type":"error"},{"inputs":[{"internalType":"uint256","name":"startId","type":"uint256"},{"internalType":"uint256","name":"endId","type":"uint256"}],"name":"InvalidRequestIdRange","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"NotEnoughEther","type":"error"},{"inputs":[{"internalType":"address","name":"_sender","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"name":"NotOwner","type":"error"},{"inputs":[],"name":"PausedExpected","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestAlreadyClaimed","type":"error"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"RequestAmountTooLarge","type":"error"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"RequestAmountTooSmall","type":"error"},{"inputs":[],"name":"RequestIdsNotSorted","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestNotFoundOrNotFinalized","type":"error"},{"inputs":[],"name":"ResumedExpected","type":"error"},{"inputs":[{"internalType":"uint256","name":"sent","type":"uint256"},{"internalType":"uint256","name":"maxExpected","type":"uint256"}],"name":"TooMuchEtherToFinalize","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[],"name":"ZeroAmountOfETH","type":"error"},{"inputs":[],"name":"ZeroPauseDuration","type":"error"},{"inputs":[],"name":"ZeroRecipient","type":"error"},{"inputs":[],"name":"ZeroShareRate","type":"error"},{"inputs":[],"name":"ZeroTimestamp","type":"error"},{"anonymous":false,"inputs":[],"name":"BunkerModeDisabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_sinceTimestamp","type":"uint256"}],"name":"BunkerModeEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_admin","type":"address"},{"indexed":false,"internalType":"address","name":"_pauser","type":"address"},{"indexed":false,"internalType":"address","name":"_resumer","type":"address"},{"indexed":false,"internalType":"address","name":"_finalizer","type":"address"},{"indexed":false,"internalType":"address","name":"_bunkerReporter","type":"address"}],"name":"InitializedV1","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"from","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"to","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfETHLocked","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesToBurn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"WithdrawalBatchFinalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfETH","type":"uint256"}],"name":"WithdrawalClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"requestor","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"WithdrawalRequested","type":"event"},{"inputs":[],"name":"BUNKER_MODE_DISABLED_TIMESTAMP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BUNKER_MODE_REPORT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"E27_PRECISION_BASE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FINALIZE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STETH_WITHDRAWAL_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_STETH_WITHDRAWAL_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_INFINITELY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STETH","outputs":[{"internalType":"contract IStETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WSTETH","outputs":[{"internalType":"contract IWstETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bunkerModeSinceTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"claimWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_hints","type":"uint256[]"}],"name":"claimWithdrawals","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_hints","type":"uint256[]"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"claimWithdrawalsTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_nextFinalizedRequestId","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"}],"name":"finalizationBatch","outputs":[{"internalType":"uint256","name":"ethToLock","type":"uint256"},{"internalType":"uint256","name":"sharesToBurn","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_nextFinalizedRequestId","type":"uint256"}],"name":"finalize","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256","name":"_firstIndex","type":"uint256"},{"internalType":"uint256","name":"_lastIndex","type":"uint256"}],"name":"findCheckpointHints","outputs":[{"internalType":"uint256[]","name":"hintIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"}],"name":"findCheckpointHintsUnbounded","outputs":[{"internalType":"uint256[]","name":"hintIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethBudget","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"},{"internalType":"uint256","name":"_maxTimestamp","type":"uint256"}],"name":"findLastFinalizableRequestId","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethBudget","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"},{"internalType":"uint256","name":"_startId","type":"uint256"},{"internalType":"uint256","name":"_endId","type":"uint256"}],"name":"findLastFinalizableRequestIdByBudget","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxTimestamp","type":"uint256"},{"internalType":"uint256","name":"_startId","type":"uint256"},{"internalType":"uint256","name":"_endId","type":"uint256"}],"name":"findLastFinalizableRequestIdByTimestamp","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_hints","type":"uint256[]"}],"name":"getClaimableEther","outputs":[{"internalType":"uint256[]","name":"claimableEthValues","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastCheckpointIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastFinalizedRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLockedEtherAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getResumeSinceTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"getWithdrawalRequests","outputs":[{"internalType":"uint256[]","name":"requestsIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"}],"name":"getWithdrawalStatus","outputs":[{"components":[{"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"internalType":"uint256","name":"amountOfShares","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bool","name":"isFinalized","type":"bool"},{"internalType":"bool","name":"isClaimed","type":"bool"}],"internalType":"struct WithdrawalQueueBase.WithdrawalRequestStatus[]","name":"statuses","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_pauser","type":"address"},{"internalType":"address","name":"_resumer","type":"address"},{"internalType":"address","name":"_finalizer","type":"address"},{"internalType":"address","name":"_bunkerReporter","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isBunkerModeActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"}],"name":"requestWithdrawals","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct WithdrawalQueue.PermitInput","name":"_permit","type":"tuple"}],"name":"requestWithdrawalsWithPermit","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"}],"name":"requestWithdrawalsWstETH","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct WithdrawalQueue.PermitInput","name":"_permit","type":"tuple"}],"name":"requestWithdrawalsWstETHWithPermit","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resume","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unfinalizedRequestNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unfinalizedStETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"_isBunkerModeNow","type":"bool"},{"internalType":"uint256","name":"_sinceTimestamp","type":"uint256"}],"name":"updateBunkerMode","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/lib/abi/WithdrawalQueueBase.json b/lib/abi/WithdrawalQueueBase.json index 1f141d586..bb0aec71d 100644 --- a/lib/abi/WithdrawalQueueBase.json +++ b/lib/abi/WithdrawalQueueBase.json @@ -1 +1 @@ -[{"inputs":[],"name":"CantSendValueRecipientMayHaveReverted","type":"error"},{"inputs":[{"internalType":"uint256","name":"_hint","type":"uint256"}],"name":"InvalidHint","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"InvalidRequestId","type":"error"},{"inputs":[{"internalType":"uint256","name":"startId","type":"uint256"},{"internalType":"uint256","name":"endId","type":"uint256"}],"name":"InvalidRequestIdRange","type":"error"},{"inputs":[],"name":"NotEnoughEther","type":"error"},{"inputs":[{"internalType":"address","name":"_sender","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"name":"NotOwner","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestAlreadyClaimed","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestNotFinalized","type":"error"},{"inputs":[{"internalType":"uint256","name":"sent","type":"uint256"},{"internalType":"uint256","name":"maxExpected","type":"uint256"}],"name":"TooMuchEtherToFinalize","type":"error"},{"inputs":[],"name":"ZeroAmountOfETH","type":"error"},{"inputs":[],"name":"ZeroShareRate","type":"error"},{"inputs":[],"name":"ZeroTimestamp","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"from","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"to","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfETHLocked","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesToBurn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"WithdrawalBatchFinalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfETH","type":"uint256"}],"name":"WithdrawalClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"requestor","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"WithdrawalRequested","type":"event"},{"inputs":[],"name":"E27_PRECISION_BASE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_nextFinalizedRequestId","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"}],"name":"finalizationBatch","outputs":[{"internalType":"uint256","name":"ethToLock","type":"uint256"},{"internalType":"uint256","name":"sharesToBurn","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"},{"internalType":"uint256","name":"_start","type":"uint256"},{"internalType":"uint256","name":"_end","type":"uint256"}],"name":"findCheckpointHint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"findCheckpointHintUnbounded","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethBudget","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"},{"internalType":"uint256","name":"_maxTimestamp","type":"uint256"}],"name":"findLastFinalizableRequestId","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethBudget","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"},{"internalType":"uint256","name":"_startId","type":"uint256"},{"internalType":"uint256","name":"_endId","type":"uint256"}],"name":"findLastFinalizableRequestIdByBudget","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxTimestamp","type":"uint256"},{"internalType":"uint256","name":"_startId","type":"uint256"},{"internalType":"uint256","name":"_endId","type":"uint256"}],"name":"findLastFinalizableRequestIdByTimestamp","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastCheckpointIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastFinalizedRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLockedEtherAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"getWithdrawalRequestStatus","outputs":[{"components":[{"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"internalType":"uint256","name":"amountOfShares","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bool","name":"isFinalized","type":"bool"},{"internalType":"bool","name":"isClaimed","type":"bool"}],"internalType":"struct WithdrawalQueueBase.WithdrawalRequestStatus","name":"status","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"getWithdrawalRequests","outputs":[{"internalType":"uint256[]","name":"requestsIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unfinalizedRequestNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unfinalizedStETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[],"name":"CantSendValueRecipientMayHaveReverted","type":"error"},{"inputs":[{"internalType":"uint256","name":"_hint","type":"uint256"}],"name":"InvalidHint","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"InvalidRequestId","type":"error"},{"inputs":[{"internalType":"uint256","name":"startId","type":"uint256"},{"internalType":"uint256","name":"endId","type":"uint256"}],"name":"InvalidRequestIdRange","type":"error"},{"inputs":[],"name":"NotEnoughEther","type":"error"},{"inputs":[{"internalType":"address","name":"_sender","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"name":"NotOwner","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestAlreadyClaimed","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestNotFoundOrNotFinalized","type":"error"},{"inputs":[{"internalType":"uint256","name":"sent","type":"uint256"},{"internalType":"uint256","name":"maxExpected","type":"uint256"}],"name":"TooMuchEtherToFinalize","type":"error"},{"inputs":[],"name":"ZeroAmountOfETH","type":"error"},{"inputs":[],"name":"ZeroShareRate","type":"error"},{"inputs":[],"name":"ZeroTimestamp","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"from","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"to","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfETHLocked","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesToBurn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"WithdrawalBatchFinalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfETH","type":"uint256"}],"name":"WithdrawalClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"requestor","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"WithdrawalRequested","type":"event"},{"inputs":[],"name":"E27_PRECISION_BASE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_nextFinalizedRequestId","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"}],"name":"finalizationBatch","outputs":[{"internalType":"uint256","name":"ethToLock","type":"uint256"},{"internalType":"uint256","name":"sharesToBurn","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethBudget","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"},{"internalType":"uint256","name":"_maxTimestamp","type":"uint256"}],"name":"findLastFinalizableRequestId","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethBudget","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"},{"internalType":"uint256","name":"_startId","type":"uint256"},{"internalType":"uint256","name":"_endId","type":"uint256"}],"name":"findLastFinalizableRequestIdByBudget","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxTimestamp","type":"uint256"},{"internalType":"uint256","name":"_startId","type":"uint256"},{"internalType":"uint256","name":"_endId","type":"uint256"}],"name":"findLastFinalizableRequestIdByTimestamp","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastCheckpointIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastFinalizedRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLockedEtherAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unfinalizedRequestNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unfinalizedStETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/abi/WithdrawalQueueERC721.json b/lib/abi/WithdrawalQueueERC721.json new file mode 100644 index 000000000..728ac3391 --- /dev/null +++ b/lib/abi/WithdrawalQueueERC721.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"_wstETH","type":"address"},{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AdminZeroAddress","type":"error"},{"inputs":[],"name":"ApprovalToOwner","type":"error"},{"inputs":[],"name":"ApproveToCaller","type":"error"},{"inputs":[],"name":"CantSendValueRecipientMayHaveReverted","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"_hint","type":"uint256"}],"name":"InvalidHint","type":"error"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"InvalidOwnerAddress","type":"error"},{"inputs":[],"name":"InvalidReportTimestamp","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"InvalidRequestId","type":"error"},{"inputs":[{"internalType":"uint256","name":"startId","type":"uint256"},{"internalType":"uint256","name":"endId","type":"uint256"}],"name":"InvalidRequestIdRange","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"NotEnoughEther","type":"error"},{"inputs":[{"internalType":"address","name":"_sender","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"name":"NotOwner","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"NotOwnerOrApproved","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"NotOwnerOrApprovedForAll","type":"error"},{"inputs":[],"name":"PausedExpected","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestAlreadyClaimed","type":"error"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"RequestAmountTooLarge","type":"error"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"RequestAmountTooSmall","type":"error"},{"inputs":[],"name":"RequestIdsNotSorted","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestNotFoundOrNotFinalized","type":"error"},{"inputs":[],"name":"ResumedExpected","type":"error"},{"inputs":[{"internalType":"string","name":"str","type":"string"}],"name":"StringTooLong","type":"error"},{"inputs":[{"internalType":"uint256","name":"sent","type":"uint256"},{"internalType":"uint256","name":"maxExpected","type":"uint256"}],"name":"TooMuchEtherToFinalize","type":"error"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"realOwner","type":"address"}],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferFromZeroAddress","type":"error"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"TransferToNonIERC721Receiver","type":"error"},{"inputs":[],"name":"TransferToThemselves","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[],"name":"ZeroAmountOfETH","type":"error"},{"inputs":[],"name":"ZeroMetadata","type":"error"},{"inputs":[],"name":"ZeroPauseDuration","type":"error"},{"inputs":[],"name":"ZeroRecipient","type":"error"},{"inputs":[],"name":"ZeroShareRate","type":"error"},{"inputs":[],"name":"ZeroTimestamp","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseURI","type":"string"}],"name":"BaseURISet","type":"event"},{"anonymous":false,"inputs":[],"name":"BunkerModeDisabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_sinceTimestamp","type":"uint256"}],"name":"BunkerModeEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_admin","type":"address"},{"indexed":false,"internalType":"address","name":"_pauser","type":"address"},{"indexed":false,"internalType":"address","name":"_resumer","type":"address"},{"indexed":false,"internalType":"address","name":"_finalizer","type":"address"},{"indexed":false,"internalType":"address","name":"_bunkerReporter","type":"address"}],"name":"InitializedV1","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"nftDescriptorAddress","type":"address"}],"name":"NftDescriptorAddressSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"from","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"to","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfETHLocked","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesToBurn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"WithdrawalBatchFinalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfETH","type":"uint256"}],"name":"WithdrawalClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"requestor","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"WithdrawalRequested","type":"event"},{"inputs":[],"name":"BUNKER_MODE_DISABLED_TIMESTAMP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BUNKER_MODE_REPORT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"E27_PRECISION_BASE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FINALIZE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_TOKEN_URI_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STETH_WITHDRAWAL_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_STETH_WITHDRAWAL_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_INFINITELY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STETH","outputs":[{"internalType":"contract IStETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WSTETH","outputs":[{"internalType":"contract IWstETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bunkerModeSinceTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"claimWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_hints","type":"uint256[]"}],"name":"claimWithdrawals","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_hints","type":"uint256[]"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"claimWithdrawalsTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_nextFinalizedRequestId","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"}],"name":"finalizationBatch","outputs":[{"internalType":"uint256","name":"ethToLock","type":"uint256"},{"internalType":"uint256","name":"sharesToBurn","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_nextFinalizedRequestId","type":"uint256"}],"name":"finalize","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256","name":"_firstIndex","type":"uint256"},{"internalType":"uint256","name":"_lastIndex","type":"uint256"}],"name":"findCheckpointHints","outputs":[{"internalType":"uint256[]","name":"hintIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"}],"name":"findCheckpointHintsUnbounded","outputs":[{"internalType":"uint256[]","name":"hintIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethBudget","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"},{"internalType":"uint256","name":"_maxTimestamp","type":"uint256"}],"name":"findLastFinalizableRequestId","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethBudget","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"},{"internalType":"uint256","name":"_startId","type":"uint256"},{"internalType":"uint256","name":"_endId","type":"uint256"}],"name":"findLastFinalizableRequestIdByBudget","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxTimestamp","type":"uint256"},{"internalType":"uint256","name":"_startId","type":"uint256"},{"internalType":"uint256","name":"_endId","type":"uint256"}],"name":"findLastFinalizableRequestIdByTimestamp","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBaseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_hints","type":"uint256[]"}],"name":"getClaimableEther","outputs":[{"internalType":"uint256[]","name":"claimableEthValues","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastCheckpointIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastFinalizedRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLockedEtherAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNFTDescriptorAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getResumeSinceTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"getWithdrawalRequests","outputs":[{"internalType":"uint256[]","name":"requestsIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"}],"name":"getWithdrawalStatus","outputs":[{"components":[{"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"internalType":"uint256","name":"amountOfShares","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bool","name":"isFinalized","type":"bool"},{"internalType":"bool","name":"isClaimed","type":"bool"}],"internalType":"struct WithdrawalQueueBase.WithdrawalRequestStatus[]","name":"statuses","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_pauser","type":"address"},{"internalType":"address","name":"_resumer","type":"address"},{"internalType":"address","name":"_finalizer","type":"address"},{"internalType":"address","name":"_bunkerReporter","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isBunkerModeActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"}],"name":"requestWithdrawals","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct WithdrawalQueue.PermitInput","name":"_permit","type":"tuple"}],"name":"requestWithdrawalsWithPermit","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"}],"name":"requestWithdrawalsWstETH","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct WithdrawalQueue.PermitInput","name":"_permit","type":"tuple"}],"name":"requestWithdrawalsWstETHWithPermit","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resume","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"},{"internalType":"bool","name":"_approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_baseURI","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_nftDescriptorAddress","type":"address"}],"name":"setNFTDescriptorAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unfinalizedRequestNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unfinalizedStETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"_isBunkerModeNow","type":"bool"},{"internalType":"uint256","name":"_sinceTimestamp","type":"uint256"}],"name":"updateBunkerMode","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/lib/abi/WithdrawalRequestNFT.json b/lib/abi/WithdrawalRequestNFT.json deleted file mode 100644 index 0ccb54190..000000000 --- a/lib/abi/WithdrawalRequestNFT.json +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"address","name":"_wstETH","type":"address"},{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AdminZeroAddress","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"ApprovalToOwner","type":"error"},{"inputs":[],"name":"ApproveToCaller","type":"error"},{"inputs":[],"name":"CantSendValueRecipientMayHaveReverted","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"_hint","type":"uint256"}],"name":"InvalidHint","type":"error"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"InvalidOwnerAddress","type":"error"},{"inputs":[],"name":"InvalidReportTimestamp","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"InvalidRequestId","type":"error"},{"inputs":[{"internalType":"uint256","name":"startId","type":"uint256"},{"internalType":"uint256","name":"endId","type":"uint256"}],"name":"InvalidRequestIdRange","type":"error"},{"inputs":[{"internalType":"uint256","name":"_expectedLength","type":"uint256"},{"internalType":"uint256","name":"_actualLength","type":"uint256"}],"name":"LengthsMismatch","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"NotEnoughEther","type":"error"},{"inputs":[{"internalType":"address","name":"_sender","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"name":"NotOwner","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"NotOwnerOrApproved","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"NotOwnerOrApprovedForAll","type":"error"},{"inputs":[],"name":"PausedExpected","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestAlreadyClaimed","type":"error"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"RequestAmountTooLarge","type":"error"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"RequestAmountTooSmall","type":"error"},{"inputs":[],"name":"RequestIdsNotSorted","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestNotFinalized","type":"error"},{"inputs":[],"name":"ResumedExpected","type":"error"},{"inputs":[{"internalType":"string","name":"str","type":"string"}],"name":"StringTooLong","type":"error"},{"inputs":[{"internalType":"uint256","name":"sent","type":"uint256"},{"internalType":"uint256","name":"maxExpected","type":"uint256"}],"name":"TooMuchEtherToFinalize","type":"error"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"realOwner","type":"address"}],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferFromZeroAddress","type":"error"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"TransferToNonIERC721Receiver","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[],"name":"Unimplemented","type":"error"},{"inputs":[],"name":"Uninitialized","type":"error"},{"inputs":[],"name":"ZeroAmountOfETH","type":"error"},{"inputs":[],"name":"ZeroMetadata","type":"error"},{"inputs":[],"name":"ZeroPauseDuration","type":"error"},{"inputs":[],"name":"ZeroShareRate","type":"error"},{"inputs":[],"name":"ZeroTimestamp","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseURI","type":"string"}],"name":"BaseURISet","type":"event"},{"anonymous":false,"inputs":[],"name":"BunkerModeDisabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_sinceTimestamp","type":"uint256"}],"name":"BunkerModeEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_admin","type":"address"},{"indexed":false,"internalType":"address","name":"_pauser","type":"address"},{"indexed":false,"internalType":"address","name":"_resumer","type":"address"},{"indexed":false,"internalType":"address","name":"_finalizer","type":"address"},{"indexed":false,"internalType":"address","name":"_bunkerReporter","type":"address"}],"name":"InitializedV1","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"from","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"to","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfETHLocked","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesToBurn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"WithdrawalBatchFinalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfETH","type":"uint256"}],"name":"WithdrawalClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"requestor","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"WithdrawalRequested","type":"event"},{"inputs":[],"name":"BUNKER_MODE_DISABLED_TIMESTAMP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BUNKER_MODE_REPORT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"E27_PRECISION_BASE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FINALIZE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STETH_WITHDRAWAL_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_STETH_WITHDRAWAL_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_INFINITELY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_BASE_URI_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STETH","outputs":[{"internalType":"contract IStETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WSTETH","outputs":[{"internalType":"contract IWstETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bunkerModeSinceTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"claimWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"},{"internalType":"uint256","name":"_hint","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"claimWithdrawalTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"uint256","name":"hint","type":"uint256"}],"internalType":"struct WithdrawalQueue.ClaimWithdrawalInput[]","name":"_claimWithdrawalInputs","type":"tuple[]"}],"name":"claimWithdrawals","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_nextFinalizedRequestId","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"}],"name":"finalizationBatch","outputs":[{"internalType":"uint256","name":"ethToLock","type":"uint256"},{"internalType":"uint256","name":"sharesToBurn","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_nextFinalizedRequestId","type":"uint256"}],"name":"finalize","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"},{"internalType":"uint256","name":"_start","type":"uint256"},{"internalType":"uint256","name":"_end","type":"uint256"}],"name":"findCheckpointHint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"findCheckpointHintUnbounded","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256","name":"_firstIndex","type":"uint256"},{"internalType":"uint256","name":"_lastIndex","type":"uint256"}],"name":"findCheckpointHints","outputs":[{"internalType":"uint256[]","name":"hintIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"}],"name":"findCheckpointHintsUnbounded","outputs":[{"internalType":"uint256[]","name":"hintIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethBudget","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"},{"internalType":"uint256","name":"_maxTimestamp","type":"uint256"}],"name":"findLastFinalizableRequestId","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethBudget","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"},{"internalType":"uint256","name":"_startId","type":"uint256"},{"internalType":"uint256","name":"_endId","type":"uint256"}],"name":"findLastFinalizableRequestIdByBudget","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxTimestamp","type":"uint256"},{"internalType":"uint256","name":"_startId","type":"uint256"},{"internalType":"uint256","name":"_endId","type":"uint256"}],"name":"findLastFinalizableRequestIdByTimestamp","outputs":[{"internalType":"uint256","name":"finalizableRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBaseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastCheckpointIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastFinalizedRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLockedEtherAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"getWithdrawalRequestStatus","outputs":[{"components":[{"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"internalType":"uint256","name":"amountOfShares","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bool","name":"isFinalized","type":"bool"},{"internalType":"bool","name":"isClaimed","type":"bool"}],"internalType":"struct WithdrawalQueueBase.WithdrawalRequestStatus","name":"status","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"}],"name":"getWithdrawalRequestStatuses","outputs":[{"components":[{"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"internalType":"uint256","name":"amountOfShares","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bool","name":"isFinalized","type":"bool"},{"internalType":"bool","name":"isClaimed","type":"bool"}],"internalType":"struct WithdrawalQueueBase.WithdrawalRequestStatus[]","name":"statuses","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"getWithdrawalRequests","outputs":[{"internalType":"uint256[]","name":"requestsIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_pauser","type":"address"},{"internalType":"address","name":"_resumer","type":"address"},{"internalType":"address","name":"_finalizer","type":"address"},{"internalType":"address","name":"_bunkerReporter","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isBunkerModeActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"}],"name":"requestWithdrawals","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct WithdrawalQueue.PermitInput","name":"_permit","type":"tuple"}],"name":"requestWithdrawalsWithPermit","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"}],"name":"requestWithdrawalsWstETH","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct WithdrawalQueue.PermitInput","name":"_permit","type":"tuple"}],"name":"requestWithdrawalsWstETHWithPermit","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resume","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"},{"internalType":"bool","name":"_approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_baseURI","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unfinalizedRequestNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unfinalizedStETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"_isBunkerModeNow","type":"bool"},{"internalType":"uint256","name":"_sinceTimestamp","type":"uint256"}],"name":"updateBunkerMode","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/package.json b/package.json index 8c2553d6e..6a3ea0df0 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,12 @@ "lint:js": "yarn lint:js:cmd .", "lint:js:fix": "yarn lint:js:cmd --fix .", "lint:js:cmd": "eslint --ext .js --cache --ignore-path .gitignore --ignore-pattern 'apps/*/app/' --ignore-pattern /gasprofile/ --ignore-pattern /scripts/", + "test:forge": "forge test", "test": "yarn run test:unit", "test-sequential": "yarn run test:unit-sequential", "test:unit": "hardhat test --parallel --network hardhat", "test:unit-sequential": "hardhat test --network hardhat", - "test:gas": "REPORT_GAS=true hardhat test --network localhost", + "test:gas": "REPORT_GAS=true hardhat test --network hardhat", "test:coverage": "hardhat coverage --testfiles test", "test:e2e": "npm run compile && ava -T 1000000 -v", "estimate-deposit-loop-gas": "yarn run test:unit ./estimate_deposit_loop_gas.js", @@ -100,9 +101,9 @@ "eslint-plugin-standard": "^4.0.1", "eth-ens-namehash": "^2.0.8", "ethereumjs-testrpc-sc": "^6.5.1-sc.1", - "hardhat": "2.9.9", + "hardhat": "2.12.7", "hardhat-contract-sizer": "^2.5.0", - "hardhat-gas-reporter": "1.0.8", + "hardhat-gas-reporter": "^1.0.8", "hardhat-ignore-warnings": "skozin/hardhat-ignore-warnings#0ecf2ae85bddb83594193ee5c463cb4ae54cde7d", "husky": "^8.0.2", "ipfs-http-client": "^55.0.0", @@ -112,7 +113,7 @@ "prettier-plugin-solidity": "^1.1.0", "solhint": "^3.3.7", "solhint-plugin-lido": "^0.0.4", - "solidity-coverage": "^0.7.22", + "solidity-coverage": "^0.8.2", "truffle": "^5.1.43", "truffle-extract": "^1.2.1", "truffle-flattener": "^1.5.0", @@ -128,6 +129,7 @@ "concurrently": "^6.4.0", "ethereumjs-util": "^7.0.8", "ethers": "^5.1.4", + "minimatch": "^6.2.0", "node-gyp": "^8.4.1", "openzeppelin-solidity": "2.0.0", "solhint-plugin-lido": "^0.0.4", diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 000000000..216904bb6 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,2 @@ +ds-test/=foundry/lib/forge-std/lib/ds-test/src/ +forge-std/=foundry/lib/forge-std/src/ \ No newline at end of file diff --git a/scripts/extract-abi.js b/scripts/extract-abi.js index 8259f49a9..276f6f5f1 100644 --- a/scripts/extract-abi.js +++ b/scripts/extract-abi.js @@ -29,7 +29,7 @@ async function extractABIs(artifactPaths, abisPath) { for (const file of files) { await fs.unlink(path.join(abisPath, file)) } - await fs.rmdir(abisPath, { recursive: true }) + await fs.rm(abisPath, { recursive: true }) } await fs.mkdir(abisPath, { recursive: true }) diff --git a/test/0.4.24/helpers/constants.js b/test/0.4.24/helpers/constants.js index 2e92dbe51..4d33269a4 100644 --- a/test/0.4.24/helpers/constants.js +++ b/test/0.4.24/helpers/constants.js @@ -16,5 +16,5 @@ const ACCOUNTS_AND_KEYS = [ module.exports = { ZERO_ADDRESS, MAX_UINT256, - ACCOUNTS_AND_KEYS + ACCOUNTS_AND_KEYS, } diff --git a/test/0.4.24/helpers/dao.js b/test/0.4.24/helpers/dao.js index 74da43a97..27e3f0c58 100644 --- a/test/0.4.24/helpers/dao.js +++ b/test/0.4.24/helpers/dao.js @@ -81,19 +81,19 @@ class AragonDAO { async createPermission(entityAddress, app, permissionName) { const permission = await app[permissionName]() - return this.acl.createPermission(entityAddress, app.address, permission, this.appManager, { + return await this.acl.createPermission(entityAddress, app.address, permission, this.appManager, { from: this.appManager }) } async grantPermission(entityAddress, app, permissionName) { const permission = await app[permissionName]() - return this.acl.grantPermission(entityAddress, app.address, permission, { from: this.appManager }) + return await this.acl.grantPermission(entityAddress, app.address, permission, { from: this.appManager }) } async hasPermission(entity, app, permissionName) { const permission = await app[permissionName]() - return this.acl.hasPermission(entity, app.address, permission) + return await this.acl.hasPermission(entity, app.address, permission) } } diff --git a/test/0.4.24/lido-deposit-scenarios.test.js b/test/0.4.24/lido-deposit-scenarios.test.js new file mode 100644 index 000000000..9950d6f0b --- /dev/null +++ b/test/0.4.24/lido-deposit-scenarios.test.js @@ -0,0 +1,247 @@ +const hre = require('hardhat') +const { deployProtocol } = require('../helpers/protocol') +const { EvmSnapshot, setBalance, getBalance } = require('../helpers/blockchain') +const { ZERO_ADDRESS } = require('@aragon/contract-helpers-test') +const { assert } = require('../helpers/assert') +const { wei } = require('../helpers/wei') +const { StakingModuleStub } = require('../helpers/stubs/staking-module.stub') +const { PUBKEY_LENGTH, FakeValidatorKeys, SIGNATURE_LENGTH } = require('../helpers/signing-keys') +const { GenericStub } = require('../helpers/stubs/generic.stub') + +hre.contract('Lido deposit scenarios', ([staker, depositor]) => { + const CURATED_MODULE_ID = 1 + const DEPOSIT_CALLDATA = '0x0' + let lido, stakingRouter + let stakingModuleStub, depositContractStub + let snapshot + + before('prepare base Lido & StakingRouter setup', async () => { + stakingModuleStub = await StakingModuleStub.new() + depositContractStub = await GenericStub.new('contracts/0.6.11/deposit_contract.sol:IDepositContract') + // just accept all ether and do nothing + await GenericStub.stub(depositContractStub, 'deposit') + const protocol = await deployProtocol({ + stakingModulesFactory: async () => { + return [ + { + module: stakingModuleStub, + name: 'stubbed staking module', + targetShares: 100_00, + moduleFee: 5_00, + treasuryFee: 5_00 + } + ] + }, + depositSecurityModuleFactory: async () => ({ address: depositor }), + depositContractFactory: () => depositContractStub, + postSetup: async ({ pool, lidoLocator, eip712StETH, voting }) => { + await pool.initialize(lidoLocator.address, eip712StETH.address, { value: wei.str`1 ether` }) + await pool.resumeProtocolAndStaking({ from: voting.address }) + } + }) + lido = protocol.pool + stakingRouter = protocol.stakingRouter + snapshot = new EvmSnapshot(hre.ethers.provider) + await snapshot.make() + }) + + afterEach(() => snapshot.rollback()) + + it('StakingRouter has non zero ETH balance & lido has unaccounted ether', async () => { + // add extra ETH value to the StakingRouter + const initialStakingRouterBalance = wei`1 ether` + await setBalance(stakingRouter, initialStakingRouterBalance) + assert.equal(await getBalance(stakingRouter), initialStakingRouterBalance) + + // add unaccounted ETH to Lido + const unaccountedLidoETHBalance = wei`1 gwei` + const initialLidoETHBalance = await getBalance(lido) + await setBalance(lido, initialLidoETHBalance + unaccountedLidoETHBalance) + assert.equal(await getBalance(lido), initialLidoETHBalance + unaccountedLidoETHBalance) + + const availableValidatorsCount = 2 + await StakingModuleStub.stubGetStakingModuleSummary(stakingModuleStub, { + totalExitedValidators: 5, + totalDepositedValidators: 16, + availableValidatorsCount + }) + + const depositDataLength = availableValidatorsCount + await StakingModuleStub.stubObtainDepositData(stakingModuleStub, { + return: { depositDataLength } + }) + + const submitAmount = wei`320 ether` + await lido.submit(ZERO_ADDRESS, { from: staker, value: wei.str(submitAmount) }) + + assert.equal(await getBalance(lido), initialLidoETHBalance + unaccountedLidoETHBalance + submitAmount) + + const maxDepositsCount = 10 + await lido.deposit(maxDepositsCount, CURATED_MODULE_ID, DEPOSIT_CALLDATA, { from: depositor }) + + assert.equals(await getBalance(stakingRouter), initialStakingRouterBalance) + const depositedEther = wei`32 ether` * wei.min(maxDepositsCount, availableValidatorsCount) + assert.equals( + await getBalance(lido), + initialLidoETHBalance + unaccountedLidoETHBalance + submitAmount - depositedEther + ) + }) + + describe('StakingModule returns invalid data', () => { + it('obtainDepositData() returns more publicKeys and signatures than expected', async () => { + const initialStakingRouterBalance = wei`1 ether` + await setBalance(stakingRouter, initialStakingRouterBalance) + assert.equals(await getBalance(stakingRouter), initialStakingRouterBalance) + + const availableValidatorsCount = 2 + await StakingModuleStub.stubGetStakingModuleSummary(stakingModuleStub, { + totalExitedValidators: 5, + totalDepositedValidators: 16, + availableValidatorsCount + }) + + const depositDataLength = availableValidatorsCount + 2 + await StakingModuleStub.stubObtainDepositData(stakingModuleStub, { + return: { depositDataLength } + }) + + const initialLidETHBalance = await getBalance(lido) + + const submitAmount = wei`320 ether` + await lido.submit(ZERO_ADDRESS, { from: staker, value: wei.str(submitAmount) }) + + assert.equals(await getBalance(lido), initialLidETHBalance + submitAmount) + + const maxDepositsCount = 10 + await assert.reverts( + lido.deposit(maxDepositsCount, CURATED_MODULE_ID, DEPOSIT_CALLDATA, { from: depositor }), + 'InvalidPublicKeysBatchLength', + [PUBKEY_LENGTH * depositDataLength, PUBKEY_LENGTH * availableValidatorsCount] + ) + }) + + it('obtainDepositData() returns more publicKeys than expected', async () => { + const initialStakingRouterBalance = wei`1 ether` + await setBalance(stakingRouter, initialStakingRouterBalance) + assert.equals(await getBalance(stakingRouter), initialStakingRouterBalance) + + const availableValidatorsCount = 2 + await StakingModuleStub.stubGetStakingModuleSummary(stakingModuleStub, { + totalExitedValidators: 5, + totalDepositedValidators: 16, + availableValidatorsCount + }) + + const depositDataLength = availableValidatorsCount + 2 + const depositData = new FakeValidatorKeys(depositDataLength) + await StakingModuleStub.stubObtainDepositData(stakingModuleStub, { + return: { + publicKeysBatch: depositData.slice()[0], // two extra signatures returned + signaturesBatch: depositData.slice(0, availableValidatorsCount)[1] + } + }) + + const initialLidETHBalance = await getBalance(lido) + + const submitAmount = wei`320 ether` + await lido.submit(ZERO_ADDRESS, { from: staker, value: wei.str(submitAmount) }) + + assert.equals(await getBalance(lido), initialLidETHBalance + submitAmount) + + const maxDepositsCount = 10 + await assert.reverts( + lido.deposit(maxDepositsCount, CURATED_MODULE_ID, DEPOSIT_CALLDATA, { from: depositor }), + 'InvalidPublicKeysBatchLength', + [PUBKEY_LENGTH * depositDataLength, PUBKEY_LENGTH * availableValidatorsCount] + ) + }) + + it('obtainDepositData() returns more signatures than expected', async () => { + const initialStakingRouterBalance = wei`1 ether` + await setBalance(stakingRouter, initialStakingRouterBalance) + assert.equals(await getBalance(stakingRouter), initialStakingRouterBalance) + + const availableValidatorsCount = 2 + await StakingModuleStub.stubGetStakingModuleSummary(stakingModuleStub, { + totalExitedValidators: 5, + totalDepositedValidators: 16, + availableValidatorsCount + }) + + const depositDataLength = availableValidatorsCount + 2 + const depositData = new FakeValidatorKeys(depositDataLength) + await StakingModuleStub.stubObtainDepositData(stakingModuleStub, { + return: { + publicKeysBatch: depositData.slice(0, availableValidatorsCount)[0], + signaturesBatch: depositData.slice()[1] // two extra signatures returned + } + }) + + const initialLidETHBalance = await getBalance(lido) + + const submitAmount = wei`320 ether` + await lido.submit(ZERO_ADDRESS, { from: staker, value: wei.str(submitAmount) }) + + assert.equals(await getBalance(lido), initialLidETHBalance + submitAmount) + + const maxDepositsCount = 10 + await assert.reverts( + lido.deposit(maxDepositsCount, CURATED_MODULE_ID, DEPOSIT_CALLDATA, { from: depositor }), + 'InvalidSignaturesBatchLength', + [SIGNATURE_LENGTH * depositDataLength, SIGNATURE_LENGTH * availableValidatorsCount] + ) + }) + + it('invalid ETH value was used for deposits in StakingRouter', async () => { + // on each deposit call forward back 1 ether to the staking router + await GenericStub.stub(depositContractStub, 'deposit', { + forwardETH: { value: wei.str`1 ether`, recipient: stakingRouter.address } + }) + + const submitAmount = wei`320 ether` + const initialLidoETHBalance = await getBalance(lido) + await lido.submit(ZERO_ADDRESS, { from: staker, value: wei.str(submitAmount) }) + + assert.equal(await getBalance(lido), initialLidoETHBalance + submitAmount) + + const availableValidatorsCount = 2 + await StakingModuleStub.stubGetStakingModuleSummary(stakingModuleStub, { + totalExitedValidators: 5, + totalDepositedValidators: 16, + availableValidatorsCount + }) + + const depositDataLength = availableValidatorsCount + await StakingModuleStub.stubObtainDepositData(stakingModuleStub, { + return: { depositDataLength } + }) + const maxDepositsCount = 10 + await assert.reverts(lido.deposit(maxDepositsCount, CURATED_MODULE_ID, DEPOSIT_CALLDATA, { from: depositor })) + }) + + it('StakingModule reverted on obtainData', async () => { + const submitAmount = wei`320 ether` + const initialLidoETHBalance = await getBalance(lido) + await lido.submit(ZERO_ADDRESS, { from: staker, value: wei.str(submitAmount) }) + + assert.equal(await getBalance(lido), initialLidoETHBalance + submitAmount) + + const availableValidatorsCount = 2 + await StakingModuleStub.stubGetStakingModuleSummary(stakingModuleStub, { + totalExitedValidators: 5, + totalDepositedValidators: 16, + availableValidatorsCount + }) + + await StakingModuleStub.stub(stakingModuleStub, 'obtainDepositData', { + revert: { reason: 'INVALID_ALLOCATED_KEYS_COUNT' } + }) + + const maxDepositsCount = 10 + await assert.reverts( + lido.deposit(maxDepositsCount, CURATED_MODULE_ID, DEPOSIT_CALLDATA, { from: depositor }), + 'INVALID_ALLOCATED_KEYS_COUNT' + ) + }) + }) +}) diff --git a/test/0.4.24/lido-handle-oracle-report.test.js b/test/0.4.24/lido-handle-oracle-report.test.js index a94fb0913..602e4341a 100644 --- a/test/0.4.24/lido-handle-oracle-report.test.js +++ b/test/0.4.24/lido-handle-oracle-report.test.js @@ -1,13 +1,87 @@ const hre = require('hardhat') const { assert } = require('../helpers/assert') -const { ETH, toBN, genKeys, setBalance } = require('../helpers/utils') +const { ETH, toBN, genKeys, setBalance, StETH, calcSharesMintedAsFees } = require('../helpers/utils') const { deployProtocol } = require('../helpers/protocol') const { EvmSnapshot } = require('../helpers/blockchain') -const { ZERO_ADDRESS } = require('../helpers/constants') +const { ZERO_ADDRESS, INITIAL_HOLDER } = require('../helpers/constants') const { setupNodeOperatorsRegistry } = require('../helpers/staking-modules') +const Lido = artifacts.require('Lido') const ONE_YEAR = 3600 * 24 * 365 const ONE_DAY = 3600 * 24 +const ORACLE_REPORT_LIMITS_BOILERPLATE = { + churnValidatorsPerDayLimit: 255, + oneOffCLBalanceDecreaseBPLimit: 100, + annualBalanceIncreaseBPLimit: 10000, + simulatedShareRateDeviationBPLimit: 10000, + maxValidatorExitRequestsPerReport: 10000, + maxAccountingExtraDataListItemsCount: 10000, + maxNodeOperatorsPerExtraDataItemCount: 10000, + requestTimestampMargin: 0, + maxPositiveTokenRebase: 1000000000, +} + +const checkEvents = async ({ + tx, + reportTimestamp = 0, + preCLValidators, + postCLValidators, + preCLBalance, + postCLBalance, + withdrawalsWithdrawn, + executionLayerRewardsWithdrawn, + postBufferedEther, + timeElapsed, + preTotalShares, + preTotalEther, + postTotalShares, + postTotalEther, + sharesMintedAsFees +}) => { + assert.emits( + tx, + 'CLValidatorsUpdated', + { + reportTimestamp: 0, + preCLValidators, + postCLValidators + }, + { + abi: Lido.abi + } + ) + assert.emits( + tx, + 'ETHDistributed', + { + reportTimestamp, + preCLBalance, + postCLBalance, + withdrawalsWithdrawn, + executionLayerRewardsWithdrawn, + postBufferedEther + }, + { + abi: Lido.abi + } + ) + assert.emits( + tx, + 'TokenRebased', + { + reportTimestamp, + timeElapsed, + preTotalShares, + preTotalEther, + postTotalShares, + postTotalEther, + sharesMintedAsFees + }, + { + abi: Lido.abi + } + ) +} contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, anotherStranger, depositor, operator]) => { let deployed, snapshot, lido, treasury, voting, oracle @@ -17,7 +91,8 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another anotherStrangerBalanceBefore, totalPooledEtherBefore, curatedModuleBalanceBefore, - treasuryBalanceBefore + treasuryBalanceBefore, + initialHolderBalanceBefore before('deploy base app', async () => { deployed = await deployProtocol({ @@ -55,8 +130,9 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another withdrawalVault = deployed.withdrawalVault.address elRewardsVault = deployed.elRewardsVault.address + assert.equals(await lido.balanceOf(INITIAL_HOLDER), StETH(1)) await lido.submit(ZERO_ADDRESS, { from: stranger, value: ETH(30) }) - await lido.submit(ZERO_ADDRESS, { from: anotherStranger, value: ETH(70) }) + await lido.submit(ZERO_ADDRESS, { from: anotherStranger, value: ETH(69) }) await checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: 0 }) @@ -85,6 +161,7 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another anotherStrangerBalanceBefore = await lido.balanceOf(anotherStranger) treasuryBalanceBefore = await lido.balanceOf(treasury) curatedModuleBalanceBefore = await lido.balanceOf(curatedModule.address) + initialHolderBalanceBefore = await lido.balanceOf(INITIAL_HOLDER) } const checkBalanceDeltas = async ({ @@ -92,7 +169,8 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another treasuryBalanceDiff, strangerBalanceDiff, anotherStrangerBalanceDiff, - curatedModuleBalanceDiff + curatedModuleBalanceDiff, + initialHolderBalanceDiff }) => { assert.equals( await lido.getTotalPooledEther(), @@ -123,6 +201,12 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another 1, 'another stranger balance check' ) + assert.equalsDelta( + await lido.balanceOf(INITIAL_HOLDER), + toBN(initialHolderBalanceBefore).add(toBN(initialHolderBalanceDiff)), + 1, + 'another stranger balance check' + ) } it('handleOracleReport access control', async () => { @@ -135,14 +219,32 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another }) it('zero report should do nothing', async () => { - await lido.handleOracleReport(0, 0, 0, 0, 0, 0, 0, 0, { from: oracle }) + const tx = await lido.handleOracleReport(0, 0, 0, 0, 0, 0, 0, 0, { from: oracle }) + await checkEvents({ + tx, + preCLValidators: 0, + postCLValidators: 0, + preCLBalance: ETH(0), + postCLBalance: ETH(0), + withdrawalsWithdrawn: 0, + executionLayerRewardsWithdrawn: 0, + postBufferedEther: ETH(100), + timeElapsed: 0, + preTotalShares: ETH(100), + preTotalEther: ETH(100), + postTotalShares: ETH(100), + postTotalEther: ETH(100), + sharesMintedAsFees: 0 + }) + await checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: 0 }) await checkBalanceDeltas({ totalPooledEtherDiff: 0, treasuryBalanceDiff: 0, strangerBalanceDiff: 0, anotherStrangerBalanceDiff: 0, - curatedModuleBalanceDiff: 0 + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: 0 }) }) @@ -155,33 +257,70 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another treasuryBalanceDiff: 0, strangerBalanceDiff: 0, anotherStrangerBalanceDiff: 0, - curatedModuleBalanceDiff: 0 + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: 0 }) }) it('first report after deposit without rewards', async () => { - await lido.handleOracleReport(0, 0, 1, ETH(32), 0, 0, 0, 0, { from: oracle }) + const tx = await lido.handleOracleReport(0, 0, 1, ETH(32), 0, 0, 0, 0, { from: oracle }) + await checkEvents({ + tx, + preCLValidators: 0, + postCLValidators: 1, + preCLBalance: ETH(32), + postCLBalance: ETH(32), + withdrawalsWithdrawn: 0, + executionLayerRewardsWithdrawn: 0, + postBufferedEther: ETH(4), + timeElapsed: 0, + preTotalShares: ETH(100), + preTotalEther: ETH(100), + postTotalShares: ETH(100), + postTotalEther: ETH(100), + sharesMintedAsFees: 0 + }) + await checkStat({ depositedValidators: 3, beaconValidators: 1, beaconBalance: ETH(32) }) await checkBalanceDeltas({ totalPooledEtherDiff: 0, treasuryBalanceDiff: 0, strangerBalanceDiff: 0, anotherStrangerBalanceDiff: 0, - curatedModuleBalanceDiff: 0 + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: 0 }) }) it('first report after deposit with rewards', async () => { - // elapsed time set to 1000000 because of annualBalanceIncrease limited by 10000 - await lido.handleOracleReport(0, 1000000, 1, ETH(33), 0, 0, 0, 0, { from: oracle }) + const tx = await lido.handleOracleReport(0, ONE_YEAR, 1, ETH(33), 0, 0, 0, 0, { from: oracle }) + const sharesMintedAsFees = calcSharesMintedAsFees(ETH(1), 10, 100, ETH(100), ETH(101)) + await checkEvents({ + tx, + preCLValidators: 0, + postCLValidators: 1, + preCLBalance: ETH(32), + postCLBalance: ETH(33), + withdrawalsWithdrawn: 0, + executionLayerRewardsWithdrawn: 0, + postBufferedEther: ETH(4), + timeElapsed: ONE_YEAR, + preTotalShares: ETH(100), + preTotalEther: ETH(100), + postTotalShares: toBN(ETH(100)).add(sharesMintedAsFees).toString(), + postTotalEther: ETH(101), + sharesMintedAsFees: sharesMintedAsFees + }) + await checkStat({ depositedValidators: 3, beaconValidators: 1, beaconBalance: ETH(33) }) await checkBalanceDeltas({ totalPooledEtherDiff: ETH(1), treasuryBalanceDiff: ETH(0.05), strangerBalanceDiff: ETH(0.3 * 0.9), - anotherStrangerBalanceDiff: ETH(0.7 * 0.9), - curatedModuleBalanceDiff: ETH(0.05) + anotherStrangerBalanceDiff: ETH(0.69 * 0.9), + curatedModuleBalanceDiff: ETH(0.05), + initialHolderBalanceDiff: ETH(0.01 * 0.9) }) }) }) @@ -211,7 +350,7 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another ) }) - it('withdrawal vault balance check', async () => { + it('withdrawal vault balance check 2', async () => { await assert.reverts( lido.handleOracleReport(0, 0, 0, 0, 1, 0, 0, 0, { from: oracle }), 'IncorrectWithdrawalsVaultBalance(0)' @@ -219,76 +358,126 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another }) it('does not revert on new total balance stay the same', async () => { - await lido.handleOracleReport(0, 0, 3, ETH(96), 0, 0, 0, 0, { from: oracle }) + let tx = await lido.handleOracleReport(0, 0, 3, ETH(96), 0, 0, 0, 0, { from: oracle }) + await checkEvents({ + tx, + preCLValidators: 0, + postCLValidators: 3, + preCLBalance: ETH(96), + postCLBalance: ETH(96), + withdrawalsWithdrawn: 0, + executionLayerRewardsWithdrawn: 0, + postBufferedEther: ETH(4), + timeElapsed: 0, + preTotalShares: ETH(100), + preTotalEther: ETH(100), + postTotalShares: ETH(100), + postTotalEther: ETH(100), + sharesMintedAsFees: 0 + }) await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96) }) await checkBalanceDeltas({ totalPooledEtherDiff: 0, treasuryBalanceDiff: 0, strangerBalanceDiff: 0, anotherStrangerBalanceDiff: 0, - curatedModuleBalanceDiff: 0 + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: 0 + }) + tx = await lido.handleOracleReport(0, 0, 3, ETH(96), 0, 0, 0, 0, { from: oracle }) + await checkEvents({ + tx, + preCLValidators: 3, + postCLValidators: 3, + preCLBalance: ETH(96), + postCLBalance: ETH(96), + withdrawalsWithdrawn: 0, + executionLayerRewardsWithdrawn: 0, + postBufferedEther: ETH(4), + timeElapsed: 0, + preTotalShares: ETH(100), + preTotalEther: ETH(100), + postTotalShares: ETH(100), + postTotalEther: ETH(100), + sharesMintedAsFees: 0 }) - await lido.handleOracleReport(0, 0, 3, ETH(96), 0, 0, 0, 0, { from: oracle }) await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96) }) await checkBalanceDeltas({ totalPooledEtherDiff: 0, treasuryBalanceDiff: 0, strangerBalanceDiff: 0, anotherStrangerBalanceDiff: 0, - curatedModuleBalanceDiff: 0 + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: 0 }) }) it('does not revert on new total balance decrease under the limit', async () => { // set oneOffCLBalanceDecreaseBPLimit = 1% await oracleReportSanityChecker.setOracleReportLimits( - { - churnValidatorsPerDayLimit: 255, - oneOffCLBalanceDecreaseBPLimit: 100, - annualBalanceIncreaseBPLimit: 10000, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, - maxPositiveTokenRebase: 1000000000, - maxAccountingExtraDataListItemsCount: 10000 - }, + ORACLE_REPORT_LIMITS_BOILERPLATE, { from: voting } ) - await lido.handleOracleReport(0, 0, 3, ETH(96), 0, 0, 0, 0, { from: oracle }) + let tx = await lido.handleOracleReport(0, 0, 3, ETH(96), 0, 0, 0, 0, { from: oracle }) + await checkEvents({ + tx, + preCLValidators: 0, + postCLValidators: 3, + preCLBalance: ETH(96), + postCLBalance: ETH(96), + withdrawalsWithdrawn: 0, + executionLayerRewardsWithdrawn: 0, + postBufferedEther: ETH(4), + timeElapsed: 0, + preTotalShares: ETH(100), + preTotalEther: ETH(100), + postTotalShares: ETH(100), + postTotalEther: ETH(100), + sharesMintedAsFees: 0 + }) await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96) }) await checkBalanceDeltas({ totalPooledEtherDiff: 0, treasuryBalanceDiff: 0, strangerBalanceDiff: 0, anotherStrangerBalanceDiff: 0, - curatedModuleBalanceDiff: 0 + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: 0 + }) + tx = await lido.handleOracleReport(0, 0, 3, ETH(95.04), 0, 0, 0, 0, { from: oracle }) + await checkEvents({ + tx, + preCLValidators: 3, + postCLValidators: 3, + preCLBalance: ETH(96), + postCLBalance: ETH(95.04), + withdrawalsWithdrawn: 0, + executionLayerRewardsWithdrawn: 0, + postBufferedEther: ETH(4), + timeElapsed: 0, + preTotalShares: ETH(100), + preTotalEther: ETH(100), + postTotalShares: ETH(100), + postTotalEther: ETH(99.04), + sharesMintedAsFees: 0 }) - await lido.handleOracleReport(0, 0, 3, ETH(95.04), 0, 0, 0, 0, { from: oracle }) await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(95.04) }) await checkBalanceDeltas({ totalPooledEtherDiff: ETH(-0.96), treasuryBalanceDiff: ETH(0), strangerBalanceDiff: ETH(-30 * 0.0096), - anotherStrangerBalanceDiff: toBN(ETH(0.0096)).mul(toBN(-70)).toString(), - curatedModuleBalanceDiff: ETH(0) + anotherStrangerBalanceDiff: toBN(ETH(0.0096)).mul(toBN(-69)).toString(), + curatedModuleBalanceDiff: ETH(0), + initialHolderBalanceDiff: ETH(-1 * 0.0096) }) }) it('reverts on new total balance decrease over the limit', async () => { // set oneOffCLBalanceDecreaseBPLimit = 1% await oracleReportSanityChecker.setOracleReportLimits( - { - churnValidatorsPerDayLimit: 255, - oneOffCLBalanceDecreaseBPLimit: 100, - annualBalanceIncreaseBPLimit: 10000, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, - maxPositiveTokenRebase: 1000000000, - maxAccountingExtraDataListItemsCount: 10000 - }, + ORACLE_REPORT_LIMITS_BOILERPLATE, { from: voting } ) @@ -299,7 +488,8 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another treasuryBalanceDiff: 0, strangerBalanceDiff: 0, anotherStrangerBalanceDiff: 0, - curatedModuleBalanceDiff: 0 + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: 0 }) await assert.reverts( lido.handleOracleReport(0, 0, 3, ETH(95.03), 0, 0, 0, 0, { from: oracle }), @@ -309,16 +499,9 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another it('does not revert on new total balance increase under the limit', async () => { // set annualBalanceIncreaseBPLimit = 1% - await oracleReportSanityChecker.setOracleReportLimits( - { - churnValidatorsPerDayLimit: 255, - oneOffCLBalanceDecreaseBPLimit: 100, + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, annualBalanceIncreaseBPLimit: 100, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, - maxPositiveTokenRebase: 1000000000, - maxAccountingExtraDataListItemsCount: 10000 }, { from: voting } ) @@ -330,32 +513,44 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another treasuryBalanceDiff: 0, strangerBalanceDiff: 0, anotherStrangerBalanceDiff: 0, - curatedModuleBalanceDiff: 0 + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: 0 + }) + const tx = await lido.handleOracleReport(0, ONE_YEAR, 3, ETH(96.96), 0, 0, 0, 0, { from: oracle }) + const sharesMintedAsFees = calcSharesMintedAsFees(ETH(0.96), 10, 100, ETH(100), ETH(100.96)) + await checkEvents({ + tx, + preCLValidators: 3, + postCLValidators: 3, + preCLBalance: ETH(96), + postCLBalance: ETH(96.96), + withdrawalsWithdrawn: 0, + executionLayerRewardsWithdrawn: 0, + postBufferedEther: ETH(4), + timeElapsed: ONE_YEAR, + preTotalShares: ETH(100), + preTotalEther: ETH(100), + postTotalShares: toBN(ETH(100)).add(sharesMintedAsFees).toString(), + postTotalEther: ETH(100.96), + sharesMintedAsFees: sharesMintedAsFees.toString() }) - await lido.handleOracleReport(0, ONE_YEAR, 3, ETH(96.96), 0, 0, 0, 0, { from: oracle }) await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96.96) }) await checkBalanceDeltas({ totalPooledEtherDiff: ETH(0.96), treasuryBalanceDiff: ETH(0.96 * 0.05), strangerBalanceDiff: ETH(30 * 0.0096 * 0.9), - anotherStrangerBalanceDiff: ETH(70 * 0.0096 * 0.9), - curatedModuleBalanceDiff: ETH(0.96 * 0.05) + anotherStrangerBalanceDiff: ETH(69 * 0.0096 * 0.9), + curatedModuleBalanceDiff: ETH(0.96 * 0.05), + initialHolderBalanceDiff: ETH(0.96 * 0.01 * 0.9) }) }) it('reverts on new total balance increase over the limit', async () => { // set annualBalanceIncreaseBPLimit = 1% - await oracleReportSanityChecker.setOracleReportLimits( - { - churnValidatorsPerDayLimit: 255, - oneOffCLBalanceDecreaseBPLimit: 100, + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, annualBalanceIncreaseBPLimit: 100, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, - maxPositiveTokenRebase: 1000000000, - maxAccountingExtraDataListItemsCount: 10000 }, { from: voting } ) @@ -367,7 +562,8 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another treasuryBalanceDiff: 0, strangerBalanceDiff: 0, anotherStrangerBalanceDiff: 0, - curatedModuleBalanceDiff: 0 + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: 0 }) await assert.reverts( lido.handleOracleReport(0, ONE_YEAR, 3, ETH(96.97), 0, 0, 0, 0, { from: oracle }), @@ -378,16 +574,10 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another it('does not revert on validators reported under limit', async () => { await lido.submit(ZERO_ADDRESS, { from: stranger, value: ETH(3100), gasPrice: 1 }) await lido.deposit(100, 1, '0x', { from: depositor }) - await oracleReportSanityChecker.setOracleReportLimits( - { + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, churnValidatorsPerDayLimit: 100, - oneOffCLBalanceDecreaseBPLimit: 100, annualBalanceIncreaseBPLimit: 100, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, - maxPositiveTokenRebase: 1000000000, - maxAccountingExtraDataListItemsCount: 10000 }, { from: voting, gasPrice: 1 } ) @@ -399,16 +589,10 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another it('reverts on validators reported when over limit', async () => { await lido.submit(ZERO_ADDRESS, { from: stranger, value: ETH(3200), gasPrice: 1 }) await lido.deposit(101, 1, '0x', { from: depositor }) - await oracleReportSanityChecker.setOracleReportLimits( - { + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, churnValidatorsPerDayLimit: 100, - oneOffCLBalanceDecreaseBPLimit: 100, annualBalanceIncreaseBPLimit: 100, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, - maxPositiveTokenRebase: 1000000000, - maxAccountingExtraDataListItemsCount: 10000 }, { from: voting, gasPrice: 1 } ) @@ -428,21 +612,15 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another treasuryBalanceDiff: 0, strangerBalanceDiff: 0, anotherStrangerBalanceDiff: 0, - curatedModuleBalanceDiff: 0 + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: 0 }) }) it('does not smooth if report in limits', async () => { - await oracleReportSanityChecker.setOracleReportLimits( - { + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, churnValidatorsPerDayLimit: 100, - oneOffCLBalanceDecreaseBPLimit: 100, - annualBalanceIncreaseBPLimit: 10000, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, - maxPositiveTokenRebase: 10000000, - maxAccountingExtraDataListItemsCount: 10000 }, { from: voting, gasPrice: 1 } ) @@ -451,16 +629,9 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another }) it('does not smooth if cl balance report over limit', async () => { - await oracleReportSanityChecker.setOracleReportLimits( - { + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, churnValidatorsPerDayLimit: 100, - oneOffCLBalanceDecreaseBPLimit: 100, - annualBalanceIncreaseBPLimit: 10000, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, - maxPositiveTokenRebase: 1000000, - maxAccountingExtraDataListItemsCount: 10000 }, { from: voting, gasPrice: 1 } ) @@ -470,24 +641,19 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another totalPooledEtherDiff: ETH(4), treasuryBalanceDiff: ETH(4 * 0.05), strangerBalanceDiff: ETH(4 * 0.3 * 0.9), - anotherStrangerBalanceDiff: ETH(4 * 0.7 * 0.9), - curatedModuleBalanceDiff: ETH(4 * 0.05) + anotherStrangerBalanceDiff: ETH(4 * 0.69 * 0.9), + curatedModuleBalanceDiff: ETH(4 * 0.05), + initialHolderBalanceDiff: ETH(0.036) }) }) it('does not smooth withdrawals if report in limits', async () => { await setBalance(withdrawalVault, ETH(1)) - await oracleReportSanityChecker.setOracleReportLimits( - { + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, churnValidatorsPerDayLimit: 100, - oneOffCLBalanceDecreaseBPLimit: 100, - annualBalanceIncreaseBPLimit: 10000, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, - maxPositiveTokenRebase: 10000000, - maxAccountingExtraDataListItemsCount: 10000 + annualBalanceIncreaseBPLimit: 100, }, { from: voting, gasPrice: 1 } ) @@ -497,8 +663,9 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another totalPooledEtherDiff: ETH(1), treasuryBalanceDiff: ETH(0.05), strangerBalanceDiff: ETH(0.3 * 0.9), - anotherStrangerBalanceDiff: ETH(0.7 * 0.9), - curatedModuleBalanceDiff: ETH(0.05) + anotherStrangerBalanceDiff: ETH(0.69 * 0.9), + curatedModuleBalanceDiff: ETH(0.05), + initialHolderBalanceDiff: ETH(0.01 * 0.9) }) assert.equals(await ethers.provider.getBalance(withdrawalVault), 0) }) @@ -506,16 +673,10 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another it('smooths withdrawals if report out of limit', async () => { await setBalance(withdrawalVault, ETH(1.1)) - await oracleReportSanityChecker.setOracleReportLimits( - { + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, churnValidatorsPerDayLimit: 100, - oneOffCLBalanceDecreaseBPLimit: 100, - annualBalanceIncreaseBPLimit: 10000, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, maxPositiveTokenRebase: 10000000, - maxAccountingExtraDataListItemsCount: 10000 }, { from: voting, gasPrice: 1 } ) @@ -525,8 +686,9 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another totalPooledEtherDiff: ETH(1), treasuryBalanceDiff: ETH(0.05), strangerBalanceDiff: ETH(0.3 * 0.9), - anotherStrangerBalanceDiff: ETH(0.7 * 0.9), - curatedModuleBalanceDiff: ETH(0.05) + anotherStrangerBalanceDiff: ETH(0.69 * 0.9), + curatedModuleBalanceDiff: ETH(0.05), + initialHolderBalanceDiff: ETH(0.01 * 0.9) }) assert.equals(await ethers.provider.getBalance(withdrawalVault), ETH(0.1)) @@ -535,16 +697,10 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another it('does not smooth el rewards if report in limit without lido fee', async () => { await setBalance(elRewardsVault, ETH(1)) - await oracleReportSanityChecker.setOracleReportLimits( - { + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, churnValidatorsPerDayLimit: 100, - oneOffCLBalanceDecreaseBPLimit: 100, - annualBalanceIncreaseBPLimit: 10000, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, maxPositiveTokenRebase: 10000000, - maxAccountingExtraDataListItemsCount: 10000 }, { from: voting, gasPrice: 1 } ) @@ -555,26 +711,21 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another totalPooledEtherDiff: ETH(1), treasuryBalanceDiff: 0, strangerBalanceDiff: ETH(0.3), - anotherStrangerBalanceDiff: ETH(0.7), - curatedModuleBalanceDiff: 0 + anotherStrangerBalanceDiff: ETH(0.69), + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: ETH(0.01) }) assert.equals(await ethers.provider.getBalance(elRewardsVault), ETH(0)) }) - it('does not smooth el rewards if report in limit without lido fee', async () => { + it('does not smooth el rewards if report in limit without lido fee 2', async () => { await setBalance(elRewardsVault, ETH(1.5)) - await oracleReportSanityChecker.setOracleReportLimits( - { + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, churnValidatorsPerDayLimit: 100, - oneOffCLBalanceDecreaseBPLimit: 100, - annualBalanceIncreaseBPLimit: 10000, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, maxPositiveTokenRebase: 10000000, - maxAccountingExtraDataListItemsCount: 10000 }, { from: voting, gasPrice: 1 } ) @@ -584,8 +735,9 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another totalPooledEtherDiff: ETH(1), treasuryBalanceDiff: 0, strangerBalanceDiff: ETH(0.3), - anotherStrangerBalanceDiff: ETH(0.7), - curatedModuleBalanceDiff: 0 + anotherStrangerBalanceDiff: ETH(0.69), + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: ETH(0.01) }) assert.equals(await ethers.provider.getBalance(elRewardsVault), ETH(0)) @@ -594,16 +746,10 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another it('smooths el rewards if report out of limit without lido fee', async () => { await setBalance(elRewardsVault, ETH(1.1)) - await oracleReportSanityChecker.setOracleReportLimits( - { + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, churnValidatorsPerDayLimit: 100, - oneOffCLBalanceDecreaseBPLimit: 100, - annualBalanceIncreaseBPLimit: 10000, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, maxPositiveTokenRebase: 10000000, - maxAccountingExtraDataListItemsCount: 10000 }, { from: voting, gasPrice: 1 } ) @@ -613,8 +759,9 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another totalPooledEtherDiff: ETH(1), treasuryBalanceDiff: 0, strangerBalanceDiff: ETH(0.3), - anotherStrangerBalanceDiff: ETH(0.7), - curatedModuleBalanceDiff: 0 + anotherStrangerBalanceDiff: ETH(0.69), + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: ETH(0.01) }) assert.equals(await ethers.provider.getBalance(elRewardsVault), ETH(0.1)) @@ -623,16 +770,9 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another it('does not smooth el rewards if report in limit', async () => { await setBalance(elRewardsVault, ETH(1)) - await oracleReportSanityChecker.setOracleReportLimits( - { + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, churnValidatorsPerDayLimit: 100, - oneOffCLBalanceDecreaseBPLimit: 100, - annualBalanceIncreaseBPLimit: 10000, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, - maxPositiveTokenRebase: 10000000, - maxAccountingExtraDataListItemsCount: 10000 }, { from: voting, gasPrice: 1 } ) @@ -643,8 +783,9 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another totalPooledEtherDiff: ETH(1), treasuryBalanceDiff: ETH(0.05), strangerBalanceDiff: ETH(0.3 * 0.9), - anotherStrangerBalanceDiff: ETH(0.7 * 0.9), - curatedModuleBalanceDiff: ETH(0.05) + anotherStrangerBalanceDiff: ETH(0.69 * 0.9), + curatedModuleBalanceDiff: ETH(0.05), + initialHolderBalanceDiff: ETH(0.01 * 0.9) }) assert.equals(await ethers.provider.getBalance(elRewardsVault), ETH(0.1)) @@ -653,16 +794,10 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another it('smooths el rewards if report out of limit', async () => { await setBalance(elRewardsVault, ETH(1.1)) - await oracleReportSanityChecker.setOracleReportLimits( - { + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, churnValidatorsPerDayLimit: 100, - oneOffCLBalanceDecreaseBPLimit: 100, - annualBalanceIncreaseBPLimit: 10000, - shareRateDeviationBPLimit: 10000, - maxValidatorExitRequestsPerReport: 10000, - requestTimestampMargin: 0, maxPositiveTokenRebase: 10000000, - maxAccountingExtraDataListItemsCount: 10000 }, { from: voting, gasPrice: 1 } ) @@ -672,11 +807,227 @@ contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, another totalPooledEtherDiff: ETH(1), treasuryBalanceDiff: ETH(0.05), strangerBalanceDiff: ETH(0.3 * 0.9), - anotherStrangerBalanceDiff: ETH(0.7 * 0.9), - curatedModuleBalanceDiff: ETH(0.05) + anotherStrangerBalanceDiff: ETH(0.69 * 0.9), + curatedModuleBalanceDiff: ETH(0.05), + initialHolderBalanceDiff: ETH(0.01 * 0.9) }) assert.equals(await ethers.provider.getBalance(elRewardsVault), ETH(0.2)) }) }) + describe('daily reports', () => { + beforeEach(async () => { + await await lido.deposit(3, 1, '0x', { from: depositor }) + await checkStat({ depositedValidators: 3, beaconValidators: 0, beaconBalance: 0 }) + await checkBalanceDeltas({ + totalPooledEtherDiff: 0, + treasuryBalanceDiff: 0, + strangerBalanceDiff: 0, + anotherStrangerBalanceDiff: 0, + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: 0 + }) + }) + it('smooths el rewards if report out of limit', async () => { + await setBalance(elRewardsVault, ETH(1.1)) + + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, + churnValidatorsPerDayLimit: 100, + maxPositiveTokenRebase: 10000000, + }, + { from: voting, gasPrice: 1 } + ) + await lido.handleOracleReport(0, ONE_DAY, 3, ETH(96.1), 0, ETH(1.1), 0, 0, { from: oracle, gasPrice: 1 }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96.1) }) + await checkBalanceDeltas({ + totalPooledEtherDiff: ETH(1), + treasuryBalanceDiff: ETH(0.05), + strangerBalanceDiff: ETH(0.3 * 0.9), + anotherStrangerBalanceDiff: ETH(0.69 * 0.9), + curatedModuleBalanceDiff: ETH(0.05), + initialHolderBalanceDiff: ETH(0.01 * 0.9) + }) + + assert.equals(await ethers.provider.getBalance(elRewardsVault), ETH(0.2)) + }) + it('does not smooth if report in limits', async () => { + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, + churnValidatorsPerDayLimit: 100, + }, + { from: voting, gasPrice: 1 } + ) + await lido.handleOracleReport(0, ONE_DAY, 3, ETH(96.0027), 0, 0, 0, 0, { from: oracle, gasPrice: 1 }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96.0027) }) + }) + + it('does not smooth if cl balance report over limit', async () => { + const clIncrease = 0.0028 + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, + churnValidatorsPerDayLimit: 100, + }, + { from: voting, gasPrice: 1 } + ) + await lido.handleOracleReport(0, ONE_DAY, 3, ETH(96 + 0.0028), 0, 0, 0, 0, { from: oracle, gasPrice: 1 }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96 + clIncrease) }) + assert( + 0.00014 + 0.0028 * 0.9 * 0.3 + 0.0028 * 0.9 * 0.69 + 0.0028 * 0.01 * 0.9 + 0.00014 === clIncrease, + 'cl balance increase' + ) + await checkBalanceDeltas({ + totalPooledEtherDiff: ETH(clIncrease), + treasuryBalanceDiff: ETH(0.00014), + strangerBalanceDiff: ETH(0.0028 * 0.9 * 0.3), + anotherStrangerBalanceDiff: ETH(0.0028 * 0.9 * 0.69), + curatedModuleBalanceDiff: ETH(0.00014), + initialHolderBalanceDiff: ETH(0.0028 * 0.01 * 0.9) + }) + }) + + it('does not smooth withdrawals if report in limits', async () => { + await setBalance(withdrawalVault, ETH(1)) + + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, + churnValidatorsPerDayLimit: 100, + annualBalanceIncreaseBPLimit: 100, + }, + { from: voting, gasPrice: 1 } + ) + await lido.handleOracleReport(0, ONE_DAY, 3, ETH(96), ETH(1), 0, 0, 0, { from: oracle, gasPrice: 1 }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96) }) + await checkBalanceDeltas({ + totalPooledEtherDiff: ETH(1), + treasuryBalanceDiff: ETH(0.05), + strangerBalanceDiff: ETH(0.3 * 0.9), + anotherStrangerBalanceDiff: ETH(0.69 * 0.9), + curatedModuleBalanceDiff: ETH(0.05), + initialHolderBalanceDiff: ETH(0.01 * 0.9) + }) + assert.equals(await ethers.provider.getBalance(withdrawalVault), 0) + }) + + it('smooths withdrawals if report out of limit', async () => { + await setBalance(withdrawalVault, ETH(1.1)) + + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, + churnValidatorsPerDayLimit: 100, + maxPositiveTokenRebase: 10000000, + }, + { from: voting, gasPrice: 1 } + ) + await lido.handleOracleReport(0, ONE_DAY, 3, ETH(96), ETH(1.1), 0, 0, 0, { from: oracle, gasPrice: 1 }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96) }) + await checkBalanceDeltas({ + totalPooledEtherDiff: ETH(1), + treasuryBalanceDiff: ETH(0.05), + strangerBalanceDiff: ETH(0.3 * 0.9), + anotherStrangerBalanceDiff: ETH(0.69 * 0.9), + curatedModuleBalanceDiff: ETH(0.05), + initialHolderBalanceDiff: ETH(0.01 * 0.9) + }) + + assert.equals(await ethers.provider.getBalance(withdrawalVault), ETH(0.1)) + }) + + it('does not smooth el rewards if report in limit without lido fee', async () => { + await setBalance(elRewardsVault, ETH(1)) + + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, + churnValidatorsPerDayLimit: 100, + maxPositiveTokenRebase: 10000000, + }, + { from: voting, gasPrice: 1 } + ) + await lido.handleOracleReport(0, ONE_DAY, 3, ETH(96), 0, ETH(1), 0, 0, { from: oracle, gasPrice: 1 }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96) }) + + await checkBalanceDeltas({ + totalPooledEtherDiff: ETH(1), + treasuryBalanceDiff: 0, + strangerBalanceDiff: ETH(0.3), + anotherStrangerBalanceDiff: ETH(0.69), + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: ETH(0.01) + }) + + assert.equals(await ethers.provider.getBalance(elRewardsVault), ETH(0)) + }) + + it('does not smooth el rewards if report in limit without lido fee 2', async () => { + await setBalance(elRewardsVault, ETH(1.5)) + + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, + churnValidatorsPerDayLimit: 100, + maxPositiveTokenRebase: 10000000, + }, + { from: voting, gasPrice: 1 } + ) + await lido.handleOracleReport(0, ONE_DAY, 3, ETH(95.5), 0, ETH(1.5), 0, 0, { from: oracle, gasPrice: 1 }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(95.5) }) + await checkBalanceDeltas({ + totalPooledEtherDiff: ETH(1), + treasuryBalanceDiff: 0, + strangerBalanceDiff: ETH(0.3), + anotherStrangerBalanceDiff: ETH(0.69), + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: ETH(0.01) + }) + + assert.equals(await ethers.provider.getBalance(elRewardsVault), ETH(0)) + }) + + it('smooths el rewards if report out of limit without lido fee', async () => { + await setBalance(elRewardsVault, ETH(1.1)) + + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, + churnValidatorsPerDayLimit: 100, + maxPositiveTokenRebase: 10000000, + }, + { from: voting, gasPrice: 1 } + ) + await lido.handleOracleReport(0, ONE_DAY, 3, ETH(96), 0, ETH(1.1), 0, 0, { from: oracle, gasPrice: 1 }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96) }) + await checkBalanceDeltas({ + totalPooledEtherDiff: ETH(1), + treasuryBalanceDiff: 0, + strangerBalanceDiff: ETH(0.3), + anotherStrangerBalanceDiff: ETH(0.69), + curatedModuleBalanceDiff: 0, + initialHolderBalanceDiff: ETH(0.01) + }) + + assert.equals(await ethers.provider.getBalance(elRewardsVault), ETH(0.1)) + }) + + it('does not smooth el rewards if report in limit', async () => { + await setBalance(elRewardsVault, ETH(1)) + + await oracleReportSanityChecker.setOracleReportLimits({ + ...ORACLE_REPORT_LIMITS_BOILERPLATE, + churnValidatorsPerDayLimit: 100, + }, + { from: voting, gasPrice: 1 } + ) + await lido.handleOracleReport(0, ONE_DAY, 3, ETH(96.1), 0, ETH(0.9), 0, 0, { from: oracle, gasPrice: 1 }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96.1) }) + + await checkBalanceDeltas({ + totalPooledEtherDiff: ETH(1), + treasuryBalanceDiff: ETH(0.05), + strangerBalanceDiff: ETH(0.3 * 0.9), + anotherStrangerBalanceDiff: ETH(0.69 * 0.9), + curatedModuleBalanceDiff: ETH(0.05), + initialHolderBalanceDiff: ETH(0.01 * 0.9) + }) + + assert.equals(await ethers.provider.getBalance(elRewardsVault), ETH(0.1)) + }) + }) }) diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index f5a951102..be070bce2 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -8,12 +8,8 @@ const { assertBn, assertEvent } = require('@aragon/contract-helpers-test/src/ass const { assertRevert } = require('../helpers/assertThrow') const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') -const { formatEther } = require('ethers/lib/utils') -const { waitBlocks, EvmSnapshot } = require('../helpers/blockchain') +const { waitBlocks, EvmSnapshot, advanceChainTime, getCurrentBlockTimestamp } = require('../helpers/blockchain') const { - getEthBalance, - formatStEth, - formatBN, hexConcat, pad, ETH, @@ -21,14 +17,16 @@ const { div15, assertNoEvent, StETH, - setBalance + shares, + setBalance, + prepIdsCountsPayload } = require('../helpers/utils') const { assert } = require('../helpers/assert') const nodeOperators = require('../helpers/node-operators') const { deployProtocol } = require('../helpers/protocol') const { setupNodeOperatorsRegistry } = require('../helpers/staking-modules') const { pushOracleReport } = require('../helpers/oracle') -const { SECONDS_PER_FRAME } = require('../helpers/constants') +const { SECONDS_PER_FRAME, INITIAL_HOLDER, MAX_UINT256 } = require('../helpers/constants') const { oracleReportSanityCheckerStubFactory } = require('../helpers/factories') const { newApp } = require('../helpers/dao') @@ -46,19 +44,12 @@ const ADDRESS_4 = '0x0000000000000000000000000000000000000004' const UNLIMITED = 1000000000 const TOTAL_BASIS_POINTS = 10000 -const FEE_PRECISION_POINTS = bn(10).pow(bn(20)) // 100 * 10e18 -const toPrecBP = (bp) => bn(bp).mul(FEE_PRECISION_POINTS).div(bn(10000)) // Divides a BN by 1e15 const MAX_DEPOSITS = 150 const CURATED_MODULE_ID = 1 const CALLDATA = '0x0' -async function getTimestamp() { - const blockNum = await ethers.provider.getBlockNumber(); - const block = await ethers.provider.getBlock(blockNum); - return block.timestamp; -} contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody, depositor, treasury]) => { let app, oracle, depositContract, operators @@ -134,8 +125,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody const pushReport = async (clValidators, clBalance) => { const elRewards = await web3.eth.getBalance(elRewardsVault.address) await pushOracleReport(consensus, oracle, clValidators, clBalance, elRewards) - await ethers.provider.send('evm_increaseTime', [SECONDS_PER_FRAME + 1000]) - await ethers.provider.send('evm_mine') + await advanceChainTime(SECONDS_PER_FRAME + 1000) } const checkStat = async ({ depositedValidators, beaconValidators, beaconBalance }) => { @@ -202,8 +192,8 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await assert.reverts(appProxy.finalizeUpgrade_v2(lidoLocator.address, eip712StETH.address), 'NOT_INITIALIZED') }) - it('reverts with NOT_INITIALIZED on implementation finalized ', async () => { - await assert.reverts(appBase.finalizeUpgrade_v2(lidoLocator.address, eip712StETH.address), 'NOT_INITIALIZED') + it('reverts with UNEXPECTED_CONTRACT_VERSION on implementation finalization', async () => { + await assert.reverts(appBase.finalizeUpgrade_v2(lidoLocator.address, eip712StETH.address), 'UNEXPECTED_CONTRACT_VERSION') }) it('reverts if already initialized', async () => { @@ -217,11 +207,11 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody }) it('reverts if lido locator address is ZERO', async () => { - await assertRevert(app.finalizeUpgrade_v2(ZERO_ADDRESS, eip712StETH.address), 'LIDO_LOCATOR_ZERO_ADDRESS') + await assert.reverts(app.finalizeUpgrade_v2(ZERO_ADDRESS, eip712StETH.address), 'LIDO_LOCATOR_ZERO_ADDRESS') }) it('reverts if eip712StETH address is ZERO', async () => { - await assertRevert(app.finalizeUpgrade_v2(lidoLocator.address, ZERO_ADDRESS), 'EIP712_STETH_ZERO_ADDRESS') + await assert.reverts(app.finalizeUpgrade_v2(lidoLocator.address, ZERO_ADDRESS), 'EIP712_STETH_ZERO_ADDRESS') }) }) @@ -235,102 +225,66 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody //assertEvent(receipt, 'MaxPositiveTokenRebaseSet', { expectedArgs: { maxPositiveTokenRebase: maxPositiveTokenRebase } }) }) - async function getStEthBalance(address) { - return formatStEth(await app.balanceOf(address)) - } - - const logLidoState = async () => { - const elRewardsVaultBalance = await getEthBalance(elRewardsVault.address) - const lidoBalance = await getEthBalance(app.address) - const lidoTotalSupply = formatBN(await app.totalSupply()) - const lidoTotalPooledEther = formatBN(await app.getTotalPooledEther()) - const lidoBufferedEther = formatBN(await app.getBufferedEther()) - const lidoTotalShares = formatBN(await app.getTotalShares()) - const beaconStat = await app.getBeaconStat() - const depositedValidators = beaconStat.depositedValidators.toString() - const beaconValidators = beaconStat.beaconValidators.toString() - const beaconBalance = formatEther(beaconStat.beaconBalance) - - console.log({ - elRewardsVaultBalance, - lidoBalance, - lidoTotalSupply, - lidoTotalPooledEther, - lidoBufferedEther, - lidoTotalShares, - depositedValidators, - beaconValidators, - beaconBalance - }) - } + it('Execution layer rewards distribution works when zero cl rewards reported', async () => { + const clRewards = 0 + const initialDeposit = 1 + const user2Deposit = 31 + const totalDeposit = initialDeposit + user2Deposit + const totalElRewards = totalDeposit / TOTAL_BASIS_POINTS + const user2Rewards = user2Deposit / TOTAL_BASIS_POINTS - const logBalances = async () => { - const user2stEthBalance = await getStEthBalance(user2) - const treasuryStEthBalance = await getStEthBalance(treasury) - console.log({ user2stEthBalance, treasuryStEthBalance }) - } - - const logAll = async () => { - await logLidoState() - await logBalances() - console.log() - } + await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(user2Deposit)) + await pushReport(1, ETH(totalDeposit)) + await setBalance(elRewardsVault.address, ETH(totalElRewards)) - it('Execution layer rewards distribution works when zero rewards reported', async () => { - const depositAmount = 32 - const elRewards = depositAmount / TOTAL_BASIS_POINTS - const beaconRewards = 0 + await pushReport(1, ETH(totalDeposit + clRewards)) - await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(depositAmount)) - await pushReport(1, ETH(depositAmount)) - await setBalance(elRewardsVault.address, ETH(elRewards)) - - await pushReport(1, ETH(depositAmount + beaconRewards)) - console.log((await app.getTotalPooledEther()).toString()) - - assertBn(await app.getTotalPooledEther(), ETH(depositAmount + elRewards + beaconRewards)) - assertBn(await app.totalSupply(), ETH(depositAmount + elRewards + beaconRewards)) - assertBn(await app.balanceOf(user2), StETH(depositAmount + elRewards)) - assertBn(await app.getTotalELRewardsCollected(), ETH(elRewards)) + assertBn(await app.getTotalPooledEther(), ETH(initialDeposit + user2Deposit + totalElRewards + clRewards)) + assertBn(await app.totalSupply(), StETH(initialDeposit + user2Deposit + totalElRewards + clRewards)) + assertBn(await app.balanceOf(user2), StETH(user2Deposit + user2Rewards)) + assertBn(await app.getTotalELRewardsCollected(), ETH(totalElRewards)) }) - it('Execution layer rewards distribution works when negative rewards reported', async () => { - const depositAmount = 32 - const elRewards = depositAmount / TOTAL_BASIS_POINTS - const beaconRewards = -2 + it('Execution layer rewards distribution works when negative cl rewards reported', async () => { + const clRewards = -2 + const initialDeposit = 1 + const user2Deposit = 31 + const totalDeposit = initialDeposit + user2Deposit + const totalElRewards = totalDeposit / TOTAL_BASIS_POINTS + const user2Rewards = user2Deposit / TOTAL_BASIS_POINTS - await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(depositAmount)) - await pushReport(1, ETH(depositAmount)) + await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(user2Deposit)) + await pushReport(1, ETH(totalDeposit)) - await setBalance(elRewardsVault.address, ETH(elRewards)) - await pushReport(1, ETH(depositAmount + beaconRewards)) + await setBalance(elRewardsVault.address, ETH(totalElRewards)) + await pushReport(1, ETH(totalDeposit + clRewards)) - assertBn(await app.getTotalPooledEther(), ETH(depositAmount + elRewards + beaconRewards)) - assertBn(await app.balanceOf(user2), StETH(depositAmount + elRewards + beaconRewards)) - assertBn(await app.getTotalELRewardsCollected(), ETH(elRewards)) + assertBn(await app.getTotalPooledEther(), ETH(initialDeposit + user2Deposit + totalElRewards + clRewards)) + assertBn(await app.balanceOf(user2), StETH(user2Deposit + user2Rewards + clRewards * 31 / 32)) + assertBn(await app.getTotalELRewardsCollected(), ETH(totalElRewards)) }) - it('Execution layer rewards distribution works when positive rewards reported', async () => { - const depositAmount = 32 - const elRewards = depositAmount / TOTAL_BASIS_POINTS - const beaconRewards = 3 + it('Execution layer rewards distribution works when positive cl rewards reported', async () => { + const clRewards = 3 + const initialDeposit = 1 + const user2Deposit = 31 + const totalDeposit = initialDeposit + user2Deposit + const totalElRewards = totalDeposit / TOTAL_BASIS_POINTS - await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(depositAmount)) - await pushReport(1, ETH(depositAmount)) + await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(user2Deposit)) + await pushReport(1, ETH(totalDeposit)) - await setBalance(elRewardsVault.address, ETH(elRewards)) - await pushReport(1, ETH(depositAmount + beaconRewards)) + await setBalance(elRewardsVault.address, ETH(totalElRewards)) + await pushReport(1, ETH(totalDeposit + clRewards)) - assertBn(await app.getTotalPooledEther(), ETH(depositAmount + elRewards + beaconRewards)) - assertBn(await app.getTotalELRewardsCollected(), ETH(elRewards)) + assertBn(await app.getTotalPooledEther(), ETH(totalDeposit + totalElRewards + clRewards)) + assertBn(await app.getTotalELRewardsCollected(), ETH(totalElRewards)) - const { modulesFee, treasuryFee } = await stakingRouter.getStakingFeeAggregateDistribution() + const fee = await app.getFee() - const stakersReward = bn(ETH(elRewards + beaconRewards)) - .mul(FEE_PRECISION_POINTS.sub(modulesFee).sub(treasuryFee)) - .div(FEE_PRECISION_POINTS) + const stakersReward = (totalElRewards + clRewards) * (TOTAL_BASIS_POINTS - fee) / TOTAL_BASIS_POINTS - assertBn(await app.balanceOf(user2), bn(StETH(depositAmount)).add(stakersReward)) + assertBn(await app.balanceOf(user2), StETH(user2Deposit + stakersReward * 31 / 32)) }) it('Attempt to set invalid execution layer rewards withdrawal limit', async () => { @@ -339,7 +293,6 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody //assertEvent(await app.setMaxPositiveTokenRebase(1, { from: voting }), 'MaxPositiveTokenRebaseSet', { // expectedArgs: { maxPositiveTokenRebase: 1 } - //}) const setupNodeOperatorsForELRewardsVaultTests = async (userAddress, initialDepositAmount) => { await app.setFee(1000, { from: voting }) // 10% @@ -412,7 +365,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await setBalance(elRewardsVault.address, ETH(elRewards)) await pushReport(1, ETH(depositAmount + beaconRewards)) - const {totalFee} = await app.getFee() + const { totalFee } = await app.getFee() const shareOfRewardsForStakers = (TOTAL_BASIS_POINTS - totalFee) / TOTAL_BASIS_POINTS assertBn(await app.getTotalPooledEther(), ETH(depositAmount + elRewards + beaconRewards)) assertBn(await app.getBufferedEther(), ETH(elRewards)) @@ -459,27 +412,6 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody }) }) - describe('receiveStakingRouterRemainder()', async () => { - it('unable to receive eth from arbitrary account', async () => { - await assertRevert(app.receiveStakingRouterDepositRemainder({ from: nobody, value: ETH(1) })) - }) - - it('event work', async () => { - // unlock stakingRouter account (allow transactions originated from stakingRouter.address) - await ethers.provider.send('hardhat_impersonateAccount', [stakingRouter.address]) - - // add some amount to the sender - - await setBalance(stakingRouter.address, ETH(100)) - - const receipt = await app.receiveStakingRouterDepositRemainder({ from: stakingRouter.address, value: ETH(2) }) - - assertEvent(receipt, 'StakingRouterDepositRemainderReceived', { - expectedArgs: { amount: ETH(2) } - }) - }) - }) - it('setWithdrawalCredentials works', async () => { await stakingRouter.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) @@ -549,16 +481,22 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await assertRevert(app.submit(ZERO_ADDRESS, { from: user1, value: ETH(0) }), 'ZERO_DEPOSIT') await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(0) }), 'ZERO_DEPOSIT') + // Initial balance (1 ETH) + assertBn(await app.getTotalPooledEther(), ETH(1)) + assertBn(await app.getBufferedEther(), ETH(1)) + assertBn(await app.balanceOf(INITIAL_HOLDER), tokens(1)) + assertBn(await app.totalSupply(), tokens(1)) + // +1 ETH await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(1) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await depositContract.totalCalls(), 0) - assertBn(await app.getTotalPooledEther(), ETH(1)) - assertBn(await app.getBufferedEther(), ETH(1)) + assertBn(await app.getTotalPooledEther(), ETH(2)) + assertBn(await app.getBufferedEther(), ETH(2)) assertBn(await app.getTotalELRewardsCollected(), 0) assertBn(await app.balanceOf(user1), tokens(1)) - assertBn(await app.totalSupply(), tokens(1)) + assertBn(await app.totalSupply(), tokens(2)) // +2 ETH const receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) // another form of a deposit call @@ -567,11 +505,11 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await depositContract.totalCalls(), 0) - assertBn(await app.getTotalPooledEther(), ETH(3)) - assertBn(await app.getBufferedEther(), ETH(3)) + assertBn(await app.getTotalPooledEther(), ETH(4)) + assertBn(await app.getBufferedEther(), ETH(4)) assertBn(await app.getTotalELRewardsCollected(), 0) assertBn(await app.balanceOf(user2), tokens(2)) - assertBn(await app.totalSupply(), tokens(3)) + assertBn(await app.totalSupply(), tokens(4)) // +30 ETH await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(30) }) @@ -586,7 +524,10 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody // set withdrawalCredentials with keys, because they were trimmed await stakingRouter.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) - assertBn(await stakingRouter.getStakingModuleMaxDepositsCount(CURATED_MODULE_ID), 0) + assertBn( + await stakingRouter.getStakingModuleMaxDepositsCount(CURATED_MODULE_ID, await app.getDepositableEther()), + 0 + ) await operators.addSigningKeys(0, 1, pad('0x010203', 48), pad('0x01', 96), { from: voting }) await operators.addSigningKeys( @@ -598,20 +539,23 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody ) await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) - assertBn(await stakingRouter.getStakingModuleMaxDepositsCount(CURATED_MODULE_ID), 1) - assertBn(await app.getTotalPooledEther(), ETH(33)) - assertBn(await app.getBufferedEther(), ETH(33)) + assertBn( + await stakingRouter.getStakingModuleMaxDepositsCount(CURATED_MODULE_ID, await app.getDepositableEther()), + 1 + ) + assertBn(await app.getTotalPooledEther(), ETH(34)) + assertBn(await app.getBufferedEther(), ETH(34)) // now deposit works await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) - assertBn(await app.getTotalPooledEther(), ETH(33)) - assertBn(await app.getBufferedEther(), ETH(1)) + assertBn(await app.getTotalPooledEther(), ETH(34)) + assertBn(await app.getBufferedEther(), ETH(2)) assertBn(await app.balanceOf(user1), tokens(1)) assertBn(await app.balanceOf(user2), tokens(2)) assertBn(await app.balanceOf(user3), tokens(30)) - assertBn(await app.totalSupply(), tokens(33)) + assertBn(await app.totalSupply(), tokens(34)) assertBn(await depositContract.totalCalls(), 1) const c0 = await depositContract.calls.call(0) @@ -624,21 +568,21 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(100) }) await app.deposit(1, 1, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 2, beaconValidators: 0, beaconBalance: ETH(0) }) - assertBn(await app.getTotalPooledEther(), ETH(133)) - assertBn(await app.getBufferedEther(), ETH(69)) + assertBn(await app.getTotalPooledEther(), ETH(134)) + assertBn(await app.getBufferedEther(), ETH(70)) assertBn(await app.balanceOf(user1), tokens(101)) assertBn(await app.balanceOf(user2), tokens(2)) assertBn(await app.balanceOf(user3), tokens(30)) - assertBn(await app.totalSupply(), tokens(133)) + assertBn(await app.totalSupply(), tokens(134)) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 4, beaconValidators: 0, beaconBalance: ETH(0) }) - assertBn(await app.getTotalPooledEther(), ETH(133)) - assertBn(await app.getBufferedEther(), ETH(5)) + assertBn(await app.getTotalPooledEther(), ETH(134)) + assertBn(await app.getBufferedEther(), ETH(6)) assertBn(await app.balanceOf(user1), tokens(101)) assertBn(await app.balanceOf(user2), tokens(2)) assertBn(await app.balanceOf(user3), tokens(30)) - assertBn(await app.totalSupply(), tokens(133)) + assertBn(await app.totalSupply(), tokens(134)) assertBn(await depositContract.totalCalls(), 4) const calls = {} @@ -734,7 +678,6 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody expectedIsStakingPaused, expectedIsStakingLimited ) => { - assert((await app.isStakingPaused()) === false) currentStakeLimit = await app.getCurrentStakeLimit() assertBn(currentStakeLimit, expectedCurrentStakeLimit) @@ -761,17 +704,18 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody } it('staking pause & unlimited resume works', async () => { + assert.equals(await app.isStakingPaused(), false) + let receipt - const MAX_UINT256 = bn(2).pow(bn(256)).sub(bn(1)) - await verifyStakeLimitState(bn(0), bn(0), MAX_UINT256, false, false) + await verifyStakeLimitState(0, 0, bn(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') - verifyStakeLimitState(bn(0), bn(0), bn(0), true, false) + await verifyStakeLimitState(0, 0, 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`) @@ -779,7 +723,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await assertRevert(app.resumeStaking(), 'APP_AUTH_FAILED') receipt = await app.resumeStaking({ from: voting }) assertEvent(receipt, 'StakingResumed') - await verifyStakeLimitState(bn(0), bn(0), MAX_UINT256, false, false) + await verifyStakeLimitState(0, 0, bn(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) }) @@ -951,7 +895,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody assertBn(await depositContract.totalCalls(), 1) await assertRevert(operators.removeSigningKey(0, 0, { from: voting }), 'OUT_OF_RANGE') - assertBn(await app.getBufferedEther(), ETH(1)) + assertBn(await app.getBufferedEther(), ETH(2)) await operators.removeSigningKey(0, 1, { from: voting }) @@ -959,8 +903,8 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) assertBn(await depositContract.totalCalls(), 1) - assertBn(await app.getTotalPooledEther(), ETH(133)) - assertBn(await app.getBufferedEther(), ETH(101)) + assertBn(await app.getTotalPooledEther(), ETH(134)) + assertBn(await app.getBufferedEther(), ETH(102)) }) it("out of signing keys doesn't revert but buffers", async () => { @@ -974,7 +918,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) - await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(100) }) + await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(99) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await depositContract.totalCalls(), 1) @@ -1023,14 +967,14 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) await assertRevert( - app.handleOracleReport(await getTimestamp(), 1, ETH(30), 0, 0, 0, 0, 0, { from: appManager }), + app.handleOracleReport(await getCurrentBlockTimestamp(), 1, ETH(30), 0, 0, 0, 0, 0, { from: appManager }), 'APP_AUTH_FAILED' ) await pushReport(1, ETH(30)) await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(30) }) - await assertRevert(app.handleOracleReport(await getTimestamp(), 1, ETH(29), 0, 0, 0, 0, 0, { from: nobody }), 'APP_AUTH_FAILED') + await assertRevert(app.handleOracleReport(await getCurrentBlockTimestamp(), 1, ETH(29), 0, 0, 0, 0, 0, { from: nobody }), 'APP_AUTH_FAILED') await pushReport(1, ETH(100)) // stale data await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(100) }) @@ -1043,7 +987,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) - await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(34) }) + await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(33) }) const withdrawal = await WithdrawalVault.new(app.address, treasury) const withdrawalCredentials = hexConcat('0x01', pad(withdrawal.address, 31)) await stakingRouter.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) @@ -1116,7 +1060,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(40) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) - assertBn(await app.getBufferedEther(), ETH(8)) + assertBn(await app.getBufferedEther(), ETH(9)) await assertRevert(app.stop({ from: user2 }), 'APP_AUTH_FAILED') await app.stop({ from: voting }) @@ -1133,7 +1077,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(4) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) - assertBn(await app.getBufferedEther(), ETH(12)) + assertBn(await app.getBufferedEther(), ETH(13)) }) it('rewards distribution on module with zero treasury and module fee', async () => { @@ -1172,7 +1116,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody // check stat before deposit await checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: 0 }) - await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(34) }) + await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(33) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await pushReport(1, ETH(36)) @@ -1198,7 +1142,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) - await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(34) }) + await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(33) }) const withdrawal = await WithdrawalVault.new(app.address, treasury) await stakingRouter.setWithdrawalCredentials(hexConcat('0x01', pad(withdrawal.address, 31)), { from: voting }) await operators.addSigningKeys(0, 1, pad('0x010203', 48), pad('0x01', 96), { from: voting }) @@ -1225,7 +1169,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) - await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(34) }) + await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(33) }) const withdrawal = await WithdrawalVault.new(app.address, treasury) await stakingRouter.setWithdrawalCredentials(hexConcat('0x01', pad(withdrawal.address, 31)), { from: voting }) await operators.addSigningKeys(0, 1, pad('0x010203', 48), pad('0x01', 96), { from: voting }) @@ -1271,16 +1215,16 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) - // Only 32 ETH deposited - await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(32) }) + // Only 32 ETH deposited (we have initial 1 ETH) + await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(31) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(32) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) - assertBn(await app.totalSupply(), tokens(64)) + assertBn(await app.totalSupply(), StETH(64)) await pushReport(1, ETH(36)) await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(36) }) - assertBn(await app.totalSupply(), tokens(68)) + assertBn(await app.totalSupply(), StETH(68)) await checkRewards({ treasury: 200, operator: 199 }) }) @@ -1312,7 +1256,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await nodeOperators.addNodeOperator(operators, { name: 'short on keys', rewardAddress: ADDRESS_4 }, { from: voting }) // Deposit huge chunk - await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(32 * 3 + 50) }) + await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(32 * 3 + 49) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 3, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await app.getTotalPooledEther(), ETH(146)) @@ -1348,7 +1292,8 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody assertBn(await operators.getUnusedSigningKeyCount(3, { from: nobody }), 0) // #1 goes below the limit - await operators.updateExitedValidatorsCount(1, 1, { from: voting }) + const { operatorIds, keysCounts } = prepIdsCountsPayload(1, 1) + await operators.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }) await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(1) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 3, beaconValidators: 0, beaconBalance: ETH(0) }) @@ -1368,7 +1313,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody // Adding a key & setting staking limit will help await operators.addSigningKeys(0, 1, pad('0x0003', 48), pad('0x01', 96), { from: voting }) - operators.setNodeOperatorStakingLimit(0, 3, { from: voting }) + await operators.setNodeOperatorStakingLimit(0, 3, { from: voting }) await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(1) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) @@ -1437,7 +1382,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody // Small deposits for (let i = 0; i < 14; i++) await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(10) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) - await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(6) }) + await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(5) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 3, beaconValidators: 0, beaconBalance: ETH(0) }) @@ -1474,7 +1419,8 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody assertBn(await operators.getUnusedSigningKeyCount(3, { from: nobody }), 0) // #1 goes below the limit (doesn't change situation. validator stop decreases limit) - await operators.updateExitedValidatorsCount(1, 1, { from: voting }) + const { operatorIds, keysCounts } = prepIdsCountsPayload(1, 1) + await operators.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }) await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(1) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 3, beaconValidators: 0, beaconBalance: ETH(0) }) @@ -1494,7 +1440,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody // Adding a key & setting staking limit will help await operators.addSigningKeys(0, 1, pad('0x0003', 48), pad('0x01', 96), { from: voting }) - operators.setNodeOperatorStakingLimit(0, 3, { from: voting }) + await operators.setNodeOperatorStakingLimit(0, 3, { from: voting }) await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(1) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) @@ -1559,7 +1505,7 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody await operators.setNodeOperatorStakingLimit(3, UNLIMITED, { from: voting }) // #1 and #0 get the funds - await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(64) }) + await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(63) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 2, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await app.getTotalPooledEther(), ETH(64)) @@ -1592,38 +1538,38 @@ contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody }) it('burnShares works', async () => { - await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(1) }) + await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(2) }) // 4 ETH total // can burn shares of an arbitrary user - const expectedPreTokenAmount = await app.getPooledEthByShares(ETH(0.5)) - let receipt = await app.burnShares(user1, ETH(0.5), { from: voting }) - const expectedPostTokenAmount = await app.getPooledEthByShares(ETH(0.5)) + const pre1SharePrice = await app.getPooledEthByShares(shares(1)) + let receipt = await app.burnShares(user1, shares(1), { from: voting }) + const post1SharePrice = await app.getPooledEthByShares(shares(1)) assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, - preRebaseTokenAmount: expectedPreTokenAmount, - postRebaseTokenAmount: expectedPostTokenAmount, - sharesAmount: ETH(0.5) + preRebaseTokenAmount: pre1SharePrice, + postRebaseTokenAmount: post1SharePrice, + sharesAmount: shares(1) } }) - const expectedPreDoubledAmount = await app.getPooledEthByShares(ETH(0.5)) - receipt = await app.burnShares(user1, ETH(0.5), { from: voting }) - const expectedPostDoubledAmount = await app.getPooledEthByShares(ETH(0.5)) + const pre2SharePrice = await app.getPooledEthByShares(shares(1)) + receipt = await app.burnShares(user1, shares(1), { from: voting }) + const post2SharePrice = await app.getPooledEthByShares(shares(1)) assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, - preRebaseTokenAmount: expectedPreDoubledAmount, - postRebaseTokenAmount: expectedPostDoubledAmount, - sharesAmount: ETH(0.5) + preRebaseTokenAmount: pre2SharePrice, + postRebaseTokenAmount: post2SharePrice, + sharesAmount: shares(1) } }) - assertBn(expectedPreTokenAmount.mul(bn(2)), expectedPreDoubledAmount) - assertBn(tokens(0), await app.getPooledEthByShares(ETH(0.5))) + assertBn(pre1SharePrice.muln(3), pre2SharePrice.muln(2)) + assertBn(await app.getPooledEthByShares(shares(1)), ETH(3)) // user1 has zero shares after all - assertBn(await app.sharesOf(user1), tokens(0)) + assertBn(await app.sharesOf(user1), shares(0)) // voting can't continue burning if user already has no shares await assertRevert(app.burnShares(user1, 1, { from: voting }), 'BURN_AMOUNT_EXCEEDS_BALANCE') diff --git a/test/0.4.24/lidoHandleOracleReport.test.js b/test/0.4.24/lidoHandleOracleReport.test.js deleted file mode 100644 index af4d83571..000000000 --- a/test/0.4.24/lidoHandleOracleReport.test.js +++ /dev/null @@ -1,238 +0,0 @@ -const hre = require('hardhat') -const { assertBn } = require('@aragon/contract-helpers-test/src/asserts') -const { assert } = require('../helpers/assert') -const { assertRevert } = require('../helpers/assertThrow') -const { deployProtocol } = require('../helpers/protocol') -const { pushOracleReport } = require('../helpers/oracle') -const { EvmSnapshot } = require('../helpers/blockchain') - -const ETH = (value) => web3.utils.toWei(value + '', 'ether') - -contract.skip('Lido: handleOracleReport', ([appManager, stranger, depositor]) => { - let app, consensus, oracle, snapshot - - before('deploy base app', async () => { - const deployed = await deployProtocol({ - depositSecurityModuleFactory: async () => { - return { address: depositor } - } - }) - - app = deployed.pool - consensus = deployed.consensusContract - oracle = deployed.oracle - - snapshot = new EvmSnapshot(hre.ethers.provider) - await snapshot.make() - }) - - afterEach(async () => { - await snapshot.rollback() - }) - - /// - /// TODO: proper tests for the new accounting - /// - - const checkStat = async ({ depositedValidators, beaconValidators, beaconBalance }) => { - const stat = await app.getBeaconStat() - assertBn(stat.depositedValidators, depositedValidators, 'depositedValidators check') - assertBn(stat.beaconValidators, beaconValidators, 'beaconValidators check') - assertBn(stat.beaconBalance, beaconBalance, 'beaconBalance check') - } - - it('reportBeacon access control', async () => { - await assertRevert(app.handleOracleReport(0, 0, 0, 0, 0, 0, 0, false, { from: stranger }), 'APP_AUTH_FAILED') - }) - - context('with depositedVals=0, beaconVals=0, bcnBal=0, bufferedEth=0', async () => { - it('report BcnValidators:0 BcnBalance:0 = no rewards', async () => { - console.log(consensus.address, oracle.address) - await pushOracleReport(consensus, oracle, 0, 0) - checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) - assertBn(await app.getBufferedEther(), ETH(0)) - assertBn(await app.getTotalPooledEther(), ETH(0)) - // assert.equal(await app.distributeFeeCalled(), false) - // assertBn(await app.totalRewards(), 0) - }) - - it('report BcnValidators:1 = revert', async () => { - await assertRevert(pushOracleReport(consensus, oracle, 1, 0), 'REPORTED_MORE_DEPOSITED') - checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) - assertBn(await app.getBufferedEther(), ETH(0)) - assertBn(await app.getTotalPooledEther(), ETH(0)) - // assert.equal(await app.distributeFeeCalled(), false) - // assertBn(await app.totalRewards(), 0) - }) - }) - - context('with depositedVals=0, beaconVals=0, bcnBal=0, bufferedEth=12', async () => { - it('report BcnValidators:0 BcnBalance:0 = no rewards', async () => { - await pushOracleReport(consensus, oracle, 0, 0) - checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) - assertBn(await app.getBufferedEther(), ETH(12)) - assertBn(await app.getTotalPooledEther(), ETH(12)) - // assert.equal(await app.distributeFeeCalled(), false) - // assertBn(await app.totalRewards(), 0) - - await assertRevert(pushOracleReport(consensus, oracle, 1, 0), 'REPORTED_MORE_DEPOSITED') - checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) - assertBn(await app.getBufferedEther(), ETH(12)) - assertBn(await app.getTotalPooledEther(), ETH(12)) - // assert.equal(await app.distributeFeeCalled(), false) - // assertBn(await app.totalRewards(), 0) - }) - }) - - context('with depositedVals=1, beaconVals=0, bcnBal=0, bufferedEth=3', async () => { - it('initial state before report', async () => { - checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) - assertBn(await app.getBufferedEther(), ETH(3)) - assertBn(await app.getTotalPooledEther(), ETH(35)) - }) - - it('report BcnValidators:0 BcnBalance:0 = no rewards', async () => { - await pushOracleReport(consensus, oracle, 0, 0) - checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) - assertBn(await app.getBufferedEther(), ETH(3)) - assertBn(await app.getTotalPooledEther(), ETH(35)) - // assert.equal(await app.distributeFeeCalled(), false) - // assertBn(await app.totalRewards(), 0) - }) - - it('report BcnValidators:2 = revert', async () => { - await assertRevert( - pushOracleReport(consensus, oracle, 2, ETH(65)), - 'REPORTED_MORE_DEPOSITED' - ) - checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) - assertBn(await app.getBufferedEther(), ETH(3)) - assertBn(await app.getTotalPooledEther(), ETH(35)) - // assert.equal(await app.distributeFeeCalled(), false) - // assertBn(await app.totalRewards(), 0) - }) - - it('report BcnValidators:1 BcnBalance:31 = no rewards', async () => { - await pushOracleReport(consensus, oracle, 1, ETH(31)) - checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(31) }) - assertBn(await app.getBufferedEther(), ETH(3)) - assertBn(await app.getTotalPooledEther(), ETH(34)) - // assert.equal(await app.distributeFeeCalled(), false) - // assertBn(await app.totalRewards(), 0) - }) - - it('report BcnValidators:1 BcnBalance:32 = no rewards', async () => { - await pushOracleReport(consensus, oracle, 1, ETH(32)) - checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(32) }) - assertBn(await app.getBufferedEther(), ETH(3)) - assertBn(await app.getTotalPooledEther(), ETH(35)) - // assert.equal(await app.distributeFeeCalled(), false) - // assertBn(await app.totalRewards(), 0) - }) - }) - - context('with depositedVals=2, beaconVals=1, bcnBal=30, bufferedEth=5', async () => { - beforeEach(async function () { - await app.setDepositedValidators(2) - await app.setBeaconBalance(ETH(30)) - await app.setBufferedEther({ from: stranger, value: ETH(5) }) - await app.setBeaconValidators(1) - await app.setTotalShares(ETH(67)) - }) - - it('initial state before report', async () => { - checkStat({ depositedValidators: 2, beaconValidators: 1, beaconBalance: ETH(30) }) - assertBn(await app.getBufferedEther(), ETH(5)) - assertBn(await app.getTotalPooledEther(), ETH(67)) - }) - - it('report BcnValidators:1 BcnBalance:0 = no rewards', async () => { - await pushOracleReport(consensus, oracle, 1, 0) - checkStat({ depositedValidators: 2, beaconValidators: 1, beaconBalance: ETH(0) }) - assertBn(await app.getBufferedEther(), ETH(5)) - assertBn(await app.getTotalPooledEther(), ETH(37)) - // assert.equal(await app.distributeFeeCalled(), false) - // assertBn(await app.totalRewards(), 0) - }) - - it('report BcnValidators:1 BcnBalance:1 = no rewards', async () => { - await pushOracleReport({epochId: 100, clValidators: 1, clBalance: ETH(1)}) - checkStat({ depositedValidators: 2, beaconValidators: 1, beaconBalance: ETH(1) }) - assertBn(await app.getBufferedEther(), ETH(5)) - assertBn(await app.getTotalPooledEther(), ETH(38)) - // assert.equal(await app.distributeFeeCalled(), false) - // assertBn(await app.totalRewards(), 0) - }) - - it('report BcnValidators:2 BcnBalance:62 = no reward', async () => { - await pushOracleReport({epochId: 100, clValidators: 2, clBalance: ETH(62)}) - checkStat({ depositedValidators: 2, beaconValidators: 2, beaconBalance: ETH(62) }) - assertBn(await app.getBufferedEther(), ETH(5)) - assertBn(await app.getTotalPooledEther(), ETH(67)) - // assert.equal(await app.distributeFeeCalled(), false) - // assertBn(await app.totalRewards(), 0) - }) - - it('report BcnValidators:1 BcnBalance:31 = reward:1', async () => { - await pushOracleReport({epochId: 100, clValidators: 2, clBalance: ETH(63)}) - checkStat({ depositedValidators: 2, beaconValidators: 2, beaconBalance: ETH(63) }) - assertBn(await app.getBufferedEther(), ETH(5)) - assertBn(await app.getTotalPooledEther(), ETH(68)) - // assert.equal(await app.distributeFeeCalled(), true) - // assertBn(await app.totalRewards(), ETH(1)) // rounding error - }) - - it('report BcnValidators:2 BcnBalance:63 = reward:1', async () => { - await pushOracleReport({epochId: 100, clValidators: 2, clBalance: ETH(63)}) - checkStat({ depositedValidators: 2, beaconValidators: 2, beaconBalance: ETH(63) }) - assertBn(await app.getBufferedEther(), ETH(5)) - assertBn(await app.getTotalPooledEther(), ETH(68)) - // assert.equal(await app.distributeFeeCalled(), true) - // assertBn(await app.totalRewards(), ETH(1)) // rounding error - }) - - it('report BcnValidators:3 = revert with REPORTED_MORE_DEPOSITED', async () => { - await assertRevert( - pushOracleReport({epochId: 110, clValidators: 3, clBalance: ETH(65)}), - 'REPORTED_MORE_DEPOSITED' - ) - checkStat({ depositedValidators: 2, beaconValidators: 1, beaconBalance: ETH(30) }) - assertBn(await app.getBufferedEther(), ETH(5)) - assertBn(await app.getTotalPooledEther(), ETH(67)) - // assert.equal(await app.distributeFeeCalled(), false) - // assertBn(await app.totalRewards(), 0) - }) - }) - - context('with depositedVals=5, beaconVals=4, bcnBal=1, bufferedEth=0', async () => { - beforeEach(async function () { - await app.setDepositedValidators(5) - await app.setBeaconBalance(ETH(1)) - await app.setBufferedEther({ from: stranger, value: ETH(0) }) - await app.setBeaconValidators(4) - }) - - // See LIP-1 for explanation - // https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-1.md - it('report decreased BcnValidators:3 = revert with REPORTED_LESS_VALIDATORS', async () => { - await assertRevert( - pushOracleReport(consensus, oracle, 3, ETH(1)), - 'REPORTED_LESS_VALIDATORS' - ) - await assertRevert( - pushOracleReport(consensus, oracle, 2, ETH(10)), - 'REPORTED_LESS_VALIDATORS' - ) - await assertRevert( - pushOracleReport(consensus, oracle, 1, ETH(123)), - 'REPORTED_LESS_VALIDATORS' - ) - // values stay intact - checkStat({ depositedValidators: 5, beaconValidators: 4, beaconBalance: ETH(1) }) - assertBn(await app.getBufferedEther(), ETH(0)) - assertBn(await app.getTotalPooledEther(), ETH(33)) - // assert.equal(await app.distributeFeeCalled(), false) - // assertBn(await app.totalRewards(), 0) - }) - }) -}) diff --git a/test/0.4.24/node-operators-registry-penalty.test.js b/test/0.4.24/node-operators-registry-penalty.test.js index cfb05752b..c21fe47af 100644 --- a/test/0.4.24/node-operators-registry-penalty.test.js +++ b/test/0.4.24/node-operators-registry-penalty.test.js @@ -1,15 +1,12 @@ const hre = require('hardhat') -const { assert } = require('../helpers/assert') -const { assertRevert } = require('../helpers/assertThrow') -const { toBN, padRight, printEvents } = require('../helpers/utils') +const { web3 } = require('hardhat') +const { bn } = require('@aragon/contract-helpers-test') const { BN } = require('bn.js') + +const { assert } = require('../helpers/assert') +const { padRight, ETH, prepIdsCountsPayload } = require('../helpers/utils') const { AragonDAO } = require('./helpers/dao') -const { EvmSnapshot } = require('../helpers/blockchain') -const { ZERO_ADDRESS, getEventAt, bn } = require('@aragon/contract-helpers-test') -const nodeOperators = require('../helpers/node-operators') -const signingKeys = require('../helpers/signing-keys') -const { web3 } = require('hardhat') -const { assertBn } = require('@aragon/contract-helpers-test/src/asserts') +const { EvmSnapshot, advanceChainTime } = require('../helpers/blockchain') const { getRandomLocatorConfig } = require('../helpers/locator') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistryMock') @@ -66,22 +63,8 @@ const NODE_OPERATORS = [ // bytes32 0x63757261746564 const CURATED_TYPE = padRight(web3.utils.fromAscii('curated'), 32) +const PENALTY_DELAY = 2 * 24 * 60 * 60 // 2 days -const pad = (hex, bytesLength) => { - const absentZeroes = bytesLength * 2 + 2 - hex.length - if (absentZeroes > 0) hex = '0x' + '0'.repeat(absentZeroes) + hex.substr(2) - return hex -} - -const hexConcat = (first, ...rest) => { - let result = first.startsWith('0x') ? first : '0x' + first - rest.forEach((item) => { - result += item.startsWith('0x') ? item.substr(2) : item - }) - return result -} - -const ETH = (value) => web3.utils.toWei(value + '', 'ether') const StETH = artifacts.require('StETHMock') contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, user4, no1, treasury]) => { @@ -91,11 +74,9 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use before('deploy base app', async () => { // Deploy the app's base contract. appBase = await NodeOperatorsRegistry.new() - steth = await StETH.new() + steth = await StETH.new({ value: ETH(1) }) - burner = await Burner.new( - voting, treasury, steth.address, bn(0), bn(0), { from: appManager } - ) + burner = await Burner.new(voting, treasury, steth.address, bn(0), bn(0), { from: appManager }) const locatorConfig = getRandomLocatorConfig({ lido: steth.address, @@ -127,15 +108,15 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use // app = await NodeOperatorsRegistry.at(proxyAddress) // Initialize the app's proxy. - const tx = await app.initialize(locator.address, CURATED_TYPE) + const tx = await app.initialize(locator.address, CURATED_TYPE, PENALTY_DELAY) //set stuck penalty voting - await app.setStuckPenaltyDelay(60*60*24*2, { from: voting }) + // await app.setStuckPenaltyDelay(PENALTY_DELAY, { from: voting }) // Implementation initializer reverts because initialization block was set to max(uint256) // in the Autopetrified base contract // await assert.reverts(appBase.initialize(steth.address, CURATED_TYPE), 'INIT_ALREADY_INITIALIZED') - await assertRevert(appBase.initialize(locator.address, CURATED_TYPE), 'INIT_ALREADY_INITIALIZED') + await assert.reverts(appBase.initialize(locator.address, CURATED_TYPE, PENALTY_DELAY), 'INIT_ALREADY_INITIALIZED') const moduleType = await app.getType() assert.emits(tx, 'ContractVersionSet', { version: 2 }) @@ -307,7 +288,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use await steth.setTotalPooledEther(ETH(100)) await steth.mintShares(app.address, ETH(10)) - //update [operator, exited, stuck] + // update [operator, exited, stuck] await app.unsafeUpdateValidatorsCount(firstNodeOperator, 1, 1, { from: voting }) await app.unsafeUpdateValidatorsCount(secondNodeOperator, 1, 0, { from: voting }) @@ -318,9 +299,9 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use await steth.setTotalPooledEther(ETH(100)) await steth.mintShares(app.address, ETH(10)) - //update [operator, exited, stuck] - await app.unsafeUpdateValidatorsCount(firstNodeOperator, 2, 2 , { from: voting }) - await app.unsafeUpdateValidatorsCount(secondNodeOperator, 3, 0 , { from: voting }) + // update [operator, exited, stuck] + await app.unsafeUpdateValidatorsCount(firstNodeOperator, 2, 1, { from: voting }) + await app.unsafeUpdateValidatorsCount(secondNodeOperator, 3, 0, { from: voting }) // perValidator = ETH(10) / 5 = 2 eth @@ -328,7 +309,6 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use // calls distributeRewards() inside receipt = await app.testing__distributeRewards({ from: voting }) - assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user1, sharesAmount: ETH(1) }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user2, sharesAmount: ETH(4*2) }) assert.notEmits(receipt, 'RewardsDistributed', { rewardAddress: user3, sharesAmount: 0 }) @@ -351,8 +331,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use await app.updateRefundedValidatorsCount(firstNodeOperator, 1, { from: voting }) assert.isTrue(await app.testing_isNodeOperatorPenalized(firstNodeOperator)) - await hre.network.provider.send('evm_increaseTime', [2 * 24 * 60 * 60 + 10]) - await hre.network.provider.send('evm_mine') + await advanceChainTime(2 * 24 * 60 * 60 + 10) assert.isFalse(await app.testing_isNodeOperatorPenalized(firstNodeOperator)) @@ -431,7 +410,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use //increase _newActiveValidatorsCount by add new depositedKeys await app.increaseNodeOperatorDepositedSigningKeysCount(3, 2) - await app.updateExitedValidatorsCount(3, 1, { from: voting }) + const { operatorIds, keysCounts } = prepIdsCountsPayload(3, 1) + await app.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }) }) @@ -465,18 +445,18 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use await app.updateTargetValidatorsLimits(0, true, 10, { from: voting }) let keysStatTotal = await app.getStakingModuleSummary() - assert.equal(keysStatTotal.totalExitedValidators, 2) - assert.equal(keysStatTotal.totalDepositedValidators, 25) - assert.equal(keysStatTotal.depositableValidatorsCount, 17) + assert.equal(+keysStatTotal.totalExitedValidators, 2) + assert.equal(+keysStatTotal.totalDepositedValidators, 25) + assert.equal(+keysStatTotal.depositableValidatorsCount, 17) let limitStatOp = await app.getNodeOperatorSummary(0) assert.equal(limitStatOp.isTargetLimitActive, true) - assert.equal(limitStatOp.targetValidatorsCount, 10) + assert.equal(+limitStatOp.targetValidatorsCount, 10) let keysStatOp = await app.getNodeOperatorSummary(0) - assert.equal(keysStatOp.totalExitedValidators, 2) + assert.equal(+keysStatOp.totalExitedValidators, 2) assert.equal(keysStatOp.totalDepositedValidators.toNumber()-keysStatOp.totalExitedValidators.toNumber(), 8) - assert.equal(keysStatOp.depositableValidatorsCount, 2) + assert.equal(+keysStatOp.depositableValidatorsCount, 2) await app.updateTargetValidatorsLimits(0, false, 10, { from: voting }) @@ -526,8 +506,10 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use assert.equal(keysStatOp.depositableValidatorsCount, 0) // console.log(o2n(limitStatOp)) - await app.updateExitedValidatorsCount(0, 3, { from: voting }) - await app.updateExitedValidatorsCount(1, 1, { from: voting }) + const { operatorIds: operatorIds1, keysCounts: keysCounts1 } = prepIdsCountsPayload(0, 3) + const { operatorIds: operatorIds2, keysCounts: keysCounts2 } = prepIdsCountsPayload(1, 1) + await app.updateExitedValidatorsCount(operatorIds1, keysCounts1, { from: voting }) + await app.updateExitedValidatorsCount(operatorIds2, keysCounts2, { from: voting }) keysStatTotal = await app.getStakingModuleSummary() // console.log(o2n(keysStatTotal)) @@ -594,7 +576,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use function o2n(o = {}) { for (const k of Object.keys(o)) { - if (BN.isBN(o[k])) { + if (bn.isBN(o[k])) { o[k] = o[k].toString() } } diff --git a/test/0.4.24/node-operators-registry.test.js b/test/0.4.24/node-operators-registry.test.js index ba8ae291a..57db0b3a5 100644 --- a/test/0.4.24/node-operators-registry.test.js +++ b/test/0.4.24/node-operators-registry.test.js @@ -7,11 +7,12 @@ const { EvmSnapshot } = require('../helpers/blockchain') const { ZERO_ADDRESS, getEventAt } = require('@aragon/contract-helpers-test') const nodeOperators = require('../helpers/node-operators') const signingKeys = require('../helpers/signing-keys') +const { prepIdsCountsPayload } = require('../helpers/utils') const { web3, artifacts } = require('hardhat') const { getRandomLocatorConfig } = require('../helpers/locator') -const { assertBn } = require('@aragon/contract-helpers-test/src/asserts') +const { randomBytes } = require('crypto') +const { toChecksumAddress } = require('ethereumjs-util') -const IStakingModule = artifacts.require('contracts/0.8.9/interfaces/IStakingModule.sol:IStakingModule') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistryMock') const SigningKeys = artifacts.require('SigningKeys') const LidoLocator = artifacts.require('LidoLocator') @@ -60,6 +61,7 @@ const NODE_OPERATORS = [ // bytes32 0x63757261746564 const CURATED_TYPE = padRight(web3.utils.fromAscii('curated'), 32) +const PENALTY_DELAY = 2 * 24 * 60 * 60 // 2 days const pad = (hex, bytesLength) => { const absentZeroes = bytesLength * 2 + 2 - hex.length @@ -85,7 +87,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob before('deploy base app', async () => { // Deploy the app's base contract. appBase = await NodeOperatorsRegistry.new() - steth = await StETH.new() + steth = await StETH.new({ value: ETH(1) }) const locatorConfig = getRandomLocatorConfig({ lido: steth.address }) @@ -111,14 +113,17 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob // const proxyAddress = await newApp(newDAO.dao, 'node-operators-registry', appBase.address, appManager) // app = await NodeOperatorsRegistry.at(proxyAddress) - await assert.reverts(app.finalizeUpgrade_v2(locator.address, CURATED_TYPE), 'CONTRACT_NOT_INITIALIZED_OR_PETRIFIED') + await assert.reverts( + app.finalizeUpgrade_v2(locator.address, CURATED_TYPE, PENALTY_DELAY), + 'CONTRACT_NOT_INITIALIZED' + ) // Initialize the app's proxy. - const tx = await app.initialize(locator.address, CURATED_TYPE) + const tx = await app.initialize(locator.address, CURATED_TYPE, PENALTY_DELAY) // Implementation initializer reverts because initialization block was set to max(uint256) // in the Autopetrified base contract - await assertRevert(appBase.initialize(locator.address, CURATED_TYPE), 'INIT_ALREADY_INITIALIZED') + await assertRevert(appBase.initialize(locator.address, CURATED_TYPE, PENALTY_DELAY), 'INIT_ALREADY_INITIALIZED') const moduleType = await app.getType() assert.emits(tx, 'ContractVersionSet', { version: 2 }) @@ -135,17 +140,17 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob describe('initialize', () => { it('sets module type correctly', async () => { const moduleType = await app.getType() - assert.equal(moduleType, CURATED_TYPE) + assert.equals(moduleType, CURATED_TYPE) }) it('sets locator correctly', async () => { const locatorAddr = await app.getLocator() - assert.equal(locatorAddr, locator.address) + assert.equals(locatorAddr, locator.address) }) it('sets contract version correctly', async () => { const contractVersion = await app.getContractVersion() - assert.equal(contractVersion, 2) + assert.equals(contractVersion, 2) }) it('sets hasInitialized() to true', async () => { @@ -153,18 +158,18 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob }) it("can't be initialized second time", async () => { - await assert.reverts(app.initialize(steth.address, CURATED_TYPE), 'INIT_ALREADY_INITIALIZED') + await assert.reverts(app.initialize(steth.address, CURATED_TYPE, PENALTY_DELAY), 'INIT_ALREADY_INITIALIZED') }) it('reverts with error "ZERO_ADDRESS" when stETH is zero address', async () => { const registry = await dao.newAppInstance({ name: 'new-node-operators-registry', base: appBase }) - await assert.reverts(registry.initialize(ZERO_ADDRESS, CURATED_TYPE), 'ZERO_ADDRESS') + await assert.reverts(registry.initialize(ZERO_ADDRESS, CURATED_TYPE, PENALTY_DELAY), 'ZERO_ADDRESS') }) it('call on implementation reverts with error "INIT_ALREADY_INITIALIZED"', async () => { // Implementation initializer reverts because initialization block was set to max(uint256) // in the Autopetrified base contract - await assert.reverts(appBase.initialize(steth.address, CURATED_TYPE), 'INIT_ALREADY_INITIALIZED') + await assert.reverts(appBase.initialize(steth.address, CURATED_TYPE, PENALTY_DELAY), 'INIT_ALREADY_INITIALIZED') }) }) @@ -174,22 +179,25 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await app.testing_setBaseVersion(0) }) - it('fails with PETRIFIED error when called on implementation', async () => { + it('fails with CONTRACT_NOT_INITIALIZED error when called on implementation', async () => { await assert.reverts( - appBase.finalizeUpgrade_v2(locator.address, CURATED_TYPE), - 'CONTRACT_NOT_INITIALIZED_OR_PETRIFIED' + appBase.finalizeUpgrade_v2(locator.address, CURATED_TYPE, PENALTY_DELAY), + 'CONTRACT_NOT_INITIALIZED' ) }) it('sets correct contract version', async () => { - await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE) + await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE, PENALTY_DELAY) assert.equals(await app.getContractVersion(), 2) }) it('reverts with error UNEXPECTED_CONTRACT_VERSION when called on already initialized contract', async () => { - await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE) + await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE, PENALTY_DELAY) assert.equals(await app.getContractVersion(), 2) - await assert.reverts(app.finalizeUpgrade_v2(locator.address, CURATED_TYPE), 'UNEXPECTED_CONTRACT_VERSION') + await assert.reverts( + app.finalizeUpgrade_v2(locator.address, CURATED_TYPE, PENALTY_DELAY), + 'UNEXPECTED_CONTRACT_VERSION' + ) }) it('sets total signing keys stats correctly', async () => { @@ -216,18 +224,18 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < NODE_OPERATORS.length; ++i) { const nodeOperator = await app.getNodeOperator(i, false) - assert.equal(nodeOperator.totalSigningKeys.toNumber(), NODE_OPERATORS[i].totalSigningKeysCount) - assert.equal(nodeOperator.stakingLimit.toNumber(), NODE_OPERATORS[i].vettedSigningKeysCount) - assert.equal(nodeOperator.usedSigningKeys.toNumber(), NODE_OPERATORS[i].depositedSigningKeysCount) - assert.equal(nodeOperator.stoppedValidators.toNumber(), NODE_OPERATORS[i].exitedSigningKeysCount) + assert.equals(nodeOperator.totalSigningKeys.toNumber(), NODE_OPERATORS[i].totalSigningKeysCount) + assert.equals(nodeOperator.stakingLimit.toNumber(), NODE_OPERATORS[i].vettedSigningKeysCount) + assert.equals(nodeOperator.usedSigningKeys.toNumber(), NODE_OPERATORS[i].depositedSigningKeysCount) + assert.equals(nodeOperator.stoppedValidators.toNumber(), NODE_OPERATORS[i].exitedSigningKeysCount) const nodeOperatorLimits = await app.getNodeOperatorSummary(i) - assert.equal(nodeOperatorLimits.stuckValidatorsCount.toNumber(), NODE_OPERATORS[i].stuckValidatorsCount) - assert.equal(nodeOperatorLimits.refundedValidatorsCount.toNumber(), NODE_OPERATORS[i].refundedValidatorsCount) - assert.equal(nodeOperatorLimits.stuckPenaltyEndTimestamp.toNumber(), NODE_OPERATORS[i].stuckPenaltyEndAt) + assert.equals(nodeOperatorLimits.stuckValidatorsCount.toNumber(), NODE_OPERATORS[i].stuckValidatorsCount) + assert.equals(nodeOperatorLimits.refundedValidatorsCount.toNumber(), NODE_OPERATORS[i].refundedValidatorsCount) + assert.equals(nodeOperatorLimits.stuckPenaltyEndTimestamp.toNumber(), NODE_OPERATORS[i].stuckPenaltyEndAt) } - await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE) + await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE, PENALTY_DELAY) const totalSigningKeysStatsAfter = await app.testing_getTotalSigningKeysStats() @@ -240,10 +248,10 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const depositedSigningKeysCount = NODE_OPERATORS.reduce((sum, c) => sum + c.depositedSigningKeysCount, 0) const exitedSigningKeysCount = NODE_OPERATORS.reduce((sum, c) => sum + c.exitedSigningKeysCount, 0) - assert.equal(totalSigningKeysStatsAfter.totalSigningKeysCount.toNumber(), totalSigningKeysCount) - assert.equal(totalSigningKeysStatsAfter.vettedSigningKeysCount.toNumber(), vettedSigningKeysCount) - assert.equal(totalSigningKeysStatsAfter.depositedSigningKeysCount.toNumber(), depositedSigningKeysCount) - assert.equal(totalSigningKeysStatsAfter.exitedSigningKeysCount.toNumber(), exitedSigningKeysCount) + assert.equals(totalSigningKeysStatsAfter.totalSigningKeysCount.toNumber(), totalSigningKeysCount) + assert.equals(totalSigningKeysStatsAfter.vettedSigningKeysCount.toNumber(), vettedSigningKeysCount) + assert.equals(totalSigningKeysStatsAfter.depositedSigningKeysCount.toNumber(), depositedSigningKeysCount) + assert.equals(totalSigningKeysStatsAfter.exitedSigningKeysCount.toNumber(), exitedSigningKeysCount) }) it("trims vettedSigningKeys if it's greater than totalSigningKeys", async () => { @@ -275,14 +283,14 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob ) let nodeOperator = await app.getNodeOperator(0, false) - assert.equal(nodeOperator.stakingLimit.toNumber(), config.vettedSigningKeysCount) - assert.equal(nodeOperator.totalSigningKeys.toNumber(), config.totalSigningKeysCount) + assert.equals(nodeOperator.stakingLimit.toNumber(), config.vettedSigningKeysCount) + assert.equals(nodeOperator.totalSigningKeys.toNumber(), config.totalSigningKeysCount) - await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE) + await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE, PENALTY_DELAY) nodeOperator = await app.getNodeOperator(0, false) - assert.equal(nodeOperator.stakingLimit.toNumber(), config.totalSigningKeysCount) - assert.equal(nodeOperator.totalSigningKeys.toNumber(), config.totalSigningKeysCount) + assert.equals(nodeOperator.stakingLimit.toNumber(), config.totalSigningKeysCount) + assert.equals(nodeOperator.totalSigningKeys.toNumber(), config.totalSigningKeysCount) }) it("trims vettedSigningKeys if it's greater than depositedSigningKeysCount", async () => { @@ -315,36 +323,36 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob ) let nodeOperator = await app.getNodeOperator(0, false) - assert.equal(nodeOperator.stakingLimit.toNumber(), config.vettedSigningKeysCount) - assert.equal(nodeOperator.totalSigningKeys.toNumber(), config.totalSigningKeysCount) + assert.equals(nodeOperator.stakingLimit.toNumber(), config.vettedSigningKeysCount) + assert.equals(nodeOperator.totalSigningKeys.toNumber(), config.totalSigningKeysCount) - await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE) + await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE, PENALTY_DELAY) nodeOperator = await app.getNodeOperator(0, false) - assert.equal(nodeOperator.stakingLimit.toNumber(), config.depositedSigningKeysCount) - assert.equal(nodeOperator.totalSigningKeys.toNumber(), config.totalSigningKeysCount) + assert.equals(nodeOperator.stakingLimit.toNumber(), config.depositedSigningKeysCount) + assert.equals(nodeOperator.totalSigningKeys.toNumber(), config.totalSigningKeysCount) }) it('emits ContractVersionSet event with correct params', async () => { - const receipt = await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE) + const receipt = await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE, PENALTY_DELAY) assert.emits(receipt, 'ContractVersionSet', { version: 2 }) }) it('emits LocatorContractSet event with correct params', async () => { - const receipt = await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE) + const receipt = await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE, PENALTY_DELAY) assert.emits(receipt, 'LocatorContractSet', { locatorAddress: locator.address }) }) it('emits StakingModuleTypeSet event with correct params', async () => { - const receipt = await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE) + const receipt = await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE, PENALTY_DELAY) const moduleType = await app.getType() assert.emits(receipt, 'StakingModuleTypeSet', { moduleType }) }) it('increases keysOpIndex & changes nonce', async () => { const [keysOpIndexBefore, nonceBefore] = await Promise.all([app.getKeysOpIndex(), app.getNonce()]) - await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE) + await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE, PENALTY_DELAY) const [keysOpIndexAfter, nonceAfter] = await Promise.all([app.getKeysOpIndex(), app.getNonce()]) assert.equals(keysOpIndexAfter, keysOpIndexBefore.toNumber() + 1) assert.notEquals(nonceAfter, nonceBefore) @@ -352,7 +360,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it('emits KeysOpIndexSet & NonceChanged', async () => { const keysOpIndexBefore = await app.getKeysOpIndex() - const receipt = await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE) + const receipt = await app.finalizeUpgrade_v2(locator.address, CURATED_TYPE, PENALTY_DELAY) const nonceAfter = await app.getNonce() assert.emits(receipt, 'KeysOpIndexSet', { keysOpIndex: keysOpIndexBefore.toNumber() + 1 }) assert.emits(receipt, 'NonceChanged', { nonce: nonceAfter }) @@ -419,12 +427,12 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const nodeOperator = await app.getNodeOperator(expectedNodeOperatorId, true) assert.isTrue(nodeOperator.active) - assert.equal(nodeOperator.name, name) - assert.equal(nodeOperator.rewardAddress, ADDRESS_1) - assert.equal(nodeOperator.stakingLimit, 0) - assert.equal(nodeOperator.stoppedValidators, 0) - assert.equal(nodeOperator.totalSigningKeys, 0) - assert.equal(nodeOperator.usedSigningKeys, 0) + assert.equals(nodeOperator.name, name) + assert.equals(nodeOperator.rewardAddress, ADDRESS_1) + assert.equals(nodeOperator.stakingLimit, 0) + assert.equals(nodeOperator.stoppedValidators, 0) + assert.equals(nodeOperator.totalSigningKeys, 0) + assert.equals(nodeOperator.usedSigningKeys, 0) }) it('returns correct node operator id', async () => { @@ -561,7 +569,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await app.activateNodeOperator(notActiveNodeOperatorId, { from: voting }) const activeNodeOperatorsCountAfter = await app.getActiveNodeOperatorsCount() - assert.equal(activeNodeOperatorsCountAfter.toNumber(), activeNodeOperatorsCountBefore.toNumber() + 1) + assert.equals(activeNodeOperatorsCountAfter.toNumber(), activeNodeOperatorsCountBefore.toNumber() + 1) }) it('emits NodeOperatorActiveSet(activate) event', async () => { @@ -585,7 +593,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const nodeOperatorsAfter = await nodeOperators.getAllNodeOperators(app) - assert.equal(nodeOperatorsBefore.length, nodeOperatorsAfter.length) + assert.equals(nodeOperatorsBefore.length, nodeOperatorsAfter.length) }) it("doesn't change other node operators active state", async () => { @@ -599,9 +607,9 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < nodeOperatorsAfter.length; ++i) { if (nodeOperatorId === i) { - assert.equal(nodeOperatorsBefore[i].active, !nodeOperatorsAfter[i].active) + assert.equals(nodeOperatorsBefore[i].active, !nodeOperatorsAfter[i].active) } else { - assert.equal(nodeOperatorsBefore[i].active, nodeOperatorsAfter[i].active) + assert.equals(nodeOperatorsBefore[i].active, nodeOperatorsAfter[i].active) } } }) @@ -670,7 +678,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await app.deactivateNodeOperator(activeNodeOperatorId, { from: voting }) const activeNodeOperatorsCountAfter = await app.getActiveNodeOperatorsCount() - assert.equal(activeNodeOperatorsCountAfter.toNumber(), activeNodeOperatorsCountBefore.toNumber() - 1) + assert.equals(activeNodeOperatorsCountAfter.toNumber(), activeNodeOperatorsCountBefore.toNumber() - 1) }) it('resets depositable when depositable was greater thatn zero', async () => { @@ -688,7 +696,10 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob 'invariant failed: readyToDepositValidatorsKeysCountBefore <= depositedSigningKeysCount' ) assert.isTrue(nodeOperator.active, 'Invariant Failed: not active') - assert.isTrue(+operatorReportBefore.totalDepositedValidators > 0, 'Invariant Failed: vettedSigningKeysCount === 0') + assert.isTrue( + +operatorReportBefore.totalDepositedValidators > 0, + 'Invariant Failed: vettedSigningKeysCount === 0' + ) await app.deactivateNodeOperator(activeNodeOperatorId, { from: voting }) const [operatorReportAfter, allValidatorsReportAfter] = await Promise.all([ @@ -742,7 +753,10 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob ]) assert.equals(operatorReportBefore.totalDepositedValidators, operatorReportAfter.totalDepositedValidators) - assert.equals(allValidatorsReportBefore.totalDepositedValidators, allValidatorsReportAfter.totalDepositedValidators) + assert.equals( + allValidatorsReportBefore.totalDepositedValidators, + allValidatorsReportAfter.totalDepositedValidators + ) }) it('emits NodeOperatorActiveSet(deactivate) event with correct params', async () => { @@ -840,7 +854,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const { name: anotherNodeOperatorNameBefore } = await app.getNodeOperator(anotherNodeOperatorId, true) await app.setNodeOperatorName(nodeOperatorId, newName, { from: voting }) const { name: anotherNodeOperatorNameAfter } = await app.getNodeOperator(anotherNodeOperatorId, true) - assert.equal(anotherNodeOperatorNameBefore, anotherNodeOperatorNameAfter) + assert.equals(anotherNodeOperatorNameBefore, anotherNodeOperatorNameAfter) }) }) @@ -890,7 +904,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob assert.notEqual(rewardAddressBefore, ADDRESS_4) await app.setNodeOperatorRewardAddress(firstNodeOperatorId, ADDRESS_4, { from: voting }) const { rewardAddress: rewardAddressAfter } = await app.getNodeOperator(firstNodeOperatorId, false) - assert.equal(rewardAddressAfter, ADDRESS_4) + assert.equals(rewardAddressAfter, ADDRESS_4) }) it('emits "NodeOperatorRewardAddressSet" event with correct params', async () => { @@ -911,7 +925,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob secondNodeOperatorId, true ) - assert.equal(secondNodeOperatorRewardAddressAfter, secondNodeOperatorRewardAddressBefore) + assert.equals(secondNodeOperatorRewardAddressAfter, secondNodeOperatorRewardAddressBefore) }) }) @@ -1002,14 +1016,14 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const { vettedSigningKeysCount: vettedSigningKeysCountBefore } = await app.testing_getTotalSigningKeysStats() await app.setNodeOperatorStakingLimit(firstNodeOperatorId, 30, { from: voting }) const { vettedSigningKeysCount: vettedSigningKeysCountAfter } = await app.testing_getTotalSigningKeysStats() - assert.equal(vettedSigningKeysCountBefore.toNumber() - vettedSigningKeysCountAfter.toNumber(), 20) + assert.equals(vettedSigningKeysCountBefore.toNumber() - vettedSigningKeysCountAfter.toNumber(), 20) }) it('increases total vetted validator keys count correctly if new value greater than previous', async () => { const { vettedSigningKeysCount: vettedSigningKeysCountBefore } = await app.testing_getTotalSigningKeysStats() await app.setNodeOperatorStakingLimit(firstNodeOperatorId, 100, { from: voting }) const { vettedSigningKeysCount: vettedSigningKeysCountAfter } = await app.testing_getTotalSigningKeysStats() - assert.equal(vettedSigningKeysCountAfter.toNumber() - vettedSigningKeysCountBefore.toNumber(), 50) + assert.equals(vettedSigningKeysCountAfter.toNumber() - vettedSigningKeysCountBefore.toNumber(), 50) }) it('emits VettedSigningKeysCountChanged event with correct params', async () => { @@ -1064,25 +1078,41 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it('reverts with "OUT_OF_RANGE" error when called on non existent validator', async () => { const hasPermission = await dao.hasPermission(voting, app, 'STAKING_ROUTER_ROLE') assert.isTrue(hasPermission) - await assert.reverts(app.updateExitedValidatorsCount(notExistedNodeOperatorId, 40, { from: voting }), 'OUT_OF_RANGE') + const { operatorIds, keysCounts } = prepIdsCountsPayload(notExistedNodeOperatorId, 40) + await assert.reverts(app.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }), 'OUT_OF_RANGE') }) it('reverts with "APP_AUTH_FAILED" error when called by sender without STAKING_ROUTER_ROLE', async () => { const hasPermission = await dao.hasPermission(nobody, app, 'STAKING_ROUTER_ROLE') assert.isFalse(hasPermission) - await assert.reverts(app.updateExitedValidatorsCount(firstNodeOperatorId, 40, { from: nobody }), 'APP_AUTH_FAILED') + const { operatorIds, keysCounts } = prepIdsCountsPayload(firstNodeOperatorId, 40) + await assert.reverts( + app.updateExitedValidatorsCount(operatorIds, keysCounts, { from: nobody }), + 'APP_AUTH_FAILED' + ) }) it("doesn't change the state when new value is equal to the previous one", async () => { - const { stoppedValidators: exitedValidatorsKeysCountBefore } = await app.getNodeOperator(firstNodeOperatorId, false) - await app.updateExitedValidatorsCount(firstNodeOperatorId, exitedValidatorsKeysCountBefore, { from: voting }) - const { stoppedValidators: exitedValidatorsKeysCountAfter } = await app.getNodeOperator(firstNodeOperatorId, false) + const { stoppedValidators: exitedValidatorsKeysCountBefore } = await app.getNodeOperator( + firstNodeOperatorId, + false + ) + const { operatorIds, keysCounts } = prepIdsCountsPayload(firstNodeOperatorId, exitedValidatorsKeysCountBefore) + await app.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }) + const { stoppedValidators: exitedValidatorsKeysCountAfter } = await app.getNodeOperator( + firstNodeOperatorId, + false + ) assert.equals(exitedValidatorsKeysCountBefore, exitedValidatorsKeysCountAfter) }) it("doesn't emit ExitedSigningKeysCountChanged event when new value is equal to the previous one", async () => { - const { stoppedValidators: exitedValidatorsKeysCountBefore } = await app.getNodeOperator(firstNodeOperatorId, false) - const receipt = await app.updateExitedValidatorsCount(firstNodeOperatorId, exitedValidatorsKeysCountBefore, { from: voting }) + const { stoppedValidators: exitedValidatorsKeysCountBefore } = await app.getNodeOperator( + firstNodeOperatorId, + false + ) + const { operatorIds, keysCounts } = prepIdsCountsPayload(firstNodeOperatorId, exitedValidatorsKeysCountBefore) + const receipt = await app.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }) assert.notEmits(receipt, 'ExitedSigningKeysCountChanged') }) @@ -1090,18 +1120,17 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const newExitedValidatorsCount = 1000 const nodeOperator = await app.getNodeOperator(firstNodeOperatorId, false) assert(newExitedValidatorsCount > nodeOperator.usedSigningKeys.toNumber()) - await assert.reverts( - app.updateExitedValidatorsCount(firstNodeOperatorId, newExitedValidatorsCount, { from: voting }), - 'OUT_OF_RANGE' - ) + const { operatorIds, keysCounts } = prepIdsCountsPayload(firstNodeOperatorId, newExitedValidatorsCount) + await assert.reverts(app.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }), 'OUT_OF_RANGE') }) it('reverts with "EXITED_VALIDATORS_COUNT_DECREASED" error when new exitedValidatorsKeysCount less then current one', async () => { const nodeOperator = await app.getNodeOperator(firstNodeOperatorId, false) assert(nodeOperator.stoppedValidators.toNumber() > 0, 'invariant failed: no exited validators') const newExitedValidatorsKeysCount = nodeOperator.stoppedValidators.toNumber() - 1 + const { operatorIds, keysCounts } = prepIdsCountsPayload(firstNodeOperatorId, newExitedValidatorsKeysCount) await assert.reverts( - app.updateExitedValidatorsCount(firstNodeOperatorId, newExitedValidatorsKeysCount, { from: voting }), + app.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }), 'EXITED_VALIDATORS_COUNT_DECREASED' ) }) @@ -1113,16 +1142,26 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob false ) assert.notEquals(exitedValidatorsKeysCountBefore, newExitedValidatorsCount) - await app.updateExitedValidatorsCount(secondNodeOperatorId, newExitedValidatorsCount, { from: voting }) - const { stoppedValidators: exitedValidatorsKeysCountAfter } = await app.getNodeOperator(secondNodeOperatorId, false) + const { operatorIds, keysCounts } = prepIdsCountsPayload(secondNodeOperatorId, newExitedValidatorsCount) + await app.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }) + const { stoppedValidators: exitedValidatorsKeysCountAfter } = await app.getNodeOperator( + secondNodeOperatorId, + false + ) assert.equals(exitedValidatorsKeysCountAfter, newExitedValidatorsCount) }) it('increases the total exited signing keys count', async () => { const newExitedValidatorsCount = 4 - const [{ stoppedValidators: exitedValidatorsKeysCountBefore }, { exitedSigningKeysCount: exitedSigningKeysCountBefore }] = - await Promise.all([await app.getNodeOperator(firstNodeOperatorId, false), app.testing_getTotalSigningKeysStats()]) - await app.updateExitedValidatorsCount(firstNodeOperatorId, newExitedValidatorsCount, { from: voting }) + const [ + { stoppedValidators: exitedValidatorsKeysCountBefore }, + { exitedSigningKeysCount: exitedSigningKeysCountBefore } + ] = await Promise.all([ + await app.getNodeOperator(firstNodeOperatorId, false), + app.testing_getTotalSigningKeysStats() + ]) + const { operatorIds, keysCounts } = prepIdsCountsPayload(firstNodeOperatorId, newExitedValidatorsCount) + await app.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }) const exitedSigningKeysCountIncrement = newExitedValidatorsCount - exitedValidatorsKeysCountBefore.toNumber() const { exitedSigningKeysCount: exitedSigningKeysCountAfter } = await app.testing_getTotalSigningKeysStats() assert.equals( @@ -1133,7 +1172,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it('emits ExitedSigningKeysCountChanged event with correct params', async () => { const newExitedValidatorsCount = 4 - const receipt = await app.updateExitedValidatorsCount(firstNodeOperatorId, newExitedValidatorsCount, { from: voting }) + const { operatorIds, keysCounts } = prepIdsCountsPayload(firstNodeOperatorId, newExitedValidatorsCount) + const receipt = await app.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }) assert.emits(receipt, 'ExitedSigningKeysCountChanged', { nodeOperatorId: firstNodeOperatorId, exitedValidatorsCount: newExitedValidatorsCount @@ -1142,8 +1182,12 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it("doesn't change the exited signing keys count of other node operators", async () => { const newExitedValidatorsCount = 4 - const { stakingLimit: secondNodeOperatorStakingLimitBefore } = await app.getNodeOperator(firstNodeOperatorId, true) - await app.updateExitedValidatorsCount(secondNodeOperatorId, newExitedValidatorsCount, { from: voting }) + const { stakingLimit: secondNodeOperatorStakingLimitBefore } = await app.getNodeOperator( + firstNodeOperatorId, + true + ) + const { operatorIds, keysCounts } = prepIdsCountsPayload(secondNodeOperatorId, newExitedValidatorsCount) + await app.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }) const { stakingLimit: secondNodeOperatorStakingLimitAfter } = await app.getNodeOperator(firstNodeOperatorId, true) assert.equals(secondNodeOperatorStakingLimitAfter, secondNodeOperatorStakingLimitBefore) }) @@ -1153,23 +1197,23 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const firstNodeOperatorId = 0 const secondNodeOperatorId = 1 const notExistedNodeOperatorId = 2 - const exitedValidatorsCount = 4 + const exitedSigningKeysCount = 3 const stuckValidatorsCount = 2 beforeEach(async () => { - await nodeOperators.addNodeOperator(app, { ...NODE_OPERATORS[0], exitedSigningKeysCount: 4 }, { from: voting }) + await nodeOperators.addNodeOperator(app, { ...NODE_OPERATORS[0], exitedSigningKeysCount }, { from: voting }) await nodeOperators.addNodeOperator(app, NODE_OPERATORS[1], { from: voting }) }) it('decreases the stuck validators count when new value is less then previous one', async () => { const newStuckValidatorsCount = 1 - await app.unsafeUpdateValidatorsCount(firstNodeOperatorId, exitedValidatorsCount, stuckValidatorsCount, { + await app.unsafeUpdateValidatorsCount(firstNodeOperatorId, exitedSigningKeysCount, stuckValidatorsCount, { from: voting }) const { stuckValidatorsCount: stuckValidatorsCountBefore } = await app.getNodeOperatorSummary(firstNodeOperatorId) assert(newStuckValidatorsCount < stuckValidatorsCountBefore) - await app.unsafeUpdateValidatorsCount(firstNodeOperatorId, exitedValidatorsCount, newStuckValidatorsCount, { + await app.unsafeUpdateValidatorsCount(firstNodeOperatorId, exitedSigningKeysCount, newStuckValidatorsCount, { from: voting }) const { stuckValidatorsCount: stuckValidatorsCountAfter } = await app.getNodeOperatorSummary(firstNodeOperatorId) @@ -1178,24 +1222,26 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it('increases the stuck validators count when new value is greater then previous one', async () => { const newStuckValidatorsCount = 3 - const { stuckValidatorsCount: stuckValidatorsCountBefore } = await app.getNodeOperatorSummary(secondNodeOperatorId) + const { stuckValidatorsCount: stuckValidatorsCountBefore } = await app.getNodeOperatorSummary( + secondNodeOperatorId + ) assert(newStuckValidatorsCount > stuckValidatorsCountBefore) - await app.unsafeUpdateValidatorsCount(secondNodeOperatorId, exitedValidatorsCount, newStuckValidatorsCount, { + await app.unsafeUpdateValidatorsCount(secondNodeOperatorId, exitedSigningKeysCount, newStuckValidatorsCount, { from: voting }) const { stuckValidatorsCount: stuckValidatorsCountAfter } = await app.getNodeOperatorSummary(secondNodeOperatorId) assert.equals(stuckValidatorsCountAfter, newStuckValidatorsCount) }) - it('emits StuckValidatorsCountChanged event with correct params', async () => { - const newStuckValidatorsCount = 3 + it('emits StuckPenaltyStateChanged event with correct params', async () => { + const newStuckValidatorsCount = 2 const receipt = await app.unsafeUpdateValidatorsCount( firstNodeOperatorId, - exitedValidatorsCount, + exitedSigningKeysCount, newStuckValidatorsCount, { from: voting } ) - assert.emits(receipt, 'StuckValidatorsCountChanged', { + assert.emits(receipt, 'StuckPenaltyStateChanged', { nodeOperatorId: firstNodeOperatorId, stuckValidatorsCount: newStuckValidatorsCount }) @@ -1203,30 +1249,25 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it("doesn't change the state when new stuck validators value is equal to the previous one", async () => { const { stuckValidatorsCount: stuckValidatorsCountBefore } = await app.getNodeOperatorSummary(firstNodeOperatorId) - await app.unsafeUpdateValidatorsCount( - firstNodeOperatorId, - exitedValidatorsCount, - stuckValidatorsCountBefore, - { - from: voting - } - ) + await app.unsafeUpdateValidatorsCount(firstNodeOperatorId, exitedSigningKeysCount, stuckValidatorsCountBefore, { + from: voting + }) const { stuckValidatorsCount: stuckValidatorsCountAfter } = await app.getNodeOperatorSummary(firstNodeOperatorId) assert.equals(stuckValidatorsCountBefore, stuckValidatorsCountAfter) }) - it("doesn't emit StuckValidatorsCountChanged event when new value is equal to the previous one", async () => { + it("doesn't emit StuckPenaltyStateChanged event when new value is equal to the previous one", async () => { const { stuckValidatorsCount: stuckValidatorsCountBefore } = await app.getNodeOperatorSummary(firstNodeOperatorId) const receipt = await app.unsafeUpdateValidatorsCount( firstNodeOperatorId, - exitedValidatorsCount, + exitedSigningKeysCount, stuckValidatorsCountBefore, { from: voting } ) - assert.notEmits(receipt, 'StuckValidatorsCountChanged') + assert.notEmits(receipt, 'StuckPenaltyStateChanged') }) it("doesn't change the stuck validators count of other node operators", async () => { @@ -1234,7 +1275,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const { stuckValidatorsCount: secondNodeOperatorStuckValidatorsCountBefore } = await app.getNodeOperatorSummary( firstNodeOperatorId ) - await app.unsafeUpdateValidatorsCount(secondNodeOperatorId, exitedValidatorsCount, newStuckValidatorsCount, { + await app.unsafeUpdateValidatorsCount(secondNodeOperatorId, exitedSigningKeysCount, newStuckValidatorsCount, { from: voting }) const { stuckValidatorsCount: secondNodeOperatorStuckValidatorsCountAfter } = await app.getNodeOperatorSummary( @@ -1243,12 +1284,12 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob assert.equals(secondNodeOperatorStuckValidatorsCountAfter, secondNodeOperatorStuckValidatorsCountBefore) }) - it('reverts with "OUT_OF_RANGE" error when new exitedValidatorsKeysCount < stuckValidatorsCount', async () => { + it('reverts with "OUT_OF_RANGE" error when new stuckValidatorsCount > depositedValidatorsKeysCount - exitedValidatorsKeysCount', async () => { const newStuckValidatorsCount = 1000 const { stuckValidatorsCount } = await app.getNodeOperatorSummary(firstNodeOperatorId) assert(newStuckValidatorsCount > stuckValidatorsCount) await assert.reverts( - app.unsafeUpdateValidatorsCount(firstNodeOperatorId, exitedValidatorsCount, newStuckValidatorsCount, { + app.unsafeUpdateValidatorsCount(firstNodeOperatorId, exitedSigningKeysCount, newStuckValidatorsCount, { from: voting }), 'OUT_OF_RANGE' @@ -1267,21 +1308,43 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it('reverts with "APP_AUTH_FAILED" error when called by sender without STAKING_ROUTER_ROLE', async () => { const hasPermission = await dao.hasPermission(nobody, app, 'STAKING_ROUTER_ROLE') assert.isFalse(hasPermission) - await assert.reverts(app.unsafeUpdateValidatorsCount(firstNodeOperatorId, 40, stuckValidatorsCount, { from: nobody }), 'APP_AUTH_FAILED') + await assert.reverts( + app.unsafeUpdateValidatorsCount(firstNodeOperatorId, 40, stuckValidatorsCount, { from: nobody }), + 'APP_AUTH_FAILED' + ) }) it("doesn't change the state when new value is equal to the previous one", async () => { - const { stoppedValidators: exitedValidatorsKeysCountBefore } = await app.getNodeOperator(firstNodeOperatorId, false) - await app.unsafeUpdateValidatorsCount(firstNodeOperatorId, exitedValidatorsKeysCountBefore, stuckValidatorsCount, { from: voting }) - const { stoppedValidators: exitedValidatorsKeysCountAfter } = await app.getNodeOperator(firstNodeOperatorId, false) + const { stoppedValidators: exitedValidatorsKeysCountBefore } = await app.getNodeOperator( + firstNodeOperatorId, + false + ) + await app.unsafeUpdateValidatorsCount( + firstNodeOperatorId, + exitedValidatorsKeysCountBefore, + stuckValidatorsCount, + { from: voting } + ) + const { stoppedValidators: exitedValidatorsKeysCountAfter } = await app.getNodeOperator( + firstNodeOperatorId, + false + ) assert.equals(exitedValidatorsKeysCountBefore, exitedValidatorsKeysCountAfter) }) it("doesn't emit ExitedSigningKeysCountChanged event when new value is equal to the previous one", async () => { - const { stoppedValidators: exitedValidatorsKeysCountBefore } = await app.getNodeOperator(firstNodeOperatorId, false) - const receipt = await app.unsafeUpdateValidatorsCount(firstNodeOperatorId, exitedValidatorsKeysCountBefore, stuckValidatorsCount, { - from: voting - }) + const { stoppedValidators: exitedValidatorsKeysCountBefore } = await app.getNodeOperator( + firstNodeOperatorId, + false + ) + const receipt = await app.unsafeUpdateValidatorsCount( + firstNodeOperatorId, + exitedValidatorsKeysCountBefore, + stuckValidatorsCount, + { + from: voting + } + ) assert.notEmits(receipt, 'ExitedSigningKeysCountChanged') }) @@ -1290,20 +1353,28 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const nodeOperator = await app.getNodeOperator(firstNodeOperatorId, false) assert(newExitedValidatorsCount > nodeOperator.usedSigningKeys.toNumber()) await assert.reverts( - app.unsafeUpdateValidatorsCount(firstNodeOperatorId, newExitedValidatorsCount, stuckValidatorsCount, { from: voting }), + app.unsafeUpdateValidatorsCount(firstNodeOperatorId, newExitedValidatorsCount, stuckValidatorsCount, { + from: voting + }), 'OUT_OF_RANGE' ) }) it('decreases the exited validators count when new value is less then previous one', async () => { - const newExitedValidatorsCount = 2 + const newExitedValidatorsCount = 1 + const newStuckValidatorsCount = 0 const { stoppedValidators: exitedValidatorsKeysCountBefore } = await app.getNodeOperator( firstNodeOperatorId, false ) assert(newExitedValidatorsCount < exitedValidatorsKeysCountBefore.toNumber()) - await app.unsafeUpdateValidatorsCount(firstNodeOperatorId, newExitedValidatorsCount, stuckValidatorsCount, { from: voting }) - const { stoppedValidators: exitedValidatorsKeysCountAfter } = await app.getNodeOperator(firstNodeOperatorId, false) + await app.unsafeUpdateValidatorsCount(firstNodeOperatorId, newExitedValidatorsCount, newStuckValidatorsCount, { + from: voting + }) + const { stoppedValidators: exitedValidatorsKeysCountAfter } = await app.getNodeOperator( + firstNodeOperatorId, + false + ) assert.equals(exitedValidatorsKeysCountAfter, newExitedValidatorsCount) }) @@ -1314,22 +1385,29 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob false ) assert(newExitedValidatorsCount > exitedValidatorsKeysCountBefore.toNumber()) - await app.unsafeUpdateValidatorsCount(secondNodeOperatorId, newExitedValidatorsCount, stuckValidatorsCount, { from: voting }) - const { stoppedValidators: exitedValidatorsKeysCountAfter } = await app.getNodeOperator(secondNodeOperatorId, false) + await app.unsafeUpdateValidatorsCount(secondNodeOperatorId, newExitedValidatorsCount, stuckValidatorsCount, { + from: voting + }) + const { stoppedValidators: exitedValidatorsKeysCountAfter } = await app.getNodeOperator( + secondNodeOperatorId, + false + ) assert.equals(exitedValidatorsKeysCountAfter, newExitedValidatorsCount) }) it('decreases the total exited signing keys count when new value is less then previous one', async () => { - const newExitedValidatorsCount = 3 + const newExitedValidatorsCount = 1 const [ { stoppedValidators: exitedValidatorsKeysCountBefore }, { exitedSigningKeysCount: exitedSigningKeysCountBefore } ] = await Promise.all([ - await app.getNodeOperator(firstNodeOperatorId, false), + app.getNodeOperator(firstNodeOperatorId, false), app.testing_getTotalSigningKeysStats() ]) assert(newExitedValidatorsCount < exitedValidatorsKeysCountBefore.toNumber()) - await app.unsafeUpdateValidatorsCount(firstNodeOperatorId, newExitedValidatorsCount, stuckValidatorsCount, { from: voting }) + await app.unsafeUpdateValidatorsCount(firstNodeOperatorId, newExitedValidatorsCount, stuckValidatorsCount, { + from: voting + }) const exitedSigningKeysCountIncrement = exitedValidatorsKeysCountBefore.toNumber() - newExitedValidatorsCount const { exitedSigningKeysCount: exitedSigningKeysCountAfter } = await app.testing_getTotalSigningKeysStats() assert.equals( @@ -1340,15 +1418,18 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it('increases the total exited signing keys count when new value is greater then previous one', async () => { const newExitedValidatorsCount = 5 + const newStuckValidatorsCount = 0 const [ { stoppedValidators: exitedValidatorsKeysCountBefore }, { exitedSigningKeysCount: exitedSigningKeysCountBefore } ] = await Promise.all([ - await app.getNodeOperator(firstNodeOperatorId, false), + app.getNodeOperator(firstNodeOperatorId, false), app.testing_getTotalSigningKeysStats() ]) assert(newExitedValidatorsCount > exitedValidatorsKeysCountBefore.toNumber()) - await app.unsafeUpdateValidatorsCount(firstNodeOperatorId, newExitedValidatorsCount, stuckValidatorsCount, { from: voting }) + await app.unsafeUpdateValidatorsCount(firstNodeOperatorId, newExitedValidatorsCount, newStuckValidatorsCount, { + from: voting + }) const exitedSigningKeysCountIncrement = newExitedValidatorsCount - exitedValidatorsKeysCountBefore.toNumber() const { exitedSigningKeysCount: exitedSigningKeysCountAfter } = await app.testing_getTotalSigningKeysStats() assert.equals( @@ -1360,23 +1441,29 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it('emits ExitedSigningKeysCountChanged event with correct params', async () => { const newExitedValidatorsCount = 2 const receipt = await app.unsafeUpdateValidatorsCount( - firstNodeOperatorId, newExitedValidatorsCount, stuckValidatorsCount, { from: voting } + secondNodeOperatorId, + newExitedValidatorsCount, + stuckValidatorsCount, + { from: voting } ) assert.emits(receipt, 'ExitedSigningKeysCountChanged', { - nodeOperatorId: firstNodeOperatorId, + nodeOperatorId: secondNodeOperatorId, exitedValidatorsCount: newExitedValidatorsCount }) }) it("doesn't change the exited signing keys count of other node operators", async () => { const newExitedValidatorsCount = 4 - const { stakingLimit: secondNodeOperatorStakingLimitBefore } = await app.getNodeOperator(firstNodeOperatorId, true) - await app.unsafeUpdateValidatorsCount( - secondNodeOperatorId, newExitedValidatorsCount, stuckValidatorsCount, { from: voting } + const { stakingLimit: secondNodeOperatorStakingLimitBefore } = await app.getNodeOperator( + firstNodeOperatorId, + true ) await app.unsafeUpdateValidatorsCount(secondNodeOperatorId, newExitedValidatorsCount, stuckValidatorsCount, { from: voting }) + await app.unsafeUpdateValidatorsCount(secondNodeOperatorId, newExitedValidatorsCount, stuckValidatorsCount, { + from: voting + }) const { stakingLimit: secondNodeOperatorStakingLimitAfter } = await app.getNodeOperator(firstNodeOperatorId, true) assert.equals(secondNodeOperatorStakingLimitAfter, secondNodeOperatorStakingLimitBefore) }) @@ -1460,8 +1547,14 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it('sets total vetted signing keys count & total signing keys count values to deposited signing keys count', async () => { const totalSigningKeysStatsBefore = await app.testing_getTotalSigningKeysStats() - assert.notEquals(totalSigningKeysStatsBefore.vettedSigningKeysCount, totalSigningKeysStatsBefore.depositedSigningKeysCount) - assert.notEquals(totalSigningKeysStatsBefore.totalSigningKeysCount, totalSigningKeysStatsBefore.depositedSigningKeysCount) + assert.notEquals( + totalSigningKeysStatsBefore.vettedSigningKeysCount, + totalSigningKeysStatsBefore.depositedSigningKeysCount + ) + assert.notEquals( + totalSigningKeysStatsBefore.totalSigningKeysCount, + totalSigningKeysStatsBefore.depositedSigningKeysCount + ) await app.onWithdrawalCredentialsChanged({ from: voting }) const totalSigningKeysStatsAfter = await app.testing_getTotalSigningKeysStats() assert.equals( @@ -1494,7 +1587,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob // invalidated all keys before the test to remove all unused keys of node operators await app.onWithdrawalCredentialsChanged({ from: voting }) // the second invalidation must not invalidate keys - const receipt = app.onWithdrawalCredentialsChanged({ from: voting }) + const receipt = await app.onWithdrawalCredentialsChanged({ from: voting }) const nonceBefore = await app.getNonce() assert.notEmits(receipt, 'NodeOperatorTotalKeysTrimmed') const nonceAfter = await app.getNonce() @@ -1510,48 +1603,48 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await nodeOperators.addNodeOperator(app, { ...NODE_OPERATORS[1], depositedSigningKeysCount: 5 }, { from: voting }) }) - it('_getCorrectedNodeOperator() - deposited < target < vetted', async () => { - let firstNodeOperatorKeysStats = await app.testing_getCorrectedNodeOperator(firstNodeOperatorId) + it('_getCorrectedNodeOperator() - deposited < exited+target < vetted', async () => { + let firstNodeOperatorKeysStats = await app.testing_getNodeOperator(firstNodeOperatorId) - assert.equals(+firstNodeOperatorKeysStats.vettedSigningKeysCount, 8) + assert.equals(+firstNodeOperatorKeysStats.maxSigningKeysCount, 8) assert.equals(+firstNodeOperatorKeysStats.depositedSigningKeysCount, 5) assert.equals(+firstNodeOperatorKeysStats.exitedSigningKeysCount, 1) await app.updateTargetValidatorsLimits(firstNodeOperatorId, true, 6, { from: voting }) - firstNodeOperatorKeysStats = await app.testing_getCorrectedNodeOperator(firstNodeOperatorId) - assert.equals(+firstNodeOperatorKeysStats.vettedSigningKeysCount, 7) + firstNodeOperatorKeysStats = await app.testing_getNodeOperator(firstNodeOperatorId) + assert.equals(+firstNodeOperatorKeysStats.maxSigningKeysCount, 7) assert.equals(+firstNodeOperatorKeysStats.depositedSigningKeysCount, 5) assert.equals(+firstNodeOperatorKeysStats.exitedSigningKeysCount, 1) }) - it('_getCorrectedNodeOperator() - target >= vetted', async () => { - let firstNodeOperatorKeysStats = await app.testing_getCorrectedNodeOperator(firstNodeOperatorId) + it('_getNodeOperator() - exited+target >= vetted', async () => { + let firstNodeOperatorKeysStats = await app.testing_getNodeOperator(firstNodeOperatorId) - assert.equals(+firstNodeOperatorKeysStats.vettedSigningKeysCount, 8) + assert.equals(+firstNodeOperatorKeysStats.maxSigningKeysCount, 8) assert.equals(+firstNodeOperatorKeysStats.depositedSigningKeysCount, 5) assert.equals(+firstNodeOperatorKeysStats.exitedSigningKeysCount, 1) await app.updateTargetValidatorsLimits(firstNodeOperatorId, true, 1000, { from: voting }) - firstNodeOperatorKeysStats = await app.testing_getCorrectedNodeOperator(firstNodeOperatorId) - assert.equals(+firstNodeOperatorKeysStats.vettedSigningKeysCount, 8) + firstNodeOperatorKeysStats = await app.testing_getNodeOperator(firstNodeOperatorId) + assert.equals(+firstNodeOperatorKeysStats.maxSigningKeysCount, 8) assert.equals(+firstNodeOperatorKeysStats.depositedSigningKeysCount, 5) assert.equals(+firstNodeOperatorKeysStats.exitedSigningKeysCount, 1) }) - it('_getCorrectedNodeOperator() - target <= deposited-exited', async () => { - let firstNodeOperatorKeysStats = await app.testing_getCorrectedNodeOperator(firstNodeOperatorId) + it('_getNodeOperator() - exited+target <= deposited', async () => { + let firstNodeOperatorKeysStats = await app.testing_getNodeOperator(firstNodeOperatorId) - assert.equals(+firstNodeOperatorKeysStats.vettedSigningKeysCount, 8) + assert.equals(+firstNodeOperatorKeysStats.maxSigningKeysCount, 8) assert.equals(+firstNodeOperatorKeysStats.depositedSigningKeysCount, 5) assert.equals(+firstNodeOperatorKeysStats.exitedSigningKeysCount, 1) await app.updateTargetValidatorsLimits(firstNodeOperatorId, true, 4, { from: voting }) - firstNodeOperatorKeysStats = await app.testing_getCorrectedNodeOperator(firstNodeOperatorId) + firstNodeOperatorKeysStats = await app.testing_getNodeOperator(firstNodeOperatorId) assert.equals( - +firstNodeOperatorKeysStats.vettedSigningKeysCount, + +firstNodeOperatorKeysStats.maxSigningKeysCount, firstNodeOperatorKeysStats.depositedSigningKeysCount ) assert.equals(+firstNodeOperatorKeysStats.depositedSigningKeysCount, 5) @@ -1580,11 +1673,11 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await app.testing_getSigningKeysAllocationData(keysToAllocate) assert.equals(allocatedKeysCount, keysToAllocate) - assert.equal(nodeOperatorIds.length, 2) + assert.equals(nodeOperatorIds.length, 2) assert.equals(nodeOperatorIds[0], firstNodeOperatorId) assert.equals(nodeOperatorIds[1], secondNodeOperatorId) - assert.equal(activeKeyCountsAfterAllocation.length, 2) + assert.equals(activeKeyCountsAfterAllocation.length, 2) // the first node operator has to receive 3 deposits cause reached limit assert.equals( activeKeyCountsAfterAllocation[0], @@ -1614,8 +1707,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const { allocatedKeysCount, nodeOperatorIds, activeKeyCountsAfterAllocation } = await app.testing_getSigningKeysAllocationData(10) assert.equals(allocatedKeysCount, 0) - assert.equal(nodeOperatorIds.length, 0) - assert.equal(activeKeyCountsAfterAllocation.length, 0) + assert.equals(nodeOperatorIds.length, 0) + assert.equals(activeKeyCountsAfterAllocation.length, 0) }) it('returns empty result when registry has no active node operators', async () => { @@ -1633,8 +1726,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const { allocatedKeysCount, nodeOperatorIds, activeKeyCountsAfterAllocation } = await app.testing_getSigningKeysAllocationData(10) assert.equals(allocatedKeysCount, 0) - assert.equal(nodeOperatorIds.length, 0) - assert.equal(activeKeyCountsAfterAllocation.length, 0) + assert.equals(nodeOperatorIds.length, 0) + assert.equals(activeKeyCountsAfterAllocation.length, 0) }) it('returns empty result when registry has no unused keys', async () => { @@ -1649,8 +1742,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const { allocatedKeysCount, nodeOperatorIds, activeKeyCountsAfterAllocation } = await app.testing_getSigningKeysAllocationData(10) assert.equals(allocatedKeysCount, 0) - assert.equal(nodeOperatorIds.length, 0) - assert.equal(activeKeyCountsAfterAllocation.length, 0) + assert.equals(nodeOperatorIds.length, 0) + assert.equals(activeKeyCountsAfterAllocation.length, 0) }) it('returns empty result when all node operators reached vetted keys limit', async () => { @@ -1665,8 +1758,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const { allocatedKeysCount, nodeOperatorIds, activeKeyCountsAfterAllocation } = await app.testing_getSigningKeysAllocationData(10) assert.equals(allocatedKeysCount, 0) - assert.equal(nodeOperatorIds.length, 0) - assert.equal(activeKeyCountsAfterAllocation.length, 0) + assert.equals(nodeOperatorIds.length, 0) + assert.equals(activeKeyCountsAfterAllocation.length, 0) }) it('excludes from result node operators without depositable keys', async () => { @@ -1689,11 +1782,11 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob assert.equals(allocatedKeysCount, expectedAllocatedKeysCount) - assert.equal(nodeOperatorIds.length, 1) - assert.equal(nodeOperatorIds[0], secondNodeOperatorId) + assert.equals(nodeOperatorIds.length, 1) + assert.equals(nodeOperatorIds[0], secondNodeOperatorId) - assert.equal(activeKeyCountsAfterAllocation.length, 1) - assert.equal(activeKeyCountsAfterAllocation[0], availableKeysCount + expectedAllocatedKeysCount) + assert.equals(activeKeyCountsAfterAllocation.length, 1) + assert.equals(activeKeyCountsAfterAllocation[0], availableKeysCount + expectedAllocatedKeysCount) assert.equals( secondNodeOperatorReport.totalExitedValidators, @@ -1726,11 +1819,11 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await app.testing_getSigningKeysAllocationData(keysToAllocate) assert.equals(allocatedKeysCount, keysToAllocate) - assert.equal(nodeOperatorIds.length, 2) + assert.equals(nodeOperatorIds.length, 2) assert.equals(nodeOperatorIds[0], firstNodeOperatorId) assert.equals(nodeOperatorIds[1], secondNodeOperatorId) - assert.equal(activeKeyCountsAfterAllocation.length, 2) + assert.equals(activeKeyCountsAfterAllocation.length, 2) // the first node operator has to receive 2 deposits according to the allocation algorithm const firstNodeOperatorActiveValidators = firstNodeOperatorReport.totalDepositedValidators - firstNodeOperatorReport.totalExitedValidators @@ -1768,10 +1861,10 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await app.testing_getSigningKeysAllocationData(keysToAllocate) assert.equals(allocatedKeysCount, keysToAllocate) - assert.equal(nodeOperatorIds.length, 1) + assert.equals(nodeOperatorIds.length, 1) assert.equals(nodeOperatorIds[0], secondNodeOperatorId) - assert.equal(activeKeyCountsAfterAllocation.length, 1) + assert.equals(activeKeyCountsAfterAllocation.length, 1) // the second node operator receives all deposits cause the first is deactivated const secondNodeOperatorActiveValidators = secondNodeOperatorReport.totalDepositedValidators - secondNodeOperatorReport.totalExitedValidators @@ -1803,11 +1896,11 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await app.testing_getSigningKeysAllocationData(keysToAllocate) assert.equals(allocatedKeysCount, keysToAllocate) - assert.equal(nodeOperatorIds.length, 2) + assert.equals(nodeOperatorIds.length, 2) assert.equals(nodeOperatorIds[0], firstNodeOperatorId) assert.equals(nodeOperatorIds[1], secondNodeOperatorId) - assert.equal(activeKeyCountsAfterAllocation.length, 2) + assert.equals(activeKeyCountsAfterAllocation.length, 2) // the first node operator has to receive 3 deposits cause reached limit const firstNodeOperatorActiveValidators = firstNodeOperatorReport.totalDepositedValidators - firstNodeOperatorReport.totalExitedValidators @@ -1834,46 +1927,34 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await assert.reverts(app.obtainDepositData(10, '0x', { from: nobody }), 'APP_AUTH_FAILED') }) - it('returns empty result when no validators to deposit to', async () => { - // clear the registry to remove all unused keys with node operators + it('reverts with error "INVALID_ALLOCATED_KEYS_COUNT" when no validators to deposit to', async () => { await app.testing_resetRegistry() const nodeOperatorsCount = await app.getNodeOperatorsCount() assert.equals(nodeOperatorsCount, 0) const keysToAllocate = 10 - const receipt = await app.testing_obtainDepositData(keysToAllocate) - const keysLoadedEvent = getEventAt(receipt, 'ValidatorsKeysLoaded').args - assert.equals(keysLoadedEvent.count, 0) - assert.isNull(keysLoadedEvent.publicKeys) - assert.isNull(keysLoadedEvent.signatures) + await assert.reverts(app.testing_obtainDepositData(keysToAllocate), 'INVALID_ALLOCATED_KEYS_COUNT') }) - it("doesn't change validators keys nonce when no available keys for deposit", async () => { - // deactivate node operators before testing to remove available keys - await app.deactivateNodeOperator(firstNodeOperatorId, { from: voting }) - await app.deactivateNodeOperator(secondNodeOperatorId, { from: voting }) - const activeNodeOperatorsCount = await app.getActiveNodeOperatorsCount() - assert.equals(activeNodeOperatorsCount, 0) + it('reverts with error "INVALID_ALLOCATED_KEYS_COUNT" when module has not enough keys', async () => { + await app.testing_resetRegistry() - const nonceBefore = await app.getNonce() - const keysToAllocate = 10 - await app.testing_obtainDepositData(keysToAllocate) - const nonceAfter = await app.getNonce() - assert.equals(nonceBefore, nonceAfter) - }) + await app.addNodeOperator('fo o', ADDRESS_1, { from: voting }) + await app.addNodeOperator(' bar', ADDRESS_2, { from: voting }) - it("doesn't emits DepositedSigningKeysCountChanged when no available keys for deposit", async () => { - // remove unused keys - await app.onWithdrawalCredentialsChanged({ from: voting }) - const [firstNodeOperator, secondNodeOperator] = await Promise.all([ - app.getNodeOperator(firstNodeOperatorId, false), - app.getNodeOperator(secondNodeOperatorId, false) - ]) - assert.equals(firstNodeOperator.totalSigningKeys, firstNodeOperator.usedSigningKeys) - assert.equals(secondNodeOperator.totalSigningKeys, secondNodeOperator.usedSigningKeys) + const firstOperatorKeys = new signingKeys.FakeValidatorKeys(3) + const secondOperatorKeys = new signingKeys.FakeValidatorKeys(3) - const keysToAllocate = 10 - const receipt = await app.testing_obtainDepositData(keysToAllocate) - assert.notEmits(receipt, 'DepositedSigningKeysCountChanged') + await app.addSigningKeys(0, 3, ...firstOperatorKeys.slice(), { from: voting }) + await app.addSigningKeys(1, 3, ...secondOperatorKeys.slice(), { from: voting }) + + await app.setNodeOperatorStakingLimit(0, 10, { from: voting }) + await app.setNodeOperatorStakingLimit(1, 10, { from: voting }) + + const stakingModuleSummary = await app.getStakingModuleSummary() + assert.equals(stakingModuleSummary.depositableValidatorsCount, 6) + + const keysToAllocate = 7 + await assert.reverts(app.testing_obtainDepositData(keysToAllocate), 'INVALID_ALLOCATED_KEYS_COUNT') }) it('loads correct signing keys', async () => { @@ -1892,12 +1973,18 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await app.setNodeOperatorStakingLimit(0, 10, { from: voting }) await app.setNodeOperatorStakingLimit(1, 10, { from: voting }) + let stakingModuleSummary = await app.getStakingModuleSummary() + assert.equals(stakingModuleSummary.depositableValidatorsCount, 6) + let keysToAllocate = 1 let receipt = await app.testing_obtainDepositData(keysToAllocate) let keysLoadedEvent = getEventAt(receipt, 'ValidatorsKeysLoaded').args - assert.equal(keysLoadedEvent.publicKeys, firstOperatorKeys.get(0)[0], 'assignment 1: pubkeys') - assert.equal(keysLoadedEvent.signatures, firstOperatorKeys.get(0)[1], 'assignment 1: signatures') + assert.equals(keysLoadedEvent.publicKeys, firstOperatorKeys.get(0)[0], 'assignment 1: pubkeys') + assert.equals(keysLoadedEvent.signatures, firstOperatorKeys.get(0)[1], 'assignment 1: signatures') + + stakingModuleSummary = await app.getStakingModuleSummary() + assert.equals(stakingModuleSummary.depositableValidatorsCount, 5) keysToAllocate = 2 receipt = await app.testing_obtainDepositData(keysToAllocate) @@ -1915,7 +2002,13 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob 'assignment 2: signatures' ) + stakingModuleSummary = await app.getStakingModuleSummary() + assert.equals(stakingModuleSummary.depositableValidatorsCount, 3) + keysToAllocate = 10 + await assert.reverts(app.testing_obtainDepositData(keysToAllocate), 'INVALID_ALLOCATED_KEYS_COUNT') + + keysToAllocate = 3 receipt = await app.testing_obtainDepositData(keysToAllocate) keysLoadedEvent = getEventAt(receipt, 'ValidatorsKeysLoaded').args @@ -1930,17 +2023,16 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob 'assignment 2: signatures' ) - keysToAllocate = 10 - receipt = await app.testing_obtainDepositData(keysToAllocate) - keysLoadedEvent = getEventAt(receipt, 'ValidatorsKeysLoaded').args + stakingModuleSummary = await app.getStakingModuleSummary() + assert.equals(stakingModuleSummary.depositableValidatorsCount, 0) - assert.equal(keysLoadedEvent.publicKeys, null, 'no singing keys left: publicKeys') - assert.equal(keysLoadedEvent.signatures, null, 'no singing keys left: signatures') + keysToAllocate = 1 + await assert.reverts(app.testing_obtainDepositData(keysToAllocate), 'INVALID_ALLOCATED_KEYS_COUNT') }) it('increases keysOpIndex & changes nonce', async () => { const [keysOpIndexBefore, nonceBefore] = await Promise.all([app.getKeysOpIndex(), app.getNonce()]) - const keysToAllocate = 10 + const { depositableValidatorsCount: keysToAllocate } = await app.getStakingModuleSummary() await app.testing_obtainDepositData(keysToAllocate) const [keysOpIndexAfter, nonceAfter] = await Promise.all([app.getKeysOpIndex(), app.getNonce()]) assert.equals(keysOpIndexAfter, keysOpIndexBefore.toNumber() + 1) @@ -1948,18 +2040,19 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob }) it('increases global deposited signing keys counter', async () => { - const keysToAllocate = 10 + const { depositableValidatorsCount: keysToAllocate } = await app.getStakingModuleSummary() const keyIndex = NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount + 1 assert.isTrue(keyIndex <= NODE_OPERATORS[secondNodeOperatorId].totalSigningKeysCount) - const { depositedSigningKeysCount: depositedSigningKeysCountBefore } = await app.testing_getTotalSigningKeysStats() + const { depositedSigningKeysCount: depositedSigningKeysCountBefore } = + await app.testing_getTotalSigningKeysStats() await app.testing_obtainDepositData(keysToAllocate) const { depositedSigningKeysCount: depositedSigningKeysCountAfter } = await app.testing_getTotalSigningKeysStats() - assert.equal(depositedSigningKeysCountAfter.toNumber(), depositedSigningKeysCountBefore.toNumber() + 4) + assert.equals(depositedSigningKeysCountAfter.toNumber(), depositedSigningKeysCountBefore.toNumber() + 4) }) it('emits KeysOpIndexSet & NonceChanged', async () => { const keysOpIndexBefore = await app.getKeysOpIndex() - const keysToAllocate = 10 + const { depositableValidatorsCount: keysToAllocate } = await app.getStakingModuleSummary() const receipt = await app.testing_obtainDepositData(keysToAllocate) const nonceAfter = await app.getNonce() assert.emits(receipt, 'KeysOpIndexSet', { keysOpIndex: keysOpIndexBefore.toNumber() + 1 }) @@ -1969,13 +2062,26 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it('emits DepositedSigningKeysCountChanged when keys were loaded', async () => { let keysToAllocate = 2 let receipt = await app.testing_obtainDepositData(keysToAllocate) - assert.emits(receipt, 'DepositedSigningKeysCountChanged', { nodeOperatorId: firstNodeOperatorId, depositedValidatorsCount: 6 }) - assert.emits(receipt, 'DepositedSigningKeysCountChanged', { nodeOperatorId: secondNodeOperatorId, depositedValidatorsCount: 8 }) + assert.emits(receipt, 'DepositedSigningKeysCountChanged', { + nodeOperatorId: firstNodeOperatorId, + depositedValidatorsCount: 6 + }) + assert.emits(receipt, 'DepositedSigningKeysCountChanged', { + nodeOperatorId: secondNodeOperatorId, + depositedValidatorsCount: 8 + }) - keysToAllocate = 10 + const stakingModuleSummary = await app.getStakingModuleSummary() + keysToAllocate = stakingModuleSummary.depositableValidatorsCount receipt = await app.testing_obtainDepositData(keysToAllocate) - assert.notEmits(receipt, 'DepositedSigningKeysCountChanged', { nodeOperatorId: firstNodeOperatorId, depositedSigningKeysCount: 6 }) - assert.emits(receipt, 'DepositedSigningKeysCountChanged', { nodeOperatorId: secondNodeOperatorId, depositedValidatorsCount: 10 }) + assert.notEmits(receipt, 'DepositedSigningKeysCountChanged', { + nodeOperatorId: firstNodeOperatorId, + depositedSigningKeysCount: 6 + }) + assert.emits(receipt, 'DepositedSigningKeysCountChanged', { + nodeOperatorId: secondNodeOperatorId, + depositedValidatorsCount: 10 + }) }) }) @@ -1996,9 +2102,9 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it('returns correct node operator info', async () => { const nodeOperator = await app.getNodeOperator(secondNodeOperatorId, true) - assert.equal(nodeOperator.active, NODE_OPERATORS[secondNodeOperatorId].isActive !== false) - assert.equal(nodeOperator.name, NODE_OPERATORS[secondNodeOperatorId].name) - assert.equal(nodeOperator.rewardAddress, NODE_OPERATORS[secondNodeOperatorId].rewardAddress) + assert.equals(nodeOperator.active, NODE_OPERATORS[secondNodeOperatorId].isActive !== false) + assert.equals(nodeOperator.name, NODE_OPERATORS[secondNodeOperatorId].name) + assert.equals(nodeOperator.rewardAddress, NODE_OPERATORS[secondNodeOperatorId].rewardAddress) assert.equals(nodeOperator.stakingLimit, NODE_OPERATORS[secondNodeOperatorId].vettedSigningKeysCount) assert.equals(nodeOperator.stoppedValidators, NODE_OPERATORS[secondNodeOperatorId].exitedSigningKeysCount) assert.equals(nodeOperator.totalSigningKeys, NODE_OPERATORS[secondNodeOperatorId].totalSigningKeysCount) @@ -2007,7 +2113,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it('returns empty name when _fullInfo is false', async () => { const nodeOperatorWithoutName = await app.getNodeOperator(firstNodeOperatorId, false) - assert.equal(nodeOperatorWithoutName.name, '') + assert.equals(nodeOperatorWithoutName.name, '') const nodeOperatorWithName = await app.getNodeOperator(firstNodeOperatorId, true) assert.isTrue(nodeOperatorWithName.name !== '') }) @@ -2043,18 +2149,20 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob }) it('returns zero shares when all validators are exited', async () => { - await app.updateExitedValidatorsCount(firstNodeOperatorId, NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount, { - from: voting - }) - await app.updateExitedValidatorsCount(secondNodeOperatorId, NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount, { - from: voting - }) + const { operatorIds, keysCounts } = prepIdsCountsPayload( + [firstNodeOperatorId, secondNodeOperatorId], + [ + NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount, + NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount + ] + ) + await app.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }) const activeNodeOperators = await nodeOperators.filterNodeOperators(app, (nodeOperator) => nodeOperator.active) const totalRewardsShare = web3.utils.toWei('10') const { recipients, shares } = await app.getRewardsDistribution(totalRewardsShare) - assert.equal(shares.length, activeNodeOperators.length) + assert.equals(shares.length, activeNodeOperators.length) shares.forEach((s) => assert.equals(s, 0)) - assert.equal(recipients.length, activeNodeOperators.length) + assert.equals(recipients.length, activeNodeOperators.length) recipients.forEach((rewardAddress, i) => assert.equals(rewardAddress, activeNodeOperators[i].rewardAddress)) }) @@ -2063,7 +2171,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const activeNodeOperators = await nodeOperators.filterNodeOperators(app, (nodeOperator) => nodeOperator.active) const { recipients, shares } = await app.getRewardsDistribution(totalRewardsShare.toString()) - assert.equal(recipients.length, activeNodeOperators.length) + assert.equals(recipients.length, activeNodeOperators.length) recipients.forEach((rewardAddress, i) => assert.equals(rewardAddress, activeNodeOperators[i].rewardAddress)) const totalActiveNodeOperators = NODE_OPERATORS.reduce( @@ -2075,7 +2183,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const expectedRewardsDistribution = NODE_OPERATORS.filter((n) => n.isActive !== false).map((n) => n.isActive === false ? 0n : perValidatorReward * BigInt(n.depositedSigningKeysCount - n.exitedSigningKeysCount) ) - assert.equal(shares.length, expectedRewardsDistribution.length) + assert.equals(shares.length, expectedRewardsDistribution.length) for (let i = 0; i < shares.length; ++i) { assert.equals(shares[i], expectedRewardsDistribution[i]) } @@ -2130,31 +2238,31 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob ) }) - it('reverts with "INVALID_LENGTH" error when public keys batch has invalid length', async () => { + it('reverts with "LENGTH_MISMATCH" error when public keys batch has invalid length', async () => { const keysCount = 2 const [publicKeys, signatures] = secondNodeOperatorKeys.slice(0, keysCount) await assert.reverts( app.addSigningKeys(firstNodeOperatorId, keysCount, publicKeys + 'deadbeaf', signatures, { from: voting }), - 'INVALID_LENGTH' + 'LENGTH_MISMATCH' ) }) - it('reverts with "INVALID_LENGTH" error when signatures batch has invalid length', async () => { + it('reverts with "LENGTH_MISMATCH" error when signatures batch has invalid length', async () => { const keysCount = 2 const [publicKeys, signatures] = secondNodeOperatorKeys.slice(0, keysCount) await assert.reverts( app.addSigningKeys(firstNodeOperatorId, keysCount, publicKeys, signatures.slice(0, -2), { from: voting }), - 'INVALID_LENGTH' + 'LENGTH_MISMATCH' ) }) - it('reverts with "INVALID_LENGTH" error when public keys and signatures length mismatch', async () => { + it('reverts with "LENGTH_MISMATCH" error when public keys and signatures length mismatch', async () => { const keysCount = 2 const [publicKeys] = secondNodeOperatorKeys.slice(0, keysCount) const [, signatures] = secondNodeOperatorKeys.slice(0, keysCount + 1) await assert.reverts( app.addSigningKeys(firstNodeOperatorId, keysCount, publicKeys, signatures.slice(0, -2), { from: voting }), - 'INVALID_LENGTH' + 'LENGTH_MISMATCH' ) }) @@ -2173,7 +2281,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob from: voting }) const { totalSigningKeys: totalSigningKeysCountAfter } = await app.getNodeOperator(firstNodeOperatorId, false) - assert.equal( + assert.equals( totalSigningKeysCountAfter.toNumber(), totalSigningKeysCountBefore.toNumber() + firstNodeOperatorKeys.count ) @@ -2195,8 +2303,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < secondNodeOperatorKeys.count; ++i) { const { key, depositSignature } = await app.getSigningKey(secondNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = secondNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } }) @@ -2219,8 +2327,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = initialKeysCount; i < firstNodeOperatorKeys.count; ++i) { const { key, depositSignature } = await app.getSigningKey(firstNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = firstNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } }) @@ -2234,8 +2342,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < firstNodeOperatorKeys.count; ++i) { const { key, depositSignature } = await app.getSigningKey(firstNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = firstNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } }) @@ -2248,12 +2356,14 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob from: voting }) const { totalSigningKeysCount: totalSigningKeysCountAfter } = await app.testing_getTotalSigningKeysStats() - assert.equal(totalSigningKeysCountAfter, totalSigningKeysCountBefore.toNumber() + firstNodeOperatorKeys.count) + assert.equals(totalSigningKeysCountAfter, totalSigningKeysCountBefore.toNumber() + firstNodeOperatorKeys.count) }) it('increases keysOpIndex & changes nonce', async () => { const [keysOpIndexBefore, nonceBefore] = await Promise.all([app.getKeysOpIndex(), app.getNonce()]) - await app.addSigningKeys(firstNodeOperatorId, firstNodeOperatorKeys.count, ...firstNodeOperatorKeys.slice(), { from: voting }) + await app.addSigningKeys(firstNodeOperatorId, firstNodeOperatorKeys.count, ...firstNodeOperatorKeys.slice(), { + from: voting + }) const [keysOpIndexAfter, nonceAfter] = await Promise.all([app.getKeysOpIndex(), app.getNonce()]) assert.equals(keysOpIndexAfter, keysOpIndexBefore.toNumber() + 1) assert.notEquals(nonceAfter, nonceBefore) @@ -2261,9 +2371,14 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it('emits KeysOpIndexSet & NonceChanged', async () => { const keysOpIndexBefore = await app.getKeysOpIndex() - const receipt = await app.addSigningKeys(firstNodeOperatorId, firstNodeOperatorKeys.count, ...firstNodeOperatorKeys.slice(), { - from: voting - }) + const receipt = await app.addSigningKeys( + firstNodeOperatorId, + firstNodeOperatorKeys.count, + ...firstNodeOperatorKeys.slice(), + { + from: voting + } + ) const nonceAfter = await app.getNonce() assert.emits(receipt, 'KeysOpIndexSet', { keysOpIndex: keysOpIndexBefore.toNumber() + 1 }) assert.emits(receipt, 'NonceChanged', { nonce: nonceAfter }) @@ -2346,8 +2461,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < firstNodeOperatorKeys.count; ++i) { const { key, depositSignature } = await app.getSigningKey(firstNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = firstNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } }) }) @@ -2400,7 +2515,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const { totalSigningKeys: totalSigningKeysBefore } = await app.getNodeOperator(secondNodeOperatorId, false) await app.removeSigningKey(secondNodeOperatorId, keyIndex, { from: voting }) const { totalSigningKeys: totalSigningKeysAfter } = await app.getNodeOperator(secondNodeOperatorId, false) - assert.equal(totalSigningKeysAfter.toNumber(), totalSigningKeysBefore.toNumber() - 1) + assert.equals(totalSigningKeysAfter.toNumber(), totalSigningKeysBefore.toNumber() - 1) }) it('decreases global total signing keys counter', async () => { @@ -2409,7 +2524,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const { totalSigningKeysCount: totalSigningKeysCountBefore } = await app.testing_getTotalSigningKeysStats() await app.removeSigningKey(secondNodeOperatorId, keyIndex, { from: voting }) const { totalSigningKeysCount: totalSigningKeysCountAfter } = await app.testing_getTotalSigningKeysStats() - assert.equal(totalSigningKeysCountAfter.toNumber(), totalSigningKeysCountBefore.toNumber() - 1) + assert.equals(totalSigningKeysCountAfter.toNumber(), totalSigningKeysCountBefore.toNumber() - 1) }) it("doesn't change vetted signing keys counter if it greater than vetted keys counter", async () => { @@ -2435,7 +2550,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob assert.isTrue(keyIndex <= NODE_OPERATORS[secondNodeOperatorId].totalSigningKeysCount) await app.removeSigningKey(secondNodeOperatorId, keyIndex, { from: voting }) const { stakingLimit: stakingLimitAfter } = await app.getNodeOperator(secondNodeOperatorId, false) - assert.equal(stakingLimitAfter.toNumber(), keyIndex) + assert.equals(stakingLimitAfter.toNumber(), keyIndex) }) it('correctly decreases global vetted signing keys count if key index is less then vetted keys counter of node operator', async () => { @@ -2445,7 +2560,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await app.removeSigningKey(secondNodeOperatorId, keyIndex, { from: voting }) const { vettedSigningKeysCount: vettedSigningKeysCountAfter } = await app.testing_getTotalSigningKeysStats() const vettedSigningKeysDecrement = NODE_OPERATORS[secondNodeOperatorId].vettedSigningKeysCount - keyIndex - assert.equal( + assert.equals( vettedSigningKeysCountAfter.toNumber(), vettedSigningKeysCountBefore.toNumber() - vettedSigningKeysDecrement ) @@ -2459,14 +2574,14 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const { isTargetLimitActive: isTargetLimitActiveBefore, targetValidatorsCount: targetValidatorsCountBefore, - excessValidatorsCount: excessValidatorsCountBefore, - } = await app.testing_getTotalTargetStats(); + excessValidatorsCount: excessValidatorsCountBefore + } = await app.testing_getTotalTargetStats() await app.removeSigningKey(secondNodeOperatorId, keyIndex, { from: voting }) const { vettedSigningKeysCount: vettedSigningKeysCountAfter } = await app.testing_getTotalSigningKeysStats() const vettedSigningKeysDecrement = NODE_OPERATORS[secondNodeOperatorId].vettedSigningKeysCount - keyIndex - assert.equal( + assert.equals( vettedSigningKeysCountAfter.toNumber(), vettedSigningKeysCountBefore.toNumber() - vettedSigningKeysDecrement ) @@ -2474,19 +2589,21 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const { isTargetLimitActive: isTargetLimitActiveAfter, targetValidatorsCount: targetValidatorsCountAfter, - excessValidatorsCount: excessValidatorsCountAfter, - } = await app.testing_getTotalTargetStats(); + excessValidatorsCount: excessValidatorsCountAfter + } = await app.testing_getTotalTargetStats() console.log({ targetValidatorsCountBefore: targetValidatorsCountBefore.toNumber(), targetValidatorsCountAfter: targetValidatorsCountAfter.toNumber(), - vettedSigningKeysCountBefore: vettedSigningKeysCountBefore.toNumber() , + vettedSigningKeysCountBefore: vettedSigningKeysCountBefore.toNumber(), vettedSigningKeysDecrement - }) assertBn(isTargetLimitActiveAfter, isTargetLimitActiveBefore) - assertBn(targetValidatorsCountAfter, targetValidatorsCountBefore.toNumber() - vettedSigningKeysCountBefore.toNumber() - vettedSigningKeysDecrement) + assertBn( + targetValidatorsCountAfter, + targetValidatorsCountBefore.toNumber() - vettedSigningKeysCountBefore.toNumber() - vettedSigningKeysDecrement + ) assertBn(excessValidatorsCountAfter, excessValidatorsCountBefore) }) @@ -2533,8 +2650,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < totalSigningKeys.toNumber(); ++i) { const { key, depositSignature } = await app.getSigningKey(firstNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = firstNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } }) @@ -2545,20 +2662,20 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < keyIndex; ++i) { const { key, depositSignature } = await app.getSigningKey(secondNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = secondNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } // at the removed key place now must be the previously last key const { key, depositSignature } = await app.getSigningKey(secondNodeOperatorId, keyIndex) const [expectedPublicKey, expectedSignature] = secondNodeOperatorKeys.get(secondNodeOperatorKeys.count - 1) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) const { totalSigningKeys } = await app.getNodeOperator(secondNodeOperatorId, false) for (let i = keyIndex + 1; i < totalSigningKeys.toNumber(); ++i) { const { key, depositSignature } = await app.getSigningKey(secondNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = secondNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } }) @@ -2576,8 +2693,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < totalSigningKeys.toNumber(); ++i) { const { key, depositSignature } = await app.getSigningKey(firstNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = firstNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } assert.equals(stakingLimit, NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount) assert.equals(usedSigningKeys, NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount) @@ -2598,8 +2715,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < secondNodeOperatorKeys.count; ++i) { const { key, depositSignature } = await app.getSigningKey(secondNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = secondNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } }) @@ -2611,8 +2728,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const keysToAdd = new signingKeys.FakeValidatorKeys(1) await app.addSigningKeys(secondNodeOperatorId, keysToAdd.count, ...keysToAdd.slice(), { from: voting }) const { key, depositSignature } = await app.getSigningKey(secondNodeOperatorId, totalSigningKeys.toNumber()) - assert.equal(key, keysToAdd.get(0)[0]) - assert.equal(depositSignature, keysToAdd.get(0)[1]) + assert.equals(key, keysToAdd.get(0)[0]) + assert.equals(depositSignature, keysToAdd.get(0)[1]) }) it('emits VettedSigningKeysCountChanged event with correct params if passed index is less then current vetted signing keys count', async () => { @@ -2656,7 +2773,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob it('emits SigningKeyRemoved event with correct params', async () => { const keyIndex = NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount - assert.isTrue(keyIndex <= NODE_OPERATORS[firstNodeOperatorId].totalSigningKeysCount) + assert.isTrue(keyIndex < NODE_OPERATORS[firstNodeOperatorId].totalSigningKeysCount) const receipt = await app.removeSigningKey(firstNodeOperatorId, keyIndex, { from: voting }) assert.emits( receipt, @@ -2764,7 +2881,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const { totalSigningKeys: totalSigningKeysBefore } = await app.getNodeOperator(secondNodeOperatorId, false) await app.removeSigningKeys(secondNodeOperatorId, keyIndex, keysCount, { from: voting }) const { totalSigningKeys: totalSigningKeysAfter } = await app.getNodeOperator(secondNodeOperatorId, false) - assert.equal(totalSigningKeysAfter.toNumber(), totalSigningKeysBefore.toNumber() - keysCount) + assert.equals(totalSigningKeysAfter.toNumber(), totalSigningKeysBefore.toNumber() - keysCount) }) it('decreases global total signing keys counter correctly', async () => { @@ -2774,7 +2891,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const { totalSigningKeysCount: totalSigningKeysCountBefore } = await app.testing_getTotalSigningKeysStats() await app.removeSigningKeys(secondNodeOperatorId, keyIndex, keysCount, { from: voting }) const { totalSigningKeysCount: totalSigningKeysCountAfter } = await app.testing_getTotalSigningKeysStats() - assert.equal(totalSigningKeysCountAfter.toNumber(), totalSigningKeysCountBefore.toNumber() - keysCount) + assert.equals(totalSigningKeysCountAfter.toNumber(), totalSigningKeysCountBefore.toNumber() - keysCount) }) it("doesn't change vetted signing keys counter if fromIndex is greater than vetted keys counter", async () => { @@ -2802,7 +2919,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob assert(keysCount > 0) await app.removeSigningKeys(secondNodeOperatorId, keyIndex, keysCount, { from: voting }) const { stakingLimit: stakingLimitAfter } = await app.getNodeOperator(secondNodeOperatorId, false) - assert.equal(stakingLimitAfter.toNumber(), keyIndex) + assert.equals(stakingLimitAfter.toNumber(), keyIndex) }) it('correctly decreases global vetted signing keys count if fromIndex is less then vetted keys counter of node operator', async () => { @@ -2812,7 +2929,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await app.removeSigningKeys(secondNodeOperatorId, keyIndex, keysCount, { from: voting }) const { vettedSigningKeysCount: vettedSigningKeysCountAfter } = await app.testing_getTotalSigningKeysStats() const vettedSigningKeysDecrement = NODE_OPERATORS[secondNodeOperatorId].vettedSigningKeysCount - keyIndex - assert.equal( + assert.equals( vettedSigningKeysCountAfter.toNumber(), vettedSigningKeysCountBefore.toNumber() - vettedSigningKeysDecrement ) @@ -2869,8 +2986,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < totalSigningKeys.toNumber(); ++i) { const { key, depositSignature } = await app.getSigningKey(secondNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = secondNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } }) @@ -2883,8 +3000,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob assert.equals( totalSigningKeys, NODE_OPERATORS[secondNodeOperatorId].totalSigningKeysCount - - (NODE_OPERATORS[secondNodeOperatorId].vettedSigningKeysCount - - NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount) + (NODE_OPERATORS[secondNodeOperatorId].vettedSigningKeysCount - + NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount) ) assert.equals(stakingLimit, NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount) @@ -2892,8 +3009,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < keyIndex; ++i) { const { key, depositSignature } = await app.getSigningKey(secondNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = secondNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } // all removed keys replaced by the keys from the end of the batch for (let i = 0; i < keysCount; ++i) { @@ -2901,15 +3018,15 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const [expectedPublicKey, expectedSignature] = secondNodeOperatorKeys.get( secondNodeOperatorKeys.count - keysCount + i ) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } // the rest of the batch stays same for (let i = keyIndex + keysCount; i < totalSigningKeys.toNumber(); ++i) { const { key, depositSignature } = await app.getSigningKey(secondNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = secondNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } }) @@ -2924,24 +3041,24 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < keyIndex; ++i) { const { key, depositSignature } = await app.getSigningKey(firstNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = firstNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } // the deposited keys must stay untouched for (let i = 0; i < keyIndex; ++i) { const { key, depositSignature } = await app.getSigningKey(firstNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = firstNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } // in this case we removed all keys except the last one assert.equals(totalSigningKeys, keyIndex + 1) // the last key stays on the same place const [expectedPublicKey, expectedSignature] = firstNodeOperatorKeys.get(firstNodeOperatorKeys.count - 1) const { key, depositSignature } = await app.getSigningKey(firstNodeOperatorId, keyIndex) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) }) it('can remove all unused keys of node operator', async () => { @@ -2955,8 +3072,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < totalSigningKeys.toNumber(); ++i) { const { key, depositSignature } = await app.getSigningKey(firstNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = firstNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } assert.equals(stakingLimit, NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount) assert.equals(usedSigningKeys, NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount) @@ -2974,8 +3091,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < secondNodeOperatorKeys.count; ++i) { const { key, depositSignature } = await app.getSigningKey(secondNodeOperatorId, i) const [expectedPublicKey, expectedSignature] = secondNodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } }) @@ -2987,8 +3104,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const keysToAdd = new signingKeys.FakeValidatorKeys(1) await app.addSigningKeys(secondNodeOperatorId, keysToAdd.count, ...keysToAdd.slice(), { from: voting }) const { key, depositSignature } = await app.getSigningKey(secondNodeOperatorId, totalSigningKeys.toNumber()) - assert.equal(key, keysToAdd.get(0)[0]) - assert.equal(depositSignature, keysToAdd.get(0)[1]) + assert.equals(key, keysToAdd.get(0)[0]) + assert.equals(depositSignature, keysToAdd.get(0)[1]) }) it('emits VettedSigningKeysCountChanged event with correct params if fromIndex is less then current vetted signing keys count', async () => { @@ -3143,7 +3260,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob }) it('reverts if no STAKING_ROUTER_ROLE', async () => { - await assert.reverts(app.onAllValidatorCountersUpdated({ from: user3 }), 'APP_AUTH_FAILED') + await assert.reverts(app.onExitedAndStuckValidatorsCountsUpdated({ from: user3 }), 'APP_AUTH_FAILED') }) it("doesn't distribute rewards if no shares to distribute", async () => { @@ -3155,13 +3272,13 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob steth.sharesOf(user3) ]) // calls distributeRewards() inside - await app.onAllValidatorCountersUpdated({ from: voting }) + await app.onExitedAndStuckValidatorsCountsUpdated({ from: voting }) const recipientsSharesAfter = await Promise.all([ steth.sharesOf(user1), steth.sharesOf(user2), steth.sharesOf(user3) ]) - assert.equal(recipientsSharesBefore.length, recipientsSharesAfter.length) + assert.equals(recipientsSharesBefore.length, recipientsSharesAfter.length) for (let i = 0; i < recipientsSharesBefore.length; ++i) { assert.equals(recipientsSharesBefore[i], recipientsSharesAfter[i]) } @@ -3172,7 +3289,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await steth.mintShares(app.address, ETH(10)) // calls distributeRewards() inside - await app.onAllValidatorCountersUpdated({ from: voting }) + await app.onExitedAndStuckValidatorsCountsUpdated({ from: voting }) assert.equals(await steth.sharesOf(user1), ETH(3)) assert.equals(await steth.sharesOf(user2), ETH(7)) @@ -3184,12 +3301,38 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await steth.mintShares(app.address, ETH(10)) // calls distributeRewards() inside - receipt = await app.onAllValidatorCountersUpdated({ from: voting }) + receipt = await app.onExitedAndStuckValidatorsCountsUpdated({ from: voting }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user1, sharesAmount: ETH(3) }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user2, sharesAmount: ETH(7) }) assert.notEmits(receipt, 'RewardsDistributed', { rewardAddress: user3, sharesAmount: 0 }) }) + + it('able to distribute rewards to the `MAX_NODE_OPERATORS_COUNT` operators', async () => { + const maxNodeOperatorsCount = await app.MAX_NODE_OPERATORS_COUNT() + + function generateRandomAddress() { + return toChecksumAddress('0x' + randomBytes(20).toString('hex')) + } + + // already have three operators added + for (let i = 3; i < maxNodeOperatorsCount; ++i) { + await app.testing_addNodeOperator(`Node Operator #${i}`, generateRandomAddress(), 5, 5, 5, 0, { from: voting }) + } + assert.equals(await app.getNodeOperatorsCount(), maxNodeOperatorsCount) + + await steth.setTotalPooledEther(ETH(100)) + await steth.mintShares(app.address, ETH(10)) + + // calls distributeRewards() inside + const tx = await app.onExitedAndStuckValidatorsCountsUpdated({ from: voting }) + + // just show the used gas + console.log(`gas used to distribute rewards for ${maxNodeOperatorsCount} NOs:`, +tx.receipt.gasUsed) + + // check that gas is lower than 10M + assert.isTrue(+tx.receipt.gasUsed < 10 * 10**6) + }) }) describe('getTotalSigningKeyCount(nodeOperatorId)', () => { @@ -3278,8 +3421,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob for (let i = 0; i < nodeOperatorKeys.count; ++i) { const { key, depositSignature } = await app.getSigningKey(nodeOperatorId, i) const [expectedPublicKey, expectedSignature] = await nodeOperatorKeys.get(i) - assert.equal(key, expectedPublicKey) - assert.equal(depositSignature, expectedSignature) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) } } }) @@ -3336,22 +3479,22 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob const { pubkeys, signatures, used } = await app.getSigningKeys(0, 1, 2) - assert.equal(pubkeys, keys[1] + keys[2].slice(2)) - assert.equal(signatures, sigs[1] + sigs[2].slice(2)) + assert.equals(pubkeys, keys[1] + keys[2].slice(2)) + assert.equals(signatures, sigs[1] + sigs[2].slice(2)) assert.sameMembers(used, [false, false]) }) }) - describe('handleRewardsMinted()', () => { + describe('onRewardsMinted()', () => { it('reverts with no STAKING_ROUTER_ROLE', async () => { const hasPermission = await dao.hasPermission(user1, app, 'STAKING_ROUTER_ROLE') assert.isFalse(hasPermission) - await assert.reverts(app.handleRewardsMinted(123, { from: user1 })) + await assert.reverts(app.onRewardsMinted(123, { from: user1 })) }) it('no reverts with STAKING_ROUTER_ROLE', async () => { const hasPermission = await dao.hasPermission(voting, app, 'STAKING_ROUTER_ROLE') assert.isTrue(hasPermission) - await app.handleRewardsMinted(123, { from: voting }) + await app.onRewardsMinted(123, { from: voting }) }) }) }) diff --git a/test/0.4.24/signingkey-lib.test.js b/test/0.4.24/signingkey-lib.test.js new file mode 100644 index 000000000..8f2e42990 --- /dev/null +++ b/test/0.4.24/signingkey-lib.test.js @@ -0,0 +1,299 @@ +const { assert } = require('../helpers/assert') +const { assertRevert } = require('../helpers/assertThrow') +const { EvmSnapshot } = require('../helpers/blockchain') +const { ZERO_ADDRESS, getEventAt } = require('@aragon/contract-helpers-test') +const { toBN, padRight } = require('../helpers/utils') +const signingKeys = require('../helpers/signing-keys') +const { prepIdsCountsPayload } = require('../helpers/utils') +const SigningKeysMock = artifacts.require('SigningKeysMock') +const SigningKeys = artifacts.require('SigningKeys') + +const nodeOpId1 = 1 +const nodeOpId2 = 2 + +contract('SigningKeys', ([appManager, voting, user1, user2, user3, nobody]) => { + let app + const snapshot = new EvmSnapshot(hre.ethers.provider) + + const firstNodeOperatorId = 0 + const firstNodeOperatorStartIndex = 0 + const firstNodeOperatorKeys = new signingKeys.FakeValidatorKeys(5, { kFill: 'a', sFill: 'b' }) + const firstNodeOperatorLastIndex = firstNodeOperatorKeys.count - 1 + const secondNodeOperatorId = 1 + const secondNodeOperatorStartIndex = 0 + const secondNodeOperatorKeys = new signingKeys.FakeValidatorKeys(7, { kFill: 'c', sFill: 'd' }) + const secondNodeOperatorLastIndex = secondNodeOperatorKeys.count - 1 + + before('deploy base app', async () => { + // Deploy the app's base contract. + app = await SigningKeysMock.new([nodeOpId1, nodeOpId2]) + await snapshot.make() + }) + + afterEach(async () => { + await snapshot.rollback() + }) + + describe('saveKeysSigs()', () => { + it('reverts with INVALID_KEYS_COUNT error when keys count > UINT64_MAX', async () => { + const keysCount = toBN('0x10000000000000001') + await assert.reverts( + app.saveKeysSigs(firstNodeOperatorId, firstNodeOperatorStartIndex, keysCount, '0x', '0x'), + 'INVALID_KEYS_COUNT' + ) + }) + + it('reverts with "INVALID_KEYS_COUNT" error when keys count is 0', async () => { + const keysCount = 0 + await assert.reverts( + app.saveKeysSigs(firstNodeOperatorId, firstNodeOperatorStartIndex, keysCount, '0x', '0x'), + 'INVALID_KEYS_COUNT' + ) + }) + + it('reverts with "LENGTH_MISMATCH" error when public keys batch has invalid length', async () => { + const keysCount = 2 + const [publicKeys, signatures] = firstNodeOperatorKeys.slice(0, keysCount) + await assert.reverts( + app.saveKeysSigs( + firstNodeOperatorId, + firstNodeOperatorStartIndex, + keysCount, + publicKeys + 'deadbeaf', + signatures + ), + 'LENGTH_MISMATCH' + ) + }) + + it('reverts with "LENGTH_MISMATCH" error when signatures batch has invalid length', async () => { + const keysCount = 2 + const [publicKeys, signatures] = firstNodeOperatorKeys.slice(0, keysCount) + await assert.reverts( + app.saveKeysSigs( + firstNodeOperatorId, + firstNodeOperatorStartIndex, + keysCount, + publicKeys, + signatures.slice(0, -2) + ), + 'LENGTH_MISMATCH' + ) + }) + + it('reverts with "LENGTH_MISMATCH" error when public keys and signatures length mismatch', async () => { + const keysCount = 2 + const [publicKeys] = firstNodeOperatorKeys.slice(0, keysCount) + const [, signatures] = firstNodeOperatorKeys.slice(0, keysCount + 1) + await assert.reverts( + app.saveKeysSigs( + firstNodeOperatorId, + firstNodeOperatorStartIndex, + keysCount, + publicKeys, + signatures.slice(0, -2) + ), + 'LENGTH_MISMATCH' + ) + }) + + it('reverts with "EMPTY_KEY" error when public key is zero bytes batch (at 1st position)', async () => { + const keysCount = 1 + const [, signature] = firstNodeOperatorKeys.get(0) + await assert.reverts( + app.saveKeysSigs( + firstNodeOperatorId, + firstNodeOperatorStartIndex, + keysCount, + signingKeys.EMPTY_PUBLIC_KEY, + signature + ), + 'EMPTY_KEY' + ) + }) + + it('reverts with "EMPTY_KEY" error when public key is zero bytes batch (at last position)', async () => { + const keysCount = 3 + let [publicKeys] = firstNodeOperatorKeys.slice(0, keysCount - 1) + const [, signatures] = firstNodeOperatorKeys.slice(0, keysCount) + publicKeys += signingKeys.EMPTY_PUBLIC_KEY.substring(2) + await assert.reverts( + app.saveKeysSigs(firstNodeOperatorId, firstNodeOperatorStartIndex, keysCount, publicKeys, signatures), + 'EMPTY_KEY' + ) + }) + + it('emits SigningKeyAdded with correct params for every added key', async () => { + const receipt = await app.saveKeysSigs( + firstNodeOperatorId, + firstNodeOperatorStartIndex, + firstNodeOperatorKeys.count, + ...firstNodeOperatorKeys.slice() + ) + for (let i = 0; i < firstNodeOperatorKeys.count; ++i) { + assert.emits( + receipt, + 'SigningKeyAdded', + { nodeOperatorId: firstNodeOperatorId, pubkey: firstNodeOperatorKeys.get(i)[0] }, + { abi: SigningKeys._json.abi } + ) + } + }) + + it('stores keys correctly', async () => { + await app.saveKeysSigs( + firstNodeOperatorId, + firstNodeOperatorStartIndex, + firstNodeOperatorKeys.count, + ...firstNodeOperatorKeys.slice() + ) + + await app.saveKeysSigs( + secondNodeOperatorId, + secondNodeOperatorStartIndex, + secondNodeOperatorKeys.count, + ...secondNodeOperatorKeys.slice() + ) + + for (let i = 0; i < firstNodeOperatorKeys.count; ++i) { + const { pubkeys, signatures } = await app.loadKeysSigs(firstNodeOperatorId, i, 1) + const [expectedPublicKey, expectedSignature] = firstNodeOperatorKeys.get(i) + assert.equal(pubkeys, expectedPublicKey) + assert.equal(signatures, expectedSignature) + } + for (let i = 0; i < secondNodeOperatorKeys.count; ++i) { + const { pubkeys, signatures } = await app.loadKeysSigs(secondNodeOperatorId, i, 1) + const [expectedPublicKey, expectedSignature] = secondNodeOperatorKeys.get(i) + assert.equal(pubkeys, expectedPublicKey) + assert.equal(signatures, expectedSignature) + } + }) + + it('read keys batch correctly', async () => { + await app.saveKeysSigs( + firstNodeOperatorId, + firstNodeOperatorStartIndex, + firstNodeOperatorKeys.count, + ...firstNodeOperatorKeys.slice() + ) + + await app.saveKeysSigs( + secondNodeOperatorId, + secondNodeOperatorStartIndex, + secondNodeOperatorKeys.count, + ...secondNodeOperatorKeys.slice() + ) + + const { pubkeys, signatures } = await app.loadKeysSigsBatch( + [secondNodeOperatorId, firstNodeOperatorId], + [secondNodeOperatorStartIndex + 2, firstNodeOperatorStartIndex + 1], + [secondNodeOperatorKeys.count - 4, firstNodeOperatorKeys.count - 2] + ) + + let expectedPublicKeys = '' + let expectedSignatures = '' + let startIndex = secondNodeOperatorStartIndex + 2 + let endIndex = startIndex + secondNodeOperatorKeys.count - 4 + for (let i = startIndex; i < endIndex; ++i) { + const [key, sig] = secondNodeOperatorKeys.get(i) + expectedPublicKeys += key.substring(2) + expectedSignatures += sig.substring(2) + } + + startIndex = firstNodeOperatorStartIndex + 1 + endIndex = startIndex + firstNodeOperatorKeys.count - 2 + for (let i = startIndex; i < endIndex; ++i) { + const [key, sig] = firstNodeOperatorKeys.get(i) + expectedPublicKeys += key.substring(2) + expectedSignatures += sig.substring(2) + } + + assert.equal(pubkeys, '0x' + expectedPublicKeys) + assert.equal(signatures, '0x' + expectedSignatures) + }) + }) + + describe('removeKeysSigs()', async () => { + beforeEach(async () => { + await app.saveKeysSigs( + firstNodeOperatorId, + firstNodeOperatorStartIndex, + firstNodeOperatorKeys.count, + ...firstNodeOperatorKeys.slice() + ) + await app.saveKeysSigs( + secondNodeOperatorId, + secondNodeOperatorStartIndex, + secondNodeOperatorKeys.count, + ...secondNodeOperatorKeys.slice() + ) + }) + + it('reverts with INVALID_KEYS_COUNT error when keys count is zero ', async () => { + const keysCount = 0 + await assert.reverts( + app.removeKeysSigs(firstNodeOperatorId, firstNodeOperatorStartIndex, keysCount, firstNodeOperatorLastIndex), + 'INVALID_KEYS_COUNT' + ) + }) + + it('reverts with INVALID_KEYS_COUNT error when index is greater than last keys index', async () => { + const keyIndex = firstNodeOperatorLastIndex + 1 + const keysCount = 1 + await assert.reverts( + app.removeKeysSigs(firstNodeOperatorId, keyIndex, keysCount, firstNodeOperatorLastIndex), + 'INVALID_KEYS_COUNT' + ) + }) + + it('reverts with INVALID_KEYS_COUNT error when keys count is greater than last keys index', async () => { + const keysCount = firstNodeOperatorKeys.count + 1 + await assert.reverts( + app.removeKeysSigs(firstNodeOperatorId, firstNodeOperatorStartIndex, keysCount, firstNodeOperatorLastIndex), + 'INVALID_KEYS_COUNT' + ) + }) + + it('emits SigningKeyAdded with correct params for every added key', async () => { + const receipt = await app.removeKeysSigs( + firstNodeOperatorId, + firstNodeOperatorStartIndex, + firstNodeOperatorKeys.count, + firstNodeOperatorKeys.count + ) + + for (let i = firstNodeOperatorStartIndex; i < firstNodeOperatorKeys.count; ++i) { + assert.emits( + receipt, + 'SigningKeyRemoved', + { nodeOperatorId: firstNodeOperatorId, pubkey: firstNodeOperatorKeys.get(i)[0] }, + { abi: SigningKeys._json.abi } + ) + } + }) + + it('removes keys correctly (clear storage)', async () => { + await app.removeKeysSigs( + firstNodeOperatorId, + firstNodeOperatorStartIndex, + firstNodeOperatorKeys.count, + firstNodeOperatorKeys.count + ) + + for (let i = firstNodeOperatorStartIndex; i < firstNodeOperatorKeys.count; ++i) { + const { pubkeys, signatures } = await app.loadKeysSigs(firstNodeOperatorId, i, 1) + assert.equal(pubkeys, signingKeys.EMPTY_PUBLIC_KEY) + assert.equal(signatures, signingKeys.EMPTY_SIGNATURE) + } + }) + + it('removes keys correctly (move last to deleted position)', async () => { + const keyIndex = 0 + await app.removeKeysSigs(firstNodeOperatorId, keyIndex, 1, firstNodeOperatorKeys.count) + const { pubkeys, signatures } = await app.loadKeysSigs(firstNodeOperatorId, keyIndex, 1) + const [expectedPublicKey, expectedSignature] = firstNodeOperatorKeys.get(firstNodeOperatorLastIndex) + assert.equal(pubkeys, expectedPublicKey) + assert.equal(signatures, expectedSignature) + }) + }) +}) diff --git a/test/0.4.24/steth.test.js b/test/0.4.24/steth.test.js index 9dd12219a..2dbe6fd34 100644 --- a/test/0.4.24/steth.test.js +++ b/test/0.4.24/steth.test.js @@ -2,16 +2,25 @@ const { assert } = require('chai') const { assertBn, assertEvent, assertAmountOfEvents } = require('@aragon/contract-helpers-test/src/asserts') const { assertRevert } = require('../helpers/assertThrow') const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') +const { tokens, ETH } = require('./../helpers/utils') +const { EvmSnapshot } = require('../helpers/blockchain') +const { INITIAL_HOLDER } = require('../helpers/constants') -const StETH = artifacts.require('StETHMock') - -const tokens = (value) => web3.utils.toWei(value + '', 'ether') +const StETHMock = artifacts.require('StETHMock') contract('StETH', ([_, __, user1, user2, user3, nobody]) => { let stEth + const snapshot = new EvmSnapshot(hre.ethers.provider) + + before('deploy mock token', async () => { + stEth = await StETHMock.new({ value: ETH(1) }) + await stEth.setTotalPooledEther(ETH(1)) - beforeEach('deploy mock token', async () => { - stEth = await StETH.new() + await snapshot.make() + }) + + afterEach(async () => { + await snapshot.rollback() }) context('ERC20 methods', () => { @@ -23,7 +32,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { context('zero supply', async () => { it('initial total supply is correct', async () => { - assertBn(await stEth.totalSupply(), tokens(0)) + assertBn(await stEth.totalSupply(), tokens(1)) }) it('initial balances are correct', async () => { @@ -51,16 +60,6 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { assertBn(await stEth.allowance(user1, user2), tokens(1)) }) - it(`transfers works with no pooled ehter, balances aren't changed`, async () => { - stEth.transfer(user1, tokens(1), { from: user2 }) - stEth.transfer(user2, tokens(100), { from: user3 }) - stEth.transfer(user3, tokens(1000), { from: user1 }) - - assertBn(await stEth.balanceOf(user1), tokens(0)) - assertBn(await stEth.balanceOf(user2), tokens(0)) - assertBn(await stEth.balanceOf(user3), tokens(0)) - }) - it(`balances aren't changed even if total pooled ether increased`, async () => { await stEth.setTotalPooledEther(tokens(100)) assertBn(await stEth.totalSupply(), tokens(100)) @@ -74,7 +73,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { context('with non-zero supply', async () => { beforeEach(async () => { await stEth.setTotalPooledEther(tokens(100)) - await stEth.mintShares(user1, tokens(100)) + await stEth.mintShares(user1, tokens(99)) }) it('total supply is correct', async () => { @@ -82,7 +81,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { }) it('balances are correct', async () => { - assertBn(await stEth.balanceOf(user1), tokens(100)) + assertBn(await stEth.balanceOf(user1), tokens(99)) assertBn(await stEth.balanceOf(user2), tokens(0)) assertBn(await stEth.balanceOf(user3), tokens(0)) }) @@ -106,7 +105,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: user2, value: amount } }) assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: user2, sharesValue: sharesAmount } }) assertBn(await stEth.balanceOf(user1), tokens(0)) - assertBn(await stEth.balanceOf(user2), tokens(100)) + assertBn(await stEth.balanceOf(user2), tokens(99)) }) it('transfer zero tokens works and emits event', async () => { @@ -117,7 +116,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { assertAmountOfEvents(receipt, 'TransferShares', { expectedAmount: 1 }) assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: user2, value: amount } }) assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: user2, sharesValue: sharesAmount } }) - assertBn(await stEth.balanceOf(user1), tokens(100)) + assertBn(await stEth.balanceOf(user1), tokens(99)) assertBn(await stEth.balanceOf(user2), tokens(0)) }) }) @@ -186,7 +185,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: user3, value: amount } }) assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: user3, sharesValue: sharesAmount } }) assertBn(await stEth.allowance(user2, user1), bn(0)) - assertBn(await stEth.balanceOf(user1), tokens(50)) + assertBn(await stEth.balanceOf(user1), tokens(49)) assertBn(await stEth.balanceOf(user3), tokens(50)) }) }) @@ -267,7 +266,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { context('with non-zero supply', async () => { beforeEach(async () => { await stEth.setTotalPooledEther(tokens(100)) - await stEth.mintShares(user1, tokens(100)) + await stEth.mintShares(user1, tokens(99)) // 1 ETH is initial hold }) it('stop/resume works', async () => { @@ -301,7 +300,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { assert.equal(await stEth.isStopped(), false) await stEth.transfer(user2, tokens(2), { from: user1 }) - assertBn(await stEth.balanceOf(user1), tokens(96)) + assertBn(await stEth.balanceOf(user1), tokens(95)) assertBn(await stEth.balanceOf(user2), tokens(4)) }) @@ -310,19 +309,19 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { await stEth.setTotalPooledEther(tokens(50)) - assertBn(await stEth.balanceOf(user1), tokens(50)) - assertBn(await stEth.sharesOf(user1), tokens(100)) + assertBn(await stEth.balanceOf(user1), tokens(49.5)) + assertBn(await stEth.sharesOf(user1), tokens(99)) assertBn(await stEth.allowance(user1, user2), tokens(75)) await assertRevert(stEth.transferFrom(user1, user2, tokens(75), { from: user2 })) await assertRevert(stEth.transferFrom(user1, user2, bn(tokens(50)).addn(10), { from: user2 })) - await stEth.transferFrom(user1, user2, tokens(50), { from: user2 }) + await stEth.transferFrom(user1, user2, tokens(49.5), { from: user2 }) assertBn(await stEth.balanceOf(user1), tokens(0)) assertBn(await stEth.sharesOf(user1), tokens(0)) - assertBn(await stEth.balanceOf(user2), tokens(50)) - assertBn(await stEth.sharesOf(user2), tokens(100)) + assertBn(await stEth.balanceOf(user2), tokens(49.5)) + assertBn(await stEth.sharesOf(user2), tokens(99)) }) context('mint', () => { @@ -332,20 +331,20 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { await stEth.setTotalPooledEther(tokens(112)) assertBn(await stEth.totalSupply(), tokens(112)) - assertBn(await stEth.balanceOf(user1), tokens(112)) + assertBn(await stEth.balanceOf(user1), tokens(111)) assertBn(await stEth.balanceOf(user2), tokens(0)) assertBn(await stEth.getTotalShares(), tokens(112)) - assertBn(await stEth.sharesOf(user1), tokens(112)) + assertBn(await stEth.sharesOf(user1), tokens(111)) assertBn(await stEth.sharesOf(user2), tokens(0)) await stEth.mintShares(user2, tokens(4)) await stEth.setTotalPooledEther(tokens(116)) assertBn(await stEth.totalSupply(), tokens(116)) - assertBn(await stEth.balanceOf(user1), tokens(112)) + assertBn(await stEth.balanceOf(user1), tokens(111)) assertBn(await stEth.balanceOf(user2), tokens(4)) assertBn(await stEth.getTotalShares(), tokens(116)) - assertBn(await stEth.sharesOf(user1), tokens(112)) + assertBn(await stEth.sharesOf(user1), tokens(111)) assertBn(await stEth.sharesOf(user2), tokens(4)) }) @@ -356,9 +355,9 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { context('burn', () => { beforeEach(async () => { - // user1 already had 100 tokens - // 100 + 100 + 100 = 300 - await stEth.setTotalPooledEther(tokens(300)) + // user1 already had 99 tokens + // 1 + 99 + 100 + 100 = 300 + await stEth.setTotalPooledEther(ETH(300)) await stEth.mintShares(user2, tokens(100)) await stEth.mintShares(user3, tokens(100)) }) @@ -383,11 +382,11 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { }) assertBn(await stEth.totalSupply(), tokens(300)) - assertBn(await stEth.balanceOf(user1), tokens(100)) + assertBn(await stEth.balanceOf(user1), tokens(99)) assertBn(await stEth.balanceOf(user2), tokens(100)) assertBn(await stEth.balanceOf(user3), tokens(100)) assertBn(await stEth.getTotalShares(), tokens(300)) - assertBn(await stEth.sharesOf(user1), tokens(100)) + assertBn(await stEth.sharesOf(user1), tokens(99)) assertBn(await stEth.sharesOf(user2), tokens(100)) assertBn(await stEth.sharesOf(user3), tokens(100)) }) @@ -395,19 +394,21 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { it('burning works (redistributes tokens)', async () => { const totalShares = await stEth.getTotalShares() const totalSupply = await stEth.totalSupply() - const user1Balance = await stEth.balanceOf(user1) - const user1Shares = await stEth.sharesOf(user1) + const user2Balance = await stEth.balanceOf(user2) + const user2Shares = await stEth.sharesOf(user2) const sharesToBurn = totalShares.sub( - totalSupply.mul(totalShares.sub(user1Shares)).div(totalSupply.sub(user1Balance).add(bn(tokens(10)))) + totalSupply + .mul(totalShares.sub(user2Shares)) + .div(totalSupply.sub(user2Balance).add(bn(tokens(10)))) ) const expectedPreTokenAmount = await stEth.getPooledEthByShares(sharesToBurn) - const receipt = await stEth.burnShares(user1, sharesToBurn) + const receipt = await stEth.burnShares(user2, sharesToBurn) const expectedPostTokenAmount = await stEth.getPooledEthByShares(sharesToBurn) assertEvent(receipt, 'SharesBurnt', { expectedArgs: { - account: user1, + account: user2, preRebaseTokenAmount: expectedPreTokenAmount, postRebaseTokenAmount: expectedPostTokenAmount, sharesAmount: sharesToBurn @@ -415,62 +416,64 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { }) assertBn(await stEth.totalSupply(), tokens(300)) - assertBn(await stEth.balanceOf(user1), bn(tokens(90)).subn(1)) // expected round error - assertBn(await stEth.balanceOf(user2), tokens(105)) + assertBn((await stEth.balanceOf(user1)).add(await stEth.balanceOf(INITIAL_HOLDER)), tokens(105)) + assertBn(await stEth.balanceOf(user2), bn(tokens(90)).subn(1)) // expected round error assertBn(await stEth.balanceOf(user3), tokens(105)) assertBn(await stEth.getTotalShares(), bn('285714285714285714285')) - assertBn(await stEth.sharesOf(user1), bn('85714285714285714285')) - assertBn(await stEth.sharesOf(user2), tokens(100)) + assertBn(await stEth.sharesOf(INITIAL_HOLDER), tokens(1)) + assertBn(await stEth.sharesOf(user1), tokens(99)) + assertBn(await stEth.sharesOf(user2), bn('85714285714285714285')) assertBn(await stEth.sharesOf(user3), tokens(100)) }) it('allowance behavior is correct after burning', async () => { - await stEth.approve(user2, tokens(75), { from: user1 }) + await stEth.approve(user3, tokens(75), { from: user2 }) const totalShares = await stEth.getTotalShares() const totalSupply = await stEth.totalSupply() - const user1Balance = await stEth.balanceOf(user1) - const user1Shares = await stEth.sharesOf(user1) + const user2Balance = await stEth.balanceOf(user2) + const user2Shares = await stEth.sharesOf(user2) const sharesToBurn = totalShares.sub( - totalSupply.mul(totalShares.sub(user1Shares)).div(totalSupply.sub(user1Balance).add(bn(tokens(50)))) + totalSupply.mul(totalShares.sub(user2Shares)) + .div(totalSupply.sub(user2Balance).add(bn(tokens(50)))) ) const expectedPreTokenAmount = await stEth.getPooledEthByShares(sharesToBurn) - const receipt = await stEth.burnShares(user1, sharesToBurn) + const receipt = await stEth.burnShares(user2, sharesToBurn) const expectedPostTokenAmount = await stEth.getPooledEthByShares(sharesToBurn) assertEvent(receipt, 'SharesBurnt', { expectedArgs: { - account: user1, + account: user2, preRebaseTokenAmount: expectedPreTokenAmount, postRebaseTokenAmount: expectedPostTokenAmount, sharesAmount: sharesToBurn } }) - assertBn(await stEth.balanceOf(user1), tokens(50)) + assertBn(await stEth.balanceOf(user2), tokens(50)) - assertBn(await stEth.allowance(user1, user2), tokens(75)) + assertBn(await stEth.allowance(user2, user3), tokens(75)) - await assertRevert(stEth.transferFrom(user1, user2, tokens(75), { from: user2 })) - await assertRevert(stEth.transferFrom(user1, user2, bn(tokens(50)).addn(10), { from: user2 })) - await stEth.transferFrom(user1, user2, tokens(50), { from: user2 }) + await assertRevert(stEth.transferFrom(user2, user3, tokens(75), { from: user3 })) + await assertRevert(stEth.transferFrom(user2, user3, bn(tokens(50)).addn(10), { from: user3 })) + await stEth.transferFrom(user2, user3, tokens(50), { from: user3 }) }) }) }) context('share-related getters and transfers', async () => { - context('with zero totalPooledEther (supply)', async () => { + context('with initial totalPooledEther (supply)', async () => { it('getTotalSupply', async () => { - assertBn(await stEth.totalSupply({ from: nobody }), tokens(0)) + assertBn(await stEth.totalSupply({ from: nobody }), tokens(1)) }) it('getTotalShares', async () => { - assertBn(await stEth.getTotalShares(), tokens(0)) + assertBn(await stEth.getTotalShares(), tokens(1)) }) it('getTotalPooledEther', async () => { - assertBn(await stEth.getTotalPooledEther(), tokens(0)) + assertBn(await stEth.getTotalPooledEther(), tokens(1)) }) it('sharesOf', async () => { @@ -479,8 +482,8 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { it('getPooledEthByShares', async () => { assertBn(await stEth.getPooledEthByShares(tokens(0)), tokens(0)) - assertBn(await stEth.getPooledEthByShares(tokens(1)), tokens(0)) - assertBn(await stEth.getPooledEthByShares(tokens(100)), tokens(0)) + assertBn(await stEth.getPooledEthByShares(tokens(1)), tokens(1)) + assertBn(await stEth.getPooledEthByShares(tokens(100)), tokens(100)) }) it('balanceOf', async () => { @@ -488,9 +491,9 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { }) it('getSharesByPooledEth', async () => { - assertBn(await stEth.getSharesByPooledEth(tokens(1)), tokens(0)) + assertBn(await stEth.getSharesByPooledEth(tokens(1)), tokens(1)) assertBn(await stEth.getSharesByPooledEth(tokens(0)), tokens(0)) - assertBn(await stEth.getSharesByPooledEth(tokens(100)), tokens(0)) + assertBn(await stEth.getSharesByPooledEth(tokens(100)), tokens(100)) }) it('transferShares', async () => { @@ -514,10 +517,10 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { }) }) - context('with non-zero totalPooledEther (supply)', async () => { + context('with additional totalPooledEther (supply)', async () => { beforeEach(async () => { await stEth.setTotalPooledEther(tokens(100)) - await stEth.mintShares(user1, tokens(100)) + await stEth.mintShares(user1, tokens(99)) }) it('getTotalSupply', async () => { @@ -533,7 +536,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { }) it('sharesOf', async () => { - assertBn(await stEth.sharesOf(user1), tokens(100)) + assertBn(await stEth.sharesOf(user1), tokens(99)) }) it('getPooledEthByShares', async () => { @@ -543,7 +546,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { }) it('balanceOf', async () => { - assertBn(await stEth.balanceOf(user1), tokens(100)) + assertBn(await stEth.balanceOf(user1), tokens(99)) assertBn(await stEth.balanceOf(user2), tokens(0)) }) @@ -554,7 +557,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { }) it('transferShares', async () => { - assertBn(await stEth.balanceOf(user1), tokens(100)) + assertBn(await stEth.balanceOf(user1), tokens(99)) assertBn(await stEth.balanceOf(nobody), tokens(0)) let receipt = await stEth.transferShares(nobody, tokens(0), { from: user1 }) @@ -563,7 +566,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: nobody, value: tokens(0) } }) assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: nobody, sharesValue: tokens(0) } }) - assertBn(await stEth.balanceOf(user1), tokens(100)) + assertBn(await stEth.balanceOf(user1), tokens(99)) assertBn(await stEth.balanceOf(nobody), tokens(0)) receipt = await stEth.transferShares(nobody, tokens(30), { from: user1 }) @@ -572,25 +575,27 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: nobody, value: tokens(30) } }) assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: nobody, sharesValue: tokens(30) } }) - assertBn(await stEth.balanceOf(user1), tokens(70)) + assertBn(await stEth.balanceOf(user1), tokens(69)) assertBn(await stEth.balanceOf(nobody), tokens(30)) await assertRevert(stEth.transferShares(nobody, tokens(75), { from: user1 }), 'TRANSFER_AMOUNT_EXCEEDS_BALANCE') await stEth.setTotalPooledEther(tokens(120)) - receipt = await stEth.transferShares(nobody, tokens(70), { from: user1 }) + const tokensToTransfer = tokens(120 * 69 / 100) + + receipt = await stEth.transferShares(nobody, tokens(69), { from: user1 }) assertAmountOfEvents(receipt, 'Transfer', { expectedAmount: 1 }) assertAmountOfEvents(receipt, 'TransferShares', { expectedAmount: 1 }) - assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: nobody, value: tokens(84) } }) - assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: nobody, sharesValue: tokens(70) } }) + assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: nobody, value: tokensToTransfer } }) + assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: nobody, sharesValue: tokens(69) } }) assertBn(await stEth.balanceOf(user1), tokens(0)) - assertBn(await stEth.balanceOf(nobody), tokens(120)) + assertBn(await stEth.balanceOf(nobody), '118800000000000000000') }) it('transferSharesFrom', async () => { - assertBn(await stEth.balanceOf(user1), tokens(100)) + assertBn(await stEth.balanceOf(user1), tokens(99)) assertBn(await stEth.balanceOf(nobody), tokens(0)) let receipt = await stEth.transferSharesFrom(user1, nobody, tokens(0), { from: user2 }) @@ -599,7 +604,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: nobody, value: tokens(0) } }) assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: nobody, sharesValue: tokens(0) } }) - assertBn(await stEth.balanceOf(user1), tokens(100)) + assertBn(await stEth.balanceOf(user1), tokens(99)) assertBn(await stEth.balanceOf(nobody), tokens(0)) await assertRevert( @@ -613,7 +618,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: nobody, value: tokens(30) } }) assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: nobody, sharesValue: tokens(30) } }) - assertBn(await stEth.balanceOf(user1), tokens(70)) + assertBn(await stEth.balanceOf(user1), tokens(69)) assertBn(await stEth.balanceOf(nobody), tokens(30)) await assertRevert(stEth.transferSharesFrom(user1, nobody, tokens(75), { from: user2 }), 'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE') @@ -625,14 +630,14 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { await assertRevert(stEth.transferSharesFrom(user1, nobody, tokens(70), { from: user2 }), 'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE') await stEth.approve(user2, tokens(84), { from: user1 }) - receipt = await stEth.transferSharesFrom(user1, nobody, tokens(70), { from: user2 }) + receipt = await stEth.transferSharesFrom(user1, nobody, tokens(69), { from: user2 }) assertAmountOfEvents(receipt, 'Transfer', { expectedAmount: 1 }) assertAmountOfEvents(receipt, 'TransferShares', { expectedAmount: 1 }) - assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: nobody, value: tokens(84) } }) - assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: nobody, sharesValue: tokens(70) } }) + assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: nobody, value: '82800000000000000000' } }) + assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: nobody, sharesValue: tokens(69) } }) assertBn(await stEth.balanceOf(user1), tokens(0)) - assertBn(await stEth.balanceOf(nobody), tokens(120)) + assertBn(await stEth.balanceOf(nobody), '118800000000000000000') }) }) }) diff --git a/test/0.4.24/stethpermit.test.js b/test/0.4.24/stethpermit.test.js index 62d99b28d..b9290fde8 100644 --- a/test/0.4.24/stethpermit.test.js +++ b/test/0.4.24/stethpermit.test.js @@ -1,26 +1,32 @@ const crypto = require('crypto') const { ACCOUNTS_AND_KEYS, MAX_UINT256, ZERO_ADDRESS } = require('./helpers/constants') const { bn } = require('@aragon/contract-helpers-test') -const { assertBn, assertEvent } = require('@aragon/contract-helpers-test/src/asserts') -const { assertRevert } = require('../helpers/assertThrow') +const { assert } = require('../helpers/assert') const { signPermit, signTransferAuthorization, makeDomainSeparator } = require('./helpers/permit_helpers') const { hexStringFromBuffer } = require('./helpers/sign_utils') const { ETH } = require('../helpers/utils') +const { EvmSnapshot } = require('../helpers/blockchain') const EIP712StETH = artifacts.require('EIP712StETH') const StETHPermit = artifacts.require('StETHPermitMock') contract('StETHPermit', ([deployer, ...accounts]) => { - let stEthPermit, chainId, domainSeparator + let stEthPermit, eip712StETH, chainId, domainSeparator + const snapshot = new EvmSnapshot(hre.ethers.provider) - beforeEach('deploy mock token', async () => { - const eip712StETH = await EIP712StETH.new({ from: deployer }) - stEthPermit = await StETHPermit.new({ from: deployer }) + before('deploy mock token', async () => { + stEthPermit = await StETHPermit.new({ from: deployer, value: ETH(1) }) + eip712StETH = await EIP712StETH.new(stEthPermit.address, { from: deployer }) await stEthPermit.initializeEIP712StETH(eip712StETH.address) chainId = await web3.eth.net.getId(); - domainSeparator = makeDomainSeparator('Liquid staked Ether 2.0', '2', chainId, eip712StETH.address) + domainSeparator = makeDomainSeparator('Liquid staked Ether 2.0', '2', chainId, stEthPermit.address) + await snapshot.make() + }) + + afterEach(async () => { + await snapshot.rollback() }) context('permit', () => { @@ -43,6 +49,31 @@ contract('StETHPermit', ([deployer, ...accounts]) => { await stEthPermit.mintShares(permitParams.owner, initialBalance, { from: deployer }) }) + it('EIP-712 signature helper reverts when zero stETH address passed', async () => { + await assert.revertsWithCustomError( + EIP712StETH.new(ZERO_ADDRESS, { from: deployer }), + `ZeroStETHAddress()` + ) + }) + + it('EIP-712 signature helper contract matches the stored one', async () => { + assert.equals(await stEthPermit.getEIP712StETH(), eip712StETH.address) + }) + + it('eip712Domain() is correct', async () => { + const { name, version, chainId, verifyingContract } = await stEthPermit.eip712Domain() + + assert.equals(name, 'Liquid staked Ether 2.0') + assert.equals(version, '2') + assert.equals(chainId, await web3.eth.net.getId()) + assert.equals(verifyingContract, stEthPermit.address) + + assert.equals( + makeDomainSeparator(name, version, chainId, verifyingContract), + domainSeparator + ) + }) + it('grants allowance when a valid permit is given', async () => { const { owner, spender, deadline } = permitParams let { value } = permitParams @@ -53,11 +84,11 @@ contract('StETHPermit', ([deployer, ...accounts]) => { let { v, r, s } = signPermit(owner, spender, value, nonce, deadline, domainSeparator, alice.key) // check that the allowance is initially zero - assertBn(await stEthPermit.allowance(owner, spender), bn(0)) + assert.equals(await stEthPermit.allowance(owner, spender), bn(0)) // check that the next nonce expected is zero - assertBn(await stEthPermit.nonces(owner), bn(0)) + assert.equals(await stEthPermit.nonces(owner), bn(0)) // check domain separator - assert.equal( + assert.equals( await stEthPermit.DOMAIN_SEPARATOR(), domainSeparator ) @@ -68,15 +99,15 @@ contract('StETHPermit', ([deployer, ...accounts]) => { ) // check that allowance is updated - assertBn(await stEthPermit.allowance(owner, spender), bn(value)) + assert.equals(await stEthPermit.allowance(owner, spender), bn(value)) - assertEvent( + assert.emits( receipt, 'Approval', - { expectedArgs: { owner: owner, spender: spender, value: bn(value) } } + { owner: owner, spender: spender, value: bn(value) } ) - assertBn(await stEthPermit.nonces(owner), bn(1)) + assert.equals(await stEthPermit.nonces(owner), bn(1)) // increment nonce nonce = 1 @@ -88,15 +119,15 @@ contract('StETHPermit', ([deployer, ...accounts]) => { const receipt2 = await stEthPermit.permit(owner, spender, value, deadline, v, r, s, { from: charlie }) // check that allowance is updated - assertBn(await stEthPermit.allowance(owner, spender), bn(value)) + assert.equals(await stEthPermit.allowance(owner, spender), bn(value)) - assertEvent( + assert.emits( receipt2, 'Approval', - { expectedArgs: { owner: owner, spender: spender, value: bn(value) } } + { owner: owner, spender: spender, value: bn(value) } ) - assertBn(await stEthPermit.nonces(owner), bn(2)) + assert.equals(await stEthPermit.nonces(owner), bn(2)) }) it('reverts if the signature does not match given parameters', async () => { @@ -105,7 +136,7 @@ contract('StETHPermit', ([deployer, ...accounts]) => { const { v, r, s } = signPermit(owner, spender, value, nonce, deadline, domainSeparator, alice.key) // try to cheat by claiming the approved amount + 1 - await assertRevert( + await assert.reverts( stEthPermit.permit( owner, spender, @@ -120,7 +151,7 @@ contract('StETHPermit', ([deployer, ...accounts]) => { ) // check that msg is incorrect even if claim the approved amount - 1 - await assertRevert( + await assert.reverts( stEthPermit.permit( owner, spender, @@ -143,7 +174,7 @@ contract('StETHPermit', ([deployer, ...accounts]) => { // try to cheat by submitting the permit that is signed by a // wrong person - await assertRevert( + await assert.reverts( stEthPermit.permit(owner, spender, value, deadline, v, r, s, { from: charlie }), @@ -155,7 +186,7 @@ contract('StETHPermit', ([deployer, ...accounts]) => { await web3.eth.sendTransaction({ to: bob.address, from: accounts[0], value: ETH(10) }) // even Bob himself can't call permit with the invalid sig - await assertRevert( + await assert.reverts( stEthPermit.permit(owner, spender, value, deadline, v, r, s, { from: bob.address }), @@ -170,7 +201,7 @@ contract('StETHPermit', ([deployer, ...accounts]) => { const { v, r, s } = signPermit(owner, spender, value, nonce, deadline, domainSeparator, alice.key) // try to submit the permit that is expired - await assertRevert( + await assert.reverts( stEthPermit.permit(owner, spender, value, deadline, v, r, s, { from: charlie }), @@ -183,11 +214,11 @@ contract('StETHPermit', ([deployer, ...accounts]) => { const { v, r, s } = signPermit(owner, spender, value, nonce, deadline1min, domainSeparator, alice.key) const receipt = await stEthPermit.permit(owner, spender, value, deadline1min, v, r, s, { from: charlie }) - assertBn(await stEthPermit.nonces(owner), bn(1)) - assertEvent( + assert.equals(await stEthPermit.nonces(owner), bn(1)) + assert.emits( receipt, 'Approval', - { expectedArgs: { owner: owner, spender: spender, value: bn(value) } } + { owner: owner, spender: spender, value: bn(value) } ) } }) @@ -198,10 +229,10 @@ contract('StETHPermit', ([deployer, ...accounts]) => { // create a signed permit const { v, r, s } = signPermit(owner, spender, value, nonce, deadline, domainSeparator, alice.key) // check that the next nonce expected is 0, not 1 - assertBn(await stEthPermit.nonces(owner), bn(0)) + assert.equals(await stEthPermit.nonces(owner), bn(0)) // try to submit the permit - await assertRevert( + await assert.reverts( stEthPermit.permit(owner, spender, value, deadline, v, r, s, { from: charlie }), @@ -218,7 +249,7 @@ contract('StETHPermit', ([deployer, ...accounts]) => { await stEthPermit.permit(owner, spender, value, deadline, v, r, s, { from: charlie }) // try to submit the permit again - await assertRevert( + await assert.reverts( stEthPermit.permit(owner, spender, value, deadline, v, r, s, { from: charlie }), @@ -230,7 +261,7 @@ contract('StETHPermit', ([deployer, ...accounts]) => { await web3.eth.sendTransaction({ to: alice.address, from: accounts[0], value: ETH(10) }) // try to submit the permit again from Alice herself - await assertRevert( + await assert.reverts( stEthPermit.permit(owner, spender, value, deadline, v, r, s, { from: alice.address }), @@ -251,7 +282,7 @@ contract('StETHPermit', ([deployer, ...accounts]) => { const permit2 = signPermit(owner, spender, 1e6, nonce, deadline, domainSeparator, alice.key) // try to submit the permit again - await assertRevert( + await assert.reverts( stEthPermit.permit(owner, spender, 1e6, deadline, permit2.v, permit2.r, permit2.s, { from: charlie }), 'ERC20Permit: invalid signature' ) @@ -265,7 +296,7 @@ contract('StETHPermit', ([deployer, ...accounts]) => { const { v, r, s } = signPermit(owner, spender, value, nonce, deadline, domainSeparator, alice.key) // try to submit the permit with invalid approval parameters - await assertRevert( + await assert.reverts( stEthPermit.permit(owner, spender, value, deadline, v, r, s, { from: charlie }), @@ -281,7 +312,7 @@ contract('StETHPermit', ([deployer, ...accounts]) => { const { v, r, s } = signTransferAuthorization(from, to, value, validAfter, validBefore, nonce, domainSeparator, alice.key) // try to submit the transfer permit - await assertRevert( + await assert.reverts( stEthPermit.permit(from, to, value, validBefore, v, r, s, { from: charlie }), diff --git a/test/0.6.12/wsteth.test.js b/test/0.6.12/wsteth.test.js index 1eedee781..94afc5764 100644 --- a/test/0.6.12/wsteth.test.js +++ b/test/0.6.12/wsteth.test.js @@ -3,14 +3,23 @@ const { expect } = require('chai') const { ZERO_ADDRESS } = constants const { shouldBehaveLikeERC20 } = require('./helpers/ERC20.behavior') +const { EvmSnapshot } = require('../helpers/blockchain') const WstETH = artifacts.require('WstETHMock') const StETH = artifacts.require('StETHMockERC20') contract('WstETH', function ([deployer, initialHolder, recipient, anotherAccount, ...otherAccounts]) { - beforeEach(async function () { + const snapshot = new EvmSnapshot(hre.ethers.provider) + + before(async function () { this.steth = await StETH.new({ from: deployer }) this.wsteth = await WstETH.new(this.steth.address, { from: deployer }) + + await snapshot.make() + }) + + afterEach(async () => { + await snapshot.rollback() }) describe(`Wrapping / Unwrapping`, function () { diff --git a/test/0.8.9/burner.test.js b/test/0.8.9/burner.test.js index 54e89d588..14da006d8 100644 --- a/test/0.8.9/burner.test.js +++ b/test/0.8.9/burner.test.js @@ -5,6 +5,7 @@ const { EvmSnapshot } = require('../helpers/blockchain') const { ETH, StETH } = require('../helpers/utils') const { assert } = require('../helpers/assert') const { deployProtocol } = require('../helpers/protocol') +const { INITIAL_HOLDER } = require('../helpers/constants') const Burner = artifacts.require('Burner.sol') @@ -50,12 +51,13 @@ contract('Burner', ([deployer, _, anotherAccount]) => { // stake ether to get an stETH in exchange await web3.eth.sendTransaction({ from: anotherAccount, to: lido.address, value: ETH(20) }) await web3.eth.sendTransaction({ from: deployer, to: lido.address, value: ETH(30) }) - await web3.eth.sendTransaction({ from: voting, to: lido.address, value: ETH(25) }) + await web3.eth.sendTransaction({ from: voting, to: lido.address, value: ETH(24) }) - // check stETH balances + // check stETH balances 1 + 20 + 30 + 24 = 75 + assert.equals(await lido.balanceOf(INITIAL_HOLDER), StETH(1)) assert.equals(await lido.balanceOf(anotherAccount), StETH(20)) assert.equals(await lido.balanceOf(deployer), StETH(30)) - assert.equals(await lido.balanceOf(voting), StETH(25)) + assert.equals(await lido.balanceOf(voting), StETH(24)) }) it(`init with already burnt counters works`, async () => { @@ -162,7 +164,7 @@ contract('Burner', ([deployer, _, anotherAccount]) => { // check stETH balances assert.equals(await lido.balanceOf(burner.address), StETH(8)) - assert.equals(await lido.balanceOf(voting), StETH(17)) + assert.equals(await lido.balanceOf(voting), StETH(16)) const sharesAmount12 = sharesAmount8StETH.mul(bn(3)).div(bn(2)) await lido.approve(burner.address, StETH(13), { from: voting }) @@ -177,7 +179,7 @@ contract('Burner', ([deployer, _, anotherAccount]) => { // check stETH balances again, we didn't execute the actual burn assert.equals(await lido.balanceOf(burner.address), StETH(20)) - assert.equals(await lido.balanceOf(voting), StETH(5)) + assert.equals(await lido.balanceOf(voting), StETH(4)) }) it(`invoke commitSharesToBurn without requested burn works`, async () => { @@ -344,26 +346,27 @@ contract('Burner', ([deployer, _, anotherAccount]) => { }) it(`a positive rebase happens after the burn application`, async () => { - await lido.approve(burner.address, StETH(25), { from: voting }) - await burner.requestBurnMyStETHForCover(StETH(25), { from: voting }) + await lido.approve(burner.address, StETH(24), { from: voting }) + await burner.requestBurnMyStETHForCover(StETH(24), { from: voting }) - assert.equals(await lido.balanceOf(burner.address), StETH(25)) + assert.equals(await lido.balanceOf(burner.address), StETH(24)) assert.equals(await lido.balanceOf(voting), StETH(0)) assert.equals(await lido.balanceOf(anotherAccount), StETH(20)) assert.equals(await lido.balanceOf(deployer), StETH(30)) await burner.commitSharesToBurn(ETH(50), { from: lido.address }) - await lido.burnShares(burner.address, await lido.getPooledEthByShares(StETH(25))) + await lido.burnShares(burner.address, await lido.getPooledEthByShares(StETH(24))) assert.equals(await lido.balanceOf(burner.address), StETH(0)) assert.equals(await lido.balanceOf(voting), StETH(0)) - // 1/3 of the shares amount was burnt, so remaining stETH becomes more 'expensive' - // totalShares become 2/3 of the previous value - // so the new share price increases by 3/2 - assert.equals(await lido.balanceOf(deployer), bn(StETH(30)).mul(bn(3)).div(bn(2))) - assert.equals(await lido.balanceOf(anotherAccount), bn(StETH(20)).mul(bn(3)).div(bn(2))) + // 24/75 of the shares amount was burnt, so remaining stETH becomes more 'expensive' + // totalShares become 51/75 of the previous value + // so the new share price increases by 75/51 + + assert.equals(await lido.balanceOf(deployer), bn(StETH(30 * 75)).divn(51)) + assert.equals(await lido.balanceOf(anotherAccount), bn(StETH(20 * 75)).divn(51)) }) it(`limit burn shares per run works (cover)`, async () => { @@ -378,7 +381,7 @@ contract('Burner', ([deployer, _, anotherAccount]) => { assert.emits( receipt, `StETHBurnt`, { - isCover: true, amountOfStETH: StETH(0.9), amountOfShares: stETHShares(0.9) + isCover: true, amountOfStETH: StETH(0.9), amountOfShares: stETHShares(0.9) }) assert.emitsNumberOfEvents(receipt, `StETHBurnt`, 1) await lido.burnShares(burner.address, stETHShares(0.9)) @@ -408,7 +411,7 @@ contract('Burner', ([deployer, _, anotherAccount]) => { assert.emits( receiptN, `StETHBurnt`, { - isCover: true, amountOfStETH: await lido.getPooledEthByShares(burnt), amountOfShares: burnt + isCover: true, amountOfStETH: await lido.getPooledEthByShares(burnt), amountOfShares: burnt }) await lido.burnShares(burner.address, burnt) diff --git a/test/0.8.9/deposit-security-module.test.js b/test/0.8.9/deposit-security-module.test.js index c02f999ee..663227ca2 100644 --- a/test/0.8.9/deposit-security-module.test.js +++ b/test/0.8.9/deposit-security-module.test.js @@ -86,7 +86,7 @@ contract('DepositSecurityModule', ([owner, stranger, guardian]) => { for (let i = 0; i < numBlocksToMine; ++i) { await network.provider.send('evm_mine') } - return web3.eth.getBlock('latest') + return await web3.eth.getBlock('latest') } describe('depositBufferedEther', () => { @@ -970,7 +970,7 @@ contract('DepositSecurityModule', ([owner, stranger, guardian]) => { assert.isTrue((await depositSecurityModule.getGuardianQuorum()) > 0, 'invariant failed: quorum > 0') const lastDepositBlockNumber = await web3.eth.getBlockNumber() - stakingRouterMock.setStakingModuleLastDepositBlock(lastDepositBlockNumber) + await stakingRouterMock.setStakingModuleLastDepositBlock(lastDepositBlockNumber) await waitBlocks(2 * MIN_DEPOSIT_BLOCK_DISTANCE) const currentBlockNumber = await web3.eth.getBlockNumber() @@ -984,7 +984,7 @@ contract('DepositSecurityModule', ([owner, stranger, guardian]) => { assert.isTrue((await depositSecurityModule.getGuardianQuorum()) > 0, 'invariant failed: quorum > 0') const lastDepositBlockNumber = await web3.eth.getBlockNumber() - stakingRouterMock.setStakingModuleLastDepositBlock(lastDepositBlockNumber) + await stakingRouterMock.setStakingModuleLastDepositBlock(lastDepositBlockNumber) const latestBlock = await waitBlocks(2 * MIN_DEPOSIT_BLOCK_DISTANCE) const minDepositBlockDistance = await depositSecurityModule.getMinDepositBlockDistance() @@ -1003,7 +1003,7 @@ contract('DepositSecurityModule', ([owner, stranger, guardian]) => { assert.equal(await stakingRouterMock.getStakingModuleIsDepositsPaused(STAKING_MODULE), false, 'invariant failed: isPaused') const lastDepositBlockNumber = await web3.eth.getBlockNumber() - stakingRouterMock.setStakingModuleLastDepositBlock(lastDepositBlockNumber) + await stakingRouterMock.setStakingModuleLastDepositBlock(lastDepositBlockNumber) await waitBlocks(2 * MIN_DEPOSIT_BLOCK_DISTANCE) const currentBlockNumber = await web3.eth.getBlockNumber() @@ -1021,7 +1021,7 @@ contract('DepositSecurityModule', ([owner, stranger, guardian]) => { assert.equal(await stakingRouterMock.getStakingModuleIsDepositsPaused(STAKING_MODULE), false, 'invariant failed: isPaused') const lastDepositBlockNumber = await web3.eth.getBlockNumber() - stakingRouterMock.setStakingModuleLastDepositBlock(lastDepositBlockNumber) + await stakingRouterMock.setStakingModuleLastDepositBlock(lastDepositBlockNumber) await waitBlocks(Math.floor(MIN_DEPOSIT_BLOCK_DISTANCE / 2)) const currentBlockNumber = await web3.eth.getBlockNumber() @@ -1036,7 +1036,7 @@ contract('DepositSecurityModule', ([owner, stranger, guardian]) => { assert.isTrue((await depositSecurityModule.getGuardianQuorum()) > 0, 'invariant failed: quorum > 0') const lastDepositBlockNumber = await web3.eth.getBlockNumber() - stakingRouterMock.setStakingModuleLastDepositBlock(lastDepositBlockNumber) + await stakingRouterMock.setStakingModuleLastDepositBlock(lastDepositBlockNumber) await waitBlocks(2 * MIN_DEPOSIT_BLOCK_DISTANCE) const currentBlockNumber = await web3.eth.getBlockNumber() diff --git a/test/0.8.9/lido-locator.test.js b/test/0.8.9/lido-locator.test.js index 030dbd55f..aea91152c 100644 --- a/test/0.8.9/lido-locator.test.js +++ b/test/0.8.9/lido-locator.test.js @@ -53,6 +53,27 @@ contract('LidoLocator', ([deployer, agent]) => { assert(actual === expected, 'coreComponents mismatch') } }) + + it('oracleReportComponentsForLido() matches', async () => { + const actualReportComponents = await lidoLocatorProxy.oracleReportComponentsForLido() + + const expectedReportComponents = [ + initialConfig.accountingOracle, + initialConfig.elRewardsVault, + initialConfig.oracleReportSanityChecker, + initialConfig.burner, + initialConfig.withdrawalQueue, + initialConfig.withdrawalVault, + initialConfig.postTokenRebaseReceiver + ] + + for (let i = 0; i < actualReportComponents.length; i++) { + const actual = actualReportComponents[i] + const expected = expectedReportComponents[i] + + assert(actual === expected, 'reportComponentsForLido mismatch') + } + }) }) describe('breaking constructor', () => { @@ -107,6 +128,27 @@ contract('LidoLocator', ([deployer, agent]) => { assert(actual === expected, 'coreComponents mismatch') } }) + + it('oracleReportComponentsForLido() matches', async () => { + const actualReportComponents = await lidoLocatorProxy.oracleReportComponentsForLido() + + const expectedReportComponents = [ + initialConfig.accountingOracle, + initialConfig.elRewardsVault, + initialConfig.oracleReportSanityChecker, + initialConfig.burner, + initialConfig.withdrawalQueue, + initialConfig.withdrawalVault, + initialConfig.postTokenRebaseReceiver + ] + + for (let i = 0; i < actualReportComponents.length; i++) { + const actual = actualReportComponents[i] + const expected = expectedReportComponents[i] + + assert(actual === expected, 'reportComponentsForLido mismatch') + } + }) }) }) }) diff --git a/test/0.8.9/oracle-daemon-config.test.js b/test/0.8.9/oracle-daemon-config.test.js index cbe587c46..bf6e4cc23 100644 --- a/test/0.8.9/oracle-daemon-config.test.js +++ b/test/0.8.9/oracle-daemon-config.test.js @@ -168,7 +168,7 @@ contract('OracleDaemonConfig', async ([deployer, manager, stranger]) => { it('deployer cannot unset a defaultValue', async () => { await config.set(defaultKey, defaultValue, { from: manager }) - assert.revertsOZAccessControl( + await assert.revertsOZAccessControl( config.unset(defaultKey, { from: deployer }), deployer, `CONFIG_MANAGER_ROLE` diff --git a/test/0.8.9/oracle-report-sanity-checker.test.js b/test/0.8.9/oracle-report-sanity-checker.test.js index aec6dab7d..c464de2f9 100644 --- a/test/0.8.9/oracle-report-sanity-checker.test.js +++ b/test/0.8.9/oracle-report-sanity-checker.test.js @@ -1,6 +1,7 @@ const hre = require('hardhat') const { ETH } = require('../helpers/utils') const { assert } = require('../helpers/assert') +const { getCurrentBlockTimestamp } = require('../helpers/blockchain') const mocksFilePath = 'contracts/0.8.9/test_helpers/OracleReportSanityCheckerMocks.sol' const LidoStub = hre.artifacts.require(`${mocksFilePath}:LidoStub`) @@ -27,20 +28,22 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa oneOffCLBalanceDecreaseLimitManagers: accounts.slice(4, 6), annualBalanceIncreaseLimitManagers: accounts.slice(6, 8), shareRateDeviationLimitManagers: accounts.slice(8, 10), - requestCreationBlockMarginManagers: accounts.slice(10, 12), - maxPositiveTokenRebaseManagers: accounts.slice(12, 14), - maxValidatorExitRequestsPerReportManagers: accounts.slice(14, 16), - maxAccountingExtraDataListItemsCountManagers: accounts.slice(16, 18), + maxValidatorExitRequestsPerReportManagers: accounts.slice(10, 12), + maxAccountingExtraDataListItemsCountManagers: accounts.slice(12, 14), + maxNodeOperatorsPerExtraDataItemCountManagers: accounts.slice(14, 16), + requestTimestampMarginManagers: accounts.slice(16, 18), + maxPositiveTokenRebaseManagers: accounts.slice(18, 20), } const defaultLimitsList = { churnValidatorsPerDayLimit: 55, oneOffCLBalanceDecreaseBPLimit: 5_00, // 5% annualBalanceIncreaseBPLimit: 10_00, // 10% - shareRateDeviationBPLimit: 2_50, // 2.5% - requestTimestampMargin: 128, - maxPositiveTokenRebase: 5_000_000, // 0.05% + simulatedShareRateDeviationBPLimit: 2_50, // 2.5% maxValidatorExitRequestsPerReport: 2000, maxAccountingExtraDataListItemsCount: 15, + maxNodeOperatorsPerExtraDataItemCount: 16, + requestTimestampMargin: 128, + maxPositiveTokenRebase: 5_000_000, // 0.05% } const correctLidoOracleReport = { timeElapsed: 24 * 60 * 60, @@ -79,21 +82,23 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa churnValidatorsPerDayLimit: 50, oneOffCLBalanceDecreaseBPLimit: 10_00, annualBalanceIncreaseBPLimit: 15_00, - shareRateDeviationBPLimit: 1_50, // 1.5% - requestTimestampMargin: 2048, - maxPositiveTokenRebase: 10_000_000, + simulatedShareRateDeviationBPLimit: 1_50, // 1.5% maxValidatorExitRequestsPerReport: 3000, maxAccountingExtraDataListItemsCount: 15 + 1, + maxNodeOperatorsPerExtraDataItemCount: 16 + 1, + requestTimestampMargin: 2048, + maxPositiveTokenRebase: 10_000_000, } const limitsBefore = await oracleReportSanityChecker.getOracleReportLimits() assert.notEquals(limitsBefore.churnValidatorsPerDayLimit, newLimitsList.churnValidatorsPerDayLimit) assert.notEquals(limitsBefore.oneOffCLBalanceDecreaseBPLimit, newLimitsList.oneOffCLBalanceDecreaseBPLimit) assert.notEquals(limitsBefore.annualBalanceIncreaseBPLimit, newLimitsList.annualBalanceIncreaseBPLimit) - assert.notEquals(limitsBefore.shareRateDeviationBPLimit, newLimitsList.shareRateDeviationBPLimit) - assert.notEquals(limitsBefore.requestTimestampMargin, newLimitsList.requestTimestampMargin) - assert.notEquals(limitsBefore.maxPositiveTokenRebase, newLimitsList.maxPositiveTokenRebase) + assert.notEquals(limitsBefore.simulatedShareRateDeviationBPLimit, newLimitsList.simulatedShareRateDeviationBPLimit) assert.notEquals(limitsBefore.maxValidatorExitRequestsPerReport, newLimitsList.maxValidatorExitRequestsPerReport) assert.notEquals(limitsBefore.maxAccountingExtraDataListItemsCount, newLimitsList.maxAccountingExtraDataListItemsCount) + assert.notEquals(limitsBefore.maxNodeOperatorsPerExtraDataItemCount, newLimitsList.maxNodeOperatorsPerExtraDataItemCount) + assert.notEquals(limitsBefore.requestTimestampMargin, newLimitsList.requestTimestampMargin) + assert.notEquals(limitsBefore.maxPositiveTokenRebase, newLimitsList.maxPositiveTokenRebase) await oracleReportSanityChecker.setOracleReportLimits(Object.values(newLimitsList), { from: managersRoster.allLimitsManagers[0] @@ -103,11 +108,12 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa assert.equals(limitsAfter.churnValidatorsPerDayLimit, newLimitsList.churnValidatorsPerDayLimit) assert.equals(limitsAfter.oneOffCLBalanceDecreaseBPLimit, newLimitsList.oneOffCLBalanceDecreaseBPLimit) assert.equals(limitsAfter.annualBalanceIncreaseBPLimit, newLimitsList.annualBalanceIncreaseBPLimit) - assert.equals(limitsAfter.shareRateDeviationBPLimit, newLimitsList.shareRateDeviationBPLimit) - assert.equals(limitsAfter.requestTimestampMargin, newLimitsList.requestTimestampMargin) - assert.equals(limitsAfter.maxPositiveTokenRebase, newLimitsList.maxPositiveTokenRebase) + assert.equals(limitsAfter.simulatedShareRateDeviationBPLimit, newLimitsList.simulatedShareRateDeviationBPLimit) assert.equals(limitsAfter.maxValidatorExitRequestsPerReport, newLimitsList.maxValidatorExitRequestsPerReport) assert.equals(limitsAfter.maxAccountingExtraDataListItemsCount, newLimitsList.maxAccountingExtraDataListItemsCount) + assert.equals(limitsAfter.maxNodeOperatorsPerExtraDataItemCount, newLimitsList.maxNodeOperatorsPerExtraDataItemCount) + assert.equals(limitsAfter.requestTimestampMargin, newLimitsList.requestTimestampMargin) + assert.equals(limitsAfter.maxPositiveTokenRebase, newLimitsList.maxPositiveTokenRebase) }) }) @@ -180,6 +186,31 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa it('passes all checks with correct oracle report data', async () => { await oracleReportSanityChecker.checkAccountingOracleReport(...Object.values(correctLidoOracleReport)) }) + + it('set maxAccountingExtraDataListItemsCount', async () => { + const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()).maxAccountingExtraDataListItemsCount + const newValue = 31 + assert.notEquals(newValue, previousValue) + await oracleReportSanityChecker.setMaxAccountingExtraDataListItemsCount(newValue, + { from: managersRoster.maxAccountingExtraDataListItemsCountManagers[0] }) + assert.equals( + (await oracleReportSanityChecker.getOracleReportLimits()).maxAccountingExtraDataListItemsCount, + newValue + ) + }) + + it('set maxNodeOperatorsPerExtraDataItemCount', async () => { + const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()).maxNodeOperatorsPerExtraDataItemCount + const newValue = 33 + assert.notEquals(newValue, previousValue) + await oracleReportSanityChecker.setMaxNodeOperatorsPerExtraDataItemCount(newValue, + { from: managersRoster.maxNodeOperatorsPerExtraDataItemCountManagers[0] }) + assert.equals( + (await oracleReportSanityChecker.getOracleReportLimits()).maxNodeOperatorsPerExtraDataItemCount, + newValue + ) + }) + }) describe('checkWithdrawalQueueOracleReport()', async () => { @@ -192,13 +223,12 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa } before(async () => { - const currentBlockNumber = await hre.ethers.provider.getBlockNumber() - const currentBlock = await hre.ethers.provider.getBlock(currentBlockNumber) - correctWithdrawalQueueOracleReport.refReportTimestamp = currentBlock.timestamp - oldRequestCreationTimestamp = currentBlock.timestamp - defaultLimitsList.requestTimestampMargin + const currentBlockTimestamp = await getCurrentBlockTimestamp() + correctWithdrawalQueueOracleReport.refReportTimestamp = currentBlockTimestamp + oldRequestCreationTimestamp = currentBlockTimestamp - defaultLimitsList.requestTimestampMargin correctWithdrawalQueueOracleReport.requestIdToFinalizeUpTo = oldRequestCreationTimestamp await withdrawalQueueMock.setRequestBlockNumber(oldRequestId, oldRequestCreationTimestamp) - newRequestCreationTimestamp = currentBlock.timestamp - Math.floor(defaultLimitsList.requestTimestampMargin / 2) + newRequestCreationTimestamp = currentBlockTimestamp - Math.floor(defaultLimitsList.requestTimestampMargin / 2) await withdrawalQueueMock.setRequestBlockNumber(newRequestId, newRequestCreationTimestamp) }) @@ -230,22 +260,22 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa simulatedShareRate: (BigInt(2) * 10n ** 27n).toString() } - it('reverts with error IncorrectFinalizationShareRate() when reported and onchain share rate differs', async () => { - const finalizationShareRate = BigInt(ETH(2.10)) * 10n ** 9n + it('reverts with error IncorrectSimulatedShareRate() when reported and onchain share rate differs', async () => { + const simulatedShareRate = BigInt(ETH(2.10)) * 10n ** 9n const actualShareRate = BigInt(2) * 10n ** 27n - const deviation = (100_00n * (finalizationShareRate - actualShareRate)) / actualShareRate + const deviation = (100_00n * (simulatedShareRate - actualShareRate)) / actualShareRate await assert.revertsWithCustomError( oracleReportSanityChecker.checkSimulatedShareRate( ...Object.values({ ...correctSimulatedShareRate, - simulatedShareRate: finalizationShareRate.toString() + simulatedShareRate: simulatedShareRate.toString() }) ), - `IncorrectFinalizationShareRate(${deviation.toString()})` + `IncorrectSimulatedShareRate(${deviation.toString()})` ) }) - it('reverts with error IncorrectFinalizationShareRate() when actual share rate is zero', async () => { + it('reverts with error IncorrectSimulatedShareRate() when actual share rate is zero', async () => { const deviation = 100_00n await assert.revertsWithCustomError( oracleReportSanityChecker.checkSimulatedShareRate( @@ -255,7 +285,7 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa postTotalPooledEther: ETH(0) }) ), - `IncorrectFinalizationShareRate(${deviation.toString()})` + `IncorrectSimulatedShareRate(${deviation.toString()})` ) }) diff --git a/test/0.8.9/oracle/accounting-oracle-access-control.test.js b/test/0.8.9/oracle/accounting-oracle-access-control.test.js index 379549713..adb8f9ae5 100644 --- a/test/0.8.9/oracle/accounting-oracle-access-control.test.js +++ b/test/0.8.9/oracle/accounting-oracle-access-control.test.js @@ -1,5 +1,4 @@ const { assert } = require('../../helpers/assert') -const { assertEvent } = require('@aragon/contract-helpers-test/src/asserts') const { e9, e18, e27 } = require('../../helpers/utils') const { @@ -10,7 +9,9 @@ const { packExtraDataList, calcExtraDataListHash, calcReportDataHash, - EXTRA_DATA_FORMAT_LIST + EXTRA_DATA_FORMAT_EMPTY, + EXTRA_DATA_FORMAT_LIST, + ZERO_HASH } = require('./accounting-oracle-deploy.test') contract('AccountingOracle', ([admin, account1, account2, member1, member2, stranger]) => { @@ -23,7 +24,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra const submitDataRoleKeccak156 = web3.utils.keccak256('SUBMIT_DATA_ROLE') - const deploy = async (options = undefined) => { + const deploy = async ({emptyExtraData = false} = {}) => { const deployed = await deployAndConfigureAccountingOracle(admin) const { refSlot } = await deployed.consensus.getCurrentFrame() @@ -42,6 +43,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra const extraDataItems = encodeExtraDataItems(extraData) extraDataList = packExtraDataList(extraDataItems) const extraDataHash = calcExtraDataListHash(extraDataList) + reportFields = { consensusVersion: CONSENSUS_VERSION, refSlot: +refSlot, @@ -54,9 +56,9 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra lastWithdrawalRequestIdToFinalize: 1, finalizationShareRate: e27(1), isBunkerMode: true, - extraDataFormat: EXTRA_DATA_FORMAT_LIST, - extraDataHash: extraDataHash, - extraDataItemsCount: extraDataItems.length + extraDataFormat: emptyExtraData ? EXTRA_DATA_FORMAT_EMPTY : EXTRA_DATA_FORMAT_LIST, + extraDataHash: emptyExtraData ? ZERO_HASH : extraDataHash, + extraDataItemsCount: emptyExtraData ? 0 : extraDataItems.length } reportItems = getReportDataItems(reportFields) const reportHash = calcReportDataHash(reportItems) @@ -65,7 +67,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra oracle = deployed.oracle consensus = deployed.consensus - mockLido = deploy.mockLido + mockLido = deployed.mockLido } context('deploying', () => { @@ -84,7 +86,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra beforeEach(deploy) context('submitReportData', () => { - it('should revert from not consensus member without SUBMIT_DATA_ROLE role ', async () => { + it('should revert from not consensus member without SUBMIT_DATA_ROLE role', async () => { await assert.reverts( oracle.submitReportData(reportItems, CONSENSUS_VERSION, { from: stranger }), 'SenderNotAllowed()' @@ -97,14 +99,14 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra await consensus.setTime(deadline) const tx = await oracle.submitReportData(reportItems, CONSENSUS_VERSION, { from: account2 }) - assertEvent(tx, 'ProcessingStarted', { expectedArgs: { refSlot: reportFields.refSlot } }) + assert.emits(tx, 'ProcessingStarted', { refSlot: reportFields.refSlot }) }) it('should allow calling from a member', async () => { await consensus.addMember(member2, 2) const tx = await oracle.submitReportData(reportItems, CONSENSUS_VERSION, { from: member2 }) - assertEvent(tx, 'ProcessingStarted', { expectedArgs: { refSlot: reportFields.refSlot } }) + assert.emits(tx, 'ProcessingStarted', { refSlot: reportFields.refSlot }) }) }) @@ -123,7 +125,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra await oracle.submitReportData(reportItems, CONSENSUS_VERSION, { from: account2 }) const tx = await oracle.submitReportExtraDataList(extraDataList, { from: account2 }) - assertEvent(tx, 'ExtraDataSubmitted', { expectedArgs: { refSlot: reportFields.refSlot } }) + assert.emits(tx, 'ExtraDataSubmitted', { refSlot: reportFields.refSlot }) }) it('should allow calling from a member', async () => { @@ -134,7 +136,37 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra await oracle.submitReportData(reportItems, CONSENSUS_VERSION, { from: member2 }) const tx = await oracle.submitReportExtraDataList(extraDataList, { from: member2 }) - assertEvent(tx, 'ExtraDataSubmitted', { expectedArgs: { refSlot: reportFields.refSlot } }) + assert.emits(tx, 'ExtraDataSubmitted', { refSlot: reportFields.refSlot }) + }) + }) + + context('submitReportExtraDataEmpty', () => { + beforeEach(() => deploy({emptyExtraData: true})) + + it('should revert from not consensus member without SUBMIT_DATA_ROLE role ', async () => { + await assert.reverts(oracle.submitReportExtraDataEmpty({ from: account1 }), 'SenderNotAllowed()') + }) + + it('should allow calling from a possessor of SUBMIT_DATA_ROLE role', async () => { + await oracle.grantRole(submitDataRoleKeccak156, account2) + const deadline = (await oracle.getConsensusReport()).processingDeadlineTime + await consensus.setTime(deadline) + + await oracle.submitReportData(reportItems, CONSENSUS_VERSION, { from: account2 }) + const tx = await oracle.submitReportExtraDataEmpty({ from: account2 }) + + assert.emits(tx, 'ExtraDataSubmitted', { refSlot: reportFields.refSlot }) + }) + + it('should allow calling from a member', async () => { + await consensus.addMember(member2, 2) + const deadline = (await oracle.getConsensusReport()).processingDeadlineTime + await consensus.setTime(deadline) + + await oracle.submitReportData(reportItems, CONSENSUS_VERSION, { from: member2 }) + const tx = await oracle.submitReportExtraDataEmpty({ from: member2 }) + + assert.emits(tx, 'ExtraDataSubmitted', { refSlot: reportFields.refSlot }) }) }) }) diff --git a/test/0.8.9/oracle/accounting-oracle-deploy.test.js b/test/0.8.9/oracle/accounting-oracle-deploy.test.js index 588a1d06c..6bd5abb21 100644 --- a/test/0.8.9/oracle/accounting-oracle-deploy.test.js +++ b/test/0.8.9/oracle/accounting-oracle-deploy.test.js @@ -1,3 +1,4 @@ +const { ZERO_ADDRESS } = require('@aragon/contract-helpers-test') const { assert } = require('../../helpers/assert') const { hex } = require('../../helpers/utils') const { @@ -39,10 +40,12 @@ const MockLegacyOracle = artifacts.require('MockLegacyOracle') const V1_ORACLE_LAST_COMPLETED_EPOCH = 2 * EPOCHS_PER_FRAME const V1_ORACLE_LAST_REPORT_SLOT = V1_ORACLE_LAST_COMPLETED_EPOCH * SLOTS_PER_EPOCH +const EXTRA_DATA_FORMAT_EMPTY = 0 const EXTRA_DATA_FORMAT_LIST = 1 const EXTRA_DATA_TYPE_STUCK_VALIDATORS = 1 const EXTRA_DATA_TYPE_EXITED_VALIDATORS = 2 + function getReportDataItems(r) { return [ r.consensusVersion, @@ -82,20 +85,10 @@ function encodeExtraDataItem(itemIndex, itemType, moduleId, nodeOperatorIds, key function encodeExtraDataItems({ stuckKeys, exitedKeys }) { const items = [] - let itemType = EXTRA_DATA_TYPE_STUCK_VALIDATORS - - for (let i = 0; i < stuckKeys.length; ++i) { - const item = stuckKeys[i] - items.push(encodeExtraDataItem(items.length, itemType, item.moduleId, item.nodeOpIds, item.keysCounts)) - } - - itemType = EXTRA_DATA_TYPE_EXITED_VALIDATORS - - for (let i = 0; i < exitedKeys.length; ++i) { - const item = exitedKeys[i] - items.push(encodeExtraDataItem(items.length, itemType, item.moduleId, item.nodeOpIds, item.keysCounts)) - } - + const encodeItem = (item, type) => + encodeExtraDataItem(items.length, type, item.moduleId, item.nodeOpIds, item.keysCounts) + stuckKeys.forEach((item) => items.push(encodeItem(item, EXTRA_DATA_TYPE_STUCK_VALIDATORS))) + exitedKeys.forEach((item) => items.push(encodeItem(item, EXTRA_DATA_TYPE_EXITED_VALIDATORS))) return items } @@ -108,8 +101,8 @@ function calcExtraDataListHash(packedExtraDataList) { } async function deployOracleReportSanityCheckerForAccounting(lidoLocator, admin) { const churnValidatorsPerDayLimit = 100 - const limitsList = [churnValidatorsPerDayLimit, 0, 0, 0, 0, 0, 32 * 12, 15] - const managersRoster = [[admin], [admin], [admin], [admin], [admin], [admin], [admin], [admin], [admin]] + const limitsList = [churnValidatorsPerDayLimit, 0, 0, 0, 32 * 12, 15, 16, 0, 0] + const managersRoster = [[admin], [admin], [admin], [admin], [admin], [admin], [admin], [admin], [admin], [admin]] const OracleReportSanityChecker = artifacts.require('OracleReportSanityChecker') @@ -148,6 +141,7 @@ module.exports = { CONSENSUS_VERSION, V1_ORACLE_LAST_COMPLETED_EPOCH, V1_ORACLE_LAST_REPORT_SLOT, + EXTRA_DATA_FORMAT_EMPTY, EXTRA_DATA_FORMAT_LIST, EXTRA_DATA_TYPE_STUCK_VALIDATORS, EXTRA_DATA_TYPE_EXITED_VALIDATORS, @@ -191,7 +185,9 @@ async function deployAccountingOracleSetup( secondsPerSlot = SECONDS_PER_SLOT, genesisTime = GENESIS_TIME, getLidoAndStakingRouter = deployMockLidoAndStakingRouter, - getLegacyOracle = deployMockLegacyOracle + getLegacyOracle = deployMockLegacyOracle, + lidoLocatorAddr: lidoLocatorAddrArg, + legacyOracleAddr: legacyOracleAddrArg } = {} ) { const locatorAddr = (await deployLocatorWithDummyAddressesImplementation(admin)).address @@ -212,9 +208,9 @@ async function deployAccountingOracleSetup( } const oracle = await AccountingOracle.new( - locatorAddr, + lidoLocatorAddrArg || locatorAddr, lido.address, - legacyOracle.address, + legacyOracleAddrArg || legacyOracle.address, secondsPerSlot, genesisTime, { from: admin } @@ -232,7 +228,16 @@ async function deployAccountingOracleSetup( // pretend we're at the first slot of the initial frame's epoch await consensus.setTime(genesisTime + initialEpoch * slotsPerEpoch * secondsPerSlot) - return { lido, stakingRouter, withdrawalQueue, locatorAddr, legacyOracle, oracle, consensus, oracleReportSanityChecker } + return { + lido, + stakingRouter, + withdrawalQueue, + locatorAddr, + legacyOracle, + oracle, + consensus, + oracleReportSanityChecker + } } async function initAccountingOracle({ @@ -251,6 +256,7 @@ async function initAccountingOracle({ await oracle.grantRole(await oracle.SUBMIT_DATA_ROLE(), dataSubmitter, { from: admin }) } + assert.equal(+(await oracle.EXTRA_DATA_FORMAT_EMPTY()), EXTRA_DATA_FORMAT_EMPTY) assert.equal(+(await oracle.EXTRA_DATA_FORMAT_LIST()), EXTRA_DATA_FORMAT_LIST) assert.equal(+(await oracle.EXTRA_DATA_TYPE_STUCK_VALIDATORS()), EXTRA_DATA_TYPE_STUCK_VALIDATORS) assert.equal(+(await oracle.EXTRA_DATA_TYPE_EXITED_VALIDATORS()), EXTRA_DATA_TYPE_EXITED_VALIDATORS) @@ -263,6 +269,7 @@ async function deployAndConfigureAccountingOracle(admin) { const initTx = await initAccountingOracle({ admin, ...deployed }) return { ...deployed, initTx } } + contract('AccountingOracle', ([admin, member1]) => { let consensus let oracle @@ -313,6 +320,9 @@ contract('AccountingOracle', ([admin, member1]) => { const deployed = await deployAccountingOracleSetup(admin, { initialEpoch: 3 + 10 * EPOCHS_PER_FRAME }) await deployed.legacyOracle.setLastCompletedEpochId(3 + 9 * EPOCHS_PER_FRAME) await initAccountingOracle({ admin, ...deployed }) + const refSlot = await deployed.oracle.getLastProcessingRefSlot() + const epoch = await deployed.legacyOracle.getLastCompletedEpochId() + assert.equals(refSlot, epoch.muln(SLOTS_PER_EPOCH)) }) it('deployment and init finishes successfully (default setup)', async () => { @@ -360,5 +370,42 @@ contract('AccountingOracle', ([admin, member1]) => { assert.equal(await oracle.LIDO(), mockLido.address) assert.equal(+(await oracle.SECONDS_PER_SLOT()), SECONDS_PER_SLOT) }) + + it('reverts if lido locator address is zero', async () => { + await assert.reverts( + deployAccountingOracleSetup(admin, { lidoLocatorAddr: ZERO_ADDRESS }), + 'LidoLocatorCannotBeZero()' + ) + }) + + it('reverts if legacy oracle address is zero', async () => { + await assert.reverts( + deployAccountingOracleSetup(admin, { legacyOracleAddr: ZERO_ADDRESS }), + 'LegacyOracleCannotBeZero()' + ) + }) + + it('initialize reverts if admin address is zero', async () => { + const { consensus } = await deployAccountingOracleSetup(admin) + await assert.reverts( + oracle.initialize(ZERO_ADDRESS, consensus.address, CONSENSUS_VERSION, { from: admin }), + 'AdminCannotBeZero()' + ) + }) + + it('initializeWithoutMigration reverts if admin address is zero', async () => { + const { consensus } = await deployAccountingOracleSetup(admin) + const { refSlot } = await consensus.getCurrentFrame() + await assert.reverts( + oracle.initializeWithoutMigration(ZERO_ADDRESS, consensus.address, CONSENSUS_VERSION, refSlot, { from: admin }), + 'AdminCannotBeZero()' + ) + }) + + it('initializeWithoutMigration succeeds', async () => { + const deployed = await deployAccountingOracleSetup(admin) + const { refSlot } = await deployed.consensus.getCurrentFrame() + await deployed.oracle.initializeWithoutMigration(admin, deployed.consensus.address, CONSENSUS_VERSION, refSlot, { from: admin }) + }) }) }) diff --git a/test/0.8.9/oracle/accounting-oracle-happy-path.test.js b/test/0.8.9/oracle/accounting-oracle-happy-path.test.js index eeb22e24f..d76ed1733 100644 --- a/test/0.8.9/oracle/accounting-oracle-happy-path.test.js +++ b/test/0.8.9/oracle/accounting-oracle-happy-path.test.js @@ -1,20 +1,26 @@ -const { BN } = require('bn.js') const { assert } = require('../../helpers/assert') -const { assertBn, assertEvent, assertAmountOfEvents } = require('@aragon/contract-helpers-test/src/asserts') -const { assertRevert } = require('../../helpers/assertThrow') -const { e9, e18, e27, hex, printEvents } = require('../../helpers/utils') -const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') +const { assertBn } = require('@aragon/contract-helpers-test/src/asserts') +const { e9, e18, e27, hex } = require('../../helpers/utils') const { - SLOTS_PER_EPOCH, SECONDS_PER_SLOT, GENESIS_TIME, SECONDS_PER_EPOCH, - EPOCHS_PER_FRAME, SLOTS_PER_FRAME, SECONDS_PER_FRAME, - computeSlotAt, computeEpochAt, computeEpochFirstSlotAt, - computeEpochFirstSlot, computeTimestampAtSlot, computeTimestampAtEpoch, - ZERO_HASH, CONSENSUS_VERSION, + SECONDS_PER_SLOT, + GENESIS_TIME, + SECONDS_PER_EPOCH, + SLOTS_PER_FRAME, + SECONDS_PER_FRAME, + computeTimestampAtSlot, + ZERO_HASH, + CONSENSUS_VERSION, V1_ORACLE_LAST_REPORT_SLOT, - EXTRA_DATA_FORMAT_LIST, EXTRA_DATA_TYPE_STUCK_VALIDATORS, EXTRA_DATA_TYPE_EXITED_VALIDATORS, - deployAndConfigureAccountingOracle, getReportDataItems, calcReportDataHash, encodeExtraDataItems, - packExtraDataList, calcExtraDataListHash} = require('./accounting-oracle-deploy.test') + EXTRA_DATA_FORMAT_EMPTY, + EXTRA_DATA_FORMAT_LIST, + deployAndConfigureAccountingOracle, + getReportDataItems, + calcReportDataHash, + encodeExtraDataItems, + packExtraDataList, + calcExtraDataListHash +} = require('./accounting-oracle-deploy.test') contract('AccountingOracle', ([admin, member1, member2, member3, stranger]) => { @@ -77,6 +83,7 @@ contract('AccountingOracle', ([admin, member1, member2, member3, stranger]) => { assert.isFalse(procState.mainDataSubmitted) assert.equal(procState.extraDataHash, ZERO_HASH) assert.equal(+procState.extraDataFormat, 0) + assert.isFalse(procState.extraDataSubmitted) assert.equal(+procState.extraDataItemsCount, 0) assert.equal(+procState.extraDataItemsSubmitted, 0) }) @@ -152,6 +159,7 @@ contract('AccountingOracle', ([admin, member1, member2, member3, stranger]) => { assert.isFalse(procState.mainDataSubmitted) assert.equal(procState.extraDataHash, ZERO_HASH) assert.equal(+procState.extraDataFormat, 0) + assert.isFalse(procState.extraDataSubmitted) assert.equal(+procState.extraDataItemsCount, 0) assert.equal(+procState.extraDataItemsSubmitted, 0) }) @@ -161,14 +169,14 @@ contract('AccountingOracle', ([admin, member1, member2, member3, stranger]) => { }) it('non-member cannot submit the data', async () => { - await assertRevert( + await assert.reverts( oracle.submitReportData(reportItems, oracleVersion, {from: stranger}), 'SenderNotAllowed()' ) }) it('the data cannot be submitted passing a different contract version', async () => { - await assertRevert( + await assert.reverts( oracle.submitReportData(reportItems, oracleVersion - 1, {from: member1}), `UnexpectedContractVersion(${oracleVersion}, ${oracleVersion - 1})` ) @@ -178,7 +186,7 @@ contract('AccountingOracle', ([admin, member1, member2, member3, stranger]) => { const invalidReport = { ...reportFields, numValidators: reportFields.numValidators + 1 } const invalidReportItems = getReportDataItems(invalidReport) const invalidReportHash = calcReportDataHash(invalidReportItems) - await assertRevert( + await assert.reverts( oracle.submitReportData(invalidReportItems, oracleVersion, {from: member1}), `UnexpectedDataHash("${reportHash}", "${invalidReportHash}")` ) @@ -189,7 +197,7 @@ contract('AccountingOracle', ([admin, member1, member2, member3, stranger]) => { it(`a committee member submits the rebase data`, async () => { prevProcessingRefSlot = +await oracle.getLastProcessingRefSlot() const tx = await oracle.submitReportData(reportItems, oracleVersion, {from: member1}) - assertEvent(tx, 'ProcessingStarted', {expectedArgs: {refSlot: reportFields.refSlot}}) + assert.emits(tx, 'ProcessingStarted', {refSlot: reportFields.refSlot}) assert.isTrue((await oracle.getConsensusReport()).processingStarted) assert.isAbove(+await oracle.getLastProcessingRefSlot(), prevProcessingRefSlot) }) @@ -207,6 +215,7 @@ contract('AccountingOracle', ([admin, member1, member2, member3, stranger]) => { assert.isTrue(procState.mainDataSubmitted) assert.equal(procState.extraDataHash, reportFields.extraDataHash) assert.equal(+procState.extraDataFormat, reportFields.extraDataFormat) + assert.isFalse(procState.extraDataSubmitted) assert.equal(+procState.extraDataItemsCount, reportFields.extraDataItemsCount) assert.equal(+procState.extraDataItemsSubmitted, 0) }) @@ -224,7 +233,6 @@ contract('AccountingOracle', ([admin, member1, member2, member3, stranger]) => { assertBn(lastOracleReportCall.elRewardsVaultBalance, reportFields.elRewardsVaultBalance) assertBn(lastOracleReportCall.lastWithdrawalRequestIdToFinalize, reportFields.lastWithdrawalRequestIdToFinalize) assertBn(lastOracleReportCall.finalizationShareRate, reportFields.finalizationShareRate) - // assert.equal(lastOracleReportCall.isBunkerMode, reportFields.isBunkerMode) }) it(`withdrawal queue got bunker mode report`, async () => { @@ -258,13 +266,20 @@ contract('AccountingOracle', ([admin, member1, member2, member3, stranger]) => { assert.equal(+lastLegacyOracleCall.clValidators, reportFields.numValidators) }) + it(`no data can be submitted for the same reference slot again`, async () => { + await assert.reverts( + oracle.submitReportData(reportItems, oracleVersion, {from: member2}), + 'RefSlotAlreadyProcessing()' + ) + }) + it('some time passes', async () => { const deadline = (await oracle.getConsensusReport()).processingDeadlineTime await consensus.setTime(deadline) }) it('a non-member cannot submit extra data', async () => { - await assertRevert( + await assert.reverts( oracle.submitReportExtraDataList(extraDataList, {from: stranger}), 'SenderNotAllowed()' ) @@ -280,20 +295,27 @@ contract('AccountingOracle', ([admin, member1, member2, member3, stranger]) => { const invalidExtraDataItems = encodeExtraDataItems(invalidExtraData) const invalidExtraDataList = packExtraDataList(invalidExtraDataItems) const invalidExtraDataHash = calcExtraDataListHash(invalidExtraDataList) - await assertRevert( + await assert.reverts( oracle.submitReportExtraDataList(invalidExtraDataList, {from: member2}), - `UnexpectedDataHash("${extraDataHash}", "${invalidExtraDataHash}")` + `UnexpectedExtraDataHash("${extraDataHash}", "${invalidExtraDataHash}")` + ) + }) + + it(`an empty extra data cannot be submitted`, async () => { + await assert.reverts( + oracle.submitReportExtraDataEmpty({from: member2}), + `UnexpectedExtraDataFormat(${EXTRA_DATA_FORMAT_LIST}, ${EXTRA_DATA_FORMAT_EMPTY})` ) }) it('a committee member submits extra data', async () => { const tx = await oracle.submitReportExtraDataList(extraDataList, {from: member2}) - assertEvent(tx, 'ExtraDataSubmitted', {expectedArgs: { + assert.emits(tx, 'ExtraDataSubmitted', { refSlot: reportFields.refSlot, itemsProcessed: extraDataItems.length, itemsCount: extraDataItems.length, - }}) + }) const frame = await consensus.getCurrentFrame() const procState = await oracle.getProcessingState() @@ -307,6 +329,7 @@ contract('AccountingOracle', ([admin, member1, member2, member3, stranger]) => { assert.isTrue(procState.mainDataSubmitted) assert.equal(procState.extraDataHash, reportFields.extraDataHash) assert.equal(+procState.extraDataFormat, reportFields.extraDataFormat) + assert.isTrue(procState.extraDataSubmitted) assert.equal(+procState.extraDataItemsCount, reportFields.extraDataItemsCount) assert.equal(+procState.extraDataItemsSubmitted, extraDataItems.length) }) @@ -345,5 +368,123 @@ contract('AccountingOracle', ([admin, member1, member2, member3, stranger]) => { assert.equal(call3.nodeOperatorIds, '0x' + [2].map(i => hex(i, 8)).join('')) assert.equal(call3.keysCounts, '0x' + [3].map(i => hex(i, 16)).join('')) }) + + it('Staking router was told that stuck and exited keys updating is finished', async () => { + const totalFinishedCalls = +await mockStakingRouter.totalCalls_onValidatorsCountsByNodeOperatorReportingFinished() + assert.equal(totalFinishedCalls, 1) + }) + + it(`extra data for the same reference slot cannot be re-submitted`, async () => { + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, {from: member1}), + 'ExtraDataAlreadyProcessed()' + ) + }) + + it('some time passes, a new reporting frame starts', async () => { + await consensus.advanceTimeToNextFrameStart() + + const frame = await consensus.getCurrentFrame() + const procState = await oracle.getProcessingState() + + assert.equal(+procState.currentFrameRefSlot, +frame.refSlot) + assert.equal(+procState.processingDeadlineTime, 0) + assert.equal(procState.mainDataHash, ZERO_HASH) + assert.isFalse(procState.mainDataSubmitted) + assert.equal(procState.extraDataHash, ZERO_HASH) + assert.equal(+procState.extraDataFormat, 0) + assert.isFalse(procState.extraDataSubmitted) + assert.equal(+procState.extraDataItemsCount, 0) + assert.equal(+procState.extraDataItemsSubmitted, 0) + }) + + it('new data report with empty extra data is agreed upon and submitted', async () => { + const {refSlot} = await consensus.getCurrentFrame() + + reportFields = { + ...reportFields, + refSlot: +refSlot, + extraDataFormat: EXTRA_DATA_FORMAT_EMPTY, + extraDataHash: ZERO_HASH, + extraDataItemsCount: 0, + } + reportItems = getReportDataItems(reportFields) + reportHash = calcReportDataHash(reportItems) + + await triggerConsensusOnHash(reportHash) + + const tx = await oracle.submitReportData(reportItems, oracleVersion, {from: member2}) + assert.emits(tx, 'ProcessingStarted', {refSlot: reportFields.refSlot}) + }) + + it(`Lido got the oracle report`, async () => { + const lastOracleReportCall = await mockLido.getLastCall_handleOracleReport() + assert.equal(lastOracleReportCall.callCount, 2) + }) + + it(`withdrawal queue got bunker mode report`, async () => { + const updateBunkerModeLastCall = await mockWithdrawalQueue.lastCall__updateBunkerMode() + assert.equal(+updateBunkerModeLastCall.callCount, 2) + }) + + it(`Staking router got the exited keys report`, async () => { + const lastExitedKeysByModuleCall = await mockStakingRouter.lastCall_updateExitedKeysByModule() + assert.equal(lastExitedKeysByModuleCall.callCount, 2) + }) + + it(`a non-empty extra data cannot be submitted`, async () => { + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, {from: member2}), + `UnexpectedExtraDataFormat(${EXTRA_DATA_FORMAT_EMPTY}, ${EXTRA_DATA_FORMAT_LIST})` + ) + }) + + it('a committee member submits empty extra data', async () => { + const tx = await oracle.submitReportExtraDataEmpty({from: member3}) + + assert.emits(tx, 'ExtraDataSubmitted', { + refSlot: reportFields.refSlot, + itemsProcessed: 0, + itemsCount: 0, + }) + + const frame = await consensus.getCurrentFrame() + const procState = await oracle.getProcessingState() + + assert.equal(+procState.currentFrameRefSlot, +frame.refSlot) + assert.equal( + +procState.processingDeadlineTime, + computeTimestampAtSlot(+frame.reportProcessingDeadlineSlot) + ) + assert.equal(procState.mainDataHash, reportHash) + assert.isTrue(procState.mainDataSubmitted) + assert.equal(procState.extraDataHash, ZERO_HASH) + assert.equal(+procState.extraDataFormat, EXTRA_DATA_FORMAT_EMPTY) + assert.isTrue(procState.extraDataSubmitted) + assert.equal(+procState.extraDataItemsCount, 0) + assert.equal(+procState.extraDataItemsSubmitted, 0) + }) + + it(`Staking router didn't get the exited keys by node op report`, async () => { + const totalReportCalls = +await mockStakingRouter.totalCalls_reportExitedKeysByNodeOperator() + assert.equal(totalReportCalls, 2) + }) + + it(`Staking router didn't get the stuck keys by node op report`, async () => { + const totalReportCalls = +await mockStakingRouter.totalCalls_reportStuckKeysByNodeOperator() + assert.equal(totalReportCalls, 3) + }) + + it('Staking router was told that stuck and exited keys updating is finished', async () => { + const totalFinishedCalls = +await mockStakingRouter.totalCalls_onValidatorsCountsByNodeOperatorReportingFinished() + assert.equal(totalFinishedCalls, 2) + }) + + it(`extra data for the same reference slot cannot be re-submitted`, async () => { + await assert.reverts( + oracle.submitReportExtraDataEmpty({from: member1}), + 'ExtraDataAlreadyProcessed()' + ) + }) }) }) diff --git a/test/0.8.9/oracle/accounting-oracle-submit-report-data.test.js b/test/0.8.9/oracle/accounting-oracle-submit-report-data.test.js index ea0c5851a..fe272762a 100644 --- a/test/0.8.9/oracle/accounting-oracle-submit-report-data.test.js +++ b/test/0.8.9/oracle/accounting-oracle-submit-report-data.test.js @@ -1,6 +1,8 @@ const { assert } = require('../../helpers/assert') const { e9, e18, e27 } = require('../../helpers/utils') +const AccountingOracleAbi = require('../../../lib/abi/AccountingOracle.json') + const { CONSENSUS_VERSION, deployAndConfigureAccountingOracle, @@ -10,10 +12,12 @@ const { calcExtraDataListHash, calcReportDataHash, EXTRA_DATA_FORMAT_LIST, + EXTRA_DATA_FORMAT_EMPTY, SLOTS_PER_FRAME, SECONDS_PER_SLOT, GENESIS_TIME, - ZERO_HASH + ZERO_HASH, + HASH_1 } = require('./accounting-oracle-deploy.test') contract('AccountingOracle', ([admin, account1, account2, member1, member2, stranger]) => { @@ -33,6 +37,23 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra let mockLegacyOracle = null let mockWithdrawalQueue = null + const getReportFields = (override = {}) => ({ + consensusVersion: CONSENSUS_VERSION, + numValidators: 10, + clBalanceGwei: e9(320), + stakingModuleIdsWithNewlyExitedValidators: [1], + numExitedValidatorsByStakingModule: [3], + withdrawalVaultBalance: e18(1), + elRewardsVaultBalance: e18(2), + lastWithdrawalRequestIdToFinalize: 1, + finalizationShareRate: e27(1), + isBunkerMode: true, + extraDataFormat: EXTRA_DATA_FORMAT_LIST, + extraDataHash: extraDataHash, + extraDataItemsCount: extraDataItems.length, + ...override + }) + const deploy = async (options = undefined) => { const deployed = await deployAndConfigureAccountingOracle(admin) const { refSlot } = await deployed.consensus.getCurrentFrame() @@ -52,22 +73,9 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra extraDataItems = encodeExtraDataItems(extraData) extraDataList = packExtraDataList(extraDataItems) extraDataHash = calcExtraDataListHash(extraDataList) - reportFields = { - consensusVersion: CONSENSUS_VERSION, - refSlot: +refSlot, - numValidators: 10, - clBalanceGwei: e9(320), - stakingModuleIdsWithNewlyExitedValidators: [1], - numExitedValidatorsByStakingModule: [3], - withdrawalVaultBalance: e18(1), - elRewardsVaultBalance: e18(2), - lastWithdrawalRequestIdToFinalize: 1, - finalizationShareRate: e27(1), - isBunkerMode: true, - extraDataFormat: EXTRA_DATA_FORMAT_LIST, - extraDataHash: extraDataHash, - extraDataItemsCount: extraDataItems.length - } + reportFields = getReportFields({ + refSlot: +refSlot + }) reportItems = getReportDataItems(reportFields) const reportHash = calcReportDataHash(reportItems) await deployed.consensus.addMember(member1, 1, { from: admin }) @@ -184,6 +192,18 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra }) }) + context('only allows submitting main data for the same ref. slot once', () => { + it('reverts on trying to submit the second time', async () => { + const tx = await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) + assert.emits(tx, 'ProcessingStarted', { refSlot: reportFields.refSlot }) + + await assert.reverts( + oracle.submitReportData(reportItems, oracleVersion, { from: member1 }), + 'RefSlotAlreadyProcessing()' + ) + }) + }) + context('checks consensus version', () => { it('should revert if incorrect consensus version', async () => { await consensus.setTime(deadline) @@ -197,8 +217,8 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra } const reportItems = getReportDataItems(newReportFields) - const reportFiledsPrevVersion = { ...reportFields, consensusVersion: incorrectPrevVersion } - const reportItemsPrevVersion = getReportDataItems(reportFiledsPrevVersion) + const reportFieldsPrevVersion = { ...reportFields, consensusVersion: incorrectPrevVersion } + const reportItemsPrevVersion = getReportDataItems(reportFieldsPrevVersion) await assert.reverts( oracle.submitReportData(reportItems, oracleVersion, { from: member1 }), @@ -211,7 +231,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra ) }) - it('should should allow calling if correct consensus version', async () => { + it('should allow calling if correct consensus version', async () => { await consensus.setTime(deadline) const { refSlot } = await consensus.getCurrentFrame() @@ -253,7 +273,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra ) }) - it('should revert if incorrect stakingModuleIdsWithNewlyExitedValidators order (when next number in list is less than previous)', async () => { + it('should revert if incorrect stakingModuleIdsWithNewlyExitedValidators order (when next number in list equals to previous)', async () => { const { newReportItems } = await prepareNextReportInNextFrame({ ...reportFields, stakingModuleIdsWithNewlyExitedValidators: [1, 1], @@ -468,6 +488,27 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra }) }) + context('warns when prev extra data has not been processed yet', () => { + it('emits WarnExtraDataIncompleteProcessing', async () => { + await consensus.setTime(deadline) + const prevRefSlot = +(await consensus.getCurrentFrame()).refSlot + await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) + await consensus.advanceTimeToNextFrameStart() + const nextRefSlot = +(await consensus.getCurrentFrame()).refSlot + const tx = await consensus.submitReport(nextRefSlot, HASH_1, CONSENSUS_VERSION, { from: member1 }) + assert.emits( + tx, + 'WarnExtraDataIncompleteProcessing', + { + refSlot: prevRefSlot, + processedItemsCount: 0, + itemsCount: extraDataItems.length + }, + { abi: AccountingOracleAbi } + ) + }) + }) + context('enforces extra data format', () => { it('should revert on invalid extra data format', async () => { await consensus.setTime(deadline) @@ -491,6 +532,79 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra `UnsupportedExtraDataFormat(${EXTRA_DATA_FORMAT_LIST + 1})` ) }) + + it('should revert on non-empty format but zero length', async () => { + await consensus.setTime(deadline) + const { refSlot } = await consensus.getCurrentFrame() + const reportFields = getReportFields({ + refSlot: +refSlot, + extraDataItemsCount: 0 + }) + const reportItems = getReportDataItems(reportFields) + const reportHash = calcReportDataHash(reportItems) + await consensus.submitReport(refSlot, reportHash, CONSENSUS_VERSION, { from: member1 }) + await assert.revertsWithCustomError( + oracle.submitReportData(reportItems, oracleVersion, { from: member1 }), + `ExtraDataItemsCountCannotBeZeroForNonEmptyData()` + ) + }) + + it('should revert on non-empty format but zero hash', async () => { + await consensus.setTime(deadline) + const { refSlot } = await consensus.getCurrentFrame() + const reportFields = getReportFields({ + refSlot: +refSlot, + extraDataHash: ZERO_HASH + }) + const reportItems = getReportDataItems(reportFields) + const reportHash = calcReportDataHash(reportItems) + await consensus.submitReport(refSlot, reportHash, CONSENSUS_VERSION, { from: member1 }) + await assert.revertsWithCustomError( + oracle.submitReportData(reportItems, oracleVersion, { from: member1 }), + `ExtraDataHashCannotBeZeroForNonEmptyData()` + ) + }) + }) + + context('enforces zero extraData fields for the empty format', () => { + it('should revert for non empty ExtraDataHash', async () => { + await consensus.setTime(deadline) + const { refSlot } = await consensus.getCurrentFrame() + const nonZeroHash = web3.utils.keccak256('nonZeroHash') + const reportFields = getReportFields({ + refSlot: +refSlot, + isBunkerMode: false, + extraDataFormat: EXTRA_DATA_FORMAT_EMPTY, + extraDataHash: nonZeroHash, + extraDataItemsCount: 0 + }) + const reportItems = getReportDataItems(reportFields) + const reportHash = calcReportDataHash(reportItems) + await consensus.submitReport(refSlot, reportHash, CONSENSUS_VERSION, { from: member1 }) + await assert.revertsWithCustomError( + oracle.submitReportData(reportItems, oracleVersion, { from: member1 }), + `UnexpectedExtraDataHash("${ZERO_HASH}", "${nonZeroHash}")` + ) + }) + + it('should revert for non zero ExtraDataLength', async () => { + await consensus.setTime(deadline) + const { refSlot } = await consensus.getCurrentFrame() + const reportFields = getReportFields({ + refSlot: +refSlot, + isBunkerMode: false, + extraDataFormat: EXTRA_DATA_FORMAT_EMPTY, + extraDataHash: ZERO_HASH, + extraDataItemsCount: 10 + }) + const reportItems = getReportDataItems(reportFields) + const reportHash = calcReportDataHash(reportItems) + await consensus.submitReport(refSlot, reportHash, CONSENSUS_VERSION, { from: member1 }) + await assert.revertsWithCustomError( + oracle.submitReportData(reportItems, oracleVersion, { from: member1 }), + `UnexpectedExtraDataItemsCount(0, 10)` + ) + }) }) context('ExtraDataProcessingState', () => { diff --git a/test/0.8.9/oracle/accounting-oracle-submit-report-extra-data.test.js b/test/0.8.9/oracle/accounting-oracle-submit-report-extra-data.test.js index b098b799f..f1f9a3c71 100644 --- a/test/0.8.9/oracle/accounting-oracle-submit-report-extra-data.test.js +++ b/test/0.8.9/oracle/accounting-oracle-submit-report-extra-data.test.js @@ -1,87 +1,124 @@ const { assert } = require('../../helpers/assert') -const { assertEvent } = require('@aragon/contract-helpers-test/src/asserts') -const { e9, e18, e27 } = require('../../helpers/utils') +const { e9, e18, e27, hex } = require('../../helpers/utils') const { CONSENSUS_VERSION, deployAndConfigureAccountingOracle, getReportDataItems, + encodeExtraDataItem, encodeExtraDataItems, packExtraDataList, calcExtraDataListHash, calcReportDataHash, + ZERO_HASH, + EXTRA_DATA_FORMAT_EMPTY, EXTRA_DATA_FORMAT_LIST, - SLOTS_PER_FRAME + EXTRA_DATA_TYPE_STUCK_VALIDATORS } = require('./accounting-oracle-deploy.test') +const getDefaultExtraData = () => ({ + stuckKeys: [ + { moduleId: 1, nodeOpIds: [0], keysCounts: [1] }, + { moduleId: 2, nodeOpIds: [0], keysCounts: [2] }, + { moduleId: 3, nodeOpIds: [2], keysCounts: [3] } + ], + exitedKeys: [ + { moduleId: 2, nodeOpIds: [1, 2], keysCounts: [1, 3] }, + { moduleId: 3, nodeOpIds: [1], keysCounts: [2] } + ] +}) + +const getDefaultReportFields = (overrides) => ({ + consensusVersion: CONSENSUS_VERSION, + numValidators: 10, + clBalanceGwei: e9(320), + stakingModuleIdsWithNewlyExitedValidators: [1], + numExitedValidatorsByStakingModule: [3], + withdrawalVaultBalance: e18(1), + elRewardsVaultBalance: e18(2), + lastWithdrawalRequestIdToFinalize: 1, + finalizationShareRate: e27(1), + isBunkerMode: true, + extraDataFormat: EXTRA_DATA_FORMAT_LIST, + // required override: refSlot, + // required override: extraDataHash, + // required override: extraDataItemsCount + ...overrides +}) + contract('AccountingOracle', ([admin, account1, account2, member1, member2, stranger]) => { let consensus = null let oracle = null - let reportItems = null - let reportFields = null - let extraDataList = null - let extraDataHash = null - let extraDataItems = null let oracleVersion = null - let deadline = null - let extraData = null + let stakingRouter = null + let oracleReportSanityChecker = null const deploy = async (options = undefined) => { const deployed = await deployAndConfigureAccountingOracle(admin) - const { refSlot } = await deployed.consensus.getCurrentFrame() - - extraData = { - stuckKeys: [ - { moduleId: 1, nodeOpIds: [0], keysCounts: [1] }, - { moduleId: 2, nodeOpIds: [0], keysCounts: [2] }, - { moduleId: 3, nodeOpIds: [2], keysCounts: [3] } - ], - exitedKeys: [ - { moduleId: 2, nodeOpIds: [1, 2], keysCounts: [1, 3] }, - { moduleId: 3, nodeOpIds: [1], keysCounts: [2] } - ] - } + oracle = deployed.oracle + consensus = deployed.consensus + stakingRouter = deployed.stakingRouter + oracleReportSanityChecker = deployed.oracleReportSanityChecker + oracleVersion = +(await oracle.getContractVersion()) + await consensus.addMember(member1, 1, { from: admin }) + } - extraDataItems = encodeExtraDataItems(extraData) - extraDataList = packExtraDataList(extraDataItems) - extraDataHash = calcExtraDataListHash(extraDataList) - reportFields = { - consensusVersion: CONSENSUS_VERSION, - refSlot: +refSlot, - numValidators: 10, - clBalanceGwei: e9(320), - stakingModuleIdsWithNewlyExitedValidators: [1], - numExitedValidatorsByStakingModule: [3], - withdrawalVaultBalance: e18(1), - elRewardsVaultBalance: e18(2), - lastWithdrawalRequestIdToFinalize: 1, - finalizationShareRate: e27(1), - isBunkerMode: true, - extraDataFormat: EXTRA_DATA_FORMAT_LIST, - extraDataHash: extraDataHash, - extraDataItemsCount: extraDataItems.length - } - reportItems = getReportDataItems(reportFields) - const reportHash = calcReportDataHash(reportItems) - await deployed.consensus.addMember(member1, 1, { from: admin }) - await deployed.consensus.submitReport(refSlot, reportHash, CONSENSUS_VERSION, { from: member1 }) + // note: reportFieldsArg.refSlot is required to pass here + function getReportData({ + extraData: extraDataArg, + extraDataItems: extraDataItemsArgs, + reportFields: reportFieldsArg + } = {}) { + const extraData = extraDataArg || getDefaultExtraData() + const extraDataItems = extraDataItemsArgs || encodeExtraDataItems(extraData) + const extraDataList = packExtraDataList(extraDataItems) + const extraDataHash = calcExtraDataListHash(extraDataList) - oracleVersion = +(await deployed.oracle.getContractVersion()) - deadline = (await deployed.oracle.getConsensusReport()).processingDeadlineTime + const reportFields = getDefaultReportFields({ + extraDataHash, + extraDataItemsCount: extraDataItems.length, + ...reportFieldsArg + }) - oracle = deployed.oracle - consensus = deployed.consensus + const reportItems = getReportDataItems(reportFields) + const reportHash = calcReportDataHash(reportItems) + + return { + extraData, + extraDataItems, + extraDataList, + extraDataHash, + reportFields, + reportItems, + reportHash + } } - async function prepareNextReport(newReportFields) { - await consensus.setTime(deadline) + async function prepareNextReport({ extraData, extraDataItems, reportFields = {} } = {}) { + const data = getReportData({ extraData, extraDataItems, reportFields }) + + await consensus.submitReport(data.reportFields.refSlot, data.reportHash, CONSENSUS_VERSION, { from: member1 }) + await oracle.submitReportData(data.reportItems, oracleVersion, { from: member1 }) - const newReportItems = getReportDataItems(newReportFields) - const reportHash = calcReportDataHash(newReportItems) + const deadline = (await oracle.getConsensusReport()).processingDeadlineTime + return { + ...data, + deadline + } + } + + async function prepareNextReportInNextFrame({ reportFields = {}, ...prepareArgs } = {}) { await consensus.advanceTimeToNextFrameStart() - await consensus.submitReport(newReportFields.refSlot, reportHash, CONSENSUS_VERSION, { from: member1 }) - await oracle.submitReportData(newReportItems, oracleVersion, { from: member1 }) + const { refSlot } = await consensus.getCurrentFrame() + const next = await prepareNextReport({ + ...prepareArgs, + reportFields: { + ...reportFields, + refSlot + } + }) + return next } context('deploying', () => { @@ -90,46 +127,119 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra it('deploying accounting oracle', async () => { assert.isNotNull(oracle) assert.isNotNull(consensus) - assert.isNotNull(reportItems) - assert.isNotNull(extraData) - assert.isNotNull(extraDataList) - assert.isNotNull(extraDataHash) - assert.isNotNull(extraDataItems) assert.isNotNull(oracleVersion) - assert.isNotNull(deadline) }) }) context('submitReportExtraDataList', () => { beforeEach(deploy) + context('enforces the deadline', () => { + it('reverts with ProcessingDeadlineMissed if deadline missed', async () => { + const { extraDataList, deadline } = await prepareNextReportInNextFrame() + await consensus.advanceTimeToNextFrameStart() + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `ProcessingDeadlineMissed(${+deadline})` + ) + }) + + it('pass successfully if time is equals exactly to deadline value', async () => { + const { extraDataList, deadline, reportFields } = await prepareNextReportInNextFrame() + await consensus.setTime(deadline) + const tx = await oracle.submitReportExtraDataList(extraDataList, { from: member1 }) + assert.emits(tx, 'ExtraDataSubmitted', { refSlot: reportFields.refSlot }) + }) + }) + + context('checks ref slot', () => { + it('reverts with CannotSubmitExtraDataBeforeMainData in attempt of try to pass extra data ahead of submitReportData', async () => { + const { refSlot } = await consensus.getCurrentFrame() + const { reportHash, extraDataList } = getReportData({ reportFields: { refSlot } }) + await consensus.submitReport(refSlot, reportHash, CONSENSUS_VERSION, { from: member1 }) + // No submitReportData here — trying to send extra data ahead of it + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `CannotSubmitExtraDataBeforeMainData()` + ) + }) + + it('pass successfully ', async () => { + const { refSlot } = await consensus.getCurrentFrame() + const { reportFields, reportItems, reportHash, extraDataList } = getReportData({ reportFields: { refSlot } }) + await consensus.submitReport(refSlot, reportHash, CONSENSUS_VERSION, { from: member1 }) + // Now submitReportData on it's place + await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) + const tx = await oracle.submitReportExtraDataList(extraDataList, { from: member1 }) + assert.emits(tx, 'ExtraDataSubmitted', { refSlot: reportFields.refSlot }) + }) + }) + + context('checks extra data hash', () => { + it('reverts with UnexpectedDataHash if hash did not match', async () => { + const { extraDataHash } = await prepareNextReportInNextFrame() + const incorrectExtraData = getDefaultExtraData() + ++incorrectExtraData.stuckKeys[0].nodeOpIds[0] + const incorrectExtraDataItems = encodeExtraDataItems(incorrectExtraData) + const incorrectExtraDataList = packExtraDataList(incorrectExtraDataItems) + const incorrectExtraDataHash = calcExtraDataListHash(incorrectExtraDataList) + await assert.reverts( + oracle.submitReportExtraDataList(incorrectExtraDataList, { from: member1 }), + `UnexpectedExtraDataHash("${extraDataHash}", "${incorrectExtraDataHash}")` + ) + }) + + it('pass successfully if data hash matches', async () => { + const { extraDataList, reportFields } = await prepareNextReportInNextFrame() + const tx = await oracle.submitReportExtraDataList(extraDataList, { from: member1 }) + assert.emits(tx, 'ExtraDataSubmitted', { refSlot: reportFields.refSlot }) + }) + }) + + context('checks items count', () => { + it('reverts with UnexpectedExtraDataItemsCount if there was wrong amount of items', async () => { + const wrongItemsCount = 1 + const reportFields = { + extraDataItemsCount: wrongItemsCount + } + const { extraDataList, extraDataItems } = await prepareNextReportInNextFrame({ reportFields }) + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `UnexpectedExtraDataItemsCount(${reportFields.extraDataItemsCount}, ${extraDataItems.length})` + ) + }) + }) + + context('enforces data format', () => { + it('reverts with UnexpectedExtraDataFormat if there was empty format submitted on first phase', async () => { + const reportFields = { + extraDataHash: ZERO_HASH, + extraDataFormat: EXTRA_DATA_FORMAT_EMPTY, + extraDataItemsCount: 0 + } + const { extraDataList } = await prepareNextReportInNextFrame({ reportFields }) + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `UnexpectedExtraDataFormat(${EXTRA_DATA_FORMAT_EMPTY}, ${EXTRA_DATA_FORMAT_LIST})` + ) + }) + }) + context('enforces module ids sorting order', () => { beforeEach(deploy) it('should revert if incorrect extra data list stuckKeys moduleId', async () => { - const { refSlot } = await consensus.getCurrentFrame() - - const nextRefSlot = +refSlot + SLOTS_PER_FRAME + const extraDataDefault = getDefaultExtraData() const invalidExtraData = { - ...extraData, + ...extraDataDefault, stuckKeys: [ - ...extraData.stuckKeys, + ...extraDataDefault.stuckKeys, { moduleId: 4, nodeOpIds: [1], keysCounts: [2] }, { moduleId: 4, nodeOpIds: [1], keysCounts: [2] } ] } - const extraDataItems = encodeExtraDataItems(invalidExtraData) - const extraDataList = packExtraDataList(extraDataItems) - const extraDataHash = calcExtraDataListHash(extraDataList) - - const newReportFields = { - ...reportFields, - refSlot: nextRefSlot, - extraDataHash: extraDataHash, - extraDataItemsCount: extraDataItems.length - } - await prepareNextReport(newReportFields) + const { extraDataList } = await prepareNextReportInNextFrame({ extraData: invalidExtraData }) await assert.reverts( oracle.submitReportExtraDataList(extraDataList, { @@ -140,29 +250,17 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra }) it('should revert if incorrect extra data list exitedKeys moduleId', async () => { - const { refSlot } = await consensus.getCurrentFrame() - - const nextRefSlot = +refSlot + SLOTS_PER_FRAME + const extraDataDefault = getDefaultExtraData() const invalidExtraData = { - ...extraData, + ...extraDataDefault, exitedKeys: [ - ...extraData.exitedKeys, + ...extraDataDefault.exitedKeys, { moduleId: 4, nodeOpIds: [1], keysCounts: [2] }, { moduleId: 4, nodeOpIds: [1], keysCounts: [2] } ] } - const extraDataItems = encodeExtraDataItems(invalidExtraData) - const extraDataList = packExtraDataList(extraDataItems) - const extraDataHash = calcExtraDataListHash(extraDataList) - - const newReportFields = { - ...reportFields, - refSlot: nextRefSlot, - extraDataHash: extraDataHash, - extraDataItemsCount: extraDataItems.length - } - await prepareNextReport(newReportFields) + const { extraDataList } = await prepareNextReportInNextFrame({ extraData: invalidExtraData }) await assert.reverts( oracle.submitReportExtraDataList(extraDataList, { @@ -172,38 +270,375 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra ) }) - it('should should allow calling if correct extra data list moduleId', async () => { - const { refSlot } = await consensus.getCurrentFrame() - - const nextRefSlot = +refSlot + SLOTS_PER_FRAME + it('should allow calling if correct extra data list moduleId', async () => { + const extraDataDefault = getDefaultExtraData() const invalidExtraData = { stuckKeys: [ - ...extraData.stuckKeys, + ...extraDataDefault.stuckKeys, { moduleId: 4, nodeOpIds: [1], keysCounts: [2] }, { moduleId: 5, nodeOpIds: [1], keysCounts: [2] } ], exitedKeys: [ - ...extraData.exitedKeys, + ...extraDataDefault.exitedKeys, { moduleId: 4, nodeOpIds: [1], keysCounts: [2] }, { moduleId: 5, nodeOpIds: [1], keysCounts: [2] } ] } - const extraDataItems = encodeExtraDataItems(invalidExtraData) - const extraDataList = packExtraDataList(extraDataItems) + + const { extraDataList, reportFields } = await prepareNextReportInNextFrame({ extraData: invalidExtraData }) + + const tx = await oracle.submitReportExtraDataList(extraDataList, { from: member1 }) + assert.emits(tx, 'ExtraDataSubmitted', { refSlot: reportFields.refSlot }) + }) + }) + + context('enforces data safety boundaries', () => { + context('checks encoded data indexes for UnexpectedExtraDataIndex reverts', () => { + // contextual helper to prepeare wrong indexed data + const getExtraWithCustomLastIndex = (itemsCount, lastIndexCustom) => { + const dummyArr = Array.from(Array(itemsCount)) + const stuckKeys = dummyArr.map((_, i) => ({ moduleId: i + 1, nodeOpIds: [0], keysCounts: [i + 1] })) + const extraData = { stuckKeys, exitedKeys: [] } + const extraDataItems = [] + const type = EXTRA_DATA_TYPE_STUCK_VALIDATORS + dummyArr.forEach((_, i) => { + const item = extraData.stuckKeys[i] + const index = i < itemsCount - 1 ? i : lastIndexCustom + extraDataItems.push(encodeExtraDataItem(index, type, item.moduleId, item.nodeOpIds, item.keysCounts)) + }) + return { + extraData, + extraDataItems, + lastIndexDefault: itemsCount - 1, + lastIndexCustom + } + } + + it('if first item index is not zero', async () => { + const { extraData, extraDataItems, lastIndexDefault, lastIndexCustom } = getExtraWithCustomLastIndex(1, 1) + const { extraDataList } = await prepareNextReportInNextFrame({ extraData, extraDataItems }) + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `UnexpectedExtraDataIndex(${lastIndexDefault}, ${lastIndexCustom})` + ) + }) + + it('if next index is greater than previous for more than +1', async () => { + const { extraData, extraDataItems, lastIndexDefault, lastIndexCustom } = getExtraWithCustomLastIndex(2, 2) + const { extraDataList } = await prepareNextReportInNextFrame({ extraData, extraDataItems }) + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `UnexpectedExtraDataIndex(${lastIndexDefault}, ${lastIndexCustom})` + ) + }) + + it('if next index equals to previous', async () => { + const { extraData, extraDataItems, lastIndexDefault, lastIndexCustom } = getExtraWithCustomLastIndex(3, 1) + const { extraDataList } = await prepareNextReportInNextFrame({ extraData, extraDataItems }) + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `UnexpectedExtraDataIndex(${lastIndexDefault}, ${lastIndexCustom})` + ) + }) + + it('if next index less than previous', async () => { + const { extraData, extraDataItems, lastIndexDefault, lastIndexCustom } = getExtraWithCustomLastIndex(3, 0) + const { extraDataList } = await prepareNextReportInNextFrame({ extraData, extraDataItems }) + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `UnexpectedExtraDataIndex(${lastIndexDefault}, ${lastIndexCustom})` + ) + }) + + it('succeeds if indexes were passed sequentially', async () => { + const { extraData, extraDataItems } = getExtraWithCustomLastIndex(3, 2) + const { extraDataList, reportFields } = await prepareNextReportInNextFrame({ extraData, extraDataItems }) + const tx = await oracle.submitReportExtraDataList(extraDataList, { from: member1 }) + assert.emits(tx, 'ExtraDataSubmitted', { refSlot: reportFields.refSlot }) + }) + }) + + context('checks data type for UnsupportedExtraDataType reverts (only supported types are `1` and `2`)', () => { + // contextual helper to prepeare wrong typed data + const getExtraWithCustomType = (typeCustom) => { + const extraData = { + stuckKeys: [{ moduleId: 1, nodeOpIds: [1], keysCounts: [2] }], + exitedKeys: [] + } + const item = extraData.stuckKeys[0] + const extraDataItems = [] + extraDataItems.push(encodeExtraDataItem(0, typeCustom, item.moduleId, item.nodeOpIds, item.keysCounts)) + return { + extraData, + extraDataItems, + wrongTypedIndex: 0, + typeCustom + } + } + + it('if type `0` was passed', async () => { + const { extraData, extraDataItems, wrongTypedIndex, typeCustom } = getExtraWithCustomType(0) + const { extraDataList } = await prepareNextReportInNextFrame({ extraData, extraDataItems }) + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `UnsupportedExtraDataType(${wrongTypedIndex}, ${typeCustom})` + ) + }) + + it('if type `3` was passed', async () => { + const { extraData, extraDataItems, wrongTypedIndex, typeCustom } = getExtraWithCustomType(3) + const { extraDataList } = await prepareNextReportInNextFrame({ extraData, extraDataItems }) + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `UnsupportedExtraDataType(${wrongTypedIndex}, ${typeCustom})` + ) + }) + + it('succeeds if `1` was passed', async () => { + const { extraData, extraDataItems } = getExtraWithCustomType(1) + const { extraDataList, reportFields } = await prepareNextReportInNextFrame({ extraData, extraDataItems }) + const tx = await oracle.submitReportExtraDataList(extraDataList, { from: member1 }) + assert.emits(tx, 'ExtraDataSubmitted', { refSlot: reportFields.refSlot }) + }) + + it('succeeds if `2` was passed', async () => { + const { extraData, extraDataItems } = getExtraWithCustomType(2) + const { extraDataList, reportFields } = await prepareNextReportInNextFrame({ extraData, extraDataItems }) + const tx = await oracle.submitReportExtraDataList(extraDataList, { from: member1 }) + assert.emits(tx, 'ExtraDataSubmitted', { refSlot: reportFields.refSlot }) + }) + }) + + context('should check node operators processing limits with OracleReportSanityChecker', () => { + it('by reverting TooManyNodeOpsPerExtraDataItem if there was too much node operators', async () => { + const problematicItemIdx = 0 + const extraData = { + stuckKeys: [{ moduleId: 1, nodeOpIds: [1, 2], keysCounts: [2, 3] }], + exitedKeys: [] + } + const problematicItemsCount = extraData.stuckKeys[problematicItemIdx].nodeOpIds.length + const { extraDataList } = await prepareNextReportInNextFrame({ extraData }) + await oracleReportSanityChecker.setMaxNodeOperatorsPerExtraDataItemCount(problematicItemsCount - 1) + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `TooManyNodeOpsPerExtraDataItem(${problematicItemIdx}, ${problematicItemsCount})` + ) + }) + + it('should not revert in case when items count exactly equals limit', async () => { + const problematicItemIdx = 0 + const extraData = { + stuckKeys: [{ moduleId: 1, nodeOpIds: [1, 2], keysCounts: [2, 3] }], + exitedKeys: [] + } + const problematicItemsCount = extraData.stuckKeys[problematicItemIdx].nodeOpIds.length + const { extraDataList, reportFields } = await prepareNextReportInNextFrame({ extraData }) + await oracleReportSanityChecker.setMaxAccountingExtraDataListItemsCount(problematicItemsCount) + const tx = await oracle.submitReportExtraDataList(extraDataList, { from: member1 }) + assert.emits(tx, 'ExtraDataSubmitted', { refSlot: reportFields.refSlot }) + }) + }) + + context('checks for InvalidExtraDataItem reverts', () => { + it('reverts if some item not long enough to contain all necessary data — early cut', async () => { + const invalidItemIndex = 1 + const extraData = { + stuckKeys: [ + { moduleId: 1, nodeOpIds: [1], keysCounts: [2] }, + { moduleId: 2, nodeOpIds: [1], keysCounts: [2] } + ], + exitedKeys: [] + } + const extraDataItems = encodeExtraDataItems(extraData) + // Cutting item to provoke error on early stage + // of `_processExtraDataItem` function, check on 776 line in AccountingOracle + const cutStop = 36 + extraDataItems[invalidItemIndex] = extraDataItems[invalidItemIndex].slice(0, cutStop) + const { extraDataList } = await prepareNextReportInNextFrame({ extraData, extraDataItems }) + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `InvalidExtraDataItem(${invalidItemIndex})` + ) + }) + + it('reverts if some item not long enough to contain all necessary data — late cut', async () => { + const invalidItemIndex = 1 + const extraData = { + stuckKeys: [ + { moduleId: 1, nodeOpIds: [1], keysCounts: [2] }, + { moduleId: 2, nodeOpIds: [1, 2, 3, 4], keysCounts: [2] } + ], + exitedKeys: [] + } + const extraDataItems = encodeExtraDataItems(extraData) + // Providing long items and cutting them from end to provoke error on late stage + // of `_processExtraDataItem` function, check on 812 line in AccountingOracle, first condition + const cutStop = extraDataItems[invalidItemIndex].length - 2 + extraDataItems[invalidItemIndex] = extraDataItems[invalidItemIndex].slice(0, cutStop) + const { extraDataList } = await prepareNextReportInNextFrame({ extraData, extraDataItems }) + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `InvalidExtraDataItem(${invalidItemIndex})` + ) + }) + + it('moduleId cannot be zero', async () => { + const invalidItemIndex = 1 + const extraData = { + stuckKeys: [ + { moduleId: 1, nodeOpIds: [1], keysCounts: [2] }, + { moduleId: 0, nodeOpIds: [1], keysCounts: [2] } + ], + exitedKeys: [] + } + const { extraDataList } = await prepareNextReportInNextFrame({ extraData }) + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `InvalidExtraDataItem(${invalidItemIndex})` + ) + }) + + it('checks node ops count to be non-zero', async () => { + const invalidItemIndex = 0 + // Empty nodeOpIds list should provoke check fail + // in `_processExtraDataItem` function, 812 line in AccountingOracle, second condition + const extraData = { + stuckKeys: [ + { moduleId: 1, nodeOpIds: [], keysCounts: [2] }, + { moduleId: 2, nodeOpIds: [1], keysCounts: [2] } + ], + exitedKeys: [] + } + const extraDataItems = encodeExtraDataItems(extraData) + const { extraDataList } = await prepareNextReportInNextFrame({ extraData, extraDataItems }) + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `InvalidExtraDataItem(${invalidItemIndex})` + ) + }) + }) + + it('reverts on extra bytes in data', async () => { + await consensus.advanceTimeToNextFrameStart() + const { refSlot } = await consensus.getCurrentFrame() + + const extraDataItems = encodeExtraDataItems(getDefaultExtraData()) + const extraDataList = packExtraDataList(extraDataItems) + 'ffff' const extraDataHash = calcExtraDataListHash(extraDataList) - const newReportFields = { - ...reportFields, - refSlot: nextRefSlot, - extraDataHash: extraDataHash, - extraDataItemsCount: extraDataItems.length + const reportFields = getDefaultReportFields({ + extraDataHash, + extraDataItemsCount: extraDataItems.length, + refSlot + }) + + const reportItems = getReportDataItems(reportFields) + const reportHash = calcReportDataHash(reportItems) + + await consensus.submitReport(reportFields.refSlot, reportHash, CONSENSUS_VERSION, { + from: member1 + }) + await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) + + await assert.reverts( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + 'UnexpectedExtraDataIndex(5, 16776960)' + ) + }) + }) + + context('delivers the data to staking router', () => { + it('calls reportStakingModuleStuckValidatorsCountByNodeOperator on StakingRouter', async () => { + const { extraData, extraDataList } = await prepareNextReportInNextFrame() + await oracle.submitReportExtraDataList(extraDataList, { from: member1 }) + + const callsCount = await stakingRouter.totalCalls_reportStuckKeysByNodeOperator() + assert.equals(callsCount, extraData.stuckKeys.length) + + for (let i = 0; i < callsCount; i++) { + const call = await stakingRouter.calls_reportStuckKeysByNodeOperator(i) + const item = extraData.stuckKeys[i] + assert.equals(+call.stakingModuleId, item.moduleId) + assert.equals(call.nodeOperatorIds, '0x' + item.nodeOpIds.map((id) => hex(id, 8)).join('')) + assert.equals(call.keysCounts, '0x' + item.keysCounts.map((count) => hex(count, 16)).join('')) } + }) - await prepareNextReport(newReportFields) + it('calls reportStakingModuleExitedValidatorsCountByNodeOperator on StakingRouter', async () => { + const { extraData, extraDataList } = await prepareNextReportInNextFrame() + await oracle.submitReportExtraDataList(extraDataList, { from: member1 }) - const tx = await oracle.submitReportExtraDataList(extraDataList, { from: member1 }) - assertEvent(tx, 'ExtraDataSubmitted', { expectedArgs: { refSlot: newReportFields.refSlot } }) + const callsCount = await stakingRouter.totalCalls_reportExitedKeysByNodeOperator() + assert.equals(callsCount, extraData.exitedKeys.length) + + for (let i = 0; i < callsCount; i++) { + const call = await stakingRouter.calls_reportExitedKeysByNodeOperator(i) + const item = extraData.exitedKeys[i] + assert.equals(+call.stakingModuleId, item.moduleId) + assert.equals(call.nodeOperatorIds, '0x' + item.nodeOpIds.map((id) => hex(id, 8)).join('')) + assert.equals(call.keysCounts, '0x' + item.keysCounts.map((count) => hex(count, 16)).join('')) + } + }) + + it('calls onValidatorsCountsByNodeOperatorReportingFinished on StakingRouter', async () => { + const { extraData, extraDataList } = await prepareNextReportInNextFrame() + await oracle.submitReportExtraDataList(extraDataList, { from: member1 }) + const callsCount = await stakingRouter.totalCalls_onValidatorsCountsByNodeOperatorReportingFinished() + assert.equals(callsCount, 1) }) }) + + it('reverts if extraData has already been already processed', async () => { + const { extraDataItems, extraDataList } = await prepareNextReportInNextFrame() + await oracle.submitReportExtraDataList(extraDataList, { from: member1 }) + const state = await oracle.getExtraDataProcessingState() + assert.equals(+state.itemsCount, extraDataItems.length) + assert.equals(+state.itemsCount, state.itemsProcessed) + await assert.revertsWithCustomError( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + `ExtraDataAlreadyProcessed()` + ) + }) + + it('reverts if main data has not been processed yet', async () => { + await consensus.advanceTimeToNextFrameStart() + const { refSlot } = await consensus.getCurrentFrame() + const { reportFields, reportHash, extraDataList } = getReportData({ reportFields: { refSlot } }) + await consensus.submitReport(reportFields.refSlot, reportHash, CONSENSUS_VERSION, { from: member1 }) + await assert.revertsWithCustomError( + oracle.submitReportExtraDataList(extraDataList, { from: member1 }), + 'CannotSubmitExtraDataBeforeMainData()' + ) + }) + + it('updates extra data processing state', async () => { + const { extraDataItems, extraDataHash, reportFields, extraDataList } = await prepareNextReportInNextFrame() + + const stateBefore = await oracle.getExtraDataProcessingState() + + assert.equals(+stateBefore.refSlot, reportFields.refSlot) + assert.equals(+stateBefore.dataFormat, EXTRA_DATA_FORMAT_LIST) + assert.isFalse(stateBefore.submitted) + assert.equals(+stateBefore.itemsCount, extraDataItems.length) + assert.equals(+stateBefore.itemsProcessed, 0) + assert.equals(+stateBefore.lastSortingKey, '0') + assert.equals(stateBefore.dataHash, extraDataHash) + + await oracle.submitReportExtraDataList(extraDataList, { from: member1 }) + + const stateAfter = await oracle.getExtraDataProcessingState() + + assert.equals(+stateAfter.refSlot, reportFields.refSlot) + assert.equals(+stateAfter.dataFormat, EXTRA_DATA_FORMAT_LIST) + assert.isTrue(stateAfter.submitted) + assert.equals(+stateAfter.itemsCount, extraDataItems.length) + assert.equals(stateAfter.itemsProcessed, extraDataItems.length) + // TODO: figure out how to build this value and test it properly + assert.equals( + stateAfter.lastSortingKey, + '3533694129556768659166595001485837031654967793751237971583444623713894401' + ) + assert.equals(stateAfter.dataHash, extraDataHash) + }) }) }) diff --git a/test/0.8.9/oracle/base-oracle-submit-report.test.js b/test/0.8.9/oracle/base-oracle-submit-report.test.js index 39003637c..feb145ea2 100644 --- a/test/0.8.9/oracle/base-oracle-submit-report.test.js +++ b/test/0.8.9/oracle/base-oracle-submit-report.test.js @@ -158,7 +158,7 @@ contract('BaseOracle', ([admin]) => { describe('_startProcessing safely advances processing state', () => { before(deployContract) - it('initial contract state,no reports, can not startProcessing', async () => { + it('initial contract state, no reports, cannot startProcessing', async () => { await assert.revertsWithCustomError(baseOracle.startProcessing(), 'ProcessingDeadlineMissed(0)') }) @@ -169,12 +169,8 @@ contract('BaseOracle', ([admin]) => { assert.emits(tx, 'MockStartProcessingResult', { prevProcessingRefSlot: '0' }) }) - it('no next report, processing same slot again, state is not changed', async () => { - const tx = await baseOracle.startProcessing() - assert.emits(tx, 'ProcessingStarted', { refSlot: initialRefSlot, hash: HASH_1 }) - assert.emits(tx, 'MockStartProcessingResult', { prevProcessingRefSlot: String(initialRefSlot) }) - const processingSlot = await baseOracle.getLastProcessingRefSlot() - assert.equals(processingSlot, initialRefSlot) + it('trying to start processing the same slot again reverts', async () => { + await assert.reverts(baseOracle.startProcessing(), 'RefSlotAlreadyProcessing()') }) it('next report comes in, start processing, state advances', async () => { diff --git a/test/0.8.9/oracle/hash-consensus-access-control.test.js b/test/0.8.9/oracle/hash-consensus-access-control.test.js index c4776fb6f..6ebea9717 100644 --- a/test/0.8.9/oracle/hash-consensus-access-control.test.js +++ b/test/0.8.9/oracle/hash-consensus-access-control.test.js @@ -1,5 +1,8 @@ +const hre = require('hardhat') + const { MaxUint256 } = require('@ethersproject/constants') const { assert } = require('../../helpers/assert') +const { EvmSnapshot } = require('../../helpers/blockchain') const { deployHashConsensus, EPOCHS_PER_FRAME, CONSENSUS_VERSION } = require('./hash-consensus-deploy.test') @@ -8,6 +11,9 @@ const MockReportProcessor = artifacts.require('MockReportProcessor') contract('HashConsensus', ([admin, account1, account2, member1, member2]) => { let consensus = null let reportProcessor = null + let snapshot = null + let reportProcessor2 = null + const manageMembersAndQuorumRoleKeccak156 = web3.utils.keccak256('MANAGE_MEMBERS_AND_QUORUM_ROLE') const disableConsensusRoleKeccak156 = web3.utils.keccak256('DISABLE_CONSENSUS_ROLE') const manageFrameConfigRoleKeccak156 = web3.utils.keccak256('MANAGE_FRAME_CONFIG_ROLE') @@ -18,6 +24,11 @@ contract('HashConsensus', ([admin, account1, account2, member1, member2]) => { const deployed = await deployHashConsensus(admin, options) consensus = deployed.consensus reportProcessor = deployed.reportProcessor + + reportProcessor2 = await MockReportProcessor.new(CONSENSUS_VERSION, { from: admin }) + + snapshot = new EvmSnapshot(hre.ethers.provider) + await snapshot.make() } context('deploying', () => { @@ -29,9 +40,11 @@ contract('HashConsensus', ([admin, account1, account2, member1, member2]) => { }) }) - context('MANAGE_MEMBERS_AND_QUORUM_ROLE', () => { - beforeEach(deploy) + afterEach(async () => { + await snapshot.rollback() + }) + context('MANAGE_MEMBERS_AND_QUORUM_ROLE', () => { context('addMember', () => { it('should revert without MANAGE_MEMBERS_AND_QUORUM_ROLE role', async () => { await assert.revertsOZAccessControl( @@ -106,8 +119,6 @@ contract('HashConsensus', ([admin, account1, account2, member1, member2]) => { }) context('DISABLE_CONSENSUS_ROLE', () => { - beforeEach(deploy) - context('setQuorum', () => { it('should revert without DISABLE_CONSENSUS_ROLE role', async () => { await assert.revertsOZAccessControl( @@ -146,8 +157,6 @@ contract('HashConsensus', ([admin, account1, account2, member1, member2]) => { }) context('MANAGE_FRAME_CONFIG_ROLE', () => { - beforeEach(deploy) - context('setFrameConfig', () => { it('should revert without MANAGE_FRAME_CONFIG_ROLE role', async () => { await assert.revertsOZAccessControl( @@ -168,11 +177,7 @@ contract('HashConsensus', ([admin, account1, account2, member1, member2]) => { }) context('MANAGE_REPORT_PROCESSOR_ROLE', () => { - beforeEach(deploy) - context('setReportProcessor', async () => { - const reportProcessor2 = await MockReportProcessor.new(CONSENSUS_VERSION, { from: admin }) - it('should revert without MANAGE_REPORT_PROCESSOR_ROLE role', async () => { await assert.revertsOZAccessControl( consensus.setReportProcessor(reportProcessor2.address, { from: account1 }), @@ -191,8 +196,6 @@ contract('HashConsensus', ([admin, account1, account2, member1, member2]) => { }) context('MANAGE_FAST_LANE_CONFIG_ROLE', () => { - beforeEach(deploy) - context('setFastLaneLengthSlots', () => { it('should revert without MANAGE_FAST_LANE_CONFIG_ROLE role', async () => { await assert.revertsOZAccessControl( diff --git a/test/0.8.9/oracle/hash-consensus-deploy.test.js b/test/0.8.9/oracle/hash-consensus-deploy.test.js index b2ddd2f5f..f8bcd930a 100644 --- a/test/0.8.9/oracle/hash-consensus-deploy.test.js +++ b/test/0.8.9/oracle/hash-consensus-deploy.test.js @@ -127,7 +127,7 @@ contract('HashConsensus', ([admin, member1]) => { }) it('reverts if report processor address is zero', async () => { - await assert.reverts(HashConsensus.new( + await assert.revertsWithCustomError(HashConsensus.new( SLOTS_PER_EPOCH, SECONDS_PER_SLOT, GENESIS_TIME, @@ -144,7 +144,7 @@ contract('HashConsensus', ([admin, member1]) => { it('reverts if admin address is zero', async () => { const reportProcessor = await MockReportProcessor.new(CONSENSUS_VERSION, { from: admin }) - await assert.reverts(HashConsensus.new( + await assert.revertsWithCustomError(HashConsensus.new( SLOTS_PER_EPOCH, SECONDS_PER_SLOT, GENESIS_TIME, diff --git a/test/0.8.9/oracle/validators-exit-bus-oracle-access-control.test.js b/test/0.8.9/oracle/validators-exit-bus-oracle-access-control.test.js new file mode 100644 index 000000000..684e59a81 --- /dev/null +++ b/test/0.8.9/oracle/validators-exit-bus-oracle-access-control.test.js @@ -0,0 +1,169 @@ +const { assert } = require('chai') +const { ZERO_ADDRESS } = require('@aragon/contract-helpers-test') + +const { + CONSENSUS_VERSION, + DATA_FORMAT_LIST, + getReportDataItems, + calcReportDataHash, + encodeExitRequestsDataList, + deployExitBusOracle +} = require('./validators-exit-bus-oracle-deploy.test') +const PUBKEYS = [ + '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + '0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc' +] + +contract('ValidatorsExitBusOracle', ([admin, member1, member2, member3, account1, stranger]) => { + let consensus + let oracle + let oracleVersion + let initTx + let exitRequests + let reportFields + let reportItems + let reportHash + + const submitDataRoleKeccak156 = web3.utils.keccak256('SUBMIT_DATA_ROLE') + const pauseRoleKeccak156 = web3.utils.keccak256('PAUSE_ROLE') + const resumeRoleKeccak156 = web3.utils.keccak256('RESUME_ROLE') + + const getReportFields = (override = {}) => ({ + consensusVersion: CONSENSUS_VERSION, + dataFormat: DATA_FORMAT_LIST, + ...override + }) + const deploy = async () => { + const deployed = await deployExitBusOracle(admin, { resumeAfterDeploy: true }) + consensus = deployed.consensus + oracle = deployed.oracle + initTx = deployed.initTx + + oracleVersion = +(await oracle.getContractVersion()) + + await consensus.addMember(member1, 1, { from: admin }) + await consensus.addMember(member2, 2, { from: admin }) + await consensus.addMember(member3, 2, { from: admin }) + + const { refSlot } = await deployed.consensus.getCurrentFrame() + exitRequests = [ + { moduleId: 1, nodeOpId: 0, valIndex: 0, valPubkey: PUBKEYS[0] }, + { moduleId: 1, nodeOpId: 0, valIndex: 2, valPubkey: PUBKEYS[1] }, + { moduleId: 2, nodeOpId: 0, valIndex: 1, valPubkey: PUBKEYS[2] } + ] + + reportFields = getReportFields({ + refSlot: +refSlot, + requestsCount: exitRequests.length, + data: encodeExitRequestsDataList(exitRequests) + }) + + reportItems = getReportDataItems(reportFields) + reportHash = calcReportDataHash(reportItems) + + await deployed.consensus.submitReport(refSlot, reportHash, CONSENSUS_VERSION, { from: member1 }) + await deployed.consensus.submitReport(refSlot, reportHash, CONSENSUS_VERSION, { from: member3 }) + } + + context('Access control', () => { + context('deploying', () => { + before(deploy) + + it('deploying accounting oracle', async () => { + assert.isDefined(oracle) + assert.isDefined(consensus) + assert.isDefined(oracleVersion) + assert.isDefined(initTx) + assert.isDefined(exitRequests) + assert.isDefined(reportFields) + assert.isDefined(reportItems) + assert.isDefined(reportHash) + }) + }) + context('DEFAULT_ADMIN_ROLE', () => { + beforeEach(deploy) + + context('Admin is set at initialize', () => { + it('should set admin at initialize', async () => { + const DEFAULT_ADMIN_ROLE = await oracle.DEFAULT_ADMIN_ROLE() + await assert.emits(initTx, 'RoleGranted', { role: DEFAULT_ADMIN_ROLE, account: admin, sender: admin }) + }) + + it('should revert without admin address', async () => { + const pauser = ZERO_ADDRESS + const resumer = ZERO_ADDRESS + await assert.reverts( + oracle.initialize(ZERO_ADDRESS, pauser, resumer, consensus.address, CONSENSUS_VERSION, 0, { + from: admin + }), + 'AdminCannotBeZero()' + ) + }) + }) + }) + + context('SUBMIT_DATA_ROLE', () => { + beforeEach(deploy) + + context('_checkMsgSenderIsAllowedToSubmitData', () => { + it('should revert from not consensus member without SUBMIT_DATA_ROLE role', async () => { + await assert.reverts( + oracle.submitReportData(reportItems, oracleVersion, { from: stranger }), + 'SenderNotAllowed()' + ) + }) + + it('should allow calling from a possessor of SUBMIT_DATA_ROLE role', async () => { + await oracle.grantRole(submitDataRoleKeccak156, account1) + const deadline = (await oracle.getConsensusReport()).processingDeadlineTime + await consensus.setTime(deadline) + + const tx = await oracle.submitReportData(reportItems, oracleVersion, { from: account1 }) + assert.emits(tx, 'ProcessingStarted', { refSlot: reportFields.refSlot }) + }) + it('should allow calling from a member', async () => { + const tx = await oracle.submitReportData(reportItems, CONSENSUS_VERSION, { from: member2 }) + assert.emits(tx, 'ProcessingStarted', { refSlot: reportFields.refSlot }) + }) + }) + }) + + context('PAUSE_ROLE', () => { + beforeEach(deploy) + + context('pause', () => { + it('should revert without PAUSE_ROLE role', async () => { + await assert.revertsOZAccessControl(oracle.pause(0, { from: stranger }), stranger, 'PAUSE_ROLE') + }) + + it('should allow calling from a possessor of PAUSE_ROLE role', async () => { + await oracle.grantRole(pauseRoleKeccak156, account1) + + const tx = await oracle.pause(9999, { from: account1 }) + assert.emits(tx, 'Paused', { duration: 9999 }) + }) + }) + }) + + context('RESUME_ROLE', () => { + beforeEach(deploy) + + context('resume', () => { + it('should revert without RESUME_ROLE role', async () => { + await oracle.pause(9999, { from: admin }) + + await assert.revertsOZAccessControl(oracle.resume({ from: stranger }), stranger, 'RESUME_ROLE') + }) + + it('should allow calling from a possessor of RESUME_ROLE role', async () => { + await oracle.pause(9999, { from: admin }) + await oracle.grantRole(resumeRoleKeccak156, account1) + + const tx = await oracle.resume({ from: account1 }) + assert.emits(tx, 'Resumed') + }) + }) + }) + }) +}) diff --git a/test/0.8.9/oracle/validators-exit-bus-oracle-deploy.test.js b/test/0.8.9/oracle/validators-exit-bus-oracle-deploy.test.js index afac6493f..35148c5b7 100644 --- a/test/0.8.9/oracle/validators-exit-bus-oracle-deploy.test.js +++ b/test/0.8.9/oracle/validators-exit-bus-oracle-deploy.test.js @@ -1,35 +1,36 @@ -const { BN } = require('bn.js') -const { assert } = require('chai') -const { assertBn, assertEvent, assertAmountOfEvents } = require('@aragon/contract-helpers-test/src/asserts') -const { assertRevert } = require('../../helpers/assertThrow') -const { assertBnClose, e18, hex, strip0x } = require('../../helpers/utils') -const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') -const { updateLocatorImplementation, deployLocatorWithDummyAddressesImplementation } = require('../../helpers/locator-deploy') +const { assert } = require('../../helpers/assert') +const { hex, strip0x } = require('../../helpers/utils') +const { ZERO_ADDRESS } = require('@aragon/contract-helpers-test') +const { + updateLocatorImplementation, + deployLocatorWithDummyAddressesImplementation +} = require('../../helpers/locator-deploy') const { - SLOTS_PER_EPOCH, SECONDS_PER_SLOT, GENESIS_TIME, SECONDS_PER_EPOCH, - computeSlotAt, computeEpochAt, computeEpochFirstSlotAt, - computeEpochFirstSlot, computeTimestampAtSlot, computeTimestampAtEpoch, - ZERO_HASH, HASH_1, HASH_2, HASH_3, HASH_4, HASH_5, CONSENSUS_VERSION, - deployHashConsensus } = require('./hash-consensus-deploy.test') + SLOTS_PER_EPOCH, + SECONDS_PER_SLOT, + GENESIS_TIME, + SECONDS_PER_EPOCH, + computeSlotAt, + computeEpochAt, + computeEpochFirstSlotAt, + computeEpochFirstSlot, + computeTimestampAtSlot, + computeTimestampAtEpoch, + ZERO_HASH, + CONSENSUS_VERSION, + deployHashConsensus +} = require('./hash-consensus-deploy.test') const ValidatorsExitBusOracle = artifacts.require('ValidatorsExitBusTimeTravellable') const DATA_FORMAT_LIST = 1 - - function getReportDataItems(r) { return [r.consensusVersion, r.refSlot, r.requestsCount, r.dataFormat, r.data] } function calcReportDataHash(reportItems) { - const data = web3.eth.abi.encodeParameters( - ['(uint256,uint256,uint256,uint256,bytes)'], - [reportItems] - ) - // const toS = x => Array.isArray(x) ? `[${x.map(toS)}]` : `${x}` - // console.log(toS(reportItems)) - // console.log(data) + const data = web3.eth.abi.encodeParameters(['(uint256,uint256,uint256,uint256,bytes)'], [reportItems]) return web3.utils.keccak256(data) } @@ -50,123 +51,153 @@ const SECONDS_PER_FRAME = EPOCHS_PER_FRAME * SECONDS_PER_EPOCH const MAX_REQUESTS_PER_REPORT = 6 const MAX_REQUESTS_LIST_LENGTH = 5 const MAX_REQUESTS_PER_DAY = 5 - - module.exports = { - SLOTS_PER_EPOCH, SECONDS_PER_SLOT, GENESIS_TIME, SECONDS_PER_EPOCH, - EPOCHS_PER_FRAME, SLOTS_PER_FRAME, SECONDS_PER_FRAME, - MAX_REQUESTS_PER_REPORT, MAX_REQUESTS_LIST_LENGTH, + SLOTS_PER_EPOCH, + SECONDS_PER_SLOT, + GENESIS_TIME, + SECONDS_PER_EPOCH, + EPOCHS_PER_FRAME, + SLOTS_PER_FRAME, + SECONDS_PER_FRAME, + MAX_REQUESTS_PER_REPORT, + MAX_REQUESTS_LIST_LENGTH, MAX_REQUESTS_PER_DAY, - computeSlotAt, computeEpochAt, computeEpochFirstSlotAt, - computeEpochFirstSlot, computeTimestampAtSlot, computeTimestampAtEpoch, - ZERO_HASH, CONSENSUS_VERSION, DATA_FORMAT_LIST, - getReportDataItems, calcReportDataHash, encodeExitRequestHex, - encodeExitRequestsDataList, deployExitBusOracle, + computeSlotAt, + computeEpochAt, + computeEpochFirstSlotAt, + computeEpochFirstSlot, + computeTimestampAtSlot, + computeTimestampAtEpoch, + ZERO_HASH, + CONSENSUS_VERSION, + DATA_FORMAT_LIST, + getReportDataItems, + calcReportDataHash, + encodeExitRequestHex, + encodeExitRequestsDataList, + deployExitBusOracle, deployOracleReportSanityCheckerForExitBus } - async function deployOracleReportSanityCheckerForExitBus(lidoLocator, admin) { const maxValidatorExitRequestsPerReport = 2000 - const limitsList = [0, 0, 0, 0, 0, 0, maxValidatorExitRequestsPerReport, 0] - const managersRoster = [[admin], [], [], [], [], [], [], [], []] + const limitsList = [0, 0, 0, 0, maxValidatorExitRequestsPerReport, 0, 0, 0, 0] + const managersRoster = [[admin], [admin], [admin], [admin], [admin], [admin], [admin], [admin], [admin], [admin]] const OracleReportSanityChecker = artifacts.require('OracleReportSanityChecker') - let oracleReportSanityChecker = await OracleReportSanityChecker.new( - lidoLocator, admin, limitsList, managersRoster, { from: admin }) - return oracleReportSanityChecker.address + const oracleReportSanityChecker = await OracleReportSanityChecker.new( + lidoLocator, + admin, + limitsList, + managersRoster, + { + from: admin + } + ) + return oracleReportSanityChecker } -async function deployExitBusOracle(admin, { - dataSubmitter = null, - lastProcessingRefSlot = 0, - resumeAfterDeploy = false, -} = {}) { +async function deployExitBusOracle( + admin, + { + dataSubmitter = null, + lastProcessingRefSlot = 0, + resumeAfterDeploy = false, + pauser = ZERO_ADDRESS, + resumer = ZERO_ADDRESS + } = {} +) { const locator = (await deployLocatorWithDummyAddressesImplementation(admin)).address - const oracle = await ValidatorsExitBusOracle.new( - SECONDS_PER_SLOT, GENESIS_TIME, locator, {from: admin}) + const oracle = await ValidatorsExitBusOracle.new(SECONDS_PER_SLOT, GENESIS_TIME, locator, { from: admin }) - const {consensus} = await deployHashConsensus(admin, { + const { consensus } = await deployHashConsensus(admin, { epochsPerFrame: EPOCHS_PER_FRAME, - reportProcessor: oracle, + reportProcessor: oracle }) const oracleReportSanityChecker = await deployOracleReportSanityCheckerForExitBus(locator, admin) await updateLocatorImplementation(locator, admin, { validatorsExitBusOracle: oracle.address, - oracleReportSanityChecker : oracleReportSanityChecker, + oracleReportSanityChecker: oracleReportSanityChecker.address }) - const tx = await oracle.initialize( + const initTx = await oracle.initialize( admin, + pauser, + resumer, consensus.address, CONSENSUS_VERSION, lastProcessingRefSlot, - {from: admin} + { from: admin } ) - assertEvent(tx, 'ContractVersionSet', {expectedArgs: {version: 1}}) + assert.emits(initTx, 'ContractVersionSet', { version: 1 }) - assertEvent(tx, 'RoleGranted', {expectedArgs: { + assert.emits(initTx, 'RoleGranted', { role: await consensus.DEFAULT_ADMIN_ROLE(), account: admin, sender: admin - }}) + }) - assertEvent(tx, 'ConsensusHashContractSet', {expectedArgs: { + assert.emits(initTx, 'ConsensusHashContractSet', { addr: consensus.address, prevAddr: ZERO_ADDRESS - }}) + }) - assertEvent(tx, 'ConsensusVersionSet', {expectedArgs: {version: CONSENSUS_VERSION, prevVersion: 0}}) + assert.emits(initTx, 'ConsensusVersionSet', { version: CONSENSUS_VERSION, prevVersion: 0 }) - await oracle.grantRole(await oracle.MANAGE_CONSENSUS_CONTRACT_ROLE(), admin, {from: admin}) - await oracle.grantRole(await oracle.MANAGE_CONSENSUS_VERSION_ROLE(), admin, {from: admin}) - await oracle.grantRole(await oracle.PAUSE_ROLE(), admin, {from: admin}) - await oracle.grantRole(await oracle.RESUME_ROLE(), admin, {from: admin}) + await oracle.grantRole(await oracle.MANAGE_CONSENSUS_CONTRACT_ROLE(), admin, { from: admin }) + await oracle.grantRole(await oracle.MANAGE_CONSENSUS_VERSION_ROLE(), admin, { from: admin }) + await oracle.grantRole(await oracle.PAUSE_ROLE(), admin, { from: admin }) + await oracle.grantRole(await oracle.RESUME_ROLE(), admin, { from: admin }) if (dataSubmitter != null) { - await oracle.grantRole(await oracle.SUBMIT_DATA_ROLE(), dataSubmitter, {from: admin}) + await oracle.grantRole(await oracle.SUBMIT_DATA_ROLE(), dataSubmitter, { from: admin }) } - assert.equal(+await oracle.DATA_FORMAT_LIST(), DATA_FORMAT_LIST) + assert.equal(+(await oracle.DATA_FORMAT_LIST()), DATA_FORMAT_LIST) if (resumeAfterDeploy) { - await oracle.resume({from: admin}) + await oracle.resume({ from: admin }) } - return {consensus, oracle, locator} + return { consensus, oracle, oracleReportSanityChecker, locator, initTx } } - contract('ValidatorsExitBusOracle', ([admin, member1]) => { let consensus let oracle context('Deployment and initial configuration', () => { - it('deployment finishes successfully', async () => { - const deployed = await deployExitBusOracle(admin, {resumeAfterDeploy: false}) + const deployed = await deployExitBusOracle(admin, { resumeAfterDeploy: false }) consensus = deployed.consensus oracle = deployed.oracle }) it('mock time-travellable setup is correct', async () => { - const time1 = +await consensus.getTime() - assert.equal(+await oracle.getTime(), time1) + const time1 = +(await consensus.getTime()) + assert.equal(+(await oracle.getTime()), time1) await consensus.advanceTimeBy(SECONDS_PER_SLOT) - - const time2 = +await consensus.getTime() + const time2 = +(await consensus.getTime()) assert.equal(time2, time1 + SECONDS_PER_SLOT) - assert.equal(+await oracle.getTime(), time2) + assert.equal(+(await oracle.getTime()), time2) }) it('initial configuration is correct', async () => { assert.equal(await oracle.getConsensusContract(), consensus.address) - assert.equal(+await oracle.getConsensusVersion(), CONSENSUS_VERSION) - assert.equal(+await oracle.SECONDS_PER_SLOT(), SECONDS_PER_SLOT) + assert.equal(+(await oracle.getConsensusVersion()), CONSENSUS_VERSION) + assert.equal(+(await oracle.SECONDS_PER_SLOT()), SECONDS_PER_SLOT) + assert.equal(await oracle.isPaused(), true) + }) + + it('pause/resume operations work', async () => { + assert.equal(await oracle.isPaused(), true) + await oracle.resume() + assert.equal(await oracle.isPaused(), false) + await oracle.pause(123) assert.equal(await oracle.isPaused(), true) }) }) diff --git a/test/0.8.9/oracle/validators-exit-bus-oracle-gas.test.js b/test/0.8.9/oracle/validators-exit-bus-oracle-gas.test.js index 9f803ed66..9478f9008 100644 --- a/test/0.8.9/oracle/validators-exit-bus-oracle-gas.test.js +++ b/test/0.8.9/oracle/validators-exit-bus-oracle-gas.test.js @@ -53,34 +53,41 @@ contract('ValidatorsExitBusOracle', ([admin, member1, member2, member3, stranger assert.equal((await consensus.getConsensusState()).consensusReport, hash) } + const NUM_MODULES = 5 + const NODE_OPS_PER_MODULE = 100 + + let nextValIndex = 1 + function generateExitRequests(totalRequests) { - const requestsByModule = Math.max(3, Math.floor(totalRequests / 3)) - const requestsByNodeOp = Math.max(1, Math.floor(requestsByModule / 3)) + const requestsPerModule = Math.max(1, Math.floor(totalRequests / NUM_MODULES)) + const requestsPerNodeOp = Math.max(1, Math.floor(requestsPerModule / NODE_OPS_PER_MODULE)) const requests = [] for (let i = 0; i < totalRequests; ++i) { - const moduleId = Math.floor(i / requestsByModule) - const nodeOpId = Math.floor((i - moduleId * requestsByModule) / requestsByNodeOp) - const valIndex = i - moduleId * requestsByModule - nodeOpId * requestsByNodeOp + const moduleId = Math.floor(i / requestsPerModule) + const nodeOpId = Math.floor((i - moduleId * requestsPerModule) / requestsPerNodeOp) + const valIndex = nextValIndex++ const valPubkey = PUBKEYS[valIndex % PUBKEYS.length] - requests.push({moduleId: moduleId + 1, nodeOpId, valIndex, valPubkey }) + requests.push({moduleId: moduleId + 1, nodeOpId, valIndex, valPubkey}) } - return requests + return {requests, requestsPerModule, requestsPerNodeOp} } // pre-heating - testGas(10, () => {}) + testGas(NUM_MODULES * NODE_OPS_PER_MODULE, () => {}) const gasUsages = []; [10, 50, 100, 1000, 2000].forEach(n => testGas(n, r => gasUsages.push(r))) after(async () => { - gasUsages.forEach(({totalRequests, gasUsed}) => console.log( - `${totalRequests} requests: total gas ${ gasUsed }, ` + - `gas per request: ${ Math.round(gasUsed / totalRequests )}` - )) + gasUsages.forEach(({totalRequests, requestsPerModule, requestsPerNodeOp, gasUsed}) => + console.log( + `${totalRequests} requests (per module ${requestsPerModule}, ` + + `per node op ${requestsPerNodeOp}): total gas ${gasUsed}, ` + + `gas per request: ${Math.round(gasUsed / totalRequests)}` + )) }) function testGas(totalRequests, reportGas) { @@ -110,9 +117,9 @@ contract('ValidatorsExitBusOracle', ([admin, member1, member2, member3, stranger reportFields = { consensusVersion: CONSENSUS_VERSION, refSlot: +refSlot, - requestsCount: exitRequests.length, + requestsCount: exitRequests.requests.length, dataFormat: DATA_FORMAT_LIST, - data: encodeExitRequestsDataList(exitRequests), + data: encodeExitRequestsDataList(exitRequests.requests), } reportItems = getReportDataItems(reportFields) @@ -149,19 +156,20 @@ contract('ValidatorsExitBusOracle', ([admin, member1, member2, member3, stranger assert.isTrue((await oracle.getConsensusReport()).processingStarted) const timestamp = await oracle.getTime() + const {requests, requestsPerModule, requestsPerNodeOp} = exitRequests - for (let i = 0; i < exitRequests.length; ++i) { + for (let i = 0; i < requests.length; ++i) { assertEvent(tx, 'ValidatorExitRequest', {index: i, expectedArgs: { - stakingModuleId: exitRequests[i].moduleId, - nodeOperatorId: exitRequests[i].nodeOpId, - validatorIndex: exitRequests[i].valIndex, - validatorPubkey: exitRequests[i].valPubkey, + stakingModuleId: requests[i].moduleId, + nodeOperatorId: requests[i].nodeOpId, + validatorIndex: requests[i].valIndex, + validatorPubkey: requests[i].valPubkey, timestamp }}) } const {gasUsed} = tx.receipt - reportGas({totalRequests, gasUsed}) + reportGas({totalRequests, requestsPerModule, requestsPerNodeOp, gasUsed}) }) it(`reports are marked as processed`, async () => { @@ -169,8 +177,8 @@ contract('ValidatorsExitBusOracle', ([admin, member1, member2, member3, stranger assert.equal(procState.dataHash, reportHash) assert.isTrue(procState.dataSubmitted) assert.equal(+procState.dataFormat, DATA_FORMAT_LIST) - assert.equal(+procState.requestsCount, exitRequests.length) - assert.equal(+procState.requestsSubmitted, exitRequests.length) + assert.equal(+procState.requestsCount, exitRequests.requests.length) + assert.equal(+procState.requestsSubmitted, exitRequests.requests.length) }) it('some time passes', async () => { diff --git a/test/0.8.9/oracle/validators-exit-bus-oracle-happy-path.test.js b/test/0.8.9/oracle/validators-exit-bus-oracle-happy-path.test.js index 1a4293154..9aa1f7fbc 100644 --- a/test/0.8.9/oracle/validators-exit-bus-oracle-happy-path.test.js +++ b/test/0.8.9/oracle/validators-exit-bus-oracle-happy-path.test.js @@ -213,5 +213,12 @@ contract('ValidatorsExitBusOracle', ([admin, member1, member2, member3, stranger assert.sameOrderedMembers(toNum(indices1), [2, -1, -1]) assert.sameOrderedMembers(toNum(indices2), [1, -1, -1]) }) + + it(`no data can be submitted for the same reference slot again`, async () => { + await assert.reverts( + oracle.submitReportData(reportItems, oracleVersion, {from: member2}), + 'RefSlotAlreadyProcessing()' + ) + }) }) }) diff --git a/test/0.8.9/oracle/validators-exit-bus-oracle-submit-report-data.test.js b/test/0.8.9/oracle/validators-exit-bus-oracle-submit-report-data.test.js new file mode 100644 index 000000000..e33f2de5a --- /dev/null +++ b/test/0.8.9/oracle/validators-exit-bus-oracle-submit-report-data.test.js @@ -0,0 +1,579 @@ +const { assert } = require('../../helpers/assert') + +const { + CONSENSUS_VERSION, + DATA_FORMAT_LIST, + getReportDataItems, + calcReportDataHash, + encodeExitRequestsDataList, + deployExitBusOracle, + computeTimestampAtSlot, + ZERO_HASH +} = require('./validators-exit-bus-oracle-deploy.test') + +const PUBKEYS = [ + '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + '0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + '0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', + '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' +] + +function assertEqualsObject(state, desired) { + for (const key in desired) { + if (Object.hasOwnProperty.call(desired, key)) { + assert.equals(state[key], desired[key], key) + } + } +} + +contract('ValidatorsExitBusOracle', ([admin, member1, member2, member3, stranger]) => { + context('submitReportData', () => { + const LAST_PROCESSING_REF_SLOT = 1 + + let consensus + let oracle + let oracleReportSanityChecker + let oracleVersion + + async function setup() { + const deployed = await deployExitBusOracle(admin, { + lastProcessingRefSlot: LAST_PROCESSING_REF_SLOT, + resumeAfterDeploy: true + }) + + consensus = deployed.consensus + oracle = deployed.oracle + oracleReportSanityChecker = deployed.oracleReportSanityChecker + + oracleVersion = +(await oracle.getContractVersion()) + + await consensus.addMember(member1, 1, { from: admin }) + await consensus.addMember(member2, 2, { from: admin }) + await consensus.addMember(member3, 2, { from: admin }) + } + + async function triggerConsensusOnHash(hash) { + const { refSlot } = await consensus.getCurrentFrame() + await consensus.submitReport(refSlot, hash, CONSENSUS_VERSION, { from: member1 }) + await consensus.submitReport(refSlot, hash, CONSENSUS_VERSION, { from: member3 }) + assert.equal((await consensus.getConsensusState()).consensusReport, hash) + } + + const getDefaultReportFields = (overrides) => ({ + consensusVersion: CONSENSUS_VERSION, + dataFormat: DATA_FORMAT_LIST, + // required override: refSlot + // required override: requestsCount + // required override: data + ...overrides + }) + + async function prepareReportAndSubmitHash( + exitRequests = [{ moduleId: 5, nodeOpId: 1, valIndex: 10, valPubkey: PUBKEYS[2] }], + options = {} + ) { + const { reportFields: reportFieldsArg = {} } = options + const { refSlot } = await consensus.getCurrentFrame() + + const reportFields = getDefaultReportFields({ + refSlot: +refSlot, + requestsCount: exitRequests.length, + data: encodeExitRequestsDataList(exitRequests), + ...reportFieldsArg + }) + + const reportItems = getReportDataItems(reportFields) + const reportHash = calcReportDataHash(reportItems) + + await triggerConsensusOnHash(reportHash) + + return reportItems + } + + async function getLastRequestedValidatorIndex(moduleId, nodeOpId) { + return +(await oracle.getLastRequestedValidatorIndices(moduleId, [nodeOpId]))[0] + } + + context('_handleConsensusReportData', () => { + beforeEach(async () => { + await setup() + await consensus.advanceTimeToNextFrameStart() + }) + + context('enforces data format', () => { + it('dataFormat = 0 reverts', async () => { + const dataFormatUnsupported = 0 + const report = await prepareReportAndSubmitHash( + [{ moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] }], + { reportFields: { dataFormat: dataFormatUnsupported } } + ) + await assert.reverts( + oracle.submitReportData(report, oracleVersion, { from: member1 }), + `UnsupportedRequestsDataFormat(${dataFormatUnsupported})` + ) + }) + + it('dataFormat = 2 reverts', async () => { + const dataFormatUnsupported = 2 + const report = await prepareReportAndSubmitHash( + [{ moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] }], + { reportFields: { dataFormat: dataFormatUnsupported } } + ) + await assert.reverts( + oracle.submitReportData(report, oracleVersion, { from: member1 }), + `UnsupportedRequestsDataFormat(${dataFormatUnsupported})` + ) + }) + + it('dataFormat = 1 pass', async () => { + const report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] } + ]) + await oracle.submitReportData(report, oracleVersion, { from: member1 }) + }) + }) + + context('enforces data length', () => { + it('reverts if there is more data than expected', async () => { + const { refSlot } = await consensus.getCurrentFrame() + const exitRequests = [{ moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] }] + const reportFields = getDefaultReportFields({ + refSlot: +refSlot, + requestsCount: exitRequests.length, + data: encodeExitRequestsDataList(exitRequests) + }) + + reportFields.data += 'aaaaaaaaaaaaaaaaaa' + const reportItems = getReportDataItems(reportFields) + const reportHash = calcReportDataHash(reportItems) + await triggerConsensusOnHash(reportHash) + + await assert.reverts( + oracle.submitReportData(reportItems, oracleVersion, { from: member1 }), + 'InvalidRequestsDataLength()' + ) + }) + + it('reverts if there is less data than expected', async () => { + const { refSlot } = await consensus.getCurrentFrame() + const exitRequests = [{ moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] }] + const reportFields = getDefaultReportFields({ + refSlot: +refSlot, + requestsCount: exitRequests.length, + data: encodeExitRequestsDataList(exitRequests) + }) + + reportFields.data = reportFields.data.slice(0, reportFields.data.length - 18) + const reportItems = getReportDataItems(reportFields) + const reportHash = calcReportDataHash(reportItems) + await triggerConsensusOnHash(reportHash) + + await assert.reverts( + oracle.submitReportData(reportItems, oracleVersion, { from: member1 }), + 'InvalidRequestsDataLength()' + ) + }) + + it('pass if there is exact amount of data', async () => { + const report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] } + ]) + await oracle.submitReportData(report, oracleVersion, { from: member1 }) + }) + }) + + context('invokes sanity check', () => { + it('reverts if request limit is reached', async () => { + const exitRequestsLimit = 1 + await oracleReportSanityChecker.setMaxExitRequestsPerOracleReport(exitRequestsLimit) + const report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 3, valIndex: 2, valPubkey: PUBKEYS[2] }, + { moduleId: 5, nodeOpId: 3, valIndex: 2, valPubkey: PUBKEYS[3] } + ]) + await assert.reverts( + oracle.submitReportData(report, oracleVersion, { from: member1 }), + `IncorrectNumberOfExitRequestsPerReport(${exitRequestsLimit})` + ) + }) + + it('pass if requests amount equals to limit', async () => { + const exitRequestsLimit = 1 + await oracleReportSanityChecker.setMaxExitRequestsPerOracleReport(exitRequestsLimit) + const report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 3, valIndex: 2, valPubkey: PUBKEYS[2] } + ]) + await oracle.submitReportData(report, oracleVersion, { from: member1 }) + }) + }) + + context('validates data.requestsCount field with given data', () => { + it('reverts if requestsCount does not match with encoded data size', async () => { + const report = await prepareReportAndSubmitHash( + [{ moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] }], + { reportFields: { requestsCount: 2 } } + ) + await assert.reverts( + oracle.submitReportData(report, oracleVersion, { from: member1 }), + 'UnexpectedRequestsDataLength()' + ) + }) + }) + + it('reverts if moduleId equals zero', async () => { + const report = await prepareReportAndSubmitHash([ + { moduleId: 0, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] } + ]) + await assert.reverts(oracle.submitReportData(report, oracleVersion, { from: member1 }), 'InvalidRequestsData()') + }) + + it('emits ValidatorExitRequest events', async () => { + const requests = [ + { moduleId: 4, nodeOpId: 2, valIndex: 2, valPubkey: PUBKEYS[2] }, + { moduleId: 5, nodeOpId: 3, valIndex: 2, valPubkey: PUBKEYS[3] } + ] + const report = await prepareReportAndSubmitHash(requests) + const tx = await oracle.submitReportData(report, oracleVersion, { from: member1 }) + const timestamp = await consensus.getTime() + + assert.emits(tx, 'ValidatorExitRequest', { + stakingModuleId: requests[0].moduleId, + nodeOperatorId: requests[0].nodeOpId, + validatorIndex: requests[0].valIndex, + validatorPubkey: requests[0].valPubkey, + timestamp + }) + + assert.emits(tx, 'ValidatorExitRequest', { + stakingModuleId: requests[1].moduleId, + nodeOperatorId: requests[1].nodeOpId, + validatorIndex: requests[1].valIndex, + validatorPubkey: requests[1].valPubkey, + timestamp + }) + }) + + it('updates processing state', async () => { + const storageBefore = await oracle.getDataProcessingState() + assert.equals(+storageBefore.refSlot, 0) + assert.equals(+storageBefore.requestsCount, 0) + assert.equals(+storageBefore.requestsProcessed, 0) + assert.equals(+storageBefore.dataFormat, 0) + + const { refSlot } = await consensus.getCurrentFrame() + const requests = [ + { moduleId: 4, nodeOpId: 3, valIndex: 2, valPubkey: PUBKEYS[2] }, + { moduleId: 5, nodeOpId: 3, valIndex: 2, valPubkey: PUBKEYS[3] } + ] + const report = await prepareReportAndSubmitHash(requests) + await oracle.submitReportData(report, oracleVersion, { from: member1 }) + + const storageAfter = await oracle.getDataProcessingState() + assert.equals(+storageAfter.refSlot, +refSlot) + assert.equals(+storageAfter.requestsCount, requests.length) + assert.equals(+storageAfter.requestsProcessed, requests.length) + assert.equals(+storageAfter.dataFormat, DATA_FORMAT_LIST) + }) + + it('updates total requests processed count', async () => { + let currentCount = 0 + const countStep0 = await oracle.getTotalRequestsProcessed() + assert.equals(+countStep0, currentCount) + + // Step 1 — process 1 item + const requestsStep1 = [{ moduleId: 3, nodeOpId: 1, valIndex: 2, valPubkey: PUBKEYS[1] }] + const reportStep1 = await prepareReportAndSubmitHash(requestsStep1) + await oracle.submitReportData(reportStep1, oracleVersion, { from: member1 }) + const countStep1 = await oracle.getTotalRequestsProcessed() + currentCount += requestsStep1.length + assert.equals(+countStep1, currentCount) + + // Step 2 — process 2 items + await consensus.advanceTimeToNextFrameStart() + const requestsStep2 = [ + { moduleId: 4, nodeOpId: 2, valIndex: 2, valPubkey: PUBKEYS[2] }, + { moduleId: 5, nodeOpId: 3, valIndex: 2, valPubkey: PUBKEYS[3] } + ] + const reportStep2 = await prepareReportAndSubmitHash(requestsStep2) + await oracle.submitReportData(reportStep2, oracleVersion, { from: member1 }) + const countStep2 = await oracle.getTotalRequestsProcessed() + currentCount += requestsStep2.length + assert.equals(+countStep2, currentCount) + + // Step 3 — process no items + await consensus.advanceTimeToNextFrameStart() + const requestsStep3 = [] + const reportStep3 = await prepareReportAndSubmitHash(requestsStep3) + await oracle.submitReportData(reportStep3, oracleVersion, { from: member1 }) + const countStep3 = await oracle.getTotalRequestsProcessed() + currentCount += requestsStep3.length + assert.equals(+countStep3, currentCount) + }) + }) + + context(`requires validator indices for the same node operator to increase`, () => { + before(setup) + + it(`requesting NO 5-3 to exit validator 0`, async () => { + await consensus.advanceTimeToNextFrameStart() + const report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] } + ]) + await oracle.submitReportData(report, oracleVersion, { from: member1 }) + assert.equal(await getLastRequestedValidatorIndex(5, 3), 0) + }) + + it(`cannot request NO 5-3 to exit validator 0 again`, async () => { + await consensus.advanceTimeToNextFrameStart() + const report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] } + ]) + await assert.reverts( + oracle.submitReportData(report, oracleVersion, { from: member1 }), + 'NodeOpValidatorIndexMustIncrease(5, 3, 0, 0)' + ) + }) + + it(`requesting NO 5-3 to exit validator 1`, async () => { + await consensus.advanceTimeToNextFrameStart() + const report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 3, valIndex: 1, valPubkey: PUBKEYS[1] } + ]) + await oracle.submitReportData(report, oracleVersion, { from: member1 }) + assert.equal(await getLastRequestedValidatorIndex(5, 3), 1) + }) + + it(`cannot request NO 5-3 to exit validator 1 again`, async () => { + await consensus.advanceTimeToNextFrameStart() + const report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 3, valIndex: 1, valPubkey: PUBKEYS[1] } + ]) + await assert.reverts( + oracle.submitReportData(report, oracleVersion, { from: member1 }), + 'NodeOpValidatorIndexMustIncrease(5, 3, 1, 1)' + ) + }) + + it(`cannot request NO 5-3 to exit validator 0 again`, async () => { + await consensus.advanceTimeToNextFrameStart() + const report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] } + ]) + await assert.reverts( + oracle.submitReportData(report, oracleVersion, { from: member1 }), + 'NodeOpValidatorIndexMustIncrease(5, 3, 1, 0)' + ) + }) + + it(`cannot request NO 5-3 to exit validator 1 again (multiple requests)`, async () => { + await consensus.advanceTimeToNextFrameStart() + const report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 1, valIndex: 10, valPubkey: PUBKEYS[0] }, + { moduleId: 5, nodeOpId: 3, valIndex: 1, valPubkey: PUBKEYS[0] } + ]) + await assert.reverts( + oracle.submitReportData(report, oracleVersion, { from: member1 }), + 'NodeOpValidatorIndexMustIncrease(5, 3, 1, 1)' + ) + }) + + it(`cannot request NO 5-3 to exit validator 1 again (multiple requests, case 2)`, async () => { + await consensus.advanceTimeToNextFrameStart() + const report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 1, valIndex: 10, valPubkey: PUBKEYS[2] }, + { moduleId: 5, nodeOpId: 3, valIndex: 1, valPubkey: PUBKEYS[3] }, + { moduleId: 5, nodeOpId: 3, valIndex: 2, valPubkey: PUBKEYS[4] } + ]) + await assert.reverts( + oracle.submitReportData(report, oracleVersion, { from: member1 }), + 'NodeOpValidatorIndexMustIncrease(5, 3, 1, 1)' + ) + }) + + it(`cannot request NO 5-3 to exit validator 2 two times per request`, async () => { + await consensus.advanceTimeToNextFrameStart() + const report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 3, valIndex: 2, valPubkey: PUBKEYS[2] }, + { moduleId: 5, nodeOpId: 3, valIndex: 2, valPubkey: PUBKEYS[3] } + ]) + await assert.reverts( + oracle.submitReportData(report, oracleVersion, { from: member1 }), + 'InvalidRequestsDataSortOrder()' + ) + }) + }) + + context(`only consensus member or SUBMIT_DATA_ROLE can submit report on unpaused contract`, () => { + beforeEach(setup) + + it('reverts on stranger', async () => { + const report = await prepareReportAndSubmitHash() + await assert.reverts(oracle.submitReportData(report, oracleVersion, { from: stranger }), 'SenderNotAllowed()') + }) + + it('SUBMIT_DATA_ROLE is allowed', async () => { + oracle.grantRole(await oracle.SUBMIT_DATA_ROLE(), stranger, { from: admin }) + await consensus.advanceTimeToNextFrameStart() + const report = await prepareReportAndSubmitHash() + await oracle.submitReportData(report, oracleVersion, { from: stranger }) + }) + + it('consensus member is allowed', async () => { + assert(await consensus.getIsMember(member1)) + await consensus.advanceTimeToNextFrameStart() + const report = await prepareReportAndSubmitHash() + await oracle.submitReportData(report, oracleVersion, { from: member1 }) + }) + + it('reverts on paused contract', async () => { + await consensus.advanceTimeToNextFrameStart() + const PAUSE_INFINITELY = await oracle.PAUSE_INFINITELY() + await oracle.pause(PAUSE_INFINITELY, { from: admin }) + const report = await prepareReportAndSubmitHash() + assert.reverts(oracle.submitReportData(report, oracleVersion, { from: member1 }), 'ResumedExpected()') + }) + }) + + context('invokes internal baseOracle checks', () => { + beforeEach(setup) + + it(`reverts on contract version mismatch`, async () => { + const report = await prepareReportAndSubmitHash() + await assert.reverts( + oracle.submitReportData(report, oracleVersion + 1, { from: member1 }), + `UnexpectedContractVersion(${oracleVersion}, ${oracleVersion + 1})` + ) + }) + + it('reverts on hash mismatch', async () => { + const report = await prepareReportAndSubmitHash() + const actualReportHash = calcReportDataHash(report) + // mess with data field to change hash + report[report.length - 1] = report[report.length - 1] + 'ff' + const changedReportHash = calcReportDataHash(report) + await assert.reverts( + oracle.submitReportData(report, oracleVersion, { from: member1 }), + `UnexpectedDataHash("${actualReportHash}", "${changedReportHash}")` + ) + }) + + it('reverts on processing deadline miss', async () => { + const report = await prepareReportAndSubmitHash() + const deadline = (await oracle.getConsensusReport()).processingDeadlineTime.toString(10) + await consensus.advanceTimeToNextFrameStart() + await assert.reverts( + oracle.submitReportData(report, oracleVersion, { from: member1 }), + `ProcessingDeadlineMissed(${deadline})` + ) + }) + }) + + context('getTotalRequestsProcessed reflects report history', () => { + before(setup) + + let requestCount + + it('should be zero at init', async () => { + requestCount = 0 + assert.equals(await oracle.getTotalRequestsProcessed(), requestCount) + }) + + it('should increase after report', async () => { + const report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] } + ]) + await oracle.submitReportData(report, oracleVersion, { from: member1 }) + requestCount += 1 + assert.equals(await oracle.getTotalRequestsProcessed(), requestCount) + }) + + it('should double increase for two exits', async () => { + await consensus.advanceTimeToNextFrameStart() + const report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 1, valIndex: 10, valPubkey: PUBKEYS[0] }, + { moduleId: 5, nodeOpId: 3, valIndex: 1, valPubkey: PUBKEYS[0] } + ]) + await oracle.submitReportData(report, oracleVersion, { from: member1 }) + requestCount += 2 + assert.equals(await oracle.getTotalRequestsProcessed(), requestCount) + }) + + it('should not change on empty report', async () => { + await consensus.advanceTimeToNextFrameStart() + const report = await prepareReportAndSubmitHash([]) + await oracle.submitReportData(report, oracleVersion, { from: member1 }) + assert.equals(await oracle.getTotalRequestsProcessed(), requestCount) + }) + }) + + context('getProcessingState reflects state change', () => { + before(setup) + + let report + let hash + + it('has correct defaults on init', async () => { + const state = await oracle.getProcessingState() + assertEqualsObject(state, { + currentFrameRefSlot: (await consensus.getCurrentFrame()).refSlot, + processingDeadlineTime: 0, + dataHash: ZERO_HASH, + dataSubmitted: false, + dataFormat: 0, + requestsCount: 0, + requestsSubmitted: 0 + }) + }) + + it('consensus report submitted', async () => { + report = await prepareReportAndSubmitHash([ + { moduleId: 5, nodeOpId: 1, valIndex: 10, valPubkey: PUBKEYS[2] }, + { moduleId: 5, nodeOpId: 3, valIndex: 1, valPubkey: PUBKEYS[3] } + ]) + hash = calcReportDataHash(report) + const state = await oracle.getProcessingState() + assertEqualsObject(state, { + currentFrameRefSlot: (await consensus.getCurrentFrame()).refSlot, + processingDeadlineTime: computeTimestampAtSlot( + (await consensus.getCurrentFrame()).reportProcessingDeadlineSlot + ), + dataHash: hash, + dataSubmitted: false, + dataFormat: 0, + requestsCount: 0, + requestsSubmitted: 0 + }) + }) + + it('report is processed', async () => { + await oracle.submitReportData(report, oracleVersion, { from: member1 }) + const state = await oracle.getProcessingState() + assertEqualsObject(state, { + currentFrameRefSlot: (await consensus.getCurrentFrame()).refSlot, + processingDeadlineTime: computeTimestampAtSlot( + (await consensus.getCurrentFrame()).reportProcessingDeadlineSlot + ), + dataHash: hash, + dataSubmitted: true, + dataFormat: DATA_FORMAT_LIST, + requestsCount: 2, + requestsSubmitted: 2 + }) + }) + + it('at next frame state resets', async () => { + await consensus.advanceTimeToNextFrameStart() + const state = await oracle.getProcessingState() + assertEqualsObject(state, { + currentFrameRefSlot: (await consensus.getCurrentFrame()).refSlot, + processingDeadlineTime: 0, + dataHash: ZERO_HASH, + dataSubmitted: false, + dataFormat: 0, + requestsCount: 0, + requestsSubmitted: 0 + }) + }) + }) + }) +}) diff --git a/test/0.8.9/pausable-until.test.js b/test/0.8.9/pausable-until.test.js new file mode 100644 index 000000000..4f56f1f1e --- /dev/null +++ b/test/0.8.9/pausable-until.test.js @@ -0,0 +1,105 @@ +const hre = require('hardhat') + +const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') +const { EvmSnapshot, advanceChainTime, getCurrentBlockTimestamp } = require('../helpers/blockchain') +const { ETH, StETH } = require('../helpers/utils') +const { assert } = require('../helpers/assert') + +const PausableUntil = artifacts.require('PausableUntilPrivateExposed') + + + + +contract('PausableUntil', ([deployer, _, anotherAccount]) => { + let pausable + let PAUSE_INFINITELY + + before('deploy lido with dao', async () => { + pausable = await PausableUntil.new( { from: deployer }) + PAUSE_INFINITELY = await pausable.PAUSE_INFINITELY() + + snapshot = new EvmSnapshot(hre.ethers.provider) + await snapshot.make() + }) + + afterEach(async () => { + await snapshot.rollback() + }) + + /// Check views, modifiers and capability to pause/resume + async function assertPausedState(resumeSinceTimestamp = undefined) { + assert.isTrue(await pausable.isPaused()) + assert.isTrue(await pausable.getResumeSinceTimestamp() > await getCurrentBlockTimestamp()) + assert.equals(await pausable.stubUnderModifierWhenPaused(), bn(42)) + if (resumeSinceTimestamp !== undefined) { + assert.equals(await pausable.getResumeSinceTimestamp(), resumeSinceTimestamp) + } + + await assert.revertsWithCustomError(pausable.pause(12345), `ResumedExpected()`) + await assert.revertsWithCustomError(pausable.stubUnderModifierWhenResumed(), `ResumedExpected()`) + } + + /// Check views, modifiers and capability to pause/resume + async function assertResumedState() { + assert.isFalse(await pausable.isPaused()) + assert.isTrue(await pausable.getResumeSinceTimestamp() <= await getCurrentBlockTimestamp()) + assert.equals(await pausable.stubUnderModifierWhenResumed(), bn(42)) + await assert.revertsWithCustomError(pausable.stubUnderModifierWhenPaused(), `PausedExpected()`) + await assert.revertsWithCustomError(pausable.resume(), `PausedExpected()`) + } + + describe('Logic', () => { + it(`state after deployment`, async () => { + await assertResumedState() + assert.equals(await pausable.getResumeSinceTimestamp(), bn(0)) + }) + + it(`revert if pause for zero duration`, async () => { + await assert.revertsWithCustomError(pausable.pause(0), `ZeroPauseDuration()`) + }) + + it(`pause infinitely`, async () => { + await assertResumedState() + const MONTH_IN_SECS = 30 * 24 * 60 * 60 + + await pausable.pause(PAUSE_INFINITELY) + await assertPausedState(PAUSE_INFINITELY) + + await advanceChainTime(MONTH_IN_SECS) + await assertPausedState(PAUSE_INFINITELY) + + await advanceChainTime(12 * MONTH_IN_SECS) + await assertPausedState(PAUSE_INFINITELY) + }) + + it(`pause on specific pauseDuration`, async () => { + assert.isFalse(await pausable.isPaused()) + const pauseDuration = 3 * 60 + + await pausable.pause(pauseDuration) + const resumeSinceTimestamp = await getCurrentBlockTimestamp() + pauseDuration + await assertPausedState(resumeSinceTimestamp) + + await advanceChainTime(Math.floor(pauseDuration / 2)) + assert.isTrue(await pausable.isPaused()) + await advanceChainTime(resumeSinceTimestamp - 1 - (await getCurrentBlockTimestamp())) + assert.equals(await getCurrentBlockTimestamp(), resumeSinceTimestamp - 1) + // Check only view here because with revert transactions chain can pass more than 1 seconds + assert.isTrue(await pausable.isPaused()) + + await advanceChainTime(1) + assert.equals(await getCurrentBlockTimestamp(), resumeSinceTimestamp) + await assertResumedState() + }) + + it(`resume`, async () => { + await pausable.pause(PAUSE_INFINITELY) + await pausable.resume() + await assertResumedState() + + await pausable.pause(123) + await pausable.resume() + await assertResumedState() + }) + }) +}) diff --git a/test/0.8.9/staking-router-deposits.test.js b/test/0.8.9/staking-router-deposits.test.js index 92e46c004..d02528e31 100644 --- a/test/0.8.9/staking-router-deposits.test.js +++ b/test/0.8.9/staking-router-deposits.test.js @@ -52,7 +52,7 @@ contract('StakingRouter', ([depositor, stranger]) => { const maxDepositsCount = 150 await web3.eth.sendTransaction({ value: ETH(maxDepositsCount * 32), to: lido.address, from: stranger }) - assert.equals(await lido.getBufferedEther(), ETH(maxDepositsCount * 32)) + assert.equals(await lido.getBufferedEther(), ETH(maxDepositsCount * 32 + 1)) const [curated] = await stakingRouter.getStakingModules() @@ -66,18 +66,19 @@ contract('StakingRouter', ([depositor, stranger]) => { }) it('Lido.deposit() :: check deposit with keys', async () => { - // balance are 0 - assert.equals(await web3.eth.getBalance(lido.address), 0) + // balance are initial + assert.equals(await web3.eth.getBalance(lido.address), ETH(1)) assert.equals(await web3.eth.getBalance(stakingRouter.address), 0) - const sendEthForKeys = ETH(101 * 32) + const sendEthForKeys = ETH(101 * 32 - 1) + const totalPooledEther = ETH(101 * 32) const maxDepositsCount = 100 await web3.eth.sendTransaction({ value: sendEthForKeys, to: lido.address, from: stranger }) - assert.equals(await lido.getBufferedEther(), sendEthForKeys) + assert.equals(await lido.getBufferedEther(), totalPooledEther) // updated balance are lido 100 && sr 0 - assert.equals(await web3.eth.getBalance(lido.address), sendEthForKeys) + assert.equals(await web3.eth.getBalance(lido.address), totalPooledEther) assert.equals(await web3.eth.getBalance(stakingRouter.address), 0) const [curated] = await stakingRouter.getStakingModules() @@ -117,7 +118,7 @@ contract('StakingRouter', ([depositor, stranger]) => { await assert.reverts( lido.methods[`deposit(uint256,uint256,bytes)`](maxDepositsCount, maxModuleId, '0x', { from: depositor }), - 'STAKING_MODULE_ID_TOO_LARGE' + 'StakingModuleIdTooLarge()' ) }) }) diff --git a/test/0.8.9/staking-router-keys-reporting.test.js b/test/0.8.9/staking-router-keys-reporting.test.js new file mode 100644 index 000000000..5da7ef45f --- /dev/null +++ b/test/0.8.9/staking-router-keys-reporting.test.js @@ -0,0 +1,714 @@ +const hre = require('hardhat') +const { EvmSnapshot } = require('../helpers/blockchain') +const { assert } = require('../helpers/assert') +const { hex, hexConcat, toNum } = require('../helpers/utils') + +const StakingRouter = artifacts.require('StakingRouterMock.sol') +const StakingModuleMock = artifacts.require('StakingModuleMock.sol') +const DepositContractMock = artifacts.require('DepositContractMock.sol') + + +contract('StakingRouter', ([deployer, lido, admin]) => { + const evmSnapshot = new EvmSnapshot(hre.ethers.provider) + + let depositContract, router + let module1, module2 + + before(async () => { + depositContract = await DepositContractMock.new({ from: deployer }) + router = await StakingRouter.new(depositContract.address, { from: deployer }) + + ;[module1, module2] = await Promise.all([ + StakingModuleMock.new({ from: deployer }), + StakingModuleMock.new({ from: deployer }) + ]) + + const wc = '0x'.padEnd(66, '1234') + await router.initialize(admin, lido, wc, { from: deployer }) + + await router.grantRole(await router.MANAGE_WITHDRAWAL_CREDENTIALS_ROLE(), admin, { from: admin }) + await router.grantRole(await router.STAKING_MODULE_PAUSE_ROLE(), admin, { from: admin }) + await router.grantRole(await router.STAKING_MODULE_MANAGE_ROLE(), admin, { from: admin }) + await router.grantRole(await router.REPORT_EXITED_VALIDATORS_ROLE(), admin, { from: admin }) + }) + + const getCallInfo = async (sModule) => { + const callCountToNum = (callInfo) => { + return { ...callInfo, callCount: +callInfo.callCount } + } + return { + updateStuckValidatorsCount: callCountToNum(await sModule.lastCall_updateStuckValidatorsCount()), + updateExitedValidatorsCount: callCountToNum(await sModule.lastCall_updateExitedValidatorsCount()), + onExitedAndStuckValidatorsCountsUpdated: { + callCount: +await sModule.callCount_onExitedAndStuckValidatorsCountsUpdated() + } + } + } + + before(async () => { + for await (moduleI of [module1, module2]) { + const callInfo = await getCallInfo(moduleI) + assert.equal(callInfo.updateStuckValidatorsCount.callCount, 0) + assert.equal(callInfo.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + } + }) + + const snapshot = () => evmSnapshot.make() + const revert = () => evmSnapshot.revert() + + describe('exited/stuck keys reporting', () => { + + describe('one staking module', async () => { + before(snapshot) + after(revert) + + let module1Id + + it('adding the only module', async () => { + await router.addStakingModule( + 'module 1', + module1.address, + 10_000, // target share 100 % + 1_000, // module fee 10 % + 5_000, // treasury fee 5 % + { from: admin } + ) + module1Id = +(await router.getStakingModuleIds())[0] + }) + + it('initially, router assumes no staking modules have exited validators', async () => { + const info = await router.getStakingModule(module1Id) + assert.equal(+info.exitedValidatorsCount, 0) + + const totalExited = await router.getExitedValidatorsCountAcrossAllModules() + assert.equal(+totalExited, 0) + }) + + it('reporting total exited validators of a non-existent module reverts', async () => { + await assert.reverts( + router.updateExitedValidatorsCountByStakingModule([module1Id + 1], [1], { from: admin }), + 'StakingModuleUnregistered()' + ) + await assert.reverts( + router.updateExitedValidatorsCountByStakingModule([module1Id, module1Id + 1], [1, 1], { from: admin }), + 'StakingModuleUnregistered()' + ) + }) + + it('reporting module 1 to have total 3 exited validators', async () => { + await router.updateExitedValidatorsCountByStakingModule([module1Id], [3], { from: admin }) + }) + + it('staking module info gets updated', async () => { + const info = await router.getStakingModule(module1Id) + assert.equal(+info.exitedValidatorsCount, 3) + }) + + it('exited validators count accross all modules gets updated', async () => { + const totalExited = await router.getExitedValidatorsCountAcrossAllModules() + assert.equal(+totalExited, 3) + }) + + it('no functions were called on the module', async () => { + const callInfo = await getCallInfo(module1) + assert.equal(callInfo.updateStuckValidatorsCount.callCount, 0) + assert.equal(callInfo.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + }) + + it(`calling onValidatorsCountsByNodeOperatorReportingFinished doesn't call ` + + `anything on the module`, async () => + { + await router.onValidatorsCountsByNodeOperatorReportingFinished({ from: admin }) + + const callInfo = await getCallInfo(module1) + assert.equal(callInfo.updateStuckValidatorsCount.callCount, 0) + assert.equal(callInfo.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + }) + + it('reporting stuck validators by node op of a non-existent module reverts', async () => { + const nonExistentModuleId = module1Id + 1 + const nodeOpIdsData = hexConcat(hex(1, 8)) + const validatorsCountsData = hexConcat(hex(1, 16)) + await assert.reverts( + router.reportStakingModuleStuckValidatorsCountByNodeOperator( + nonExistentModuleId, nodeOpIdsData, validatorsCountsData, + { from: admin } + ), + 'StakingModuleUnregistered()' + ) + }) + + it('passing empty data while reporting stuck validators by node operator reverts', async () => { + await assert.reverts( + router.reportStakingModuleStuckValidatorsCountByNodeOperator( + module1Id, '0x', '0x', + { from: admin } + ), + 'InvalidReportData(1)' + ) + }) + + const mismatchedLengthData = [ + { + nodeOpIds: '0x', + validatorsCounts: hexConcat(hex(1, 16)), + }, + { + nodeOpIds: hexConcat(hex(1, 8)), + validatorsCounts: '0x', + }, + { + nodeOpIds: hexConcat(hex(1, 8), hex(2, 8)), + validatorsCounts: hexConcat(hex(1, 16)), + }, + { + nodeOpIds: hexConcat(hex(1, 8)), + validatorsCounts: hexConcat(hex(1, 16), hex(1, 16)), + }, + ] + + it('passing data with mismatched length while reporting stuck validators by node operator ' + + 'reverts', async () => + { + await Promise.all(mismatchedLengthData.map(data => assert.reverts( + router.reportStakingModuleStuckValidatorsCountByNodeOperator( + module1Id, data.nodeOpIds, data.validatorsCounts, + { from: admin } + ), + 'InvalidReportData(2)' + ))) + }) + + const invalidLengthData = [ + { + nodeOpIds: '0x00', + validatorsCounts: '0x', + }, + { + nodeOpIds: '0x', + validatorsCounts: '0x00', + }, + { + nodeOpIds: '0x00', + validatorsCounts: '0x00', + }, + { + nodeOpIds: hexConcat(hex(1, 8), '0x00'), + validatorsCounts: hexConcat(hex(1, 16)), + }, + { + nodeOpIds: hexConcat(hex(1, 8), '0x00'), + validatorsCounts: hexConcat(hex(1, 16), '0x00'), + }, + { + nodeOpIds: hexConcat(hex(1, 8)), + validatorsCounts: hexConcat(hex(1, 16), '0x00'), + }, + { + nodeOpIds: hexConcat(hex(1, 8), hex(2, 8), '0x00'), + validatorsCounts: hexConcat(hex(1, 16)), + }, + { + nodeOpIds: hexConcat(hex(1, 8), hex(2, 8)), + validatorsCounts: hexConcat(hex(1, 16), '0x00'), + }, + { + nodeOpIds: hexConcat(hex(1, 8), '0x00'), + validatorsCounts: hexConcat(hex(1, 16), hex(1, 16)), + }, + { + nodeOpIds: hexConcat(hex(1, 8)), + validatorsCounts: hexConcat(hex(1, 16), hex(1, 16), '0x00'), + }, + ] + + it('passing data with invalid length while reporting stuck validators by node operator ' + + 'reverts', async () => + { + await Promise.all(invalidLengthData.map(data => assert.reverts( + router.reportStakingModuleStuckValidatorsCountByNodeOperator( + module1Id, data.nodeOpIds, data.validatorsCounts, + { from: admin } + ), + 'InvalidReportData(3)' + ))) + }) + + it('reporting stuck validators by node operator passes the info to the module', async () => { + const nodeOpIds = [3, 5] + const validatorsCounts = [1, 1] + + const nodeOpIdsData = hexConcat(...nodeOpIds.map(i => hex(i, 8))) + const validatorsCountsData = hexConcat(...validatorsCounts.map(c => hex(c, 16))) + + await router.reportStakingModuleStuckValidatorsCountByNodeOperator( + module1Id, nodeOpIdsData, validatorsCountsData, + { from: admin } + ) + + const callInfo = await getCallInfo(module1) + assert.equal(callInfo.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo.updateStuckValidatorsCount.nodeOperatorIds, nodeOpIdsData) + assert.equal(callInfo.updateStuckValidatorsCount.validatorsCounts, validatorsCountsData) + + assert.equal(callInfo.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + }) + + it(`calling onValidatorsCountsByNodeOperatorReportingFinished still doesn't call ` + + `anything on the module`, async () => + { + await router.onValidatorsCountsByNodeOperatorReportingFinished({ from: admin }) + + const callInfo = await getCallInfo(module1) + assert.equal(callInfo.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + + assert.equal(callInfo.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo.updateExitedValidatorsCount.callCount, 0) + }) + + it('reporting exited validators by node op of a non-existent module reverts', async () => { + const nonExistentModuleId = module1Id + 1 + const nodeOpIdsData = hexConcat(hex(1, 8)) + const validatorsCountsData = hexConcat(hex(1, 16)) + await assert.reverts( + router.reportStakingModuleExitedValidatorsCountByNodeOperator( + nonExistentModuleId, nodeOpIdsData, validatorsCountsData, + { from: admin } + ), + 'StakingModuleUnregistered()' + ) + }) + + it('passing empty data while reporting exited validators by node operator reverts', async () => { + await assert.reverts( + router.reportStakingModuleExitedValidatorsCountByNodeOperator( + module1Id, '0x', '0x', + { from: admin } + ), + 'InvalidReportData(1)' + ) + }) + + it('passing data with mismatched length while reporting exited validators by node operator ' + + 'reverts', async () => + { + await Promise.all(mismatchedLengthData.map(data => assert.reverts( + router.reportStakingModuleExitedValidatorsCountByNodeOperator( + module1Id, data.nodeOpIds, data.validatorsCounts, + { from: admin } + ), + 'InvalidReportData(2)' + ))) + }) + + it('passing data with invalid length while reporting exited validators by node operator ' + + 'reverts', async () => + { + await Promise.all(invalidLengthData.map(data => assert.reverts( + router.reportStakingModuleExitedValidatorsCountByNodeOperator( + module1Id, data.nodeOpIds, data.validatorsCounts, + { from: admin } + ), + 'InvalidReportData(3)' + ))) + }) + + it('reporting exited validators by node operator (total 2) passes the info to the module', + async () => + { + const nodeOpIds = [1, 2] + const validatorsCounts = [1, 1] + + const nodeOpIdsData = hexConcat(...nodeOpIds.map(i => hex(i, 8))) + const validatorsCountsData = hexConcat(...validatorsCounts.map(c => hex(c, 16))) + + await router.reportStakingModuleExitedValidatorsCountByNodeOperator( + module1Id, nodeOpIdsData, validatorsCountsData, + { from: admin } + ) + + const callInfo = await getCallInfo(module1) + assert.equal(callInfo.updateStuckValidatorsCount.callCount, 1) + + assert.equal(callInfo.updateExitedValidatorsCount.callCount, 1) + assert.equal(callInfo.updateExitedValidatorsCount.nodeOperatorIds, nodeOpIdsData) + assert.equal(callInfo.updateExitedValidatorsCount.validatorsCounts, validatorsCountsData) + + assert.equal(callInfo.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + }) + + it('the staking module updates its internal total exited counter to 2', async () => { + await module1.setTotalExitedValidatorsCount(2) + }) + + it(`router's view on exited validators count accross all modules stays the same`, async () => { + const totalExited = await router.getExitedValidatorsCountAcrossAllModules() + assert.equal(+totalExited, 3) + }) + + it(`calling onValidatorsCountsByNodeOperatorReportingFinished still doesn't call ` + + `anything on the module`, async () => + { + await router.onValidatorsCountsByNodeOperatorReportingFinished({ from: admin }) + + const callInfo = await getCallInfo(module1) + assert.equal(callInfo.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + + assert.equal(callInfo.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo.updateExitedValidatorsCount.callCount, 1) + }) + + it('reporting one more exited validator by node operator passes the info to the module', + async () => + { + const nodeOpIds = [3] + const validatorsCounts = [1] + + const nodeOpIdsData = hexConcat(...nodeOpIds.map(i => hex(i, 8))) + const validatorsCountsData = hexConcat(...validatorsCounts.map(c => hex(c, 16))) + + await router.reportStakingModuleExitedValidatorsCountByNodeOperator( + module1Id, nodeOpIdsData, validatorsCountsData, + { from: admin } + ) + + const callInfo = await getCallInfo(module1) + assert.equal(callInfo.updateStuckValidatorsCount.callCount, 1) + + assert.equal(callInfo.updateExitedValidatorsCount.callCount, 2) + assert.equal(callInfo.updateExitedValidatorsCount.nodeOperatorIds, nodeOpIdsData) + assert.equal(callInfo.updateExitedValidatorsCount.validatorsCounts, validatorsCountsData) + + assert.equal(callInfo.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + }) + + it('the staking module updates its internal total exited counter to 3', async () => { + await module1.setTotalExitedValidatorsCount(3) + }) + + it(`now that exited validators totals in the router and in the module match, calling` + + `onValidatorsCountsByNodeOperatorReportingFinished calls ` + + `onExitedAndStuckValidatorsCountsUpdated on the module`, async () => + { + await router.onValidatorsCountsByNodeOperatorReportingFinished({ from: admin }) + + const callInfo = await getCallInfo(module1) + assert.equal(callInfo.onExitedAndStuckValidatorsCountsUpdated.callCount, 1) + + assert.equal(callInfo.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo.updateExitedValidatorsCount.callCount, 2) + }) + + it(`calling onValidatorsCountsByNodeOperatorReportingFinished one more time calls ` + + `onExitedAndStuckValidatorsCountsUpdated on the module again`, async () => + { + await router.onValidatorsCountsByNodeOperatorReportingFinished({ from: admin }) + + const callInfo = await getCallInfo(module1) + assert.equal(callInfo.onExitedAndStuckValidatorsCountsUpdated.callCount, 2) + + assert.equal(callInfo.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo.updateExitedValidatorsCount.callCount, 2) + }) + }) + + describe('two staking modules', async () => { + before(snapshot) + after(revert) + + let moduleIds + + it('adding the two modules', async () => { + await router.addStakingModule( + 'module 1', + module1.address, + 10_000, // 100 % _targetShare + 1_000, // 10 % _moduleFee + 5_000, // 50 % _treasuryFee + { from: admin } + ) + await router.addStakingModule( + 'module 2', + module2.address, + 200, // 2 % _targetShare + 5_000, // 50 % _moduleFee + 0, // 0 % _treasuryFee + { from: admin } + ) + moduleIds = toNum(await router.getStakingModuleIds()) + }) + + it('initially, router assumes no staking modules have exited validators', async () => { + const info1 = await router.getStakingModule(moduleIds[0]) + assert.equal(+info1.exitedValidatorsCount, 0) + + const info2 = await router.getStakingModule(moduleIds[1]) + assert.equal(+info2.exitedValidatorsCount, 0) + + const totalExited = await router.getExitedValidatorsCountAcrossAllModules() + assert.equal(+totalExited, 0) + }) + + it('reporting 3 exited keys total for module 1 and 2 exited keys total for module 2', async () => { + await router.updateExitedValidatorsCountByStakingModule(moduleIds, [3, 2], { from: admin }) + }) + + it('staking modules info gets updated', async () => { + const info1 = await router.getStakingModule(moduleIds[0]) + assert.equal(+info1.exitedValidatorsCount, 3) + + const info2 = await router.getStakingModule(moduleIds[1]) + assert.equal(+info2.exitedValidatorsCount, 2) + }) + + it('exited validators count accross all modules gets updated', async () => { + const totalExited = await router.getExitedValidatorsCountAcrossAllModules() + assert.equal(+totalExited, 5) + }) + + it('no functions were called on any module', async () => { + const callInfo1 = await getCallInfo(module1) + assert.equal(callInfo1.updateStuckValidatorsCount.callCount, 0) + assert.equal(callInfo1.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo1.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + + const callInfo2 = await getCallInfo(module2) + assert.equal(callInfo2.updateStuckValidatorsCount.callCount, 0) + assert.equal(callInfo2.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo2.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + }) + + it(`calling onValidatorsCountsByNodeOperatorReportingFinished doesn't call ` + + `anything on any module`, async () => + { + await router.onValidatorsCountsByNodeOperatorReportingFinished({ from: admin }) + + const callInfo1 = await getCallInfo(module1) + assert.equal(callInfo1.updateStuckValidatorsCount.callCount, 0) + assert.equal(callInfo1.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo1.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + + const callInfo2 = await getCallInfo(module2) + assert.equal(callInfo2.updateStuckValidatorsCount.callCount, 0) + assert.equal(callInfo2.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo2.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + }) + + it('reporting stuck validators by node operator passes the info to the module 1', async () => { + const nodeOpIds = [1] + const validatorsCounts = [3] + + const nodeOpIdsData = hexConcat(...nodeOpIds.map(i => hex(i, 8))) + const validatorsCountsData = hexConcat(...validatorsCounts.map(c => hex(c, 16))) + + await router.reportStakingModuleStuckValidatorsCountByNodeOperator( + moduleIds[0], nodeOpIdsData, validatorsCountsData, + { from: admin } + ) + + const callInfo1 = await getCallInfo(module1) + assert.equal(callInfo1.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo1.updateStuckValidatorsCount.nodeOperatorIds, nodeOpIdsData) + assert.equal(callInfo1.updateStuckValidatorsCount.validatorsCounts, validatorsCountsData) + + assert.equal(callInfo1.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo1.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + + const callInfo2 = await getCallInfo(module2) + assert.equal(callInfo2.updateStuckValidatorsCount.callCount, 0) + assert.equal(callInfo2.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo2.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + }) + + it('reporting stuck validators by node operator passes the info to the module 2', async () => { + const nodeOpIds = [33] + const validatorsCounts = [7] + + const nodeOpIdsData = hexConcat(...nodeOpIds.map(i => hex(i, 8))) + const validatorsCountsData = hexConcat(...validatorsCounts.map(c => hex(c, 16))) + + await router.reportStakingModuleStuckValidatorsCountByNodeOperator( + moduleIds[1], nodeOpIdsData, validatorsCountsData, + { from: admin } + ) + + const callInfo2 = await getCallInfo(module2) + assert.equal(callInfo2.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo2.updateStuckValidatorsCount.nodeOperatorIds, nodeOpIdsData) + assert.equal(callInfo2.updateStuckValidatorsCount.validatorsCounts, validatorsCountsData) + + assert.equal(callInfo2.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo2.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + + const callInfo1 = await getCallInfo(module1) + assert.equal(callInfo1.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo1.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo1.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + }) + + it(`calling onValidatorsCountsByNodeOperatorReportingFinished still doesn't call ` + + `anything on any module`, async () => + { + await router.onValidatorsCountsByNodeOperatorReportingFinished({ from: admin }) + + const callInfo1 = await getCallInfo(module1) + assert.equal(callInfo1.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo1.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo1.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + + const callInfo2 = await getCallInfo(module2) + assert.equal(callInfo2.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo2.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo2.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + }) + + it('reporting exited validators by node operator passes the info to the module 1', async () => { + const nodeOpIds = [3, 4] + const validatorsCounts = [1, 1] + + const nodeOpIdsData = hexConcat(...nodeOpIds.map(i => hex(i, 8))) + const validatorsCountsData = hexConcat(...validatorsCounts.map(c => hex(c, 16))) + + await router.reportStakingModuleExitedValidatorsCountByNodeOperator( + moduleIds[0], nodeOpIdsData, validatorsCountsData, + { from: admin } + ) + + const callInfo1 = await getCallInfo(module1) + assert.equal(callInfo1.updateExitedValidatorsCount.callCount, 1) + assert.equal(callInfo1.updateExitedValidatorsCount.nodeOperatorIds, nodeOpIdsData) + assert.equal(callInfo1.updateExitedValidatorsCount.validatorsCounts, validatorsCountsData) + + assert.equal(callInfo1.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo1.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + + const callInfo2 = await getCallInfo(module2) + assert.equal(callInfo2.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo2.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo2.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + }) + + it('the staking module 1 updates its internal total exited counter to 2', async () => { + await module1.setTotalExitedValidatorsCount(2) + }) + + it(`calling onValidatorsCountsByNodeOperatorReportingFinished still doesn't call ` + + `anything on any module`, async () => + { + await router.onValidatorsCountsByNodeOperatorReportingFinished({ from: admin }) + + const callInfo1 = await getCallInfo(module1) + assert.equal(callInfo1.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo1.updateExitedValidatorsCount.callCount, 1) + assert.equal(callInfo1.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + + const callInfo2 = await getCallInfo(module2) + assert.equal(callInfo2.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo2.updateExitedValidatorsCount.callCount, 0) + assert.equal(callInfo2.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + }) + + it('reporting exited validators by node operator passes the info to the module 2', async () => { + const nodeOpIds = [20] + const validatorsCounts = [1] + + const nodeOpIdsData = hexConcat(...nodeOpIds.map(i => hex(i, 8))) + const validatorsCountsData = hexConcat(...validatorsCounts.map(c => hex(c, 16))) + + await router.reportStakingModuleExitedValidatorsCountByNodeOperator( + moduleIds[1], nodeOpIdsData, validatorsCountsData, + { from: admin } + ) + + const callInfo2 = await getCallInfo(module2) + assert.equal(callInfo2.updateExitedValidatorsCount.callCount, 1) + assert.equal(callInfo2.updateExitedValidatorsCount.nodeOperatorIds, nodeOpIdsData) + assert.equal(callInfo2.updateExitedValidatorsCount.validatorsCounts, validatorsCountsData) + + assert.equal(callInfo2.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo2.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + + const callInfo1 = await getCallInfo(module1) + assert.equal(callInfo1.updateExitedValidatorsCount.callCount, 1) + assert.equal(callInfo1.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo1.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + }) + + it('the staking module 2 updates its internal total exited counter to 2', async () => { + await module2.setTotalExitedValidatorsCount(2) + }) + + it(`now that router's view on exited validators total match the module 2's view,` + + `calling onValidatorsCountsByNodeOperatorReportingFinished calls ` + + `onExitedAndStuckValidatorsCountsUpdated on the module 2`, async () => + { + await router.onValidatorsCountsByNodeOperatorReportingFinished({ from: admin }) + + const callInfo1 = await getCallInfo(module1) + const callInfo2 = await getCallInfo(module2) + + assert.equal(callInfo1.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + assert.equal(callInfo2.onExitedAndStuckValidatorsCountsUpdated.callCount, 1) + + assert.equal(callInfo1.updateExitedValidatorsCount.callCount, 1) + assert.equal(callInfo1.updateStuckValidatorsCount.callCount, 1) + + assert.equal(callInfo2.updateExitedValidatorsCount.callCount, 1) + assert.equal(callInfo2.updateStuckValidatorsCount.callCount, 1) + }) + + it('reporting exited validators by node operator passes the info to the module 1', async () => { + const nodeOpIds = [55] + const validatorsCounts = [1] + + const nodeOpIdsData = hexConcat(...nodeOpIds.map(i => hex(i, 8))) + const validatorsCountsData = hexConcat(...validatorsCounts.map(c => hex(c, 16))) + + await router.reportStakingModuleExitedValidatorsCountByNodeOperator( + moduleIds[0], nodeOpIdsData, validatorsCountsData, + { from: admin } + ) + + const callInfo1 = await getCallInfo(module1) + assert.equal(callInfo1.updateExitedValidatorsCount.callCount, 2) + assert.equal(callInfo1.updateExitedValidatorsCount.nodeOperatorIds, nodeOpIdsData) + assert.equal(callInfo1.updateExitedValidatorsCount.validatorsCounts, validatorsCountsData) + + assert.equal(callInfo1.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo1.onExitedAndStuckValidatorsCountsUpdated.callCount, 0) + + const callInfo2 = await getCallInfo(module2) + assert.equal(callInfo2.updateStuckValidatorsCount.callCount, 1) + assert.equal(callInfo2.updateExitedValidatorsCount.callCount, 1) + assert.equal(callInfo2.onExitedAndStuckValidatorsCountsUpdated.callCount, 1) + }) + + it('the staking module 1 updates its internal total exited counter to 3', async () => { + await module1.setTotalExitedValidatorsCount(3) + }) + + it(`now that router's view on exited validators total match the both modules' view,` + + `calling onValidatorsCountsByNodeOperatorReportingFinished calls ` + + `onExitedAndStuckValidatorsCountsUpdated on both modules`, async () => + { + await router.onValidatorsCountsByNodeOperatorReportingFinished({ from: admin }) + + const callInfo1 = await getCallInfo(module1) + const callInfo2 = await getCallInfo(module2) + + assert.equal(callInfo1.onExitedAndStuckValidatorsCountsUpdated.callCount, 1) + assert.equal(callInfo2.onExitedAndStuckValidatorsCountsUpdated.callCount, 2) + + assert.equal(callInfo1.updateExitedValidatorsCount.callCount, 2) + assert.equal(callInfo1.updateStuckValidatorsCount.callCount, 1) + + assert.equal(callInfo2.updateExitedValidatorsCount.callCount, 1) + assert.equal(callInfo2.updateStuckValidatorsCount.callCount, 1) + }) + }) + }) +}) diff --git a/test/0.8.9/staking-router.test.js b/test/0.8.9/staking-router.test.js index 883b5566e..17e514e48 100644 --- a/test/0.8.9/staking-router.test.js +++ b/test/0.8.9/staking-router.test.js @@ -6,6 +6,7 @@ const { assert } = require('../helpers/assert') const { EvmSnapshot } = require('../helpers/blockchain') const { newDao, newApp } = require('../helpers/dao') const { artifacts } = require('hardhat') +const { ETH } = require('../helpers/utils') const DepositContractMock = artifacts.require('DepositContractMock') const StakingRouterMock = artifacts.require('StakingRouterMock.sol') @@ -199,6 +200,19 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { await snapshot.revert() }) + it('reverts if module address exists', async () => { + await assert.revertsWithCustomError( + app.addStakingModule( + 'Test', + stakingModule.address, + 100, + 1000, + 2000, + { from: appManager}), + 'StakingModuleAddressExists()' + ) + }) + it('set withdrawal credentials does not allowed without role', async () => { const newWC = '0x'.padEnd(66, '5678') await assert.reverts( @@ -265,12 +279,14 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { await snapshot.revert() }) it('staking modules limit is 32', async () => { - const stakingModule = await StakingModuleMock.new({ from: deployer }) for (var i = 0; i < 32; i++) { - await app.addStakingModule('Test module', stakingModule.address, 100, 100, 100, { from: appManager }) + const stakingModule = await StakingModuleMock.new({ from: deployer }) + let tx = await app.addStakingModule('Test module', stakingModule.address, 100, 100, 100, { from: appManager }) } + + const oneMoreStakingModule = await StakingModuleMock.new({ from: deployer }) await assert.revertsWithCustomError( - app.addStakingModule('Test module', stakingModule.address, 100, 100, 100, { from: appManager }), + app.addStakingModule('Test module', oneMoreStakingModule.address, 100, 100, 100, { from: appManager }), `StakingModulesLimitExceeded()` ) }) @@ -718,7 +734,7 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { it('deposit fails', async () => { await assert.revertsWithCustomError( - app.deposit(100, stakingModulesParams[0].expectedModuleId, '0x00', { value: 100, from: lido }), + app.deposit(100, stakingModulesParams[0].expectedModuleId, '0x00', { value: ETH(32 * 100), from: lido }), 'StakingModuleNotActive()' ) }) diff --git a/test/0.8.9/withdrawal-queue.test.js b/test/0.8.9/withdrawal-queue.test.js index 377f65df4..1601cf401 100644 --- a/test/0.8.9/withdrawal-queue.test.js +++ b/test/0.8.9/withdrawal-queue.test.js @@ -2,7 +2,7 @@ const hre = require('hardhat') const { artifacts, contract, ethers } = require('hardhat') const { bn, getEventArgument, ZERO_ADDRESS } = require('@aragon/contract-helpers-test') -const { ETH, StETH, shareRate, shares } = require('../helpers/utils') +const { ETH, StETH, shareRate, shares, setBalance } = require('../helpers/utils') const { assert } = require('../helpers/assert') const withdrawals = require('../helpers/withdrawals') const { signPermit, makeDomainSeparator } = require('../0.6.12/helpers/permit_helpers') @@ -18,7 +18,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { const snapshot = new EvmSnapshot(ethers.provider) before('Deploy', async () => { - steth = await StETHMock.new({ value: ETH(601) }) + steth = await StETHMock.new({ value: ETH(1) }) wsteth = await WstETH.new(steth.address) withdrawalQueue = (await withdrawals.deploy(daoAgent, wsteth.address)).queue @@ -26,12 +26,14 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { await withdrawalQueue.initialize(daoAgent, daoAgent, daoAgent, steth.address, steth.address) await withdrawalQueue.resume({ from: daoAgent }) - await steth.setTotalPooledEther(ETH(300)) + await steth.setTotalPooledEther(ETH(600)) + // we need 1 ETH additionally to pay gas on finalization because coverage ingnores gasPrice=0 + await setBalance(steth.address, ETH(600 + 1)) await steth.mintShares(user, shares(1)) await steth.approve(withdrawalQueue.address, StETH(300), { from: user }) - impersonate(ethers.provider, steth.address) - snapshot.make(); + await impersonate(ethers.provider, steth.address) + await snapshot.make(); }) afterEach(async () => { @@ -67,7 +69,10 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { assert.equals(await withdrawalQueue.unfinalizedStETH(), StETH(300)) assert.equals(await withdrawalQueue.getWithdrawalRequests(owner), [1]) - const request = await withdrawalQueue.getWithdrawalRequestStatus(requestId) + const requests = await withdrawalQueue.getWithdrawalStatus([requestId]) + assert.equals(requests.length, 1) + + const request = requests[0] assert.equals(request.owner, owner) assert.equals(request.amountOfStETH, StETH(300)) @@ -104,7 +109,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { assert.equals(await withdrawalQueue.getLastRequestId(), requestId) assert.equals(await withdrawalQueue.getLastFinalizedRequestId(), 0) - const request = await withdrawalQueue.getWithdrawalRequestStatus(requestId) + const request = (await withdrawalQueue.getWithdrawalStatus([requestId]))[0] assert.equals(request.owner, owner) assert.equals(request.amountOfStETH, min) @@ -125,7 +130,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { it('One can request MAX', async () => { const max = await withdrawalQueue.MAX_STETH_WITHDRAWAL_AMOUNT() - await steth.setTotalPooledEther(max) + await steth.setTotalPooledEther(max.muln(2)) await steth.approve(withdrawalQueue.address, max, { from: user }) const receipt = await withdrawalQueue.requestWithdrawals([max], owner, { from: user }) @@ -142,7 +147,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { assert.equals(await withdrawalQueue.getLastRequestId(), requestId) assert.equals(await withdrawalQueue.getLastFinalizedRequestId(), 0) - const request = await withdrawalQueue.getWithdrawalRequestStatus(requestId) + const request = (await withdrawalQueue.getWithdrawalStatus([requestId]))[0] assert.equals(request.owner, owner) assert.equals(request.amountOfStETH, max) @@ -195,7 +200,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { }) it('Same discounts is squashed into one', async () => { - await steth.setTotalPooledEther(ETH(600)) + await steth.setTotalPooledEther(ETH(900)) await steth.mintShares(user, shares(1)) await steth.approve(withdrawalQueue.address, StETH(300), { from: user }) @@ -209,7 +214,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { }) it('One can finalize a batch of requests at once', async () => { - await steth.setTotalPooledEther(ETH(600)) + await steth.setTotalPooledEther(ETH(900)) await steth.mintShares(user, shares(1)) await steth.approve(withdrawalQueue.address, StETH(300), { from: user }) @@ -225,7 +230,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { }) it('One can finalize part of the queue', async () => { - await steth.setTotalPooledEther(ETH(600)) + await steth.setTotalPooledEther(ETH(900)) await steth.mintShares(user, shares(1)) await steth.approve(withdrawalQueue.address, StETH(600), { from: user }) @@ -247,12 +252,48 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { }) }) + context('getClaimableEth()', () => { + beforeEach(async () => { + await withdrawalQueue.requestWithdrawals([ETH(1)], owner, { from: user }) + }) + + it('works', async () => { + await withdrawalQueue.requestWithdrawals([ETH(1)], owner, { from: user }) + await withdrawalQueue.finalize(1, { from: steth.address, value: ETH(1) }) + + assert.equals(await withdrawalQueue.getClaimableEther([1], [1]), ETH(1)) + }) + + it('return 0 for non-finalized request', async () => { + assert.equals(await withdrawalQueue.getClaimableEther([1], [1]), ETH(0)) + assert.equals(await withdrawalQueue.getClaimableEther([1], [51]), ETH(0)) + }) + + it('return 0 for claimed request', async () => { + await withdrawalQueue.finalize(1, { from: steth.address, value: ETH(1) }) + await withdrawalQueue.claimWithdrawals([1], [1], { from: owner }) + + assert.equals(await withdrawalQueue.getClaimableEther([1], [1]), ETH(0)) + assert.equals(await withdrawalQueue.getClaimableEther([1], [51]), ETH(0)) + }) + + it('reverts on invalid params', async () => { + await assert.reverts(withdrawalQueue.getClaimableEther([0], [1]), 'InvalidRequestId(0)') + await assert.reverts(withdrawalQueue.getClaimableEther([2], [1]), 'InvalidRequestId(2)') + + await withdrawalQueue.finalize(1, { from: steth.address, value: ETH(1) }) + await assert.reverts(withdrawalQueue.getClaimableEther([1], [2]), 'InvalidHint(2)') + await assert.reverts(withdrawalQueue.getClaimableEther([1], [0]), 'InvalidHint(0)') + + await withdrawalQueue.requestWithdrawals([ETH(1)], owner, { from: user }) + await assert.reverts(withdrawalQueue.getClaimableEther([1], [2]), 'InvalidHint(2)') + }) + }) + context('claimWithdrawal()', async () => { - let requestId const amount = ETH(300) beforeEach('Enqueue a request', async () => { - const receipt = await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) - requestId = getEventArgument(receipt, "WithdrawalRequested", "requestId") + await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) }) it('Owner can claim a finalized request to recipient address', async () => { @@ -260,7 +301,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { const balanceBefore = bn(await ethers.provider.getBalance(user)) - await withdrawalQueue.claimWithdrawalTo(requestId, 1, user, { from: owner }) + await withdrawalQueue.claimWithdrawalsTo([1], [1], user, { from: owner }) assert.equals(await ethers.provider.getBalance(user), balanceBefore.add(bn(amount))) }) @@ -270,51 +311,52 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { const balanceBefore = bn(await ethers.provider.getBalance(owner)) - await withdrawalQueue.claimWithdrawal(requestId, { from: owner }) + const tx = await withdrawalQueue.claimWithdrawal(1, { from: owner }) - assert.equals(await ethers.provider.getBalance(owner), balanceBefore.add(bn(amount))) + // tx.receipt.gasUsed is a workaround for coverage, because it ignores gasPrice=0 + assert.almostEqual(await ethers.provider.getBalance(owner), balanceBefore.add(bn(amount)), tx.receipt.gasUsed) }) - it('One cant claim not finalized request', async () => { - await assert.reverts(withdrawalQueue.claimWithdrawalTo(requestId, 1, owner, { from: owner }), - `RequestNotFinalized(${requestId})`) + it('One cant claim not finalized or not existed request', async () => { + await assert.reverts(withdrawalQueue.claimWithdrawals([1], [1], { from: owner }), `RequestNotFoundOrNotFinalized(1)`) + await assert.reverts(withdrawalQueue.claimWithdrawals([2], [1], { from: owner }), `RequestNotFoundOrNotFinalized(2)`) }) it('Cant claim request with a wrong hint', async () => { - await steth.setTotalPooledEther(ETH(600)) + await steth.setTotalPooledEther(ETH(900)) await steth.mintShares(user, shares(1)) await steth.approve(withdrawalQueue.address, StETH(600), { from: user }) await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) await withdrawalQueue.finalize(2, { from: steth.address, value: amount }) - await assert.reverts(withdrawalQueue.claimWithdrawalTo(requestId, 0, owner, { from: owner }), 'InvalidHint(0)') - await assert.reverts(withdrawalQueue.claimWithdrawalTo(requestId, 2, owner, { from: owner }), 'InvalidHint(2)') + await assert.reverts(withdrawalQueue.claimWithdrawals([1], [0], { from: owner }), 'InvalidHint(0)') + await assert.reverts(withdrawalQueue.claimWithdrawals([1], [2], { from: owner }), 'InvalidHint(2)') }) it('Cant withdraw token two times', async () => { await withdrawalQueue.finalize(1, { from: steth.address, value: amount }) - await withdrawalQueue.claimWithdrawal(requestId, { from: owner }) + await withdrawalQueue.claimWithdrawal(1, { from: owner }) - await assert.reverts(withdrawalQueue.claimWithdrawalTo(requestId, 1, owner, { from: owner }), + await assert.reverts(withdrawalQueue.claimWithdrawal(1, { from: owner }), 'RequestAlreadyClaimed(1)') }) it('Discounted withdrawals produce less eth', async () => { await withdrawalQueue.finalize(1, { from: steth.address, value: ETH(150) }) - const hint = await withdrawalQueue.findCheckpointHintUnbounded(requestId) const balanceBefore = bn(await ethers.provider.getBalance(owner)) assert.equals(await withdrawalQueue.getLockedEtherAmount(), ETH(150)) - await withdrawalQueue.claimWithdrawalTo(requestId, hint, owner, { from: owner }) + const tx = await withdrawalQueue.claimWithdrawal(1, { from: owner }) assert.equals(await withdrawalQueue.getLockedEtherAmount(), ETH(0)) - assert.equals(bn(await ethers.provider.getBalance(owner)).sub(balanceBefore), ETH(150)) + // tx.receipt.gasUsed is a workaround for coverage, because it ignores gasPrice=0 + assert.almostEqual(bn(await ethers.provider.getBalance(owner)).sub(balanceBefore), ETH(150), tx.receipt.gasUsed) }) it('One can claim a lot of withdrawals with different discounts', async () => { - await steth.setTotalPooledEther(ETH(21)) + await steth.setTotalPooledEther(ETH(22)) await steth.mintShares(user, shares(21)) await steth.approve(withdrawalQueue.address, StETH(21), { from: user }) @@ -350,7 +392,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { it('works', async () => { for (let i = 1; i <= numOfRequests; i++) { - const timestamp = (await withdrawalQueue.getWithdrawalRequestStatus(i)).timestamp; + const timestamp = ((await withdrawalQueue.getWithdrawalStatus([i]))[0]).timestamp; assert.equals(await withdrawalQueue.findLastFinalizableRequestIdByTimestamp(timestamp, 1, 10), i) } }) @@ -360,7 +402,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { }) it('return zero if no unfinalized request found', async () => { - const timestamp = (await withdrawalQueue.getWithdrawalRequestStatus(1)).timestamp; + const timestamp = ((await withdrawalQueue.getWithdrawalStatus([1]))[0]).timestamp; await withdrawalQueue.finalize(1, { from: steth.address, value: ETH[10] }) assert.equals(await withdrawalQueue.findLastFinalizableRequestIdByTimestamp(timestamp, 2, 10), 0) @@ -370,7 +412,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { await assert.reverts(withdrawalQueue.findLastFinalizableRequestIdByTimestamp(0, 0, 10), "ZeroTimestamp()") - const timestamp = (await withdrawalQueue.getWithdrawalRequestStatus(2)).timestamp; + const timestamp = ((await withdrawalQueue.getWithdrawalStatus([2]))[0]).timestamp; await assert.reverts(withdrawalQueue.findLastFinalizableRequestIdByTimestamp(timestamp, 0, 10), "InvalidRequestIdRange(0, 10)") @@ -443,7 +485,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { it('works', async () => { for (let i = 1; i <= numOfRequests; i++) { const budget = ETH(i * 10 + 5); - const timestamp = (await withdrawalQueue.getWithdrawalRequestStatus(i)).timestamp; + const timestamp = (await withdrawalQueue.getWithdrawalStatus([i]))[0].timestamp; assert.equals(await withdrawalQueue.findLastFinalizableRequestId(budget, shareRate(150), timestamp), i) } }) @@ -451,7 +493,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { it('returns zero if no unfinalized requests', async () => { await withdrawalQueue.finalize(10, { from: steth.address, value: ETH[10] }) - const timestamp = (await withdrawalQueue.getWithdrawalRequestStatus(10)).timestamp; + const timestamp = (await withdrawalQueue.getWithdrawalStatus([10]))[0].timestamp; assert.equals(await withdrawalQueue.findLastFinalizableRequestId(ETH(100), shareRate(100), timestamp), 0) }) @@ -467,7 +509,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { }) }) - context('findCheckpointHint()', async () => { + context('findCheckpointsHint()', async () => { const numOfRequests = 10; const requests = Array(numOfRequests).fill(ETH(20)) const discountedPrices = Array(numOfRequests).fill().map((_, i) => ETH(i)); @@ -478,31 +520,38 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { await withdrawalQueue.finalize(i, { from: steth.address, value: discountedPrices[i] }) } assert.equals(await withdrawalQueue.getLastCheckpointIndex(), numOfRequests) - assert.equals(await withdrawalQueue.findCheckpointHintUnbounded(await withdrawalQueue.getLastFinalizedRequestId()), + assert.equals(await withdrawalQueue.findCheckpointHintsUnbounded([await withdrawalQueue.getLastFinalizedRequestId()]), await withdrawalQueue.getLastCheckpointIndex()) }) it('works unbounded', async () => { - assert.equals(await withdrawalQueue.findCheckpointHintUnbounded(10), await withdrawalQueue.getLastCheckpointIndex()) + assert.equals(await withdrawalQueue.findCheckpointHintsUnbounded([10]), await withdrawalQueue.getLastCheckpointIndex()) }) it('reverts if request is not finalized', async () => { - await assert.reverts(withdrawalQueue.findCheckpointHint(11, 1, 10), "RequestNotFinalized(11)") - await assert.reverts(withdrawalQueue.findCheckpointHintUnbounded(11), "RequestNotFinalized(11)") + await withdrawalQueue.requestWithdrawals([ETH(1)], owner, { from: user }) + await assert.reverts(withdrawalQueue.findCheckpointHints([11], 1, 10), "RequestNotFoundOrNotFinalized(11)") + await assert.reverts(withdrawalQueue.findCheckpointHintsUnbounded([11]), "RequestNotFoundOrNotFinalized(11)") + + }) + + it('reverts if there is no such a request', async () => { + await assert.reverts(withdrawalQueue.findCheckpointHints([12], 1, 10), "RequestNotFoundOrNotFinalized(12)") + await assert.reverts(withdrawalQueue.findCheckpointHintsUnbounded([12]), "RequestNotFoundOrNotFinalized(12)") }) it('range search (found)', async () => { - assert.equals(await withdrawalQueue.findCheckpointHint(5, 1, 9), 5) - assert.equals(await withdrawalQueue.findCheckpointHint(1, 1, 9), 1) - assert.equals(await withdrawalQueue.findCheckpointHint(9, 1, 9), 9) - assert.equals(await withdrawalQueue.findCheckpointHint(5, 5, 5), 5) + assert.equals(await withdrawalQueue.findCheckpointHints([5], 1, 9), 5) + assert.equals(await withdrawalQueue.findCheckpointHints([1], 1, 9), 1) + assert.equals(await withdrawalQueue.findCheckpointHints([9], 1, 9), 9) + assert.equals(await withdrawalQueue.findCheckpointHints([5], 5, 5), 5) }) it('range search (not found)', async () => { - assert.equals(await withdrawalQueue.findCheckpointHint(10, 1, 5), 0) - assert.equals(await withdrawalQueue.findCheckpointHint(6, 1, 5), 0) - assert.equals(await withdrawalQueue.findCheckpointHint(1, 5, 5), 0) - assert.equals(await withdrawalQueue.findCheckpointHint(4, 5, 9), 0) + assert.equals(await withdrawalQueue.findCheckpointHints([10], 1, 5), 0) + assert.equals(await withdrawalQueue.findCheckpointHints([6], 1, 5), 0) + assert.equals(await withdrawalQueue.findCheckpointHints([1], 5, 5), 0) + assert.equals(await withdrawalQueue.findCheckpointHints([4], 5, 9), 0) }) it('sequential search', async () => { @@ -517,7 +566,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { for (let i = 1; i <= lastIndex; i += searchLength) { let end = i + searchLength - 1 if (end > lastIndex) end = lastIndex - let foundIndex = await withdrawalQueue.findCheckpointHint(requestId, i, end) + let foundIndex = await withdrawalQueue.findCheckpointHints([requestId], i, end) if (foundIndex != 0) return foundIndex } } @@ -652,14 +701,9 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { await withdrawalQueue.finalize(secondRequestId, { from: steth.address, value: ETH(30) }) const balanceBefore = bn(await ethers.provider.getBalance(owner)) - await withdrawalQueue.claimWithdrawals( - [ - [requestId, 1], - [secondRequestId, 1] - ], - { from: owner } - ) - assert.equals(await ethers.provider.getBalance(owner), balanceBefore.add(bn(ETH(30)))) + const tx = await withdrawalQueue.claimWithdrawals([1, 2], [1, 1], { from: owner }) + // tx.receipt.gasUsed is a workaround for coverage, because it ignores gasPrice=0 + assert.almostEqual(await ethers.provider.getBalance(owner), balanceBefore.add(bn(ETH(30))), tx.receipt.gasUsed) }) }) @@ -771,12 +815,12 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { }) it("One can't change someone else's request", async () => { - await assert.reverts(withdrawalQueue.transferFrom(user, owner, requestId, { from: stranger }), + await assert.reverts(withdrawalQueue.transferFrom(user, owner, requestId, { from: stranger }), `NotOwnerOrApproved("${stranger}")`) }) it("One can't pass zero owner", async () => { - await assert.reverts(withdrawalQueue.transferFrom(user, ZERO_ADDRESS, requestId, { from: user }), + await assert.reverts(withdrawalQueue.transferFrom(user, ZERO_ADDRESS, requestId, { from: user }), 'TransferToZeroAddress()') }) @@ -794,7 +838,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user]) => { it("Changing owner doesn't work with wrong request id", async () => { const wrongRequestId = requestId + 1 await assert.reverts(withdrawalQueue.transferFrom(user, owner, wrongRequestId, { from: user }), - `InvalidRequestId(${wrongRequestId})`) + `InvalidRequestId(${wrongRequestId})`) }) }) diff --git a/test/0.8.9/withdrawal-request-nft.test.js b/test/0.8.9/withdrawal-request-nft.test.js index 1dd851933..d38a37440 100644 --- a/test/0.8.9/withdrawal-request-nft.test.js +++ b/test/0.8.9/withdrawal-request-nft.test.js @@ -1,7 +1,7 @@ const hre = require('hardhat') const { assert } = require('../helpers/assert') const { EvmSnapshot } = require('../helpers/blockchain') -const { shares, ETH, shareRate } = require('../helpers/utils') +const { shares, ETH, shareRate, setBalance } = require('../helpers/utils') const withdrawals = require('../helpers/withdrawals') const StETH = hre.artifacts.require('StETHMock') @@ -11,34 +11,36 @@ const ERC721ReceiverMock = hre.artifacts.require('ERC721ReceiverMock') hre.contract( 'WithdrawalNFT', ([deployer, stEthHolder, wstEthHolder, nftHolderStETH, nftHolderWstETH, recipient, stranger]) => { - let withdrawalRequestNFT, stETH, wstETH, erc721ReceiverMock + let withdrawalQueueERC721, stETH, wstETH, erc721ReceiverMock let nftHolderStETHTokenIds, nftHolderWstETHTokenIds, nonExistedTokenId const snapshot = new EvmSnapshot(hre.ethers.provider) before(async () => { - stETH = await StETH.new({ value: ETH(100), from: deployer }) + stETH = await StETH.new({ value: ETH(1), from: deployer }) + await setBalance(stETH.address, ETH(100)) + wstETH = await WstETH.new(stETH.address, { from: deployer }) erc721ReceiverMock = await ERC721ReceiverMock.new({ from: deployer }) - withdrawalRequestNFT = (await withdrawals.deploy(deployer, wstETH.address)).queue - await withdrawalRequestNFT.initialize( + withdrawalQueueERC721 = (await withdrawals.deploy(deployer, wstETH.address, "Lido TEST Request", "unstEsT")).queue + await withdrawalQueueERC721.initialize( deployer, // owner deployer, // pauser deployer, // resumer deployer, // finalizer deployer ) - await withdrawalRequestNFT.resume({ from: deployer }) + await withdrawalQueueERC721.resume({ from: deployer }) - await stETH.setTotalPooledEther(ETH(100)) + await stETH.setTotalPooledEther(ETH(101)) await stETH.mintShares(stEthHolder, shares(50)) await stETH.mintShares(wstETH.address, shares(50)) await wstETH.mint(wstEthHolder, ETH(25)) - await stETH.approve(withdrawalRequestNFT.address, ETH(50), { from: stEthHolder }) - await wstETH.approve(withdrawalRequestNFT.address, ETH(25), { from: wstEthHolder }) - await withdrawalRequestNFT.requestWithdrawals([ETH(25), ETH(25)], nftHolderStETH,{ from: stEthHolder }) + await stETH.approve(withdrawalQueueERC721.address, ETH(50), { from: stEthHolder }) + await wstETH.approve(withdrawalQueueERC721.address, ETH(25), { from: wstEthHolder }) + await withdrawalQueueERC721.requestWithdrawals([ETH(25), ETH(25)], nftHolderStETH, { from: stEthHolder }) nftHolderStETHTokenIds = [1, 2] - await withdrawalRequestNFT.requestWithdrawalsWstETH([ETH(25)], nftHolderWstETH, { from: wstEthHolder }) + await withdrawalQueueERC721.requestWithdrawalsWstETH([ETH(25)], nftHolderWstETH, { from: wstEthHolder }) nftHolderWstETHTokenIds = [3] nonExistedTokenId = 4 await snapshot.make() @@ -50,81 +52,81 @@ hre.contract( describe('ERC721Metadata', () => { it('Initial properties', async () => { - assert.equals(await withdrawalRequestNFT.symbol(), "unstETH") - assert.equals(await withdrawalRequestNFT.name(), "Lido Withdrawal Request") + assert.equals(await withdrawalQueueERC721.symbol(), "unstEsT") + assert.equals(await withdrawalQueueERC721.name(), "Lido TEST Request") }) }) describe('supportsInterface()', () => { it('returns true for IERC165 interfaceiId (0x01ffc9a7)', async () => { - assert.isTrue(await withdrawalRequestNFT.supportsInterface('0x01ffc9a7')) + assert.isTrue(await withdrawalQueueERC721.supportsInterface('0x01ffc9a7')) }) it('returns true for IERC721 interface id (0x80ac58cd)', async () => { - assert.isTrue(await withdrawalRequestNFT.supportsInterface('0x80ac58cd')) + assert.isTrue(await withdrawalQueueERC721.supportsInterface('0x80ac58cd')) }) it('returns true for AccessControlEnumerable interface id (0x5a05180f)', async () => { - assert.isTrue(await withdrawalRequestNFT.supportsInterface('0x5a05180f')) + assert.isTrue(await withdrawalQueueERC721.supportsInterface('0x5a05180f')) }) it('returns false for unsupported e interface id (0xffffffff)', async () => { - assert.isFalse(await withdrawalRequestNFT.supportsInterface('0xffffffff')) + assert.isFalse(await withdrawalQueueERC721.supportsInterface('0xffffffff')) }) it('returns false for unsupported e interface id (0xdeadbeaf)', async () => { - assert.isFalse(await withdrawalRequestNFT.supportsInterface('0xdeadbeaf')) + assert.isFalse(await withdrawalQueueERC721.supportsInterface('0xdeadbeaf')) }) }) describe('balanceOf()', () => { it('return 0 when user has not withdrawal requests', async () => { - assert.equals(await withdrawalRequestNFT.balanceOf(recipient), 0) + assert.equals(await withdrawalQueueERC721.balanceOf(recipient), 0) }) it('return correct withdrawal requests count', async () => { - assert.equals(await withdrawalRequestNFT.balanceOf(nftHolderStETH), 2) - assert.equals(await withdrawalRequestNFT.balanceOf(nftHolderWstETH), 1) + assert.equals(await withdrawalQueueERC721.balanceOf(nftHolderStETH), 2) + assert.equals(await withdrawalQueueERC721.balanceOf(nftHolderWstETH), 1) }) }) describe('ownerOf()', () => { it('reverts with error InvalidRequestId() when token id is 0', async () => { - await assert.reverts(withdrawalRequestNFT.ownerOf(0), `InvalidRequestId(0)`) + await assert.reverts(withdrawalQueueERC721.ownerOf(0), `InvalidRequestId(0)`) }) it('reverts with error InvalidRequestId() when called with non existed token id', async () => { - await assert.reverts(withdrawalRequestNFT.ownerOf(nonExistedTokenId), `InvalidRequestId(${nonExistedTokenId})`) + await assert.reverts(withdrawalQueueERC721.ownerOf(nonExistedTokenId), `InvalidRequestId(${nonExistedTokenId})`) }) it('reverts correct owner', async () => { - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[0]), nftHolderStETH) - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[1]), nftHolderStETH) - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderWstETHTokenIds[0]), nftHolderWstETH) + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[0]), nftHolderStETH) + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[1]), nftHolderStETH) + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderWstETHTokenIds[0]), nftHolderWstETH) }) }) describe('approve()', async () => { it('reverts with message "ApprovalToOwner()" when approval for owner address', async () => { await assert.reverts( - withdrawalRequestNFT.approve(nftHolderStETH, nftHolderStETHTokenIds[0], { from: nftHolderStETH }), + withdrawalQueueERC721.approve(nftHolderStETH, nftHolderStETHTokenIds[0], { from: nftHolderStETH }), 'ApprovalToOwner()' ) }) it('reverts with message "NotOwnerOrApprovedForAll()" when called noy by owner', async () => { await assert.reverts( - withdrawalRequestNFT.approve(recipient, nftHolderStETHTokenIds[0], { from: stranger }), + withdrawalQueueERC721.approve(recipient, nftHolderStETHTokenIds[0], { from: stranger }), `NotOwnerOrApprovedForAll("${stranger}")` ) }) it('sets approval for address', async () => { - await withdrawalRequestNFT.approve(recipient, nftHolderStETHTokenIds[0], { from: nftHolderStETH }) - assert.equal(await withdrawalRequestNFT.getApproved(nftHolderStETHTokenIds[0]), recipient) + await withdrawalQueueERC721.approve(recipient, nftHolderStETHTokenIds[0], { from: nftHolderStETH }) + assert.equal(await withdrawalQueueERC721.getApproved(nftHolderStETHTokenIds[0]), recipient) }) }) describe('getApproved()', async () => { it('reverts with message "InvalidRequestId()" when called with non existed token id', async () => { await assert.reverts( - withdrawalRequestNFT.getApproved(nonExistedTokenId), + withdrawalQueueERC721.getApproved(nonExistedTokenId), `InvalidRequestId(${nonExistedTokenId})` ) }) @@ -133,7 +135,7 @@ hre.contract( describe('setApprovalForAll()', async () => { it('reverts with message "ApproveToCaller()" when owner equal to operator', async () => { await assert.reverts( - withdrawalRequestNFT.setApprovalForAll(nftHolderStETH, true, { from: nftHolderStETH }), + withdrawalQueueERC721.setApprovalForAll(nftHolderStETH, true, { from: nftHolderStETH }), 'ApproveToCaller()' ) }) @@ -142,7 +144,7 @@ hre.contract( describe('safeTransferFrom(address,address,uint256)', async () => { it('reverts with message "NotOwnerOrApproved()" when approvalNotSet and not owner', async () => { await assert.reverts( - withdrawalRequestNFT.safeTransferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { + withdrawalQueueERC721.safeTransferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { from: stranger }), `NotOwnerOrApproved("${stranger}")` @@ -150,39 +152,39 @@ hre.contract( }) it('transfers if called by owner', async () => { - assert.notEqual(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[0]), recipient) - await withdrawalRequestNFT.safeTransferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { + assert.notEqual(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[0]), recipient) + await withdrawalQueueERC721.safeTransferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { from: nftHolderStETH }) - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[0]), recipient) + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[0]), recipient) }) it('transfers if token approval set', async () => { - await withdrawalRequestNFT.approve(recipient, nftHolderStETHTokenIds[0], { from: nftHolderStETH }) - assert.notEqual(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[0]), recipient) - await withdrawalRequestNFT.safeTransferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { + await withdrawalQueueERC721.approve(recipient, nftHolderStETHTokenIds[0], { from: nftHolderStETH }) + assert.notEqual(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[0]), recipient) + await withdrawalQueueERC721.safeTransferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { from: recipient }) - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[0]), recipient) + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[0]), recipient) }) it('transfers if operator approval set', async () => { - await withdrawalRequestNFT.setApprovalForAll(recipient, true, { from: nftHolderStETH }) - assert.notEqual(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[0]), recipient) - assert.notEqual(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[1]), recipient) - await withdrawalRequestNFT.safeTransferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { + await withdrawalQueueERC721.setApprovalForAll(recipient, true, { from: nftHolderStETH }) + assert.notEqual(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[0]), recipient) + assert.notEqual(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[1]), recipient) + await withdrawalQueueERC721.safeTransferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { from: recipient }) - await withdrawalRequestNFT.safeTransferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[1], { + await withdrawalQueueERC721.safeTransferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[1], { from: recipient }) - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[0]), recipient) - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[1]), recipient) + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[0]), recipient) + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[1]), recipient) }) it('reverts with message "TransferToNonIERC721Receiver()" when transfer to contract that not implements IERC721Receiver interface', async () => { await assert.reverts( - withdrawalRequestNFT.safeTransferFrom(nftHolderWstETH, stETH.address, nftHolderWstETHTokenIds[0], { + withdrawalQueueERC721.safeTransferFrom(nftHolderWstETH, stETH.address, nftHolderWstETHTokenIds[0], { from: nftHolderWstETH }), `TransferToNonIERC721Receiver("${stETH.address}")` @@ -192,7 +194,7 @@ hre.contract( it('reverts with propagated error message when recipient contract implements ERC721Receiver and reverts on onERC721Received call', async () => { await erc721ReceiverMock.setDoesAcceptTokens(false, { from: deployer }) await assert.reverts( - withdrawalRequestNFT.safeTransferFrom(nftHolderStETH, erc721ReceiverMock.address, nftHolderStETHTokenIds[0], { + withdrawalQueueERC721.safeTransferFrom(nftHolderStETH, erc721ReceiverMock.address, nftHolderStETHTokenIds[0], { from: nftHolderStETH }), 'ERC721_NOT_ACCEPT_TOKENS' @@ -201,8 +203,8 @@ hre.contract( it("doesn't revert when recipient contract implements ERC721Receiver interface and accepts tokens", async () => { await erc721ReceiverMock.setDoesAcceptTokens(true, { from: deployer }) - assert.notEqual(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[0]), erc721ReceiverMock.address) - await withdrawalRequestNFT.safeTransferFrom( + assert.notEqual(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[0]), erc721ReceiverMock.address) + await withdrawalQueueERC721.safeTransferFrom( nftHolderStETH, erc721ReceiverMock.address, nftHolderStETHTokenIds[0], @@ -210,23 +212,33 @@ hre.contract( from: nftHolderStETH } ) - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[0]), erc721ReceiverMock.address) + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[0]), erc721ReceiverMock.address) }) }) describe('transferFrom()', async () => { it('reverts with message "NotOwnerOrApproved()" when approvalNotSet and not owner', async () => { await assert.reverts( - withdrawalRequestNFT.transferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { from: stranger }), + withdrawalQueueERC721.transferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { from: stranger }), `NotOwnerOrApproved("${stranger}")` ) }) + it('reverts when transfer to the same address', async () => { + await assert.reverts( + withdrawalQueueERC721.transferFrom( + nftHolderWstETH, nftHolderWstETH, nftHolderWstETHTokenIds[0], + {from: nftHolderWstETH } + ), + 'TransferToThemselves()' + ) + }) + it('reverts with error "RequestAlreadyClaimed()" when called on claimed request', async () => { - const batch = await withdrawalRequestNFT.finalizationBatch(3, shareRate(1)) - await withdrawalRequestNFT.finalize(3, { from: deployer, value: batch.ethToLock }) + const batch = await withdrawalQueueERC721.finalizationBatch(3, shareRate(1)) + await withdrawalQueueERC721.finalize(3, { from: deployer, value: batch.ethToLock }) const ownerETHBefore = await hre.ethers.provider.getBalance(nftHolderStETH) - const tx = await withdrawalRequestNFT.methods['claimWithdrawal(uint256)'](nftHolderStETHTokenIds[0], { + const tx = await withdrawalQueueERC721.methods['claimWithdrawal(uint256)'](nftHolderStETHTokenIds[0], { from: nftHolderStETH }) const ownerETHAfter = await hre.ethers.provider.getBalance(nftHolderStETH) @@ -234,7 +246,7 @@ hre.contract( assert.almostEqual(ownerETHAfter, ownerETHBefore.add(ETH(25)), tx.receipt.gasUsed) await assert.reverts( - withdrawalRequestNFT.transferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { + withdrawalQueueERC721.transferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { from: nftHolderStETH }), `RequestAlreadyClaimed(${nftHolderStETHTokenIds[0]})` @@ -242,47 +254,47 @@ hre.contract( }) it('transfers if called by owner', async () => { - assert.notEqual(await withdrawalRequestNFT.ownerOf(nftHolderWstETHTokenIds[0]), recipient) - await withdrawalRequestNFT.transferFrom(nftHolderWstETH, recipient, nftHolderWstETHTokenIds[0], { + assert.notEqual(await withdrawalQueueERC721.ownerOf(nftHolderWstETHTokenIds[0]), recipient) + await withdrawalQueueERC721.transferFrom(nftHolderWstETH, recipient, nftHolderWstETHTokenIds[0], { from: nftHolderWstETH }) - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderWstETHTokenIds[0]), recipient) + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderWstETHTokenIds[0]), recipient) }) it('transfers if token approval set', async () => { - await withdrawalRequestNFT.approve(recipient, nftHolderStETHTokenIds[0], { from: nftHolderStETH }) - assert.notEqual(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[0]), recipient) - await withdrawalRequestNFT.transferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { + await withdrawalQueueERC721.approve(recipient, nftHolderStETHTokenIds[0], { from: nftHolderStETH }) + assert.notEqual(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[0]), recipient) + await withdrawalQueueERC721.transferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { from: recipient }) - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[0]), recipient) + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[0]), recipient) }) it('transfers if operator approval set', async () => { - await withdrawalRequestNFT.setApprovalForAll(recipient, true, { from: nftHolderStETH }) - assert.notEqual(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[0]), recipient) - assert.notEqual(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[1]), recipient) - await withdrawalRequestNFT.transferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { + await withdrawalQueueERC721.setApprovalForAll(recipient, true, { from: nftHolderStETH }) + assert.notEqual(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[0]), recipient) + assert.notEqual(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[1]), recipient) + await withdrawalQueueERC721.transferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { from: recipient }) - await withdrawalRequestNFT.transferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[1], { + await withdrawalQueueERC721.transferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[1], { from: recipient }) - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[0]), recipient) - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[1]), recipient) + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[0]), recipient) + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[1]), recipient) }) it('can claim request after transfer', async () => { - await withdrawalRequestNFT.transferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { + await withdrawalQueueERC721.transferFrom(nftHolderStETH, recipient, nftHolderStETHTokenIds[0], { from: nftHolderStETH }) - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderStETHTokenIds[0]), recipient) + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderStETHTokenIds[0]), recipient) - const batch = await withdrawalRequestNFT.finalizationBatch(3, shareRate(1)) - await withdrawalRequestNFT.finalize(3, { from: deployer, value: batch.ethToLock }) + const batch = await withdrawalQueueERC721.finalizationBatch(3, shareRate(1)) + await withdrawalQueueERC721.finalize(3, { from: deployer, value: batch.ethToLock }) const recipientETHBefore = await hre.ethers.provider.getBalance(recipient) - const tx = await withdrawalRequestNFT.methods['claimWithdrawal(uint256)'](nftHolderStETHTokenIds[0], { + const tx = await withdrawalQueueERC721.methods['claimWithdrawal(uint256)'](nftHolderStETHTokenIds[0], { from: recipient }) const recipientETHAfter = await hre.ethers.provider.getBalance(recipient) @@ -291,11 +303,26 @@ hre.contract( }) it("doesn't reverts when transfer to contract that not implements IERC721Receiver interface", async () => { - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderWstETHTokenIds[0]), nftHolderWstETH) - await withdrawalRequestNFT.transferFrom(nftHolderWstETH, stETH.address, nftHolderWstETHTokenIds[0], { + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderWstETHTokenIds[0]), nftHolderWstETH) + await withdrawalQueueERC721.transferFrom(nftHolderWstETH, stETH.address, nftHolderWstETHTokenIds[0], { from: nftHolderWstETH }) - assert.equal(await withdrawalRequestNFT.ownerOf(nftHolderWstETHTokenIds[0]), stETH.address) + assert.equal(await withdrawalQueueERC721.ownerOf(nftHolderWstETHTokenIds[0]), stETH.address) + }) + }) + + describe('Burn', () => { + it('balanceOf decreases after claim', async () => { + const balanceBefore = await withdrawalQueueERC721.balanceOf(nftHolderStETH); + + const batch = await withdrawalQueueERC721.finalizationBatch(3, shareRate(1)) + await withdrawalQueueERC721.finalize(3, { from: deployer, value: batch.ethToLock }) + + await withdrawalQueueERC721.methods['claimWithdrawal(uint256)'](nftHolderStETHTokenIds[0], { + from: nftHolderStETH + }) + + assert.equals(balanceBefore - await withdrawalQueueERC721.balanceOf(nftHolderStETH), 1) }) }) } diff --git a/test/common/lib/mem-utils.test.js b/test/common/lib/mem-utils.test.js deleted file mode 100644 index 2faf066ff..000000000 --- a/test/common/lib/mem-utils.test.js +++ /dev/null @@ -1,115 +0,0 @@ -const { assert } = require('../../helpers/assert') -const { printEvents } = require('../../helpers/utils') - -const MemUtilsTest = artifacts.require('MemUtilsTest') - - -contract('MemUtils', () => { - let test - - before(async () => { - test = await MemUtilsTest.new() - }) - - context('unsafeAllocateBytes', () => { - - it('allocates empty byte array', async () => { - await test.unsafeAlloc_allocates_empty_byte_array() - }) - - it('allocates memory and advances free mem pointer', async () => { - await test.unsafeAlloc_allocates_memory_and_advances_free_mem_pointer() - }) - - it('pads free mem pointer to 32 bytes', async () => { - await test.unsafeAlloc_pads_free_mem_pointer_to_32_bytes() - }) - - it('handles misaligned free mem pointer and pads it to 32 bytes', async () => { - await test.unsafeAlloc_handles_misaligned_free_mem_pointer_and_pads_to_32_bytes() - }) - }) - - context('memcpy', () => { - - it('copies mem chunks that are multiples of 32 bytes', async () => { - await test.memcpy_copies_mem_chunks_that_are_multiples_of_32_bytes() - }) - - it('copies mem chunks that are multiples of 32 bytes from a non-32 byte offset', async () => { - await test.memcpy_copies_mem_chunks_that_are_multiples_of_32_bytes_from_a_non_32b_offset() - }) - - it('copies mem chunks that are multiples of 32 bytes to a non-32 byte offset', async () => { - await test.memcpy_copies_mem_chunks_that_are_multiples_of_32b_to_a_non_32b_offset() - }) - - it('copies mem chunks that are multiples of 32 bytes from and to a non-32 byte offset', async () => { - await test.memcpy_copies_mem_chunks_that_are_multiples_of_32_bytes_from_and_to_a_non_32b_offset() - }) - - it('copies mem chunks that are not multiples of 32 bytes', async () => { - await test.memcpy_copies_mem_chunks_that_are_not_multiples_of_32_bytes() - }) - - it('copies mem chunks that are not multiples of 32 bytes from a non-32 byte offset', async () => { - await test.memcpy_copies_mem_chunks_that_are_not_multiples_of_32_bytes_from_a_non_32b_offset() - }) - - it('copies mem chunks that are not multiples of 32 bytes to a non-32 byte offset', async () => { - await test.memcpy_copies_mem_chunks_that_are_not_multiples_of_32_bytes_to_a_non_32b_offset() - }) - - it('copies mem chunks that are not multiples of 32 bytes from and to a non-32 byte offset', async () => { - await test.memcpy_copies_mem_chunks_that_are_not_multiples_of_32_bytes_from_and_to_a_non_32b_offset() - }) - - it('copies mem chunks shorter than 32 bytes', async () => { - await test.memcpy_copies_mem_chunks_shorter_than_32_bytes() - }) - - it('copies mem chunks shorter than 32 bytes from a non-32 byte offset', async () => { - await test.memcpy_copies_mem_chunks_shorter_than_32_bytes_from_a_non_32b_offset() - }) - - it('copies mem chunks shorter than 32 bytes to a non-32 byte offset', async () => { - await test.memcpy_copies_mem_chunks_shorter_than_32_bytes_to_a_non_32b_offset() - }) - - it('copies mem chunks shorter than 32 bytes from and to a non-32 byte offset', async () => { - await test.memcpy_copies_mem_chunks_shorter_than_32_bytes_from_and_to_a_non_32b_offset() - }) - - it('zero length is handled correctly', async () => { - await test.memcpy_zero_length_is_handled_correctly() - }) - }) - - context('keccakUint256Array', () => { - - it('calculates a keccak256 over a uint256 array', async () => { - await test.keccakUint256Array_calcs_keccak_over_a_uint_array() - }) - - it('calculates a keccak256 over an empty uint256 array', async () => { - await test.keccakUint256Array_calcs_keccak_over_an_empty_array() - }) - }) - - context('trimUint256Array', () => { - - it('decreases length of a uint256 array', async () => { - await test.trimUint256Array_decreases_length_of_a_uint_array() - }) - - it('allows trimming to a zero length', async () => { - await test.trimUint256Array_allows_trimming_to_zero_length() - }) - - it('reverts on trying to trim by more than the array length', async () => { - await assert.reverts( - test.trimUint256Array_reverts_on_trying_to_trim_by_more_than_length() - ) - }) - }) -}) diff --git a/contracts/common/test_helpers/MemUtilsTest.sol b/test/common/lib/mem-utils.test.sol similarity index 89% rename from contracts/common/test_helpers/MemUtilsTest.sol rename to test/common/lib/mem-utils.test.sol index ff977b755..cb4e462f4 100644 --- a/contracts/common/test_helpers/MemUtilsTest.sol +++ b/test/common/lib/mem-utils.test.sol @@ -1,16 +1,15 @@ // SPDX-FileCopyrightText: 2023 Lido // SPDX-License-Identifier: GPL-3.0 -/* See contracts/COMPILERS.md */ pragma solidity 0.8.9; +import "forge-std/Test.sol"; -import { MemUtils } from "../lib/MemUtils.sol"; +import { MemUtils } from "contracts/common/lib/MemUtils.sol"; +import "contracts/common/test_helpers/Assertions.sol"; -import "./Assertions.sol"; - -contract MemUtilsTest { +contract MemUtilsTest is Test { function getDataPtr(bytes memory arr) internal pure returns (uint256 dataPtr) { assembly { dataPtr := add(arr, 32) @@ -28,7 +27,7 @@ contract MemUtilsTest { /// unsafeAllocateBytes /// - function unsafeAlloc_allocates_empty_byte_array() external pure { + function test_unsafeAlloc_allocates_empty_byte_array() external pure { // disable all compiler optimizations by including an assembly block not marked as mem-safe assembly { mstore(0x00, 0x1) @@ -47,7 +46,7 @@ contract MemUtilsTest { Assert.equal(freeMemPtr, preAllocFreeMemPtr + 32); } - function unsafeAlloc_allocates_memory_and_advances_free_mem_pointer() external pure { + function test_unsafeAlloc_allocates_memory_and_advances_free_mem_pointer() external pure { // disable all compiler optimizations by including an assembly block not marked as mem-safe assembly { mstore(0x00, 0x1) @@ -115,7 +114,7 @@ contract MemUtilsTest { )); } - function unsafeAlloc_pads_free_mem_pointer_to_32_bytes() external pure { + function test_unsafeAlloc_pads_free_mem_pointer_to_32_bytes() external pure { // disable all compiler optimizations by including an assembly block not marked as mem-safe assembly { mstore(0x00, 0x1) @@ -166,7 +165,7 @@ contract MemUtilsTest { Assert.equal(freeMemPtr, preAllocFreeMemPtr + 32 + 32 * 101); } - function unsafeAlloc_handles_misaligned_free_mem_pointer_and_pads_to_32_bytes() external pure { + function test_unsafeAlloc_handles_misaligned_free_mem_pointer_and_pads_to_32_bytes() external pure { uint256 freeMemPtr = getFreeMemPtr(); // assert free mem pointer is 32-byte aligned initially @@ -217,7 +216,7 @@ contract MemUtilsTest { /// memcpy /// - function memcpy_copies_mem_chunks_that_are_multiples_of_32_bytes() external pure { + function test_memcpy_copies_mem_chunks_that_are_multiples_of_32_bytes() external pure { bytes memory src = abi.encodePacked( bytes32(0x1111111111111111111111111111111111111111111111111111111111111111), bytes32(0x2222222222222222222222222222222222222222222222222222222222222222) @@ -238,7 +237,7 @@ contract MemUtilsTest { )); } - function memcpy_copies_mem_chunks_that_are_multiples_of_32_bytes_from_a_non_32b_offset() external pure { + function test_memcpy_copies_mem_chunks_that_are_multiples_of_32_bytes_from_a_non_32b_offset() external pure { bytes memory src = abi.encodePacked( bytes32(0x1111111111111111111111111111111111111111111111111111111111111111), bytes32(0x2222222222222222222222222222222222222222222222222222222222222222), @@ -260,7 +259,7 @@ contract MemUtilsTest { )); } - function memcpy_copies_mem_chunks_that_are_multiples_of_32b_to_a_non_32b_offset() external pure { + function test_memcpy_copies_mem_chunks_that_are_multiples_of_32b_to_a_non_32b_offset() external pure { bytes memory src = abi.encodePacked( bytes32(0x1111111111111111111111111111111111111111111111111111111111111111), bytes32(0x2222222222222222222222222222222222222222222222222222222222222222) @@ -281,7 +280,7 @@ contract MemUtilsTest { )); } - function memcpy_copies_mem_chunks_that_are_multiples_of_32_bytes_from_and_to_a_non_32b_offset() external pure { + function test_memcpy_copies_mem_chunks_that_are_multiples_of_32_bytes_from_and_to_a_non_32b_offset() external pure { bytes memory src = abi.encodePacked( bytes32(0x1111111111111111111111111111111111111111111111111111111111111111), bytes32(0x2222222222222222222222222222222222222222222222222222222222222222), @@ -303,7 +302,7 @@ contract MemUtilsTest { )); } - function memcpy_copies_mem_chunks_that_are_not_multiples_of_32_bytes() external pure { + function test_memcpy_copies_mem_chunks_that_are_not_multiples_of_32_bytes() external pure { bytes memory src = abi.encodePacked( bytes32(0x1111111111111111111111111111111111111111111111111111111111111111), bytes32(0x2222222222222222222222222222222222222222222222222222222222222222) @@ -322,7 +321,7 @@ contract MemUtilsTest { )); } - function memcpy_copies_mem_chunks_that_are_not_multiples_of_32_bytes_from_a_non_32b_offset() external pure { + function test_memcpy_copies_mem_chunks_that_are_not_multiples_of_32_bytes_from_a_non_32b_offset() external pure { bytes memory src = abi.encodePacked( bytes32(0x1111111111111111111111111111111111111111111111111111111111111111), bytes32(0x2222222222222222222222222222222222222222222222222222222222222222) @@ -341,7 +340,7 @@ contract MemUtilsTest { )); } - function memcpy_copies_mem_chunks_that_are_not_multiples_of_32_bytes_to_a_non_32b_offset() external pure { + function test_memcpy_copies_mem_chunks_that_are_not_multiples_of_32_bytes_to_a_non_32b_offset() external pure { bytes memory src = abi.encodePacked( bytes32(0x1111111111111111111111111111111111111111111111111111111111111111), bytes32(0x2222222222222222222222222222222222222222222222222222222222222222) @@ -360,7 +359,7 @@ contract MemUtilsTest { )); } - function memcpy_copies_mem_chunks_that_are_not_multiples_of_32_bytes_from_and_to_a_non_32b_offset() external pure { + function test_memcpy_copies_mem_chunks_that_are_not_multiples_of_32_bytes_from_and_to_a_non_32b_offset() external pure { bytes memory src = abi.encodePacked( bytes32(0x1111111111111111111111111111111111111111111111111111111111111111), bytes32(0x2222222222222222222222222222222222222222222222222222222222222222) @@ -379,7 +378,7 @@ contract MemUtilsTest { )); } - function memcpy_copies_mem_chunks_shorter_than_32_bytes() external pure { + function test_memcpy_copies_mem_chunks_shorter_than_32_bytes() external pure { bytes memory src = abi.encodePacked( bytes32(0x1111111111111111111111111111111111111111111111111111111111111111) ); @@ -395,7 +394,7 @@ contract MemUtilsTest { )); } - function memcpy_copies_mem_chunks_shorter_than_32_bytes_from_a_non_32b_offset() external pure { + function test_memcpy_copies_mem_chunks_shorter_than_32_bytes_from_a_non_32b_offset() external pure { bytes memory src = abi.encodePacked( bytes32(0xcccccccccccccccccccccccccccccccccc8badf00d1234eeeeeeeeeeeeeeeeee) ); @@ -411,7 +410,7 @@ contract MemUtilsTest { )); } - function memcpy_copies_mem_chunks_shorter_than_32_bytes_to_a_non_32b_offset() external pure { + function test_memcpy_copies_mem_chunks_shorter_than_32_bytes_to_a_non_32b_offset() external pure { bytes memory src = abi.encodePacked( bytes32(0x1111111111111111111111111111111111111111111111111111111111111111) ); @@ -427,7 +426,7 @@ contract MemUtilsTest { )); } - function memcpy_copies_mem_chunks_shorter_than_32_bytes_from_and_to_a_non_32b_offset() external pure { + function test_memcpy_copies_mem_chunks_shorter_than_32_bytes_from_and_to_a_non_32b_offset() external pure { bytes memory src = abi.encodePacked( bytes32(0xcccccccccccccccccccccccccccccccccc8badf00d1234eeeeeeeeeeeeeeeeee) ); @@ -443,7 +442,7 @@ contract MemUtilsTest { )); } - function memcpy_zero_length_is_handled_correctly() external pure { + function test_memcpy_zero_length_is_handled_correctly() external pure { bytes memory src = abi.encodePacked( bytes32(0x1111111111111111111111111111111111111111111111111111111111111111) ); @@ -463,7 +462,7 @@ contract MemUtilsTest { /// keccakUint256Array /// - function keccakUint256Array_calcs_keccak_over_a_uint_array() external pure { + function test_keccakUint256Array_calcs_keccak_over_a_uint_array() external pure { uint256[] memory array = new uint256[](5); array[0] = uint256(0x1111111111111111111111111111111111111111111111111111111111111111); array[1] = uint256(0x2222222222222222222222222222222222222222222222222222222222222222); @@ -477,7 +476,7 @@ contract MemUtilsTest { Assert.equal(actual, expected); } - function keccakUint256Array_calcs_keccak_over_an_empty_array() external pure { + function test_keccakUint256Array_calcs_keccak_over_an_empty_array() external pure { uint256[] memory array = new uint256[](0); bytes32 expected = keccak256(abi.encodePacked(array)); @@ -490,7 +489,7 @@ contract MemUtilsTest { /// trimUint256Array /// - function trimUint256Array_decreases_length_of_a_uint_array() external pure { + function test_trimUint256Array_decreases_length_of_a_uint_array() external pure { uint256[] memory array = new uint256[](5); array[0] = uint256(0x1111111111111111111111111111111111111111111111111111111111111111); array[1] = uint256(0x2222222222222222222222222222222222222222222222222222222222222222); @@ -512,7 +511,7 @@ contract MemUtilsTest { )); } - function trimUint256Array_allows_trimming_to_zero_length() external pure { + function test_trimUint256Array_allows_trimming_to_zero_length() external pure { uint256[] memory array = new uint256[](3); array[0] = uint256(0x1111111111111111111111111111111111111111111111111111111111111111); array[1] = uint256(0x2222222222222222222222222222222222222222222222222222222222222222); @@ -523,13 +522,13 @@ contract MemUtilsTest { Assert.empty(array); } - function trimUint256Array_reverts_on_trying_to_trim_by_more_than_length() external pure { + function test_trimUint256Array_reverts_on_trying_to_trim_by_more_than_length() external { uint256[] memory array = new uint256[](3); array[0] = uint256(0x1111111111111111111111111111111111111111111111111111111111111111); array[1] = uint256(0x2222222222222222222222222222222222222222222222222222222222222222); array[2] = uint256(0x3333333333333333333333333333333333333333333333333333333333333333); + vm.expectRevert(); MemUtils.trimUint256Array(array, 4); - revert Assert.RevertExpected(); } } diff --git a/test/common/lib/min-first-allocation-strategy.0.4.24.test.sol b/test/common/lib/min-first-allocation-strategy.0.4.24.test.sol new file mode 100644 index 000000000..b59740113 --- /dev/null +++ b/test/common/lib/min-first-allocation-strategy.0.4.24.test.sol @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.4.24; + +import { MinFirstAllocationStrategyFuzzTesting, MinFirstAllocationStrategyAllocateTestWrapper} from "./min-first-allocation-strategy.helpers.sol"; +import {MinFirstAllocationStrategy} from "contracts/common/lib/MinFirstAllocationStrategy.sol"; + +/// @dev this contract is required to make Foundry invariants testing work +contract MinFirstAllocationStrategyAllocateTestWrapper_0_4_24 is MinFirstAllocationStrategyAllocateTestWrapper {} + +contract MinFirstAllocationStrategyFuzzTesting_0_4_24 is MinFirstAllocationStrategyFuzzTesting { + function setUp() external { + testWrapper = new MinFirstAllocationStrategyAllocateTestWrapper_0_4_24(); + } +} diff --git a/test/common/lib/min-first-allocation-strategy.0.8.9.test.sol b/test/common/lib/min-first-allocation-strategy.0.8.9.test.sol new file mode 100644 index 000000000..cdafa0c26 --- /dev/null +++ b/test/common/lib/min-first-allocation-strategy.0.8.9.test.sol @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.9; + +import { MinFirstAllocationStrategyFuzzTesting, MinFirstAllocationStrategyAllocateTestWrapper} from "./min-first-allocation-strategy.helpers.sol"; +import {MinFirstAllocationStrategy} from "contracts/common/lib/MinFirstAllocationStrategy.sol"; + +/// @dev this contract is required to make Foundry invariants testing work +contract MinFirstAllocationStrategyAllocateTestWrapper_0_8_9 is MinFirstAllocationStrategyAllocateTestWrapper {} + +contract MinFirstAllocationStrategyFuzzTesting_0_8_9 is MinFirstAllocationStrategyFuzzTesting { + function setUp() external { + testWrapper = new MinFirstAllocationStrategyAllocateTestWrapper_0_8_9(); + } +} diff --git a/test/common/lib/min-first-allocation-strategy.helpers.sol b/test/common/lib/min-first-allocation-strategy.helpers.sol new file mode 100644 index 000000000..e10748a9f --- /dev/null +++ b/test/common/lib/min-first-allocation-strategy.helpers.sol @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +// solhint-disable-next-line +pragma solidity >=0.4.24 <0.9.0; + +import {console2} from "forge-std/console2.sol"; + +import {Math256} from "contracts/common/lib/Math256.sol"; +import {MinFirstAllocationStrategy} from "contracts/common/lib/MinFirstAllocationStrategy.sol"; + +contract MinFirstAllocationStrategyFuzzTesting { + uint256 private constant MAX_BUCKETS_COUNT = 32; + uint256 private constant MAX_BUCKET_VALUE = 8192; + uint256 private constant MAX_CAPACITY_VALUE = 8192; + uint256 private constant MAX_ALLOCATION_SIZE = 1024; + + MinFirstAllocationStrategyTestWrapper internal testWrapper; + + function invariant_allocated_output() external view { + (, , uint256 allocatedActual) = testWrapper.getActualOutput(); + (, , uint256 allocatedExpected) = testWrapper.getExpectedOutput(); + _assertAllocated(allocatedExpected, allocatedActual); + } + + function invariant_buckets_output() external view { + (uint256[] memory bucketsActual, , ) = testWrapper.getActualOutput(); + (uint256[] memory bucketsExpected, , ) = testWrapper.getExpectedOutput(); + _assertBucketsAllocation(bucketsExpected, bucketsActual); + } + + function invariant_allocated_bucket_values_not_exceed_capacities() external view { + (uint256[] memory inputBuckets, uint256[] memory inputCapacities, ) = testWrapper.getInput(); + (uint256[] memory buckets, uint256[] memory capacities, ) = testWrapper.getActualOutput(); + for (uint256 i = 0; i < buckets.length; ++i) { + // when bucket initially overloaded skip it from the check + if (inputBuckets[i] > inputCapacities[i]) continue; + if (buckets[i] > capacities[i]) { + console2.log("Bucket value exceeds capacity"); + console2.log("bucket index: ", i); + console2.log("bucket value:", buckets[i]); + console2.log("capacity value:", capacities[i]); + revert("BUCKET_VALUE_EXCEEDS_CAPACITY"); + } + } + } + + // invariant 5. the sum of new allocation minus the sum of prev allocation equal to distributed + function invariant_allocated_matches_bucket_changes() external view { + (uint256[] memory inputBuckets, , ) = testWrapper.getInput(); + (uint256[] memory buckets, , uint256 allocated) = testWrapper.getActualOutput(); + uint256 inputSum = 0; + uint256 outputSum = 0; + for (uint256 i = 0; i < buckets.length; ++i) { + inputSum += inputBuckets[i]; + outputSum += buckets[i]; + } + if (outputSum != inputSum + allocated) { + console2.log("Sum of all buckets is incorrect"); + console2.log("expected buckets sum:", inputSum + allocated); + console2.log("actual buckets sum:", outputSum); + revert("INVALID_BUCKETS_SUM"); + } + } + + function invariant_allocated_less_then_allocation_size_only_when_all_buckets_filled() external view { + (, , uint256 allocationSize) = testWrapper.getInput(); + (uint256[] memory buckets, uint256[] memory capacities , uint256 allocated) = testWrapper.getActualOutput(); + if (allocationSize == allocated) return; + for (uint256 i = 0; i < buckets.length; ++i) { + if (buckets[i] < capacities[i]) { + console2.log("The bucket is unfilled"); + console2.log("bucket index:", i); + console2.log("bucket value:", buckets[i]); + console2.log("bucket capacity:", capacities[i]); + revert("BUCKET_IS_UNFILLED"); + } + } + } + + function _assertAllocated(uint256 _expected, uint256 _actual) internal view { + if (_expected != _actual) { + console2.log("Invalid allocated value"); + console2.log("expected allocated value: ", _expected); + console2.log("actual allocated value:", _actual); + revert("INVALID_ALLOCATED_VALUE"); + } + } + + function _assertBucketsAllocation(uint256[] memory _expected, uint256[] memory _actual) internal view { + for (uint256 i = 0; i < _expected.length; ++i) { + if (_expected[i] != _actual[i]) { + console2.log("Invalid bucket value after allocation:"); + console2.log("bucket index:", i); + console2.log("expected bucket value:", _expected[i]); + console2.log("actual bucket value:", _actual[i]); + revert("INVALID_ALLOCATED_VALUE"); + } + } + } +} + +contract MinFirstAllocationStrategyTestWrapper { + uint256 public constant MAX_BUCKETS_COUNT = 32; + uint256 public constant MAX_BUCKET_VALUE = 8192; + uint256 public constant MAX_CAPACITY_VALUE = 8192; + uint256 public constant MAX_ALLOCATION_SIZE = 1024; + + struct TestInput { + uint256[] buckets; + uint256[] capacities; + uint256 allocationSize; + } + + struct TestOutput { + uint256[] buckets; + uint256[] capacities; + uint256 allocated; + } + + TestInput internal _input; + TestOutput internal _actual; + TestOutput internal _expected; + + function getInput() + external + view + returns ( + uint256[] memory buckets, + uint256[] memory capacities, + uint256 allocationSize + ) + { + buckets = _input.buckets; + capacities = _input.capacities; + allocationSize = _input.allocationSize; + } + + function getExpectedOutput() + external + view + returns ( + uint256[] memory buckets, + uint256[] memory capacities, + uint256 allocated + ) + { + buckets = _expected.buckets; + capacities = _expected.capacities; + allocated = _expected.allocated; + } + + function getActualOutput() + external + view + returns ( + uint256[] memory buckets, + uint256[] memory capacities, + uint256 allocated + ) + { + buckets = _actual.buckets; + capacities = _actual.capacities; + allocated = _actual.allocated; + } + + function _fillTestInput( + uint256[] memory _fuzzBuckets, + uint256[] memory _fuzzCapacities, + uint256 _fuzzAllocationSize + ) internal { + uint256 bucketsCount = Math256.min(_fuzzBuckets.length, _fuzzCapacities.length) % MAX_BUCKETS_COUNT; + _input.buckets = new uint256[](bucketsCount); + _input.capacities = new uint256[](bucketsCount); + for (uint256 i = 0; i < bucketsCount; ++i) { + _input.buckets[i] = _fuzzBuckets[i] % MAX_BUCKET_VALUE; + _input.capacities[i] = _fuzzCapacities[i] % MAX_CAPACITY_VALUE; + } + _input.allocationSize = _fuzzAllocationSize % MAX_ALLOCATION_SIZE; + } +} + +contract MinFirstAllocationStrategyAllocateTestWrapper is MinFirstAllocationStrategyTestWrapper { + function allocate( + uint256[] memory _fuzzBuckets, + uint256[] memory _fuzzCapacities, + uint256 _fuzzAllocationSize + ) public { + _fillTestInput(_fuzzBuckets, _fuzzCapacities, _fuzzAllocationSize); + + _fillActualAllocateOutput(); + _fillExpectedAllocateOutput(); + } + + function _fillExpectedAllocateOutput() internal { + uint256[] memory buckets = _input.buckets; + uint256[] memory capacities = _input.capacities; + uint256 allocationSize = _input.allocationSize; + + uint256 allocated = NaiveMinFirstAllocationStrategy.allocate(buckets, capacities, allocationSize); + + _expected.allocated = allocated; + _expected.buckets = buckets; + _expected.capacities = capacities; + } + + function _fillActualAllocateOutput() internal { + uint256[] memory buckets = _input.buckets; + uint256[] memory capacities = _input.capacities; + uint256 allocationSize = _input.allocationSize; + + uint256 allocated = MinFirstAllocationStrategy.allocate(buckets, capacities, allocationSize); + + _actual.allocated = allocated; + _actual.buckets = buckets; + _actual.capacities = capacities; + } +} + +library NaiveMinFirstAllocationStrategy { + uint256 private constant MAX_UINT256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + + function allocate( + uint256[] memory buckets, + uint256[] memory capacities, + uint256 allocationSize + ) internal pure returns (uint256 allocated) { + while (allocated < allocationSize) { + uint256 bestCandidateIndex = MAX_UINT256; + uint256 bestCandidateAllocation = MAX_UINT256; + for (uint256 i = 0; i < buckets.length; ++i) { + if (buckets[i] >= capacities[i]) continue; + if (buckets[i] < bestCandidateAllocation) { + bestCandidateAllocation = buckets[i]; + bestCandidateIndex = i; + } + } + if (bestCandidateIndex == MAX_UINT256) break; + buckets[bestCandidateIndex] += 1; + allocated += 1; + } + } +} diff --git a/test/deposit.test.js b/test/deposit.test.js index c5bbbc078..f9906c3cb 100644 --- a/test/deposit.test.js +++ b/test/deposit.test.js @@ -6,7 +6,7 @@ const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') const { EvmSnapshot } = require('./helpers/blockchain') const { setupNodeOperatorsRegistry } = require('./helpers/staking-modules') const { deployProtocol } = require('./helpers/protocol') -const { ETH, pad, hexConcat, changeEndianness } = require('./helpers/utils') +const { ETH, pad, hexConcat, changeEndianness, prepIdsCountsPayload } = require('./helpers/utils') const nodeOperators = require('./helpers/node-operators') const { assert } = require('./helpers/assert') const { depositContractFactory } = require('./helpers/factories') @@ -53,7 +53,7 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d }, depositContractFactory: depositContractFactory, postSetup: async ({ pool, lidoLocator, eip712StETH, withdrawalQueue, appManager, voting }) => { - await pool.initialize(lidoLocator.address, eip712StETH.address) + await pool.initialize(lidoLocator.address, eip712StETH.address, { value: ETH(1) }) await withdrawalQueue.updateBunkerMode(false, 0, { from: appManager.address }) await pool.resumeProtocolAndStaking({ from: voting.address }) } @@ -102,31 +102,31 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 0, beaconBalance: 0 }) - assertBn(await app.getTotalPooledEther(), ETH(1)) - assertBn(await app.getBufferedEther(), ETH(1)) + assertBn(await app.getTotalPooledEther(), ETH(2)) + assertBn(await app.getBufferedEther(), ETH(2)) assertBn(await token.balanceOf(user1), tokens(1)) - assertBn(await token.totalSupply(), tokens(1)) + assertBn(await token.totalSupply(), tokens(2)) // +2 ETH await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) // another form of a deposit call await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 0, beaconBalance: 0 }) assertBn(bn(await depositContract.get_deposit_count()), 0) - assertBn(await app.getTotalPooledEther(), ETH(3)) - assertBn(await app.getBufferedEther(), ETH(3)) + assertBn(await app.getTotalPooledEther(), ETH(4)) + assertBn(await app.getBufferedEther(), ETH(4)) assertBn(await token.balanceOf(user2), tokens(2)) - assertBn(await token.totalSupply(), tokens(3)) + assertBn(await token.totalSupply(), tokens(4)) // +30 ETH await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(30) }) await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 1, beaconBalance: 0 }) - assertBn(await app.getTotalPooledEther(), ETH(33)) - assertBn(await app.getBufferedEther(), ETH(1)) + assertBn(await app.getTotalPooledEther(), ETH(34)) + assertBn(await app.getBufferedEther(), ETH(2)) assertBn(await token.balanceOf(user1), tokens(1)) assertBn(await token.balanceOf(user2), tokens(2)) assertBn(await token.balanceOf(user3), tokens(30)) - assertBn(await token.totalSupply(), tokens(33)) + assertBn(await token.totalSupply(), tokens(34)) assertBn(bn(changeEndianness(await depositContract.get_deposit_count())), 1) @@ -135,12 +135,12 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 4, beaconBalance: 0 }) - assertBn(await app.getTotalPooledEther(), ETH(133)) - assertBn(await app.getBufferedEther(), ETH(5)) + assertBn(await app.getTotalPooledEther(), ETH(134)) + assertBn(await app.getBufferedEther(), ETH(6)) assertBn(await token.balanceOf(user1), tokens(101)) assertBn(await token.balanceOf(user2), tokens(2)) assertBn(await token.balanceOf(user3), tokens(30)) - assertBn(await token.totalSupply(), tokens(133)) + assertBn(await token.totalSupply(), tokens(134)) assertBn(bn(changeEndianness(await depositContract.get_deposit_count())), 4) }) @@ -170,7 +170,7 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) - await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(33) }) + await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(32) }) await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) assertBn(bn(changeEndianness(await depositContract.get_deposit_count())), 1) @@ -217,7 +217,7 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d await nodeOperators.addNodeOperator(operators, { name: 'short on keys', rewardAddress: ADDRESS_4 }, { from: voting }) // Deposit huge chunk - await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(32 * 3 + 50) }) + await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(32 * 3 + 50 - 1/* initial */) }) tx = await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) // assertEvent(tx, 'StakingRouterTransferReceived') @@ -260,7 +260,8 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d assertBn(await operators.getUnusedSigningKeyCount(3, { from: nobody }), 0) // #1 goes below the limit (nothing changed cause staking limit decreases) - await operators.updateExitedValidatorsCount(1, 1, { from: voting }) + const { operatorIds, keysCounts } = prepIdsCountsPayload(1, 1) + await operators.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }) await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(1) }) await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) @@ -281,7 +282,7 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d // Adding a key & setting staking limit will help await operators.addSigningKeys(0, 1, pad('0x0003', 48), pad('0x01', 96), { from: voting }) - operators.setNodeOperatorStakingLimit(0, 3, { from: voting }) + await operators.setNodeOperatorStakingLimit(0, 3, { from: voting }) await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(1) }) await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) @@ -353,7 +354,7 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d for (let i = 0; i < 14; i++) await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(10) }) await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) - await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(6) }) + await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(5) }) await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 3, beaconBalance: 0 }) @@ -391,7 +392,8 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d assertBn(await operators.getUnusedSigningKeyCount(3, { from: nobody }), 0) // #1 goes below the limit (nothing changed cause staking limit decreases) - await operators.updateExitedValidatorsCount(1, 1, { from: voting }) + const { operatorIds, keysCounts } = prepIdsCountsPayload(1, 1) + await operators.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }) await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(1) }) await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) @@ -412,7 +414,7 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d // Adding a key & setting staking limit will help await operators.addSigningKeys(0, 1, pad('0x0003', 48), pad('0x01', 96), { from: voting }) - operators.setNodeOperatorStakingLimit(0, 3, { from: voting }) + await operators.setNodeOperatorStakingLimit(0, 3, { from: voting }) await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(1) }) await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) @@ -479,7 +481,7 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d await operators.setNodeOperatorStakingLimit(3, UNLIMITED, { from: voting }) // #1 and #0 get the funds - await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(64) }) + await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(63) }) await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) await checkStat({ depositedValidators: 2, beaconBalance: 0 }) @@ -537,7 +539,7 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) - await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(amountToDeposit) }) + await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(amountToDeposit - 1) }) await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor, gas: 20000000 }) @@ -571,7 +573,7 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d // Fix deposit tx price await web3.eth.sendTransaction({ to: user1, from: user2, value: ETH(2000) }) - await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(amountToDeposit) }) + await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(amountToDeposit - 1 /* initial */) }) await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor, gas: 20000000 }) diff --git a/test/helpers/assert.js b/test/helpers/assert.js index 64f5bbabe..b48d07fad 100644 --- a/test/helpers/assert.js +++ b/test/helpers/assert.js @@ -44,8 +44,8 @@ chai.util.addMethod(chai.assert, 'notEmits', function (receipt, eventName, args this.isUndefined(event, `Expected that event "${eventName}" with args ${args} wouldn't be emitted, but it was.`) }) -chai.util.addMethod(chai.assert, 'reverts', async function (receipt, reason) { - await assertRevert(receipt, reason) +chai.util.addMethod(chai.assert, 'reverts', async function (receipt, reason, customErrorArgs) { + await assertRevert(receipt, customErrorArgs !== undefined ? `${reason}(${customErrorArgs.join(', ')})` : reason) }) chai.util.addMethod(chai.assert, 'equals', function (actual, expected, errorMsg) { @@ -53,7 +53,12 @@ chai.util.addMethod(chai.assert, 'equals', function (actual, expected, errorMsg) }) chai.util.addMethod(chai.assert, 'equalsDelta', function (actual, expected, delta, errorMsg) { - this.isAtMost(+toBN(actual.toString()).sub(toBN(expected.toString())).abs().toString(), delta, errorMsg) + const diff = toBN(actual).sub(toBN(expected)).abs() + chai.assert( + diff.lte(toBN(delta)), + () => `${errorMsg ? `${errorMsg}: ` : ''}Expected ${actual} to be close to ${expected} with max diff ${delta}, actual diff ${diff}`, + () => `${errorMsg ? `${errorMsg}: ` : ''}Expected ${actual} not to be close to ${expected} with min diff ${delta}, actual diff ${diff}`, + ) }) chai.util.addMethod(chai.assert, 'notEquals', function (actual, expected, errorMsg) { diff --git a/test/helpers/blockchain.js b/test/helpers/blockchain.js index baf5ae570..c6ce77081 100644 --- a/test/helpers/blockchain.js +++ b/test/helpers/blockchain.js @@ -1,12 +1,26 @@ +const hre = require('hardhat') +const { wei } = require('./wei') + async function waitBlocks(numBlocksToMine) { let block for (let i = 0; i < numBlocksToMine; ++i) { - await network.provider.send('evm_mine') + await hre.network.provider.send('evm_mine') block = await web3.eth.getBlock('latest') } return block } +async function advanceChainTime(seconds) { + await hre.network.provider.send('evm_increaseTime', [seconds]) + await hre.network.provider.send('evm_mine') +} + +async function getCurrentBlockTimestamp() { + const blockNum = await hre.ethers.provider.getBlockNumber() + const block = await hre.ethers.provider.getBlock(blockNum) + return block.timestamp +} + /** * Allows to make snapshot of the blockchain and revert to the previous state */ @@ -38,8 +52,23 @@ function impersonate(provider, address) { return provider.send('hardhat_impersonateAccount', [address]) } +async function getBalance(addressOrContract) { + const address = addressOrContract.address || addressOrContract + return wei.int(await hre.ethers.provider.getBalance(address)) +} + +async function setBalance(addressOrContract, value) { + const address = addressOrContract.address || addressOrContract + const hexValue = hre.web3.utils.numberToHex(wei.str(value)) + await hre.network.provider.send('hardhat_setBalance', [address, hexValue]) +} + module.exports = { EvmSnapshot, waitBlocks, - impersonate + advanceChainTime, + getCurrentBlockTimestamp, + impersonate, + getBalance, + setBalance } diff --git a/test/helpers/config.js b/test/helpers/config.js index 8167d6f8a..c13fa9692 100644 --- a/test/helpers/config.js +++ b/test/helpers/config.js @@ -23,9 +23,10 @@ const DEFAULT_DEPLOY_PARAMS = { churnValidatorsPerDayLimit: 255, oneOffCLBalanceDecreaseBPLimit: 10000, annualBalanceIncreaseBPLimit: 10000, - shareRateDeviationBPLimit: 10000, + simulatedShareRateDeviationBPLimit: 10000, maxValidatorExitRequestsPerReport: 10000, maxAccountingExtraDataListItemsCount: 100, + maxNodeOperatorsPerExtraDataItemCount: 100, requestTimestampMargin: 0, maxPositiveTokenRebase: 1000000000, }, @@ -37,6 +38,7 @@ const DEFAULT_DEPLOY_PARAMS = { shareRateDeviationLimitManagers: [], maxValidatorExitRequestsPerReportManagers: [], maxAccountingExtraDataListItemsCountManagers: [], + maxNodeOperatorsPerExtraDataItemCountManagers: [], requestTimestampMarginManagers: [], maxPositiveTokenRebaseManagers: [], } diff --git a/test/helpers/constants.js b/test/helpers/constants.js index d15d0d603..fcddbf468 100644 --- a/test/helpers/constants.js +++ b/test/helpers/constants.js @@ -1,4 +1,5 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' +const INITIAL_HOLDER = '0x000000000000000000000000000000000000dead' const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000' const MAX_UINT256 = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' @@ -12,6 +13,7 @@ const SECONDS_PER_EPOCH = SLOTS_PER_EPOCH * SECONDS_PER_SLOT const SECONDS_PER_FRAME = SECONDS_PER_EPOCH * EPOCHS_PER_FRAME const SLOTS_PER_FRAME = EPOCHS_PER_FRAME * SLOTS_PER_EPOCH + module.exports = { ZERO_ADDRESS, ZERO_BYTES32, @@ -22,5 +24,6 @@ module.exports = { SECONDS_PER_EPOCH, SECONDS_PER_FRAME, SLOTS_PER_FRAME, - CONSENSUS_VERSION + CONSENSUS_VERSION, + INITIAL_HOLDER, } diff --git a/test/helpers/dao.js b/test/helpers/dao.js index e383e9d18..27e3f0c58 100644 --- a/test/helpers/dao.js +++ b/test/helpers/dao.js @@ -81,19 +81,19 @@ class AragonDAO { async createPermission(entityAddress, app, permissionName) { const permission = await app[permissionName]() - return this.acl.createPermission(entityAddress, app.address, permission, this.appManager, { + return await this.acl.createPermission(entityAddress, app.address, permission, this.appManager, { from: this.appManager }) } async grantPermission(entityAddress, app, permissionName) { const permission = await app[permissionName]() - return this.acl.grantPermission(entityAddress, app.address, permission, { from: this.appManager }) + return await this.acl.grantPermission(entityAddress, app.address, permission, { from: this.appManager }) } async hasPermission(entity, app, permissionName) { const permission = await app[permissionName]() - return this.acl.hasPermission(entity, app.address, permission) + return await this.acl.hasPermission(entity, app.address, permission) } } @@ -137,4 +137,4 @@ module.exports = { AragonDAO, newDao, newApp -} \ No newline at end of file +} diff --git a/test/helpers/factories.js b/test/helpers/factories.js index 86f15a638..896c97b18 100644 --- a/test/helpers/factories.js +++ b/test/helpers/factories.js @@ -2,6 +2,7 @@ const withdrawals = require('./withdrawals') const { newApp } = require('./dao') const { artifacts } = require('hardhat') const { deployLocatorWithDummyAddressesImplementation } = require('./locator-deploy') +const { ETH } = require("./utils") const { SLOTS_PER_EPOCH, @@ -195,7 +196,7 @@ async function withdrawalCredentialsFactory() { return '0x'.padEnd(66, '1234') } -async function stakingRouterFactory({ depositContract, dao, appManager, voting, pool, withdrawalCredentials }) { +async function stakingRouterFactory({ depositContract, dao, appManager, voting, pool, oracle, withdrawalCredentials }) { const base = await StakingRouter.new(depositContract.address) const proxyAddress = await newApp(dao, 'lido-oracle', base.address, appManager.address) @@ -217,7 +218,10 @@ async function stakingRouterFactory({ depositContract, dao, appManager, voting, await stakingRouter.grantRole(await stakingRouter.STAKING_MODULE_MANAGE_ROLE(), voting.address, { from: appManager.address }) - await stakingRouter.grantRole(await stakingRouter.REPORT_EXITED_VALIDATORS_ROLE(), pool.address, { + await stakingRouter.grantRole(await stakingRouter.REPORT_EXITED_VALIDATORS_ROLE(), voting.address, { + from: appManager.address + }) + await stakingRouter.grantRole(await stakingRouter.REPORT_EXITED_VALIDATORS_ROLE(), oracle.address, { from: appManager.address }) await stakingRouter.grantRole(await stakingRouter.UNSAFE_SET_EXITED_VALIDATORS_ROLE(), voting.address, { @@ -280,8 +284,8 @@ async function withdrawalVaultFactory({ pool, treasury }) { return await WithdrawalVault.new(pool.address, treasury.address) } -async function eip712StETHFactory({ appManager }) { - return await EIP712StETH.new({ from: appManager.address }) +async function eip712StETHFactory({ pool, appManager }) { + return await EIP712StETH.new(pool.address, { from: appManager.address }) } async function stakingModulesFactory(_) { @@ -352,7 +356,7 @@ async function postSetup({ legacyOracle, consensusContract }) { - await pool.initialize(lidoLocator.address, eip712StETH.address) + await pool.initialize(lidoLocator.address, eip712StETH.address, { value: ETH(1) }) await legacyOracle.initialize(lidoLocator.address, consensusContract.address) diff --git a/test/helpers/locator-deploy.js b/test/helpers/locator-deploy.js index c54ff12ce..0ddd3e0ca 100644 --- a/test/helpers/locator-deploy.js +++ b/test/helpers/locator-deploy.js @@ -16,6 +16,7 @@ const invalidButNonZeroLocatorConfig = { validatorsExitBusOracle: DUMMY_ADDRESS, withdrawalQueue: DUMMY_ADDRESS, withdrawalVault: DUMMY_ADDRESS, + oracleDaemonConfig: DUMMY_ADDRESS } @@ -56,6 +57,7 @@ async function getLocatorConfig(locatorAddress) { validatorsExitBusOracle: await locator.validatorsExitBusOracle(), withdrawalQueue: await locator.withdrawalQueue(), withdrawalVault: await locator.withdrawalVault(), + oracleDaemonConfig: await locator.oracleDaemonConfig() } return config } diff --git a/test/helpers/locator.js b/test/helpers/locator.js index b4218c879..7b44d1897 100644 --- a/test/helpers/locator.js +++ b/test/helpers/locator.js @@ -14,7 +14,8 @@ const locatorServices = [ 'validatorsExitBusOracle', 'withdrawalQueue', 'withdrawalVault', - 'postTokenRebaseReceiver' + 'postTokenRebaseReceiver', + 'oracleDaemonConfig' ] function getRandomLocatorConfig(overrides = {}) { diff --git a/test/helpers/node-operators.js b/test/helpers/node-operators.js index a7c5adc80..76dd4b44b 100644 --- a/test/helpers/node-operators.js +++ b/test/helpers/node-operators.js @@ -1,7 +1,7 @@ const hre = require('hardhat') -const { assert } = require('chai') const { assertBn } = require('@aragon/contract-helpers-test/src/asserts') const { FakeValidatorKeys } = require('./signing-keys') +const { prepIdsCountsPayload } = require('./utils') /*** * Adds new Node Operator to the registry and configures it @@ -44,8 +44,8 @@ async function addNodeOperator(registry, config, txOptions) { throw new Error('Invalid keys config: depositedSigningKeysCount < exitedSigningKeysCount') } - if (exitedSigningKeysCount < stuckValidatorsCount) { - throw new Error('Invalid keys config: exitedSigningKeysCount < stuckValidatorsCount') + if (stuckValidatorsCount > depositedSigningKeysCount - exitedSigningKeysCount) { + throw new Error('Invalid keys config: stuckValidatorsCount > depositedSigningKeysCount - exitedSigningKeysCount') } if (totalSigningKeysCount < exitedSigningKeysCount + depositedSigningKeysCount) { @@ -58,20 +58,17 @@ async function addNodeOperator(registry, config, txOptions) { await registry.addSigningKeys(newOperatorId, totalSigningKeysCount, ...validatorKeys.slice(), txOptions) } - if (depositedSigningKeysCount > 0) { - await registry.increaseNodeOperatorDepositedSigningKeysCount(newOperatorId, depositedSigningKeysCount, txOptions) - } - if (vettedSigningKeysCount > 0) { await registry.setNodeOperatorStakingLimit(newOperatorId, vettedSigningKeysCount, txOptions) } - if (exitedSigningKeysCount > 0) { - await registry.updateExitedValidatorsCount(newOperatorId, exitedSigningKeysCount, txOptions) + if (depositedSigningKeysCount > 0) { + await registry.increaseNodeOperatorDepositedSigningKeysCount(newOperatorId, depositedSigningKeysCount, txOptions) } if (exitedSigningKeysCount > 0) { - await registry.updateExitedValidatorsCount(newOperatorId, exitedSigningKeysCount, txOptions) + const { operatorIds, keysCounts } = prepIdsCountsPayload(newOperatorId, exitedSigningKeysCount) + await registry.updateExitedValidatorsCount(operatorIds, keysCounts, txOptions) } if (!isActive) { diff --git a/test/helpers/oracle.js b/test/helpers/oracle.js index 45c839165..704abed4a 100644 --- a/test/helpers/oracle.js +++ b/test/helpers/oracle.js @@ -53,7 +53,7 @@ async function pushOracleReport(consensus, oracle, numValidators, clBalance, elR lastWithdrawalRequestIdToFinalize: 0, finalizationShareRate: 0, isBunkerMode: false, - extraDataFormat: 1, + extraDataFormat: 0, extraDataHash: ZERO_BYTES32, extraDataItemsCount: 0 } @@ -66,7 +66,10 @@ async function pushOracleReport(consensus, oracle, numValidators, clBalance, elR const oracleVersion = await oracle.getContractVersion() - return await oracle.submitReportData(reportItems, oracleVersion, { from: members.addresses[0] }) + const submitDataTx = await oracle.submitReportData(reportItems, oracleVersion, { from: members.addresses[0] }) + const submitExtraDataTx = await oracle.submitReportExtraDataEmpty({ from: members.addresses[0] }) + + return { submitDataTx, submitExtraDataTx } } module.exports = { getReportDataItems, calcReportDataHash, pushOracleReport } diff --git a/test/helpers/protocol.js b/test/helpers/protocol.js index 68e1c2879..2649da274 100644 --- a/test/helpers/protocol.js +++ b/test/helpers/protocol.js @@ -30,11 +30,16 @@ async function deployProtocol(factories = {}, deployParams = {}) { protocol.burner = await protocol.factories.burnerFactory(protocol) protocol.lidoLocator = await protocol.factories.lidoLocatorFactory(protocol) + await updateLocatorImplementation(protocol.lidoLocator.address, protocol.appManager.address, { lido: protocol.pool.address, burner: protocol.burner.address }) + protocol.validatorExitBus = await protocol.factories.validatorExitBusFactory(protocol) + protocol.oracleReportSanityChecker = await protocol.factories.oracleReportSanityCheckerFactory(protocol) + protocol.oracle = await protocol.factories.accountingOracleFactory(protocol) + protocol.withdrawalCredentials = await protocol.factories.withdrawalCredentialsFactory(protocol) protocol.stakingRouter = await protocol.factories.stakingRouterFactory(protocol) protocol.stakingModules = await addStakingModules(protocol.factories.stakingModulesFactory, protocol) @@ -51,14 +56,7 @@ async function deployProtocol(factories = {}, deployParams = {}) { stakingRouter: protocol.stakingRouter.address, treasury: protocol.treasury.address, withdrawalVault: protocol.withdrawalVault.address, - postTokenRebaseReceiver: protocol.legacyOracle.address - }) - - protocol.validatorExitBus = await protocol.factories.validatorExitBusFactory(protocol) - protocol.oracleReportSanityChecker = await protocol.factories.oracleReportSanityCheckerFactory(protocol) - protocol.oracle = await protocol.factories.accountingOracleFactory(protocol) - - await updateLocatorImplementation(protocol.lidoLocator.address, protocol.appManager.address, { + postTokenRebaseReceiver: protocol.legacyOracle.address, accountingOracle: protocol.oracle.address, oracleReportSanityChecker: protocol.oracleReportSanityChecker.address, validatorsExitBusOracle: protocol.validatorExitBus.address diff --git a/test/helpers/signing-keys.js b/test/helpers/signing-keys.js index ac3c45ded..dffd9a4f8 100644 --- a/test/helpers/signing-keys.js +++ b/test/helpers/signing-keys.js @@ -1,6 +1,7 @@ const PUBKEY_LENGTH = 48 const SIGNATURE_LENGTH = 96 const EMPTY_PUBLIC_KEY = '0x' + '0'.repeat(2 * PUBKEY_LENGTH) +const EMPTY_SIGNATURE = '0x' + '0'.repeat(2 * SIGNATURE_LENGTH) const { pad, hexConcat, hexSplit } = require('./utils') const { strip0x } = require('../0.6.12/helpers') @@ -39,18 +40,18 @@ class ValidatorKeys { } class FakeValidatorKeys extends ValidatorKeys { - constructor(length, seed = randomInt(10, 10 ** 9)) { + constructor(length, { seed = randomInt(10, 10 ** 9), kFill = 'f', sFill = 'e' } = {}) { super( Array(length) .fill(0) .map((_, i) => Number(seed + i).toString(16)) .map((v) => (v.length % 2 === 0 ? v : '0' + v)) // make resulting hex str length representation even(faa -> 0faa) - .map((v) => pad('0x' + v, PUBKEY_LENGTH, 'f')), + .map((v) => pad('0x' + v, PUBKEY_LENGTH, kFill)), Array(length) .fill(0) .map((_, i) => Number(seed + i).toString(16)) .map((v) => (v.length % 2 === 0 ? v : '0' + v)) // make resulting hex str length representation even(faa -> 0faa) - .map((v) => pad('0x' + v, SIGNATURE_LENGTH, 'e')) + .map((v) => pad('0x' + v, SIGNATURE_LENGTH, sFill)) ) } } @@ -71,6 +72,7 @@ module.exports = { PUBKEY_LENGTH, SIGNATURE_LENGTH, EMPTY_PUBLIC_KEY, + EMPTY_SIGNATURE, FakeValidatorKeys, splitPublicKeysBatch, splitSignaturesBatch diff --git a/test/helpers/staking-modules.js b/test/helpers/staking-modules.js index 85e08e404..a6b645266 100644 --- a/test/helpers/staking-modules.js +++ b/test/helpers/staking-modules.js @@ -2,6 +2,8 @@ const { newApp } = require('./dao') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') const NodeOperatorsRegistryMock = artifacts.require('NodeOperatorsRegistryMock') +const PENALTY_DELAY = 2 * 24 * 60 * 60 // 2 days + async function setupNodeOperatorsRegistry({ dao, acl, lidoLocator, stakingRouter, voting, appManager }, mock = false) { const nodeOperatorsRegistryBase = mock ? await NodeOperatorsRegistryMock.new() : await NodeOperatorsRegistry.new() const name = 'node-operators-registry-' + Math.random().toString(36).slice(2, 6) @@ -16,7 +18,7 @@ async function setupNodeOperatorsRegistry({ dao, acl, lidoLocator, stakingRouter ? await NodeOperatorsRegistryMock.at(nodeOperatorsRegistryProxyAddress) : await NodeOperatorsRegistry.at(nodeOperatorsRegistryProxyAddress) - await nodeOperatorsRegistry.initialize(lidoLocator.address, '0x01') + await nodeOperatorsRegistry.initialize(lidoLocator.address, '0x01', PENALTY_DELAY) const [ NODE_OPERATOR_REGISTRY_MANAGE_SIGNING_KEYS, diff --git a/test/helpers/stubs/generic.stub.js b/test/helpers/stubs/generic.stub.js new file mode 100644 index 000000000..b74a44b8c --- /dev/null +++ b/test/helpers/stubs/generic.stub.js @@ -0,0 +1,176 @@ +const hre = require('hardhat') +const { ZERO_ADDRESS } = require('../constants') + +class GenericStub { + static LOG_TYPE = Object.freeze({ + LOG0: 0, + LOG1: 1, + LOG2: 2, + LOG3: 3, + LOG4: 4 + }) + + static GenericStubContract = hre.artifacts.require('GenericStub') + + static async new(contractName) { + const stubInstance = await GenericStub.GenericStubContract.new() + const StubbedContractFactory = hre.artifacts.require(contractName) + return StubbedContractFactory.at(stubInstance.address) + } + + static async addState(stubbedContract) { + const stubInstance = await GenericStub.GenericStubContract.at(stubbedContract.address) + await stubInstance.GenericStub__addState() + } + + static async setState(stubbedContract, stateIndex) { + const stubInstance = await GenericStub.GenericStubContract.at(stubbedContract.address) + await stubInstance.GenericStub__setState(stateIndex) + } + + /** + * @typedef {object} TypedTuple - stores a info about tuple type & value + * @property {string[]} - tuple with type names + * @property {any[]} - tuple with values for types + * + * @param {object} stubbedContract instance of the GenericStub contract to add stub + * + * @param {string} methodName name of the method to stub + * + * @param {object} config stubbed method params + * @param {TypedTuple} [config.input] the input value to trigger the stub + * @param {TypedTuple} [config.return] the output value to return or revert from stub + * @param {object} [config.revert] the revert info when stub must finish with error + * @param {string} [config.revert.reason] the revert reason. Used when method reverts with string message + * @param {string} [config.revert.error] the custom error name when method must revert with custom error + * @param {TypedTuple} [config.revert.args] the arguments info for custom error + * @param {object} [config.forwardETH] amount and recipient where to send ETH + * @param {string} config.forwardETH.recipient recipient address of the ETH + * @param {object} config.forwardETH.value amount of ETH to send + * @param {number} [config.nextState] one based state index to set after stub call + * @param {object[]} [config.emit] events to emit when stub called + * @param {string} config.emit.name name of the event to emit + * @param {object} [config.emit.args] arguments of the event + * @param {string[]} [config.emit.args.type] tuple with type names + * @param {any[]} [config.emit.args.value] tuple with values for types + * @param {bool[]} [config.emit.args.indexed] is value indexed or not + */ + static async stub(stubbedContract, methodName, config = {}) { + const stubInstance = await GenericStub.GenericStubContract.at(stubbedContract.address) + + const { abi: abis } = stubbedContract + const methodAbis = abis.filter((abi) => abi.type === 'function' && abi.name === methodName) + + if (methodAbis.length > 1) { + throw new Error('Support of methods overloading has not implemented yet') + } + const [methodAbi] = methodAbis + + const configParser = new GenericStubConfigParser() + const parsedConfig = configParser.parse(methodAbi.signature, config) + await stubInstance.GenericStub__addStub(Object.values(parsedConfig)) + } +} + +module.exports = { + GenericStub +} + +class GenericStubConfigParser { + parse(methodAbi, config) { + return { + input: this._parseInput(methodAbi, config), + output: this._parseOutput(config), + logs: this._parseLogs(config), + forwardETH: this._parseForwardETH(config), + isRevert: this._parseIsRevert(config), + nextState: this._parseNextState(config) + } + } + + _parseInput(methodSignature, config) { + return methodSignature + this._encode(config.input || { type: [], value: [] }).slice(2) + } + + _parseOutput(config) { + if (config.return) { + return this._encode(config.return) + } + if (config.revert) { + return config.revert.error + ? this._encodeError(config.revert.error) + : this._encodeError({ error: 'Error', args: { type: ['string'], value: [config.revert.reason || ''] } }) + } + return this._encode({ type: [], value: [] }) + } + + _parseLogs(config) { + if (!config.emit || config.emit.length === 0) return [] + return config.emit.map((event) => { + // required field so just read it + const name = event.name + // if not passed event considered as without arguments + const args = event.args ? { type: event.args.type, value: event.args.value } : { type: [], value: [] } + // when indexed is passed take its values or consider all fields as non-indexed in other cases + const indexed = event.args && event.args.indexed ? event.args.indexed : args.value.map(() => false) + // filter all indexed args indices to pass them as topics + const indexedIndices = indexed.map((indexed, index) => (indexed ? index : -1)).filter((i) => i >= 0) + // filter all non-indexed args indices to pass them as data + const nonIndexedIndices = indexed.map((indexed, index) => (indexed ? -1 : index)).filter((i) => i >= 0) + + // signature of the event always goes as topic1 + const signature = this._eventSignature(name, args.type) + // collect argument into topics via ABI encoding + const topics = indexedIndices.map((i) => this._encode({ type: [args.type[i]], value: [args.value[i]] })) + // collect non-indexed args to encode them via ABI encoder and use it as data + const nonIndexedArgs = nonIndexedIndices + .map((i) => [args.type[i], args.value[i]]) + .reduce((args, [type, value]) => ({ type: [...args.type, type], value: [...args.value, value] }), { + type: [], + value: [] + }) + + const logType = topics.length + 1 // first topic is event signature + return [ + logType, + this._encode(nonIndexedArgs), + signature, + logType >= 2 ? topics[0] : '0x0', + logType >= 3 ? topics[1] : '0x0', + logType === 4 ? topics[2] : '0x0' + ] + }) + } + + _parseForwardETH(config) { + const { forwardETH = { recipient: ZERO_ADDRESS, value: 0 } } = config + return [forwardETH.recipient, forwardETH.value] + } + + _parseIsRevert(config) { + return !!config.revert + } + + _parseNextState(config) { + return config.nextState || 0 + } + + _encode({ type, value }) { + return hre.ethers.utils.defaultAbiCoder.encode(type, value) + } + + _encodeError({ error, args }) { + const signature = this._errorSignature(error, args.type) + return signature + this._encode(args).slice(2) + } + + _errorSignature(name, argTypes) { + const fullName = `${name}(${argTypes.join(',')})` + return hre.ethers.utils.keccak256(hre.ethers.utils.toUtf8Bytes(fullName)).slice(0, 10) + } + + _eventSignature(name, argTypes) { + const fullName = `${name}(${argTypes.join(',')})` + return hre.ethers.utils.keccak256(hre.ethers.utils.toUtf8Bytes(fullName)) + } +} diff --git a/test/helpers/stubs/staking-module.stub.js b/test/helpers/stubs/staking-module.stub.js new file mode 100644 index 000000000..224d5bc00 --- /dev/null +++ b/test/helpers/stubs/staking-module.stub.js @@ -0,0 +1,59 @@ +const { GenericStub } = require('./generic.stub') +const { FakeValidatorKeys } = require('../../helpers/signing-keys') + +class StakingModuleStub extends GenericStub { + static new() { + return GenericStub.new('IStakingModule') + } + + static async stubGetStakingModuleSummary( + stakingModuleStub, + { totalExitedValidators, totalDepositedValidators, availableValidatorsCount } + ) { + await GenericStub.stub(stakingModuleStub, 'getStakingModuleSummary', { + return: { + type: ['uint256', 'uint256', 'uint256'], + value: [totalExitedValidators, totalDepositedValidators, availableValidatorsCount] + } + }) + } + + /** + * @param {object} stakingModuleStub instance of GenericStub contract + * @param {object} config config for the method stub + * @param {object} config.input the input stub must return value for. When not set + * config.return value will be returned for any input + * @param {number} config.input.depositsCount the input value of the _depositsCount to trigger stub + * @param {string} config.input.calldata the input value of the _calldata to trigger stub + * @param {object} config.return the config for the return value + * @param {object} config.return.depositData the instance of the FakeValidatorKeys to return from the stub. + * If not set will be used FakeValidatorKeys instance of default length + * @param {number} config.return.depositDataLength the length of the FakeValidatorKeys instance + * to use for return value + * @param {string} config.return.publicKeysBatch the bytes batch of the public keys + * @param {string} config.return.signaturesBatch the bytes batch of the signatures + */ + static async stubObtainDepositData(stakingModuleStub, config) { + const input = config.input + ? { type: ['uint256', 'bytes'], value: [config.input.depositsCount, config.input.calldata] } + : undefined + const depositData = config.return.depositData + ? config.return.depositData + : new FakeValidatorKeys(config.return.depositDataLength) + const [defaultPublicKeysBatch, defaultSignaturesBatch] = depositData.slice() + await GenericStub.stub(stakingModuleStub, 'obtainDepositData', { + input, + return: { + type: ['bytes', 'bytes'], + value: [ + config.return.publicKeysBatch || defaultPublicKeysBatch, + config.return.signaturesBatch || defaultSignaturesBatch + ] + } + }) + } +} + +module.exports = { + StakingModuleStub +} diff --git a/test/helpers/utils.js b/test/helpers/utils.js index 27bcd91b9..01f0bb3f7 100644 --- a/test/helpers/utils.js +++ b/test/helpers/utils.js @@ -145,6 +145,27 @@ const setBalance = async (address, value) => { await hre.network.provider.send('hardhat_setBalance', [address, web3.utils.numberToHex(value)]) } +const prepIdsCountsPayload = (ids, counts) => { + if (!Array.isArray(ids)) ids = [ids] + if (!Array.isArray(counts)) counts = [counts] + return { + operatorIds: '0x' + ids.map((id) => hex(id, 8)).join(''), + keysCounts: '0x' + counts.map((count) => hex(count, 16)).join('') + } +} + +const calcSharesMintedAsFees = (rewards, fee, feePoints, prevTotalShares, newTotalEther) => { + return toBN(rewards) + .mul(toBN(fee)) + .mul(toBN(prevTotalShares)) + .div( + toBN(newTotalEther) + .mul(toBN(feePoints)) + .sub(toBN(rewards).mul(toBN(fee))) + ) +} + + module.exports = { ZERO_HASH, pad, @@ -178,5 +199,7 @@ module.exports = { padRight, toNum, toStr, - setBalance + setBalance, + prepIdsCountsPayload, + calcSharesMintedAsFees } diff --git a/test/helpers/wei.js b/test/helpers/wei.js new file mode 100644 index 000000000..f15da3916 --- /dev/null +++ b/test/helpers/wei.js @@ -0,0 +1,98 @@ +const hre = require('hardhat') + +function wei(...args) { + return parseWeiExpression(weiExpressionTag(...args)) +} + +wei.int = (...args) => { + if (args.length === 0) { + throw new Error('No arguments provided to wei.int() call') + } + + // when str is used as JS tag it first argument will be array of strings + if (Array.isArray(args[0]) && args[0].every((e) => typeof e === 'string')) { + return wei(...args) + } + + // when first argument is string, consider it as wei expression + if (typeof args[0] === 'string') { + return wei(...args) + } + + // in all other cases just cast first item to string and convert it to BigInt + return BigInt(args[0].toString()) +} + +wei.str = (...args) => { + if (args.length === 0) { + throw new Error('No arguments provided to wei.str() call') + } + + // when str is used as JS tag it first argument will be array of strings + if (Array.isArray(args[0]) && args[0].every((e) => typeof e === 'string')) { + return wei(...args).toString() + } + + // when first argument is string, consider it as wei expression + if (typeof args[0] === 'string') { + return wei(...args).toString() + } + + // in all other cases just cast first item to string + return args[0].toString() +} + +wei.min = (...values) => { + if (values.length === 0) { + throw new Error(`No arguments provided to wei.min() call`) + } + return values.reduce((min, value) => (wei.int(value) < min ? wei.int(value) : min), wei.int(values[0])) +} + +wei.max = (...values) => { + if (values.length === 0) { + throw new Error(`No arguments provided to wei.min() call`) + } + return values.reduce((max, value) => (wei.int(value) > max ? wei.int(value) : max), wei.int(values[0])) +} + +function weiExpressionTag(strings, ...values) { + if (!Array.isArray(strings) && typeof strings !== 'string') { + throw new Error(`wei was used with invalid arg type. Make sure that was passed valid JS template string`) + } + // when wei used not like js tag but called like regular function + // the first argument will be string instead of array of strings + if (typeof strings === 'string') { + strings = [strings] + } + + // case when wei used without arguments + if (strings.length === 1 && strings[0] === '' && values.length === 0) { + throw new Error('Empty wei tag template. Please specify expression inside wei`` tag') + } + + // combine interpolations in one expression + let expression = strings[0] + for (let i = 1; i < strings.length; ++i) { + expression += values[i - 1].toString() + strings[i] + } + return expression +} + +function parseWeiExpression(expression) { + const [amount, unit = 'wei'] = expression + .replaceAll('_', '') // remove all _ from numbers written like '100_00' + .trim() // remove all leading and trealing spaces + .split(' ') // split amount and unit parts + .filter((v) => !!v) // remove all empty strings if value had redundant spaces between amount and unit parts + + if (!Number.isFinite(+amount)) { + throw new Error(`Amount ${amount} is not a number`) + } + + return BigInt(hre.web3.utils.toWei(amount, unit.toLowerCase())) +} + +module.exports = { + wei +} diff --git a/test/helpers/withdrawals.js b/test/helpers/withdrawals.js index b89d7a01f..9a247fc80 100644 --- a/test/helpers/withdrawals.js +++ b/test/helpers/withdrawals.js @@ -1,12 +1,12 @@ const { utils } = require('ethers') const OssifiableProxy = artifacts.require('OssifiableProxy.sol') -const WithdrawalRequestNFT = artifacts.require('WithdrawalRequestNFT.sol') +const WithdrawalQueueERC721 = artifacts.require('WithdrawalQueueERC721.sol') -async function deploy(ownerAddress, wstethAddress, name = "Lido Withdrawal Request", symbol = "unstETH") { - const impl = await WithdrawalRequestNFT.new(wstethAddress, name, symbol) +async function deploy(ownerAddress, wstethAddress, name = "Lido: Withdrawal Request NFT", symbol = "unstETH") { + const impl = await WithdrawalQueueERC721.new(wstethAddress, name, symbol) const proxy = await OssifiableProxy.new(impl.address, ownerAddress, '0x') - const queue = await WithdrawalRequestNFT.at(proxy.address) + const queue = await WithdrawalQueueERC721.at(proxy.address) return { impl, diff --git a/test/scenario/changing_oracles_during_epoch.js b/test/scenario/changing_oracles_during_epoch.js index d041a9ba6..53d8495be 100644 --- a/test/scenario/changing_oracles_during_epoch.js +++ b/test/scenario/changing_oracles_during_epoch.js @@ -38,7 +38,7 @@ contract('AccountingOracle', ([appManager, voting, malicious1, malicious2, membe lastWithdrawalRequestIdToFinalize: 0, finalizationShareRate: 0, isBunkerMode: false, - extraDataFormat: 1, + extraDataFormat: 0, extraDataHash: ZERO_HASH, extraDataItemsCount: 0 } diff --git a/test/scenario/deposit_distribution.js b/test/scenario/deposit_distribution.js index 7315fae02..780881545 100644 --- a/test/scenario/deposit_distribution.js +++ b/test/scenario/deposit_distribution.js @@ -5,7 +5,7 @@ const { EvmSnapshot } = require('../helpers/blockchain') const { deployProtocol } = require('../helpers/protocol') const { setupNodeOperatorsRegistry } = require('../helpers/staking-modules') -contract('StakingRouter', ([depositor, stranger1, dsm, address1, address2]) => { +contract('StakingRouter', ([depositor, stranger1, address1, address2]) => { const snapshot = new EvmSnapshot(hre.ethers.provider) let depositContract, stakingRouter let lido, curated, anotherCurated, voting @@ -68,12 +68,12 @@ contract('StakingRouter', ([depositor, stranger1, dsm, address1, address2]) => { await curated.setNodeOperatorStakingLimit(0, 100000, { from: voting, gasPrice: 10 }) await anotherCurated.setNodeOperatorStakingLimit(0, 100000, { from: voting, gasPrice: 10 }) - // balance are 0 - assert.equals(await web3.eth.getBalance(lido.address), 0) + // balance are initial + assert.equals(await web3.eth.getBalance(lido.address), ETH(1)) assert.equals(await web3.eth.getBalance(stakingRouter.address), 0) await web3.eth.sendTransaction({ value: sendEthForKeys, to: lido.address, from: stranger1 }) - assert.equals(await lido.getBufferedEther(), sendEthForKeys) + assert.equals(await lido.getBufferedEther(), ETH(200 * 32 + 1)) const keysAllocation = await stakingRouter.getDepositsAllocation(200) @@ -87,10 +87,10 @@ contract('StakingRouter', ([depositor, stranger1, dsm, address1, address2]) => { assert.equals(await depositContract.totalCalls(), 100, 'invalid deposits count') // on deposit we return balance to Lido - assert.equals(await web3.eth.getBalance(lido.address), ETH(100 * 32), 'invalid lido balance') + assert.equals(await web3.eth.getBalance(lido.address), ETH(100 * 32 + 1), 'invalid lido balance') assert.equals(await web3.eth.getBalance(stakingRouter.address), 0, 'invalid staking_router balance') - assert.equals(await lido.getBufferedEther(), ETH(100 * 32), 'invalid total buffer') + assert.equals(await lido.getBufferedEther(), ETH(100 * 32 + 1), 'invalid total buffer') }) }) }) diff --git a/test/scenario/lido_deposit_iteration_limit.js b/test/scenario/lido_deposit_iteration_limit.js index 26ce0a09e..78dee023e 100644 --- a/test/scenario/lido_deposit_iteration_limit.js +++ b/test/scenario/lido_deposit_iteration_limit.js @@ -83,7 +83,7 @@ contract('Lido: deposit loop iteration limit', ([user1, nobody, nodeOperator]) = it('a user submits 25 * 32 ETH', async () => { const depositAmount = 25 * 32 const referral = ZERO_ADDRESS - await pool.submit(referral, { from: user1, value: ETH(depositAmount) }) + await pool.submit(referral, { from: user1, value: ETH(depositAmount - 1) }) assertBn(await pool.getTotalPooledEther(), ETH(depositAmount), 'total controlled ether') // at this point, no deposit assignments were made and all ether is buffered diff --git a/test/scenario/lido_happy_path.js b/test/scenario/lido_happy_path.js index e112cd687..70a3ba5ff 100644 --- a/test/scenario/lido_happy_path.js +++ b/test/scenario/lido_happy_path.js @@ -9,29 +9,12 @@ const { DSMAttestMessage, DSMPauseMessage } = require('../helpers/signatures') const { waitBlocks } = require('../helpers/blockchain') const { deployProtocol } = require('../helpers/protocol') const { setupNodeOperatorsRegistry } = require('../helpers/staking-modules') -const { gwei, ZERO_HASH } = require('../helpers/utils') const { pushOracleReport } = require('../helpers/oracle') +const { INITIAL_HOLDER } = require('../helpers/constants') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') const CURATED_MODULE_ID = 1 -const makeAccountingReport = ({refSlot, numValidators, clBalanceGwei}) => ({ - refSlot, - consensusVersion: 1, - numValidators: numValidators, - clBalanceGwei: clBalanceGwei, - stakingModuleIdsWithNewlyExitedValidators: [], - numExitedValidatorsByStakingModule: [], - withdrawalVaultBalance: 0, - elRewardsVaultBalance: 0, - lastWithdrawalRequestIdToFinalize: 0, - finalizationShareRate: 0, - isBunkerMode: false, - extraDataFormat: 0, - extraDataHash: ZERO_HASH, - extraDataItemsCount: 0, -}) - contract('Lido: happy path', (addresses) => { const [ // node operators @@ -177,7 +160,7 @@ contract('Lido: happy path', (addresses) => { }) it('the first user deposits 3 ETH to the pool', async () => { - await web3.eth.sendTransaction({ to: pool.address, from: user1, value: ETH(3) }) + await web3.eth.sendTransaction({ to: pool.address, from: user1, value: ETH(2) }) const block = await web3.eth.getBlock('latest') const keysOpIndex = await nodeOperatorsRegistry.getKeysOpIndex() @@ -214,7 +197,7 @@ contract('Lido: happy path', (addresses) => { // The amount of tokens corresponding to the deposited ETH value was minted to the user - assertBn(await token.balanceOf(user1), tokens(3), 'user1 tokens') + assertBn(await token.balanceOf(user1), tokens(2), 'user1 tokens') assertBn(await token.totalSupply(), tokens(3), 'token total supply') }) @@ -264,7 +247,7 @@ contract('Lido: happy path', (addresses) => { // The amount of tokens corresponding to the deposited ETH value was minted to the users - assertBn(await token.balanceOf(user1), tokens(3), 'user1 tokens') + assertBn(await token.balanceOf(user1), tokens(2), 'user1 tokens') assertBn(await token.balanceOf(user2), tokens(30), 'user2 tokens') assertBn(await token.totalSupply(), tokens(3 + 30), 'token total supply') @@ -370,7 +353,7 @@ contract('Lido: happy path', (addresses) => { // The amount of tokens corresponding to the deposited ETH value was minted to the users - assertBn(await token.balanceOf(user1), tokens(3), 'user1 tokens') + assertBn(await token.balanceOf(user1), tokens(2), 'user1 tokens') assertBn(await token.balanceOf(user2), tokens(30), 'user2 tokens') assertBn(await token.balanceOf(user3), tokens(64), 'user3 tokens') @@ -422,21 +405,16 @@ contract('Lido: happy path', (addresses) => { // Token user balances increased - assertBn(await token.balanceOf(user1), new BN('3008907216494845360'), 'user1 tokens') - assertBn(await token.balanceOf(user2), new BN('30089072164948453608'), 'user2 tokens') - assertBn(await token.balanceOf(user3), new BN('64190020618556701031'), 'user3 tokens') + assertBn(await token.balanceOf(INITIAL_HOLDER), '1002969072164948453', 'initial holder tokens') + assertBn(await token.balanceOf(user1), '2005938144329896907', 'user1 tokens') + assertBn(await token.balanceOf(user2), '30089072164948453608', 'user2 tokens') + assertBn(await token.balanceOf(user3), '64190020618556701031', 'user3 tokens') // Fee, in the form of minted tokens, was distributed between treasury, insurance fund // and node operators // treasuryTokenBalance ~= mintedAmount * treasuryFeePoints / 10000 - // insuranceTokenBalance ~= mintedAmount * insuranceFeePoints / 10000 - assert.equalsDelta(await token.balanceOf(treasuryAddr), new BN('16000000000000000'), 1, 'treasury tokens') - assert.equalsDelta( - await token.balanceOf(nodeOperatorsRegistry.address), - new BN('16000000000000000'), - 1, - 'insurance tokens' - ) + assert.equalsDelta(await token.balanceOf(treasuryAddr), '16000000000000000', 1, 'treasury tokens') + assert.equalsDelta(await token.balanceOf(nodeOperatorsRegistry.address), 0, 1, 'staking module tokens') // The node operators' fee is distributed between all active node operators, // proportional to their effective stake (the amount of Ether staked by the operator's @@ -445,13 +423,18 @@ contract('Lido: happy path', (addresses) => { // In our case, both node operators received the same fee since they have the same // effective stake (one signing key used from each operator, staking 32 ETH) - assertBn(await token.balanceOf(nodeOperator1.address), 0, 'operator_1 tokens') - assertBn(await token.balanceOf(nodeOperator2.address), 0, 'operator_2 tokens') + assert.equalsDelta(await token.balanceOf(nodeOperator1.address), '8000000000000000', 1, 'operator_1 tokens') + assert.equalsDelta(await token.balanceOf(nodeOperator2.address), '8000000000000000', 1, 'operator_2 tokens') // Real minted amount should be a bit less than calculated caused by round errors on mint and transfer operations assert( mintedAmount - .sub(new BN(0).add(await token.balanceOf(treasuryAddr)).add(await token.balanceOf(nodeOperatorsRegistry.address))) + .sub( + new BN(0) + .add(await token.balanceOf(treasuryAddr)) + .add(await token.balanceOf(nodeOperator1.address)) + .add(await token.balanceOf(nodeOperator2.address)) + ) .lt(mintedAmount.divn(100)) ) }) diff --git a/test/scenario/lido_penalties_slashing.js b/test/scenario/lido_penalties_slashing.js index 2690c7d02..67772eafa 100644 --- a/test/scenario/lido_penalties_slashing.js +++ b/test/scenario/lido_penalties_slashing.js @@ -1,14 +1,13 @@ const hre = require('hardhat') -const { BN } = require('bn.js') const { assertBn } = require('@aragon/contract-helpers-test/src/asserts') -const { getEventArgument } = require('@aragon/contract-helpers-test') +const { getEventArgument, bn } = require('@aragon/contract-helpers-test') const { assert } = require('../helpers/assert') -const { pad, ETH, tokens, toBN } = require('../helpers/utils') +const { pad, ETH, StETH, shares, prepIdsCountsPayload } = require('../helpers/utils') const { waitBlocks } = require('../helpers/blockchain') const { deployProtocol } = require('../helpers/protocol') const { setupNodeOperatorsRegistry } = require('../helpers/staking-modules') -const { SLOTS_PER_FRAME, SECONDS_PER_FRAME } = require('../helpers/constants') +const { SECONDS_PER_FRAME } = require('../helpers/constants') const { pushOracleReport } = require('../helpers/oracle') const { oracleReportSanityCheckerStubFactory } = require('../helpers/factories') const { DSMAttestMessage, DSMPauseMessage, signDepositData } = require('../helpers/signatures') @@ -35,51 +34,50 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { let elRewardsVault before('DAO, node operators registry, token, pool and deposit security module are deployed and initialized', async () => { - const deployed = await deployProtocol({ - oracleReportSanityCheckerFactory: oracleReportSanityCheckerStubFactory, - stakingModulesFactory: async (protocol) => { - const curatedModule = await setupNodeOperatorsRegistry(protocol) - return [ - { - module: curatedModule, - name: 'Curated', - targetShares: 10000, - moduleFee: 500, - treasuryFee: 500 - } - ] - } - }) - - // contracts/StETH.sol - token = deployed.pool - - // contracts/Lido.sol - pool = deployed.pool - - // contracts/nos/NodeOperatorsRegistry.sol - nodeOperatorsRegistry = deployed.stakingModules[0] - - // mocks - oracle = deployed.oracle - consensus = deployed.consensusContract - depositContractMock = deployed.depositContract - - stakingRouter = deployed.stakingRouter - - // addresses - treasuryAddr = deployed.treasury.address - depositSecurityModule = deployed.depositSecurityModule - guardians = deployed.guardians - voting = deployed.voting.address - elRewardsVault = deployed.elRewardsVault - - depositRoot = await depositContractMock.get_deposit_root() - withdrawalCredentials = pad('0x0202', 32) - - await stakingRouter.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) - } - ) + const deployed = await deployProtocol({ + oracleReportSanityCheckerFactory: oracleReportSanityCheckerStubFactory, + stakingModulesFactory: async (protocol) => { + const curatedModule = await setupNodeOperatorsRegistry(protocol) + return [ + { + module: curatedModule, + name: 'Curated', + targetShares: 10000, + moduleFee: 500, + treasuryFee: 500 + } + ] + } + }) + + // contracts/StETH.sol + token = deployed.pool + + // contracts/Lido.sol + pool = deployed.pool + + // contracts/nos/NodeOperatorsRegistry.sol + nodeOperatorsRegistry = deployed.stakingModules[0] + + // mocks + oracle = deployed.oracle + consensus = deployed.consensusContract + depositContractMock = deployed.depositContract + + stakingRouter = deployed.stakingRouter + + // addresses + treasuryAddr = deployed.treasury.address + depositSecurityModule = deployed.depositSecurityModule + guardians = deployed.guardians + voting = deployed.voting.address + elRewardsVault = deployed.elRewardsVault + + depositRoot = await depositContractMock.get_deposit_root() + withdrawalCredentials = pad('0x0202', 32) + + await stakingRouter.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) + }) const pushReport = async (clValidators, clBalance) => { const elRewards = await web3.eth.getBalance(elRewardsVault.address) @@ -88,8 +86,10 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { await ethers.provider.send('evm_mine') } - let awaitingTotalShares = new BN(0) - let awaitingUser1Balance = new BN(0) + // storing incremental calculated values that changes all across the test suite + let expectedTotalShares = shares(0) + let expectedUser1Balance = StETH(0) + let expectedUser1Shares = shares(0) // Each node operator has its Ethereum 1 address, a name and a set of registered // validators, each of them defined as a (public key, signature) pair @@ -143,10 +143,8 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { }) it('the user deposits 32 ETH to the pool', async () => { - const depositAmount = ETH(32) - awaitingTotalShares = new BN(depositAmount) - awaitingUser1Balance = new BN(depositAmount) - await web3.eth.sendTransaction({ to: pool.address, from: user1, value: depositAmount }) + await web3.eth.sendTransaction({ to: pool.address, from: user1, value: ETH(32) }) + const block = await web3.eth.getBlock('latest') const keysOpIndex = await nodeOperatorsRegistry.getKeysOpIndex() @@ -182,18 +180,19 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { assertBn(ether2Stat.depositedValidators, 0, 'no validators have received the ether2') assertBn(ether2Stat.beaconBalance, 0, 'remote ether2 not reported yet') - // All Ether was buffered within the pool contract atm - - assertBn(await pool.getBufferedEther(), ETH(32), `all ether is buffered until there's a validator to deposit it`) - assertBn(await pool.getTotalPooledEther(), ETH(32), 'total pooled ether') + assertBn(await pool.getBufferedEther(), ETH(33), `All Ether was buffered within the pool contract atm`) + assertBn(await pool.getTotalPooledEther(), ETH(33), 'total pooled ether') - // The amount of tokens corresponding to the deposited ETH value was minted to the user + expectedUser1Balance = StETH(32) + expectedUser1Shares = shares(32) - assertBn(await token.balanceOf(user1), awaitingUser1Balance, 'user1 tokens') + assertBn(await token.sharesOf(user1), shares(32), "User1 holds 32 shares") + assertBn(await token.balanceOf(user1), expectedUser1Balance, + 'The amount of tokens corresponding to the deposited ETH value was minted to the user') + assertBn(await token.totalSupply(), StETH(33), 'token total supply') - assertBn(await token.totalSupply(), tokens(32), 'token total supply') - // Total shares are equal to deposited eth before ratio change and fee mint - assertBn(await token.getTotalShares(), awaitingTotalShares, 'total shares') + assertBn(await token.getTotalShares(), shares(33), + 'Total shares are equal to deposited eth before ratio change and fee mint') }) it(`voting grants first operator right to have one validator`, async () => { @@ -201,8 +200,8 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { }) it(`new validator doesn't get buffered ether even if there's 32 ETH deposit in the pool`, async () => { - assertBn(await pool.getBufferedEther(), ETH(32), `all ether is buffered until there's a validator to deposit it`) - assertBn(await pool.getTotalPooledEther(), ETH(32), 'total pooled ether') + assertBn(await pool.getBufferedEther(), ETH(33), `all ether is buffered until there's a validator to deposit it`) + assertBn(await pool.getTotalPooledEther(), ETH(33), 'total pooled ether') assertBn(await nodeOperatorsRegistry.getUnusedSigningKeyCount(0), 1, 'one key available for the first validator') }) @@ -235,103 +234,62 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { }) it('new validator gets the 32 ETH deposit from the pool', async () => { - assertBn(await pool.getBufferedEther(), ETH(0), `all ether is buffered until there's a validator to deposit it`) - assertBn(await pool.getTotalPooledEther(), ETH(32), 'total pooled ether') + assertBn(await pool.getBufferedEther(), ETH(1), `only initial eth is left`) + assertBn(await pool.getTotalPooledEther(), ETH(33), 'total pooled ether') assertBn(await nodeOperatorsRegistry.getUnusedSigningKeyCount(0), 0, 'no more available keys for the first validator') }) it('first oracle report is taken as-is for Lido', async () => { - const oldTotalShares = await token.getTotalShares() - - // Old total pooled Ether + assertBn(await pool.getTotalPooledEther(), ETH(33), '32 ETH deposit + 1 ETH initial') - const oldTotalPooledEther = await pool.getTotalPooledEther() - assertBn(oldTotalPooledEther, ETH(32), 'total pooled ether') - - const refSlot = 1 * SLOTS_PER_FRAME // Reporting 1 ETH balance loss (32 => 31) - const balanceReported = ETH(31) - awaitingTotalShares = oldTotalShares - awaitingUser1Balance = new BN(balanceReported) - - await pushReport(1, balanceReported) - - // Total shares stay the same because no fee shares are added - - const newTotalShares = await token.getTotalShares() - assertBn(newTotalShares, awaitingTotalShares, `total shares don't change on no reward`) + await pushReport(1, ETH(31)) - // Total pooled Ether decreased + assertBn(await token.getTotalShares(), shares(33), + 'Total shares stay the same because no fee shares are added') - const newTotalPooledEther = await pool.getTotalPooledEther() - assertBn(newTotalPooledEther, balanceReported, 'total pooled ether equals the reported balance') - - // Ether2 stat reported by the pool changed correspondingly - - const ether2Stat = await pool.getBeaconStat() - assertBn(ether2Stat.depositedValidators, 1, 'deposited ether2') - assertBn(ether2Stat.beaconBalance, balanceReported, 'remote ether2 balance equals the reported value') + assertBn(await pool.getTotalPooledEther(), ETH(32), 'Total pooled Ether decreased') - // Buffered Ether amount didn't change + const clStat = await pool.getBeaconStat() + assertBn(clStat.depositedValidators, 1, 'validators count') + assertBn(clStat.beaconBalance, ETH(31), 'Ether2 stat reported by the pool changed correspondingly') - assertBn(await pool.getBufferedEther(), ETH(0), 'buffered ether') + assertBn(await pool.getBufferedEther(), ETH(1), 'Initial stake remains in the buffer') + assertBn(await token.totalSupply(), StETH(32), 'Token total supply penalized') - // Total supply accounts for penalties taken by the validator - assertBn(await token.totalSupply(), tokens(31), 'token total supply') + assertBn(await token.sharesOf(user1), shares(32), "User1 still holds 32 shares") + expectedUser1Balance = bn(shares(32)).muln(32).divn(33) // 32/33 ETH/share is a new price + assertBn(await token.balanceOf(user1), expectedUser1Balance, `Token user balances decreased`) - // Token user balances decreased - assertBn(await token.balanceOf(user1), awaitingUser1Balance, `user1 balance decreased`) - - // No fees distributed yet - assertBn(await token.balanceOf(treasuryAddr), new BN(0), 'treasury tokens') - assertBn(await token.balanceOf(nodeOperator1.address), new BN(0), 'operator_1 tokens') + assertBn(await token.balanceOf(treasuryAddr), 0, 'No fees distributed yet: treasury') + assertBn(await token.balanceOf(nodeOperator1.address), 0, 'No fees distributed yet: operator_1') }) - it('the oracle reports balance loss on Ethereum2 side', async () => { - // Total shares are equal to deposited eth before ratio change and fee mint - assertBn(await token.getTotalShares(), awaitingTotalShares, 'old total shares') - - // Old total pooled Ether - - const oldTotalPooledEther = await pool.getTotalPooledEther() - assertBn(oldTotalPooledEther, ETH(31), 'old total pooled ether') - - const balanceReported = ETH(29) - awaitingUser1Balance = new BN(balanceReported) + it('the oracle reports balance loss on CL side', async () => { + assertBn(await token.getTotalShares(), shares(33), + 'Total shares are equal to deposited eth before ratio change and fee mint') + assertBn(await pool.getTotalPooledEther(), ETH(32), + 'Old total pooled Ether 31 ETH od previous report + 1 ETH initial') // Reporting 2 ETH balance loss (31 => 29) - await pushReport(1, balanceReported) - - // Total shares stay the same because no fee shares are added - - const newTotalShares = await token.getTotalShares() - assertBn(newTotalShares, awaitingTotalShares, `total shares don't change without rewards`) + await pushReport(1, ETH(29)) - // Total pooled Ether decreased - - const newTotalPooledEther = await pool.getTotalPooledEther() - assertBn(newTotalPooledEther, balanceReported, 'new total pooled ether') - - // Ether2 stat reported by the pool changed correspondingly + assertBn(await token.getTotalShares(), shares(33), + `Total shares stay the same because no fee shares are added`) + assertBn(await pool.getTotalPooledEther(), ETH(30), 'Total pooled Ether decreased') const ether2Stat = await pool.getBeaconStat() - assertBn(ether2Stat.depositedValidators, 1, 'deposited ether2') - assertBn(ether2Stat.beaconBalance, balanceReported, 'remote ether2') - - // Buffered Ether amount didn't change - - assertBn(await pool.getBufferedEther(), ETH(0), 'buffered ether') + assertBn(ether2Stat.depositedValidators, 1, 'deposited validators') + assertBn(ether2Stat.beaconBalance, ETH(29), 'Ether2 stat reported by the pool changed correspondingly') - // Total supply accounts for penalties taken by the validator - assertBn(await token.totalSupply(), tokens(29), 'token total supply shrunk by loss taken') + assertBn(await pool.getBufferedEther(), ETH(1), 'Buffered Ether amount didnt change') + assertBn(await token.totalSupply(), StETH(30), 'Total supply accounts for penalties taken by the validator') - // Token user balances decreased - assertBn(await token.balanceOf(user1), awaitingUser1Balance, 'user1 tokens shrunk by loss taken') + expectedUser1Balance = bn(shares(32)).muln(30).divn(33) // New share price is 30/33 ETH/share + assertBn(await token.balanceOf(user1), expectedUser1Balance, 'Token user1 balances decreased') - // No fees distributed yet - assertBn(await token.balanceOf(treasuryAddr), new BN(0), 'no treasury tokens on no reward') - - assertBn(await token.balanceOf(nodeOperator1.address), new BN(0), 'no operator_1 reward') + assertBn(await token.balanceOf(treasuryAddr), 0, 'No fees distributed yet: treasury') + assertBn(await token.balanceOf(nodeOperator1.address), 0, 'No fees distributed yet: operator_1') }) const nodeOperator2 = { @@ -382,10 +340,10 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { }) it('the user deposits another 32 ETH to the pool', async () => { - const depositAmount = ETH(32) - awaitingUser1Balance = awaitingUser1Balance.add(new BN(depositAmount)) - const tokenSupplyBefore = await token.totalSupply() - await web3.eth.sendTransaction({ to: pool.address, from: user1, value: depositAmount }) + assertBn(await token.totalSupply(), StETH(30), 'token total supply before') + assertBn(await token.getTotalShares(), shares(33), 'token total supply before') + + await web3.eth.sendTransaction({ to: pool.address, from: user1, value: ETH(32) }) const block = await waitBlocks(await depositSecurityModule.getMinDepositBlockDistance()) const keysOpIndex = await nodeOperatorsRegistry.getKeysOpIndex() const signatures = [ @@ -418,68 +376,49 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') assertBn(ether2Stat.beaconBalance, ETH(29), 'remote ether2 as reported last time') - // All Ether was buffered within the pool contract atm - - assertBn(await pool.getBufferedEther(), ETH(0), 'buffered ether') - assertBn(await pool.getTotalPooledEther(), ETH(61), 'total pooled ether') + assertBn(await pool.getBufferedEther(), ETH(1), 'Only initial ether is in the buffer') + assertBn(await pool.getTotalPooledEther(), ETH(62), 'total pooled ether') + assertBn(await token.totalSupply(), StETH(62), 'token total supply') - // The amount of tokens corresponding to the deposited ETH value was minted to the user + // 32 ETH deposit to shares if price is 30/33 ETH/shares + const sharesAdded = bn(ETH(32)).mul(bn(ETH(33))).div(bn(shares(30))) + expectedUser1Shares = bn(expectedUser1Shares).add(sharesAdded) + assertBn(await token.sharesOf(user1), expectedUser1Shares, "User1 acquires new shares by new share price") + assertBn(await token.getTotalShares(), bn(shares(1)).add(expectedUser1Shares), 'total shares are changed proportionally') - assertBn(await token.balanceOf(user1), awaitingUser1Balance, 'user1 tokens') - - assertBn(await token.totalSupply(), tokens(61), 'token total supply') - - const oldShares = awaitingTotalShares - const newDeposit = new BN(depositAmount) - const sharesAdded = newDeposit.mul(oldShares).div(tokenSupplyBefore) - awaitingTotalShares = awaitingTotalShares.add(sharesAdded) - assertBn(await token.getTotalShares(), new BN(depositAmount).add(sharesAdded), 'total shares are changed proportionally') - assertBn(await token.balanceOf(user1), awaitingUser1Balance, `user1 balance increased by deposited ETH`) + expectedUser1Balance = bn(expectedUser1Balance).add(bn(StETH(32))) + assertBn(await token.balanceOf(user1), expectedUser1Balance, 'user1 tokens') }) it('the oracle reports balance loss for the third time', async () => { - // Old total pooled Ether - const oldTotalPooledEther = await pool.getTotalPooledEther() - assertBn(oldTotalPooledEther, ETH(61), 'old total pooled ether') - - const lossReported = ETH(1) - awaitingUser1Balance = awaitingUser1Balance.sub(new BN(lossReported)) + assertBn(await pool.getTotalPooledEther(), ETH(62), 'Old total pooled Ether') + const expectedTotalShares = bn(shares(1)).add(bn(expectedUser1Shares)) + assertBn(await token.getTotalShares(), expectedTotalShares, 'Old total shares') // Reporting 1 ETH balance loss ( total pooled 61 => 60) - await pushReport(1, ETH(28)) - // Total shares stay the same because no fee shares are added - - const newTotalShares = await token.getTotalShares() - assertBn(newTotalShares, awaitingTotalShares, `total shares don't change on no reward`) - - // Total pooled Ether decreased - - const newTotalPooledEther = await pool.getTotalPooledEther() - assertBn(newTotalPooledEther, ETH(60), 'new total pooled ether') - - // Ether2 stat reported by the pool changed correspondingly + assertBn(await token.getTotalShares(), bn(shares(1)).add(expectedUser1Shares), + 'Total shares stay the same because no fee shares are added') + assertBn(await pool.getTotalPooledEther(), ETH(61), 'Total pooled Ether decreased') const ether2Stat = await pool.getBeaconStat() - assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') - assertBn(ether2Stat.beaconBalance, ETH(28), 'remote ether2') - - // Buffered Ether amount didn't change - - assertBn(await pool.getBufferedEther(), ETH(0), 'buffered ether') - - // Total supply accounts for penalties taken by the validator - assertBn(await token.totalSupply(), tokens(60), 'token total supply shrunk by loss taken') - - // Token user balances decreased - assertBn(await token.balanceOf(user1), awaitingUser1Balance, 'user1 tokens shrunk by loss taken') - - // No fees distributed yet - assertBn(await token.balanceOf(treasuryAddr), new BN(0), 'no treasury tokens on no reward') - - assertBn(await token.balanceOf(nodeOperator1.address), new BN(0), 'no operator_1 reward') - assertBn(await token.balanceOf(user1), ETH(60), `user1 balance decreased by lost ETH`) + assertBn(ether2Stat.depositedValidators, 2, 'Another validator is deposited') + assertBn(ether2Stat.beaconBalance, ETH(28), 'Ether2 stat reported by the pool changed correspondingly') + + assertBn(await pool.getBufferedEther(), ETH(1), 'Only initial ETH in the buffer') + assertBn(await pool.getTotalPooledEther(), ETH(61), 'Total pooled Ether') + assertBn(await token.totalSupply(), StETH(61), 'token total supply shrunk by loss taken') + assertBn(await token.getTotalShares(), expectedTotalShares, 'total shares stays same') + + assertBn(await token.sharesOf(user1), expectedUser1Shares, 'User1 shares stays same') + expectedUser1Balance = bn(expectedUser1Shares) // New price + .mul(bn(StETH(61))) + .div(expectedTotalShares) + assertBn(await token.balanceOf(user1), expectedUser1Balance, 'Token user balances decreased') + + assertBn(await token.balanceOf(treasuryAddr), 0, 'No fees distributed yet: treasury') + assertBn(await token.balanceOf(nodeOperator1.address), 0, 'No fees distributed yet: operator_1') }) it(`the oracle can't report less validators than previously`, async () => { @@ -487,11 +426,11 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { }) it(`user deposits another 32 ETH to the pool`, async () => { - const totalPooledEther = await pool.getTotalPooledEther() - const depositAmount = ETH(32) - awaitingTotalShares = awaitingTotalShares.add(new BN(depositAmount).mul(awaitingTotalShares).div(totalPooledEther)) - awaitingUser1Balance = awaitingUser1Balance.add(new BN(depositAmount)) - await web3.eth.sendTransaction({ to: pool.address, from: user1, value: depositAmount }) + assertBn(await pool.getTotalPooledEther(), ETH(61), 'Old total pooled Ether') + const oldTotalShares = bn(shares(1)).add(bn(expectedUser1Shares)) + assertBn(await token.getTotalShares(), oldTotalShares, 'Old total shares') + + await web3.eth.sendTransaction({ to: pool.address, from: user1, value: ETH(32) }) await hre.network.provider.send("hardhat_mine", ['0x100']) @@ -521,22 +460,25 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { const ether2Stat = await pool.getBeaconStat() assertBn(ether2Stat.depositedValidators, 2, 'no validators have received the current deposit') - // The amount of tokens corresponding to the deposited ETH value was minted to the user + assertBn(await pool.getBufferedEther(), ETH(33), '33 ETH is pooled') + assertBn(await pool.getTotalPooledEther(), ETH(93), 'Total pooled Ether') + assertBn(await token.totalSupply(), StETH(93), 'token total supply') - assertBn(await token.balanceOf(user1), awaitingUser1Balance, 'user1 tokens') + const sharesAdded = bn(ETH(32)).mul(oldTotalShares).div(bn(ETH(61))) + assertBn(await token.getTotalShares(), oldTotalShares.add(sharesAdded), + 'Total shares are equal to deposited eth before ratio change and fee mint') - assertBn(await token.totalSupply(), tokens(92), 'token total supply') - // Total shares are equal to deposited eth before ratio change and fee mint - assertBn(await token.getTotalShares(), awaitingTotalShares, 'total shares') - - // All Ether was buffered within the pool contract atm - assertBn(await pool.getBufferedEther(), ETH(32), `32 ETH is pooled`) + expectedUser1Shares = bn(expectedUser1Shares).add(sharesAdded) + assertBn(await token.sharesOf(user1), expectedUser1Shares, "User1 bought shares on 32 ETH") + expectedUser1Balance = expectedUser1Balance.add(bn(StETH(32))) + assertBn(await token.balanceOf(user1), expectedUser1Balance, + 'The amount of tokens corresponding to the deposited ETH value was minted to the user') }) it(`voting stops the staking module`, async () => { await stakingRouter.setStakingModuleStatus(1, 2, { from: voting }) - assertBn(await stakingRouter.getStakingModulesCount(), new BN(1), 'only 1 module exists') - assertBn(await stakingRouter.getStakingModuleStatus(1), new BN(2), 'no active staking modules') + assertBn(await stakingRouter.getStakingModulesCount(), 1, 'only 1 module exists') + assertBn(await stakingRouter.getStakingModuleStatus(1), 2, 'no active staking modules') }) it(`first operator adds a second validator`, async () => { @@ -564,15 +506,15 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { const activeOperatorsBefore = await nodeOperatorsRegistry.getActiveNodeOperatorsCount() await nodeOperatorsRegistry.deactivateNodeOperator(0, { from: voting }) const activeOperatorsAfter = await nodeOperatorsRegistry.getActiveNodeOperatorsCount() - assertBn(activeOperatorsAfter, activeOperatorsBefore.sub(new BN(1)), 'deactivated one operator') + assertBn(activeOperatorsAfter, activeOperatorsBefore.subn(1), 'deactivated one operator') }) it(`voting stops the second operator`, async () => { const activeOperatorsBefore = await nodeOperatorsRegistry.getActiveNodeOperatorsCount() await nodeOperatorsRegistry.deactivateNodeOperator(1, { from: voting }) const activeOperatorsAfter = await nodeOperatorsRegistry.getActiveNodeOperatorsCount() - assertBn(activeOperatorsAfter, activeOperatorsBefore.sub(new BN(1)), 'deactivated one operator') - assertBn(activeOperatorsAfter, new BN(0), 'no active operators') + assertBn(activeOperatorsAfter, activeOperatorsBefore.subn(1), 'deactivated one operator') + assertBn(activeOperatorsAfter, 0, 'no active operators') }) it(`without active staking modules fee is sent to treasury`, async () => { @@ -602,8 +544,8 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { assertBn(nodeOperator2TokenSharesAfter, nodeOperator2TokenSharesBefore, `second node operator hasn't got fees`) assertBn(nodeOperatorsRegistrySharesAfter, nodeOperatorsRegistrySharesBefore, `NOR stakingModule hasn't got fees`) - const tenKBN = new BN(10000) - const totalFeeToDistribute = new BN(beaconBalanceIncrement.toString()).mul(new BN(totalFeePoints)).div(tenKBN) + const tenKBN = bn(10000) + const totalFeeToDistribute = bn(beaconBalanceIncrement.toString()).mul(bn(totalFeePoints)).div(tenKBN) const totalPooledEther = await pool.getTotalPooledEther() let sharesToMint = totalFeeToDistribute @@ -617,8 +559,8 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { it(`voting starts staking module`, async () => { await stakingRouter.setStakingModuleStatus(1, 0, { from: voting }) - assertBn(await stakingRouter.getStakingModulesCount(), new BN(1), 'only 1 module exists') - assertBn(await stakingRouter.getStakingModuleStatus(1), new BN(0), 'no active staking modules') + assertBn(await stakingRouter.getStakingModulesCount(), 1, 'only 1 module exists') + assertBn(await stakingRouter.getStakingModuleStatus(1), 0, 'no active staking modules') }) it(`oracle reports profit, previously stopped staking module gets the fee`, async () => { @@ -641,17 +583,16 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { await pushReport(2, ETH(96)) // kicks rewards distribution - await nodeOperatorsRegistry.updateExitedValidatorsCount(0, 1, { from: voting }) + const { operatorIds, keysCounts } = prepIdsCountsPayload(0, 1) + await nodeOperatorsRegistry.updateExitedValidatorsCount(operatorIds, keysCounts, { from: voting }) const nodeOperator1TokenSharesAfter = await token.sharesOf(nodeOperator1.address) const nodeOperator2TokenSharesAfter = await token.sharesOf(nodeOperator2.address) - console.log({ - nodeOperator1TokenSharesBefore: nodeOperator1TokenSharesBefore.toString(), - nodeOperator1TokenSharesAfter: nodeOperator1TokenSharesAfter.toString(), - nodeOperator2TokenSharesBefore: nodeOperator2TokenSharesBefore.toString(), - nodeOperator2TokenSharesAfter: nodeOperator2TokenSharesAfter.toString(), - }) - assertBn(nodeOperator1TokenSharesAfter, nodeOperator1TokenSharesBefore, `first node operator balance hasn't changed`) + assertBn( + nodeOperator1TokenSharesAfter, + nodeOperator1TokenSharesBefore, + `first node operator balance hasn't changed` + ) assert( !nodeOperator2TokenSharesBefore.sub(nodeOperator2TokenSharesAfter).positive, `second node operator gained shares under fee distribution` diff --git a/test/scenario/lido_rewards_distribution_math.js b/test/scenario/lido_rewards_distribution_math.js index c226b8ede..0a1d43672 100644 --- a/test/scenario/lido_rewards_distribution_math.js +++ b/test/scenario/lido_rewards_distribution_math.js @@ -3,7 +3,7 @@ const { assertBn, assertEvent } = require('@aragon/contract-helpers-test/src/ass const { ZERO_ADDRESS } = require('@aragon/contract-helpers-test') const { waitBlocks } = require('../helpers/blockchain') -const { pad, ETH, hexConcat } = require('../helpers/utils') +const { pad, ETH, hexConcat, toBN, calcSharesMintedAsFees } = require('../helpers/utils') const { deployProtocol } = require('../helpers/protocol') const { setupNodeOperatorsRegistry } = require('../helpers/staking-modules') const { assert } = require('../helpers/assert') @@ -12,14 +12,9 @@ const { pushOracleReport } = require('../helpers/oracle') const { SECONDS_PER_FRAME } = require('../helpers/constants') const { oracleReportSanityCheckerStubFactory } = require('../helpers/factories') -const tenKBN = new BN(10000) -// Fee and its distribution are in basis points, 10000 corresponding to 100% +const Lido = artifacts.require('Lido') -// Total max fee is 10% -const totalFeePoints = 0.1 * 10000 - -// 50% goes to node operators -const nodeOperatorsFeePoints = 0.5 * 10000 +const initialHolderBalanceETH = 1 const StakingModuleStatus = { Active: 0, // deposits and rewards allowed @@ -28,17 +23,7 @@ const StakingModuleStatus = { } contract('Lido: rewards distribution math', (addresses) => { - const [ - // the address which we use to simulate the voting DAO application - // node operators - operator_1, - operator_2, - // users who deposit Ether to the pool - user1, - user2, - // unrelated address - nobody - ] = addresses + const [, , , , , , , , , , , , , operator_1, operator_2, operator_3, user1, user2, nobody] = addresses let pool, nodeOperatorsRegistry, token let stakingRouter @@ -75,12 +60,21 @@ contract('Lido: rewards distribution math', (addresses) => { ] } + const nodeOperator3 = { + name: 'operator', + address: operator_3, + validators: [...Array(10).keys()].map((i) => ({ + key: pad('0xaa01' + i.toString(16), 48), + sig: pad('0x' + i.toString(16), 96) + })) + } + async function reportBeacon(validatorsCount, balance) { - const tx = await pushOracleReport(consensus, oracle, validatorsCount, balance) + const receipts = await pushOracleReport(consensus, oracle, validatorsCount, balance) await ethers.provider.send('evm_increaseTime', [SECONDS_PER_FRAME + 1000]) await ethers.provider.send('evm_mine') - return tx + return receipts } before(async () => { @@ -167,6 +161,7 @@ contract('Lido: rewards distribution math', (addresses) => { it(`registers submit correctly`, async () => { const depositedEthValue = 34 const depositAmount = ETH(depositedEthValue) + const expectedTotalEther = ETH(depositedEthValue + initialHolderBalanceETH) const receipt = await pool.submit(ZERO_ADDRESS, { value: depositAmount, from: user1 }) @@ -176,19 +171,20 @@ contract('Lido: rewards distribution math', (addresses) => { assertBn(ether2Stat.depositedValidators, 0, 'one validator have received the ether2') assertBn(ether2Stat.beaconBalance, 0, `no remote ether2 on validator's balance is reported yet`) - assertBn(await pool.getBufferedEther(), ETH(depositedEthValue), `all the ether is buffered until deposit`) - assertBn(await pool.getTotalPooledEther(), depositAmount, 'total pooled ether') + assertBn(await pool.getBufferedEther(), expectedTotalEther, `all the ether is buffered until deposit`) + assertBn(await pool.getTotalPooledEther(), expectedTotalEther, 'total pooled ether') // The amount of tokens corresponding to the deposited ETH value was minted to the user assertBn(await token.balanceOf(user1), depositAmount, 'user1 tokens') - assertBn(await token.totalSupply(), depositAmount, 'token total supply') + assertBn(await token.totalSupply(), expectedTotalEther, 'token total supply') // Total shares are equal to deposited eth before ratio change and fee mint - assertBn(await token.getTotalShares(), depositAmount, 'total shares') + assertBn(await token.getTotalShares(), expectedTotalEther, 'total shares') assertBn(await token.balanceOf(treasuryAddr), new BN(0), 'treasury balance is zero') assertBn(await token.balanceOf(nodeOperator1.address), new BN(0), 'nodeOperator1 balance is zero') + }) it(`the first deposit gets deployed`, async () => { @@ -219,7 +215,6 @@ contract('Lido: rewards distribution math', (addresses) => { '0x', signatures ) - assertBn( await nodeOperatorsRegistry.getUnusedSigningKeyCount(0), 0, @@ -231,7 +226,7 @@ contract('Lido: rewards distribution math', (addresses) => { 'user1 balance is equal first reported value + their buffered deposit value' ) assertBn(await token.sharesOf(user1), ETH(34), 'user1 shares are equal to the first deposit') - assertBn(await token.totalSupply(), ETH(34), 'token total supply') + assertBn(await token.totalSupply(), ETH(34 + initialHolderBalanceETH), 'token total supply') assertBn(await token.balanceOf(treasuryAddr), ETH(0), 'treasury balance equals buffered value') assertBn(await token.balanceOf(nodeOperator1.address), new BN(0), 'nodeOperator1 balance is zero') @@ -245,70 +240,116 @@ contract('Lido: rewards distribution math', (addresses) => { const nodeOperator1TokenBefore = await token.balanceOf(operator_1) // for some reason there's nothing in this receipt's log, so we're not going to use it - const treasuryBalanceBefore = await pool.balanceOf(treasuryAddr) - const nodeOperatorsRegistryBalanceBefore = await pool.balanceOf(nodeOperatorsRegistry.address) const treasurySharesBefore = await pool.sharesOf(treasuryAddr) - const nodeOperatorsRegistrySharesBefore = await pool.sharesOf(nodeOperatorsRegistry.address) - - const receipt = await reportBeacon(1, reportingValue) - - const treasuryTokenDelta = (await pool.balanceOf(treasuryAddr)) - treasuryBalanceBefore - const treasurySharesDelta = (await pool.sharesOf(treasuryAddr)) - treasurySharesBefore - const nodeOperatorsRegistryTokenDelta = - (await pool.balanceOf(nodeOperatorsRegistry.address)) - nodeOperatorsRegistryBalanceBefore - const nodeOperatorsRegistrySharesDelta = - (await pool.sharesOf(nodeOperatorsRegistry.address)) - nodeOperatorsRegistrySharesBefore - - const awaitedDeltas = await getAwaitedFeesSharesTokensDeltas(profitAmount, prevTotalShares, 1) - const { - totalFeeToDistribute, - nodeOperatorsSharesToMint, - treasurySharesToMint, - nodeOperatorsFeeToMint, - treasuryFeeToMint - } = awaitedDeltas - - assert.equals(nodeOperatorsRegistrySharesDelta, nodeOperatorsSharesToMint, 'nodeOperator1 shares are correct') - assert.equals(treasurySharesDelta, treasurySharesToMint, 'treasury shares are correct') - assert.equalsDelta(nodeOperatorsFeeToMint, treasuryTokenDelta, 1, 'reported the expected total fee') - assert.equalsDelta(nodeOperatorsRegistryTokenDelta, nodeOperatorsFeeToMint, 1, 'treasury shares are correct') - - assert.emits(receipt, 'Transfer', { - to: nodeOperatorsRegistry.address, - value: nodeOperatorsFeeToMint - }) - assert.emits(receipt, 'Transfer', { - to: treasuryAddr, - value: nodeOperatorsFeeToMint - }) - assert.emits(receipt, 'TransferShares', { - to: nodeOperatorsRegistry.address, - sharesValue: nodeOperatorsSharesToMint - }) - assert.emits(receipt, 'TransferShares', { - to: treasuryAddr, - sharesValue: treasurySharesToMint - }) + const nodeOperator1SharesBefore = await pool.sharesOf(nodeOperator1.address) - assertBn( + const { submitDataTx, submitExtraDataTx } = await reportBeacon(1, reportingValue) + + const sharesMintedAsFees = calcSharesMintedAsFees( + profitAmount, + 1000, + 10000, + prevTotalShares, + await pool.getTotalPooledEther() + ) + + const totalFeeToDistribute = await pool.getPooledEthByShares(sharesMintedAsFees) + const nodeOperatorsSharesToMint = sharesMintedAsFees.div(toBN(2)) + const treasurySharesToMint = sharesMintedAsFees.sub(nodeOperatorsSharesToMint) + const nodeOperatorsFeeToMint = await pool.getPooledEthByShares(nodeOperatorsSharesToMint) + const treasuryFeeMint = await pool.getPooledEthByShares(treasurySharesToMint) + + assert.equalsDelta(await pool.sharesOf(nodeOperatorsRegistry.address), 0, 1) + assert.equals( + await pool.sharesOf(nodeOperator1.address), + nodeOperator1SharesBefore.add(nodeOperatorsSharesToMint), + 'nodeOperator1 shares are correct' + ) + assert.equals( + await pool.sharesOf(treasuryAddr), + treasurySharesBefore.add(treasurySharesToMint), + 'treasury shares are correct' + ) + assert.equalsDelta( + await pool.balanceOf(treasuryAddr), + await pool.getPooledEthByShares(treasurySharesBefore.add(treasurySharesToMint)), + 1, + 'reported the expected total fee' + ) + assert.equalsDelta( + await pool.balanceOf(nodeOperator1.address), + await pool.getPooledEthByShares(nodeOperator1SharesBefore.add(nodeOperatorsSharesToMint)), + 1, + 'reported the expected total fee' + ) + + assert.emits( + submitDataTx, + 'Transfer', + { + to: nodeOperatorsRegistry.address, + value: nodeOperatorsFeeToMint + }, + { abi: Lido.abi } + ) + assert.emits( + submitDataTx, + 'Transfer', + { + to: treasuryAddr, + value: treasuryFeeMint + }, + { abi: Lido.abi } + ) + assert.emits( + submitDataTx, + 'TransferShares', + { + to: nodeOperatorsRegistry.address, + sharesValue: nodeOperatorsSharesToMint + }, + { abi: Lido.abi } + ) + assert.emits( + submitDataTx, + 'TransferShares', + { + to: treasuryAddr, + sharesValue: treasurySharesToMint + }, + { abi: Lido.abi } + ) + assert.emits( + submitExtraDataTx, + 'Transfer', + { + from: nodeOperatorsRegistry.address, + to: nodeOperator1.address, + value: nodeOperatorsFeeToMint + }, + { abi: Lido.abi } + ) + assert.emits( + submitExtraDataTx, + 'TransferShares', + { + from: nodeOperatorsRegistry.address, + to: nodeOperator1.address, + sharesValue: nodeOperatorsSharesToMint.toString() + }, + { abi: Lido.abi } + ) + + assert.equalsDelta( await token.balanceOf(user1), - // 32 staked 2 buffered 1 profit - new BN(ETH(32 + 2 + 1)).sub(totalFeeToDistribute), + '34874285714285714286', + 1, 'user1 balance is equal first reported value + their buffered deposit value' ) assertBn(await token.sharesOf(user1), ETH(34), 'user1 shares are equal to the first deposit') - assertBn(await token.totalSupply(), ETH(35), 'token total supply') - - assertBn(await token.balanceOf(treasuryAddr), treasuryFeeToMint, 'treasury balance = fee') - assertBn(treasuryTokenDelta, treasuryFeeToMint, 'treasury balance = fee') - // kicks rewards distribution - await nodeOperatorsRegistry.finishUpdatingExitedValidatorsKeysCount({ from: voting }) - assertBn(await token.balanceOf(nodeOperator1.address), nodeOperatorsFeeToMint, 'nodeOperator1 balance = fee') - - const nodeOperator1TokenDelta = (await token.balanceOf(operator_1)) - nodeOperator1TokenBefore - // TODO merge: 1 wei - // assertBn(nodeOperator1TokenDelta, nodeOperatorsFeeToMint, 'nodeOperator1 balance = fee') + assertBn(await token.totalSupply(), ETH(36), 'token total supply') + assert.equals(await pool.getTotalShares(), prevTotalShares.add(sharesMintedAsFees)) }) it(`adds another node operator`, async () => { @@ -355,13 +396,17 @@ contract('Lido: rewards distribution math', (addresses) => { assertEvent(receipt, 'Transfer', { expectedArgs: { from: 0, to: user2, value: awaitedTokens } }) // 2 from the previous deposit of the first user - assertBn(await pool.getBufferedEther(), ETH(depositedEthValue + 2), `all the ether is buffered until deposit`) + assertBn( + await pool.getBufferedEther(), + ETH(depositedEthValue + 2 + initialHolderBalanceETH), + `all the ether is buffered until deposit` + ) // The amount of tokens corresponding to the deposited ETH value was minted to the user assertBn(await token.balanceOf(user2), awaitedTokens, 'user2 tokens') // current deposit + firstDeposit + first profit - assertBn(await token.totalSupply(), ETH(depositedEthValue + 34 + 1), 'token total supply') + assertBn(await token.totalSupply(), ETH(depositedEthValue + 34 + 1 + initialHolderBalanceETH), 'token total supply') // Total shares are equal to deposited eth before ratio change and fee mint assertBn(await token.getTotalShares(), sharesBefore.add(awaitedShares), 'total shares') }) @@ -447,124 +492,155 @@ contract('Lido: rewards distribution math', (addresses) => { it(`balances change correctly on second profit`, async () => { const profitAmountEth = 2 const profitAmount = ETH(profitAmountEth) - const bufferedAmount = ETH(2) - // first deposit + first profit + second deposit - // note no buffered eth values - const reportingValue = ETH(32 + 1 + 32 + profitAmountEth) + + const reportingValue = ETH(65 + profitAmountEth) const prevTotalShares = await pool.getTotalShares() - const treasuryBalanceBefore = await pool.balanceOf(treasuryAddr) - const nodeOperatorsRegistryBalanceBefore = await pool.balanceOf(nodeOperatorsRegistry.address) const treasurySharesBefore = await pool.sharesOf(treasuryAddr) - const nodeOperatorsRegistrySharesBefore = await pool.sharesOf(nodeOperatorsRegistry.address) - - const receipt = await reportBeacon(2, reportingValue) - const treasuryTokenDelta = (await pool.balanceOf(treasuryAddr)) - treasuryBalanceBefore - const treasurySharesDelta = (await pool.sharesOf(treasuryAddr)) - treasurySharesBefore - const nodeOperatorsRegistryTokenDelta = - (await pool.balanceOf(nodeOperatorsRegistry.address)) - nodeOperatorsRegistryBalanceBefore - const nodeOperatorsRegistrySharesDelta = - (await pool.sharesOf(nodeOperatorsRegistry.address)) - nodeOperatorsRegistrySharesBefore - - const awaitedDeltas = await getAwaitedFeesSharesTokensDeltas(profitAmount, prevTotalShares, 2) - const { - totalFeeToDistribute, - nodeOperatorsSharesToMint, - treasurySharesToMint, - nodeOperatorsFeeToMint, - treasuryFeeToMint - } = awaitedDeltas - - assert.equals(nodeOperatorsRegistrySharesDelta, nodeOperatorsSharesToMint, 'nodeOperator1 shares are correct') - assert.equals(treasurySharesDelta, treasurySharesToMint, 'treasury shares are correct') - assert.equalsDelta(nodeOperatorsFeeToMint, treasuryTokenDelta, 1, 'reported the expected total fee') - assert.equalsDelta(nodeOperatorsRegistryTokenDelta, nodeOperatorsFeeToMint, 1, 'treasury shares are correct') - - assert.emits(receipt, 'Transfer', { - to: nodeOperatorsRegistry.address, - value: nodeOperatorsFeeToMint - }) - assert.emits(receipt, 'Transfer', { - to: treasuryAddr, - value: nodeOperatorsFeeToMint - }) - assert.emits(receipt, 'TransferShares', { - to: nodeOperatorsRegistry.address, - sharesValue: nodeOperatorsSharesToMint - }) - assert.emits(receipt, 'TransferShares', { - to: treasuryAddr, - sharesValue: treasurySharesToMint - }) - // TODO merge: 1 wei - // assertBn(await token.balanceOf(nodeOperatorsRegistry.address), nodeOperatorsFeeToMint, 'nodeOperatorsRegistry balance = fee') - const nodeOperator1SharesBefore = await token.sharesOf(nodeOperator1.address) - const nodeOperator2SharesBefore = await token.sharesOf(nodeOperator2.address) - // kicks rewards distribution - await nodeOperatorsRegistry.finishUpdatingExitedValidatorsKeysCount({ from: voting }) + const nodeOperator1SharesBefore = await pool.sharesOf(nodeOperator1.address) + const nodeOperator2SharesBefore = await pool.sharesOf(nodeOperator2.address) - const nodeOperator1SharesDelta = (await token.sharesOf(nodeOperator1.address)).sub(nodeOperator1SharesBefore) - const nodeOperator2SharesDelta = (await token.sharesOf(nodeOperator2.address)).sub(nodeOperator2SharesBefore) - assertBn( - nodeOperator2SharesDelta, - await pool.sharesOf(nodeOperator2.address), - 'node operator 2 got only fee on balance' - ) + const { submitDataTx, submitExtraDataTx } = await reportBeacon(2, reportingValue) - assertBn( - nodeOperator1SharesDelta.add(nodeOperator2SharesDelta), - nodeOperatorsSharesToMint, + const sharesMintedAsFees = calcSharesMintedAsFees( + profitAmount, + 1000, + 10000, + prevTotalShares, + await pool.getTotalPooledEther() + ) + const nodeOperatorsSharesToMint = sharesMintedAsFees.div(toBN(2)) + const nodeOperatorSharesToMint = nodeOperatorsSharesToMint.div(toBN(2)) + const treasurySharesToMint = sharesMintedAsFees.sub(nodeOperatorsSharesToMint) + const nodeOperatorsFeeToMint = await pool.getPooledEthByShares(nodeOperatorsSharesToMint) + const nodeOperatorFeeToMint = await pool.getPooledEthByShares(nodeOperatorSharesToMint) + const treasuryFeeMint = await pool.getPooledEthByShares(treasurySharesToMint) + + assert.equalsDelta(await pool.sharesOf(nodeOperatorsRegistry.address), 0, 1) + + assert.equals( + await pool.sharesOf(nodeOperator1.address), + nodeOperator1SharesBefore.add(nodeOperatorSharesToMint), 'nodeOperator1 shares are correct' ) - assertBn(treasurySharesDelta, treasurySharesToMint, 'treasury shares are correct') - assertBn( - nodeOperator1SharesDelta, - nodeOperator2SharesDelta, - 'operators with equal amount of validators received equal shares' + assert.equals( + await pool.sharesOf(nodeOperator2.address), + nodeOperator2SharesBefore.add(nodeOperatorSharesToMint), + 'nodeOperator2 shares are correct' + ) + assert.equals( + await pool.sharesOf(treasuryAddr), + treasurySharesBefore.add(treasurySharesToMint), + 'treasury shares are correct' + ) + assert.equalsDelta( + await pool.balanceOf(treasuryAddr), + await pool.getPooledEthByShares(treasurySharesBefore.add(treasurySharesToMint)), + 1, + 'reported the expected treasury fee' + ) + assert.equalsDelta( + await pool.balanceOf(nodeOperator1.address), + await pool.getPooledEthByShares(nodeOperator1SharesBefore.add(nodeOperatorSharesToMint)), + 1, + 'reported the expected nodeOperator1 fee' + ) + assert.equalsDelta( + await pool.balanceOf(nodeOperator2.address), + await pool.getPooledEthByShares(nodeOperator2SharesBefore.add(nodeOperatorSharesToMint)), + 1, + 'reported the expected nodeOperator2 fee' ) - const reportingValueBN = new BN(reportingValue) - const totalSupply = reportingValueBN.add(new BN(bufferedAmount)) - - const treasuryBalanceAfter = valuesAfter[0] - const treasuryShareBefore = valuesBefore[1] - const user1BalanceAfter = valuesAfter[2] - const user1SharesBefore = valuesBefore[3] - const user2BalanceAfter = valuesAfter[4] - const user2SharesBefore = valuesBefore[5] - const singleNodeOperatorFeeShare = nodeOperatorsSharesToMint.div(new BN(2)) - - const awaitingTotalShares = prevTotalShares.add(sharesToMint) - - assertBn( - await token.balanceOf(nodeOperator1.address), - nodeOperator1SharesBefore.add(singleNodeOperatorFeeShare).mul(totalSupply).div(awaitingTotalShares), - `first node operator token balance is correct` + assert.emits( + submitDataTx, + 'Transfer', + { + to: nodeOperatorsRegistry.address, + value: nodeOperatorsFeeToMint + }, + { abi: Lido.abi } ) - assertBn( - await token.balanceOf(nodeOperator2.address), - nodeOperator2SharesBefore.add(singleNodeOperatorFeeShare).mul(totalSupply).div(awaitingTotalShares), - `first node operator token balance is correct` + assert.emits( + submitDataTx, + 'Transfer', + { + to: treasuryAddr, + value: treasuryFeeMint + }, + { abi: Lido.abi } ) - assertBn( - treasuryBalanceAfter, - treasuryShareBefore.add(treasurySharesToMint).mul(totalSupply).div(awaitingTotalShares), - 'treasury token balance changed correctly' + assert.emits( + submitDataTx, + 'TransferShares', + { + to: nodeOperatorsRegistry.address, + sharesValue: nodeOperatorsSharesToMint + }, + { abi: Lido.abi } ) - assertBn(user1SharesDelta, new BN(0), `user1 didn't get any shares from profit`) - assertBn( - user1BalanceAfter, - user1SharesBefore.mul(totalSupply).div(awaitingTotalShares), - `user1 token balance increased` + assert.emits( + submitDataTx, + 'TransferShares', + { + to: treasuryAddr, + sharesValue: treasurySharesToMint + }, + { abi: Lido.abi } ) - assertBn(user2SharesDelta, new BN(0), `user2 didn't get any shares from profit`) - assertBn( - user2BalanceAfter, - user2SharesBefore.mul(totalSupply).div(awaitingTotalShares), - `user2 token balance increased` + assert.emits( + submitExtraDataTx, + 'Transfer', + { + from: nodeOperatorsRegistry.address, + to: nodeOperator1.address, + value: nodeOperatorFeeToMint + }, + { abi: Lido.abi } + ) + assert.emits( + submitExtraDataTx, + 'TransferShares', + { + from: nodeOperatorsRegistry.address, + to: nodeOperator1.address, + sharesValue: nodeOperatorSharesToMint + }, + { abi: Lido.abi } + ) + assert.emits( + submitExtraDataTx, + 'Transfer', + { + from: nodeOperatorsRegistry.address, + to: nodeOperator2.address, + value: nodeOperatorFeeToMint + }, + { abi: Lido.abi } + ) + assert.emits( + submitExtraDataTx, + 'TransferShares', + { + from: nodeOperatorsRegistry.address, + to: nodeOperator2.address, + sharesValue: nodeOperatorSharesToMint + }, + { abi: Lido.abi } + ) + + assert.equalsDelta( + await token.balanceOf(user1), + '35797428571428571429', + 1, + 'user1 balance is equal first reported value + their buffered deposit value' ) + assertBn(await token.sharesOf(user1), ETH(34), 'user1 shares are equal to the first deposit') + + assertBn(await token.totalSupply(), ETH(70), 'token total supply') + assert.equals(await pool.getTotalShares(), prevTotalShares.add(sharesMintedAsFees)) }) it(`add another staking module`, async () => { @@ -573,8 +649,8 @@ contract('Lido: rewards distribution math', (addresses) => { 'Curated limited', anotherCuratedModule.address, 5_000, // 50 % _targetShare - 100, // 1 % _moduleFee - 100, // 1 % _treasuryFee + 2000, // 20 % _moduleFee + 2000, // 20 % _treasuryFee { from: voting } ) @@ -582,23 +658,15 @@ contract('Lido: rewards distribution math', (addresses) => { assert(modulesList.length, 2, 'module added') - const operator = { - name: 'operator', - address: operator_2, - validators: [...Array(10).keys()].map((i) => ({ - key: pad('0xaa01' + i.toString(16), 48), - sig: pad('0x' + i.toString(16), 96) - })) - } const validatorsCount = 10 - await anotherCuratedModule.addNodeOperator(operator.name, operator.address, { from: voting }) + await anotherCuratedModule.addNodeOperator(nodeOperator3.name, nodeOperator3.address, { from: voting }) await anotherCuratedModule.addSigningKeysOperatorBH( 0, validatorsCount, - hexConcat(...operator.validators.map((v) => v.key)), - hexConcat(...operator.validators.map((v) => v.sig)), + hexConcat(...nodeOperator3.validators.map((v) => v.key)), + hexConcat(...nodeOperator3.validators.map((v) => v.sig)), { - from: operator.address + from: nodeOperator3.address } ) await anotherCuratedModule.setNodeOperatorStakingLimit(0, validatorsCount, { from: voting }) @@ -653,138 +721,140 @@ contract('Lido: rewards distribution math', (addresses) => { ) assertBn(await token.sharesOf(user1), user1SharesBefore, 'user1 shares are equal to the first deposit') assertBn(await token.totalSupply(), totalSupplyBefore, 'token total supply') - assertBn(await token.getBufferedEther(), ETH(2), '') + assertBn(await token.getBufferedEther(), ETH(3), '') }) it(`rewards distribution`, async () => { const bufferedBefore = await token.getBufferedEther() + const totalSharesBefore = await token.getTotalShares() const totalPooledEtherBefore = await token.getTotalPooledEther() - // FIXME: oracle doesn't support reporting anything smaller than 1 gwei, here we're trying to report 2 wei - const newBeaconBalance = totalPooledEtherBefore.sub(bufferedBefore).add(new BN(2)) + const rewardsAmount = ETH(1) + const newBeaconBalance = totalPooledEtherBefore.sub(bufferedBefore).add(toBN(rewardsAmount)) - const firstModuleSharesBefore = await token.sharesOf(nodeOperatorsRegistry.address) - const secondModuleSharesBefore = await token.sharesOf(anotherCuratedModule.address) const treasurySharesBefore = await await token.sharesOf(treasuryAddr) + const nodeOperator1SharesBefore = await await token.sharesOf(nodeOperator1.address) + const nodeOperator2SharesBefore = await await token.sharesOf(nodeOperator2.address) + const nodeOperator3SharesBefore = await await token.sharesOf(nodeOperator3.address) await reportBeacon(3, newBeaconBalance) - assert.equalsDelta(await token.totalSupply(), newBeaconBalance.add(bufferedBefore), 1, 'token total supply') + assert.equals(await token.totalSupply(), totalPooledEtherBefore.add(toBN(rewardsAmount)), 'token total supply') - const rewardsToDistribute = await token.getSharesByPooledEth( - newBeaconBalance.add(bufferedBefore).sub(totalPooledEtherBefore) + const sharesMintedAsFees = calcSharesMintedAsFees( + rewardsAmount, + 2000, + 10000, + totalSharesBefore, + await pool.getTotalPooledEther() ) + const treasureRewardsShares = sharesMintedAsFees.div(toBN(2)) + const nodeOperator1RewardsShares = sharesMintedAsFees.div(toBN(12)) + const nodeOperator2RewardsShares = sharesMintedAsFees.div(toBN(12)) + const nodeOperator3RewardsShares = sharesMintedAsFees.div(toBN(3)) - const { treasuryFee } = await stakingRouter.getStakingFeeAggregateDistribution() - const { stakingModuleFees, precisionPoints } = await stakingRouter.getStakingRewardsDistribution() - const [firstModuleFee, secondModuleFee] = stakingModuleFees - const expectedRewardsDistribution = { - firstModule: rewardsToDistribute.mul(firstModuleFee).div(precisionPoints), - secondModule: rewardsToDistribute.mul(secondModuleFee).div(precisionPoints), - treasury: rewardsToDistribute.mul(treasuryFee).div(precisionPoints) - } + assert.equalsDelta(await token.sharesOf(nodeOperatorsRegistry.address), 0, 1, 'first module balance') + assert.equalsDelta(await token.sharesOf(anotherCuratedModule.address), 0, 1, 'second module balance') - const firstModuleSharesAfter = await token.sharesOf(nodeOperatorsRegistry.address) - const secondModuleSharesAfter = await token.sharesOf(anotherCuratedModule.address) - const treasurySharesAfter = await await token.sharesOf(treasuryAddr) - - assertBn( - firstModuleSharesAfter, - firstModuleSharesBefore.add(expectedRewardsDistribution.firstModule), - 'first module balance' + assert.equalsDelta( + await pool.sharesOf(treasuryAddr), + treasurySharesBefore.add(treasureRewardsShares), + 1, + 'treasury shares' ) - assertBn( - secondModuleSharesAfter, - secondModuleSharesBefore.add(expectedRewardsDistribution.secondModule), - 'second module balance' + assert.equals( + await pool.sharesOf(nodeOperator1.address), + nodeOperator1SharesBefore.add(nodeOperator1RewardsShares), + 'nodeOperator1 shares' + ) + assert.equals( + await pool.sharesOf(nodeOperator2.address), + nodeOperator2SharesBefore.add(nodeOperator2RewardsShares), + 'nodeOperator2 shares' + ) + assert.equals( + await pool.sharesOf(nodeOperator3.address), + nodeOperator3SharesBefore.add(nodeOperator3RewardsShares), + 'nodeOperator3 shares' ) - assertBn(treasurySharesAfter, treasurySharesBefore.add(expectedRewardsDistribution.treasury), 'treasury balance') }) it(`module rewards should received by treasury if module stopped`, async () => { const [firstModule] = await stakingRouter.getStakingModules() + const totalSharesBefore = await token.getTotalShares() const totalPooledEtherBefore = await token.getTotalPooledEther() const bufferedBefore = await token.getBufferedEther() - // FIXME: oracle doesn't support reporting anything smaller than 1 gwei, here we're trying to report 1 wei - const newBeaconBalance = totalPooledEtherBefore.sub(bufferedBefore).add(new BN(1)) + const rewardsAmount = ETH(1) + const newBeaconBalance = totalPooledEtherBefore.sub(bufferedBefore).add(toBN(rewardsAmount)) await stakingRouter.setStakingModuleStatus(firstModule.id, StakingModuleStatus.Stopped, { from: voting }) - const firstModuleSharesBefore = await token.sharesOf(nodeOperatorsRegistry.address) - const secondModuleSharesBefore = await token.sharesOf(anotherCuratedModule.address) const treasurySharesBefore = await await token.sharesOf(treasuryAddr) + const nodeOperator1SharesBefore = await await token.sharesOf(nodeOperator1.address) + const nodeOperator2SharesBefore = await await token.sharesOf(nodeOperator2.address) + const nodeOperator3SharesBefore = await await token.sharesOf(nodeOperator3.address) await reportBeacon(3, newBeaconBalance) - assert.equalsDelta(await token.totalSupply(), newBeaconBalance.add(bufferedBefore), 1, 'token total supply') + assert.equals(await token.totalSupply(), totalPooledEtherBefore.add(toBN(rewardsAmount)), 'token total supply') - const rewardsToDistribute = await token.getSharesByPooledEth( - newBeaconBalance.add(bufferedBefore).sub(totalPooledEtherBefore) + const sharesMintedAsFees = calcSharesMintedAsFees( + rewardsAmount, + 2000, + 10000, + totalSharesBefore, + await pool.getTotalPooledEther() ) - const { treasuryFee } = await stakingRouter.getStakingFeeAggregateDistribution() - const { stakingModuleFees, precisionPoints } = await stakingRouter.getStakingRewardsDistribution() - const [firstModuleFee, secondModuleFee] = stakingModuleFees - const expectedRewardsDistribution = { - firstModule: new BN(0), - secondModule: rewardsToDistribute.mul(secondModuleFee).div(precisionPoints), - treasury: rewardsToDistribute.mul(treasuryFee.add(firstModuleFee)).div(precisionPoints) - } - - const firstModuleSharesAfter = await token.sharesOf(nodeOperatorsRegistry.address) - const secondModuleSharesAfter = await token.sharesOf(anotherCuratedModule.address) - const treasurySharesAfter = await token.sharesOf(treasuryAddr) - - assertBn( - firstModuleSharesAfter, - firstModuleSharesBefore.add(expectedRewardsDistribution.firstModule), - 'first module balance' - ) - assertBn( - secondModuleSharesAfter, - secondModuleSharesBefore.add(expectedRewardsDistribution.secondModule), - 'second module balance' + const treasureRewardsShares = sharesMintedAsFees.mul(toBN(2)).div(toBN(3)) + const nodeOperator3RewardsShares = sharesMintedAsFees.div(toBN(3)) + + assert.equalsDelta(await token.sharesOf(nodeOperatorsRegistry.address), 0, 1, 'first module balance') + assert.equalsDelta(await token.sharesOf(anotherCuratedModule.address), 0, 1, 'second module balance') + + assert.equals(await pool.sharesOf(treasuryAddr), treasurySharesBefore.add(treasureRewardsShares), 'treasury shares') + assert.equals(await pool.sharesOf(nodeOperator1.address), nodeOperator1SharesBefore, 'nodeOperator1 shares') + assert.equals(await pool.sharesOf(nodeOperator2.address), nodeOperator2SharesBefore, 'nodeOperator2 shares') + assert.equals( + await pool.sharesOf(nodeOperator3.address), + nodeOperator3SharesBefore.add(nodeOperator3RewardsShares), + 'nodeOperator3 shares' ) - assertBn(treasurySharesAfter, treasurySharesBefore.add(expectedRewardsDistribution.treasury), 'treasury balance') }) - // test multiple staking modules reward distribution - async function getAwaitedFeesSharesTokensDeltas(profitAmount, prevTotalShares, validatorsCount) { - const totalPooledEther = await pool.getTotalPooledEther() - const totalShares = await pool.getTotalShares() - - const totalFeeToDistribute = new BN(profitAmount).mul(new BN(totalFeePoints)).div(tenKBN) + it(`module rewards should received by treasury if all modules stopped`, async () => { + const [, secondModule] = await stakingRouter.getStakingModules() + const totalSharesBefore = await token.getTotalShares() + const totalPooledEtherBefore = await token.getTotalPooledEther() + const bufferedBefore = await token.getBufferedEther() + const rewardsAmount = ETH(1) + const newBeaconBalance = totalPooledEtherBefore.sub(bufferedBefore).add(toBN(rewardsAmount)) - const sharesToMintSol = new BN(profitAmount) - .mul(new BN(totalFeePoints)) - .mul(prevTotalShares) - .div(totalPooledEther.mul(tenKBN).sub(new BN(profitAmount).mul(new BN(totalFeePoints)))) + await stakingRouter.setStakingModuleStatus(secondModule.id, StakingModuleStatus.Stopped, { from: voting }) - const sharesToMint = totalFeeToDistribute.mul(prevTotalShares).div(totalPooledEther.sub(totalFeeToDistribute)) + const treasurySharesBefore = await await token.sharesOf(treasuryAddr) + const nodeOperator1SharesBefore = await await token.sharesOf(nodeOperator1.address) + const nodeOperator2SharesBefore = await await token.sharesOf(nodeOperator2.address) + const nodeOperator3SharesBefore = await await token.sharesOf(nodeOperator3.address) - assert.equals(sharesToMintSol, sharesToMint) + await reportBeacon(3, newBeaconBalance) - const nodeOperatorsSharesToMint = sharesToMint.mul(new BN(nodeOperatorsFeePoints)).div(tenKBN) - const treasurySharesToMint = sharesToMint.sub(nodeOperatorsSharesToMint) + assert.equals(await token.totalSupply(), totalPooledEtherBefore.add(toBN(rewardsAmount)), 'token total supply') - const validatorsCountBN = new BN(validatorsCount) + const sharesMintedAsFees = calcSharesMintedAsFees( + rewardsAmount, + 2000, + 10000, + totalSharesBefore, + await pool.getTotalPooledEther() + ) - const nodeOperatorsFeeToMint = nodeOperatorsSharesToMint - .mul(totalPooledEther) - .div(totalShares) - .div(validatorsCountBN) - .mul(validatorsCountBN) - const treasuryFeeToMint = treasurySharesToMint.mul(totalPooledEther).div(totalShares) + assert.equalsDelta(await token.sharesOf(nodeOperatorsRegistry.address), 0, 1, 'first module balance') + assert.equalsDelta(await token.sharesOf(anotherCuratedModule.address), 0, 1, 'second module balance') - return { - totalPooledEther, - totalShares, - totalFeeToDistribute, - sharesToMint, - nodeOperatorsSharesToMint, - treasurySharesToMint, - nodeOperatorsFeeToMint, - treasuryFeeToMint - } - } + assert.equals(await pool.sharesOf(treasuryAddr), treasurySharesBefore.add(sharesMintedAsFees), 'treasury shares') + assert.equals(await pool.sharesOf(nodeOperator1.address), nodeOperator1SharesBefore, 'nodeOperator1 shares') + assert.equals(await pool.sharesOf(nodeOperator2.address), nodeOperator2SharesBefore, 'nodeOperator2 shares') + assert.equals(await pool.sharesOf(nodeOperator3.address), nodeOperator3SharesBefore, 'nodeOperator3 shares') + }) async function getSharesTokenDeltas(tx, ...addresses) { const valuesBefore = await Promise.all(addresses.flatMap((addr) => [token.balanceOf(addr), token.sharesOf(addr)])) @@ -793,20 +863,4 @@ contract('Lido: rewards distribution math', (addresses) => { return [{ receipt, valuesBefore, valuesAfter }, valuesAfter.map((val, i) => val.sub(valuesBefore[i]))] } - async function readLastPoolEventLog() { - const events = await pool.getPastEvents('Transfer') - let reportedMintAmount = new BN(0) - const tos = [] - const values = [] - events.forEach(({ args }) => { - reportedMintAmount = reportedMintAmount.add(args.value) - tos.push(args.to) - values.push(args.value) - }) - return { - reportedMintAmount, - tos, - values - } - } }) diff --git a/yarn.lock b/yarn.lock index ce3c533ab..48d24ce95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1787,87 +1787,6 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/block@npm:^3.5.0, @ethereumjs/block@npm:^3.6.2, @ethereumjs/block@npm:^3.6.3": - version: 3.6.3 - resolution: "@ethereumjs/block@npm:3.6.3" - dependencies: - "@ethereumjs/common": ^2.6.5 - "@ethereumjs/tx": ^3.5.2 - ethereumjs-util: ^7.1.5 - merkle-patricia-tree: ^4.2.4 - checksum: c0f44f3fabbec6a0040dcf5f2cc13619e2130187232e19f3e4d92426d1b6f47d245e730bf60ee717c3d09af12443b94383c0b58d114f042bfce2b43c59981ab8 - languageName: node - linkType: hard - -"@ethereumjs/blockchain@npm:^5.5.2, @ethereumjs/blockchain@npm:^5.5.3": - version: 5.5.3 - resolution: "@ethereumjs/blockchain@npm:5.5.3" - dependencies: - "@ethereumjs/block": ^3.6.2 - "@ethereumjs/common": ^2.6.4 - "@ethereumjs/ethash": ^1.1.0 - debug: ^4.3.3 - ethereumjs-util: ^7.1.5 - level-mem: ^5.0.1 - lru-cache: ^5.1.1 - semaphore-async-await: ^1.5.1 - checksum: 2524125d5cb7ac26c4ada4edbe4ba36165ad2dd2835719f469272e1948b9219495b3d3ebf765a2f8a9cb9cc05fcc4de6cc837850b6aa77737193c2e1acd2d763 - languageName: node - linkType: hard - -"@ethereumjs/common@npm:^2.6.4, @ethereumjs/common@npm:^2.6.5": - version: 2.6.5 - resolution: "@ethereumjs/common@npm:2.6.5" - dependencies: - crc-32: ^1.2.0 - ethereumjs-util: ^7.1.5 - checksum: 86792c74eeead4e699b97d61ea23a9eddf48ed798922ddb77f570ac47e81e02d19be350e5308d24ee13ba57a78034161c0c9412d32f226ef794072f803aa32f8 - languageName: node - linkType: hard - -"@ethereumjs/ethash@npm:^1.1.0": - version: 1.1.0 - resolution: "@ethereumjs/ethash@npm:1.1.0" - dependencies: - "@ethereumjs/block": ^3.5.0 - "@types/levelup": ^4.3.0 - buffer-xor: ^2.0.1 - ethereumjs-util: ^7.1.1 - miller-rabin: ^4.0.0 - checksum: dc9ca39e2b5f19927d982ad73015446d589b01daed78aee8c6c82e36eabd8ef916eb0c55b1f95d0c7377631a59b8fb414fcee83ecac9c3f067660d7053cfc480 - languageName: node - linkType: hard - -"@ethereumjs/tx@npm:^3.5.1, @ethereumjs/tx@npm:^3.5.2": - version: 3.5.2 - resolution: "@ethereumjs/tx@npm:3.5.2" - dependencies: - "@ethereumjs/common": ^2.6.4 - ethereumjs-util: ^7.1.5 - checksum: 6162af9474ed673a8f57121dec88958ad0c6c26dd9158c262253397a6ce542904883971b42b065c608568761a18039c784c5f705c70e8fda28b0c836d48fbc5b - languageName: node - linkType: hard - -"@ethereumjs/vm@npm:^5.9.0": - version: 5.9.3 - resolution: "@ethereumjs/vm@npm:5.9.3" - dependencies: - "@ethereumjs/block": ^3.6.3 - "@ethereumjs/blockchain": ^5.5.3 - "@ethereumjs/common": ^2.6.5 - "@ethereumjs/tx": ^3.5.2 - async-eventemitter: ^0.2.4 - core-js-pure: ^3.0.1 - debug: ^4.3.3 - ethereumjs-util: ^7.1.5 - functional-red-black-tree: ^1.0.1 - mcl-wasm: ^0.7.1 - merkle-patricia-tree: ^4.2.4 - rustbn.js: ~0.2.0 - checksum: 4d259a2e660cf9b36c610f8f2c022945258cf4a1bc55a68a9340e27e9f2ba6f3460d911aa4ec5b094751af33b90ad2b98cfc4111b02bdaed319c98705f3dbb26 - languageName: node - linkType: hard - "@ethersproject/abi@npm:5.0.0-beta.153": version: 5.0.0-beta.153 resolution: "@ethersproject/abi@npm:5.0.0-beta.153" @@ -1885,7 +1804,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abi@npm:5.7.0, @ethersproject/abi@npm:^5.0.0-beta.146, @ethersproject/abi@npm:^5.7.0": +"@ethersproject/abi@npm:5.7.0, @ethersproject/abi@npm:^5.0.0-beta.146, @ethersproject/abi@npm:^5.0.9, @ethersproject/abi@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abi@npm:5.7.0" dependencies: @@ -3648,21 +3567,22 @@ __metadata: ethereumjs-testrpc-sc: ^6.5.1-sc.1 ethereumjs-util: ^7.0.8 ethers: ^5.1.4 - hardhat: 2.9.9 + hardhat: 2.12.7 hardhat-contract-sizer: ^2.5.0 - hardhat-gas-reporter: 1.0.8 + hardhat-gas-reporter: ^1.0.8 hardhat-ignore-warnings: "skozin/hardhat-ignore-warnings#0ecf2ae85bddb83594193ee5c463cb4ae54cde7d" husky: ^8.0.2 ipfs-http-client: ^55.0.0 lerna: ^3.22.1 lint-staged: ">=10" + minimatch: ^6.2.0 node-gyp: ^8.4.1 openzeppelin-solidity: 2.0.0 prettier: ^2.8.1 prettier-plugin-solidity: ^1.1.0 solhint: ^3.3.7 solhint-plugin-lido: ^0.0.4 - solidity-coverage: ^0.7.22 + solidity-coverage: ^0.8.2 truffle: ^5.1.43 truffle-extract: ^1.2.1 truffle-flattener: ^1.5.0 @@ -3767,6 +3687,271 @@ __metadata: languageName: node linkType: hard +"@nomicfoundation/ethereumjs-block@npm:^4.0.0": + version: 4.0.0 + resolution: "@nomicfoundation/ethereumjs-block@npm:4.0.0" + dependencies: + "@nomicfoundation/ethereumjs-common": ^3.0.0 + "@nomicfoundation/ethereumjs-rlp": ^4.0.0 + "@nomicfoundation/ethereumjs-trie": ^5.0.0 + "@nomicfoundation/ethereumjs-tx": ^4.0.0 + "@nomicfoundation/ethereumjs-util": ^8.0.0 + ethereum-cryptography: 0.1.3 + checksum: c034da8adb519fb592de4cb927381759bb79a5597e208e8efff446b5e1058b83e11af6e0f6810e2f473523d7d381572b9ec534630dd6e939d56dfafc10ed2939 + languageName: node + linkType: hard + +"@nomicfoundation/ethereumjs-blockchain@npm:^6.0.0": + version: 6.0.0 + resolution: "@nomicfoundation/ethereumjs-blockchain@npm:6.0.0" + dependencies: + "@nomicfoundation/ethereumjs-block": ^4.0.0 + "@nomicfoundation/ethereumjs-common": ^3.0.0 + "@nomicfoundation/ethereumjs-ethash": ^2.0.0 + "@nomicfoundation/ethereumjs-rlp": ^4.0.0 + "@nomicfoundation/ethereumjs-trie": ^5.0.0 + "@nomicfoundation/ethereumjs-util": ^8.0.0 + abstract-level: ^1.0.3 + debug: ^4.3.3 + ethereum-cryptography: 0.1.3 + level: ^8.0.0 + lru-cache: ^5.1.1 + memory-level: ^1.0.0 + checksum: 482149f2cfd8110c1e74c2e1cc5c578778e921dc6a8a6d98ecfebeacf713f9beee2d27c37b642ca553158985a69bc8e04806eac059d8d6a2ba66f6c85a066ba2 + languageName: node + linkType: hard + +"@nomicfoundation/ethereumjs-common@npm:^3.0.0": + version: 3.0.0 + resolution: "@nomicfoundation/ethereumjs-common@npm:3.0.0" + dependencies: + "@nomicfoundation/ethereumjs-util": ^8.0.0 + crc-32: ^1.2.0 + checksum: 2381dd1223524e386f145cca4508b2c613afda39cbb9a0a146120909588cc59700edecc9fa6767fcc867f5b3d674b6356411e8614b8940473ef3e9d8dcba5b5f + languageName: node + linkType: hard + +"@nomicfoundation/ethereumjs-ethash@npm:^2.0.0": + version: 2.0.0 + resolution: "@nomicfoundation/ethereumjs-ethash@npm:2.0.0" + dependencies: + "@nomicfoundation/ethereumjs-block": ^4.0.0 + "@nomicfoundation/ethereumjs-rlp": ^4.0.0 + "@nomicfoundation/ethereumjs-util": ^8.0.0 + abstract-level: ^1.0.3 + bigint-crypto-utils: ^3.0.23 + ethereum-cryptography: 0.1.3 + checksum: ab2c3abdab6ec1dd1c858b8b11f97feee4ced6c9f021adcb3ba8c560839523d9cab2e2368f1188d9db12fd48202fd46abea9d7e09448b7ce19dc7573d822ecc5 + languageName: node + linkType: hard + +"@nomicfoundation/ethereumjs-evm@npm:^1.0.0": + version: 1.0.0 + resolution: "@nomicfoundation/ethereumjs-evm@npm:1.0.0" + dependencies: + "@nomicfoundation/ethereumjs-common": ^3.0.0 + "@nomicfoundation/ethereumjs-util": ^8.0.0 + "@types/async-eventemitter": ^0.2.1 + async-eventemitter: ^0.2.4 + debug: ^4.3.3 + ethereum-cryptography: 0.1.3 + mcl-wasm: ^0.7.1 + rustbn.js: ~0.2.0 + checksum: d58581a7ac6c01bbfebb5d60b49fa0c3463bcf5a02d6e3a0da58f631bffb6ac3a4b48c302b3707541047f26f1c6658aa5719b91367c16f41d3fb2a3b28f31cf4 + languageName: node + linkType: hard + +"@nomicfoundation/ethereumjs-rlp@npm:^4.0.0, @nomicfoundation/ethereumjs-rlp@npm:^4.0.0-beta.2": + version: 4.0.0 + resolution: "@nomicfoundation/ethereumjs-rlp@npm:4.0.0" + bin: + rlp: bin/rlp + checksum: feb52ec7c8f2a13463042441e7b217e9818a85c58473f04c8f1ba67f9039e312895e18bc4dbb908a104aca473b152bd2288c7342a8c8baa483f67af65d4aa1de + languageName: node + linkType: hard + +"@nomicfoundation/ethereumjs-statemanager@npm:^1.0.0": + version: 1.0.0 + resolution: "@nomicfoundation/ethereumjs-statemanager@npm:1.0.0" + dependencies: + "@nomicfoundation/ethereumjs-common": ^3.0.0 + "@nomicfoundation/ethereumjs-rlp": ^4.0.0 + "@nomicfoundation/ethereumjs-trie": ^5.0.0 + "@nomicfoundation/ethereumjs-util": ^8.0.0 + debug: ^4.3.3 + ethereum-cryptography: 0.1.3 + functional-red-black-tree: ^1.0.1 + checksum: 4657e551d96a5f7ef93c630a514bb912531e8253c0a9ccb9d2b4253aaebf120dbc0a95c00de5583b8f712ca6066d45020f8667fa526c8dd4745609ac829b2a17 + languageName: node + linkType: hard + +"@nomicfoundation/ethereumjs-trie@npm:^5.0.0": + version: 5.0.0 + resolution: "@nomicfoundation/ethereumjs-trie@npm:5.0.0" + dependencies: + "@nomicfoundation/ethereumjs-rlp": ^4.0.0 + "@nomicfoundation/ethereumjs-util": ^8.0.0 + ethereum-cryptography: 0.1.3 + readable-stream: ^3.6.0 + checksum: d6963e4a20e5c67bb926fbb63f8b1a6a273590d7781fb63e774534b93c62e08d871a60fbc0f1aabce2934f02796210757d5223986b572261056c13e3ea5b5961 + languageName: node + linkType: hard + +"@nomicfoundation/ethereumjs-tx@npm:^4.0.0": + version: 4.0.0 + resolution: "@nomicfoundation/ethereumjs-tx@npm:4.0.0" + dependencies: + "@nomicfoundation/ethereumjs-common": ^3.0.0 + "@nomicfoundation/ethereumjs-rlp": ^4.0.0 + "@nomicfoundation/ethereumjs-util": ^8.0.0 + ethereum-cryptography: 0.1.3 + checksum: fd6a09467a5e4fd2ec0fbf14d26487abb1ae3e1f6e3b1343860e5c2f8968359067f26ceb571271059bc5c33ae4c16ae346816b55c460177732677a4f6181e2f2 + languageName: node + linkType: hard + +"@nomicfoundation/ethereumjs-util@npm:^8.0.0": + version: 8.0.0 + resolution: "@nomicfoundation/ethereumjs-util@npm:8.0.0" + dependencies: + "@nomicfoundation/ethereumjs-rlp": ^4.0.0-beta.2 + ethereum-cryptography: 0.1.3 + checksum: d75c50e29dd52d7ea7a9d9c8522e1c4c00de023cd64a9bde0141a971ae3bf1a8762594f8e93ac43825fc5fbb954b175a52fe9d5b44236db98d60385cb2942773 + languageName: node + linkType: hard + +"@nomicfoundation/ethereumjs-vm@npm:^6.0.0": + version: 6.0.0 + resolution: "@nomicfoundation/ethereumjs-vm@npm:6.0.0" + dependencies: + "@nomicfoundation/ethereumjs-block": ^4.0.0 + "@nomicfoundation/ethereumjs-blockchain": ^6.0.0 + "@nomicfoundation/ethereumjs-common": ^3.0.0 + "@nomicfoundation/ethereumjs-evm": ^1.0.0 + "@nomicfoundation/ethereumjs-rlp": ^4.0.0 + "@nomicfoundation/ethereumjs-statemanager": ^1.0.0 + "@nomicfoundation/ethereumjs-trie": ^5.0.0 + "@nomicfoundation/ethereumjs-tx": ^4.0.0 + "@nomicfoundation/ethereumjs-util": ^8.0.0 + "@types/async-eventemitter": ^0.2.1 + async-eventemitter: ^0.2.4 + debug: ^4.3.3 + ethereum-cryptography: 0.1.3 + functional-red-black-tree: ^1.0.1 + mcl-wasm: ^0.7.1 + rustbn.js: ~0.2.0 + checksum: 4cb2de3821f5b3b649e3652473aa1e2661fc8f958770720a027cfdeb8887b8946e9ad85f98703e44e2d23b53def89fec73e5192de6b9ef0243ccc33dfd6975f8 + languageName: node + linkType: hard + +"@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.0": + version: 0.1.0 + resolution: "@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.0" + checksum: e6db014b5c63c5d32bbde1370dea6201f62d1def69cf5387b0280b95c0046e491cbe85fc6a835bea30590ed62ef972319f1a70097cc208a8bd88dd983a93d23b + languageName: node + linkType: hard + +"@nomicfoundation/solidity-analyzer-darwin-x64@npm:0.1.0": + version: 0.1.0 + resolution: "@nomicfoundation/solidity-analyzer-darwin-x64@npm:0.1.0" + checksum: 1f5c8a592e70b93fc25de560460e23dd5fea90c51196d5d480c0c2f5b758de7fb409fe34f2c9c5edf9f80ad2bb9dfd96a3dbbffbda5819a1da80a6d31590f45a + languageName: node + linkType: hard + +"@nomicfoundation/solidity-analyzer-freebsd-x64@npm:0.1.0": + version: 0.1.0 + resolution: "@nomicfoundation/solidity-analyzer-freebsd-x64@npm:0.1.0" + checksum: 6e697edc5a68f62eeac141dfe26ed3ba21fd363b3c061ce8b7a00b154d372d3d627c3deb71609788af66a542a8570f9418dbb8de969489110279daa6bf8d8040 + languageName: node + linkType: hard + +"@nomicfoundation/solidity-analyzer-linux-arm64-gnu@npm:0.1.0": + version: 0.1.0 + resolution: "@nomicfoundation/solidity-analyzer-linux-arm64-gnu@npm:0.1.0" + checksum: 2c7d290e4d89164a7b695343a04462ebd6ce0d3dfa39151bd2c53f515fbc968d41a0850a3e1a8ae70dc1ff12ecd1c2b49d34b07b0ecb3866210cc81123ac3a48 + languageName: node + linkType: hard + +"@nomicfoundation/solidity-analyzer-linux-arm64-musl@npm:0.1.0": + version: 0.1.0 + resolution: "@nomicfoundation/solidity-analyzer-linux-arm64-musl@npm:0.1.0" + checksum: 07cf45eba4cd5ff29ee627dbea848b9cada734f19895f88934b772608893a31af0535c5e6971624ab95b53ab4fa6668271ece06c6d514a2c751d47973a3bfdd1 + languageName: node + linkType: hard + +"@nomicfoundation/solidity-analyzer-linux-x64-gnu@npm:0.1.0": + version: 0.1.0 + resolution: "@nomicfoundation/solidity-analyzer-linux-x64-gnu@npm:0.1.0" + checksum: 13af17479644b1cc95f8c6c572e113aef1abbab153e3ce92c1090e89989235ed77e8ff64ceb2dd9531542529f89ac052aa733db65fc063238190cb14c04c4181 + languageName: node + linkType: hard + +"@nomicfoundation/solidity-analyzer-linux-x64-musl@npm:0.1.0": + version: 0.1.0 + resolution: "@nomicfoundation/solidity-analyzer-linux-x64-musl@npm:0.1.0" + checksum: 1fd982bc50141113280af5c396f54d142b16dd78978b1e63292858ad79f303d686a19c1266f269ff7d65c213484bf656b762496b4843b46acadcdfbe6c49700c + languageName: node + linkType: hard + +"@nomicfoundation/solidity-analyzer-win32-arm64-msvc@npm:0.1.0": + version: 0.1.0 + resolution: "@nomicfoundation/solidity-analyzer-win32-arm64-msvc@npm:0.1.0" + checksum: 628d798d809c92e1148ce3c57f12dfecb71fbfc6c8373a5da14a70992c68c88a9ae4338dbf124c24bd1efd7b065af2e6ba89a4c57daf2267546ca6d7792a8741 + languageName: node + linkType: hard + +"@nomicfoundation/solidity-analyzer-win32-ia32-msvc@npm:0.1.0": + version: 0.1.0 + resolution: "@nomicfoundation/solidity-analyzer-win32-ia32-msvc@npm:0.1.0" + checksum: 4c6d8fcff9a20bb82d4689b2c985214cfce44a8c9a66a7351a410a594a6a7b90368309855f878948a315bad1c6c93f73097dafb5b70790ea8db8838aa2563d86 + languageName: node + linkType: hard + +"@nomicfoundation/solidity-analyzer-win32-x64-msvc@npm:0.1.0": + version: 0.1.0 + resolution: "@nomicfoundation/solidity-analyzer-win32-x64-msvc@npm:0.1.0" + checksum: f0b0d3ddea8472ca34db682cc913905743ef95bef01418c94b039f9245bc7fa863bb029ab6e681fcaa44d4db54d03e6987135040c5ff0fb796c7e5d5387c2d86 + languageName: node + linkType: hard + +"@nomicfoundation/solidity-analyzer@npm:^0.1.0": + version: 0.1.0 + resolution: "@nomicfoundation/solidity-analyzer@npm:0.1.0" + dependencies: + "@nomicfoundation/solidity-analyzer-darwin-arm64": 0.1.0 + "@nomicfoundation/solidity-analyzer-darwin-x64": 0.1.0 + "@nomicfoundation/solidity-analyzer-freebsd-x64": 0.1.0 + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": 0.1.0 + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": 0.1.0 + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": 0.1.0 + "@nomicfoundation/solidity-analyzer-linux-x64-musl": 0.1.0 + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": 0.1.0 + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": 0.1.0 + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": 0.1.0 + dependenciesMeta: + "@nomicfoundation/solidity-analyzer-darwin-arm64": + optional: true + "@nomicfoundation/solidity-analyzer-darwin-x64": + optional: true + "@nomicfoundation/solidity-analyzer-freebsd-x64": + optional: true + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": + optional: true + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": + optional: true + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": + optional: true + "@nomicfoundation/solidity-analyzer-linux-x64-musl": + optional: true + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": + optional: true + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": + optional: true + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": + optional: true + checksum: 6bc08ce289b9818817b54d480f8c4520035cb1a45dbbb4ee65834296ccb69e3566586318b99e18b4b87ac461c77222a5e915511492a12e0bd90c96410d841458 + languageName: node + linkType: hard + "@nomiclabs/buidler@npm:1.4.7": version: 1.4.7 resolution: "@nomiclabs/buidler@npm:1.4.7" @@ -4694,13 +4879,6 @@ __metadata: languageName: node linkType: hard -"@truffle/error@npm:^0.0.12": - version: 0.0.12 - resolution: "@truffle/error@npm:0.0.12" - checksum: 0065c5c8fd5023a54886a8962250e05fd570c52bc947420130a9ff824fe916f695f5efe342ecf8e0c16aaf70be5a35fa36d126ca3bbc18bcb345f60c3d0854b6 - languageName: node - linkType: hard - "@truffle/error@npm:^0.0.8": version: 0.0.8 resolution: "@truffle/error@npm:0.0.8" @@ -4750,29 +4928,6 @@ __metadata: languageName: node linkType: hard -"@truffle/interface-adapter@npm:^0.4.19": - version: 0.4.19 - resolution: "@truffle/interface-adapter@npm:0.4.19" - dependencies: - bn.js: ^5.1.3 - ethers: ^4.0.32 - source-map-support: ^0.5.19 - web3: 1.2.9 - checksum: 434f1ab60621d03a3aef82ea4eea5034231b78a6ff36990e43e5f0596e2dd641967d35dd262f3bc9197abd4282dd9f263561dde0bf13fcd2f690708900cf206b - languageName: node - linkType: hard - -"@truffle/provider@npm:^0.2.24": - version: 0.2.26 - resolution: "@truffle/provider@npm:0.2.26" - dependencies: - "@truffle/error": ^0.0.12 - "@truffle/interface-adapter": ^0.4.19 - web3: 1.2.9 - checksum: bbec2ab2724ef7fdb1017a38202de6807ff33dae0fc7cfd9d92e23a0cdc72b1202e8cb6fcd577c566f17a33ed09de57fb770aeccb68968f93f5c269d74c59f94 - languageName: node - linkType: hard - "@trufflesuite/chromafi@npm:^2.2.0": version: 2.2.0 resolution: "@trufflesuite/chromafi@npm:2.2.0" @@ -4906,10 +5061,10 @@ __metadata: languageName: node linkType: hard -"@types/abstract-leveldown@npm:*": - version: 7.2.1 - resolution: "@types/abstract-leveldown@npm:7.2.1" - checksum: 7a1ce7f30f7e179db0d2dcc9b056a2aa54d541f1810b66e9e66f89826a1635a1038e159c441cec80e656f952264388a2ce596ef22ecc999ace03667123cf2c57 +"@types/async-eventemitter@npm:^0.2.1": + version: 0.2.1 + resolution: "@types/async-eventemitter@npm:0.2.1" + checksum: ca5182a046c91d0e4cd333d296997a05a5b7a15f12b4434648bed3d4188d057611a17fd943898be901b82a1fb029ab2b8a22cc1bed761d7c2f01b4dacf026469 languageName: node linkType: hard @@ -5019,24 +5174,6 @@ __metadata: languageName: node linkType: hard -"@types/level-errors@npm:*": - version: 3.0.0 - resolution: "@types/level-errors@npm:3.0.0" - checksum: cc9fd32145d46ad574e9d6c255ccf3c745717214a573e09dc94f63e4c5320ba8d15cc5741cb9565d4680b200b8abf729da38e38a63559ecd4cb574a81185e334 - languageName: node - linkType: hard - -"@types/levelup@npm:^4.3.0": - version: 4.3.3 - resolution: "@types/levelup@npm:4.3.3" - dependencies: - "@types/abstract-leveldown": "*" - "@types/level-errors": "*" - "@types/node": "*" - checksum: f09553ab633dacc7c550a09895d5f5d743cc0301a5358fa6c26cb0f6cca60643c881cc9823ae5727b7d641b8ab8cba60423f75242dc12ee20ec9fa246a0cd8b3 - languageName: node - linkType: hard - "@types/long@npm:^4.0.1": version: 4.0.1 resolution: "@types/long@npm:4.0.1" @@ -5195,13 +5332,6 @@ __metadata: languageName: node linkType: hard -"@ungap/promise-all-settled@npm:1.1.2": - version: 1.1.2 - resolution: "@ungap/promise-all-settled@npm:1.1.2" - checksum: be6c80a2fcea2fc4ed9ccf707da9837c2f501ba21312bf9b39f8b0c4ac7e581ed37909f4f64f8dab0c51c0ae5bdf2a45b59d9a215050876b3f27e6844dba30b6 - languageName: node - linkType: hard - "@yarnpkg/lockfile@npm:^1.1.0": version: 1.1.0 resolution: "@yarnpkg/lockfile@npm:1.1.0" @@ -5255,6 +5385,21 @@ __metadata: languageName: node linkType: hard +"abstract-level@npm:^1.0.0, abstract-level@npm:^1.0.2, abstract-level@npm:^1.0.3": + version: 1.0.3 + resolution: "abstract-level@npm:1.0.3" + dependencies: + buffer: ^6.0.3 + catering: ^2.1.0 + is-buffer: ^2.0.5 + level-supports: ^4.0.0 + level-transcoder: ^1.0.1 + module-error: ^1.0.1 + queue-microtask: ^1.2.3 + checksum: f60adf9a39ce131b9b257cea56ddfc6464debc986e222bf704a97c431946ae1ab432b3f37991e94db33277fa49b8526483595879cd6bacbe933a1d75fdafd061 + languageName: node + linkType: hard + "abstract-leveldown@npm:3.0.0": version: 3.0.0 resolution: "abstract-leveldown@npm:3.0.0" @@ -5282,19 +5427,6 @@ __metadata: languageName: node linkType: hard -"abstract-leveldown@npm:^6.2.1": - version: 6.3.0 - resolution: "abstract-leveldown@npm:6.3.0" - dependencies: - buffer: ^5.5.0 - immediate: ^3.2.3 - level-concat-iterator: ~2.0.0 - level-supports: ~1.0.0 - xtend: ~4.0.0 - checksum: fddd5380019790bcf889fbd1b5c56c81f66df85a2949befc33fab8f154bfe8d8c9cebc86295d7ad9151677d798b5c61f6dfb43477f7c7f97e3143288caf76ca4 - languageName: node - linkType: hard - "abstract-leveldown@npm:~2.6.0": version: 2.6.3 resolution: "abstract-leveldown@npm:2.6.3" @@ -5304,19 +5436,6 @@ __metadata: languageName: node linkType: hard -"abstract-leveldown@npm:~6.2.1": - version: 6.2.3 - resolution: "abstract-leveldown@npm:6.2.3" - dependencies: - buffer: ^5.5.0 - immediate: ^3.2.3 - level-concat-iterator: ~2.0.0 - level-supports: ~1.0.0 - xtend: ~4.0.0 - checksum: 193fd90d2110b63bf46aa761f18b3cb8c6e774d5b969c4b5ff8230d215b8982eac9edb8e10dcda7d9ccef361a845bcaa99e6a510d4ba7215a709b3d3ffffee8a - languageName: node - linkType: hard - "accepts@npm:~1.3.4, accepts@npm:~1.3.7": version: 1.3.7 resolution: "accepts@npm:1.3.7" @@ -7062,6 +7181,22 @@ __metadata: languageName: node linkType: hard +"bigint-crypto-utils@npm:^3.0.23": + version: 3.1.8 + resolution: "bigint-crypto-utils@npm:3.1.8" + dependencies: + bigint-mod-arith: ^3.1.0 + checksum: 8787187e10ab7fb8307c6a1e3b6aac6ae059c494caa0d3646fa147995af8f166d5c4b25cdbdee2008550db78d389107783e693871431b1702b6c983ffa8b754d + languageName: node + linkType: hard + +"bigint-mod-arith@npm:^3.1.0": + version: 3.1.2 + resolution: "bigint-mod-arith@npm:3.1.2" + checksum: d3edc1c9918becc5303ddaef6802d020dfb2bf2100ad81bd03abfab06f2fe181bf043ecbd2575c0f26a79961a6872f5141a517796fd61c05f613c6b55b81f734 + languageName: node + linkType: hard + "bignumber.js@git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2": version: 2.0.7 resolution: "bignumber.js@https://github.com/debris/bignumber.js.git#commit=94d7146671b9719e00a09c29b01a691bc85048c2" @@ -7409,6 +7544,18 @@ __metadata: languageName: node linkType: hard +"browser-level@npm:^1.0.1": + version: 1.0.1 + resolution: "browser-level@npm:1.0.1" + dependencies: + abstract-level: ^1.0.2 + catering: ^2.1.1 + module-error: ^1.0.2 + run-parallel-limit: ^1.1.0 + checksum: fafa1351ab97eceb64a60fe0372c088595d74ac7a66eb9fc5ac7a807b8cc4a2cdb10d679ecee703814fadeaf985d763f471a4820aab9d18cef2a6a30c6dbbbc7 + languageName: node + linkType: hard + "browser-process-hrtime@npm:^1.0.0": version: 1.0.0 resolution: "browser-process-hrtime@npm:1.0.0" @@ -8004,6 +8151,13 @@ __metadata: languageName: node linkType: hard +"catering@npm:^2.1.0, catering@npm:^2.1.1": + version: 2.1.1 + resolution: "catering@npm:2.1.1" + checksum: 47202d87b1dc13fdb5167557466458f491e13a03be8f13c1ab5975eb9545b34de00f4ee26f67551c23bb3596324b571277db1b13d8c1ea1e68110ab11c3d5a43 + languageName: node + linkType: hard + "caw@npm:^2.0.1": version: 2.0.1 resolution: "caw@npm:2.0.1" @@ -8365,6 +8519,20 @@ __metadata: languageName: node linkType: hard +"classic-level@npm:^1.2.0": + version: 1.2.0 + resolution: "classic-level@npm:1.2.0" + dependencies: + abstract-level: ^1.0.2 + catering: ^2.1.0 + module-error: ^1.0.1 + napi-macros: ~2.0.0 + node-gyp: latest + node-gyp-build: ^4.3.0 + checksum: 0c5265a227684d4f5db4ee192b376f138f774604213be10155fb904ee843dc90aa081e7d36d6fcbe1c83b7990ea58eaa5ab6a0cc538ecf456b5f43fba4f0df87 + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -9730,7 +9898,19 @@ __metadata: languageName: node linkType: hard -"debug@npm:4.3.3, debug@npm:^4.3.1, debug@npm:^4.3.3": +"debug@npm:4.3.4": + version: 4.3.4 + resolution: "debug@npm:4.3.4" + dependencies: + ms: 2.1.2 + peerDependenciesMeta: + supports-color: + optional: true + checksum: 7d71c93be25ed05e85840ec12df1a6d1848adbf8bc66948017ad4120fed17d7889e305adec560c18d70710e6de0c55548fbed170f4355045bbbbbd2afd1532a2 + languageName: node + linkType: hard + +"debug@npm:^4.3.1, debug@npm:^4.3.3": version: 4.3.3 resolution: "debug@npm:4.3.3" dependencies: @@ -9954,16 +10134,6 @@ __metadata: languageName: node linkType: hard -"deferred-leveldown@npm:~5.3.0": - version: 5.3.0 - resolution: "deferred-leveldown@npm:5.3.0" - dependencies: - abstract-leveldown: ~6.2.1 - inherits: ^2.0.3 - checksum: 2d2dbff3db68635a872218145b6c88f37358d26e31cf9e1efda229a68a56b420f82278794915c4ceb7129a651facfad4e44c8b6740d43733da6f808d981ac014 - languageName: node - linkType: hard - "define-properties@npm:^1.1.2, define-properties@npm:^1.1.3": version: 1.1.3 resolution: "define-properties@npm:1.1.3" @@ -10176,6 +10346,15 @@ __metadata: languageName: node linkType: hard +"difflib@npm:^0.2.4": + version: 0.2.4 + resolution: "difflib@npm:0.2.4" + dependencies: + heap: ">= 0.2.0" + checksum: e65f90b03773277b0b1137b22fccb8abc70e8dcee3d8402370966188a31934eb8ea463a81ffab2a0803c45409314b78ed865494bee66c39ce72a1649b6192682 + languageName: node + linkType: hard + "dir-glob@npm:^2.2.2": version: 2.2.2 resolution: "dir-glob@npm:2.2.2" @@ -10585,18 +10764,6 @@ __metadata: languageName: node linkType: hard -"encoding-down@npm:^6.3.0": - version: 6.3.0 - resolution: "encoding-down@npm:6.3.0" - dependencies: - abstract-leveldown: ^6.2.1 - inherits: ^2.0.3 - level-codec: ^9.0.0 - level-errors: ^2.0.0 - checksum: 002e025e623647585f410e9184840bbdec3451e7473b27dba75d89e7c031b8aef51b16aa21bd3955ef2f5f57ead57a477715d7234b677885c14f5f3af69a8873 - languageName: node - linkType: hard - "encoding@npm:^0.1.11, encoding@npm:^0.1.12, encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -11433,7 +11600,7 @@ __metadata: languageName: node linkType: hard -"eth-gas-reporter@npm:^0.2.24": +"eth-gas-reporter@npm:^0.2.25": version: 0.2.25 resolution: "eth-gas-reporter@npm:0.2.25" dependencies: @@ -11685,7 +11852,7 @@ __metadata: languageName: node linkType: hard -"ethereum-cryptography@npm:^0.1.2, ethereum-cryptography@npm:^0.1.3": +"ethereum-cryptography@npm:0.1.3, ethereum-cryptography@npm:^0.1.2, ethereum-cryptography@npm:^0.1.3": version: 0.1.3 resolution: "ethereum-cryptography@npm:0.1.3" dependencies: @@ -12008,19 +12175,6 @@ __metadata: languageName: node linkType: hard -"ethereumjs-util@npm:^7.1.1, ethereumjs-util@npm:^7.1.4, ethereumjs-util@npm:^7.1.5": - version: 7.1.5 - resolution: "ethereumjs-util@npm:7.1.5" - dependencies: - "@types/bn.js": ^5.1.0 - bn.js: ^5.1.2 - create-hash: ^1.1.2 - ethereum-cryptography: ^0.1.3 - rlp: ^2.2.4 - checksum: b438d76ce8699e56929110e32a5dc9a265cc3b53cdb5fa497f0f164ed596e5c689702b09b0be17093401618063337e4695c647dc516327cc85c9fafb37fa7b31 - languageName: node - linkType: hard - "ethereumjs-vm@npm:4.2.0": version: 4.2.0 resolution: "ethereumjs-vm@npm:4.2.0" @@ -14144,16 +14298,16 @@ fsevents@~2.3.2: languageName: node linkType: hard -"hardhat-gas-reporter@npm:1.0.8": - version: 1.0.8 - resolution: "hardhat-gas-reporter@npm:1.0.8" +"hardhat-gas-reporter@npm:^1.0.8": + version: 1.0.9 + resolution: "hardhat-gas-reporter@npm:1.0.9" dependencies: array-uniq: 1.0.3 - eth-gas-reporter: ^0.2.24 + eth-gas-reporter: ^0.2.25 sha1: ^1.1.1 peerDependencies: hardhat: ^2.0.2 - checksum: cbee5088e797a149360a2e38ef40edb7234de272993c3ef426b4635991cb176964fd816caa8f40ec3931025b2164d46b7e3f6b24e9a14d110ac26d1f3ca65a25 + checksum: 97997c5c146bbb89b1915692c06a4100898d67b5d0a5e205f4e21a59f900b728486ea6e5cd3d0c8edf3004a3787ca30fd0041815622392c4f5c02c32f4dfdbac languageName: node linkType: hard @@ -14168,19 +14322,24 @@ fsevents@~2.3.2: languageName: node linkType: hard -"hardhat@npm:2.9.9": - version: 2.9.9 - resolution: "hardhat@npm:2.9.9" +"hardhat@npm:2.12.7": + version: 2.12.7 + resolution: "hardhat@npm:2.12.7" dependencies: - "@ethereumjs/block": ^3.6.2 - "@ethereumjs/blockchain": ^5.5.2 - "@ethereumjs/common": ^2.6.4 - "@ethereumjs/tx": ^3.5.1 - "@ethereumjs/vm": ^5.9.0 "@ethersproject/abi": ^5.1.2 "@metamask/eth-sig-util": ^4.0.0 + "@nomicfoundation/ethereumjs-block": ^4.0.0 + "@nomicfoundation/ethereumjs-blockchain": ^6.0.0 + "@nomicfoundation/ethereumjs-common": ^3.0.0 + "@nomicfoundation/ethereumjs-evm": ^1.0.0 + "@nomicfoundation/ethereumjs-rlp": ^4.0.0 + "@nomicfoundation/ethereumjs-statemanager": ^1.0.0 + "@nomicfoundation/ethereumjs-trie": ^5.0.0 + "@nomicfoundation/ethereumjs-tx": ^4.0.0 + "@nomicfoundation/ethereumjs-util": ^8.0.0 + "@nomicfoundation/ethereumjs-vm": ^6.0.0 + "@nomicfoundation/solidity-analyzer": ^0.1.0 "@sentry/node": ^5.18.1 - "@solidity-parser/parser": ^0.14.1 "@types/bn.js": ^5.1.0 "@types/lru-cache": ^5.1.0 abort-controller: ^3.0.0 @@ -14193,31 +14352,28 @@ fsevents@~2.3.2: debug: ^4.1.1 enquirer: ^2.3.0 env-paths: ^2.2.0 - ethereum-cryptography: ^0.1.2 + ethereum-cryptography: ^1.0.3 ethereumjs-abi: ^0.6.8 - ethereumjs-util: ^7.1.4 find-up: ^2.1.0 fp-ts: 1.19.3 fs-extra: ^7.0.1 glob: 7.2.0 immutable: ^4.0.0-rc.12 io-ts: 1.10.4 + keccak: ^3.0.2 lodash: ^4.17.11 - merkle-patricia-tree: ^4.2.4 mnemonist: ^0.38.0 - mocha: ^9.2.0 + mocha: ^10.0.0 p-map: ^4.0.0 qs: ^6.7.0 raw-body: ^2.4.1 resolve: 1.17.0 semver: ^6.3.0 - slash: ^3.0.0 solc: 0.7.3 source-map-support: ^0.5.13 stacktrace-parser: ^0.1.10 - true-case-path: ^2.2.1 tsort: 0.0.1 - undici: ^5.4.0 + undici: ^5.14.0 uuid: ^8.3.2 ws: ^7.4.6 peerDependencies: @@ -14230,7 +14386,7 @@ fsevents@~2.3.2: optional: true bin: hardhat: internal/cli/cli.js - checksum: 79080cc2dfdedb3bb7790e0cbec3e4d51ded9444f4da7f8a71dcc9969bbf626c03146fe68dbaf2ef6917ce116f5d6b3f52ad57c0739fa4b6747324a52ec7850f + checksum: cdd74f22e6783a1395781a66992ea869c012645d1598da7c20a71bb55ef1707921b056ea8df88371be918e705ca5691d3244b6099a95da595012b4475686e374 languageName: node linkType: hard @@ -14396,6 +14552,13 @@ fsevents@~2.3.2: languageName: node linkType: hard +"heap@npm:>= 0.2.0": + version: 0.2.7 + resolution: "heap@npm:0.2.7" + checksum: bedbcb978c6e3845af2b6484ac0fdfc131dd95c99a4e905791ceb80e883a6264d9583983cc9e509665b629c3d8e7813b55a5fa689e9fd2166feaadd6f55eac34 + languageName: node + linkType: hard + "hex-color-regex@npm:^1.1.0": version: 1.1.0 resolution: "hex-color-regex@npm:1.1.0" @@ -15641,6 +15804,13 @@ fsevents@~2.3.2: languageName: node linkType: hard +"is-buffer@npm:^2.0.5": + version: 2.0.5 + resolution: "is-buffer@npm:2.0.5" + checksum: 1a6dc68206e834887d3b0d8e8ec6c95e0d780314479526a5a2cf458838d1bc441a105c4cebf95d8cc16e383238f66c41761ec622f6154043186e3d9439d56970 + languageName: node + linkType: hard + "is-buffer@npm:~2.0.3": version: 2.0.4 resolution: "is-buffer@npm:2.0.4" @@ -16983,6 +17153,18 @@ fsevents@~2.3.2: languageName: node linkType: hard +"keccak@npm:^3.0.2": + version: 3.0.3 + resolution: "keccak@npm:3.0.3" + dependencies: + node-addon-api: ^2.0.0 + node-gyp: latest + node-gyp-build: ^4.2.0 + readable-stream: ^3.6.0 + checksum: d95dbe1871ee87f1df270b57366061c72e16cf43623ca83116479082ffbee9ef67feeebbb40ccfb4db305a571efba1a20e5e66ae4d1ff95e4501aa6896036cb3 + languageName: node + linkType: hard + "keyv@npm:3.0.0, keyv@npm:^3.0.0": version: 3.0.0 resolution: "keyv@npm:3.0.0" @@ -17132,13 +17314,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"level-concat-iterator@npm:~2.0.0": - version: 2.0.1 - resolution: "level-concat-iterator@npm:2.0.1" - checksum: d5ac362a950bcf63bc19c4e8d397055c9bbd335eea2c56f7de372d38c66147d6db2430596025861d99820f890a9881aef9a192ee62b109ccf0885c0b5c5c2586 - languageName: node - linkType: hard - "level-errors@npm:^1.0.3, level-errors@npm:~1.0.3": version: 1.0.5 resolution: "level-errors@npm:1.0.5" @@ -17191,17 +17366,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"level-iterator-stream@npm:~4.0.0": - version: 4.0.2 - resolution: "level-iterator-stream@npm:4.0.2" - dependencies: - inherits: ^2.0.4 - readable-stream: ^3.4.0 - xtend: ^4.0.2 - checksum: 795cdbbf856b58fffaf19e0d0b294f65fd4d8feb22bdee8563408f739107ce88b03b8b36ecc60be95c736ea676d611f95dabfbbb15346a9005ed627ae644dfb5 - languageName: node - linkType: hard - "level-mem@npm:^3.0.1": version: 3.0.1 resolution: "level-mem@npm:3.0.1" @@ -17212,26 +17376,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"level-mem@npm:^5.0.1": - version: 5.0.1 - resolution: "level-mem@npm:5.0.1" - dependencies: - level-packager: ^5.0.3 - memdown: ^5.0.0 - checksum: 715c2e59e3b6814324d961155cd280be7053c3e82446906b1ab7e6a024719d5eb545238c946ab8059da301fefa092c64678661de1ece9a035ed396dd21af306a - languageName: node - linkType: hard - -"level-packager@npm:^5.0.3": - version: 5.1.1 - resolution: "level-packager@npm:5.1.1" - dependencies: - encoding-down: ^6.3.0 - levelup: ^4.3.2 - checksum: bf36e98c6dcb1f35c6e05b6fee9e505feb0ca5b88d88206e96bfd328da5ed6c2e52d8b0c5ba53d283f74d8a9e9ab634697c4f7de69339ad26a4e302d8299b98e - languageName: node - linkType: hard - "level-packager@npm:~4.0.0": version: 4.0.1 resolution: "level-packager@npm:4.0.1" @@ -17269,12 +17413,20 @@ fsevents@~2.3.2: languageName: node linkType: hard -"level-supports@npm:~1.0.0": +"level-supports@npm:^4.0.0": + version: 4.0.1 + resolution: "level-supports@npm:4.0.1" + checksum: 6addbea71960db2ea5518010c76bcafb4283f58a7f56b636b495380be39b7445d1d08a1dd20b24c9b96b05bd8eddcaf7212290f52ade7ba79b8358ef7e679099 + languageName: node + linkType: hard + +"level-transcoder@npm:^1.0.1": version: 1.0.1 - resolution: "level-supports@npm:1.0.1" + resolution: "level-transcoder@npm:1.0.1" dependencies: - xtend: ^4.0.2 - checksum: 73c845921516d38ea57d6574908e86fedbdf3a069938f14f72e221e3f9dc04adc93b0b484fc9d7591c3baab7405f33292d90250b4239a75c6afc7f37bc5b3540 + buffer: ^6.0.3 + module-error: ^1.0.1 + checksum: 4edafcd791f8c548e44f68e6967b12abf48d4d3d973143f2deca4ab98cfdb54a8018dcac043fc68b7baf3a1db931735beb5abe894032b3ffe4aed9b2f5fa29b3 languageName: node linkType: hard @@ -17299,14 +17451,13 @@ fsevents@~2.3.2: languageName: node linkType: hard -"level-ws@npm:^2.0.0": - version: 2.0.0 - resolution: "level-ws@npm:2.0.0" +"level@npm:^8.0.0": + version: 8.0.0 + resolution: "level@npm:8.0.0" dependencies: - inherits: ^2.0.3 - readable-stream: ^3.1.0 - xtend: ^4.0.1 - checksum: c826ebd63a167389fc172235ec66cedd016055768c53353b7577462dfdfd16462cb13ee87529c2f95ee61124d94e3da61acf6bd8b62eec1a9605e8a96e5bb8eb + browser-level: ^1.0.1 + classic-level: ^1.2.0 + checksum: e04aa34bb3392865a968a02c229513b80ad46c486bc6a49184268e62c7047375738e5193453bb045545e6e8c443f6ccff5c2fd643ed283febb76ddc10ddb46aa languageName: node linkType: hard @@ -17337,19 +17488,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"levelup@npm:^4.3.2": - version: 4.4.0 - resolution: "levelup@npm:4.4.0" - dependencies: - deferred-leveldown: ~5.3.0 - level-errors: ~2.0.0 - level-iterator-stream: ~4.0.0 - level-supports: ~1.0.0 - xtend: ~4.0.0 - checksum: 7cc5b84c29baeae52ee81c5d6f56a03557ec0eed4b95b1a7f22c82d8a0ebe4cea537166d2823e0cf45125dff86f119c058ccd824b5c5f2c2c8041df745b6dd39 - languageName: node - linkType: hard - "leven@npm:^3.1.0": version: 3.1.0 resolution: "leven@npm:3.1.0" @@ -18182,20 +18320,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"memdown@npm:^5.0.0": - version: 5.1.0 - resolution: "memdown@npm:5.1.0" - dependencies: - abstract-leveldown: ~6.2.1 - functional-red-black-tree: ~1.0.1 - immediate: ~3.2.3 - inherits: ~2.0.1 - ltgt: ~2.2.0 - safe-buffer: ~5.2.0 - checksum: ce9ad9e7ddb761eadb89e72a0e9bcc50474eab9bc1dafdf662140e0264d746d58b07b0bb19de0326f097e04c9d7a8c91b59ff811b4661d30bac8655b06b540df - languageName: node - linkType: hard - "memdown@npm:~3.0.0": version: 3.0.0 resolution: "memdown@npm:3.0.0" @@ -18210,6 +18334,17 @@ fsevents@~2.3.2: languageName: node linkType: hard +"memory-level@npm:^1.0.0": + version: 1.0.0 + resolution: "memory-level@npm:1.0.0" + dependencies: + abstract-level: ^1.0.0 + functional-red-black-tree: ^1.0.1 + module-error: ^1.0.1 + checksum: 391eb84bbd5afbd8273a7b20534c3449ad01e294ec7a79998bbfe84f17f1620e015da56e06a8fbf4dcbae711ef2cb81351b8274ab21c27e120e4144494cd245e + languageName: node + linkType: hard + "memorystream@npm:^0.3.1": version: 0.3.1 resolution: "memorystream@npm:0.3.1" @@ -18350,20 +18485,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"merkle-patricia-tree@npm:^4.2.4": - version: 4.2.4 - resolution: "merkle-patricia-tree@npm:4.2.4" - dependencies: - "@types/levelup": ^4.3.0 - ethereumjs-util: ^7.1.4 - level-mem: ^5.0.1 - level-ws: ^2.0.0 - readable-stream: ^3.6.0 - semaphore-async-await: ^1.5.1 - checksum: e69956f11ac30d53486fe6590968f363b8449b1e12006a4ec7e95a3d887687b916a3ba3d6d024a9176cd67983c44a3fde9417d2d156e414bd753b7ead4de5b56 - languageName: node - linkType: hard - "methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" @@ -18513,12 +18634,12 @@ fsevents@~2.3.2: languageName: node linkType: hard -"minimatch@npm:4.2.1": - version: 4.2.1 - resolution: "minimatch@npm:4.2.1" +"minimatch@npm:5.0.1": + version: 5.0.1 + resolution: "minimatch@npm:5.0.1" dependencies: - brace-expansion: ^1.1.7 - checksum: 953d3cd9bd77627db5bf81ab24fd903321eb0c336ac357cba862172ffa1f9db07a315f2de38f0dcf72462549a32863585fce55cfde91eb57ba731ec4edc7bb97 + brace-expansion: ^2.0.1 + checksum: 6554aca25884b6c31bf42ca7c91970bb0f0c82447b99468f87809ba9ad7803fcd6dd888e58fcd592df5b1a0fd4f5e2b752656a1dbd6a9bbeb629d2c40f1f8b94 languageName: node linkType: hard @@ -18531,6 +18652,15 @@ fsevents@~2.3.2: languageName: node linkType: hard +"minimatch@npm:^6.2.0": + version: 6.2.0 + resolution: "minimatch@npm:6.2.0" + dependencies: + brace-expansion: ^2.0.1 + checksum: 675bc5a2f94ee6a762fd70baf777520c841cdca16cf6e9fc918171ca436cf4ebb8f3fb1ccaa5fd20e370ae60044fb6bfe653ceee4f18aec7fa51a79984a71316 + languageName: node + linkType: hard + "minimist-options@npm:4.1.0": version: 4.1.0 resolution: "minimist-options@npm:4.1.0" @@ -18730,6 +18860,41 @@ fsevents@~2.3.2: languageName: node linkType: hard +"mocha@npm:7.1.2": + version: 7.1.2 + resolution: "mocha@npm:7.1.2" + dependencies: + ansi-colors: 3.2.3 + browser-stdout: 1.3.1 + chokidar: 3.3.0 + debug: 3.2.6 + diff: 3.5.0 + escape-string-regexp: 1.0.5 + find-up: 3.0.0 + glob: 7.1.3 + growl: 1.10.5 + he: 1.2.0 + js-yaml: 3.13.1 + log-symbols: 3.0.0 + minimatch: 3.0.4 + mkdirp: 0.5.5 + ms: 2.1.1 + node-environment-flags: 1.0.6 + object.assign: 4.1.0 + strip-json-comments: 2.0.1 + supports-color: 6.0.0 + which: 1.3.1 + wide-align: 1.1.3 + yargs: 13.3.2 + yargs-parser: 13.1.2 + yargs-unparser: 1.6.0 + bin: + _mocha: bin/_mocha + mocha: bin/mocha + checksum: 19d87fa1f957b1f3ec725e110ab5cf4c117b9716a307f53823dbe5d1e94173638804ca3263700ffd07645f39339f501f4c17fd0d27f93d2dcdc2e51e5843bb98 + languageName: node + linkType: hard + "mocha@npm:8.1.2": version: 8.1.2 resolution: "mocha@npm:8.1.2" @@ -18766,6 +18931,38 @@ fsevents@~2.3.2: languageName: node linkType: hard +"mocha@npm:^10.0.0": + version: 10.2.0 + resolution: "mocha@npm:10.2.0" + dependencies: + ansi-colors: 4.1.1 + browser-stdout: 1.3.1 + chokidar: 3.5.3 + debug: 4.3.4 + diff: 5.0.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 7.2.0 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.0.1 + ms: 2.1.3 + nanoid: 3.3.3 + serialize-javascript: 6.0.0 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.2.1 + yargs: 16.2.0 + yargs-parser: 20.2.4 + yargs-unparser: 2.0.0 + bin: + _mocha: bin/_mocha + mocha: bin/mocha.js + checksum: 4298ccaf890d6c888bc5539ef7e35c237fa3a9ca33d168f6c5122e0250823b5460dfb09e1ab870a93255d67963727dff9241557f8f41b96258f203fb12878189 + languageName: node + linkType: hard + "mocha@npm:^7.1.1, mocha@npm:^7.1.2": version: 7.2.0 resolution: "mocha@npm:7.2.0" @@ -18801,41 +18998,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"mocha@npm:^9.2.0": - version: 9.2.2 - resolution: "mocha@npm:9.2.2" - dependencies: - "@ungap/promise-all-settled": 1.1.2 - ansi-colors: 4.1.1 - browser-stdout: 1.3.1 - chokidar: 3.5.3 - debug: 4.3.3 - diff: 5.0.0 - escape-string-regexp: 4.0.0 - find-up: 5.0.0 - glob: 7.2.0 - growl: 1.10.5 - he: 1.2.0 - js-yaml: 4.1.0 - log-symbols: 4.1.0 - minimatch: 4.2.1 - ms: 2.1.3 - nanoid: 3.3.1 - serialize-javascript: 6.0.0 - strip-json-comments: 3.1.1 - supports-color: 8.1.1 - which: 2.0.2 - workerpool: 6.2.0 - yargs: 16.2.0 - yargs-parser: 20.2.4 - yargs-unparser: 2.0.0 - bin: - _mocha: bin/_mocha - mocha: bin/mocha - checksum: 1c9bb011e2f755a763d67bd0e2f2c9b6070435c6bfba832146b0eef4f1763b149908615f291bfcf75083b3854af0017d334600ef5500105f878017ebc7ce52fc - languageName: node - linkType: hard - "mock-fs@npm:^4.1.0": version: 4.13.0 resolution: "mock-fs@npm:4.13.0" @@ -18850,6 +19012,13 @@ fsevents@~2.3.2: languageName: node linkType: hard +"module-error@npm:^1.0.1, module-error@npm:^1.0.2": + version: 1.0.2 + resolution: "module-error@npm:1.0.2" + checksum: 833bf8cb735e1834813e76bfe42c4f03b44fccb983df85ce7e9dc9b5b2ceaa1c0e5ff644da3461376425f66971caac4d1b6d3cea09749c049fbe970e20b6fa91 + languageName: node + linkType: hard + "mold-source-map@npm:~0.4.0": version: 0.4.0 resolution: "mold-source-map@npm:0.4.0" @@ -19188,12 +19357,12 @@ fsevents@~2.3.2: languageName: node linkType: hard -"nanoid@npm:3.3.1": - version: 3.3.1 - resolution: "nanoid@npm:3.3.1" +"nanoid@npm:3.3.3": + version: 3.3.3 + resolution: "nanoid@npm:3.3.3" bin: nanoid: bin/nanoid.cjs - checksum: a357d61e7c404c45526a6de65d186711bfcca134d8ce67db2d160752e1bb0bc35dd41d3d78a7f5d74fb50ad040ecf59512e92b84d114607326399c94d435fdad + checksum: 012f9e5ab59cf28359e6c6f670571a1febcf03a6d6c48800971ecf81c5df846290280e3e90236a2ee36c87bf0b91807c19aed3c82fbd58faed00aedd7186fbc8 languageName: node linkType: hard @@ -19241,6 +19410,13 @@ fsevents@~2.3.2: languageName: node linkType: hard +"napi-macros@npm:~2.0.0": + version: 2.0.0 + resolution: "napi-macros@npm:2.0.0" + checksum: 1b67e5271e6688d7801a5bfa49c50c097ead038ed552af5f4b1e849255f711581b389cbeb8d746ee14803612dbdf205c086e75606f8ff7d354bc9d4b5863912c + languageName: node + linkType: hard + "native-abort-controller@npm:^1.0.3, native-abort-controller@npm:^1.0.4": version: 1.0.4 resolution: "native-abort-controller@npm:1.0.4" @@ -19391,6 +19567,17 @@ fsevents@~2.3.2: languageName: node linkType: hard +"node-gyp-build@npm:^4.3.0": + version: 4.6.0 + resolution: "node-gyp-build@npm:4.6.0" + bin: + node-gyp-build: bin.js + node-gyp-build-optional: optional.js + node-gyp-build-test: build-test.js + checksum: f132cb9553c71c03d2e47febe28eeefe4d13e9b61b9444a2404753bc6e31e10b409d0984839bc199b01897ede8db0663110462d13e383c58c8cd9de15eff2f57 + languageName: node + linkType: hard + "node-gyp-build@npm:~3.7.0": version: 3.7.0 resolution: "node-gyp-build@npm:3.7.0" @@ -22086,6 +22273,13 @@ fsevents@~2.3.2: languageName: node linkType: hard +"queue-microtask@npm:^1.2.2, queue-microtask@npm:^1.2.3": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 0f88d794d4d825d39cdc2cda2fa701722858fc8de9567ad612776fce0d113376a3fc67f6a0091f31c9142b28f0c14fef08e9f92422b49f2372d5537e250fbfad + languageName: node + linkType: hard + "quick-lru@npm:^1.0.0": version: 1.1.0 resolution: "quick-lru@npm:1.1.0" @@ -22434,7 +22628,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"readable-stream@npm:2 || 3, readable-stream@npm:^3.0.2, readable-stream@npm:^3.0.6, readable-stream@npm:^3.1.0, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": +"readable-stream@npm:2 || 3, readable-stream@npm:^3.0.2, readable-stream@npm:^3.0.6, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": version: 3.6.0 resolution: "readable-stream@npm:3.6.0" dependencies: @@ -23166,6 +23360,15 @@ resolve@1.1.x: languageName: node linkType: hard +"run-parallel-limit@npm:^1.1.0": + version: 1.1.0 + resolution: "run-parallel-limit@npm:1.1.0" + dependencies: + queue-microtask: ^1.2.2 + checksum: 0176e95921f24d611259c39618bffd3970343c838243b9bfedc463a5e91042fa13d3f8868f3fd9b4437545595d3d21d68ae3b769f05e251d10812aa5a901ea93 + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.1.9 resolution: "run-parallel@npm:1.1.9" @@ -23391,13 +23594,6 @@ resolve@1.1.x: languageName: node linkType: hard -"semaphore-async-await@npm:^1.5.1": - version: 1.5.1 - resolution: "semaphore-async-await@npm:1.5.1" - checksum: 439e67dfad14a77b094cdd1488a7edd9d01e5b91f0b35ca9893ebe7fe70eb44083ef83cee04a9bcbcf94a9a3457e278139742c3ef7fb97c94085506962ac4541 - languageName: node - linkType: hard - "semaphore@npm:>=1.0.1, semaphore@npm:^1.0.3, semaphore@npm:^1.1.0": version: 1.1.0 resolution: "semaphore@npm:1.1.0" @@ -24162,31 +24358,35 @@ resolve@1.1.x: languageName: node linkType: hard -"solidity-coverage@npm:^0.7.22": - version: 0.7.22 - resolution: "solidity-coverage@npm:0.7.22" +"solidity-coverage@npm:^0.8.2": + version: 0.8.2 + resolution: "solidity-coverage@npm:0.8.2" dependencies: - "@solidity-parser/parser": ^0.14.0 - "@truffle/provider": ^0.2.24 + "@ethersproject/abi": ^5.0.9 + "@solidity-parser/parser": ^0.14.1 chalk: ^2.4.2 death: ^1.1.0 detect-port: ^1.3.0 + difflib: ^0.2.4 fs-extra: ^8.1.0 ghost-testrpc: ^0.0.2 global-modules: ^2.0.0 globby: ^10.0.1 jsonschema: ^1.2.4 lodash: ^4.17.15 + mocha: 7.1.2 node-emoji: ^1.10.0 pify: ^4.0.1 recursive-readdir: ^2.2.2 sc-istanbul: ^0.4.5 semver: ^7.3.4 shelljs: ^0.8.3 - web3-utils: ^1.3.0 + web3-utils: ^1.3.6 + peerDependencies: + hardhat: ^2.11.0 bin: solidity-coverage: plugins/bin.js - checksum: a29d97d451b0196a0a0de9b9dace2a80b55bd43272038aea23a1db8f9ed547ec0e3843f6073f3005fd73ce2aa054ad8e43f7f25c57bb1bd9c8a50dea015859d9 + checksum: 3cf570696a74c3fa9b45625ea11b73c9539a5a22400fe81449856d106697418cb55c0225c3ad9257caee6731503c5f63a2979658ab28c6e715c6b179a741e26b languageName: node linkType: hard @@ -25665,13 +25865,6 @@ resolve@1.1.x: languageName: node linkType: hard -"true-case-path@npm:^2.2.1": - version: 2.2.1 - resolution: "true-case-path@npm:2.2.1" - checksum: 963316436960f9fa2c396b50b065218b6d9a6690ef5c0e70c3e560168272da46d14cb8c19e361c5e9c29fa0425f18b5d44ded8fdb55a38e06a58fe218522be5f - languageName: node - linkType: hard - "truffle-extract@npm:^1.2.1": version: 1.2.1 resolution: "truffle-extract@npm:1.2.1" @@ -26042,12 +26235,12 @@ resolve@1.1.x: languageName: node linkType: hard -"undici@npm:^5.4.0": - version: 5.16.0 - resolution: "undici@npm:5.16.0" +"undici@npm:^5.14.0": + version: 5.19.1 + resolution: "undici@npm:5.19.1" dependencies: busboy: ^1.6.0 - checksum: 48049fea77d4d43ad921ba221c7a2671f6ca5bf7509f09f1308ca4b070a4a39a475811b3a4e499b0106609f65a5880c6b57ac89ab11cdd9b5c5366651a7b1227 + checksum: fda67aea0d188864ff9bd8273ab50471b754b0a6f9942a4a57d1bffaf4e481f3eba453e8740776196ed4c8fcf5f55c04bb00629f4fbe6f46794e1b57415008cd languageName: node linkType: hard @@ -27722,6 +27915,21 @@ resolve@1.1.x: languageName: node linkType: hard +"web3-utils@npm:^1.3.6": + version: 1.8.2 + resolution: "web3-utils@npm:1.8.2" + dependencies: + bn.js: ^5.2.1 + ethereum-bloom-filters: ^1.0.6 + ethereumjs-util: ^7.1.0 + ethjs-unit: 0.1.6 + number-to-bn: 1.7.0 + randombytes: ^2.1.0 + utf8: 3.0.0 + checksum: bf401b4fa55da722c552299f459365edd2afffa71995479eb39dc233e74c267f584c13a6b288a344a866950dba1dcaa75620f1b5f6c46bd96afc523ccc1685a7 + languageName: node + linkType: hard + "web3@npm:1.2.1": version: 1.2.1 resolution: "web3@npm:1.2.1" @@ -28049,10 +28257,10 @@ resolve@1.1.x: languageName: node linkType: hard -"workerpool@npm:6.2.0": - version: 6.2.0 - resolution: "workerpool@npm:6.2.0" - checksum: b0590cd8359001226ac9578ec6188c8fb7776359232e9a3ca9e413332b07afbf3a4f5ad023668c1bd477ed590c21b70ae75d366baa3b595a62be76ae06b84af3 +"workerpool@npm:6.2.1": + version: 6.2.1 + resolution: "workerpool@npm:6.2.1" + checksum: 1ddf04aea8149bd822c90b7f9dcb9bcd39d5fa474e1193098f0623b3bd82fb07ebbddfac7d0b441dbb97747f6ace0d5e1432e451167c0166445d06de13d05b2d languageName: node linkType: hard @@ -28339,7 +28547,7 @@ resolve@1.1.x: languageName: node linkType: hard -"xtend@npm:^4.0.0, xtend@npm:^4.0.1, xtend@npm:^4.0.2, xtend@npm:~4.0.0, xtend@npm:~4.0.1": +"xtend@npm:^4.0.0, xtend@npm:^4.0.1, xtend@npm:~4.0.0, xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2" checksum: 37ee522a3e9fb9b143a400c30b21dc122aa8c9c9411c6afae1005a4617dc20a21765c114d544e37a6bb60c2733dd8ee0a44ed9e80d884ac78cccd30b5e0ab0da