diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..1d699b37d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# top-most EditorConfig file +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 + +[*.js] +indent_size = 2 diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index f595a7d2f..ae6e9f605 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -44,8 +44,8 @@ jobs: - name: Run Solidity tests run: yarn test:unit - - name: Run Solidity linters - run: yarn lint:sol:solhint + - name: Run Solidity linters + run: yarn lint:sol - name: Run JS linters run: yarn lint:js diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..2fe539f76 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,6 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn compile +yarn lint:sol +yarn lint:js:fix diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 000000000..840674ec6 --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,4 @@ +{ + "require": "hardhat/register", + "timeout": 40000 +} diff --git a/.prettierrc b/.prettierrc index 0d22c886f..10aa76fde 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,5 +4,17 @@ "trailingComma": "none", "bracketSpacing": true, "jsxBracketSameLine": false, - "printWidth": 140 + "printWidth": 140, + "overrides": [ + { + "files": "*.sol", + "options": { + "printWidth": 120, + "tabWidth": 4, + "useTabs": false, + "singleQuote": false, + "bracketSpacing": false + } + } + ] } diff --git a/.solhint.json b/.solhint.json index b9f9e1ece..2658af5d5 100644 --- a/.solhint.json +++ b/.solhint.json @@ -10,6 +10,9 @@ "reason-string": "off", "no-empty-blocks": "off", "func-name-mixedcase": "off", - "lido/fixed-compiler-version": "error" + "lido/fixed-compiler-version": "error", + "visibility-modifier-order": "error", + "no-unused-vars": "error", + "func-visibility": ["warn",{"ignoreConstructors":true}] } } diff --git a/.soliumignore b/.solhintignore similarity index 100% rename from .soliumignore rename to .solhintignore diff --git a/.soliumrc.json b/.soliumrc.json deleted file mode 100644 index bd45f50f1..000000000 --- a/.soliumrc.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "extends": "solium:all", - "plugins": ["security"], - "rules": { - "security/no-low-level-calls": "off", - "security/no-inline-assembly": "off", - "security/no-assign-params": "warning", - "error-reason": "off", - "imports-on-top": "error", - "variable-declarations": "error", - "array-declarations": "error", - "operator-whitespace": "error", - "conditionals-whitespace": "error", - "comma-whitespace": "error", - "semicolon-whitespace": "error", - "function-whitespace": "error", - "lbrace": "error", - "mixedcase": "off", - "camelcase": "error", - "uppercase": "error", - "no-empty-blocks": "error", - "no-unused-vars": "error", - "quotes": "error", - "blank-lines": "error", - "indentation": "error", - "arg-overflow": ["error", 8], - "whitespace": "error", - "deprecated-suicide": "error", - "pragma-on-top": "error", - "function-order": [ - "error", - {"ignore": {"functions": ["initialize"]}} - ], - "emit": "error", - "no-constant": "error", - "value-in-payable": "error", - "max-len": "error", - "visibility-first": "error", - "linebreak-style": "error" - } -} diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 90e5d55a4..e444e195b 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2020 Lido +// SPDX-FileCopyrightText: 2023 Lido // SPDX-License-Identifier: GPL-3.0 @@ -7,48 +7,31 @@ pragma solidity 0.4.24; import "@aragon/os/contracts/apps/AragonApp.sol"; import "@aragon/os/contracts/lib/math/SafeMath.sol"; -import "@aragon/os/contracts/lib/math/SafeMath64.sol"; import "solidity-bytes-utils/contracts/BytesLib.sol"; -import "./interfaces/ILido.sol"; import "./interfaces/INodeOperatorsRegistry.sol"; import "./interfaces/IDepositContract.sol"; import "./interfaces/ILidoExecutionLayerRewardsVault.sol"; +import "./interfaces/IWithdrawalQueue.sol"; +import "./interfaces/IWithdrawalVault.sol"; import "./StETH.sol"; import "./lib/StakeLimitUtils.sol"; - -interface IERC721 { - /// @notice Transfer ownership of an NFT - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - function transferFrom(address _from, address _to, uint256 _tokenId) external payable; -} - - /** * @title Liquid staking pool implementation * -* Lido is an Ethereum 2.0 liquid staking protocol solving the problem of frozen staked Ethers -* until transfers become available in Ethereum 2.0. -* Whitepaper: https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf -* -* NOTE: the code below assumes moderate amount of node operators, e.g. up to 200. +* Lido is an Ethereum liquid staking protocol solving the problem of frozen staked ether on Consensus Layer +* being unavailable for transfers and DeFi on Execution Layer. * * Since balances of all token holders change when the amount of total pooled Ether * changes, this token cannot fully implement ERC20 standard: it only emits `Transfer` * events upon explicit transfer between holders. In contrast, when Lido oracle reports * rewards, no Transfer events are generated: doing so would require emitting an event * for each token holder and thus running an unbounded loop. -* -* At the moment withdrawals are not possible in the beacon chain and there's no workaround. -* Pool will be upgraded to an actual implementation when withdrawals are enabled -* (Phase 1.5 or 2 of Eth2 launch, likely late 2022 or 2023). */ -contract Lido is ILido, StETH, AragonApp { +contract Lido is StETH, AragonApp { using SafeMath for uint256; using UnstructuredStorage for bytes32; using StakeLimitUnstructuredStorage for bytes32; @@ -64,10 +47,7 @@ contract Lido is ILido, StETH, AragonApp { bytes32 constant public MANAGE_PROTOCOL_CONTRACTS_ROLE = keccak256("MANAGE_PROTOCOL_CONTRACTS_ROLE"); bytes32 constant public BURN_ROLE = keccak256("BURN_ROLE"); bytes32 constant public DEPOSIT_ROLE = keccak256("DEPOSIT_ROLE"); - bytes32 constant public SET_EL_REWARDS_VAULT_ROLE = keccak256("SET_EL_REWARDS_VAULT_ROLE"); - bytes32 constant public SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE = keccak256( - "SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE" - ); + bytes32 constant public SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE = keccak256("SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE"); uint256 constant public PUBKEY_LENGTH = 48; uint256 constant public WITHDRAWAL_CREDENTIALS_LENGTH = 32; @@ -78,20 +58,19 @@ contract Lido is ILido, StETH, AragonApp { uint256 internal constant DEPOSIT_AMOUNT_UNIT = 1000000000 wei; uint256 internal constant TOTAL_BASIS_POINTS = 10000; - /// @dev default value for maximum number of Ethereum 2.0 validators registered in a single depositBufferedEther call + /// @dev default value for maximum number of Consensus Layer validators registered in a single depositBufferedEther call uint256 internal constant DEFAULT_MAX_DEPOSITS_PER_CALL = 150; bytes32 internal constant FEE_POSITION = keccak256("lido.Lido.fee"); bytes32 internal constant TREASURY_FEE_POSITION = keccak256("lido.Lido.treasuryFee"); - bytes32 internal constant INSURANCE_FEE_POSITION = keccak256("lido.Lido.insuranceFee"); bytes32 internal constant NODE_OPERATORS_FEE_POSITION = keccak256("lido.Lido.nodeOperatorsFee"); bytes32 internal constant DEPOSIT_CONTRACT_POSITION = keccak256("lido.Lido.depositContract"); bytes32 internal constant ORACLE_POSITION = keccak256("lido.Lido.oracle"); bytes32 internal constant NODE_OPERATORS_REGISTRY_POSITION = keccak256("lido.Lido.nodeOperatorsRegistry"); bytes32 internal constant TREASURY_POSITION = keccak256("lido.Lido.treasury"); - bytes32 internal constant INSURANCE_FUND_POSITION = keccak256("lido.Lido.insuranceFund"); bytes32 internal constant EL_REWARDS_VAULT_POSITION = keccak256("lido.Lido.executionLayerRewardsVault"); + bytes32 internal constant WITHDRAWAL_QUEUE_POSITION = keccak256("lido.Lido.withdrawalQueue"); /// @dev storage slot position of the staking rate limit structure bytes32 internal constant STAKING_STATE_POSITION = keccak256("lido.Lido.stakeLimit"); @@ -114,13 +93,50 @@ contract Lido is ILido, StETH, AragonApp { /// @dev Credentials which allows the DAO to withdraw Ether on the 2.0 side bytes32 internal constant WITHDRAWAL_CREDENTIALS_POSITION = keccak256("lido.Lido.withdrawalCredentials"); + /// @dev Amount of eth in deposit buffer to reserve for withdrawals + bytes32 internal constant WITHDRAWAL_RESERVE_POSITION = keccak256("lido.Lido.withdrawalReserve"); + + event Stopped(); + event Resumed(); + + event StakingPaused(); + event StakingResumed(); + event StakingLimitSet(uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock); + event StakingLimitRemoved(); + + event ProtocolContactsSet( + address oracle, + address treasury, + address _executionLayerRewardsVault, + address _withdrawalQueue + ); + + event FeeSet(uint16 feeBasisPoints); + event FeeDistributionSet(uint16 treasuryFeeBasisPoints, uint16 operatorsFeeBasisPoints); + + // The amount of ETH withdrawn from LidoExecutionLayerRewardsVault contract to Lido contract + event ELRewardsReceived(uint256 amount); + + // Percent in basis points of total pooled ether allowed to withdraw from LidoExecutionLayerRewardsVault per LidoOracle report + event ELRewardsWithdrawalLimitSet(uint256 limitPoints); + + event WithdrawalCredentialsSet(bytes32 withdrawalCredentials); + + // Records a deposit made by a user + event Submitted(address indexed sender, uint256 amount, address referral); + + // The `amount` of ether was sent to the deposit_contract.deposit function + event Unbuffered(uint256 amount); + + event WithdrawalsReceived(uint256 amount); + /** * @dev As AragonApp, Lido contract must be initialized with following variables: - * @param _depositContract official ETH2 Deposit contract + * @param _depositContract official Ethereum Deposit contract * @param _oracle oracle contract * @param _operators instance of Node Operators Registry * @param _treasury treasury contract - * @param _insuranceFund insurance fund contract + * @param _executionLayerRewardsVault execution layer rewards vault contract * NB: by default, staking and the whole Lido pool are in paused state */ function initialize( @@ -128,14 +144,15 @@ contract Lido is ILido, StETH, AragonApp { address _oracle, INodeOperatorsRegistry _operators, address _treasury, - address _insuranceFund + address _executionLayerRewardsVault, + address _withdrawalQueue ) public onlyInit { NODE_OPERATORS_REGISTRY_POSITION.setStorageAddress(address(_operators)); DEPOSIT_CONTRACT_POSITION.setStorageAddress(address(_depositContract)); - _setProtocolContracts(_oracle, _treasury, _insuranceFund); + _setProtocolContracts(_oracle, _treasury, _executionLayerRewardsVault, _withdrawalQueue); initialized(); } @@ -226,6 +243,7 @@ contract Lido is ILido, StETH, AragonApp { return STAKING_STATE_POSITION.getStorageStakeLimitStruct().isStakingPaused(); } + /** * @notice Returns how much Ether can be staked in the current block * @dev Special return values: @@ -272,10 +290,11 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Send funds to the pool * @dev Users are able to submit their funds by transacting to the fallback function. - * Unlike vanilla Eth2.0 Deposit contract, accepting only 32-Ether transactions, Lido + * Unlike vanilla Ethereum Deposit contract, accepting only 32-Ether transactions, Lido * accepts payments of any size. Submitted Ethers are stored in Buffer until someone calls - * depositBufferedEther() and pushes them to the ETH2 Deposit contract. + * depositBufferedEther() and pushes them to the Ethereum Deposit contract. */ + // solhint-disable-next-line function() external payable { // protection against accidental submissions by calling non-existent function require(msg.data.length == 0, "NON_EMPTY_DATA"); @@ -305,6 +324,17 @@ contract Lido is ILido, StETH, AragonApp { emit ELRewardsReceived(msg.value); } + /** + * @notice A payable function for withdrawals acquisition. Can be called only by WithdrawalVault contract + * @dev We need a dedicated function because funds received by the default payable function + * are treated as a user deposit + */ + function receiveWithdrawals() external payable { + require(msg.sender == _getWithdrawalVault()); + + emit WithdrawalsReceived(msg.value); + } + /** * @notice Deposits buffered ethers to the official DepositContract. * @dev This function is separated from submit() to reduce the cost of sending funds. @@ -316,7 +346,7 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Deposits buffered ethers to the official DepositContract, making no more than `_maxDeposits` deposit calls. + * @notice Deposits buffered ethers to the official DepositContract, making no more than `_maxDeposits` deposit calls * @dev This function is separated from submit() to reduce the cost of sending funds. */ function depositBufferedEther(uint256 _maxDeposits) external { @@ -357,7 +387,7 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Set fee rate to `_feeBasisPoints` basis points. * The fees are accrued when: - * - oracles report staking results (beacon chain balance increase) + * - oracles report staking results (consensus layer balance increase) * - validators gain execution layer rewards (priority fees and MEV) * @param _feeBasisPoints Fee rate, in basis points */ @@ -370,59 +400,54 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Set fee distribution - * @param _treasuryFeeBasisPoints basis points go to the treasury, - * @param _insuranceFeeBasisPoints basis points go to the insurance fund, - * @param _operatorsFeeBasisPoints basis points go to node operators. + * @param _treasuryFeeBasisPoints basis points go to the treasury + * @param _operatorsFeeBasisPoints basis points go to node operators * @dev The sum has to be 10 000. */ - function setFeeDistribution( - uint16 _treasuryFeeBasisPoints, - uint16 _insuranceFeeBasisPoints, - uint16 _operatorsFeeBasisPoints - ) + function setFeeDistribution(uint16 _treasuryFeeBasisPoints, uint16 _operatorsFeeBasisPoints) external { _auth(MANAGE_FEE); require( TOTAL_BASIS_POINTS == uint256(_treasuryFeeBasisPoints) - .add(uint256(_insuranceFeeBasisPoints)) .add(uint256(_operatorsFeeBasisPoints)), "FEES_DONT_ADD_UP" ); _setBPValue(TREASURY_FEE_POSITION, _treasuryFeeBasisPoints); - _setBPValue(INSURANCE_FEE_POSITION, _insuranceFeeBasisPoints); _setBPValue(NODE_OPERATORS_FEE_POSITION, _operatorsFeeBasisPoints); - emit FeeDistributionSet(_treasuryFeeBasisPoints, _insuranceFeeBasisPoints, _operatorsFeeBasisPoints); + emit FeeDistributionSet(_treasuryFeeBasisPoints, _operatorsFeeBasisPoints); } /** - * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). + * @notice Set Lido protocol contracts (oracle, treasury, execution layer rewards vault). * * @dev Oracle contract specified here is allowed to make * periodical updates of beacon stats * by calling pushBeacon. Treasury contract specified here is used - * to accumulate the protocol treasury fee. Insurance fund contract - * specified here is used to accumulate the protocol insurance fee. + * to accumulate the protocol treasury fee. + * Execution layer rewards vault is set as `feeRecipient` + * by the Lido-participating node operators. * * @param _oracle oracle contract * @param _treasury treasury contract - * @param _insuranceFund insurance fund contract + * @param _executionLayerRewardsVault execution layer rewards vault contract */ function setProtocolContracts( address _oracle, address _treasury, - address _insuranceFund + address _executionLayerRewardsVault, + address _withdrawalQueue ) external { _auth(MANAGE_PROTOCOL_CONTRACTS_ROLE); - _setProtocolContracts(_oracle, _treasury, _insuranceFund); + _setProtocolContracts(_oracle, _treasury, _executionLayerRewardsVault, _withdrawalQueue); } /** - * @notice Set credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched to `_withdrawalCredentials` + * @notice Set credentials to withdraw ETH on the Consensus Layer side to `_withdrawalCredentials` * @dev Note that setWithdrawalCredentials discards all unused signing keys as the signatures are invalidated. * @param _withdrawalCredentials withdrawal credentials field as defined in the Ethereum PoS consensus specs */ @@ -435,18 +460,6 @@ contract Lido is ILido, StETH, AragonApp { emit WithdrawalCredentialsSet(_withdrawalCredentials); } - /** - * @dev Sets the address of LidoExecutionLayerRewardsVault contract - * @param _executionLayerRewardsVault Execution layer rewards vault contract address - */ - function setELRewardsVault(address _executionLayerRewardsVault) external { - _auth(SET_EL_REWARDS_VAULT_ROLE); - - EL_REWARDS_VAULT_POSITION.setStorageAddress(_executionLayerRewardsVault); - - emit ELRewardsVaultSet(_executionLayerRewardsVault); - } - /** * @dev Sets limit on amount of ETH to withdraw from execution layer rewards vault per LidoOracle report * @param _limitPoints limit in basis points to amount of ETH to withdraw per LidoOracle report @@ -458,59 +471,58 @@ contract Lido is ILido, StETH, AragonApp { emit ELRewardsWithdrawalLimitSet(_limitPoints); } + function getBufferWithdrawalsReserve() public view returns (uint256) { + return WITHDRAWAL_RESERVE_POSITION.getStorageUint256(); + } + /** - * @notice Updates beacon stats, collects rewards from LidoExecutionLayerRewardsVault and distributes all rewards if beacon balance increased + * @notice Updates accounting stats, collects EL rewards and distributes collected rewards if beacon balance increased * @dev periodically called by the Oracle contract - * @param _beaconValidators number of Lido's keys in the beacon state - * @param _beaconBalance summarized balance of Lido-controlled keys in wei + * @param _beaconValidators number of Lido validators on Consensus Layer + * @param _beaconBalance sum of all Lido validators' balances + * @param _withdrawalVaultBalance withdrawal vaultt balance on report block + * @param _withdrawalsReserveAmount amount of ether in deposit buffer that should be reserved for future withdrawals + * @param _requestIdToFinalizeUpTo batches of withdrawal requests that should be finalized, + * encoded as the right boundaries in the range (`lastFinalizedId`, `_requestIdToFinalizeUpTo`] + * @param _finalizationShareRates share rates that should be used for finalization of the each batch */ - function handleOracleReport(uint256 _beaconValidators, uint256 _beaconBalance) external whenNotStopped { + function handleOracleReport( + // CL values + uint256 _beaconValidators, + uint256 _beaconBalance, + // EL values + uint256 _withdrawalVaultBalance, + // decision + uint256 _withdrawalsReserveAmount, + uint256[] _requestIdToFinalizeUpTo, + uint256[] _finalizationShareRates + ) external { require(msg.sender == getOracle(), "APP_AUTH_FAILED"); + _whenNotStopped(); - uint256 depositedValidators = DEPOSITED_VALIDATORS_POSITION.getStorageUint256(); - require(_beaconValidators <= depositedValidators, "REPORTED_MORE_DEPOSITED"); - - uint256 beaconValidators = BEACON_VALIDATORS_POSITION.getStorageUint256(); - // Since the calculation of funds in the ingress queue is based on the number of validators - // that are in a transient state (deposited but not seen on beacon yet), we can't decrease the previously - // reported number (we'll be unable to figure out who is in the queue and count them). - // See LIP-1 for details https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-1.md - require(_beaconValidators >= beaconValidators, "REPORTED_LESS_VALIDATORS"); - uint256 appearedValidators = _beaconValidators.sub(beaconValidators); - - // RewardBase is the amount of money that is not included in the reward calculation - // Just appeared validators * 32 added to the previously reported beacon balance - uint256 rewardBase = (appearedValidators.mul(DEPOSIT_SIZE)).add(BEACON_BALANCE_POSITION.getStorageUint256()); - - // Save the current beacon balance and validators to - // calculate rewards on the next push - BEACON_BALANCE_POSITION.setStorageUint256(_beaconBalance); - BEACON_VALIDATORS_POSITION.setStorageUint256(_beaconValidators); - - // If LidoExecutionLayerRewardsVault address is not set just do as if there were no execution layer rewards at all - // Otherwise withdraw all rewards and put them to the buffer - // Thus, execution layer rewards are handled the same way as beacon rewards + // update withdrawals reserve + WITHDRAWAL_RESERVE_POSITION.setStorageUint256(_withdrawalsReserveAmount); - uint256 executionLayerRewards; - address executionLayerRewardsVaultAddress = getELRewardsVault(); + uint256 preBeaconBalance = BEACON_BALANCE_POSITION.getStorageUint256(); - if (executionLayerRewardsVaultAddress != address(0)) { - executionLayerRewards = ILidoExecutionLayerRewardsVault(executionLayerRewardsVaultAddress).withdrawRewards( - (_getTotalPooledEther() * EL_REWARDS_WITHDRAWAL_LIMIT_POSITION.getStorageUint256()) / TOTAL_BASIS_POINTS - ); + uint256 appearedValidators = _processBeaconStateUpdate( + _beaconValidators, + _beaconBalance + ); - if (executionLayerRewards != 0) { - BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(executionLayerRewards)); - } - } + uint256 executionLayerRewards = _processFundsMoving( + _requestIdToFinalizeUpTo, + _finalizationShareRates, + _withdrawalVaultBalance + ); - // Don’t mint/distribute any protocol fee on the non-profitable Lido oracle report - // (when beacon chain balance delta is zero or negative). - // See ADR #3 for details: https://research.lido.fi/t/rewards-distribution-after-the-merge-architecture-decision-record/1535 - if (_beaconBalance > rewardBase) { - uint256 rewards = _beaconBalance.sub(rewardBase); - distributeFee(rewards.add(executionLayerRewards)); - } + _processRewards( + preBeaconBalance, + _beaconBalance, + appearedValidators, + executionLayerRewards, + _withdrawalVaultBalance + ); } /** @@ -526,6 +538,7 @@ contract Lido is ILido, StETH, AragonApp { if (_token == ETH) { balance = _getUnaccountedEther(); // Transfer replaced by call to prevent transfer gas amount issue + // solhint-disable-next-line require(vault.call.value(balance)(), "RECOVER_TRANSFER_FAILED"); } else { ERC20 token = ERC20(_token); @@ -552,22 +565,36 @@ contract Lido is ILido, StETH, AragonApp { view returns ( uint16 treasuryFeeBasisPoints, - uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints ) { treasuryFeeBasisPoints = uint16(TREASURY_FEE_POSITION.getStorageUint256()); - insuranceFeeBasisPoints = uint16(INSURANCE_FEE_POSITION.getStorageUint256()); operatorsFeeBasisPoints = uint16(NODE_OPERATORS_FEE_POSITION.getStorageUint256()); } /** - * @notice Returns current credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched + * @notice Returns current credentials to withdraw ETH on the Consensus Layer side */ function getWithdrawalCredentials() public view returns (bytes32) { return WITHDRAWAL_CREDENTIALS_POSITION.getStorageBytes32(); } + /** + * @notice Returns the address of the vault where withdrawals arrive + * @dev withdrawal vault address is encoded as a last 160 bits of withdrawal credentials type 0x01 + * @return address of the vault or address(0) if the vault is not set + */ + function getWithdrawalVault() external view returns (address) { + return _getWithdrawalVault(); + } + + /** + * @notice Returns the address of WithdrawalQueue contract. Can be address(0) if withdrawals + */ + function getWithdrawalQueue() public view returns (address) { + return WITHDRAWAL_QUEUE_POSITION.getStorageAddress(); + } + /** * @notice Get the amount of Ether temporary buffered on this contract balance * @dev Buffered balance is kept on the contract from the moment the funds are received from user @@ -626,17 +653,10 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Returns the insurance fund address - */ - function getInsuranceFund() public view returns (address) { - return INSURANCE_FUND_POSITION.getStorageAddress(); - } - - /** - * @notice Returns the key values related to Beacon-side + * @notice Returns the key values related to Consensus Layer side of the contract (Beacon chain was deprecated) * @return depositedValidators - number of deposited validators - * @return beaconValidators - number of Lido's validators visible in the Beacon state, reported by oracles - * @return beaconBalance - total amount of Beacon-side Ether (sum of all the balances of Lido validators) + * @return beaconValidators - number of Lido's validators visible on the Consensus Layer state, reported by oracle + * @return beaconBalance - total amount of Ether on the Consensus Layer side (sum of all the balances of Lido validators) */ function getBeaconStat() public view returns (uint256 depositedValidators, uint256 beaconValidators, uint256 beaconBalance) { depositedValidators = DEPOSITED_VALIDATORS_POSITION.getStorageUint256(); @@ -651,20 +671,159 @@ contract Lido is ILido, StETH, AragonApp { return EL_REWARDS_VAULT_POSITION.getStorageAddress(); } + /** + * @dev updates beacon state + */ + function _processBeaconStateUpdate( + // CL values + uint256 _postBeaconValidators, + uint256 _postBeaconBalance + ) internal returns (uint256 appearedValidators) { + uint256 depositedValidators = DEPOSITED_VALIDATORS_POSITION.getStorageUint256(); + require(_postBeaconValidators <= depositedValidators, "REPORTED_MORE_DEPOSITED"); + + uint256 preBeaconValidators = BEACON_VALIDATORS_POSITION.getStorageUint256(); + require(_postBeaconValidators >= preBeaconValidators, "REPORTED_LESS_VALIDATORS"); + + // Save the current beacon balance and validators to + // calculate rewards on the next push + + BEACON_BALANCE_POSITION.setStorageUint256(_postBeaconBalance); + + if (_postBeaconValidators > preBeaconValidators) { + BEACON_VALIDATORS_POSITION.setStorageUint256(_postBeaconValidators); + } + + return _postBeaconValidators.sub(preBeaconValidators); + } + + /** + * @dev move funds between ELRewardsVault, WithdrawalVault and deposit buffer. Updates counters respectively + */ + function _processFundsMoving( + uint256[] _requestIdToFinalizeUpTo, + uint256[] _finalizationShareRates, + uint256 _withdrawalVaultBalance + ) internal returns (uint256 executionLayerRewards) { + executionLayerRewards = 0; + address elRewardsVaultAddress = getELRewardsVault(); + // If LidoExecutionLayerRewardsVault address is not set just do as if there were no execution layer rewards at all + // Otherwise withdraw all rewards and put them to the buffer + if (elRewardsVaultAddress != address(0)) { + executionLayerRewards = ILidoExecutionLayerRewardsVault(elRewardsVaultAddress).withdrawRewards( + (_getTotalPooledEther() * EL_REWARDS_WITHDRAWAL_LIMIT_POSITION.getStorageUint256()) / TOTAL_BASIS_POINTS + ); + } + + address withdrawalVaultAddress = _getWithdrawalVault(); + + uint256 lockedToWithdrawalQueue = 0; + + if (withdrawalVaultAddress != address(0)) { + // we pull all the accounted ether from WithdrawalVault + IWithdrawalVault(withdrawalVaultAddress).withdrawWithdrawals(_withdrawalVaultBalance); + + // And pass some ether to WithdrawalQueue to fulfill requests + lockedToWithdrawalQueue = _processWithdrawals( + _requestIdToFinalizeUpTo, + _finalizationShareRates + ); + } + + uint256 preBufferedEther = _getBufferedEther(); + + uint256 postBufferedEther = _getBufferedEther() + .add(executionLayerRewards) + .add(_withdrawalVaultBalance) + .sub(lockedToWithdrawalQueue); + + if (preBufferedEther != postBufferedEther) { + BUFFERED_ETHER_POSITION.setStorageUint256(postBufferedEther); + } + } + + function _processRewards( + uint256 _preBeaconBalance, + uint256 _postBeaconBalance, + uint256 _appearedValidators, + uint256 _executionLayerRewards, + uint256 _withdrawalVaultBalance + ) internal { + // Post-withdrawal rewards + // rewards = (beacon balance new - beacon balance old) - (appeared validators x 32 ETH) + // + withdrawn from execution layer rewards vault + withdrawn from withdrawal credentials vault + + uint256 rewardsBase = (_appearedValidators.mul(DEPOSIT_SIZE)).add(_preBeaconBalance); + + // Don’t mint/distribute any protocol fee on the non-profitable Lido oracle report + // (when consensus layer balance delta is zero or negative). + // See ADR #3 for details: + // https://research.lido.fi/t/rewards-distribution-after-the-merge-architecture-decision-record/1535 + if (_postBeaconBalance.add(_withdrawalVaultBalance) > rewardsBase) { + uint256 consensusLayerRewards = _postBeaconBalance.add(_withdrawalVaultBalance).sub(rewardsBase); + _distributeFee(consensusLayerRewards.add(_executionLayerRewards)); + } + } + + /** + * @dev finalize requests in the queue, burn shares + * @return transferredToWithdrawalQueue amount locked on WithdrawalQueue to fulfill withdrawal requests + */ + function _processWithdrawals( + uint256[] _requestIdToFinalizeUpTo, + uint256[] _finalizationShareRates + ) internal returns (uint256 lockedToWithdrawalQueue) { + address withdrawalQueueAddress = _getWithdrawalVault(); + // do nothing if the withdrawals vault address is not configured + if (withdrawalQueueAddress == address(0)) { + return 0; + } + + IWithdrawalQueue withdrawalQueue = IWithdrawalQueue(withdrawalQueueAddress); + + lockedToWithdrawalQueue = 0; + uint256 burnedSharesAccumulator = 0; + + for (uint256 i = 0; i < _requestIdToFinalizeUpTo.length; i++) { + uint256 lastIdToFinalize = _requestIdToFinalizeUpTo[i]; + require(lastIdToFinalize >= withdrawalQueue.finalizedRequestsCounter(), "BAD_FINALIZATION_PARAMS"); + + uint256 shareRate = _finalizationShareRates[i]; + + (uint256 etherToLock, uint256 sharesToBurn) = withdrawalQueue.calculateFinalizationParams( + lastIdToFinalize, + shareRate + ); + + burnedSharesAccumulator = burnedSharesAccumulator.add(sharesToBurn); + + withdrawalQueue.finalize.value(etherToLock)( + lastIdToFinalize, + shareRate + ); + } + + _burnShares(withdrawalQueueAddress, sharesToBurn); + } + /** * @dev Internal function to set authorized oracle address * @param _oracle oracle contract + * @param _treasury treasury contract + * @param _executionLayerRewardsVault execution layer rewards vault contract */ - function _setProtocolContracts(address _oracle, address _treasury, address _insuranceFund) internal { + function _setProtocolContracts( + address _oracle, address _treasury, address _executionLayerRewardsVault, address _withdrawalQueue + ) internal { require(_oracle != address(0), "ORACLE_ZERO_ADDRESS"); require(_treasury != address(0), "TREASURY_ZERO_ADDRESS"); - require(_insuranceFund != address(0), "INSURANCE_FUND_ZERO_ADDRESS"); + //NB: _executionLayerRewardsVault and _withdrawalQueue can be zero ORACLE_POSITION.setStorageAddress(_oracle); TREASURY_POSITION.setStorageAddress(_treasury); - INSURANCE_FUND_POSITION.setStorageAddress(_insuranceFund); + EL_REWARDS_VAULT_POSITION.setStorageAddress(_executionLayerRewardsVault); - emit ProtocolContactsSet(_oracle, _treasury, _insuranceFund); + emit ProtocolContactsSet(_oracle, _treasury, _executionLayerRewardsVault, _withdrawalQueue); } /** @@ -715,22 +874,30 @@ contract Lido is ILido, StETH, AragonApp { /** * @dev Deposits buffered eth to the DepositContract and assigns chunked deposits to node operators */ - function _depositBufferedEther(uint256 _maxDeposits) internal whenNotStopped { + function _depositBufferedEther(uint256 _maxDeposits) internal { + _whenNotStopped(); + uint256 buffered = _getBufferedEther(); - if (buffered >= DEPOSIT_SIZE) { - uint256 unaccounted = _getUnaccountedEther(); - uint256 numDeposits = buffered.div(DEPOSIT_SIZE); - _markAsUnbuffered(_ETH2Deposit(numDeposits < _maxDeposits ? numDeposits : _maxDeposits)); - assert(_getUnaccountedEther() == unaccounted); + uint256 withdrawalReserve = getBufferWithdrawalsReserve(); + + if (buffered > withdrawalReserve) { + buffered = buffered.sub(withdrawalReserve); + + if (buffered >= DEPOSIT_SIZE) { + uint256 unaccounted = _getUnaccountedEther(); + uint256 numDeposits = buffered.div(DEPOSIT_SIZE); + _markAsUnbuffered(_ConsensusLayerDeposit(numDeposits < _maxDeposits ? numDeposits : _maxDeposits)); + assert(_getUnaccountedEther() == unaccounted); + } } } /** - * @dev Performs deposits to the ETH 2.0 side + * @dev Performs deposits to the Consensus Layer side * @param _numDeposits Number of deposits to perform * @return actually deposited Ether amount */ - function _ETH2Deposit(uint256 _numDeposits) internal returns (uint256) { + function _ConsensusLayerDeposit(uint256 _numDeposits) internal returns (uint256) { (bytes memory pubkeys, bytes memory signatures) = getOperators().assignNextSigningKeys(_numDeposits); if (pubkeys.length == 0) { @@ -796,9 +963,9 @@ contract Lido is ILido, StETH, AragonApp { /** * @dev Distributes fee portion of the rewards by minting and distributing corresponding amount of liquid tokens. - * @param _totalRewards Total rewards accrued on the Ethereum 2.0 side in wei + * @param _totalRewards Total rewards accrued both on the Consensus Layer and Execution Layer sides in wei */ - function distributeFee(uint256 _totalRewards) internal { + function _distributeFee(uint256 _totalRewards) internal { // We need to take a defined percentage of the reported reward as a fee, and we do // this by minting new token shares and assigning them to the fee recipients (see // StETH docs for the explanation of the shares mechanics). The staking rewards fee @@ -837,19 +1004,14 @@ contract Lido is ILido, StETH, AragonApp { // balances of the holders, as if the fee was taken in parts from each of them. _mintShares(address(this), shares2mint); - (,uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints) = getFeeDistribution(); - - uint256 toInsuranceFund = shares2mint.mul(insuranceFeeBasisPoints).div(TOTAL_BASIS_POINTS); - address insuranceFund = getInsuranceFund(); - _transferShares(address(this), insuranceFund, toInsuranceFund); - _emitTransferAfterMintingShares(insuranceFund, toInsuranceFund); + (, uint16 operatorsFeeBasisPoints) = getFeeDistribution(); uint256 distributedToOperatorsShares = _distributeNodeOperatorsReward( shares2mint.mul(operatorsFeeBasisPoints).div(TOTAL_BASIS_POINTS) ); // Transfer the rest of the fee to treasury - uint256 toTreasury = shares2mint.sub(toInsuranceFund).sub(distributedToOperatorsShares); + uint256 toTreasury = shares2mint.sub(distributedToOperatorsShares); address treasury = getTreasury(); _transferShares(address(this), treasury, toTreasury); @@ -880,7 +1042,7 @@ contract Lido is ILido, StETH, AragonApp { /** * @dev Records a deposit to the deposit_contract.deposit function - * @param _amount Total amount deposited to the ETH 2.0 side + * @param _amount Total amount deposited to the Consensus Layer side */ function _markAsUnbuffered(uint256 _amount) internal { BUFFERED_ETHER_POSITION.setStorageUint256( @@ -914,6 +1076,14 @@ contract Lido is ILido, StETH, AragonApp { return address(this).balance.sub(_getBufferedEther()); } + function _getWithdrawalVault() internal view returns (address) { + uint8 credentialsType = uint8(uint256(getWithdrawalCredentials()) >> 248); + if (credentialsType == 0x01) { + return address(uint160(getWithdrawalCredentials())); + } + return address(0); + } + /** * @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 beacon state. @@ -932,9 +1102,9 @@ contract Lido is ILido, StETH, AragonApp { * @return total balance in wei */ function _getTotalPooledEther() internal view returns (uint256) { - return _getBufferedEther().add( - BEACON_BALANCE_POSITION.getStorageUint256() - ).add(_getTransientBalance()); + return _getBufferedEther() + .add(_getTransientBalance()) + .add(BEACON_BALANCE_POSITION.getStorageUint256()); } /** diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index 970c83658..129dc987c 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -376,9 +376,10 @@ contract StETH is IERC20, Pausable { * - `_spender` cannot be the zero address. * - the contract must not be paused. */ - function _approve(address _owner, address _spender, uint256 _amount) internal whenNotStopped { + function _approve(address _owner, address _spender, uint256 _amount) internal { require(_owner != address(0), "APPROVE_FROM_ZERO_ADDRESS"); require(_spender != address(0), "APPROVE_TO_ZERO_ADDRESS"); + _whenNotStopped(); allowances[_owner][_spender] = _amount; emit Approval(_owner, _spender, _amount); @@ -408,9 +409,10 @@ contract StETH is IERC20, Pausable { * - `_sender` must hold at least `_sharesAmount` shares. * - the contract must not be paused. */ - function _transferShares(address _sender, address _recipient, uint256 _sharesAmount) internal whenNotStopped { + function _transferShares(address _sender, address _recipient, uint256 _sharesAmount) internal { require(_sender != address(0), "TRANSFER_FROM_THE_ZERO_ADDRESS"); require(_recipient != address(0), "TRANSFER_TO_THE_ZERO_ADDRESS"); + _whenNotStopped(); uint256 currentSenderShares = shares[_sender]; require(_sharesAmount <= currentSenderShares, "TRANSFER_AMOUNT_EXCEEDS_BALANCE"); @@ -428,8 +430,9 @@ contract StETH is IERC20, Pausable { * - `_recipient` cannot be the zero address. * - the contract must not be paused. */ - function _mintShares(address _recipient, uint256 _sharesAmount) internal whenNotStopped returns (uint256 newTotalShares) { + function _mintShares(address _recipient, uint256 _sharesAmount) internal returns (uint256 newTotalShares) { require(_recipient != address(0), "MINT_TO_THE_ZERO_ADDRESS"); + _whenNotStopped(); newTotalShares = _getTotalShares().add(_sharesAmount); TOTAL_SHARES_POSITION.setStorageUint256(newTotalShares); @@ -454,8 +457,9 @@ contract StETH is IERC20, Pausable { * - `_account` must hold at least `_sharesAmount` shares. * - the contract must not be paused. */ - function _burnShares(address _account, uint256 _sharesAmount) internal whenNotStopped returns (uint256 newTotalShares) { + function _burnShares(address _account, uint256 _sharesAmount) internal returns (uint256 newTotalShares) { require(_account != address(0), "BURN_FROM_THE_ZERO_ADDRESS"); + _whenNotStopped(); uint256 accountShares = shares[_account]; require(_sharesAmount <= accountShares, "BURN_AMOUNT_EXCEEDS_BALANCE"); diff --git a/contracts/0.4.24/interfaces/IACL.sol b/contracts/0.4.24/interfaces/IACL.sol index c49ad52d2..7cca67a88 100644 --- a/contracts/0.4.24/interfaces/IACL.sol +++ b/contracts/0.4.24/interfaces/IACL.sol @@ -10,5 +10,5 @@ interface IACL { // TODO: this should be external // See https://github.com/ethereum/solidity/issues/4832 - function hasPermission(address who, address where, bytes32 what, bytes how) public view returns (bool); + function hasPermission(address who, address where, bytes32 what, bytes how) external view returns (bool); } diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol deleted file mode 100644 index 17b5427cc..000000000 --- a/contracts/0.4.24/interfaces/ILido.sol +++ /dev/null @@ -1,264 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Lido - -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.4.24; - - -/** - * @title Liquid staking pool - * - * For the high-level description of the pool operation please refer to the paper. - * Pool manages withdrawal keys and fees. It receives ether submitted by users on the ETH 1 side - * and stakes it via the deposit_contract.sol contract. It doesn't hold ether on it's balance, - * only a small portion (buffer) of it. - * It also mints new tokens for rewards generated at the ETH 2.0 side. - * - * At the moment withdrawals are not possible in the beacon chain and there's no workaround. - * Pool will be upgraded to an actual implementation when withdrawals are enabled - * (Phase 1.5 or 2 of Eth2 launch, likely late 2022 or 2023). - */ -interface ILido { - function totalSupply() external view returns (uint256); - function getTotalShares() external view returns (uint256); - - /** - * @notice Stop pool routine operations - */ - function stop() external; - - /** - * @notice Resume pool routine operations - */ - function resume() external; - - /** - * @notice Stops accepting new Ether to the protocol - * - * @dev While accepting new Ether is stopped, calls to the `submit` function, - * as well as to the default payable function, will revert. - * - * Emits `StakingPaused` event. - */ - function pauseStaking() external; - - /** - * @notice Resumes accepting new Ether to the protocol (if `pauseStaking` was called previously) - * NB: Staking could be rate-limited by imposing a limit on the stake amount - * at each moment in time, see `setStakingLimit()` and `removeStakingLimit()` - * - * @dev Preserves staking limit if it was set previously - * - * Emits `StakingResumed` event - */ - function resumeStaking() external; - - /** - * @notice Sets the staking rate limit - * - * @dev Reverts if: - * - `_maxStakeLimit` == 0 - * - `_maxStakeLimit` >= 2^96 - * - `_maxStakeLimit` < `_stakeLimitIncreasePerBlock` - * - `_maxStakeLimit` / `_stakeLimitIncreasePerBlock` >= 2^32 (only if `_stakeLimitIncreasePerBlock` != 0) - * - * Emits `StakingLimitSet` event - * - * @param _maxStakeLimit max stake limit value - * @param _stakeLimitIncreasePerBlock stake limit increase per single block - */ - function setStakingLimit(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) external; - - /** - * @notice Removes the staking rate limit - * - * Emits `StakingLimitRemoved` event - */ - function removeStakingLimit() external; - - /** - * @notice Check staking state: whether it's paused or not - */ - function isStakingPaused() external view returns (bool); - - /** - * @notice Returns how much Ether can be staked in the current block - * @dev Special return values: - * - 2^256 - 1 if staking is unlimited; - * - 0 if staking is paused or if limit is exhausted. - */ - function getCurrentStakeLimit() external view returns (uint256); - - /** - * @notice Returns full info about current stake limit params and state - * @dev Might be used for the advanced integration requests. - * @return isStakingPaused staking pause state (equivalent to return of isStakingPaused()) - * @return isStakingLimitSet whether the stake limit is set - * @return currentStakeLimit current stake limit (equivalent to return of getCurrentStakeLimit()) - * @return maxStakeLimit max stake limit - * @return maxStakeLimitGrowthBlocks blocks needed to restore max stake limit from the fully exhausted state - * @return prevStakeLimit previously reached stake limit - * @return prevStakeBlockNumber previously seen block number - */ - function getStakeLimitFullInfo() external view returns ( - bool isStakingPaused, - bool isStakingLimitSet, - uint256 currentStakeLimit, - uint256 maxStakeLimit, - uint256 maxStakeLimitGrowthBlocks, - uint256 prevStakeLimit, - uint256 prevStakeBlockNumber - ); - - event Stopped(); - event Resumed(); - - event StakingPaused(); - event StakingResumed(); - event StakingLimitSet(uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock); - event StakingLimitRemoved(); - - /** - * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). - * @param _oracle oracle contract - * @param _treasury treasury contract - * @param _insuranceFund insurance fund contract - */ - function setProtocolContracts( - address _oracle, - address _treasury, - address _insuranceFund - ) external; - - event ProtocolContactsSet(address oracle, address treasury, address insuranceFund); - - /** - * @notice Set fee rate to `_feeBasisPoints` basis points. - * The fees are accrued when: - * - oracles report staking results (beacon chain balance increase) - * - validators gain execution layer rewards (priority fees and MEV) - * @param _feeBasisPoints Fee rate, in basis points - */ - function setFee(uint16 _feeBasisPoints) external; - - /** - * @notice Set fee distribution - * @param _treasuryFeeBasisPoints basis points go to the treasury, - * @param _insuranceFeeBasisPoints basis points go to the insurance fund, - * @param _operatorsFeeBasisPoints basis points go to node operators. - * @dev The sum has to be 10 000. - */ - function setFeeDistribution( - uint16 _treasuryFeeBasisPoints, - uint16 _insuranceFeeBasisPoints, - uint16 _operatorsFeeBasisPoints - ) external; - - /** - * @notice Returns staking rewards fee rate - */ - function getFee() external view returns (uint16 feeBasisPoints); - - /** - * @notice Returns fee distribution proportion - */ - function getFeeDistribution() external view returns ( - uint16 treasuryFeeBasisPoints, - uint16 insuranceFeeBasisPoints, - uint16 operatorsFeeBasisPoints - ); - - event FeeSet(uint16 feeBasisPoints); - - event FeeDistributionSet(uint16 treasuryFeeBasisPoints, uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints); - - /** - * @notice A payable function supposed to be called only by LidoExecutionLayerRewardsVault contract - * @dev We need a dedicated function because funds received by the default payable function - * are treated as a user deposit - */ - function receiveELRewards() external payable; - - // The amount of ETH withdrawn from LidoExecutionLayerRewardsVault contract to Lido contract - event ELRewardsReceived(uint256 amount); - - /** - * @dev Sets limit on amount of ETH to withdraw from execution layer rewards vault per LidoOracle report - * @param _limitPoints limit in basis points to amount of ETH to withdraw per LidoOracle report - */ - function setELRewardsWithdrawalLimit(uint16 _limitPoints) external; - - // Percent in basis points of total pooled ether allowed to withdraw from LidoExecutionLayerRewardsVault per LidoOracle report - event ELRewardsWithdrawalLimitSet(uint256 limitPoints); - - /** - * @notice Set credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched to `_withdrawalCredentials` - * @dev Note that setWithdrawalCredentials discards all unused signing keys as the signatures are invalidated. - * @param _withdrawalCredentials withdrawal credentials field as defined in the Ethereum PoS consensus specs - */ - function setWithdrawalCredentials(bytes32 _withdrawalCredentials) external; - - /** - * @notice Returns current credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched - */ - function getWithdrawalCredentials() external view returns (bytes); - - event WithdrawalCredentialsSet(bytes32 withdrawalCredentials); - - /** - * @dev Sets the address of LidoExecutionLayerRewardsVault contract - * @param _executionLayerRewardsVault Execution layer rewards vault contract address - */ - function setELRewardsVault(address _executionLayerRewardsVault) external; - - // The `executionLayerRewardsVault` was set as the execution layer rewards vault for Lido - event ELRewardsVaultSet(address executionLayerRewardsVault); - - /** - * @notice Ether on the ETH 2.0 side reported by the oracle - * @param _epoch Epoch id - * @param _eth2balance Balance in wei on the ETH 2.0 side - */ - function handleOracleReport(uint256 _epoch, uint256 _eth2balance) external; - - - // User functions - - /** - * @notice Adds eth to the pool - * @return StETH Amount of StETH generated - */ - function submit(address _referral) external payable returns (uint256 StETH); - - // Records a deposit made by a user - event Submitted(address indexed sender, uint256 amount, address referral); - - // The `amount` of ether was sent to the deposit_contract.deposit function - event Unbuffered(uint256 amount); - - // Requested withdrawal of `etherAmount` to `pubkeyHash` on the ETH 2.0 side, `tokenAmount` burned by `sender`, - // `sentFromBuffer` was sent on the current Ethereum side. - event Withdrawal(address indexed sender, uint256 tokenAmount, uint256 sentFromBuffer, - bytes32 indexed pubkeyHash, uint256 etherAmount); - - - // Info functions - - /** - * @notice Gets the amount of Ether controlled by the system - */ - function getTotalPooledEther() external view returns (uint256); - - /** - * @notice Gets the amount of Ether temporary buffered on this contract balance - */ - function getBufferedEther() external view returns (uint256); - - /** - * @notice Returns the key values related to Beacon-side - * @return depositedValidators - number of deposited validators - * @return beaconValidators - number of Lido's validators visible in the Beacon state, reported by oracles - * @return beaconBalance - total amount of Beacon-side Ether (sum of all the balances of Lido validators) - */ - function getBeaconStat() external view returns (uint256 depositedValidators, uint256 beaconValidators, uint256 beaconBalance); -} diff --git a/contracts/0.4.24/interfaces/ILidoOracle.sol b/contracts/0.4.24/interfaces/ILidoOracle.sol index 8847dd08d..2a91e2ce4 100644 --- a/contracts/0.4.24/interfaces/ILidoOracle.sol +++ b/contracts/0.4.24/interfaces/ILidoOracle.sol @@ -4,7 +4,7 @@ pragma solidity 0.4.24; -import "../interfaces/ILido.sol"; +import "../Lido.sol"; /** @@ -49,7 +49,7 @@ interface ILidoOracle { /** * @notice Return the Lido contract address */ - function getLido() public view returns (ILido); + function getLido() public view returns (Lido); /** * @notice Return the number of exactly the same reports needed to finalize the epoch @@ -183,7 +183,7 @@ interface ILidoOracle { uint256 timeElapsed ); - + /** * @notice Initialize the contract (version 3 for now) from scratch * @dev For details see https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-10.md diff --git a/contracts/0.4.24/interfaces/IWithdrawalQueue.sol b/contracts/0.4.24/interfaces/IWithdrawalQueue.sol new file mode 100644 index 000000000..365dc51ec --- /dev/null +++ b/contracts/0.4.24/interfaces/IWithdrawalQueue.sol @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Lido + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.4.24; + +/** + * @notice WithdrawalQueue interface to be used in Lido.sol contract + */ +interface IWithdrawalQueue { + function calculateFinalizationParams( + uint256 _lastIdToFinalize, + uint256 _shareRate + ) external view returns (uint256 sharesToBurn, uint256 etherToLock); + + function finalize( + uint256 _lastIdToFinalize, + uint256 _shareRate + ) external payable; + + function finalizedRequestsCounter() external view returns (uint256); +} diff --git a/contracts/0.4.24/interfaces/IWithdrawalVault.sol b/contracts/0.4.24/interfaces/IWithdrawalVault.sol new file mode 100644 index 000000000..e8126fbf7 --- /dev/null +++ b/contracts/0.4.24/interfaces/IWithdrawalVault.sol @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2023 Lido + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.4.24; + +/** + * @notice interface for WithdrawalVault to use in Lido contract + */ +interface IWithdrawalVault { + function withdrawWithdrawals(uint256 _amount) external; +} diff --git a/contracts/0.4.24/lib/Pausable.sol b/contracts/0.4.24/lib/Pausable.sol index 527e4f8cf..f4b75b7f2 100644 --- a/contracts/0.4.24/lib/Pausable.sol +++ b/contracts/0.4.24/lib/Pausable.sol @@ -15,26 +15,28 @@ contract Pausable { bytes32 internal constant ACTIVE_FLAG_POSITION = keccak256("lido.Pausable.activeFlag"); - modifier whenNotStopped() { + function _whenNotStopped() internal view { require(ACTIVE_FLAG_POSITION.getStorageBool(), "CONTRACT_IS_STOPPED"); - _; } - modifier whenStopped() { + function _whenStopped() internal view { require(!ACTIVE_FLAG_POSITION.getStorageBool(), "CONTRACT_IS_ACTIVE"); - _; } function isStopped() external view returns (bool) { return !ACTIVE_FLAG_POSITION.getStorageBool(); } - function _stop() internal whenNotStopped { + function _stop() internal { + _whenNotStopped(); + ACTIVE_FLAG_POSITION.setStorageBool(false); emit Stopped(); } - function _resume() internal whenStopped { + function _resume() internal { + _whenStopped(); + ACTIVE_FLAG_POSITION.setStorageBool(true); emit Resumed(); } diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index 46bccbd40..90ec0cbd4 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -10,9 +10,10 @@ import "@aragon/os/contracts/lib/math/SafeMath.sol"; import "openzeppelin-solidity/contracts/introspection/ERC165Checker.sol"; import "../interfaces/IBeaconReportReceiver.sol"; -import "../interfaces/ILido.sol"; import "../interfaces/ILidoOracle.sol"; +import "../Lido.sol"; + import "./ReportUtils.sol"; @@ -133,8 +134,8 @@ contract LidoOracle is ILidoOracle, AragonApp { /** * @notice Return the Lido contract address */ - function getLido() public view returns (ILido) { - return ILido(LIDO_POSITION.getStorageAddress()); + function getLido() public view returns (Lido) { + return Lido(LIDO_POSITION.getStorageAddress()); } /** @@ -633,9 +634,16 @@ contract LidoOracle is ILidoOracle, AragonApp { _clearReportingAndAdvanceTo(_epochId + _beaconSpec.epochsPerFrame); // report to the Lido and collect stats - ILido lido = getLido(); + Lido lido = getLido(); uint256 prevTotalPooledEther = lido.totalSupply(); - lido.handleOracleReport(_beaconValidators, _beaconBalanceEth1); + lido.handleOracleReport( + _beaconValidators, + _beaconBalanceEth1, + 0, + 0, + new uint256[](0), + new uint256[](0) + ); // here should be withdrawal params uint256 postTotalPooledEther = lido.totalSupply(); PRE_COMPLETED_TOTAL_POOLED_ETHER_POSITION.setStorageUint256(prevTotalPooledEther); diff --git a/contracts/0.4.24/oracle/ReportUtils.sol b/contracts/0.4.24/oracle/ReportUtils.sol index b4777fb5e..bd007bfb8 100644 --- a/contracts/0.4.24/oracle/ReportUtils.sol +++ b/contracts/0.4.24/oracle/ReportUtils.sol @@ -9,7 +9,7 @@ pragma solidity 0.4.24; * * +00 | uint16 | count | 0..256 | number of reports received exactly like this * +16 | uint32 | beaconValidators | 0..1e9 | number of Lido's validators in beacon chain - * +48 | uint64 | beaconBalance | 0..1e18 | total amout of their balance + * +48 | uint64 | beaconBalance | 0..1e18 | total amount of their balance * * Note that the 'count' is the leftmost field here. Thus it is possible to apply addition * operations to it when it is encoded, provided that you watch for the overflow. diff --git a/contracts/0.4.24/template/IETHRegistrarController.sol b/contracts/0.4.24/template/IETHRegistrarController.sol index c2236884d..d90868a77 100644 --- a/contracts/0.4.24/template/IETHRegistrarController.sol +++ b/contracts/0.4.24/template/IETHRegistrarController.sol @@ -9,10 +9,10 @@ interface IETHRegistrarController { function minCommitmentAge() external view returns (uint256); function maxCommitmentAge() external view returns (uint256); - function rentPrice(string name, uint256 duration) view external returns (uint256); + function rentPrice(string name, uint256 duration) external view returns (uint256); function valid(string name) external pure returns (bool); function available(string name) external view returns (bool); - function makeCommitment(string name, address owner, bytes32 secret) pure external returns (bytes32); + function makeCommitment(string name, address owner, bytes32 secret) external pure returns (bytes32); function commit(bytes32 commitment) external; function register(string name, address owner, uint256 duration, bytes32 secret) external payable; } diff --git a/contracts/0.4.24/template/LidoTemplate.sol b/contracts/0.4.24/template/LidoTemplate.sol index c43d1120a..00215241b 100644 --- a/contracts/0.4.24/template/LidoTemplate.sol +++ b/contracts/0.4.24/template/LidoTemplate.sol @@ -30,57 +30,57 @@ import "../oracle/LidoOracle.sol"; import "../nos/NodeOperatorsRegistry.sol"; import "../interfaces/IDepositContract.sol"; - contract LidoTemplate is IsContract { // Configuration errors - string constant private ERROR_ZERO_OWNER = "TMPL_ZERO_OWNER"; - string constant private ERROR_ENS_NOT_CONTRACT = "TMPL_ENS_NOT_CONTRACT"; - string constant private ERROR_DAO_FACTORY_NOT_CONTRACT = "TMPL_DAO_FAC_NOT_CONTRACT"; - string constant private ERROR_MINIME_FACTORY_NOT_CONTRACT = "TMPL_MINIME_FAC_NOT_CONTRACT"; - string constant private ERROR_ARAGON_ID_NOT_CONTRACT = "TMPL_ARAGON_ID_NOT_CONTRACT"; - string constant private ERROR_APM_REGISTRY_FACTORY_NOT_CONTRACT = "TMPL_APM_REGISTRY_FAC_NOT_CONTRACT"; - string constant private ERROR_EMPTY_HOLDERS = "TMPL_EMPTY_HOLDERS"; - string constant private ERROR_BAD_AMOUNTS_LEN = "TMPL_BAD_AMOUNTS_LEN"; - string constant private ERROR_INVALID_ID = "TMPL_INVALID_ID"; - string constant private ERROR_UNEXPECTED_TOTAL_SUPPLY = "TMPL_UNEXPECTED_TOTAL_SUPPLY"; + string private constant ERROR_ZERO_OWNER = "TMPL_ZERO_OWNER"; + string private constant ERROR_ENS_NOT_CONTRACT = "TMPL_ENS_NOT_CONTRACT"; + string private constant ERROR_DAO_FACTORY_NOT_CONTRACT = "TMPL_DAO_FAC_NOT_CONTRACT"; + string private constant ERROR_MINIME_FACTORY_NOT_CONTRACT = "TMPL_MINIME_FAC_NOT_CONTRACT"; + string private constant ERROR_ARAGON_ID_NOT_CONTRACT = "TMPL_ARAGON_ID_NOT_CONTRACT"; + string private constant ERROR_APM_REGISTRY_FACTORY_NOT_CONTRACT = "TMPL_APM_REGISTRY_FAC_NOT_CONTRACT"; + string private constant ERROR_EMPTY_HOLDERS = "TMPL_EMPTY_HOLDERS"; + string private constant ERROR_BAD_AMOUNTS_LEN = "TMPL_BAD_AMOUNTS_LEN"; + string private constant ERROR_INVALID_ID = "TMPL_INVALID_ID"; + string private constant ERROR_UNEXPECTED_TOTAL_SUPPLY = "TMPL_UNEXPECTED_TOTAL_SUPPLY"; // Operational errors - string constant private ERROR_PERMISSION_DENIED = "TMPL_PERMISSION_DENIED"; - string constant private ERROR_REGISTRY_ALREADY_DEPLOYED = "TMPL_REGISTRY_ALREADY_DEPLOYED"; - string constant private ERROR_ENS_NODE_NOT_OWNED_BY_TEMPLATE = "TMPL_ENS_NODE_NOT_OWNED_BY_TEMPLATE"; - string constant private ERROR_REGISTRY_NOT_DEPLOYED = "TMPL_REGISTRY_NOT_DEPLOYED"; - string constant private ERROR_DAO_ALREADY_DEPLOYED = "TMPL_DAO_ALREADY_DEPLOYED"; - string constant private ERROR_DAO_NOT_DEPLOYED = "TMPL_DAO_NOT_DEPLOYED"; - string constant private ERROR_ALREADY_FINALIZED = "TMPL_ALREADY_FINALIZED"; + string private constant ERROR_PERMISSION_DENIED = "TMPL_PERMISSION_DENIED"; + string private constant ERROR_REGISTRY_ALREADY_DEPLOYED = "TMPL_REGISTRY_ALREADY_DEPLOYED"; + string private constant ERROR_ENS_NODE_NOT_OWNED_BY_TEMPLATE = "TMPL_ENS_NODE_NOT_OWNED_BY_TEMPLATE"; + string private constant ERROR_REGISTRY_NOT_DEPLOYED = "TMPL_REGISTRY_NOT_DEPLOYED"; + string private constant ERROR_DAO_ALREADY_DEPLOYED = "TMPL_DAO_ALREADY_DEPLOYED"; + string private constant ERROR_DAO_NOT_DEPLOYED = "TMPL_DAO_NOT_DEPLOYED"; + string private constant ERROR_ALREADY_FINALIZED = "TMPL_ALREADY_FINALIZED"; // Aragon app IDs - bytes32 constant private ARAGON_AGENT_APP_ID = 0x9ac98dc5f995bf0211ed589ef022719d1487e5cb2bab505676f0d084c07cf89a; // agent.aragonpm.eth - bytes32 constant private ARAGON_VAULT_APP_ID = 0x7e852e0fcfce6551c13800f1e7476f982525c2b5277ba14b24339c68416336d1; // vault.aragonpm.eth - bytes32 constant private ARAGON_VOTING_APP_ID = 0x9fa3927f639745e587912d4b0fea7ef9013bf93fb907d29faeab57417ba6e1d4; // voting.aragonpm.eth - bytes32 constant private ARAGON_FINANCE_APP_ID = 0xbf8491150dafc5dcaee5b861414dca922de09ccffa344964ae167212e8c673ae; // finance.aragonpm.eth - bytes32 constant private ARAGON_TOKEN_MANAGER_APP_ID = 0x6b20a3010614eeebf2138ccec99f028a61c811b3b1a3343b6ff635985c75c91f; // token-manager.aragonpm.eth + bytes32 private constant ARAGON_AGENT_APP_ID = 0x9ac98dc5f995bf0211ed589ef022719d1487e5cb2bab505676f0d084c07cf89a; // agent.aragonpm.eth + bytes32 private constant ARAGON_VAULT_APP_ID = 0x7e852e0fcfce6551c13800f1e7476f982525c2b5277ba14b24339c68416336d1; // vault.aragonpm.eth + bytes32 private constant ARAGON_VOTING_APP_ID = 0x9fa3927f639745e587912d4b0fea7ef9013bf93fb907d29faeab57417ba6e1d4; // voting.aragonpm.eth + bytes32 private constant ARAGON_FINANCE_APP_ID = 0xbf8491150dafc5dcaee5b861414dca922de09ccffa344964ae167212e8c673ae; // finance.aragonpm.eth + bytes32 private constant ARAGON_TOKEN_MANAGER_APP_ID = + 0x6b20a3010614eeebf2138ccec99f028a61c811b3b1a3343b6ff635985c75c91f; // token-manager.aragonpm.eth // APM app names, see https://github.com/aragon/aragonOS/blob/f3ae59b/contracts/apm/APMRegistry.sol#L11 - string constant private APM_APP_NAME = "apm-registry"; - string constant private APM_REPO_APP_NAME = "apm-repo"; - string constant private APM_ENSSUB_APP_NAME = "apm-enssub"; + string private constant APM_APP_NAME = "apm-registry"; + string private constant APM_REPO_APP_NAME = "apm-repo"; + string private constant APM_ENSSUB_APP_NAME = "apm-enssub"; // Aragon app names - string constant private ARAGON_AGENT_APP_NAME = "aragon-agent"; - string constant private ARAGON_FINANCE_APP_NAME = "aragon-finance"; - string constant private ARAGON_TOKEN_MANAGER_APP_NAME = "aragon-token-manager"; - string constant private ARAGON_VOTING_APP_NAME = "aragon-voting"; + string private constant ARAGON_AGENT_APP_NAME = "aragon-agent"; + string private constant ARAGON_FINANCE_APP_NAME = "aragon-finance"; + string private constant ARAGON_TOKEN_MANAGER_APP_NAME = "aragon-token-manager"; + string private constant ARAGON_VOTING_APP_NAME = "aragon-voting"; // Lido app names - string constant private LIDO_APP_NAME = "lido"; - string constant private NODE_OPERATORS_REGISTRY_APP_NAME = "node-operators-registry"; - string constant private ORACLE_APP_NAME = "oracle"; + string private constant LIDO_APP_NAME = "lido"; + string private constant NODE_OPERATORS_REGISTRY_APP_NAME = "node-operators-registry"; + string private constant ORACLE_APP_NAME = "oracle"; // DAO config constants - bool constant private TOKEN_TRANSFERABLE = true; - uint8 constant private TOKEN_DECIMALS = uint8(18); - uint64 constant private DEFAULT_FINANCE_PERIOD = uint64(30 days); - uint256 constant private TOKEN_MAX_PER_ACCOUNT = 0; + bool private constant TOKEN_TRANSFERABLE = true; + uint8 private constant TOKEN_DECIMALS = uint8(18); + uint64 private constant DEFAULT_FINANCE_PERIOD = uint64(30 days); + uint256 private constant TOKEN_MAX_PER_ACCOUNT = 0; struct APMRepos { Repo lido; @@ -134,7 +134,7 @@ contract LidoTemplate is IsContract { _; } - function setOwner(address _newOwner) onlyOwner external { + function setOwner(address _newOwner) external onlyOwner { owner = _newOwner; } @@ -145,9 +145,7 @@ contract LidoTemplate is IsContract { MiniMeTokenFactory _miniMeFactory, IFIFSResolvingRegistrar _aragonID, APMRegistryFactory _apmRegistryFactory - ) - public - { + ) public { require(_owner != address(0), ERROR_ZERO_OWNER); require(isContract(address(_daoFactory)), ERROR_DAO_FACTORY_NOT_CONTRACT); require(isContract(address(_ens)), ERROR_ENS_NOT_CONTRACT); @@ -163,25 +161,22 @@ contract LidoTemplate is IsContract { apmRegistryFactory = _apmRegistryFactory; } - function getConfig() external view returns ( - address _owner, - address _daoFactory, - address _ens, - address _miniMeFactory, - address _aragonID, - address _apmRegistryFactory - ) { - return ( - owner, - daoFactory, - ens, - miniMeFactory, - aragonID, - apmRegistryFactory - ); + function getConfig() + external + view + returns ( + address _owner, + address _daoFactory, + address _ens, + address _miniMeFactory, + address _aragonID, + address _apmRegistryFactory + ) + { + return (owner, daoFactory, ens, miniMeFactory, aragonID, apmRegistryFactory); } - function deployLidoAPM(bytes32 _tld, bytes32 _label) onlyOwner external { + function deployLidoAPM(bytes32 _tld, bytes32 _label) external onlyOwner { require(deployState.lidoRegistry == address(0), ERROR_REGISTRY_ALREADY_DEPLOYED); bytes32 node = keccak256(abi.encodePacked(_tld, _label)); @@ -208,7 +203,7 @@ contract LidoTemplate is IsContract { /** * @dev An escape hatch function to reclaim the domain if APM fails to deploy. */ - function cancelAndTransferDomain(bytes32 node, address _to) onlyOwner external { + function cancelAndTransferDomain(bytes32 node, address _to) external onlyOwner { require(ens.owner(node) == address(this), ERROR_ENS_NODE_NOT_OWNED_BY_TEMPLATE); ens.setOwner(node, _to); } @@ -221,10 +216,7 @@ contract LidoTemplate is IsContract { bytes _nodeOperatorsRegistryContentURI, address _oracleImplAddress, bytes _oracleContentURI - ) - onlyOwner - external - { + ) external onlyOwner { require(deployState.lidoRegistry != address(0), ERROR_REGISTRY_NOT_DEPLOYED); APMRegistry lidoRegistry = deployState.lidoRegistry; @@ -301,11 +293,8 @@ contract LidoTemplate is IsContract { string _tokenSymbol, uint64[4] _votingSettings, IDepositContract _beaconDepositContract, - uint32[4] _beaconSpec - ) - onlyOwner - external - { + uint32[4] + ) external onlyOwner { DeployState memory state = deployState; require(state.lidoRegistry != address(0), ERROR_REGISTRY_NOT_DEPLOYED); @@ -316,12 +305,7 @@ contract LidoTemplate is IsContract { state.agent = _installAgentApp(state.lidoRegistryEnsNode, state.dao); - state.finance = _installFinanceApp( - state.lidoRegistryEnsNode, - state.dao, - state.agent, - DEFAULT_FINANCE_PERIOD - ); + state.finance = _installFinanceApp(state.lidoRegistryEnsNode, state.dao, state.agent, DEFAULT_FINANCE_PERIOD); state.tokenManager = _installTokenManagerApp( state.lidoRegistryEnsNode, @@ -343,34 +327,32 @@ contract LidoTemplate is IsContract { bytes memory noInit = new bytes(0); - state.lido = Lido(_installNonDefaultApp( - state.dao, - _getAppId(LIDO_APP_NAME, state.lidoRegistryEnsNode), - noInit - )); + state.lido = Lido( + _installNonDefaultApp(state.dao, _getAppId(LIDO_APP_NAME, state.lidoRegistryEnsNode), noInit) + ); - state.operators = NodeOperatorsRegistry(_installNonDefaultApp( - state.dao, - _getAppId(NODE_OPERATORS_REGISTRY_APP_NAME, state.lidoRegistryEnsNode), - noInit - )); + state.operators = NodeOperatorsRegistry( + _installNonDefaultApp( + state.dao, + _getAppId(NODE_OPERATORS_REGISTRY_APP_NAME, state.lidoRegistryEnsNode), + noInit + ) + ); - state.oracle = LidoOracle(_installNonDefaultApp( - state.dao, - _getAppId(ORACLE_APP_NAME, state.lidoRegistryEnsNode), - noInit - )); - - state.oracle.initialize( - state.lido, - _beaconSpec[0], // epochsPerFrame - _beaconSpec[1], // slotsPerEpoch - _beaconSpec[2], // secondsPerSlot - _beaconSpec[3], // genesisTime - 100000, - 50000 + state.oracle = LidoOracle( + _installNonDefaultApp(state.dao, _getAppId(ORACLE_APP_NAME, state.lidoRegistryEnsNode), noInit) ); + // state.oracle.initialize( + // state.lido, + // _beaconSpec[0], // epochsPerFrame + // _beaconSpec[1], // slotsPerEpoch + // _beaconSpec[2], // secondsPerSlot + // _beaconSpec[3], // genesisTime + // 100000, + // 50000 + // ); + state.operators.initialize(state.lido); state.lido.initialize( @@ -378,7 +360,8 @@ contract LidoTemplate is IsContract { state.oracle, state.operators, state.agent, // treasury - state.agent // insurance fund + address(0), // execution layer rewards vault + address(0) // withdrawal queue ); // used for issuing vested tokens in the next step @@ -397,10 +380,7 @@ contract LidoTemplate is IsContract { uint64 _vestingEnd, bool _vestingRevokable, uint256 _expectedFinalTotalSupply - ) - onlyOwner - external - { + ) external onlyOwner { require(_holders.length > 0, ERROR_EMPTY_HOLDERS); require(_holders.length == _amounts.length, ERROR_BAD_AMOUNTS_LEN); @@ -426,14 +406,12 @@ contract LidoTemplate is IsContract { string _daoName, uint16 _totalFeeBP, uint16 _treasuryFeeBP, - uint16 _insuranceFeeBP, uint16 _operatorsFeeBP, uint256 _unvestedTokensAmount, address _elRewardsVault, uint16 _elRewardsWithdrawalLimit ) - onlyOwner - external + external onlyOwner { DeployState memory state = deployState; APMRepos memory repos = apmRepos; @@ -445,14 +423,19 @@ contract LidoTemplate is IsContract { bytes32 LIDO_MANAGE_FEE = state.lido.MANAGE_FEE(); _createPermissionForTemplate(state.acl, state.lido, LIDO_MANAGE_FEE); state.lido.setFee(_totalFeeBP); - state.lido.setFeeDistribution(_treasuryFeeBP, _insuranceFeeBP, _operatorsFeeBP); + state.lido.setFeeDistribution(_treasuryFeeBP, _operatorsFeeBP); _removePermissionFromTemplate(state.acl, state.lido, LIDO_MANAGE_FEE); // Set Execution Layer rewards parameters on Lido contract - bytes32 LIDO_SET_EL_REWARDS_VAULT = state.lido.SET_EL_REWARDS_VAULT_ROLE(); - _createPermissionForTemplate(state.acl, state.lido, LIDO_SET_EL_REWARDS_VAULT); - state.lido.setELRewardsVault(_elRewardsVault); - _removePermissionFromTemplate(state.acl, state.lido, LIDO_SET_EL_REWARDS_VAULT); + bytes32 MANAGE_PROTOCOL_CONTRACTS_ROLE = state.lido.MANAGE_PROTOCOL_CONTRACTS_ROLE(); + _createPermissionForTemplate(state.acl, state.lido, MANAGE_PROTOCOL_CONTRACTS_ROLE); + state.lido.setProtocolContracts( + state.lido.getOracle(), + state.lido.getTreasury(), + _elRewardsVault, + address(0) + ); + _removePermissionFromTemplate(state.acl, state.lido, MANAGE_PROTOCOL_CONTRACTS_ROLE); bytes32 LIDO_SET_EL_REWARDS_WITHDRAWAL_LIMIT = state.lido.SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE(); _createPermissionForTemplate(state.acl, state.lido, LIDO_SET_EL_REWARDS_WITHDRAWAL_LIMIT); @@ -478,11 +461,11 @@ contract LidoTemplate is IsContract { /* DAO AND APPS */ /** - * @dev Create a DAO using the DAO Factory and grant the template root permissions so it has full - * control during setup. Once the DAO setup has finished, it is recommended to call the - * `_transferRootPermissionsFromTemplateAndFinalizeDAO()` helper to transfer the root - * permissions to the end entity in control of the organization. - */ + * @dev Create a DAO using the DAO Factory and grant the template root permissions so it has full + * control during setup. Once the DAO setup has finished, it is recommended to call the + * `_transferRootPermissionsFromTemplateAndFinalizeDAO()` helper to transfer the root + * permissions to the end entity in control of the organization. + */ function _createDAO() private returns (Kernel dao, ACL acl) { dao = daoFactory.newDAO(this); acl = ACL(dao.acl()); @@ -502,9 +485,7 @@ contract LidoTemplate is IsContract { Kernel _dao, Vault _vault, uint64 _periodDuration - ) - private returns (Finance) - { + ) private returns (Finance) { bytes32 appId = _getAppId(ARAGON_FINANCE_APP_NAME, _lidoRegistryEnsNode); bytes memory initializeData = abi.encodeWithSelector(Finance(0).initialize.selector, _vault, _periodDuration); return Finance(_installNonDefaultApp(_dao, appId, initializeData)); @@ -516,9 +497,7 @@ contract LidoTemplate is IsContract { MiniMeToken _token, bool _transferable, uint256 _maxAccountTokens - ) - private returns (TokenManager) - { + ) private returns (TokenManager) { bytes32 appId = _getAppId(ARAGON_TOKEN_MANAGER_APP_NAME, _lidoRegistryEnsNode); TokenManager tokenManager = TokenManager(_installNonDefaultApp(_dao, appId, new bytes(0))); _token.changeController(tokenManager); @@ -538,15 +517,31 @@ contract LidoTemplate is IsContract { private returns (Voting) { bytes32 appId = _getAppId(ARAGON_VOTING_APP_NAME, _lidoRegistryEnsNode); - bytes memory initializeData = abi.encodeWithSelector(Voting(0).initialize.selector, _token, _support, _acceptance, _duration, _objectionPhaseDuration); + bytes memory initializeData = abi.encodeWithSelector( + Voting(0).initialize.selector, + _token, + _support, + _acceptance, + _duration, + _objectionPhaseDuration + ); return Voting(_installNonDefaultApp(_dao, appId, initializeData)); } - function _installNonDefaultApp(Kernel _dao, bytes32 _appId, bytes memory _initializeData) internal returns (address) { + function _installNonDefaultApp( + Kernel _dao, + bytes32 _appId, + bytes memory _initializeData + ) internal returns (address) { return _installApp(_dao, _appId, _initializeData, false); } - function _installApp(Kernel _dao, bytes32 _appId, bytes memory _initializeData, bool _setDefault) internal returns (address) { + function _installApp( + Kernel _dao, + bytes32 _appId, + bytes memory _initializeData, + bool _setDefault + ) internal returns (address) { address latestBaseAppAddress = _apmResolveLatest(_appId).contractAddress; address instance = address(_dao.newAppInstance(_appId, latestBaseAppAddress, _initializeData, _setDefault)); emit TmplAppInstalled(instance, _appId); @@ -555,7 +550,11 @@ contract LidoTemplate is IsContract { /* TOKEN */ - function _createToken(string memory _name, string memory _symbol, uint8 _decimals) internal returns (MiniMeToken) { + function _createToken( + string memory _name, + string memory _symbol, + uint8 _decimals + ) internal returns (MiniMeToken) { MiniMeToken token = miniMeFactory.createCloneToken(MiniMeToken(address(0)), 0, _name, _decimals, _symbol, true); return token; } @@ -585,7 +584,14 @@ contract LidoTemplate is IsContract { require(_token.totalSupply() == _expectedFinalTotalSupply, ERROR_UNEXPECTED_TOTAL_SUPPLY); for (i = 0; i < _holders.length; ++i) { - _tokenManager.assignVested(_holders[i], _amounts[i], _vestingStart, _vestingCliff, _vestingEnd, _vestingRevokable); + _tokenManager.assignVested( + _holders[i], + _amounts[i], + _vestingStart, + _vestingCliff, + _vestingEnd, + _vestingRevokable + ); } return totalAmount; @@ -620,32 +626,33 @@ contract LidoTemplate is IsContract { // APM repos // using loops to save contract size - Repo[10] memory repoAddrs; - - repoAddrs[0] = _repos.lido; - repoAddrs[1] = _repos.oracle; - repoAddrs[2] = _repos.nodeOperatorsRegistry; - repoAddrs[3] = _repos.aragonAgent; - repoAddrs[4] = _repos.aragonFinance; - repoAddrs[5] = _repos.aragonTokenManager; - repoAddrs[6] = _repos.aragonVoting; - repoAddrs[7] = _resolveRepo(_getAppId(APM_APP_NAME, _state.lidoRegistryEnsNode)); - repoAddrs[8] = _resolveRepo(_getAppId(APM_REPO_APP_NAME, _state.lidoRegistryEnsNode)); - repoAddrs[9] = _resolveRepo(_getAppId(APM_ENSSUB_APP_NAME, _state.lidoRegistryEnsNode)); - - for (uint256 i = 0; i < repoAddrs.length; ++i) { - _transferPermissionFromTemplate(apmACL, repoAddrs[i], voting, REPO_CREATE_VERSION_ROLE); + Repo[10] memory repoAddresses; + + repoAddresses[0] = _repos.lido; + repoAddresses[1] = _repos.oracle; + repoAddresses[2] = _repos.nodeOperatorsRegistry; + repoAddresses[3] = _repos.aragonAgent; + repoAddresses[4] = _repos.aragonFinance; + repoAddresses[5] = _repos.aragonTokenManager; + repoAddresses[6] = _repos.aragonVoting; + repoAddresses[7] = _resolveRepo(_getAppId(APM_APP_NAME, _state.lidoRegistryEnsNode)); + repoAddresses[8] = _resolveRepo(_getAppId(APM_REPO_APP_NAME, _state.lidoRegistryEnsNode)); + repoAddresses[9] = _resolveRepo(_getAppId(APM_ENSSUB_APP_NAME, _state.lidoRegistryEnsNode)); + + for (uint256 i = 0; i < repoAddresses.length; ++i) { + _transferPermissionFromTemplate(apmACL, repoAddresses[i], voting, REPO_CREATE_VERSION_ROLE); } // using loops to save contract size - bytes32[10] memory perms; + bytes32[8] memory perms; // Oracle - perms[0] = _state.oracle.MANAGE_MEMBERS(); - perms[1] = _state.oracle.MANAGE_QUORUM(); - perms[2] = _state.oracle.SET_BEACON_SPEC(); - perms[3] = _state.oracle.SET_REPORT_BOUNDARIES(); - perms[4] = _state.oracle.SET_BEACON_REPORT_RECEIVER(); + // TODO + // perms[0] = _state.oracle.MANAGE_MEMBERS(); + // perms[1] = _state.oracle.MANAGE_QUORUM(); + // perms[2] = _state.oracle.SET_BEACON_SPEC(); + // perms[3] = _state.oracle.SET_REPORT_BOUNDARIES(); + // perms[4] = _state.oracle.SET_BEACON_REPORT_RECEIVER(); for (i = 0; i < 5; ++i) { _createPermissionForVoting(acl, _state.oracle, perms[i], voting); @@ -673,10 +680,8 @@ contract LidoTemplate is IsContract { perms[5] = _state.lido.RESUME_ROLE(); perms[6] = _state.lido.STAKING_PAUSE_ROLE(); perms[7] = _state.lido.STAKING_CONTROL_ROLE(); - perms[8] = _state.lido.SET_EL_REWARDS_VAULT_ROLE(); - perms[9] = _state.lido.SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE(); - for (i = 0; i < 10; ++i) { + for (i = 0; i < 8; ++i) { _createPermissionForVoting(acl, _state.lido, perms[i], voting); } } @@ -686,20 +691,38 @@ contract LidoTemplate is IsContract { _createPermissionForTemplate(_acl, _tokenManager, _tokenManager.ASSIGN_ROLE()); } - function _createPermissionForVoting(ACL _acl, address _app, bytes32 perm, address _voting) internal { - _acl.createPermission(_voting, _app, perm, _voting); + function _createPermissionForVoting( + ACL _acl, + address _app, + bytes32 perm, + address _voting + ) internal { + _acl.createPermission(_voting, _app, perm, _voting); } - function _createAgentPermissions(ACL _acl, Agent _agent, address _voting) internal { + function _createAgentPermissions( + ACL _acl, + Agent _agent, + address _voting + ) internal { _createPermissionForVoting(_acl, _agent, _agent.EXECUTE_ROLE(), _voting); _createPermissionForVoting(_acl, _agent, _agent.RUN_SCRIPT_ROLE(), _voting); } - function _createVaultPermissions(ACL _acl, Vault _vault, address _finance, address _voting) internal { + function _createVaultPermissions( + ACL _acl, + Vault _vault, + address _finance, + address _voting + ) internal { _acl.createPermission(_finance, _vault, _vault.TRANSFER_ROLE(), _voting); } - function _createFinancePermissions(ACL _acl, Finance _finance, address _voting) internal { + function _createFinancePermissions( + ACL _acl, + Finance _finance, + address _voting + ) internal { _createPermissionForVoting(_acl, _finance, _finance.EXECUTE_PAYMENTS_ROLE(), _voting); _createPermissionForVoting(_acl, _finance, _finance.MANAGE_PAYMENTS_ROLE(), _voting); _createPermissionForVoting(_acl, _finance, _finance.CREATE_PAYMENTS_ROLE(), _voting); @@ -711,23 +734,39 @@ contract LidoTemplate is IsContract { _createPermissionForVoting(_acl, registry, registry.REGISTRY_ADD_EXECUTOR_ROLE(), _voting); } - function _createVotingPermissions(ACL _acl, Voting _voting, address _tokenManager) internal { + function _createVotingPermissions( + ACL _acl, + Voting _voting, + address _tokenManager + ) internal { _createPermissionForVoting(_acl, _voting, _voting.MODIFY_QUORUM_ROLE(), _voting); _createPermissionForVoting(_acl, _voting, _voting.MODIFY_SUPPORT_ROLE(), _voting); _acl.createPermission(_tokenManager, _voting, _voting.CREATE_VOTES_ROLE(), _voting); } - function _configureTokenManagerPermissions(ACL _acl, TokenManager _tokenManager, address _voting) internal { + function _configureTokenManagerPermissions( + ACL _acl, + TokenManager _tokenManager, + address _voting + ) internal { _removePermissionFromTemplate(_acl, _tokenManager, _tokenManager.ISSUE_ROLE()); _removePermissionFromTemplate(_acl, _tokenManager, _tokenManager.ASSIGN_ROLE()); _createPermissionForVoting(_acl, _tokenManager, _tokenManager.ASSIGN_ROLE(), _voting); } - function _createPermissionForTemplate(ACL _acl, address _app, bytes32 _permission) private { + function _createPermissionForTemplate( + ACL _acl, + address _app, + bytes32 _permission + ) private { _acl.createPermission(address(this), _app, _permission, address(this)); } - function _removePermissionFromTemplate(ACL _acl, address _app, bytes32 _permission) private { + function _removePermissionFromTemplate( + ACL _acl, + address _app, + bytes32 _permission + ) private { _acl.revokePermission(address(this), _app, _permission); _acl.removePermissionManager(_app, _permission); } @@ -738,11 +777,22 @@ contract LidoTemplate is IsContract { _transferPermissionFromTemplate(_acl, _acl, _voting, _acl.CREATE_PERMISSIONS_ROLE(), _voting); } - function _transferPermissionFromTemplate(ACL _acl, address _app, address _to, bytes32 _permission) private { + function _transferPermissionFromTemplate( + ACL _acl, + address _app, + address _to, + bytes32 _permission + ) private { _transferPermissionFromTemplate(_acl, _app, _to, _permission, _to); } - function _transferPermissionFromTemplate(ACL _acl, address _app, address _to, bytes32 _permission, address _manager) private { + function _transferPermissionFromTemplate( + ACL _acl, + address _app, + address _to, + bytes32 _permission, + address _manager + ) private { _acl.grantPermission(_to, _app, _permission); _acl.revokePermission(address(this), _app, _permission); _acl.setPermissionManager(_manager, _app, _permission); diff --git a/contracts/0.4.24/test_helpers/LidoMock.sol b/contracts/0.4.24/test_helpers/LidoMock.sol index 1b452b68a..5c683f654 100644 --- a/contracts/0.4.24/test_helpers/LidoMock.sol +++ b/contracts/0.4.24/test_helpers/LidoMock.sol @@ -7,60 +7,43 @@ pragma solidity 0.4.24; import "../Lido.sol"; import "./VaultMock.sol"; - /** - * @dev Only for testing purposes! Lido version with some functions exposed. - */ + * @dev Only for testing purposes! Lido version with some functions exposed. + */ contract LidoMock is Lido { - function initialize( - IDepositContract _depositContract, - address _oracle, - INodeOperatorsRegistry _operators - ) - public - { - super.initialize( - _depositContract, - _oracle, - _operators, - new VaultMock(), - new VaultMock() - ); - } - /** - * @dev For use in tests to make protocol operational after deployment - */ - function resumeProtocolAndStaking() { - _resume(); - _resumeStaking(); + * @dev For use in tests to make protocol operational after deployment + */ + function resumeProtocolAndStaking() public { + _resume(); + _resumeStaking(); } /** - * @dev Gets unaccounted (excess) Ether on this contract balance - */ + * @dev Gets unaccounted (excess) Ether on this contract balance + */ function getUnaccountedEther() public view returns (uint256) { return _getUnaccountedEther(); } /** - * @dev Padding memory array with zeroes up to 64 bytes on the right - * @param _b Memory array of size 32 .. 64 - */ + * @dev Padding memory array with zeroes up to 64 bytes on the right + * @param _b Memory array of size 32 .. 64 + */ function pad64(bytes memory _b) public pure returns (bytes memory) { return _pad64(_b); } /** - * @dev Converting value to little endian bytes and padding up to 32 bytes on the right - * @param _value Number less than `2**64` for compatibility reasons - */ + * @dev Converting value to little endian bytes and padding up to 32 bytes on the right + * @param _value Number less than `2**64` for compatibility reasons + */ function toLittleEndian64(uint256 _value) public pure returns (uint256 result) { return _toLittleEndian64(_value); } /** - * @dev Only for testing recovery vault - */ + * @dev Only for testing recovery vault + */ function makeUnaccountedEther() public payable {} } diff --git a/contracts/0.4.24/test_helpers/LidoMockForOracle.sol b/contracts/0.4.24/test_helpers/LidoMockForOracle.sol index c9b1299e9..c9f4d46d2 100644 --- a/contracts/0.4.24/test_helpers/LidoMockForOracle.sol +++ b/contracts/0.4.24/test_helpers/LidoMockForOracle.sol @@ -15,7 +15,7 @@ contract LidoMockForOracle { return totalPooledEther; } - function handleOracleReport(uint256 /*_beaconValidators*/, uint256 _beaconBalance) external { + function handleOracleReport(uint256, uint256 _beaconBalance, uint256, uint256, uint256[], uint256[]) external { totalPooledEther = _beaconBalance; } diff --git a/contracts/0.4.24/test_helpers/LidoOracleMock.sol b/contracts/0.4.24/test_helpers/LidoOracleMock.sol index 289040272..11ca14be5 100644 --- a/contracts/0.4.24/test_helpers/LidoOracleMock.sol +++ b/contracts/0.4.24/test_helpers/LidoOracleMock.sol @@ -6,10 +6,9 @@ pragma solidity 0.4.24; import "../oracle/LidoOracle.sol"; - /** - * @dev Only for testing purposes! LidoOracle version with some functions exposed. - */ + * @dev Only for testing purposes! LidoOracle version with some functions exposed. + */ contract LidoOracleMock is LidoOracle { uint256 private time; @@ -30,6 +29,6 @@ contract LidoOracleMock is LidoOracle { } function setVersion(uint256 _version) external { - CONTRACT_VERSION_POSITION.setStorageUint256(_version); + CONTRACT_VERSION_POSITION.setStorageUint256(_version); } } diff --git a/contracts/0.4.24/test_helpers/LidoPushableMock.sol b/contracts/0.4.24/test_helpers/LidoPushableMock.sol index 704b3d346..ab809c00e 100644 --- a/contracts/0.4.24/test_helpers/LidoPushableMock.sol +++ b/contracts/0.4.24/test_helpers/LidoPushableMock.sol @@ -7,12 +7,10 @@ pragma solidity 0.4.24; import "../Lido.sol"; import "./VaultMock.sol"; - /** * @dev Mock for unit-testing handleOracleReport and how reward get calculated */ contract LidoPushableMock is Lido { - uint256 public totalRewards; bool public distributeFeeCalled; @@ -20,18 +18,16 @@ contract LidoPushableMock is Lido { IDepositContract depositContract, address _oracle, INodeOperatorsRegistry _operators - ) - public - { - super.initialize( - depositContract, - _oracle, - _operators, - new VaultMock(), - new VaultMock() - ); + ) public { + super.initialize(depositContract, _oracle, _operators, new VaultMock(), address(0), address(0)); + + _resume(); + } + function initialize(address _oracle) public onlyInit { + _setProtocolContracts(_oracle, _oracle, address(0), address(0)); _resume(); + initialized(); } function setDepositedValidators(uint256 _depositedValidators) public { @@ -51,10 +47,8 @@ contract LidoPushableMock is Lido { BEACON_VALIDATORS_POSITION.setStorageUint256(_beaconValidators); } - function initialize(address _oracle) public onlyInit { - _setProtocolContracts(_oracle, _oracle, _oracle); - _resume(); - initialized(); + function setTotalShares(uint256 _totalShares) public { + TOTAL_SHARES_POSITION.setStorageUint256(_totalShares); } function resetDistributeFee() public { @@ -62,7 +56,7 @@ contract LidoPushableMock is Lido { distributeFeeCalled = false; } - function distributeFee(uint256 _totalRewards) internal { + function _distributeFee(uint256 _totalRewards) internal { totalRewards = _totalRewards; distributeFeeCalled = true; } diff --git a/contracts/0.4.24/test_helpers/OracleMock.sol b/contracts/0.4.24/test_helpers/OracleMock.sol index 4293d430f..8e23e6306 100644 --- a/contracts/0.4.24/test_helpers/OracleMock.sol +++ b/contracts/0.4.24/test_helpers/OracleMock.sol @@ -1,28 +1,32 @@ -// SPDX-FileCopyrightText: 2020 Lido +// SPDX-FileCopyrightText: 2023 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.4.24; -import "../interfaces/ILido.sol"; - +import "../Lido.sol"; /** - * @dev This is a mock. Don't use in production. - */ + * @dev This is a mock. Don't use in production. + */ contract OracleMock { - ILido private pool; + Lido private pool; address private beaconReceiver; function setPool(address _pool) external { - pool = ILido(_pool); + pool = Lido(_pool); } - function reportBeacon(uint256 _epochId, uint128 _beaconValidators, uint128 _beaconBalance) external { - pool.handleOracleReport(_beaconValidators, _beaconBalance); + function reportBeacon( + uint256, + uint128 _beaconValidators, + uint128 _beaconBalance + ) external { + uint256[] memory empty = new uint256[](0); + pool.handleOracleReport(_beaconValidators, _beaconBalance, 0, 0, empty, empty); } - function setBeaconReportReceiver(address _receiver) { + function setBeaconReportReceiver(address _receiver) public { beaconReceiver = _receiver; } diff --git a/contracts/0.8.9/CommitteeQuorum.sol b/contracts/0.8.9/CommitteeQuorum.sol new file mode 100644 index 000000000..a42c86fd5 --- /dev/null +++ b/contracts/0.8.9/CommitteeQuorum.sol @@ -0,0 +1,219 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +import "./lib/AragonUnstructuredStorage.sol"; + + +/** + * @title Implementation of oracle committee consensus reporting + */ +contract CommitteeQuorum { + using UnstructuredStorage for bytes32; + + event MemberAdded(address indexed member); + event MemberRemoved(address indexed member); + event QuorumChanged(uint256 quorum); + + /// Maximum number of oracle committee members + uint256 public constant MAX_MEMBERS = 256; + + /// Number of exactly the same reports needed to finalize the epoch + bytes32 internal constant QUORUM_POSITION = keccak256("lido.CommitteeQuorum.quorum"); + + uint256 internal constant MEMBER_NOT_FOUND = type(uint256).max; + + /// The bitmask of the oracle members that pushed their reports + bytes32 internal constant REPORTS_BITMASK_POSITION = keccak256("lido.CommitteeQuorum.reportsBitmask"); + + ///! STRUCTURED STORAGE OF THE CONTRACT + ///! SLOT 0: address[] members + ///! SLOT 1: bytes[] distinctReports + ///! SLOT 2: bytes[] distinctReportHashes + ///! SLOT 3: bytes32[] distinctReportCounters + + address[] internal members; + bytes[] internal distinctReports; + bytes32[] internal distinctReportHashes; + uint16[] internal distinctReportCounters; + + /** + * @notice Return the current reporting bitmap, representing oracles who have already pushed + * their version of report during the expected epoch + * @dev Every oracle bit corresponds to the index of the oracle in the current members list + */ + function getCurrentOraclesReportStatus() external view returns (uint256) { + return REPORTS_BITMASK_POSITION.getStorageUint256(); + } + + /** + * @notice Return number of distinct reported + */ + function getDistinctMemberReportsCount() external view returns (uint256) { + return distinctReports.length; + } + + /** + * @notice Return the current oracle member committee list + */ + function getOracleMembers() external view returns (address[] memory) { + return members; + } + + /** + * @notice Return the number of exactly the same reports needed to finalize the epoch + */ + function getQuorum() public view returns (uint256) { + return QUORUM_POSITION.getStorageUint256(); + } + + + function _handleMemberReport(address _reporter, bytes memory _report) + internal returns (bool isQuorumReached) + { + // make sure the oracle is from members list and has not yet voted + uint256 index = _getMemberId(_reporter); + if (index == MEMBER_NOT_FOUND) { revert NotMemberReported(); } + + uint256 bitMask = REPORTS_BITMASK_POSITION.getStorageUint256(); + uint256 mask = 1 << index; + if (bitMask & mask != 0) { revert MemberAlreadyReported(); } + REPORTS_BITMASK_POSITION.setStorageUint256(bitMask | mask); + + bytes32 reportHash = keccak256(_report); + isQuorumReached = false; + + uint256 i = 0; + bool isFound = false; + while (i < distinctReports.length && distinctReportHashes[i] != reportHash) { + ++i; + } + while (i < distinctReports.length) { + if (distinctReportHashes[i] == reportHash) { + isFound = true; + break; + } + ++i; + } + + if (isFound && i < distinctReports.length) { + distinctReportCounters[i] += 1; + } else { + distinctReports.push(_report); + distinctReportHashes.push(reportHash); + distinctReportCounters.push(1); + } + + // Check is quorum reached + if (distinctReportCounters[i] >= QUORUM_POSITION.getStorageUint256()) { + isQuorumReached = true; + } + } + + + function _getQuorumReport(uint256 _quorum) internal view + returns (bool isQuorumReached, uint256 reportIndex) + { + // check most frequent cases first: all reports are the same or no reports yet + if (distinctReports.length == 0) { + return (false, 0); + } else if (distinctReports.length == 1) { + return (distinctReportCounters[0] >= _quorum, 0); + } + + // If there are multiple reports with the same count above quorum we consider + // committee quorum not reached + reportIndex = 0; + bool areMultipleMaxReports = false; + uint16 maxCount = 0; + uint16 currentCount = 0; + for (uint256 i = 0; i < distinctReports.length; ++i) { + currentCount = distinctReportCounters[i]; + if (currentCount >= maxCount) { + if (currentCount == maxCount) { + areMultipleMaxReports = true; + } else { + reportIndex = i; + maxCount = currentCount; + areMultipleMaxReports = false; + } + } + } + isQuorumReached = maxCount >= _quorum && !areMultipleMaxReports; + } + + function _addOracleMember(address _member) internal { + if (_member == address(0)) { revert ZeroMemberAddress(); } + if (MEMBER_NOT_FOUND != _getMemberId(_member)) { revert MemberExists(); } + if (members.length >= MAX_MEMBERS) { revert TooManyMembers(); } + + members.push(_member); + + emit MemberAdded(_member); + } + + + function _removeOracleMember(address _member) internal { + uint256 index = _getMemberId(_member); + if (index == MEMBER_NOT_FOUND) { revert MemberNotFound(); } + + uint256 last = members.length - 1; + if (index != last) { + members[index] = members[last]; + } + members.pop(); + emit MemberRemoved(_member); + + // delete the data for the last epoch, let remained oracles report it again + REPORTS_BITMASK_POSITION.setStorageUint256(0); + delete distinctReports; + } + + function _setQuorum(uint256 _quorum) internal { + QUORUM_POSITION.setStorageUint256(_quorum); + emit QuorumChanged(_quorum); + } + + function _updateQuorum(uint256 _quorum) internal + returns (bool isQuorumReached, uint256 reportIndex) + { + if (0 == _quorum) { revert QuorumWontBeMade(); } + uint256 oldQuorum = QUORUM_POSITION.getStorageUint256(); + + _setQuorum(_quorum); + + if (_quorum < oldQuorum) { + return _getQuorumReport(_quorum); + } + } + + /** + * @notice Return `_member` index in the members list or revert with MemberNotFound error + */ + function _getMemberId(address _member) internal view returns (uint256) { + uint256 length = members.length; + for (uint256 i = 0; i < length; ++i) { + if (members[i] == _member) { + return i; + } + } + return MEMBER_NOT_FOUND; + } + + function _clearReporting() internal { + REPORTS_BITMASK_POSITION.setStorageUint256(0); + delete distinctReports; + delete distinctReportHashes; + delete distinctReportCounters; + } + + + error NotMemberReported(); + error ZeroMemberAddress(); + error MemberNotFound(); + error TooManyMembers(); + error MemberExists(); + error MemberAlreadyReported(); + error QuorumWontBeMade(); + +} diff --git a/contracts/0.8.9/LidoOracleNew.sol b/contracts/0.8.9/LidoOracleNew.sol new file mode 100644 index 000000000..904da7bcb --- /dev/null +++ b/contracts/0.8.9/LidoOracleNew.sol @@ -0,0 +1,583 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.9; + +import { ERC165Checker } from "@openzeppelin/contracts-v4.4/utils/introspection/ERC165Checker.sol"; +import { AccessControlEnumerable } from "@openzeppelin/contracts-v4.4/access/AccessControlEnumerable.sol"; + +import "./CommitteeQuorum.sol"; +import "./ReportEpochChecker.sol"; +import "./interfaces/IBeaconReportReceiver.sol"; + +interface INodeOperatorsRegistry { + /** + * @notice Report `_stoppedIncrement` more stopped validators of the node operator #`_id` + */ + function reportStoppedValidators(uint256 _id, uint64 _stoppedIncrement) external; +} + +/** + * @notice Part of Lido interface required for `LidoOracleNew` to work + */ +interface ILido { + function getOperators() external returns (INodeOperatorsRegistry); + + function totalSupply() external returns (uint256); + + function getTotalShares() external returns (uint256); + + function handleOracleReport(uint256, uint256, uint256, uint256, uint256[] calldata, uint256[] calldata) external; +} + +/** + * @title Implementation of an ETH 2.0 -> ETH oracle + * + * The goal of the oracle is to inform other parts of the system about balances controlled by the + * DAO on the ETH 2.0 side. The balances can go up because of reward accumulation and can go down + * because of slashing. + * + * The timeline is divided into consecutive frames. Every oracle member may push its report once + * per frame. When the equal reports reach the configurable 'quorum' value, this frame is + * considered finalized and the resulting report is pushed to Lido. + * + * Not all frames may come to a quorum. Oracles may report only to the first epoch of the frame and + * only if no quorum is reached for this epoch yet. + */ +contract LidoOracleNew is CommitteeQuorum, AccessControlEnumerable, ReportEpochChecker { + using ERC165Checker for address; + using UnstructuredStorage for bytes32; + + event AllowedBeaconBalanceAnnualRelativeIncreaseSet(uint256 value); + event AllowedBeaconBalanceRelativeDecreaseSet(uint256 value); + event BeaconReportReceiverSet(address callback); + + event CommitteeMemberReported( + uint256 epochId, + uint256 beaconBalance, + uint256 beaconValidators, + address caller, + uint256 withdrawalVaultBalance, + uint256[] requestIdToFinalizeUpTo, + uint256[] finalizationShareRates + ); + + event ConsensusReached( + uint256 epochId, + uint256 beaconBalance, + uint256 beaconValidators, + uint256 _withdrawalVaultBalance, + uint256[] requestIdToFinalizeUpTo, + uint256[] finalizationShareRates + ); + + event PostTotalShares( + uint256 postTotalPooledEther, + uint256 preTotalPooledEther, + uint256 timeElapsed, + uint256 totalShares + ); + + event ContractVersionSet(uint256 version); + + + struct MemberReport { + // Consensus info + uint256 epochId; + // CL values + uint256 beaconValidators; + uint64 beaconBalanceGwei; + address[] stakingModules; + uint256[] nodeOperatorsWithExitedValidators; + uint64[] exitedValidatorsNumbers; + // EL values + uint256 withdrawalVaultBalance; + // decision + uint256 newDepositBufferWithdrawalsReserve; + uint256[] requestIdToFinalizeUpTo; + uint256[] finalizationShareRates; + } + + /// ACL + bytes32 constant public MANAGE_MEMBERS_ROLE = keccak256("MANAGE_MEMBERS_ROLE"); + bytes32 constant public MANAGE_QUORUM_ROLE = keccak256("MANAGE_QUORUM_ROLE"); + bytes32 constant public SET_BEACON_SPEC_ROLE = keccak256("SET_BEACON_SPEC_ROLE"); + bytes32 constant public SET_REPORT_BOUNDARIES_ROLE = keccak256("SET_REPORT_BOUNDARIES_ROLE"); + bytes32 constant public SET_BEACON_REPORT_RECEIVER_ROLE = keccak256("SET_BEACON_REPORT_RECEIVER_ROLE"); + + /// Eth1 denomination is 18 digits, while Eth2 has 9 digits. Because we work with Eth2 + /// balances and to support old interfaces expecting eth1 format, we multiply by this + /// coefficient. + uint128 internal constant DENOMINATION_OFFSET = 1e9; + + /// Historic data about 2 last completed reports and their times + bytes32 internal constant POST_COMPLETED_TOTAL_POOLED_ETHER_POSITION = keccak256("lido.LidoOracle.postCompletedTotalPooledEther"); + bytes32 internal constant PRE_COMPLETED_TOTAL_POOLED_ETHER_POSITION = keccak256("lido.LidoOracle.preCompletedTotalPooledEther"); + bytes32 internal constant LAST_COMPLETED_EPOCH_ID_POSITION = keccak256("lido.LidoOracle.lastCompletedEpochId"); + bytes32 internal constant TIME_ELAPSED_POSITION = keccak256("lido.LidoOracle.timeElapsed"); + + /// Address of the Lido contract + bytes32 internal constant LIDO_POSITION = keccak256("lido.LidoOracle.lido"); + + /// Version of the initialized contract data + /// NB: Contract versioning starts from 1. + /// The version stored in CONTRACT_VERSION_POSITION equals to + /// - 0 right after deployment when no initializer is invoked yet + /// - N after calling initialize() during deployment from scratch, where N is the current contract version + /// - N after upgrading contract from the previous version (after calling finalize_vN()) + bytes32 internal constant CONTRACT_VERSION_POSITION = keccak256("lido.LidoOracle.contractVersion"); + + /// Receiver address to be called when the report is pushed to Lido + bytes32 internal constant BEACON_REPORT_RECEIVER_POSITION = keccak256("lido.LidoOracle.beaconReportReceiver"); + + /// Upper bound of the reported balance possible increase in APR, controlled by the governance + bytes32 internal constant ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION = + keccak256("lido.LidoOracle.allowedBeaconBalanceAnnualRelativeIncrease"); + + /// Lower bound of the reported balance possible decrease, controlled by the governance + /// + /// @notice When slashing happens, the balance may decrease at a much faster pace. Slashing are + /// one-time events that decrease the balance a fair amount - a few percent at a time in a + /// realistic scenario. Thus, instead of sanity check for an APR, we check if the plain relative + /// decrease is within bounds. Note that it's not annual value, its just one-jump value. + bytes32 internal constant ALLOWED_BEACON_BALANCE_RELATIVE_DECREASE_POSITION = + keccak256("lido.LidoOracle.allowedBeaconBalanceDecrease"); + + + ///! STRUCTURED STORAGE OF THE CONTRACT + ///! Inherited from CommitteeQuorum: + ///! SLOT 0: address[] members + ///! SLOT 1: bytes[] distinctReports + ///! SLOT 2: bytes[] distinctReportHashes + ///! SLOT 3: bytes32[] distinctReportCounters + ///! Inherited from AccessControlEnumerable: + ///! SLOT 4: mapping(bytes32 => RoleData) _roles + ///! SLOT 5: mapping(bytes32 => EnumerableSet.AddressSet) _roleMembers + + + /** + * @notice Initialize the contract (version 3 for now) from scratch + * @dev For details see https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-10.md + * @param _admin Admin which can modify OpenZeppelin role holders + * @param _lido Address of Lido contract + * @param _epochsPerFrame Number of epochs per frame + * @param _slotsPerEpoch Number of slots per epoch + * @param _secondsPerSlot Number of seconds per slot + * @param _genesisTime Genesis time + * @param _allowedBeaconBalanceAnnualRelativeIncrease Allowed beacon balance annual relative increase (e.g. 1000 means 10% increase) + * @param _allowedBeaconBalanceRelativeDecrease Allowed beacon balance instantaneous decrease (e.g. 500 means 5% decrease) + */ + function initialize( + address _admin, + address _lido, + uint64 _epochsPerFrame, + uint64 _slotsPerEpoch, + uint64 _secondsPerSlot, + uint64 _genesisTime, + uint256 _allowedBeaconBalanceAnnualRelativeIncrease, + uint256 _allowedBeaconBalanceRelativeDecrease + ) + external + { + assert(1 == ((1 << (MAX_MEMBERS - 1)) >> (MAX_MEMBERS - 1))); // static assert + + // We consider storage state right after deployment (no initialize() called yet) as version 0 + + // Initializations for v0 --> v1 + if (CONTRACT_VERSION_POSITION.getStorageUint256() != 0) { + revert CanInitializeOnlyOnZeroVersion(); + } + if (_admin == address(0)) { revert ZeroAdminAddress(); } + + CONTRACT_VERSION_POSITION.setStorageUint256(1); + emit ContractVersionSet(1); + + _grantRole(DEFAULT_ADMIN_ROLE, _admin); + + LIDO_POSITION.setStorageAddress(_lido); + + _setQuorum(1); + + ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION + .setStorageUint256(_allowedBeaconBalanceAnnualRelativeIncrease); + emit AllowedBeaconBalanceAnnualRelativeIncreaseSet(_allowedBeaconBalanceAnnualRelativeIncrease); + + ALLOWED_BEACON_BALANCE_RELATIVE_DECREASE_POSITION + .setStorageUint256(_allowedBeaconBalanceRelativeDecrease); + emit AllowedBeaconBalanceRelativeDecreaseSet(_allowedBeaconBalanceRelativeDecrease); + + _setBeaconSpec(_epochsPerFrame, _slotsPerEpoch, _secondsPerSlot, _genesisTime); + + // set expected epoch to the first epoch for the next frame + _setExpectedEpochToFirstOfNextFrame(); + } + + /** + * @notice Return the Lido contract address + */ + function getLido() public view returns (ILido) { + return ILido(LIDO_POSITION.getStorageAddress()); + } + + /** + * @notice Return the upper bound of the reported balance possible increase in APR + */ + function getAllowedBeaconBalanceAnnualRelativeIncrease() external view returns (uint256) { + return ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION.getStorageUint256(); + } + + /** + * @notice Return the lower bound of the reported balance possible decrease + */ + function getAllowedBeaconBalanceRelativeDecrease() external view returns (uint256) { + return ALLOWED_BEACON_BALANCE_RELATIVE_DECREASE_POSITION.getStorageUint256(); + } + + /** + * @notice Set the upper bound of the reported balance possible increase in APR to `_value` + */ + function setAllowedBeaconBalanceAnnualRelativeIncrease(uint256 _value) + external onlyRole(SET_BEACON_REPORT_RECEIVER_ROLE) + { + ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION.setStorageUint256(_value); + emit AllowedBeaconBalanceAnnualRelativeIncreaseSet(_value); + } + + /** + * @notice Set the lower bound of the reported balance possible decrease to `_value` + */ + function setAllowedBeaconBalanceRelativeDecrease(uint256 _value) + external onlyRole(SET_REPORT_BOUNDARIES_ROLE) + { + ALLOWED_BEACON_BALANCE_RELATIVE_DECREASE_POSITION.setStorageUint256(_value); + emit AllowedBeaconBalanceRelativeDecreaseSet(_value); + } + + /** + * @notice Return the receiver contract address to be called when the report is pushed to Lido + */ + function getBeaconReportReceiver() external view returns (address) { + return BEACON_REPORT_RECEIVER_POSITION.getStorageAddress(); + } + + /** + * @notice Return the current reporting array element with index `_index` + */ + function getMemberReport(uint256 _index) + external + view + returns ( + MemberReport memory report + ) + { + report = _decodeReport(distinctReports[_index]); + } + + /** + * @notice Set the receiver contract address to `_address` to be called when the report is pushed + * @dev Specify 0 to disable this functionality + */ + function setBeaconReportReceiver(address _address) + external onlyRole(SET_BEACON_REPORT_RECEIVER_ROLE) + { + if(_address != address(0)) { + IBeaconReportReceiver iBeacon; + if (!_address.supportsInterface(iBeacon.processLidoOracleReport.selector)) { + revert BadBeaconReportReceiver(); + } + } + + BEACON_REPORT_RECEIVER_POSITION.setStorageAddress(_address); + emit BeaconReportReceiverSet(_address); + } + + /** + * @notice Return the initialized version of this contract starting from 0 + */ + function getVersion() external view returns (uint256) { + return CONTRACT_VERSION_POSITION.getStorageUint256(); + } + + /** + * @notice Report beacon balance and its change during the last frame + */ + function getLastCompletedReportDelta() + external + view + returns ( + uint256 postTotalPooledEther, + uint256 preTotalPooledEther, + uint256 timeElapsed + ) + { + postTotalPooledEther = POST_COMPLETED_TOTAL_POOLED_ETHER_POSITION.getStorageUint256(); + preTotalPooledEther = PRE_COMPLETED_TOTAL_POOLED_ETHER_POSITION.getStorageUint256(); + timeElapsed = TIME_ELAPSED_POSITION.getStorageUint256(); + } + + /** + * @notice Return last completed epoch + */ + function getLastCompletedEpochId() external view returns (uint256) { + return LAST_COMPLETED_EPOCH_ID_POSITION.getStorageUint256(); + } + + /** + * @notice Add `_member` to the oracle member committee list + */ + function addOracleMember(address _member) + external onlyRole(MANAGE_MEMBERS_ROLE) + { + _addOracleMember(_member); + } + + /** + * @notice Remove '_member` from the oracle member committee list + */ + function removeOracleMember(address _member) + external onlyRole(MANAGE_MEMBERS_ROLE) + { + _removeOracleMember(_member); + } + + function handleCommitteeMemberReport( + MemberReport calldata _report + ) external { + BeaconSpec memory beaconSpec = _getBeaconSpec(); + bool hasEpochAdvanced = _validateAndUpdateExpectedEpoch(_report.epochId, beaconSpec); + if (hasEpochAdvanced) { + _clearReporting(); + } + + if (_handleMemberReport(msg.sender, _encodeReport(_report))) { + _handleConsensusReport(_report, beaconSpec); + } + + uint128 beaconBalance = DENOMINATION_OFFSET * uint128(_report.beaconBalanceGwei); + emit CommitteeMemberReported( + _report.epochId, + beaconBalance, + _report.beaconValidators, + msg.sender, + _report.withdrawalVaultBalance, + _report.requestIdToFinalizeUpTo, + _report.finalizationShareRates + ); + } + + function _decodeReport(bytes memory _reportData) internal pure returns ( + MemberReport memory report + ) { + report = abi.decode(_reportData, (MemberReport)); + } + + function _encodeReport(MemberReport memory _report) internal pure returns ( + bytes memory reportData + ) { + reportData = abi.encode(_report); + } + + /** + * @notice Set the number of exactly the same reports needed to finalize the epoch to `_quorum` + */ + function updateQuorum(uint256 _quorum) + external onlyRole(MANAGE_QUORUM_ROLE) + { + (bool isQuorum, uint256 reportIndex) = _updateQuorum(_quorum); + if (isQuorum) { + MemberReport memory report = _decodeReport(distinctReports[reportIndex]); + _handleConsensusReport(report, _getBeaconSpec()); + } + } + + /** + * @notice Update beacon specification data + */ + function setBeaconSpec( + uint64 _epochsPerFrame, + uint64 _slotsPerEpoch, + uint64 _secondsPerSlot, + uint64 _genesisTime + ) + external onlyRole(SET_BEACON_SPEC_ROLE) + { + _setBeaconSpec( + _epochsPerFrame, + _slotsPerEpoch, + _secondsPerSlot, + _genesisTime + ); + } + + + /** + * @notice Super admin has all roles (can change committee members etc) + */ + function testnet_setAdmin(address _newAdmin) + external onlyRole(DEFAULT_ADMIN_ROLE) + { + // TODO: remove this temporary function + _grantRole(DEFAULT_ADMIN_ROLE, _newAdmin); + _revokeRole(DEFAULT_ADMIN_ROLE, msg.sender); + } + + + function testnet_addAdmin(address _newAdmin) + external onlyRole(DEFAULT_ADMIN_ROLE) + { + // TODO: remove this temporary function + _grantRole(DEFAULT_ADMIN_ROLE, _newAdmin); + } + + + function testnet_assignAllNonAdminRolesTo(address _rolesHolder) + external onlyRole(DEFAULT_ADMIN_ROLE) + { + // TODO: remove this temporary function + _grantRole(MANAGE_MEMBERS_ROLE, _rolesHolder); + _grantRole(MANAGE_QUORUM_ROLE, _rolesHolder); + _grantRole(SET_BEACON_SPEC_ROLE, _rolesHolder); + _grantRole(SET_REPORT_BOUNDARIES_ROLE, _rolesHolder); + _grantRole(SET_BEACON_REPORT_RECEIVER_ROLE, _rolesHolder); + } + + function testnet_setLido(address _newLido) external { + // TODO: remove this temporary function + LIDO_POSITION.setStorageAddress(_newLido); + } + + // /** + // * @notice Push the given report to Lido and performs accompanying accounting + // * @param _epochId Beacon chain epoch, proven to be >= expected epoch and <= current epoch + // * @param _beaconBalanceEth1 Validators balance in eth1 (18-digit denomination) + // * @param _beaconSpec current beacon specification data + // */ + function _handleConsensusReport( + MemberReport memory _report, + BeaconSpec memory _beaconSpec + ) + internal + { + uint128 beaconBalance = DENOMINATION_OFFSET * uint128(_report.beaconBalanceGwei); + + // TODO: maybe add additional report validity sanity checks + + emit ConsensusReached( + _report.epochId, + beaconBalance, + _report.beaconValidators, + _report.withdrawalVaultBalance, + _report.requestIdToFinalizeUpTo, + _report.finalizationShareRates + ); + + // now this frame is completed, so the expected epoch should be advanced to the first epoch + // of the next frame + _advanceExpectedEpoch(_report.epochId + _beaconSpec.epochsPerFrame); + _clearReporting(); + + ILido lido = getLido(); + INodeOperatorsRegistry registry = lido.getOperators(); + for (uint256 i = 0; i < _report.exitedValidatorsNumbers.length; ++i) { + registry.reportStoppedValidators( + _report.nodeOperatorsWithExitedValidators[i], + _report.exitedValidatorsNumbers[i] + ); + } + + // report to the Lido and collect stats + uint256 prevTotalPooledEther = lido.totalSupply(); + + // TODO: report other values from MemberReport + lido.handleOracleReport( + _report.beaconValidators, + beaconBalance, + _report.withdrawalVaultBalance, + _report.newDepositBufferWithdrawalsReserve, + _report.requestIdToFinalizeUpTo, + _report.finalizationShareRates + ); + uint256 postTotalPooledEther = lido.totalSupply(); + + _doWorkAfterReportingToLido( + prevTotalPooledEther, + postTotalPooledEther, + _report.epochId, + _beaconSpec + ); + } + + + function _doWorkAfterReportingToLido( + uint256 _prevTotalPooledEther, + uint256 _postTotalPooledEther, + uint256 _epochId, + BeaconSpec memory _beaconSpec + ) internal { + PRE_COMPLETED_TOTAL_POOLED_ETHER_POSITION.setStorageUint256(_prevTotalPooledEther); + POST_COMPLETED_TOTAL_POOLED_ETHER_POSITION.setStorageUint256(_postTotalPooledEther); + uint256 timeElapsed = (_epochId - LAST_COMPLETED_EPOCH_ID_POSITION.getStorageUint256()) * + _beaconSpec.slotsPerEpoch * _beaconSpec.secondsPerSlot; + TIME_ELAPSED_POSITION.setStorageUint256(timeElapsed); + LAST_COMPLETED_EPOCH_ID_POSITION.setStorageUint256(_epochId); + + // rollback on boundaries violation + _reportSanityChecks(_postTotalPooledEther, _prevTotalPooledEther, timeElapsed); + + // emit detailed statistics and call the quorum delegate with this data + emit PostTotalShares(_postTotalPooledEther, _prevTotalPooledEther, timeElapsed, getLido().getTotalShares()); + IBeaconReportReceiver receiver = IBeaconReportReceiver(BEACON_REPORT_RECEIVER_POSITION.getStorageAddress()); + if (address(receiver) != address(0)) { + receiver.processLidoOracleReport(_postTotalPooledEther, _prevTotalPooledEther, timeElapsed); + } + } + + /** + * @notice Performs logical consistency check of the Lido changes as the result of reports push + * @dev To make oracles less dangerous, we limit rewards report by 10% _annual_ increase and 5% + * _instant_ decrease in stake, with both values configurable by the governance in case of + * extremely unusual circumstances. + **/ + function _reportSanityChecks( + uint256 _postTotalPooledEther, + uint256 _preTotalPooledEther, + uint256 _timeElapsed) + internal + view + { + // TODO: update sanity checks + + if (_postTotalPooledEther >= _preTotalPooledEther) { + // increase = _postTotalPooledEther - _preTotalPooledEther, + // relativeIncrease = increase / _preTotalPooledEther, + // annualRelativeIncrease = relativeIncrease / (timeElapsed / 365 days), + // annualRelativeIncreaseBp = annualRelativeIncrease * 10000, in basis points 0.01% (1e-4) + uint256 allowedAnnualRelativeIncreaseBp = + ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION.getStorageUint256(); + // check that annualRelativeIncreaseBp <= allowedAnnualRelativeIncreaseBp + if (uint256(10000 * 365 days) * (_postTotalPooledEther - _preTotalPooledEther) > + allowedAnnualRelativeIncreaseBp * _preTotalPooledEther * _timeElapsed) + { + revert AllowedBeaconBalanceIncreaseExceeded(); + } + } else { + // decrease = _preTotalPooledEther - _postTotalPooledEther + // relativeDecrease = decrease / _preTotalPooledEther + // relativeDecreaseBp = relativeDecrease * 10000, in basis points 0.01% (1e-4) + uint256 allowedRelativeDecreaseBp = + ALLOWED_BEACON_BALANCE_RELATIVE_DECREASE_POSITION.getStorageUint256(); + // check that relativeDecreaseBp <= allowedRelativeDecreaseBp + if (uint256(10000) * (_preTotalPooledEther - _postTotalPooledEther) > + allowedRelativeDecreaseBp * _preTotalPooledEther) + { + revert AllowedBeaconBalanceDecreaseExceeded(); + } + } + } + + error CanInitializeOnlyOnZeroVersion(); + error ZeroAdminAddress(); + error BadBeaconReportReceiver(); + error AllowedBeaconBalanceIncreaseExceeded(); + error AllowedBeaconBalanceDecreaseExceeded(); + +} diff --git a/contracts/0.8.9/ReportEpochChecker.sol b/contracts/0.8.9/ReportEpochChecker.sol new file mode 100644 index 000000000..2a6597ec8 --- /dev/null +++ b/contracts/0.8.9/ReportEpochChecker.sol @@ -0,0 +1,200 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.9; + +import "./lib/AragonUnstructuredStorage.sol"; + + +contract ReportEpochChecker { + using UnstructuredStorage for bytes32; + + event ExpectedEpochIdUpdated( + uint256 epochId + ); + + event BeaconSpecSet( + uint64 epochsPerFrame, + uint64 slotsPerEpoch, + uint64 secondsPerSlot, + uint64 genesisTime + ); + + struct BeaconSpec { + uint64 epochsPerFrame; + uint64 slotsPerEpoch; + uint64 secondsPerSlot; + uint64 genesisTime; + } + + /// Storage for the actual beacon chain specification + bytes32 internal constant BEACON_SPEC_POSITION = keccak256("lido.ReportEpochChecker.beaconSpec"); + + /// Epoch that we currently collect reports + bytes32 internal constant EXPECTED_EPOCH_ID_POSITION = keccak256("lido.ReportEpochChecker.expectedEpochId"); + + + /** + * @notice Returns epoch that can be reported by oracles + */ + function getExpectedEpochId() external view returns (uint256) { + return EXPECTED_EPOCH_ID_POSITION.getStorageUint256(); + } + + /** + * @notice Return beacon specification data + */ + function getBeaconSpec() + external + view + returns ( + uint64 epochsPerFrame, + uint64 slotsPerEpoch, + uint64 secondsPerSlot, + uint64 genesisTime + ) + { + BeaconSpec memory beaconSpec = _getBeaconSpec(); + return ( + beaconSpec.epochsPerFrame, + beaconSpec.slotsPerEpoch, + beaconSpec.secondsPerSlot, + beaconSpec.genesisTime + ); + } + + + /** + * @notice Return the epoch calculated from current timestamp + */ + function getCurrentEpochId() external view returns (uint256) { + BeaconSpec memory beaconSpec = _getBeaconSpec(); + return _getCurrentEpochId(beaconSpec); + } + + /** + * @notice Return currently reportable epoch (the first epoch of the current frame) as well as + * its start and end times in seconds + */ + function getCurrentFrame() + external + view + returns ( + uint256 frameEpochId, + uint256 frameStartTime, + uint256 frameEndTime + ) + { + BeaconSpec memory beaconSpec = _getBeaconSpec(); + uint64 genesisTime = beaconSpec.genesisTime; + uint64 secondsPerEpoch = beaconSpec.secondsPerSlot * beaconSpec.slotsPerEpoch; + + frameEpochId = _getFrameFirstEpochId(_getCurrentEpochId(beaconSpec), beaconSpec); + frameStartTime = frameEpochId * secondsPerEpoch + genesisTime; + frameEndTime = (frameEpochId + beaconSpec.epochsPerFrame) * secondsPerEpoch + genesisTime - 1; + } + + function _validateAndUpdateExpectedEpoch(uint256 _epochId, BeaconSpec memory _beaconSpec) + internal returns (bool hasEpochAdvanced) + { + uint256 expectedEpoch = EXPECTED_EPOCH_ID_POSITION.getStorageUint256(); + if (_epochId < expectedEpoch) { revert EpochIsTooOld(); } + + // if expected epoch has advanced, check that this is the first epoch of the current frame + if (_epochId > expectedEpoch) { + if (_epochId != _getFrameFirstEpochId(_getCurrentEpochId(_beaconSpec), _beaconSpec)) { + revert UnexpectedEpoch(); + } + hasEpochAdvanced = true; + _advanceExpectedEpoch(_epochId); + } + } + + /** + * @notice Return beacon specification data + */ + function _getBeaconSpec() + internal + view + returns (BeaconSpec memory beaconSpec) + { + uint256 data = BEACON_SPEC_POSITION.getStorageUint256(); + beaconSpec.epochsPerFrame = uint64(data >> 192); + beaconSpec.slotsPerEpoch = uint64(data >> 128); + beaconSpec.secondsPerSlot = uint64(data >> 64); + beaconSpec.genesisTime = uint64(data); + return beaconSpec; + } + + /** + * @notice Set beacon specification data + */ + function _setBeaconSpec( + uint64 _epochsPerFrame, + uint64 _slotsPerEpoch, + uint64 _secondsPerSlot, + uint64 _genesisTime + ) + internal + { + if (_epochsPerFrame == 0) { revert BadEpochsPerFrame(); } + if (_slotsPerEpoch == 0) { revert BadSlotsPerEpoch(); } + if (_secondsPerSlot == 0) { revert BadSecondsPerSlot(); } + if (_genesisTime == 0) { revert BadGenesisTime(); } + + uint256 data = ( + uint256(_epochsPerFrame) << 192 | + uint256(_slotsPerEpoch) << 128 | + uint256(_secondsPerSlot) << 64 | + uint256(_genesisTime) + ); + BEACON_SPEC_POSITION.setStorageUint256(data); + emit BeaconSpecSet( + _epochsPerFrame, + _slotsPerEpoch, + _secondsPerSlot, + _genesisTime); + } + + function _setExpectedEpochToFirstOfNextFrame() internal { + BeaconSpec memory beaconSpec = _getBeaconSpec(); + uint256 expectedEpoch = _getFrameFirstEpochId(0, beaconSpec) + beaconSpec.epochsPerFrame; + EXPECTED_EPOCH_ID_POSITION.setStorageUint256(expectedEpoch); + emit ExpectedEpochIdUpdated(expectedEpoch); + } + + /** + * @notice Remove the current reporting progress and advances to accept the later epoch `_epochId` + */ + function _advanceExpectedEpoch(uint256 _epochId) internal { + EXPECTED_EPOCH_ID_POSITION.setStorageUint256(_epochId); + emit ExpectedEpochIdUpdated(_epochId); + } + + /** + * @notice Return the epoch calculated from current timestamp + */ + function _getCurrentEpochId(BeaconSpec memory _beaconSpec) internal view returns (uint256) { + return (_getTime() - _beaconSpec.genesisTime) / (_beaconSpec.slotsPerEpoch * _beaconSpec.secondsPerSlot); + } + + /** + * @notice Return the first epoch of the frame that `_epochId` belongs to + */ + function _getFrameFirstEpochId(uint256 _epochId, BeaconSpec memory _beaconSpec) internal pure returns (uint256) { + return _epochId / _beaconSpec.epochsPerFrame * _beaconSpec.epochsPerFrame; + } + + /** + * @notice Return the current timestamp + */ + function _getTime() internal virtual view returns (uint256) { + return block.timestamp; // solhint-disable-line not-rely-on-time + } + + error EpochIsTooOld(); + error UnexpectedEpoch(); + error BadEpochsPerFrame(); + error BadSlotsPerEpoch(); + error BadSecondsPerSlot(); + error BadGenesisTime(); +} diff --git a/contracts/0.8.9/ValidatorExitBus.sol b/contracts/0.8.9/ValidatorExitBus.sol new file mode 100644 index 000000000..9c86c4138 --- /dev/null +++ b/contracts/0.8.9/ValidatorExitBus.sol @@ -0,0 +1,341 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +import { AccessControlEnumerable } from "@openzeppelin/contracts-v4.4/access/AccessControlEnumerable.sol"; + +import "./lib/RateLimitUtils.sol"; +import "./ReportEpochChecker.sol"; +import "./CommitteeQuorum.sol"; + + +contract ValidatorExitBus is CommitteeQuorum, AccessControlEnumerable, ReportEpochChecker { + using UnstructuredStorage for bytes32; + using RateLimitUtils for LimitState.Data; + using LimitUnstructuredStorage for bytes32; + + event ValidatorExitRequest( + address indexed stakingModule, + uint256 indexed nodeOperatorId, + uint256 indexed validatorId, + bytes validatorPubkey + ); + + event RateLimitSet( + uint256 maxLimit, + uint256 limitIncreasePerBlock + ); + + event CommitteeMemberReported( + address[] stakingModules, + uint256[] nodeOperatorIds, + uint256[] validatorIds, + bytes[] validatorPubkeys, + uint256 indexed epochId + ); + + event ConsensusReached( + address[] stakingModules, + uint256[] nodeOperatorIds, + uint256[] validatorIds, + bytes[] validatorPubkeys, + uint256 indexed epochId + ); + + event ContractVersionSet(uint256 version); + + // ACL + + bytes32 constant public MANAGE_MEMBERS_ROLE = keccak256("MANAGE_MEMBERS_ROLE"); + bytes32 constant public MANAGE_QUORUM_ROLE = keccak256("MANAGE_QUORUM_ROLE"); + bytes32 constant public SET_BEACON_SPEC_ROLE = keccak256("SET_BEACON_SPEC_ROLE"); + + // UNSTRUCTURED STORAGE + + bytes32 internal constant RATE_LIMIT_STATE_POSITION = keccak256("lido.ValidatorExitBus.rateLimitState"); + + /// Version of the initialized contract data + /// NB: Contract versioning starts from 1. + /// The version stored in CONTRACT_VERSION_POSITION equals to + /// - 0 right after deployment when no initializer is invoked yet + /// - N after calling initialize() during deployment from scratch, where N is the current contract version + /// - N after upgrading contract from the previous version (after calling finalize_vN()) + bytes32 internal constant CONTRACT_VERSION_POSITION = keccak256("lido.ValidatorExitBus.contractVersion"); + + bytes32 internal constant TOTAL_EXIT_REQUESTS_POSITION = keccak256("lido.ValidatorExitBus.totalExitRequests"); + + + + + ///! STRUCTURED STORAGE OF THE CONTRACT + ///! Inherited from CommitteeQuorum: + ///! SLOT 0: address[] members + ///! SLOT 1: bytes[] distinctReports + ///! SLOT 2: bytes[] distinctReportHashes + ///! SLOT 3: bytes32[] distinctReportCounters + ///! Inherited from AccessControlEnumerable: + ///! SLOT 4: mapping(bytes32 => RoleData) _roles + ///! SLOT 5: mapping(bytes32 => EnumerableSet.AddressSet) _roleMembers + ///! Own: + ///! SLOT 6: mapping(address => mapping (uint256 => uint256)) lastRequestedValidatorIds + + /// (stakingModuleAddress, nodeOperatorId) => lastRequestedValidatorId + mapping(address => mapping (uint256 => uint256)) public lastRequestedValidatorIds; + + + // TODO: this might be used if we decide to report not responded validators + // /// (stakingModuleAddress, nodeOperatorId) => notRespondedValidatorsCount + // ///! SLOT 7: mapping(address => mapping (uint256 => uint256)) notRespondedValidatorsCount + // mapping(address => mapping (uint256 => uint256)) public notRespondedValidatorsCount; + // /// Time in seconds before a validator requested to perform voluntary exit is considered as not responded + // /// and should be reported as such + // bytes32 internal constant TIME_TO_CONSIDER_VALIDATOR_NOT_RESPONDED_SECS_POSITION + // = keccak256("lido.ValidatorExitBus.totalExitRequests"); + + function initialize( + address _admin, + uint256 _maxRequestsPerDayE18, + uint256 _numRequestsLimitIncreasePerBlockE18, + uint64 _epochsPerFrame, + uint64 _slotsPerEpoch, + uint64 _secondsPerSlot, + uint64 _genesisTime + ) external + { + // Initializations for v0 --> v1 + if (CONTRACT_VERSION_POSITION.getStorageUint256() != 0) { + revert CanInitializeOnlyOnZeroVersion(); + } + if (_admin == address(0)) { revert ZeroAdminAddress(); } + + CONTRACT_VERSION_POSITION.setStorageUint256(1); + emit ContractVersionSet(1); + + _grantRole(DEFAULT_ADMIN_ROLE, _admin); + + LimitState.Data memory limitData = RATE_LIMIT_STATE_POSITION.getStorageLimitStruct(); + limitData.setLimit(_maxRequestsPerDayE18, _numRequestsLimitIncreasePerBlockE18); + limitData.setPrevBlockNumber(block.number); + RATE_LIMIT_STATE_POSITION.setStorageLimitStruct(limitData); + emit RateLimitSet(_maxRequestsPerDayE18, _numRequestsLimitIncreasePerBlockE18); + + _setQuorum(1); + + _setBeaconSpec(_epochsPerFrame, _slotsPerEpoch, _secondsPerSlot, _genesisTime); + + // set expected epoch to the first epoch for the next frame + _setExpectedEpochToFirstOfNextFrame(); + } + + /** + * @notice Return the initialized version of this contract starting from 0 + * @dev See LIP-10 for the details + */ + function getVersion() external view returns (uint256) { + return CONTRACT_VERSION_POSITION.getStorageUint256(); + } + + function getTotalExitRequests() external view returns (uint256) { + return TOTAL_EXIT_REQUESTS_POSITION.getStorageUint256(); + } + + + function handleCommitteeMemberReport( + address[] calldata _stakingModules, + uint256[] calldata _nodeOperatorIds, + uint256[] calldata _validatorIds, + bytes[] calldata _validatorPubkeys, + uint256 _epochId + ) external { + if (_nodeOperatorIds.length != _validatorPubkeys.length) { revert ArraysMustBeSameSize(); } + if (_stakingModules.length != _validatorPubkeys.length) { revert ArraysMustBeSameSize(); } + if (_validatorIds.length != _validatorPubkeys.length) { revert ArraysMustBeSameSize(); } + if (_validatorPubkeys.length == 0) { revert EmptyArraysNotAllowed(); } + + // TODO: maybe check lengths of pubkeys + + BeaconSpec memory beaconSpec = _getBeaconSpec(); + bool hasEpochAdvanced = _validateAndUpdateExpectedEpoch(_epochId, beaconSpec); + if (hasEpochAdvanced) { + _clearReporting(); + } + + bytes memory reportBytes = _encodeReport(_stakingModules, _nodeOperatorIds, _validatorIds, _validatorPubkeys, _epochId); + if (_handleMemberReport(msg.sender, reportBytes)) { + _reportKeysToEject(_stakingModules, _nodeOperatorIds, _validatorIds, _validatorPubkeys, _epochId, beaconSpec); + } + + emit CommitteeMemberReported(_stakingModules, _nodeOperatorIds, _validatorIds, _validatorPubkeys, _epochId); + } + + + function testnet_setAdmin(address _newAdmin) + external onlyRole(DEFAULT_ADMIN_ROLE) + { + // TODO: remove this temporary function + _grantRole(DEFAULT_ADMIN_ROLE, _newAdmin); + _revokeRole(DEFAULT_ADMIN_ROLE, msg.sender); + } + + + function testnet_addAdmin(address _newAdmin) + external onlyRole(DEFAULT_ADMIN_ROLE) + { + // TODO: remove this temporary function + _grantRole(DEFAULT_ADMIN_ROLE, _newAdmin); + } + + + function testnet_assignAllNonAdminRolesTo(address _rolesHolder) + external onlyRole(DEFAULT_ADMIN_ROLE) + { + // TODO: remove this temporary function + _grantRole(MANAGE_MEMBERS_ROLE, _rolesHolder); + _grantRole(MANAGE_QUORUM_ROLE, _rolesHolder); + _grantRole(SET_BEACON_SPEC_ROLE, _rolesHolder); + } + + + function setRateLimit(uint256 _maxLimit, uint256 _limitIncreasePerBlock) external { + _setRateLimit(_maxLimit, _limitIncreasePerBlock); + } + + + function getMaxLimit() external view returns (uint96) { + LimitState.Data memory state = RATE_LIMIT_STATE_POSITION.getStorageLimitStruct(); + return state.maxLimit; + } + + + function getLimitState() external view returns (LimitState.Data memory) { + return RATE_LIMIT_STATE_POSITION.getStorageLimitStruct(); + } + + + function getCurrentLimit() external view returns (uint256) { + return RATE_LIMIT_STATE_POSITION.getStorageLimitStruct().calculateCurrentLimit(); + } + + function getLastRequestedValidatorId(address _stakingModule, uint256 _nodeOperatorId) + external view returns (uint256) + { + return lastRequestedValidatorIds[_stakingModule][_nodeOperatorId]; + } + + /** + * @notice Set the number of exactly the same reports needed to finalize the epoch to `_quorum` + */ + function updateQuorum(uint256 _quorum) + external onlyRole(MANAGE_QUORUM_ROLE) + { + (bool isQuorumReached, uint256 reportIndex) = _updateQuorum(_quorum); + if (isQuorumReached) { + ( + address[] memory stakingModules, + uint256[] memory nodeOperatorIds, + uint256[] memory validatorIds, + bytes[] memory validatorPubkeys, + uint256 epochId + ) = _decodeReport(distinctReports[reportIndex]); + _reportKeysToEject(stakingModules, nodeOperatorIds, validatorIds, validatorPubkeys, epochId, _getBeaconSpec()); + } + } + + /** + * @notice Add `_member` to the oracle member committee list + */ + function addOracleMember(address _member) + external onlyRole(MANAGE_MEMBERS_ROLE) + { + _addOracleMember(_member); + } + + + /** + * @notice Remove '_member` from the oracle member committee list + */ + function removeOracleMember(address _member) + external onlyRole(MANAGE_MEMBERS_ROLE) + { + _removeOracleMember(_member); + } + + + function _reportKeysToEject( + address[] memory _stakingModules, + uint256[] memory _nodeOperatorIds, + uint256[] memory _validatorIds, + bytes[] memory _validatorPubkeys, + uint256 _epochId, + BeaconSpec memory _beaconSpec + ) internal { + emit ConsensusReached(_stakingModules, _nodeOperatorIds, _validatorIds, _validatorPubkeys, _epochId); + + _advanceExpectedEpoch(_epochId + _beaconSpec.epochsPerFrame); + _clearReporting(); + + uint256 numKeys = _validatorPubkeys.length; + LimitState.Data memory limitData = RATE_LIMIT_STATE_POSITION.getStorageLimitStruct(); + uint256 currentLimit = limitData.calculateCurrentLimit(); + uint256 numKeysE18 = numKeys * 10**18; + if (numKeysE18 > currentLimit) { revert RateLimitExceeded(); } + limitData.updatePrevLimit(currentLimit - numKeysE18); + RATE_LIMIT_STATE_POSITION.setStorageLimitStruct(limitData); + + // TODO: maybe do some additional report validity sanity checks + + for (uint256 i = 0; i < numKeys; ++i) { + emit ValidatorExitRequest( + _stakingModules[i], + _nodeOperatorIds[i], + _validatorIds[i], + _validatorPubkeys[i] + ); + + lastRequestedValidatorIds[_stakingModules[i]][_nodeOperatorIds[i]] = _validatorIds[i]; + } + + TOTAL_EXIT_REQUESTS_POSITION.setStorageUint256( + TOTAL_EXIT_REQUESTS_POSITION.getStorageUint256() + numKeys + ); + } + + function _setRateLimit(uint256 _maxLimit, uint256 _limitIncreasePerBlock) internal { + LimitState.Data memory limitData = RATE_LIMIT_STATE_POSITION.getStorageLimitStruct(); + limitData.setLimit(_maxLimit, _limitIncreasePerBlock); + RATE_LIMIT_STATE_POSITION.setStorageLimitStruct(limitData); + + emit RateLimitSet(_maxLimit, _limitIncreasePerBlock); + } + + function _decodeReport(bytes memory _reportData) internal pure returns ( + address[] memory stakingModules, + uint256[] memory nodeOperatorIds, + uint256[] memory validatorIds, + bytes[] memory validatorPubkeys, + uint256 epochId + ) { + (stakingModules, nodeOperatorIds, validatorIds, validatorPubkeys, epochId) + = abi.decode(_reportData, (address[], uint256[], uint256[], bytes[], uint256)); + } + + + function _encodeReport( + address[] calldata _stakingModules, + uint256[] calldata _nodeOperatorIds, + uint256[] calldata _validatorIds, + bytes[] calldata _validatorPubkeys, + uint256 _epochId + ) internal pure returns ( + bytes memory reportData + ) { + reportData = abi.encode(_stakingModules, _nodeOperatorIds, _validatorIds, _validatorPubkeys, _epochId); + } + + error CanInitializeOnlyOnZeroVersion(); + error ZeroAdminAddress(); + error RateLimitExceeded(); + error ArraysMustBeSameSize(); + error EmptyArraysNotAllowed(); + +} diff --git a/contracts/0.8.9/WithdrawalQueue.sol b/contracts/0.8.9/WithdrawalQueue.sol new file mode 100644 index 000000000..4ae4e36ac --- /dev/null +++ b/contracts/0.8.9/WithdrawalQueue.sol @@ -0,0 +1,623 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +/* See contracts/COMPILERS.md */ +pragma solidity 0.8.9; + +import "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4.4/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts-v4.4/token/ERC20/extensions/draft-IERC20Permit.sol"; +import "@openzeppelin/contracts-v4.4/token/ERC20/utils/SafeERC20.sol"; + +import "./lib/AragonUnstructuredStorage.sol"; + +/** + * @title Interface defining a Lido liquid staking pool + * @dev see also [Lido liquid staking pool core contract](https://docs.lido.fi/contracts/lido) + */ +interface IStETH { + /** + * @notice Get stETH token amount by the provided shares amount + * @param _sharesAmount shares amount + * @dev dual to `getSharesByPooledEth`. + */ + function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256); + + /** + * @notice Get shares amount by the stETH token amount + * @param _pooledEthAmount stETH token amount + * @dev dual to `getPooledEthByShares`. + */ + function getSharesByPooledEth(uint256 _pooledEthAmount) external view returns (uint256); +} + +interface IWstETH { + /** + * @notice Exchanges wstETH to stETH + * @param _wstETHAmount amount of wstETH to unwrap in exchange for stETH + * @dev Requirements: + * - `_wstETHAmount` must be non-zero + * - msg.sender must have at least `_wstETHAmount` wstETH. + * @return Amount of stETH user receives after unwrap + */ + function unwrap(uint256 _wstETHAmount) external returns (uint256); + + /** + * @notice Get amount of stETH for a given amount of wstETH + * @param _wstETHAmount amount of wstETH + * @return Amount of stETH for a given wstETH amount + */ + function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256); +} + +/** + * @title A dedicated contract for handling stETH withdrawal request queue + * @author folkyatina + */ +contract WithdrawalQueue { + using SafeERC20 for IERC20; + using UnstructuredStorage for bytes32; + + /// @notice structure representing a request for withdrawal. + struct WithdrawalRequest { + /// @notice sum of the all requested ether including this request + uint128 cumulativeEther; + /// @notice sum of the all shares locked for withdrawal including this request + uint128 cumulativeShares; + /// @notice payable address of the recipient withdrawal will be transferred to + address payable recipient; + /// @notice block.number when the request created + uint64 requestBlockNumber; + /// @notice flag if the request was already claimed + bool claimed; + } + + /** + * @notice structure representing share rate for a range (`prevIndex`, `index`] in request queue + */ + struct ShareRate { + /// @notice share/ETH rate with 1e27 precision for the protocol + uint256 value; + /// @notice last index in queue this rate is actual for + /// @dev the rate is valid for (`prevIndex`, `index`] where `prevIndex` is previous element `index` value or 0 + uint256 index; + } + + /// Version of the initialized contract data + /// NB: Contract versioning starts from 1. + /// The version stored in CONTRACT_VERSION_POSITION equals to + /// - 0 right after deployment when no initializer is invoked yet + /// - N after calling initialize() during deployment from scratch, where N is the current contract version + /// - N after upgrading contract from the previous version (after calling finalize_vN()) + bytes32 internal constant CONTRACT_VERSION_POSITION = keccak256("lido.WithdrawalQueue.contractVersion"); + + /// Lido DAO Agent contract address + /// Used to call administrative levers + bytes32 internal constant LIDO_DAO_AGENT_POSITION = keccak256("lido.WithdrawalQueue.lidoDAOAgent"); + + /// Requests placement resume/pause control storage slot + bytes32 internal constant REQUESTS_PLACEMENT_RESUMED_POSITION = + keccak256("lido.WithdrawalQueue.requestsPlacementResumed"); + + /// Lido stETH token address to be set upon construction + address public immutable STETH; + /// Lido wstETH token address to be set upon construction + address public immutable WSTETH; + + /** + * @notice All state-modifying calls are allowed only from owner protocol. + * @dev should be Lido + */ + address payable public immutable OWNER; + + /** + * @notice minimal possible sum that is possible to withdraw + */ + uint256 public constant MIN_STETH_WITHDRAWAL_AMOUNT = 100 wei; + + /** + * @notice maximum possible sum that is possible to withdraw by a single request + * Prevents accumulating too much funds per single request fulfillment in the future. + * @dev To withdraw larger amounts, recommended to split it to several requests + */ + uint256 public constant MAX_STETH_WITHDRAWAL_AMOUNT = 1000 ether; + + ///! STRUCTURED STORAGE OF THE CONTRACT + ///! SLOT 0: uint128 lockedEtherAmount + ///! SLOT 1: uint256 finalizedRequestsCounter + ///! SLOT 2: WithdrawalRequest[] queue + ///! SLOT 3: mapping(address => uint256[]) requestsByRecipient + ///! SLOT 4 ShareRate[] finalizationRates + + /** + * @notice amount of ETH on this contract balance that is locked for withdrawal and waiting for claim + * @dev Invariant: `lockedEtherAmount <= this.balance` + */ + uint128 public lockedEtherAmount = 0; + + /// @notice length of the finalized part of the queue + uint256 public finalizedRequestsCounter = 0; + + /// @notice queue for withdrawal requests + WithdrawalRequest[] public queue; + + /// @notice withdrawal requests mapped to the recipients + mapping(address => uint256[]) public requestsByRecipient; + + /// @notice finalization rates history + ShareRate[] public finalizationRates; + + /** + * @param _owner address that will be able to invoke `restake` and `finalize` methods. + * @param _stETH address of StETH contract + * @param _wstETH address of WstETH contract + */ + constructor(address payable _owner, address _stETH, address _wstETH) { + if (_owner == address(0)) revert ZeroOwner(); + + // init immutables + STETH = _stETH; + WSTETH = _wstETH; + OWNER = _owner; + + // petrify the implementation by assigning a zero Lido agent address + _initialize(address(0)); + } + + function initialize(address _lidoDAOAgent) external { + if (_lidoDAOAgent == address(0)) { + revert LidoDAOAgentZeroAddress(); + } + + _initialize(_lidoDAOAgent); + } + + /// @notice Resume new withdrawal requests placement + function resumeRequestsPlacement() external whenInitialized whenPaused onlyLidoDAOAgent { + REQUESTS_PLACEMENT_RESUMED_POSITION.setStorageBool(true); + + emit WithdrawalRequestsPlacementResumed(); + } + + /// @notice Pause new withdrawal requests placement + function pauseRequestsPlacement() external whenResumed onlyLidoDAOAgent { + REQUESTS_PLACEMENT_RESUMED_POSITION.setStorageBool(false); + + emit WithdrawalRequestsPlacementPaused(); + } + + /** + * @notice Getter for withdrawal queue length + * @return length of the request queue + */ + function queueLength() external view returns (uint256) { + return queue.length; + } + + /// @notice Request withdrawal of the provided stETH token amount + function requestWithdrawal( + uint256 _amountOfStETH, + address _recipient + ) external whenResumed returns (uint256 requestId) { + _recipient = _checkWithdrawalRequestInput(_amountOfStETH, _recipient); + return _requestWithdrawal(_amountOfStETH, _recipient); + } + + /** + * @notice Request withdrawal of the provided stETH token amount using EIP-2612 Permit + * @dev NB: requires permit in stETH being implemented + */ + function requestWithdrawalWithPermit( + uint256 _amountOfStETH, + address _recipient, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external whenResumed returns (uint256 requestId) { + _recipient = _checkWithdrawalRequestInput(_amountOfStETH, _recipient); + IERC20Permit(STETH).permit(msg.sender, address(this), _amountOfStETH, _deadline, _v, _r, _s); + return _requestWithdrawal(_amountOfStETH, _recipient); + } + + /// @notice Request withdrawal of the provided wstETH token amount + function requestWithdrawalWstETH( + uint256 _amountOfWstETH, + address _recipient + ) external whenResumed returns (uint256 requestId) { + _recipient = _checkWithdrawalRequestInput(IWstETH(WSTETH).getStETHByWstETH(_amountOfWstETH), _recipient); + return _requestWithdrawalWstETH(_amountOfWstETH, _recipient); + } + + /// @notice Request withdrawal of the provided wstETH token amount using EIP-2612 Permit + function requestWithdrawalWstETHWithPermit( + uint256 _amountOfWstETH, + address _recipient, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external whenResumed returns (uint256 requestId) { + _recipient = _checkWithdrawalRequestInput(IWstETH(WSTETH).getStETHByWstETH(_amountOfWstETH), _recipient); + IERC20Permit(WSTETH).permit(msg.sender, address(this), _amountOfWstETH, _deadline, _v, _r, _s); + return _requestWithdrawalWstETH(_amountOfWstETH, _recipient); + } + + /// @notice Claim withdrawals batch once finalized (claimable) + /// NB: Always reverts + function claimWithdrawalsBatch(uint256[] calldata /*_requests*/) external pure { + revert Unimplemented(); + } + + /// @notice Returns withdrawal requests placed for the `_recipient` address + function getWithdrawalRequests(address _recipient) external view returns (uint256[] memory requestsIds) { + return requestsByRecipient[_recipient]; + } + + /// @notice Returns status of the withdrawal request + function getWithdrawalRequestStatus(uint256 _requestId) + external + view + returns ( + address recipient, + uint256 requestBlockNumber, + uint256 etherToWithdraw, + uint256 shares, + bool isFinalized, + bool isClaimed + ) + { + if (_requestId < queue.length) { + WithdrawalRequest memory request = queue[_requestId]; + + recipient = request.recipient; + requestBlockNumber = request.requestBlockNumber; + + shares = request.cumulativeShares; + etherToWithdraw = request.cumulativeEther; + if (_requestId > 0) { + shares -= queue[_requestId - 1].cumulativeShares; + etherToWithdraw -= queue[_requestId - 1].cumulativeEther; + } + + isFinalized = _requestId < finalizedRequestsCounter; + isClaimed = request.claimed; + } + } + + /// @notice Returns Lido DAO Agent address + function getLidoDAOAgent() external view returns (address) { + return LIDO_DAO_AGENT_POSITION.getStorageAddress(); + } + + /// @notice Returns whether the contract is initialized or not + function isInitialized() external view returns (bool) { + return CONTRACT_VERSION_POSITION.getStorageUint256() != 0; + } + + /// @notice Returns whether the requests placement is paused or not + function isRequestsPlacementPaused() external view returns (bool) { + return !REQUESTS_PLACEMENT_RESUMED_POSITION.getStorageBool(); + } + + /** + * @notice Finalize requests in [`finalizedRequestsCounter`,`_lastIdToFinalize`] range with `_shareRate` + * @dev ether to finalize all the requests should be calculated using `calculateFinalizationParams` and sent with + * this call as msg.value + * @param _lastIdToFinalize request index in the queue that will be last finalized request in a batch + * @param _shareRate share/ETH rate for the protocol with 1e27 decimals + */ + function finalize(uint256 _lastIdToFinalize, uint256 _shareRate) external payable onlyOwner { + if (_lastIdToFinalize < finalizedRequestsCounter || _lastIdToFinalize >= queue.length) { + revert InvalidFinalizationId(); + } + if (lockedEtherAmount + msg.value > address(this).balance) revert NotEnoughEther(); + + _updateRateHistory(_shareRate, _lastIdToFinalize); + + lockedEtherAmount += _toUint128(msg.value); + + finalizedRequestsCounter = _lastIdToFinalize + 1; + } + + /** + * @notice calculates the params to fulfill the next batch of requests in queue + * @param _lastIdToFinalize last id in the queue to finalize upon + * @param _shareRate share rate to finalize requests with + * + * @return etherToLock amount of eth required to finalize the batch + * @return sharesToBurn amount of shares that should be burned on finalization + */ + function calculateFinalizationParams( + uint256 _lastIdToFinalize, + uint256 _shareRate + ) external view returns (uint256 etherToLock, uint256 sharesToBurn) { + return _calculateDiscountedBatch(finalizedRequestsCounter, _lastIdToFinalize, _shareRate); + } + + /** + * @notice Transfer the right to claim withdrawal to another `_newRecipient` + * @dev should be called by the old recepient + * @param _requestId id of the request subject to change + * @param _newRecipient new recipient address for withdrawal + */ + function changeRecipient(uint256 _requestId, address _newRecipient) external { + WithdrawalRequest storage request = queue[_requestId]; + + if (request.recipient != msg.sender) revert RecipientExpected(request.recipient, msg.sender); + if (request.claimed) revert RequestAlreadyClaimed(); + + request.recipient = payable(_newRecipient); + } + + /** + * @notice Claim `_requestId` request and transfer reserved ether to recipient + * @param _requestId request id to claim + * @param _rateIndexHint rate index found offchain that should be used for claiming + */ + function claimWithdrawal(uint256 _requestId, uint256 _rateIndexHint) external { + // request must be finalized + if (_requestId >= finalizedRequestsCounter) revert RequestNotFinalized(); + + WithdrawalRequest storage request = queue[_requestId]; + + if (request.claimed) revert RequestAlreadyClaimed(); + request.claimed = true; + + ShareRate memory shareRate; + + if (_isRateHintValid(_requestId, _rateIndexHint)) { + shareRate = finalizationRates[_rateIndexHint]; + } else { + // unbounded loop branch. Can fail with OOG + shareRate = finalizationRates[findRateHint(_requestId)]; + } + + (uint128 etherToBeClaimed, ) = _calculateDiscountedBatch(_requestId, _requestId, shareRate.value); + + lockedEtherAmount -= etherToBeClaimed; + + _sendValue(request.recipient, etherToBeClaimed); + + emit WithdrawalClaimed(_requestId, request.recipient, msg.sender); + } + + /** + * @notice view function to find a proper ShareRate offchain to pass it to `claim()` later + * @param _requestId request id to be claimed later + * + * @return hint rate index for this request + */ + function findRateHint(uint256 _requestId) public view returns (uint256 hint) { + if (_requestId >= finalizedRequestsCounter) revert RateNotFound(); + + for (uint256 i = finalizationRates.length; i > 0; i--) { + if (_isRateHintValid(_requestId, i - 1)) { + return i - 1; + } + } + assert(false); + } + + /// @dev calculates `eth` and `shares` for the batch of requests in (`_firstId`, `_lastId`] range using `_shareRate` + function _calculateDiscountedBatch( + uint256 _firstId, + uint256 _lastId, + uint256 _shareRate + ) internal view returns (uint128 eth, uint128 shares) { + eth = queue[_lastId].cumulativeEther; + shares = queue[_lastId].cumulativeShares; + + if (_firstId > 0) { + eth -= queue[_firstId - 1].cumulativeEther; + shares -= queue[_firstId - 1].cumulativeShares; + } + + eth = _min(eth, _toUint128((shares * _shareRate) / 1e9)); + } + + /// @dev checks if provided request included in the rate hint boundaries + function _isRateHintValid(uint256 _requestId, uint256 _hint) internal view returns (bool isInRange) { + uint256 rightBoundary = finalizationRates[_hint].index; + + isInRange = _requestId <= rightBoundary; + if (_hint > 0) { + uint256 leftBoundary = finalizationRates[_hint - 1].index; + + isInRange = isInRange && leftBoundary < _requestId; + } + } + + /// @dev add a new entry to share rates history or modify the last one if rate does not change + function _updateRateHistory(uint256 _shareRate, uint256 _index) internal { + if (finalizationRates.length == 0) { + finalizationRates.push(ShareRate(_shareRate, _index)); + } else { + ShareRate storage lastRate = finalizationRates[finalizationRates.length - 1]; + + if (_shareRate == lastRate.value) { + lastRate.index = _index; + } else { + finalizationRates.push(ShareRate(_shareRate, _index)); + } + } + } + + /// @notice internal initialization helper + /// @dev doesn't check provided address intentionally + function _initialize(address _lidoDAOAgent) internal { + if (CONTRACT_VERSION_POSITION.getStorageUint256() != 0) { + revert AlreadyInitialized(); + } + + LIDO_DAO_AGENT_POSITION.setStorageAddress(_lidoDAOAgent); + CONTRACT_VERSION_POSITION.setStorageUint256(1); + + emit InitializedV1(_lidoDAOAgent, msg.sender); + } + + function _requestWithdrawal(uint256 _amountOfStETH, address _recipient) internal returns (uint256 requestId) { + IERC20(STETH).safeTransferFrom(msg.sender, address(this), _amountOfStETH); + + return _enqueue(_amountOfStETH, _recipient); + } + + function _requestWithdrawalWstETH( + uint256 _amountOfWstETH, + address _recipient + ) internal returns (uint256 requestId) { + IERC20(WSTETH).safeTransferFrom(msg.sender, address(this), _amountOfWstETH); + uint256 amountOfStETH = IWstETH(WSTETH).unwrap(_amountOfWstETH); + + return _enqueue(amountOfStETH, _recipient); + } + + function _checkWithdrawalRequestInput(uint256 _amountOfStETH, address _recipient) internal view returns (address) { + if (_amountOfStETH < MIN_STETH_WITHDRAWAL_AMOUNT) { + revert RequestAmountTooSmall(_amountOfStETH); + } + if (_amountOfStETH > MAX_STETH_WITHDRAWAL_AMOUNT) { + revert RequestAmountTooLarge(_amountOfStETH); + } + if (_recipient == address(0)) { + _recipient = msg.sender; + } + + return _recipient; + } + + function _enqueue(uint256 _amountOfStETH, address _recipient) internal returns (uint256 requestId) { + requestId = queue.length; + + uint256 shares = IStETH(STETH).getSharesByPooledEth(_amountOfStETH); + + uint256 cumulativeShares = shares; + uint256 cumulativeEther = _amountOfStETH; + + if (requestId > 0) { + WithdrawalRequest memory prevRequest = queue[requestId - 1]; + + cumulativeShares += prevRequest.cumulativeShares; + cumulativeShares += prevRequest.cumulativeEther; + } + + queue.push( + WithdrawalRequest( + uint128(cumulativeEther), + uint128(cumulativeShares), + payable(_recipient), + uint64(block.number), + false + ) + ); + + requestsByRecipient[msg.sender].push(requestId); + + emit WithdrawalRequested(requestId, msg.sender, _recipient, _amountOfStETH, shares); + } + + function _min(uint128 a, uint128 b) internal pure returns (uint128) { + return a < b ? a : b; + } + + function _sendValue(address payable recipient, uint256 amount) internal { + if (address(this).balance < amount) revert NotEnoughEther(); + + // solhint-disable-next-line + (bool success, ) = recipient.call{value: amount}(""); + if (!success) revert CantSendValueRecipientMayHaveReverted(); + } + + function _toUint64(uint256 value) internal pure returns (uint64) { + if (value > type(uint64).max) revert SafeCastValueDoesNotFit96Bits(); + return uint64(value); + } + + function _toUint128(uint256 value) internal pure returns (uint128) { + if (value > type(uint128).max) revert SafeCastValueDoesNotFit128Bits(); + return uint128(value); + } + + modifier onlyOwner() { + if (msg.sender != OWNER) revert NotOwner(); + _; + } + + /// @notice Reverts when the contract is uninitialized + modifier whenInitialized() { + if (CONTRACT_VERSION_POSITION.getStorageUint256() == 0) { + revert Uninitialized(); + } + _; + } + + /// @notice Reverts when the caller is not Lido DAO Agent + modifier onlyLidoDAOAgent() { + if (msg.sender != LIDO_DAO_AGENT_POSITION.getStorageAddress()) { + revert LidoDAOAgentExpected(msg.sender); + } + _; + } + + /// @notice Reverts when new withdrawal requests placement resumed + modifier whenPaused() { + if (REQUESTS_PLACEMENT_RESUMED_POSITION.getStorageBool()) { + revert PausedRequestsPlacementExpected(); + } + _; + } + + /// @notice Reverts when new withdrawal requests placement paused + modifier whenResumed() { + if (!REQUESTS_PLACEMENT_RESUMED_POSITION.getStorageBool()) { + revert ResumedRequestsPlacementExpected(); + } + _; + } + + /// @notice Emitted when a new withdrawal request enqueued + /// @dev Contains both stETH token amount and its corresponding shares amount + event WithdrawalRequested( + uint256 indexed requestId, + address indexed requestor, + address indexed recipient, + uint256 amountOfStETH, + uint256 amountOfShares + ); + /// @notice Emitted when withdrawal requests placement paused + event WithdrawalRequestsPlacementPaused(); + + /// @notice Emitted when withdrawal requests placement resumed + event WithdrawalRequestsPlacementResumed(); + + /// @notice Emitted when the contract initialized + /// @param _lidoDAOAgent provided Lido DAO Agent address + /// @param _caller initialization `msg.sender` + event InitializedV1(address _lidoDAOAgent, address _caller); + + event WithdrawalClaimed(uint256 indexed requestId, address indexed receiver, address initiator); + + error StETHInvalidAddress(address _stETH); + error WstETHInvalidAddress(address _wstETH); + error InvalidWithdrawalRequest(uint256 _requestId); + error LidoDAOAgentZeroAddress(); + error LidoDAOAgentExpected(address _msgSender); + error RecipientExpected(address _recipient, address _msgSender); + error AlreadyInitialized(); + error Uninitialized(); + error Unimplemented(); + error PausedRequestsPlacementExpected(); + error ResumedRequestsPlacementExpected(); + error RequestAmountTooSmall(uint256 _amountOfStETH); + error RequestAmountTooLarge(uint256 _amountOfStETH); + error ZeroOwner(); + error InvalidFinalizationId(); + error NotEnoughEther(); + error RequestNotFinalized(); + error RequestAlreadyClaimed(); + error RateNotFound(); + error NotOwner(); + error CantSendValueRecipientMayHaveReverted(); + error SafeCastValueDoesNotFit96Bits(); + error SafeCastValueDoesNotFit128Bits(); +} diff --git a/contracts/0.8.9/WithdrawalVault.sol b/contracts/0.8.9/WithdrawalVault.sol new file mode 100644 index 000000000..b6c3bef90 --- /dev/null +++ b/contracts/0.8.9/WithdrawalVault.sol @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2022 Lido + +// SPDX-License-Identifier: GPL-3.0 + +/* See contracts/COMPILERS.md */ +pragma solidity 0.8.9; + +import "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4.4/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts-v4.4/token/ERC20/utils/SafeERC20.sol"; + +interface ILido { + /** + * @notice A payable function supposed to be called only by WithdrawalVault contract + * @dev We need a dedicated function because funds received by the default payable function + * are treated as a user deposit + */ + function receiveWithdrawals() external payable; +} + + +/** + * @title A vault for temporary storage of withdrawals + */ +contract WithdrawalVault { + using SafeERC20 for IERC20; + + address public immutable LIDO; + address public immutable TREASURY; + + /** + * Emitted when the ERC20 `token` recovered (i.e. transferred) + * to the Lido treasury address by `requestedBy` sender. + */ + event ERC20Recovered( + address indexed requestedBy, + address indexed token, + uint256 amount + ); + + /** + * Emitted when the ERC721-compatible `token` (NFT) recovered (i.e. transferred) + * to the Lido treasury address by `requestedBy` sender. + */ + event ERC721Recovered( + address indexed requestedBy, + address indexed token, + uint256 tokenId + ); + + /** + * Ctor + * + * @param _lido the Lido token (stETH) address + * @param _treasury the Lido treasury address (see ERC20/ERC721-recovery interfaces) + */ + constructor(address _lido, address _treasury) { + require(_lido != address(0), "LIDO_ZERO_ADDRESS"); + require(_treasury != address(0), "TREASURY_ZERO_ADDRESS"); + + LIDO = _lido; + TREASURY = _treasury; + } + + /** + * @notice Withdraw `_maxAmount` of accumulated withdrawals to Lido contract + * @dev Can be called only by the Lido contract + * @param _amount Max amount of ETH to withdraw + */ + function withdrawWithdrawals(uint256 _amount) external { + require(msg.sender == LIDO, "ONLY_LIDO_CAN_WITHDRAW"); + + uint256 balance = address(this).balance; + + if (balance > _amount) { + revert NotEnoughEther(_amount, balance); + } + + + ILido(LIDO).receiveWithdrawals{value: _amount}(); + } + + /** + * Transfers a given `_amount` of an ERC20-token (defined by the `_token` contract address) + * currently belonging to the burner contract address to the Lido treasury address. + * + * @param _token an ERC20-compatible token + * @param _amount token amount + */ + function recoverERC20(address _token, uint256 _amount) external { + require(_amount > 0, "ZERO_RECOVERY_AMOUNT"); + + emit ERC20Recovered(msg.sender, _token, _amount); + + IERC20(_token).safeTransfer(TREASURY, _amount); + } + + /** + * Transfers a given token_id of an ERC721-compatible NFT (defined by the token contract address) + * currently belonging to the burner contract address to the Lido treasury address. + * + * @param _token an ERC721-compatible token + * @param _tokenId minted token id + */ + function recoverERC721(address _token, uint256 _tokenId) external { + emit ERC721Recovered(msg.sender, _token, _tokenId); + + IERC721(_token).transferFrom(address(this), TREASURY, _tokenId); + } + + error NotEnoughEther(uint256 requested, uint256 balance); +} diff --git a/contracts/0.8.9/lib/AragonUnstructuredStorage.sol b/contracts/0.8.9/lib/AragonUnstructuredStorage.sol new file mode 100644 index 000000000..f4ac8d1bc --- /dev/null +++ b/contracts/0.8.9/lib/AragonUnstructuredStorage.sol @@ -0,0 +1,56 @@ +/* + * SPDX-License-Identifier: MIT + */ + +pragma solidity 0.8.9; + + +library UnstructuredStorage { + function getStorageBool(bytes32 position) internal view returns (bool data) { + assembly { + data := sload(position) + } + } + + function getStorageAddress(bytes32 position) internal view returns (address data) { + assembly { + data := sload(position) + } + } + + function getStorageBytes32(bytes32 position) internal view returns (bytes32 data) { + assembly { + data := sload(position) + } + } + + function getStorageUint256(bytes32 position) internal view returns (uint256 data) { + assembly { + data := sload(position) + } + } + + function setStorageBool(bytes32 position, bool data) internal { + assembly { + sstore(position, data) + } + } + + function setStorageAddress(bytes32 position, address data) internal { + assembly { + sstore(position, data) + } + } + + function setStorageBytes32(bytes32 position, bytes32 data) internal { + assembly { + sstore(position, data) + } + } + + function setStorageUint256(bytes32 position, uint256 data) internal { + assembly { + sstore(position, data) + } + } +} diff --git a/contracts/0.8.9/lib/RateLimitUtils.sol b/contracts/0.8.9/lib/RateLimitUtils.sol new file mode 100644 index 000000000..440e4e1cf --- /dev/null +++ b/contracts/0.8.9/lib/RateLimitUtils.sol @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + + +import "./AragonUnstructuredStorage.sol"; + +// +// We need to pack four variables into the same 256bit-wide storage slot +// to lower the costs per each staking request. +// +// As a result, slot's memory aligned as follows: +// +// MSB ------------------------------------------------------------------------------> LSB +// 256____________160_________________________128_______________32_____________________ 0 +// |_______________|___________________________|________________|_______________________| +// | maxLimit | maxLimitGrowthBlocks | prevLimit | prevBlockNumber | +// |<-- 96 bits -->|<---------- 32 bits ------>|<-- 96 bits --->|<----- 32 bits ------->| +// +// +// NB: Internal representation conventions: +// +// - the `maxLimitGrowthBlocks` field above represented as follows: +// `maxLimitGrowthBlocks` = `maxLimit` / `limitIncreasePerBlock` +// 32 bits 96 bits 96 bits +// +// + +/** +* @notice Library for the internal structs definitions +* @dev solidity <0.6 doesn't support top-level structs +* using the library to have a proper namespace +*/ +library LimitState { + /** + * @dev Internal representation struct (slot-wide) + */ + struct Data { + uint32 prevBlockNumber; + uint96 prevLimit; + uint32 maxLimitGrowthBlocks; + uint96 maxLimit; + } +} + + +library LimitUnstructuredStorage { + using UnstructuredStorage for bytes32; + + /// @dev Storage offset for `maxLimit` (bits) + uint256 internal constant MAX_LIMIT_OFFSET = 160; + /// @dev Storage offset for `maxLimitGrowthBlocks` (bits) + uint256 internal constant MAX_LIMIT_GROWTH_BLOCKS_OFFSET = 128; + /// @dev Storage offset for `prevLimit` (bits) + uint256 internal constant PREV_LIMIT_OFFSET = 32; + /// @dev Storage offset for `prevBlockNumber` (bits) + uint256 internal constant PREV_BLOCK_NUMBER_OFFSET = 0; + + /** + * @dev Read limit state from the unstructured storage position + * @param _position storage offset + */ + function getStorageLimitStruct(bytes32 _position) internal view returns (LimitState.Data memory rateLimit) { + uint256 slotValue = _position.getStorageUint256(); + + rateLimit.prevBlockNumber = uint32(slotValue >> PREV_BLOCK_NUMBER_OFFSET); + rateLimit.prevLimit = uint96(slotValue >> PREV_LIMIT_OFFSET); + rateLimit.maxLimitGrowthBlocks = uint32(slotValue >> MAX_LIMIT_GROWTH_BLOCKS_OFFSET); + rateLimit.maxLimit = uint96(slotValue >> MAX_LIMIT_OFFSET); + } + + /** + * @dev Write limit state to the unstructured storage position + * @param _position storage offset + * @param _data limit state structure instance + */ + function setStorageLimitStruct(bytes32 _position, LimitState.Data memory _data) internal { + _position.setStorageUint256( + uint256(_data.prevBlockNumber) << PREV_BLOCK_NUMBER_OFFSET + | uint256(_data.prevLimit) << PREV_LIMIT_OFFSET + | uint256(_data.maxLimitGrowthBlocks) << MAX_LIMIT_GROWTH_BLOCKS_OFFSET + | uint256(_data.maxLimit) << MAX_LIMIT_OFFSET + ); + } +} + +/** +* @notice Interface library with helper functions to deal with take limit struct in a more high-level approach. +*/ +library RateLimitUtils { + /** + * @notice Calculate limit for the current block. + */ + function calculateCurrentLimit(LimitState.Data memory _data) + internal view returns(uint256 limit) + { + uint256 limitIncPerBlock = 0; + if (_data.maxLimitGrowthBlocks != 0) { + limitIncPerBlock = _data.maxLimit / _data.maxLimitGrowthBlocks; + } + + limit = _data.prevLimit + ((block.number - _data.prevBlockNumber) * limitIncPerBlock); + if (limit > _data.maxLimit) { + limit = _data.maxLimit; + } + } + + /** + * @notice Update limit repr with the desired limits + * @dev Input `_data` param is mutated and the func returns effectively the same pointer + * @param _data limit state struct + * @param _maxLimit limit max value + * @param _limitIncreasePerBlock limit increase (restoration) per block + */ + function setLimit( + LimitState.Data memory _data, + uint256 _maxLimit, + uint256 _limitIncreasePerBlock + ) internal view { + if (_maxLimit == 0) { revert ZeroMaxLimit(); } + if (_maxLimit >= type(uint96).max) { revert TooLargeMaxLimit(); } + if (_maxLimit < _limitIncreasePerBlock) { revert TooLargeLimitIncrease(); } + if ( + (_limitIncreasePerBlock != 0) + && (_maxLimit / _limitIncreasePerBlock >= type(uint32).max) + ) { + revert TooSmallLimitIncrease(); + } + + // if no limit was set previously, + // or new limit is lower than previous, then + // reset prev limit to the new max limit + if ((_data.maxLimit == 0) || (_maxLimit < _data.prevLimit)) { + _data.prevLimit = uint96(_maxLimit); + } + _data.maxLimitGrowthBlocks = _limitIncreasePerBlock != 0 ? uint32(_maxLimit / _limitIncreasePerBlock) : 0; + + _data.maxLimit = uint96(_maxLimit); + + if (_data.prevBlockNumber != 0) { + _data.prevBlockNumber = uint32(block.number); + } + } + + + /** + * @notice Update limit repr after submitting user's eth + * @dev Input `_data` param is mutated and the func returns effectively the same pointer + * @param _data limit state struct + * @param _newPrevLimit new value for the `prevLimit` field + */ + function updatePrevLimit( + LimitState.Data memory _data, + uint256 _newPrevLimit + ) internal view { + assert(_newPrevLimit < type(uint96).max); + assert(_data.prevBlockNumber != 0); + + _data.prevLimit = uint96(_newPrevLimit); + _data.prevBlockNumber = uint32(block.number); + } + + function setPrevBlockNumber( + LimitState.Data memory _data, + uint256 _blockNumber + ) internal pure { + _data.prevBlockNumber = uint32(_blockNumber); + } + + error ZeroMaxLimit(); + error TooLargeMaxLimit(); + error TooLargeLimitIncrease(); + error TooSmallLimitIncrease(); +} diff --git a/contracts/0.8.9/proxy/OssifiableProxy.sol b/contracts/0.8.9/proxy/OssifiableProxy.sol new file mode 100644 index 000000000..76aee44df --- /dev/null +++ b/contracts/0.8.9/proxy/OssifiableProxy.sol @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2022 Lido + +// SPDX-License-Identifier: GPL-3.0 + +/* See contracts/COMPILERS.md */ +pragma solidity 0.8.9; + +import {Address} from "@openzeppelin/contracts-v4.4/utils/Address.sol"; +import {StorageSlot} from "@openzeppelin/contracts-v4.4/utils/StorageSlot.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts-v4.4/proxy/ERC1967/ERC1967Proxy.sol"; + +/// @notice An ossifiable proxy contract. Extends the ERC1967Proxy contract by +/// adding admin functionality +contract OssifiableProxy is ERC1967Proxy { + /// @dev Initializes the upgradeable proxy with the initial implementation and admin + /// @param implementation_ Address of the implementation + /// @param admin_ Address of the admin of the proxy + /// @param data_ Data used in a delegate call to implementation. The delegate call will be + /// skipped if the data is empty bytes + constructor( + address implementation_, + address admin_, + bytes memory data_ + ) ERC1967Proxy(implementation_, data_) { + _changeAdmin(admin_); + } + + /// @notice Returns the current admin of the proxy + function proxy__getAdmin() external view returns (address) { + return _getAdmin(); + } + + /// @notice Returns the current implementation address + function proxy__getImplementation() external view returns (address) { + return _implementation(); + } + + /// @notice Returns whether the implementation is locked forever + function proxy__getIsOssified() external view returns (bool) { + return _getAdmin() == address(0); + } + + /// @notice Allows to transfer admin rights to zero address and prevent future + /// upgrades of the proxy + function proxy__ossify() external onlyAdmin { + address prevAdmin = _getAdmin(); + StorageSlot.getAddressSlot(_ADMIN_SLOT).value = address(0); + emit AdminChanged(prevAdmin, address(0)); + emit ProxyOssified(); + } + + /// @notice Changes the admin of the proxy + /// @param newAdmin_ Address of the new admin + function proxy__changeAdmin(address newAdmin_) external onlyAdmin { + _changeAdmin(newAdmin_); + } + + /// @notice Upgrades the implementation of the proxy + /// @param newImplementation_ Address of the new implementation + function proxy__upgradeTo(address newImplementation_) external onlyAdmin { + _upgradeTo(newImplementation_); + } + + /// @notice Upgrades the proxy to a new implementation, optionally performing an additional + /// setup call. + /// @param newImplementation_ Address of the new implementation + /// @param setupCalldata_ Data for the setup call. The call is skipped if setupCalldata_ is + /// empty and forceCall_ is false + /// @param forceCall_ Forces make delegate call to the implementation even with empty data_ + function proxy__upgradeToAndCall( + address newImplementation_, + bytes memory setupCalldata_, + bool forceCall_ + ) external onlyAdmin { + _upgradeToAndCall(newImplementation_, setupCalldata_, forceCall_); + } + + /// @dev Validates that proxy is not ossified and that method is called by the admin + /// of the proxy + modifier onlyAdmin() { + address admin = _getAdmin(); + if (admin == address(0)) { + revert ErrorProxyIsOssified(); + } + if (admin != msg.sender) { + revert ErrorNotAdmin(); + } + _; + } + + event ProxyOssified(); + + error ErrorNotAdmin(); + error ErrorProxyIsOssified(); +} diff --git a/contracts/0.8.9/test_helpers/LidoMockForOracleNew.sol b/contracts/0.8.9/test_helpers/LidoMockForOracleNew.sol new file mode 100644 index 000000000..85e626997 --- /dev/null +++ b/contracts/0.8.9/test_helpers/LidoMockForOracleNew.sol @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2020 Lido + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.9; + + +/** + * @dev Only for testing purposes! Lido version with some functions exposed. + */ +contract LidoMockForOracleNew { + uint256 private totalPooledEther; + address private nodeOperatorsRegistry; + + constructor (address _nodeOperatorsRegistry) { + nodeOperatorsRegistry = _nodeOperatorsRegistry; + } + + function totalSupply() external view returns (uint256) { + return totalPooledEther; + } + + function handleOracleReport( + uint256, + uint256 _beaconBalance, + uint256, + uint256, + uint256[] calldata, + uint256[] calldata + ) external { + totalPooledEther = _beaconBalance; + } + + function getTotalShares() public view returns (uint256) { + return 42; + } + + function pretendTotalPooledEtherGweiForTest(uint256 _val) public { + totalPooledEther = _val * 1e9; // gwei to wei + } + + function getOperators() external view returns (address) { + return nodeOperatorsRegistry; + } +} diff --git a/contracts/0.8.9/test_helpers/LidoOracleNewMock.sol b/contracts/0.8.9/test_helpers/LidoOracleNewMock.sol new file mode 100644 index 000000000..9671de24e --- /dev/null +++ b/contracts/0.8.9/test_helpers/LidoOracleNewMock.sol @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2022 Lido + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.9; + +import "../LidoOracleNew.sol"; + + +/** + * @dev Only for testing purposes! LidoOracleNew version with some functions exposed. + */ +contract LidoOracleNewMock is LidoOracleNew { + uint256 private time; + using UnstructuredStorage for bytes32; + + function setTime(uint256 _time) public { + time = _time; + } + + function getTimeOriginal() external view returns (uint256) { + return ReportEpochChecker._getTime(); + } + + function _getTime() internal override view returns (uint256) { + return time; + } + + function setVersion(uint256 _version) external { + CONTRACT_VERSION_POSITION.setStorageUint256(_version); + } +} diff --git a/contracts/0.8.9/test_helpers/NodeOperatorsRegistryMockForLidoOracleNew.sol b/contracts/0.8.9/test_helpers/NodeOperatorsRegistryMockForLidoOracleNew.sol new file mode 100644 index 000000000..6b538d78a --- /dev/null +++ b/contracts/0.8.9/test_helpers/NodeOperatorsRegistryMockForLidoOracleNew.sol @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2021 Lido + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.9; + +contract NodeOperatorsRegistryMockForLidoOracleNew { + + /** + * @notice Report `_stoppedIncrement` more stopped validators of the node operator #`_id` + */ + function reportStoppedValidators(uint256 _id, uint64 _stoppedIncrement) external { + + } +} diff --git a/contracts/0.8.9/test_helpers/Owner.sol b/contracts/0.8.9/test_helpers/Owner.sol new file mode 100644 index 000000000..59a521ce3 --- /dev/null +++ b/contracts/0.8.9/test_helpers/Owner.sol @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2021 Lido + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.9; + +contract Owner { + constructor() payable {} + + function receiveRestake() external payable {} +} diff --git a/contracts/0.8.9/test_helpers/StETHMockForWithdrawalQueue.sol b/contracts/0.8.9/test_helpers/StETHMockForWithdrawalQueue.sol new file mode 100644 index 000000000..3468a7030 --- /dev/null +++ b/contracts/0.8.9/test_helpers/StETHMockForWithdrawalQueue.sol @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2020 Lido + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.9; + + +/** + * @dev Only for testing purposes! Lido version with some functions exposed. + */ +contract StETHMockForWithdrawalQueue { + + function getPooledEthByShares(uint256 _sharesAmount) + external view returns (uint256) + { + return (_sharesAmount * 123 * 10**18) / 10**18; + } + + function getSharesByPooledEth(uint256 _pooledEthAmount) + external view returns (uint256) + { + return (_pooledEthAmount * 899 * 10**18) / 10**18; + } + +} diff --git a/contracts/0.8.9/test_helpers/ValidatorExitBusMock.sol b/contracts/0.8.9/test_helpers/ValidatorExitBusMock.sol new file mode 100644 index 000000000..03902be1e --- /dev/null +++ b/contracts/0.8.9/test_helpers/ValidatorExitBusMock.sol @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2022 Lido + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.9; + +import "../ValidatorExitBus.sol"; + + +/** + * @dev Only for testing purposes! LidoOracleNew version with some functions exposed. + */ +contract ValidatorExitBusMock is ValidatorExitBus { + uint256 private time; + using UnstructuredStorage for bytes32; + + function setTime(uint256 _time) public { + time = _time; + } + + function getTimeOriginal() external view returns (uint256) { + return ReportEpochChecker._getTime(); + } + + function _getTime() internal override view returns (uint256) { + return time; + } + + function setVersion(uint256 _version) external { + CONTRACT_VERSION_POSITION.setStorageUint256(_version); + } +} diff --git a/lib/abi/CommitteeQuorum.json b/lib/abi/CommitteeQuorum.json new file mode 100644 index 000000000..e52c956ce --- /dev/null +++ b/lib/abi/CommitteeQuorum.json @@ -0,0 +1 @@ +[{"inputs":[],"name":"MemberAlreadyReported","type":"error"},{"inputs":[],"name":"MemberExists","type":"error"},{"inputs":[],"name":"MemberNotFound","type":"error"},{"inputs":[],"name":"NotMemberReported","type":"error"},{"inputs":[],"name":"QuorumWontBeMade","type":"error"},{"inputs":[],"name":"TooManyMembers","type":"error"},{"inputs":[],"name":"ZeroMemberAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"}],"name":"MemberAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"}],"name":"MemberRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"quorum","type":"uint256"}],"name":"QuorumChanged","type":"event"},{"inputs":[],"name":"MAX_MEMBERS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentOraclesReportStatus","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDistinctMemberReportsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOracleMembers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getQuorum","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/abi/IERC721.json b/lib/abi/IERC721.json deleted file mode 100644 index b777358c2..000000000 --- a/lib/abi/IERC721.json +++ /dev/null @@ -1 +0,0 @@ -[{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":true,"stateMutability":"payable","type":"function"}] \ No newline at end of file diff --git a/lib/abi/ILido.json b/lib/abi/ILido.json index f08bfb199..3f9e51de9 100644 --- a/lib/abi/ILido.json +++ b/lib/abi/ILido.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_sharesAmount","type":"uint256"}],"name":"burnShares","outputs":[{"internalType":"uint256","name":"newTotalShares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pooledEthAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[],"name":"receiveWithdrawals","outputs":[],"stateMutability":"payable","type":"function"}] \ No newline at end of file diff --git a/lib/abi/INodeOperatorsRegistry.json b/lib/abi/INodeOperatorsRegistry.json index 49a40bbae..73b738c0f 100644 --- a/lib/abi/INodeOperatorsRegistry.json +++ b/lib/abi/INodeOperatorsRegistry.json @@ -1 +1 @@ -[{"inputs":[],"name":"getKeysOpIndex","outputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"uint256","name":"_id","type":"uint256"},{"internalType":"uint64","name":"_stoppedIncrement","type":"uint64"}],"name":"reportStoppedValidators","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/lib/abi/IStETH.json b/lib/abi/IStETH.json new file mode 100644 index 000000000..eccd3709f --- /dev/null +++ b/lib/abi/IStETH.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"uint256","name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pooledEthAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/abi/IWstETH.json b/lib/abi/IWstETH.json new file mode 100644 index 000000000..d17d10c63 --- /dev/null +++ b/lib/abi/IWstETH.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"uint256","name":"_wstETHAmount","type":"uint256"}],"name":"getStETHByWstETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_wstETHAmount","type":"uint256"}],"name":"unwrap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/lib/abi/Lido.json b/lib/abi/Lido.json index c08657854..7cd3bf43c 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":false,"inputs":[{"name":"_depositContract","type":"address"},{"name":"_oracle","type":"address"},{"name":"_operators","type":"address"},{"name":"_treasury","type":"address"},{"name":"_insuranceFund","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getInsuranceFund","outputs":[{"name":"","type":"address"}],"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":"getOperators","outputs":[{"name":"","type":"address"}],"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":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":"DEPOSIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_SIZE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","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":"MANAGE_WITHDRAWAL_KEY","outputs":[{"name":"","type":"bytes32"}],"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":"receiveELRewards","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getELRewardsWithdrawalLimit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","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":false,"inputs":[{"name":"_limitPoints","type":"uint16"}],"name":"setELRewardsWithdrawalLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_beaconValidators","type":"uint256"},{"name":"_beaconBalance","type":"uint256"}],"name":"handleOracleReport","outputs":[],"payable":false,"stateMutability":"nonpayable","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":true,"inputs":[],"name":"SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getELRewardsVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","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":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_executionLayerRewardsVault","type":"address"}],"name":"setELRewardsVault","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":"MANAGE_PROTOCOL_CONTRACTS_ROLE","outputs":[{"name":"","type":"bytes32"}],"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":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_treasuryFeeBasisPoints","type":"uint16"},{"name":"_insuranceFeeBasisPoints","type":"uint16"},{"name":"_operatorsFeeBasisPoints","type":"uint16"}],"name":"setFeeDistribution","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_feeBasisPoints","type":"uint16"}],"name":"setFee","outputs":[],"payable":false,"stateMutability":"nonpayable","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":false,"inputs":[{"name":"_maxDeposits","type":"uint256"}],"name":"depositBufferedEther","outputs":[],"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":"MANAGE_FEE","outputs":[{"name":"","type":"bytes32"}],"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":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"WITHDRAWAL_CREDENTIALS_LENGTH","outputs":[{"name":"","type":"uint256"}],"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":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PUBKEY_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_EL_REWARDS_VAULT_ROLE","outputs":[{"name":"","type":"bytes32"}],"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":true,"inputs":[],"name":"getDepositContract","outputs":[{"name":"","type":"address"}],"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":"removeStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"BURN_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"feeBasisPoints","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":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":false,"inputs":[{"name":"_oracle","type":"address"},{"name":"_treasury","type":"address"},{"name":"_insuranceFund","type":"address"}],"name":"setProtocolContracts","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"burnShares","outputs":[{"name":"newTotalShares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","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":[{"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":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":[],"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":false,"name":"oracle","type":"address"},{"indexed":false,"name":"treasury","type":"address"},{"indexed":false,"name":"insuranceFund","type":"address"}],"name":"ProtocolContactsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint16"}],"name":"FeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasuryFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"insuranceFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"operatorsFeeBasisPoints","type":"uint16"}],"name":"FeeDistributionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"ELRewardsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"limitPoints","type":"uint256"}],"name":"ELRewardsWithdrawalLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"withdrawalCredentials","type":"bytes32"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"executionLayerRewardsVault","type":"address"}],"name":"ELRewardsVaultSet","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":"sender","type":"address"},{"indexed":false,"name":"tokenAmount","type":"uint256"},{"indexed":false,"name":"sentFromBuffer","type":"uint256"},{"indexed":true,"name":"pubkeyHash","type":"bytes32"},{"indexed":false,"name":"etherAmount","type":"uint256"}],"name":"Withdrawal","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":"getWithdrawalQueue","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_beaconValidators","type":"uint256"},{"name":"_beaconBalance","type":"uint256"},{"name":"_withdrawalVaultBalance","type":"uint256"},{"name":"_withdrawalsReserveAmount","type":"uint256"},{"name":"_requestIdToFinalizeUpTo","type":"uint256[]"},{"name":"_finalizationShareRates","type":"uint256[]"}],"name":"handleOracleReport","outputs":[],"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":"getBufferWithdrawalsReserve","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOperators","outputs":[{"name":"","type":"address"}],"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":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":"DEPOSIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_SIZE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","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":"MANAGE_WITHDRAWAL_KEY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_oracle","type":"address"},{"name":"_treasury","type":"address"},{"name":"_executionLayerRewardsVault","type":"address"},{"name":"_withdrawalQueue","type":"address"}],"name":"setProtocolContracts","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"receiveELRewards","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getELRewardsWithdrawalLimit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","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":false,"inputs":[{"name":"_limitPoints","type":"uint16"}],"name":"setELRewardsWithdrawalLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","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":true,"inputs":[],"name":"SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getELRewardsVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","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":"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":"MANAGE_PROTOCOL_CONTRACTS_ROLE","outputs":[{"name":"","type":"bytes32"}],"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":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_feeBasisPoints","type":"uint16"}],"name":"setFee","outputs":[],"payable":false,"stateMutability":"nonpayable","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":false,"inputs":[{"name":"_maxDeposits","type":"uint256"}],"name":"depositBufferedEther","outputs":[],"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":"MANAGE_FEE","outputs":[{"name":"","type":"bytes32"}],"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":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"WITHDRAWAL_CREDENTIALS_LENGTH","outputs":[{"name":"","type":"uint256"}],"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":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PUBKEY_LENGTH","outputs":[{"name":"","type":"uint256"}],"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":true,"inputs":[],"name":"getDepositContract","outputs":[{"name":"","type":"address"}],"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":"removeStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"BURN_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_depositContract","type":"address"},{"name":"_oracle","type":"address"},{"name":"_operators","type":"address"},{"name":"_treasury","type":"address"},{"name":"_executionLayerRewardsVault","type":"address"},{"name":"_withdrawalQueue","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"feeBasisPoints","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":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":false,"inputs":[{"name":"_treasuryFeeBasisPoints","type":"uint16"},{"name":"_operatorsFeeBasisPoints","type":"uint16"}],"name":"setFeeDistribution","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"burnShares","outputs":[{"name":"newTotalShares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","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"},{"constant":true,"inputs":[],"name":"getWithdrawalVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"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":false,"name":"oracle","type":"address"},{"indexed":false,"name":"treasury","type":"address"},{"indexed":false,"name":"_executionLayerRewardsVault","type":"address"},{"indexed":false,"name":"_withdrawalQueue","type":"address"}],"name":"ProtocolContactsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint16"}],"name":"FeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasuryFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"operatorsFeeBasisPoints","type":"uint16"}],"name":"FeeDistributionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"ELRewardsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"limitPoints","type":"uint256"}],"name":"ELRewardsWithdrawalLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"withdrawalCredentials","type":"bytes32"}],"name":"WithdrawalCredentialsSet","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":"WithdrawalsReceived","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":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":[{"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/LidoOracleNew.json b/lib/abi/LidoOracleNew.json new file mode 100644 index 000000000..9149a125b --- /dev/null +++ b/lib/abi/LidoOracleNew.json @@ -0,0 +1 @@ +[{"inputs":[],"name":"AllowedBeaconBalanceDecreaseExceeded","type":"error"},{"inputs":[],"name":"AllowedBeaconBalanceIncreaseExceeded","type":"error"},{"inputs":[],"name":"BadBeaconReportReceiver","type":"error"},{"inputs":[],"name":"BadEpochsPerFrame","type":"error"},{"inputs":[],"name":"BadGenesisTime","type":"error"},{"inputs":[],"name":"BadSecondsPerSlot","type":"error"},{"inputs":[],"name":"BadSlotsPerEpoch","type":"error"},{"inputs":[],"name":"CanInitializeOnlyOnZeroVersion","type":"error"},{"inputs":[],"name":"EpochIsTooOld","type":"error"},{"inputs":[],"name":"MemberAlreadyReported","type":"error"},{"inputs":[],"name":"MemberExists","type":"error"},{"inputs":[],"name":"MemberNotFound","type":"error"},{"inputs":[],"name":"NotMemberReported","type":"error"},{"inputs":[],"name":"QuorumWontBeMade","type":"error"},{"inputs":[],"name":"TooManyMembers","type":"error"},{"inputs":[],"name":"UnexpectedEpoch","type":"error"},{"inputs":[],"name":"ZeroAdminAddress","type":"error"},{"inputs":[],"name":"ZeroMemberAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"AllowedBeaconBalanceAnnualRelativeIncreaseSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"AllowedBeaconBalanceRelativeDecreaseSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"callback","type":"address"}],"name":"BeaconReportReceiverSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"epochsPerFrame","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"slotsPerEpoch","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"secondsPerSlot","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"genesisTime","type":"uint64"}],"name":"BeaconSpecSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"epochId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"beaconBalance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"beaconValidators","type":"uint256"},{"indexed":false,"internalType":"address","name":"caller","type":"address"},{"indexed":false,"internalType":"uint256","name":"withdrawalVaultBalance","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"requestIdToFinalizeUpTo","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"finalizationShareRates","type":"uint256[]"}],"name":"CommitteeMemberReported","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"epochId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"beaconBalance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"beaconValidators","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_withdrawalVaultBalance","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"requestIdToFinalizeUpTo","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"finalizationShareRates","type":"uint256[]"}],"name":"ConsensusReached","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":"epochId","type":"uint256"}],"name":"ExpectedEpochIdUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"}],"name":"MemberAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"}],"name":"MemberRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"postTotalPooledEther","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"preTotalPooledEther","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timeElapsed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalShares","type":"uint256"}],"name":"PostTotalShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"quorum","type":"uint256"}],"name":"QuorumChanged","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"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_MEMBERS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_QUORUM_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_MEMBERS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_BEACON_REPORT_RECEIVER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_BEACON_SPEC_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_REPORT_BOUNDARIES_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_member","type":"address"}],"name":"addOracleMember","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getAllowedBeaconBalanceAnnualRelativeIncrease","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllowedBeaconBalanceRelativeDecrease","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBeaconReportReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBeaconSpec","outputs":[{"internalType":"uint64","name":"epochsPerFrame","type":"uint64"},{"internalType":"uint64","name":"slotsPerEpoch","type":"uint64"},{"internalType":"uint64","name":"secondsPerSlot","type":"uint64"},{"internalType":"uint64","name":"genesisTime","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentEpochId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentFrame","outputs":[{"internalType":"uint256","name":"frameEpochId","type":"uint256"},{"internalType":"uint256","name":"frameStartTime","type":"uint256"},{"internalType":"uint256","name":"frameEndTime","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentOraclesReportStatus","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDistinctMemberReportsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExpectedEpochId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastCompletedEpochId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastCompletedReportDelta","outputs":[{"internalType":"uint256","name":"postTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"preTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"timeElapsed","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLido","outputs":[{"internalType":"contract ILido","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"getMemberReport","outputs":[{"components":[{"internalType":"uint256","name":"epochId","type":"uint256"},{"internalType":"uint256","name":"beaconValidators","type":"uint256"},{"internalType":"uint64","name":"beaconBalanceGwei","type":"uint64"},{"internalType":"address[]","name":"stakingModules","type":"address[]"},{"internalType":"uint256[]","name":"nodeOperatorsWithExitedValidators","type":"uint256[]"},{"internalType":"uint64[]","name":"exitedValidatorsNumbers","type":"uint64[]"},{"internalType":"uint256","name":"withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"newDepositBufferWithdrawalsReserve","type":"uint256"},{"internalType":"uint256[]","name":"requestIdToFinalizeUpTo","type":"uint256[]"},{"internalType":"uint256[]","name":"finalizationShareRates","type":"uint256[]"}],"internalType":"struct LidoOracleNew.MemberReport","name":"report","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOracleMembers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getQuorum","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":"getVersion","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":[{"components":[{"internalType":"uint256","name":"epochId","type":"uint256"},{"internalType":"uint256","name":"beaconValidators","type":"uint256"},{"internalType":"uint64","name":"beaconBalanceGwei","type":"uint64"},{"internalType":"address[]","name":"stakingModules","type":"address[]"},{"internalType":"uint256[]","name":"nodeOperatorsWithExitedValidators","type":"uint256[]"},{"internalType":"uint64[]","name":"exitedValidatorsNumbers","type":"uint64[]"},{"internalType":"uint256","name":"withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"newDepositBufferWithdrawalsReserve","type":"uint256"},{"internalType":"uint256[]","name":"requestIdToFinalizeUpTo","type":"uint256[]"},{"internalType":"uint256[]","name":"finalizationShareRates","type":"uint256[]"}],"internalType":"struct LidoOracleNew.MemberReport","name":"_report","type":"tuple"}],"name":"handleCommitteeMemberReport","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":"uint64","name":"_epochsPerFrame","type":"uint64"},{"internalType":"uint64","name":"_slotsPerEpoch","type":"uint64"},{"internalType":"uint64","name":"_secondsPerSlot","type":"uint64"},{"internalType":"uint64","name":"_genesisTime","type":"uint64"},{"internalType":"uint256","name":"_allowedBeaconBalanceAnnualRelativeIncrease","type":"uint256"},{"internalType":"uint256","name":"_allowedBeaconBalanceRelativeDecrease","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_member","type":"address"}],"name":"removeOracleMember","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":"uint256","name":"_value","type":"uint256"}],"name":"setAllowedBeaconBalanceAnnualRelativeIncrease","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"setAllowedBeaconBalanceRelativeDecrease","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"setBeaconReportReceiver","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"_epochsPerFrame","type":"uint64"},{"internalType":"uint64","name":"_slotsPerEpoch","type":"uint64"},{"internalType":"uint64","name":"_secondsPerSlot","type":"uint64"},{"internalType":"uint64","name":"_genesisTime","type":"uint64"}],"name":"setBeaconSpec","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":"address","name":"_newAdmin","type":"address"}],"name":"testnet_addAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_rolesHolder","type":"address"}],"name":"testnet_assignAllNonAdminRolesTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newAdmin","type":"address"}],"name":"testnet_setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newLido","type":"address"}],"name":"testnet_setLido","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_quorum","type":"uint256"}],"name":"updateQuorum","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/lib/abi/LidoTemplate.json b/lib/abi/LidoTemplate.json index bcba81d91..35c0d1a01 100644 --- a/lib/abi/LidoTemplate.json +++ b/lib/abi/LidoTemplate.json @@ -1 +1 @@ -[{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_daoName","type":"string"},{"name":"_totalFeeBP","type":"uint16"},{"name":"_treasuryFeeBP","type":"uint16"},{"name":"_insuranceFeeBP","type":"uint16"},{"name":"_operatorsFeeBP","type":"uint16"},{"name":"_unvestedTokensAmount","type":"uint256"},{"name":"_elRewardsVault","type":"address"},{"name":"_elRewardsWithdrawalLimit","type":"uint16"}],"name":"finalizeDAO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_tld","type":"bytes32"},{"name":"_label","type":"bytes32"}],"name":"deployLidoAPM","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_holders","type":"address[]"},{"name":"_amounts","type":"uint256[]"},{"name":"_vestingStart","type":"uint64"},{"name":"_vestingCliff","type":"uint64"},{"name":"_vestingEnd","type":"uint64"},{"name":"_vestingRevokable","type":"bool"},{"name":"_expectedFinalTotalSupply","type":"uint256"}],"name":"issueTokens","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"_to","type":"address"}],"name":"cancelAndTransferDomain","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_initialSemanticVersion","type":"uint16[3]"},{"name":"_lidoImplAddress","type":"address"},{"name":"_lidoContentURI","type":"bytes"},{"name":"_nodeOperatorsRegistryImplAddress","type":"address"},{"name":"_nodeOperatorsRegistryContentURI","type":"bytes"},{"name":"_oracleImplAddress","type":"address"},{"name":"_oracleContentURI","type":"bytes"}],"name":"createRepos","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_tokenName","type":"string"},{"name":"_tokenSymbol","type":"string"},{"name":"_votingSettings","type":"uint64[4]"},{"name":"_beaconDepositContract","type":"address"},{"name":"_beaconSpec","type":"uint32[4]"}],"name":"newDAO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getConfig","outputs":[{"name":"_owner","type":"address"},{"name":"_daoFactory","type":"address"},{"name":"_ens","type":"address"},{"name":"_miniMeFactory","type":"address"},{"name":"_aragonID","type":"address"},{"name":"_apmRegistryFactory","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_owner","type":"address"},{"name":"_daoFactory","type":"address"},{"name":"_ens","type":"address"},{"name":"_miniMeFactory","type":"address"},{"name":"_aragonID","type":"address"},{"name":"_apmRegistryFactory","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"apm","type":"address"}],"name":"TmplAPMDeployed","type":"event"},{"anonymous":false,"inputs":[],"name":"TmplReposCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"appProxy","type":"address"},{"indexed":false,"name":"appId","type":"bytes32"}],"name":"TmplAppInstalled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"dao","type":"address"},{"indexed":false,"name":"token","type":"address"}],"name":"TmplDAOAndTokenDeployed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"totalAmount","type":"uint256"}],"name":"TmplTokensIssued","type":"event"},{"anonymous":false,"inputs":[],"name":"TmplDaoFinalized","type":"event"}] \ No newline at end of file +[{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_tld","type":"bytes32"},{"name":"_label","type":"bytes32"}],"name":"deployLidoAPM","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_holders","type":"address[]"},{"name":"_amounts","type":"uint256[]"},{"name":"_vestingStart","type":"uint64"},{"name":"_vestingCliff","type":"uint64"},{"name":"_vestingEnd","type":"uint64"},{"name":"_vestingRevokable","type":"bool"},{"name":"_expectedFinalTotalSupply","type":"uint256"}],"name":"issueTokens","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"_to","type":"address"}],"name":"cancelAndTransferDomain","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_initialSemanticVersion","type":"uint16[3]"},{"name":"_lidoImplAddress","type":"address"},{"name":"_lidoContentURI","type":"bytes"},{"name":"_nodeOperatorsRegistryImplAddress","type":"address"},{"name":"_nodeOperatorsRegistryContentURI","type":"bytes"},{"name":"_oracleImplAddress","type":"address"},{"name":"_oracleContentURI","type":"bytes"}],"name":"createRepos","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_tokenName","type":"string"},{"name":"_tokenSymbol","type":"string"},{"name":"_votingSettings","type":"uint64[4]"},{"name":"_beaconDepositContract","type":"address"},{"name":"","type":"uint32[4]"}],"name":"newDAO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getConfig","outputs":[{"name":"_owner","type":"address"},{"name":"_daoFactory","type":"address"},{"name":"_ens","type":"address"},{"name":"_miniMeFactory","type":"address"},{"name":"_aragonID","type":"address"},{"name":"_apmRegistryFactory","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_daoName","type":"string"},{"name":"_totalFeeBP","type":"uint16"},{"name":"_treasuryFeeBP","type":"uint16"},{"name":"_operatorsFeeBP","type":"uint16"},{"name":"_unvestedTokensAmount","type":"uint256"},{"name":"_elRewardsVault","type":"address"},{"name":"_elRewardsWithdrawalLimit","type":"uint16"}],"name":"finalizeDAO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_owner","type":"address"},{"name":"_daoFactory","type":"address"},{"name":"_ens","type":"address"},{"name":"_miniMeFactory","type":"address"},{"name":"_aragonID","type":"address"},{"name":"_apmRegistryFactory","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"apm","type":"address"}],"name":"TmplAPMDeployed","type":"event"},{"anonymous":false,"inputs":[],"name":"TmplReposCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"appProxy","type":"address"},{"indexed":false,"name":"appId","type":"bytes32"}],"name":"TmplAppInstalled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"dao","type":"address"},{"indexed":false,"name":"token","type":"address"}],"name":"TmplDAOAndTokenDeployed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"totalAmount","type":"uint256"}],"name":"TmplTokensIssued","type":"event"},{"anonymous":false,"inputs":[],"name":"TmplDaoFinalized","type":"event"}] \ No newline at end of file diff --git a/lib/abi/OssifiableProxy.json b/lib/abi/OssifiableProxy.json new file mode 100644 index 000000000..4e931a704 --- /dev/null +++ b/lib/abi/OssifiableProxy.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"implementation_","type":"address"},{"internalType":"address","name":"admin_","type":"address"},{"internalType":"bytes","name":"data_","type":"bytes"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ErrorNotAdmin","type":"error"},{"inputs":[],"name":"ErrorProxyIsOssified","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"beacon","type":"address"}],"name":"BeaconUpgraded","type":"event"},{"anonymous":false,"inputs":[],"name":"ProxyOssified","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"address","name":"newAdmin_","type":"address"}],"name":"proxy__changeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"proxy__getAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxy__getImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxy__getIsOssified","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxy__ossify","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation_","type":"address"}],"name":"proxy__upgradeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation_","type":"address"},{"internalType":"bytes","name":"setupCalldata_","type":"bytes"},{"internalType":"bool","name":"forceCall_","type":"bool"}],"name":"proxy__upgradeToAndCall","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/lib/abi/RateLimitUtils.json b/lib/abi/RateLimitUtils.json new file mode 100644 index 000000000..cbb320783 --- /dev/null +++ b/lib/abi/RateLimitUtils.json @@ -0,0 +1 @@ +[{"inputs":[],"name":"TooLargeLimitIncrease","type":"error"},{"inputs":[],"name":"TooLargeMaxLimit","type":"error"},{"inputs":[],"name":"TooSmallLimitIncrease","type":"error"},{"inputs":[],"name":"ZeroMaxLimit","type":"error"}] \ No newline at end of file diff --git a/lib/abi/ReportEpochChecker.json b/lib/abi/ReportEpochChecker.json new file mode 100644 index 000000000..7307c6e18 --- /dev/null +++ b/lib/abi/ReportEpochChecker.json @@ -0,0 +1 @@ +[{"inputs":[],"name":"BadEpochsPerFrame","type":"error"},{"inputs":[],"name":"BadGenesisTime","type":"error"},{"inputs":[],"name":"BadSecondsPerSlot","type":"error"},{"inputs":[],"name":"BadSlotsPerEpoch","type":"error"},{"inputs":[],"name":"EpochIsTooOld","type":"error"},{"inputs":[],"name":"UnexpectedEpoch","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"epochsPerFrame","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"slotsPerEpoch","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"secondsPerSlot","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"genesisTime","type":"uint64"}],"name":"BeaconSpecSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"epochId","type":"uint256"}],"name":"ExpectedEpochIdUpdated","type":"event"},{"inputs":[],"name":"getBeaconSpec","outputs":[{"internalType":"uint64","name":"epochsPerFrame","type":"uint64"},{"internalType":"uint64","name":"slotsPerEpoch","type":"uint64"},{"internalType":"uint64","name":"secondsPerSlot","type":"uint64"},{"internalType":"uint64","name":"genesisTime","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentEpochId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentFrame","outputs":[{"internalType":"uint256","name":"frameEpochId","type":"uint256"},{"internalType":"uint256","name":"frameStartTime","type":"uint256"},{"internalType":"uint256","name":"frameEndTime","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExpectedEpochId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/abi/ValidatorExitBus.json b/lib/abi/ValidatorExitBus.json new file mode 100644 index 000000000..346120623 --- /dev/null +++ b/lib/abi/ValidatorExitBus.json @@ -0,0 +1 @@ +[{"inputs":[],"name":"ArraysMustBeSameSize","type":"error"},{"inputs":[],"name":"BadEpochsPerFrame","type":"error"},{"inputs":[],"name":"BadGenesisTime","type":"error"},{"inputs":[],"name":"BadSecondsPerSlot","type":"error"},{"inputs":[],"name":"BadSlotsPerEpoch","type":"error"},{"inputs":[],"name":"CanInitializeOnlyOnZeroVersion","type":"error"},{"inputs":[],"name":"EmptyArraysNotAllowed","type":"error"},{"inputs":[],"name":"EpochIsTooOld","type":"error"},{"inputs":[],"name":"MemberAlreadyReported","type":"error"},{"inputs":[],"name":"MemberExists","type":"error"},{"inputs":[],"name":"MemberNotFound","type":"error"},{"inputs":[],"name":"NotMemberReported","type":"error"},{"inputs":[],"name":"QuorumWontBeMade","type":"error"},{"inputs":[],"name":"RateLimitExceeded","type":"error"},{"inputs":[],"name":"TooLargeLimitIncrease","type":"error"},{"inputs":[],"name":"TooLargeMaxLimit","type":"error"},{"inputs":[],"name":"TooManyMembers","type":"error"},{"inputs":[],"name":"TooSmallLimitIncrease","type":"error"},{"inputs":[],"name":"UnexpectedEpoch","type":"error"},{"inputs":[],"name":"ZeroAdminAddress","type":"error"},{"inputs":[],"name":"ZeroMaxLimit","type":"error"},{"inputs":[],"name":"ZeroMemberAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"epochsPerFrame","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"slotsPerEpoch","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"secondsPerSlot","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"genesisTime","type":"uint64"}],"name":"BeaconSpecSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"stakingModules","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"nodeOperatorIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"validatorIds","type":"uint256[]"},{"indexed":false,"internalType":"bytes[]","name":"validatorPubkeys","type":"bytes[]"},{"indexed":true,"internalType":"uint256","name":"epochId","type":"uint256"}],"name":"CommitteeMemberReported","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"stakingModules","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"nodeOperatorIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"validatorIds","type":"uint256[]"},{"indexed":false,"internalType":"bytes[]","name":"validatorPubkeys","type":"bytes[]"},{"indexed":true,"internalType":"uint256","name":"epochId","type":"uint256"}],"name":"ConsensusReached","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":"epochId","type":"uint256"}],"name":"ExpectedEpochIdUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"}],"name":"MemberAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"}],"name":"MemberRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"quorum","type":"uint256"}],"name":"QuorumChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxLimit","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"limitIncreasePerBlock","type":"uint256"}],"name":"RateLimitSet","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":"stakingModule","type":"address"},{"indexed":true,"internalType":"uint256","name":"nodeOperatorId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"validatorId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"validatorPubkey","type":"bytes"}],"name":"ValidatorExitRequest","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_MEMBERS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_QUORUM_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_MEMBERS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_BEACON_SPEC_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_member","type":"address"}],"name":"addOracleMember","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getBeaconSpec","outputs":[{"internalType":"uint64","name":"epochsPerFrame","type":"uint64"},{"internalType":"uint64","name":"slotsPerEpoch","type":"uint64"},{"internalType":"uint64","name":"secondsPerSlot","type":"uint64"},{"internalType":"uint64","name":"genesisTime","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentEpochId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentFrame","outputs":[{"internalType":"uint256","name":"frameEpochId","type":"uint256"},{"internalType":"uint256","name":"frameStartTime","type":"uint256"},{"internalType":"uint256","name":"frameEndTime","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentOraclesReportStatus","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDistinctMemberReportsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExpectedEpochId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_stakingModule","type":"address"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"}],"name":"getLastRequestedValidatorId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLimitState","outputs":[{"components":[{"internalType":"uint32","name":"prevBlockNumber","type":"uint32"},{"internalType":"uint96","name":"prevLimit","type":"uint96"},{"internalType":"uint32","name":"maxLimitGrowthBlocks","type":"uint32"},{"internalType":"uint96","name":"maxLimit","type":"uint96"}],"internalType":"struct LimitState.Data","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxLimit","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOracleMembers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getQuorum","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":"getTotalExitRequests","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVersion","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":"address[]","name":"_stakingModules","type":"address[]"},{"internalType":"uint256[]","name":"_nodeOperatorIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_validatorIds","type":"uint256[]"},{"internalType":"bytes[]","name":"_validatorPubkeys","type":"bytes[]"},{"internalType":"uint256","name":"_epochId","type":"uint256"}],"name":"handleCommitteeMemberReport","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":"uint256","name":"_maxRequestsPerDayE18","type":"uint256"},{"internalType":"uint256","name":"_numRequestsLimitIncreasePerBlockE18","type":"uint256"},{"internalType":"uint64","name":"_epochsPerFrame","type":"uint64"},{"internalType":"uint64","name":"_slotsPerEpoch","type":"uint64"},{"internalType":"uint64","name":"_secondsPerSlot","type":"uint64"},{"internalType":"uint64","name":"_genesisTime","type":"uint64"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"lastRequestedValidatorIds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_member","type":"address"}],"name":"removeOracleMember","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":"uint256","name":"_maxLimit","type":"uint256"},{"internalType":"uint256","name":"_limitIncreasePerBlock","type":"uint256"}],"name":"setRateLimit","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":"address","name":"_newAdmin","type":"address"}],"name":"testnet_addAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_rolesHolder","type":"address"}],"name":"testnet_assignAllNonAdminRolesTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newAdmin","type":"address"}],"name":"testnet_setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_quorum","type":"uint256"}],"name":"updateQuorum","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/lib/abi/WithdrawalQueue.json b/lib/abi/WithdrawalQueue.json new file mode 100644 index 000000000..b9b9e639d --- /dev/null +++ b/lib/abi/WithdrawalQueue.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address payable","name":"_owner","type":"address"},{"internalType":"address","name":"_stETH","type":"address"},{"internalType":"address","name":"_wstETH","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"CantSendValueRecipientMayHaveReverted","type":"error"},{"inputs":[],"name":"InvalidFinalizationId","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"InvalidWithdrawalRequest","type":"error"},{"inputs":[{"internalType":"address","name":"_msgSender","type":"address"}],"name":"LidoDAOAgentExpected","type":"error"},{"inputs":[],"name":"LidoDAOAgentZeroAddress","type":"error"},{"inputs":[],"name":"NotEnoughEther","type":"error"},{"inputs":[],"name":"NotOwner","type":"error"},{"inputs":[],"name":"PausedRequestsPlacementExpected","type":"error"},{"inputs":[],"name":"RateNotFound","type":"error"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"address","name":"_msgSender","type":"address"}],"name":"RecipientExpected","type":"error"},{"inputs":[],"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":"RequestNotFinalized","type":"error"},{"inputs":[],"name":"ResumedRequestsPlacementExpected","type":"error"},{"inputs":[],"name":"SafeCastValueDoesNotFit128Bits","type":"error"},{"inputs":[],"name":"SafeCastValueDoesNotFit96Bits","type":"error"},{"inputs":[{"internalType":"address","name":"_stETH","type":"address"}],"name":"StETHInvalidAddress","type":"error"},{"inputs":[],"name":"Unimplemented","type":"error"},{"inputs":[],"name":"Uninitialized","type":"error"},{"inputs":[{"internalType":"address","name":"_wstETH","type":"address"}],"name":"WstETHInvalidAddress","type":"error"},{"inputs":[],"name":"ZeroOwner","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_lidoDAOAgent","type":"address"},{"indexed":false,"internalType":"address","name":"_caller","type":"address"}],"name":"InitializedV1","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"address","name":"initiator","type":"address"}],"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":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"WithdrawalRequested","type":"event"},{"anonymous":false,"inputs":[],"name":"WithdrawalRequestsPlacementPaused","type":"event"},{"anonymous":false,"inputs":[],"name":"WithdrawalRequestsPlacementResumed","type":"event"},{"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":"OWNER","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WSTETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_lastIdToFinalize","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"}],"name":"calculateFinalizationParams","outputs":[{"internalType":"uint256","name":"etherToLock","type":"uint256"},{"internalType":"uint256","name":"sharesToBurn","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"},{"internalType":"uint256","name":"_rateIndexHint","type":"uint256"}],"name":"claimWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"name":"claimWithdrawalsBatch","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"finalizationRates","outputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"index","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_lastIdToFinalize","type":"uint256"},{"internalType":"uint256","name":"_shareRate","type":"uint256"}],"name":"finalize","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"finalizedRequestsCounter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"findRateHint","outputs":[{"internalType":"uint256","name":"hint","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLidoDAOAgent","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"getWithdrawalRequestStatus","outputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"requestBlockNumber","type":"uint256"},{"internalType":"uint256","name":"etherToWithdraw","type":"uint256"},{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"bool","name":"isFinalized","type":"bool"},{"internalType":"bool","name":"isClaimed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"}],"name":"getWithdrawalRequests","outputs":[{"internalType":"uint256[]","name":"requestsIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_lidoDAOAgent","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isInitialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isRequestsPlacementPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockedEtherAmount","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pauseRequestsPlacement","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"queue","outputs":[{"internalType":"uint128","name":"cumulativeEther","type":"uint128"},{"internalType":"uint128","name":"cumulativeShares","type":"uint128"},{"internalType":"address payable","name":"recipient","type":"address"},{"internalType":"uint64","name":"requestBlockNumber","type":"uint64"},{"internalType":"bool","name":"claimed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"queueLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"requestWithdrawal","outputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"uint8","name":"_v","type":"uint8"},{"internalType":"bytes32","name":"_r","type":"bytes32"},{"internalType":"bytes32","name":"_s","type":"bytes32"}],"name":"requestWithdrawalWithPermit","outputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountOfWstETH","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"requestWithdrawalWstETH","outputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountOfWstETH","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"uint8","name":"_v","type":"uint8"},{"internalType":"bytes32","name":"_r","type":"bytes32"},{"internalType":"bytes32","name":"_s","type":"bytes32"}],"name":"requestWithdrawalWstETHWithPermit","outputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"requestsByRecipient","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"resumeRequestsPlacement","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/lib/abi/WithdrawalVault.json b/lib/abi/WithdrawalVault.json new file mode 100644 index 000000000..a8f27704b --- /dev/null +++ b/lib/abi/WithdrawalVault.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"_lido","type":"address"},{"internalType":"address","name":"_treasury","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"requested","type":"uint256"},{"internalType":"uint256","name":"balance","type":"uint256"}],"name":"NotEnoughEther","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"},{"inputs":[],"name":"LIDO","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":"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":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawWithdrawals","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/package.json b/package.json index dcbb7a479..9148ae053 100644 --- a/package.json +++ b/package.json @@ -2,25 +2,27 @@ "name": "@lido/dao", "version": "0.0.1", "private": true, + "author": "Lido ", + "homepage": "https://lido.fi/", + "license": "GPL-3.0", "workspaces": [ "apps/*/app", "lib", "gasprofile" ], "scripts": { - "postinstall": "patch-package", + "postinstall": "patch-package && husky install", "build:apps": "yarn compile && hardhat run --no-compile scripts/build-apps-frontend.js", "lint": "yarn lint:sol && yarn lint:js", - "lint:sol": "yarn lint:sol:solhint && yarn lint:sol:solium", + "lint:sol": "solhint \"contracts/**/*.sol\" --ignore-path .solhintignore", + "lint:sol:fix": "yarn lint:sol --fix", "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/", - "lint:sol:solium": "solium --dir ./contracts", - "lint:sol:solium:fix": "yarn lint:sol:solium --fix", - "lint:sol:solhint": "solhint \"contracts/**/*.sol\" --ignore-path .soliumignore", - "lint:sol:solhint:fix": "yarn lint:sol:solhint --fix", "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:coverage": "hardhat coverage --testfiles test", "test:e2e": "npm run compile && ava -T 1000000 -v", @@ -60,9 +62,6 @@ "aragon:start": "node scripts/start-aragon.js", "lido:start": "hardhat node& yarn deploy:all && yarn lido:apps& hardhat run --no-compile scripts/start-aragon.js" }, - "author": "Lido ", - "homepage": "https://lido.fi/", - "license": "GPL-3.0", "devDependencies": { "@aragon/apps-agent": "^2.0.0", "@aragon/apps-finance": "^3.0.0", @@ -87,6 +86,7 @@ "babel-plugin-istanbul": "^6.0.0", "chai": "^4.2.0", "dotenv": "^8.2.0", + "electron": "^22.0.0", "eslint": "^7.10.0", "eslint-config-prettier": "^6.12.0", "eslint-config-standard": "^14.1.1", @@ -101,14 +101,15 @@ "hardhat": "2.9.9", "hardhat-contract-sizer": "^2.5.0", "hardhat-gas-reporter": "1.0.8", - "husky": "^4.3.0", + "husky": "^8.0.2", "ipfs-http-client": "^55.0.0", "lerna": "^3.22.1", "lint-staged": ">=10", "prettier": "^2.1.2", - "solhint": "^3.2.2", + "prettier-plugin-solidity": "^1.1.0", + "solhint": "^3.3.7", + "solhint-plugin-lido": "^0.0.4", "solidity-coverage": "^0.7.18", - "solium": "^1.2.5", "truffle": "^5.1.43", "truffle-extract": "^1.2.1", "truffle-flattener": "^1.5.0", @@ -123,20 +124,11 @@ "@truffle/contract": "^4.2.22", "concurrently": "^6.4.0", "ethereumjs-util": "^7.0.8", - "ethers": "^5.0.19", + "ethers": "^5.1.4", "node-gyp": "^8.4.1", "openzeppelin-solidity": "2.0.0", "patch-package": "^6.4.7", - "solhint-plugin-lido": "^0.0.4", "solidity-bytes-utils": "0.0.6", "yargs": "^16.0.3" - }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, - "lint-staged": { - "*.{js,jsx}": "yarn lint:js:fix" } } diff --git a/scripts/testnets/deploy-ossifiable-proxy.js b/scripts/testnets/deploy-ossifiable-proxy.js new file mode 100644 index 000000000..fa5d46a0e --- /dev/null +++ b/scripts/testnets/deploy-ossifiable-proxy.js @@ -0,0 +1,29 @@ +const runOrWrapScript = require('../helpers/run-or-wrap-script') +const { log, logSplitter, logWideSplitter, yl, gr } = require('../helpers/log') +const hre = require("hardhat") +const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') + +const DEPLOYER = process.env.DEPLOYER || '' + +async function deployOssifiableProxy({ web3, artifacts }) { + const netId = await web3.eth.net.getId() + + logWideSplitter() + log(`Network ID:`, yl(netId)) + log(`DEPLOYER`, yl(DEPLOYER)) + + const implementation = '0x53088a55756fD8abe32E308a1d6f7AEdfa48a886' + + let OssifiableProxy = await hre.ethers.getContractFactory("OssifiableProxy") + + let proxy = await OssifiableProxy.deploy( + implementation, + DEPLOYER, + [], + { gasLimit: 8000000} + ) + console.log(proxy.address) + +} + +module.exports = runOrWrapScript(deployOssifiableProxy, module) diff --git a/scripts/testnets/goerli-deploy-new-lido-oracle.js b/scripts/testnets/goerli-deploy-new-lido-oracle.js new file mode 100644 index 000000000..2060a5793 --- /dev/null +++ b/scripts/testnets/goerli-deploy-new-lido-oracle.js @@ -0,0 +1,68 @@ +const runOrWrapScript = require('../helpers/run-or-wrap-script') +const { log, logSplitter, logWideSplitter, yl, gr } = require('../helpers/log') +const { saveDeployTx } = require('../helpers/deploy') +const { readNetworkState, assertRequiredNetworkState, persistNetworkState } = require('../helpers/persisted-network-state') +const hre = require("hardhat") + +const { APP_NAMES } = require('../multisig/constants') + +const DEPLOYER = process.env.DEPLOYER || '' +const REQUIRED_NET_STATE = ['daoInitialSettings', `app:${APP_NAMES.LIDO}`] + +async function deployLidoOracleNew({ web3, artifacts }) { + const netId = await web3.eth.net.getId() + + logWideSplitter() + log(`Network ID:`, yl(netId)) + + const state = readNetworkState(network.name, netId) + assertRequiredNetworkState(state, REQUIRED_NET_STATE) + const lidoAddress = state[`app:${APP_NAMES.LIDO}`].proxyAddress + const oldOracleAddress = state[`app:${APP_NAMES.ORACLE}`].proxyAddress + log(`Using Lido contract address:`, yl(lidoAddress)) + + const lido = await artifacts.require('Lido').at(lidoAddress) + const treasuryAddr = await lido.getTreasury() + + log(`Using Lido Treasury contract address:`, yl(treasuryAddr)) + logSplitter() + + let LidoOracleNew = await hre.ethers.getContractFactory("LidoOracleNew") + + let oracle = await LidoOracleNew.deploy() + console.log(oracle.address) + + const oracleAdmin = DEPLOYER + const epochsPerFrame = 10 + const slotsPerEpoch = 32 + const secondsPerSlot = 12 + const genesisTime = 1616508000 + const allowedBeaconBalanceAnnualRelativeIncrease = 3000 + const allowedBeaconBalanceRelativeDecrease = 5000 + + console.log('LidoOracleNew initialize parameters:') + console.log([ + oracleAdmin.toString(), + lidoAddress.toString(), + epochsPerFrame.toString(), + slotsPerEpoch.toString(), + secondsPerSlot.toString(), + genesisTime.toString(), + allowedBeaconBalanceAnnualRelativeIncrease.toString(), + allowedBeaconBalanceRelativeDecrease.toString(), + ]) + + // await oracle.initialize( + // oracleAdmin.toString(), + // lidoAddress.toString(), + // epochsPerFrame.toString(), + // slotsPerEpoch.toString(), + // secondsPerSlot.toString(), + // genesisTime.toString(), + // allowedBeaconBalanceAnnualRelativeIncrease.toString(), + // allowedBeaconBalanceRelativeDecrease.toString() + // ) + +} + +module.exports = runOrWrapScript(deployLidoOracleNew, module) diff --git a/scripts/testnets/goerli-deploy-validator-exit-bus.js b/scripts/testnets/goerli-deploy-validator-exit-bus.js new file mode 100644 index 000000000..32be86992 --- /dev/null +++ b/scripts/testnets/goerli-deploy-validator-exit-bus.js @@ -0,0 +1,55 @@ +const runOrWrapScript = require('../helpers/run-or-wrap-script') +const { log, logSplitter, logWideSplitter, yl, gr } = require('../helpers/log') +const hre = require("hardhat") +const { bn } = require('@aragon/contract-helpers-test') + +const DEPLOYER = process.env.DEPLOYER || '' + +const blockDurationSeconds = 12 +const secondsInDay = 24 * 60 * 60 +const blocksInDay = secondsInDay / blockDurationSeconds + + +async function deployValidatorExitBus({ web3, artifacts }) { + const netId = await web3.eth.net.getId() + + logWideSplitter() + log(`Network ID:`, yl(netId)) + + let ValidatorExitBus = await hre.ethers.getContractFactory("ValidatorExitBus") + let bus = await ValidatorExitBus.deploy() + console.log(bus.address) + + const admin = DEPLOYER + const maxRequestsPerDayE18 = bn(5400).mul(bn(10).pow(bn(18))) + const numRequestsLimitIncreasePerBlockE18 = maxRequestsPerDayE18.div(bn(blocksInDay)) + const epochsPerFrame = 10 + const slotsPerEpoch = 32 + const secondsPerSlot = 12 + const genesisTime = 1616508000 + console.log([ + admin.toString(), + maxRequestsPerDayE18.toString(), + numRequestsLimitIncreasePerBlockE18.toString(), + epochsPerFrame.toString(), + slotsPerEpoch.toString(), + secondsPerSlot.toString(), + genesisTime.toString(), + ]) + + // await bus.initialize( + // admin, + // maxRequestsPerDayE18, + // numRequestsLimitIncreasePerBlockE18, + // epochsPerFrame, + // slotsPerEpoch, + // secondsPerSlot, + // genesisTime, + // { from: DEPLOYER } + // ) + + log('Initialized.') + +} + +module.exports = runOrWrapScript(deployValidatorExitBus, module) diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 643b43df9..1006a2d77 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -1,23 +1,24 @@ const { hash } = require('eth-ens-namehash') const { assert } = require('chai') -const { newDao, newApp } = require('./helpers/dao') +const { artifacts } = require('hardhat') + +const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') const { getInstalledApp } = require('@aragon/contract-helpers-test/src/aragon-os') const { assertBn, assertRevert, assertEvent } = require('@aragon/contract-helpers-test/src/asserts') -const { ZERO_ADDRESS, bn, getEventAt } = require('@aragon/contract-helpers-test') -const { BN } = require('bn.js') -const { formatEther } = require('ethers/lib/utils') -const { getEthBalance, formatStEth: formamtStEth, formatBN } = require('../helpers/utils') -const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') +const { newDao, newApp } = require('./helpers/dao') +const { pad, hexConcat, ETH, tokens, div15, assertNoEvent, StETH } = require('../helpers/utils') -const Lido = artifacts.require('LidoMock.sol') +const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') +const LidoMock = artifacts.require('LidoMock.sol') const ELRewardsVault = artifacts.require('LidoExecutionLayerRewardsVault.sol') const OracleMock = artifacts.require('OracleMock.sol') const DepositContractMock = artifacts.require('DepositContractMock.sol') const ERC20Mock = artifacts.require('ERC20Mock.sol') -const ERC721Mock = artifacts.require('ERC721Mock.sol') -const VaultMock = artifacts.require('AragonVaultMock.sol') +const VaultMock = artifacts.require('VaultMock.sol') +const AragonVaultMock = artifacts.require('AragonVaultMock.sol') const RewardEmulatorMock = artifacts.require('RewardEmulatorMock.sol') +const WithdrawalVault = artifacts.require('WithdrawalVault.sol') const ADDRESS_1 = '0x0000000000000000000000000000000000000001' const ADDRESS_2 = '0x0000000000000000000000000000000000000002' @@ -27,41 +28,16 @@ const ADDRESS_4 = '0x0000000000000000000000000000000000000004' const UNLIMITED = 1000000000 const TOTAL_BASIS_POINTS = 10000 -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 assertNoEvent = (receipt, eventName, msg) => { - const event = getEventAt(receipt, eventName) - assert.equal(event, undefined, msg) -} - -// Divides a BN by 1e15 -const div15 = (bn) => bn.div(new BN('1000000000000000')) - -const ETH = (value) => web3.utils.toWei(value + '', 'ether') -const STETH = ETH -const tokens = ETH - contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) => { - let appBase, nodeOperatorsRegistryBase, app, oracle, depositContract, operators - let treasuryAddr, insuranceAddr + let appBase, nodeOperatorsRegistryBase, app, oracle, depositContract, operators, treasury + let treasuryAddr let dao, acl - let elRewardsVault, rewarder + let elRewardsVault before('deploy base app', async () => { // Deploy the app's base contract. - appBase = await Lido.new() + treasury = await VaultMock.new() + appBase = await LidoMock.new() oracle = await OracleMock.new() yetAnotherOracle = await OracleMock.new() depositContract = await DepositContractMock.new() @@ -74,7 +50,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) // Instantiate a proxy for the app, using the base contract as its logic implementation. let proxyAddress = await newApp(dao, 'lido', appBase.address, appManager) - app = await Lido.at(proxyAddress) + app = await LidoMock.at(proxyAddress) // NodeOperatorsRegistry proxyAddress = await newApp(dao, 'node-operators-registry', nodeOperatorsRegistryBase.address, appManager) @@ -88,7 +64,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await acl.createPermission(voting, app.address, await app.MANAGE_WITHDRAWAL_KEY(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.BURN_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.MANAGE_PROTOCOL_CONTRACTS_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.SET_EL_REWARDS_VAULT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE(), appManager, { from: appManager }) @@ -108,8 +83,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) await acl.createPermission(depositor, app.address, await app.DEPOSIT_ROLE(), appManager, { from: appManager }) + elRewardsVault = await ELRewardsVault.new(app.address, treasury.address) // Initialize the app's proxy. - await app.initialize(depositContract.address, oracle.address, operators.address) + await app.initialize(depositContract.address, oracle.address, operators.address, treasury.address, elRewardsVault.address, ZERO_ADDRESS) assert((await app.isStakingPaused()) === true) assert((await app.isStopped()) === true) @@ -118,21 +94,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assert((await app.isStopped()) === false) treasuryAddr = await app.getTreasury() - insuranceAddr = await app.getInsuranceFund() await oracle.setPool(app.address) await depositContract.reset() - - elRewardsVault = await ELRewardsVault.new(app.address, treasuryAddr) - rewarder = await RewardEmulatorMock.new(elRewardsVault.address) - await assertRevert(app.setELRewardsVault(elRewardsVault.address), 'APP_AUTH_FAILED') - let receipt = await app.setELRewardsVault(elRewardsVault.address, { from: voting }) - assertEvent(receipt, 'ELRewardsVaultSet', { expectedArgs: { executionLayerRewardsVault: elRewardsVault.address } }) - - const elRewardsWithdrawalLimitPoints = 3 - await assertRevert(app.setELRewardsWithdrawalLimit(elRewardsWithdrawalLimitPoints), 'APP_AUTH_FAILED') - receipt = await app.setELRewardsWithdrawalLimit(elRewardsWithdrawalLimitPoints, { from: voting }) - assertEvent(receipt, 'ELRewardsWithdrawalLimitSet', { expectedArgs: { limitPoints: elRewardsWithdrawalLimitPoints } }) }) const checkStat = async ({ depositedValidators, beaconValidators, beaconBalance }) => { @@ -143,10 +107,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) } // Assert reward distribution. The values must be divided by 1e15. - const checkRewards = async ({ treasury, insurance, operator }) => { - const [treasury_b, insurance_b, operators_b, a1, a2, a3, a4] = await Promise.all([ + const checkRewards = async ({ treasury, operator }) => { + const [treasury_b, operators_b, a1, a2, a3, a4] = await Promise.all([ app.balanceOf(treasuryAddr), - app.balanceOf(insuranceAddr), app.balanceOf(operators.address), app.balanceOf(ADDRESS_1), app.balanceOf(ADDRESS_2), @@ -155,144 +118,117 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) ]) assertBn(div15(treasury_b), treasury, 'treasury token balance check') - assertBn(div15(insurance_b), insurance, 'insurance fund token balance check') assertBn(div15(operators_b.add(a1).add(a2).add(a3).add(a4)), operator, 'node operators token balance check') } - async function getStEthBalance(address) { - return formamtStEth(await app.balanceOf(address)) - } + context('EL Rewards', async () => { + let rewarder - 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 + beforeEach('set up rewarder and limits', async () => { + rewarder = await RewardEmulatorMock.new(elRewardsVault.address) + + const elRewardsWithdrawalLimitPoints = 3 + await assertRevert(app.setELRewardsWithdrawalLimit(elRewardsWithdrawalLimitPoints), 'APP_AUTH_FAILED') + receipt = await app.setELRewardsWithdrawalLimit(elRewardsWithdrawalLimitPoints, { from: voting }) + assertEvent(receipt, 'ELRewardsWithdrawalLimitSet', { expectedArgs: { limitPoints: elRewardsWithdrawalLimitPoints } }) }) - } - const logBalances = async () => { - const user2stEthBalance = await getStEthBalance(user2) - const treasuryStEthBalance = await getStEthBalance(treasuryAddr) - const insuranceStEthBalance = await getStEthBalance(insuranceAddr) - console.log({ user2stEthBalance, treasuryStEthBalance, insuranceStEthBalance }) - } + const setupNodeOperatorsForELRewardsVaultTests = async (userAddress, initialDepositAmount) => { + await app.setFee(1000, { from: voting }) // 10% - const logAll = async () => { - await logLidoState() - await logBalances() - console.log() - } + await web3.eth.sendTransaction({ to: app.address, from: userAddress, value: initialDepositAmount }) - const setupNodeOperatorsForELRewardsVaultTests = async (userAddress, initialDepositAmount) => { - await app.setFee(1000, { from: voting }) // 10% + const withdrawal = await WithdrawalVault.new(app.address, treasury.address) + await app.setWithdrawalCredentials(hexConcat('0x01', pad(withdrawal.address, 31)), { from: voting }) - await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) - await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) + await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) + await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) - await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) - await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) + await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) + await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) - await app.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) - await operators.addSigningKeys(0, 1, pad('0x010203', 48), pad('0x01', 96), { from: voting }) - await operators.addSigningKeys( - 0, - 3, - hexConcat(pad('0x010204', 48), pad('0x010205', 48), pad('0x010206', 48)), - hexConcat(pad('0x01', 96), pad('0x01', 96), pad('0x01', 96)), - { from: voting } - ) + await operators.addSigningKeys(0, 1, pad('0x010203', 48), pad('0x01', 96), { from: voting }) + await operators.addSigningKeys( + 0, + 3, + hexConcat(pad('0x010204', 48), pad('0x010205', 48), pad('0x010206', 48)), + hexConcat(pad('0x01', 96), pad('0x01', 96), pad('0x01', 96)), + { from: voting } + ) - await web3.eth.sendTransaction({ to: app.address, from: userAddress, value: initialDepositAmount }) - await app.methods['depositBufferedEther()']({ from: depositor }) - } + await app.methods['depositBufferedEther()']({ from: depositor }) + } - it('Execution layer rewards distribution works when zero rewards reported', async () => { - const depositAmount = 32 - const elRewards = depositAmount / TOTAL_BASIS_POINTS - const beaconRewards = 0 + it('Execution layer rewards distribution works when zero rewards reported', async () => { + const depositAmount = 32 + const elRewards = depositAmount / TOTAL_BASIS_POINTS + const beaconRewards = 0 - await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(depositAmount)) - await oracle.reportBeacon(100, 1, ETH(depositAmount)) + await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(depositAmount)) + await oracle.reportBeacon(100, 1, ETH(depositAmount)) - await rewarder.reward({ from: user1, value: ETH(elRewards) }) - await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) + await rewarder.reward({ from: user1, value: ETH(elRewards) }) + await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) - assertBn(await app.getTotalPooledEther(), ETH(depositAmount + elRewards + beaconRewards)) - assertBn(await app.getBufferedEther(), ETH(elRewards)) - assertBn(await app.balanceOf(user2), STETH(depositAmount + elRewards)) - assertBn(await app.getTotalELRewardsCollected(), ETH(elRewards)) - }) + assertBn(await app.getTotalPooledEther(), ETH(depositAmount + elRewards + beaconRewards)) + assertBn(await app.getBufferedEther(), ETH(elRewards)) + assertBn(await app.balanceOf(user2), StETH(depositAmount + elRewards)) + assertBn(await app.getTotalELRewardsCollected(), ETH(elRewards)) + }) - 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 rewards reported', async () => { + const depositAmount = 32 + const elRewards = depositAmount / TOTAL_BASIS_POINTS + const beaconRewards = -2 - await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(depositAmount)) - await oracle.reportBeacon(100, 1, ETH(depositAmount)) + await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(depositAmount)) + await oracle.reportBeacon(100, 1, ETH(depositAmount)) - await rewarder.reward({ from: user1, value: ETH(elRewards) }) - await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) + await rewarder.reward({ from: user1, value: ETH(elRewards) }) + await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) - assertBn(await app.getTotalPooledEther(), ETH(depositAmount + elRewards + beaconRewards)) - assertBn(await app.getBufferedEther(), ETH(elRewards)) - assertBn(await app.balanceOf(user2), STETH(depositAmount + elRewards + beaconRewards)) - assertBn(await app.getTotalELRewardsCollected(), ETH(elRewards)) - }) + assertBn(await app.getTotalPooledEther(), ETH(depositAmount + elRewards + beaconRewards)) + assertBn(await app.getBufferedEther(), ETH(elRewards)) + assertBn(await app.balanceOf(user2), StETH(depositAmount + elRewards + beaconRewards)) + assertBn(await app.getTotalELRewardsCollected(), ETH(elRewards)) + }) - 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 rewards reported', async () => { + const depositAmount = 32 + const elRewards = depositAmount / TOTAL_BASIS_POINTS + const beaconRewards = 3 - await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(depositAmount)) - await oracle.reportBeacon(100, 1, ETH(depositAmount)) + await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(depositAmount)) + await oracle.reportBeacon(100, 1, ETH(depositAmount)) - await rewarder.reward({ from: user1, value: ETH(elRewards) }) - await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) + await rewarder.reward({ from: user1, value: ETH(elRewards) }) + await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) - const protocolFeePoints = await app.getFee() - const shareOfRewardsForStakers = (TOTAL_BASIS_POINTS - protocolFeePoints) / TOTAL_BASIS_POINTS - assertBn(await app.getTotalPooledEther(), ETH(depositAmount + elRewards + beaconRewards)) - assertBn(await app.getBufferedEther(), ETH(elRewards)) - assertBn(await app.balanceOf(user2), STETH(depositAmount + shareOfRewardsForStakers * (elRewards + beaconRewards))) - assertBn(await app.getTotalELRewardsCollected(), ETH(elRewards)) - }) + const protocolFeePoints = await app.getFee() + const shareOfRewardsForStakers = (TOTAL_BASIS_POINTS - protocolFeePoints) / TOTAL_BASIS_POINTS + assertBn(await app.getTotalPooledEther(), ETH(depositAmount + elRewards + beaconRewards)) + assertBn(await app.getBufferedEther(), ETH(elRewards)) + assertBn(await app.balanceOf(user2), StETH(depositAmount + shareOfRewardsForStakers * (elRewards + beaconRewards))) + assertBn(await app.getTotalELRewardsCollected(), ETH(elRewards)) + }) - it('Attempt to set invalid execution layer rewards withdrawal limit', async () => { - const initialValue = await app.getELRewardsWithdrawalLimit() + it('Attempt to set invalid execution layer rewards withdrawal limit', async () => { + const initialValue = await app.getELRewardsWithdrawalLimit() - assertEvent(await app.setELRewardsWithdrawalLimit(1, { from: voting }), 'ELRewardsWithdrawalLimitSet', { - expectedArgs: { limitPoints: 1 } - }) + assertEvent(await app.setELRewardsWithdrawalLimit(1, { from: voting }), 'ELRewardsWithdrawalLimitSet', { + expectedArgs: { limitPoints: 1 } + }) - await assertNoEvent(app.setELRewardsWithdrawalLimit(1, { from: voting }), 'ELRewardsWithdrawalLimitSet') + await assertNoEvent(app.setELRewardsWithdrawalLimit(1, { from: voting }), 'ELRewardsWithdrawalLimitSet') - await app.setELRewardsWithdrawalLimit(10000, { from: voting }) - await assertRevert(app.setELRewardsWithdrawalLimit(10001, { from: voting }), 'VALUE_OVER_100_PERCENT') + await app.setELRewardsWithdrawalLimit(10000, { from: voting }) + await assertRevert(app.setELRewardsWithdrawalLimit(10001, { from: voting }), 'VALUE_OVER_100_PERCENT') - await app.setELRewardsWithdrawalLimit(initialValue, { from: voting }) + await app.setELRewardsWithdrawalLimit(initialValue, { from: voting }) - // unable to receive execution layer rewards from arbitrary account - assertRevert(app.receiveELRewards({ from: user1, value: ETH(1) })) + // unable to receive execution layer rewards from arbitrary account + assertRevert(app.receiveELRewards({ from: user1, value: ETH(1) })) + }) }) it('setFee works', async () => { @@ -305,18 +241,17 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it('setFeeDistribution works', async () => { - await app.setFeeDistribution(3000, 2000, 5000, { from: voting }) - await assertRevert(app.setFeeDistribution(3000, 2000, 5000, { from: user1 }), 'APP_AUTH_FAILED') - await assertRevert(app.setFeeDistribution(3000, 2000, 5000, { from: nobody }), 'APP_AUTH_FAILED') + await app.setFeeDistribution(3000, 7000, { from: voting }) + await assertRevert(app.setFeeDistribution(3000, 7000, { from: user1 }), 'APP_AUTH_FAILED') + await assertRevert(app.setFeeDistribution(3000, 7000, { from: nobody }), 'APP_AUTH_FAILED') - await assertRevert(app.setFeeDistribution(3000, 2000, 5001, { from: voting }), 'FEES_DONT_ADD_UP') - await assertRevert(app.setFeeDistribution(3000, 2000 - 1, 5000, { from: voting }), 'FEES_DONT_ADD_UP') - await assertRevert(app.setFeeDistribution(0, 0, 15000, { from: voting }), 'FEES_DONT_ADD_UP') + await assertRevert(app.setFeeDistribution(3000, 7001, { from: voting }), 'FEES_DONT_ADD_UP') + await assertRevert(app.setFeeDistribution(3000 - 1, 7000, { from: voting }), 'FEES_DONT_ADD_UP') + await assertRevert(app.setFeeDistribution(0, 15000, { from: voting }), 'FEES_DONT_ADD_UP') const distribution = await app.getFeeDistribution({ from: nobody }) assertBn(distribution.treasuryFeeBasisPoints, 3000) - assertBn(distribution.insuranceFeeBasisPoints, 2000) - assertBn(distribution.operatorsFeeBasisPoints, 5000) + assertBn(distribution.operatorsFeeBasisPoints, 7000) }) it('setWithdrawalCredentials works', async () => { @@ -327,8 +262,8 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it('setOracle works', async () => { - await assertRevert(app.setProtocolContracts(ZERO_ADDRESS, user2, user3, { from: voting }), 'ORACLE_ZERO_ADDRESS') - const receipt = await app.setProtocolContracts(yetAnotherOracle.address, oracle.address, oracle.address, { from: voting }) + await assertRevert(app.setProtocolContracts(ZERO_ADDRESS, user2, user3, ZERO_ADDRESS, { from: voting }), 'ORACLE_ZERO_ADDRESS') + const receipt = await app.setProtocolContracts(yetAnotherOracle.address, oracle.address, oracle.address, ZERO_ADDRESS, { from: voting }) assertEvent(receipt, 'ProtocolContactsSet', { expectedArgs: { oracle: yetAnotherOracle.address } }) assert.equal(await app.getOracle(), yetAnotherOracle.address) }) @@ -841,13 +776,17 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it('handleOracleReport works', async () => { + await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(34) }) + await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) - await app.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) + const withdrawal = await WithdrawalVault.new(app.address, treasury.address) + await app.setWithdrawalCredentials(hexConcat('0x01', pad(withdrawal.address, 31)), { from: voting }) + await operators.addSigningKeys(0, 1, pad('0x010203', 48), pad('0x01', 96), { from: voting }) await operators.addSigningKeys( 0, @@ -857,16 +796,15 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) { from: voting } ) - await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(34) }) await app.methods['depositBufferedEther()']({ from: depositor }) await checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) - await assertRevert(app.handleOracleReport(1, ETH(30), { from: appManager }), 'APP_AUTH_FAILED') + await assertRevert(app.handleOracleReport(1, ETH(30), 0, 0, [], [], { from: appManager }), 'APP_AUTH_FAILED') await oracle.reportBeacon(100, 1, ETH(30)) await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(30) }) - await assertRevert(app.handleOracleReport(1, ETH(29), { from: nobody }), 'APP_AUTH_FAILED') + await assertRevert(app.handleOracleReport(1, ETH(29), 0, 0, [], [], { from: nobody }), 'APP_AUTH_FAILED') await oracle.reportBeacon(50, 1, ETH(100)) // stale data await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(100) }) @@ -876,13 +814,17 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it('oracle data affects deposits', async () => { + await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(34) }) + await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) - await app.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) + const withdrawal = await WithdrawalVault.new(app.address, treasury.address) + await app.setWithdrawalCredentials(hexConcat('0x01', pad(withdrawal.address, 31)), { from: voting }) + await operators.addSigningKeys(0, 1, pad('0x010203', 48), pad('0x01', 96), { from: voting }) await operators.addSigningKeys( 0, @@ -892,9 +834,8 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) { from: voting } ) await app.setFee(5000, { from: voting }) - await app.setFeeDistribution(3000, 2000, 5000, { from: voting }) + await app.setFeeDistribution(3000, 7000, { from: voting }) - await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(34) }) await app.methods['depositBufferedEther()']({ from: depositor }) await checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await depositContract.totalCalls(), 1) @@ -930,20 +871,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(await app.getTotalPooledEther(), ETH(52)) assertBn(await app.getBufferedEther(), ETH(4)) assertBn(await app.totalSupply(), tokens(52)) - /* - - // 2nd deposit, ratio is 2 - await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(2) }) - await app.methods['depositBufferedEther()']({ from: depositor }) - - await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(72)}) - assertBn(await depositContract.totalCalls(), 1) - assertBn(await app.getTotalPooledEther(), ETH(78)) - assertBn(await app.getBufferedEther(), ETH(6)) - assertBn(await app.balanceOf(user1), tokens(8)) - assertBn(await app.balanceOf(user3), tokens(2)) - assertBn(await app.totalSupply(), tokens(78)) -*/ }) it('can stop and resume', async () => { @@ -987,13 +914,17 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it('rewards distribution works in a simple case', async () => { + await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(34) }) + await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) - await app.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) + const withdrawal = await WithdrawalVault.new(app.address, treasury.address) + await app.setWithdrawalCredentials(hexConcat('0x01', pad(withdrawal.address, 31)), { from: voting }) + await operators.addSigningKeys(0, 1, pad('0x010203', 48), pad('0x01', 96), { from: voting }) await operators.addSigningKeys( 0, @@ -1004,25 +935,28 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) ) await app.setFee(5000, { from: voting }) - await app.setFeeDistribution(3000, 2000, 5000, { from: voting }) + await app.setFeeDistribution(3000, 7000, { from: voting }) - await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(34) }) await app.methods['depositBufferedEther()']({ from: depositor }) await oracle.reportBeacon(300, 1, ETH(36)) await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(36) }) assertBn(await app.totalSupply(), tokens(38)) // remote + buffered - await checkRewards({ treasury: 600, insurance: 399, operator: 999 }) + await checkRewards({ treasury: 600, operator: 1399 }) }) it('rewards distribution works', async () => { + await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(34) }) + await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) - await app.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) + const withdrawal = await WithdrawalVault.new(app.address, treasury.address) + await app.setWithdrawalCredentials(hexConcat('0x01', pad(withdrawal.address, 31)), { from: voting }) + await operators.addSigningKeys(0, 1, pad('0x010203', 48), pad('0x01', 96), { from: voting }) await operators.addSigningKeys( 0, @@ -1033,9 +967,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) ) await app.setFee(5000, { from: voting }) - await app.setFeeDistribution(3000, 2000, 5000, { from: voting }) - - await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(34) }) + await app.setFeeDistribution(3000, 7000, { from: voting }) await app.methods['depositBufferedEther()']({ from: depositor }) // some slashing occurred await oracle.reportBeacon(100, 1, ETH(30)) @@ -1043,36 +975,38 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(30) }) // ToDo check buffer=2 assertBn(await app.totalSupply(), tokens(32)) // 30 remote (slashed) + 2 buffered = 32 - await checkRewards({ treasury: 0, insurance: 0, operator: 0 }) + await checkRewards({ treasury: 0, operator: 0 }) // rewarded 200 Ether (was 30, became 230) await oracle.reportBeacon(200, 1, ETH(130)) await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(130) }) // Todo check reward effects - // await checkRewards({ treasury: 0, insurance: 0, operator: 0 }) + // await checkRewards({ treasury: 0, operator: 0 }) await oracle.reportBeacon(300, 1, ETH(2230)) await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(2230) }) assertBn(await app.totalSupply(), tokens(2232)) // Todo check reward effects - // await checkRewards({ treasury: tokens(33), insurance: tokens(22), operator: tokens(55) }) + // await checkRewards({ treasury: tokens(33), operator: tokens(55) }) }) it('deposits accounted properly during rewards distribution', async () => { + await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(32) }) + await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) - await app.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) + const withdrawal = await WithdrawalVault.new(app.address, treasury.address) + await app.setWithdrawalCredentials(hexConcat('0x01', pad(withdrawal.address, 31)), { from: voting }) + await operators.addSigningKeys(0, 1, pad('0x010203', 48), pad('0x01', 96), { from: voting }) await app.setFee(5000, { from: voting }) - await app.setFeeDistribution(3000, 2000, 5000, { from: voting }) + await app.setFeeDistribution(3000, 7000, { from: voting }) - // Only 32 ETH deposited - await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(32) }) await app.methods['depositBufferedEther()']({ from: depositor }) await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(32) }) await app.methods['depositBufferedEther()']({ from: depositor }) @@ -1081,7 +1015,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await oracle.reportBeacon(300, 1, ETH(36)) await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(36) }) assertBn(await app.totalSupply(), tokens(68)) - await checkRewards({ treasury: 600, insurance: 399, operator: 999 }) + await checkRewards({ treasury: 599, operator: 1399 }) }) it('Node Operators filtering during deposit works when doing a huge deposit', async () => { @@ -1110,7 +1044,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await operators.setNodeOperatorStakingLimit(3, UNLIMITED, { from: voting }) await app.setFee(5000, { from: voting }) - await app.setFeeDistribution(3000, 2000, 5000, { from: voting }) + await app.setFeeDistribution(3000, 7000, { from: voting }) // Deposit huge chunk await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(32 * 3 + 50) }) @@ -1232,7 +1166,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await operators.setNodeOperatorStakingLimit(3, UNLIMITED, { from: voting }) await app.setFee(5000, { from: voting }) - await app.setFeeDistribution(3000, 2000, 5000, { from: voting }) + await app.setFeeDistribution(3000, 7000, { from: voting }) // Small deposits for (let i = 0; i < 14; i++) await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(10) }) @@ -1357,7 +1291,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await operators.setNodeOperatorStakingLimit(3, UNLIMITED, { from: voting }) await app.setFee(5000, { from: voting }) - await app.setFeeDistribution(3000, 2000, 5000, { from: voting }) + await app.setFeeDistribution(3000, 7000, { from: voting }) // #1 and #0 get the funds await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(64) }) @@ -1434,78 +1368,60 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it(`treasury can't be set by an arbitrary address`, async () => { - await assertRevert(app.setProtocolContracts(await app.getOracle(), user1, await app.getInsuranceFund(), { from: nobody })) - await assertRevert(app.setProtocolContracts(await app.getOracle(), user1, await app.getInsuranceFund(), { from: user1 })) + // TODO: restore the test when function `transferToVault` is restored + await assertRevert(app.setProtocolContracts(await app.getOracle(), user1, ZERO_ADDRESS, ZERO_ADDRESS, { from: nobody })) + await assertRevert(app.setProtocolContracts(await app.getOracle(), user1, ZERO_ADDRESS, ZERO_ADDRESS, { from: user1 })) }) it('voting can set treasury', async () => { - const receipt = await app.setProtocolContracts(await app.getOracle(), user1, await app.getInsuranceFund(), { from: voting }) + const receipt = await app.setProtocolContracts(await app.getOracle(), user1, ZERO_ADDRESS, ZERO_ADDRESS, { from: voting }) assertEvent(receipt, 'ProtocolContactsSet', { expectedArgs: { treasury: user1 } }) assert.equal(await app.getTreasury(), user1) }) it('reverts when treasury is zero address', async () => { + // TODO: restore the test when function `setProtocolContracts` is restored await assertRevert( - app.setProtocolContracts(await app.getOracle(), ZERO_ADDRESS, await app.getInsuranceFund(), { from: voting }), + app.setProtocolContracts(await app.getOracle(), ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, { from: voting }), 'TREASURY_ZERO_ADDRESS' ) }) }) - context('insurance fund', () => { - it('insurance fund address has been set after init', async () => { - assert.notEqual(await app.getInsuranceFund(), ZERO_ADDRESS) - }) - - it(`insurance fund can't be set by an arbitrary address`, async () => { - await assertRevert(app.setProtocolContracts(await app.getOracle(), await app.getTreasury(), user1, { from: nobody })) - await assertRevert(app.setProtocolContracts(await app.getOracle(), await app.getTreasury(), user1, { from: user1 })) - }) - - it('voting can set insurance fund', async () => { - const receipt = await app.setProtocolContracts(await app.getOracle(), await app.getTreasury(), user1, { from: voting }) - assertEvent(receipt, 'ProtocolContactsSet', { expectedArgs: { insuranceFund: user1 } }) - assert.equal(await app.getInsuranceFund(), user1) - }) - - it('reverts when insurance fund is zero address', async () => { - await assertRevert( - app.setProtocolContracts(await app.getOracle(), await app.getTreasury(), ZERO_ADDRESS, { from: voting }), - 'INSURANCE_FUND_ZERO_ADDRESS' - ) - }) - }) - context('recovery vault', () => { beforeEach(async () => { await anyToken.mint(app.address, 100) }) - it('reverts when vault is not set', async () => { + it.skip('reverts when vault is not set', async () => { + // TODO: restore the test when function `setProtocolContracts` is restored await assertRevert(app.transferToVault(anyToken.address, { from: nobody }), 'RECOVER_VAULT_ZERO') }) - context('recovery works with vault mock deployed', () => { + context.skip('recovery works with vault mock deployed', () => { + // TODO: restore the test when function `transferToVault` is restored let vault beforeEach(async () => { // Create a new vault and set that vault as the default vault in the kernel const vaultId = hash('vault.aragonpm.test') - const vaultBase = await VaultMock.new() + const vaultBase = await AragonVaultMock.new() const vaultReceipt = await dao.newAppInstance(vaultId, vaultBase.address, '0x', true) const vaultAddress = getInstalledApp(vaultReceipt) - vault = await VaultMock.at(vaultAddress) + vault = await AragonVaultMock.at(vaultAddress) await vault.initialize() await dao.setRecoveryVaultAppId(vaultId) }) - it('recovery with erc20 tokens works and emits event', async () => { + it.skip('recovery with erc20 tokens works and emits event', async () => { + // TODO: restore the test when function `setProtocolContracts` is restored const receipt = await app.transferToVault(anyToken.address, { from: nobody }) assertEvent(receipt, 'RecoverToVault', { expectedArgs: { vault: vault.address, token: anyToken.address, amount: 100 } }) }) - it('recovery with unaccounted ether works and emits event', async () => { + it.skip('recovery with unaccounted ether works and emits event', async () => { + // TODO: restore the test when function `setProtocolContracts` is restored await app.makeUnaccountedEther({ from: user1, value: ETH(10) }) const receipt = await app.transferToVault(ZERO_ADDRESS, { from: nobody }) assertEvent(receipt, 'RecoverToVault', { expectedArgs: { vault: vault.address, token: ZERO_ADDRESS, amount: ETH(10) } }) diff --git a/test/0.4.24/lidoHandleOracleReport.test.js b/test/0.4.24/lidoHandleOracleReport.test.js index 747dd30c9..f82db4e11 100644 --- a/test/0.4.24/lidoHandleOracleReport.test.js +++ b/test/0.4.24/lidoHandleOracleReport.test.js @@ -1,27 +1,25 @@ const { assert } = require('chai') const { newDao, newApp } = require('./helpers/dao') -const { assertBn, assertRevert, assertEvent } = require('@aragon/contract-helpers-test/src/asserts') +const { assertBn, assertRevert } = require('@aragon/contract-helpers-test/src/asserts') -const Lido = artifacts.require('LidoPushableMock.sol') +const LidoPushableMock = artifacts.require('LidoPushableMock.sol') const OracleMock = artifacts.require('OracleMock.sol') const ETH = (value) => web3.utils.toWei(value + '', 'ether') -contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, nobody]) => { +contract('Lido handleOracleReport', ([appManager, user1, user2]) => { let appBase, app, oracle before('deploy base app', async () => { - appBase = await Lido.new() + appBase = await LidoPushableMock.new() oracle = await OracleMock.new() }) beforeEach('deploy dao and app', async () => { - const { dao, acl } = await newDao(appManager) + const { dao } = await newDao(appManager) proxyAddress = await newApp(dao, 'lido', appBase.address, appManager) - app = await Lido.at(proxyAddress) - - // await acl.createPermission(voting, app.address, await app.PAUSE_ROLE(), appManager, { from: appManager }) + app = await LidoPushableMock.at(proxyAddress) await app.initialize(oracle.address) await oracle.setPool(app.address) @@ -145,12 +143,13 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n }) }) - context('with depositedVals=2, beaconVals=1, bcnBal=30, bufferedEth=3', async () => { + 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: user1, value: ETH(5) }) await app.setBeaconValidators(1) + await app.setTotalShares(ETH(67)) }) it('initial state before report', async () => { @@ -192,7 +191,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n assertBn(await app.getBufferedEther(), ETH(5)) assertBn(await app.getTotalPooledEther(), ETH(68)) assert.equal(await app.distributeFeeCalled(), true) - assertBn(await app.totalRewards(), ETH(1)) + assertBn(await app.totalRewards(), ETH(1)) // rounding error }) it('report BcnValidators:2 BcnBalance:63 = reward:1', async () => { @@ -201,7 +200,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n assertBn(await app.getBufferedEther(), ETH(5)) assertBn(await app.getTotalPooledEther(), ETH(68)) assert.equal(await app.distributeFeeCalled(), true) - assertBn(await app.totalRewards(), ETH(1)) + assertBn(await app.totalRewards(), ETH(1)) // rounding error }) it('report BcnValidators:3 = revert with REPORTED_MORE_DEPOSITED', async () => { diff --git a/test/0.4.24/lidooracle.test.js b/test/0.4.24/lidooracle.test.js index 1e7d7386b..74b14feb3 100644 --- a/test/0.4.24/lidooracle.test.js +++ b/test/0.4.24/lidooracle.test.js @@ -813,7 +813,7 @@ contract('LidoOracle', ([appManager, voting, user1, user2, user3, user4, user5, await app.reportBeacon(1, 65, 3, { from: user6 }) await assertExpectedEpochs(1, 1) - // decreasing quorum does not help because colflicting parts are equal + // decreasing quorum does not help because conflicting parts are equal await app.setQuorum(3, { from: voting }) await assertExpectedEpochs(1, 1) await app.setQuorum(1, { from: voting }) @@ -844,7 +844,7 @@ contract('LidoOracle', ([appManager, voting, user1, user2, user3, user4, user5, assertEvent(receipt, 'Completed', { expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1 } }) }) - it('only 1 report is enough in quorum loweres to 1', async () => { + it('only 1 report is enough in quorum lowers to 1', async () => { await app.reportBeacon(1, 32, 1, { from: user1 }) await assertExpectedEpochs(1, 1) diff --git a/test/0.8.9/lido-exec-layer-rewards-vault.js b/test/0.8.9/lido-exec-layer-rewards-vault.js index 746b903bd..986bcaff1 100644 --- a/test/0.8.9/lido-exec-layer-rewards-vault.js +++ b/test/0.8.9/lido-exec-layer-rewards-vault.js @@ -1,39 +1,32 @@ -const { assertBn, assertRevert, assertEvent, assertAmountOfEvents } = require('@aragon/contract-helpers-test/src/asserts') +const { assert } = require('chai') + +const { assertBn, assertRevert, assertEvent } = require('@aragon/contract-helpers-test/src/asserts') const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') const { newDao, newApp } = require('../0.4.24/helpers/dao') - -const { assert } = require('chai') +const { StETH, ETH } = require('../helpers/utils') const LidoELRewardsVault = artifacts.require('LidoExecutionLayerRewardsVault.sol') - const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') - const LidoMock = artifacts.require('LidoMock.sol') +const VaultMock = artifacts.require('VaultMock.sol') const LidoOracleMock = artifacts.require('OracleMock.sol') const DepositContractMock = artifacts.require('DepositContractMock.sol') const ERC20OZMock = artifacts.require('ERC20OZMock.sol') const ERC721OZMock = artifacts.require('ERC721OZMock.sol') -const ETH = (value) => web3.utils.toWei(value + '', 'ether') -// semantic aliases -const stETH = ETH -const stETHShares = ETH - -contract('LidoExecutionLayerRewardsVault', ([appManager, voting, deployer, depositor, anotherAccount, ...otherAccounts]) => { - let oracle, lido, elRewardsVault +contract('LidoExecutionLayerRewardsVault', ([appManager, voting, deployer, anotherAccount]) => { + let lido, elRewardsVault let treasuryAddr - let dao, acl, operators beforeEach('deploy lido with dao', async () => { + const treasury = await VaultMock.new() const lidoBase = await LidoMock.new({ from: deployer }) - oracle = await LidoOracleMock.new({ from: deployer }) + const oracle = await LidoOracleMock.new({ from: deployer }) const depositContract = await DepositContractMock.new({ from: deployer }) const nodeOperatorsRegistryBase = await NodeOperatorsRegistry.new({ from: deployer }) - const daoAclObj = await newDao(appManager) - dao = daoAclObj.dao - acl = daoAclObj.acl + const { dao, acl } = await newDao(appManager) // Instantiate a proxy for the app, using the base contract as its logic implementation. let proxyAddress = await newApp(dao, 'lido', lidoBase.address, appManager) @@ -42,20 +35,27 @@ contract('LidoExecutionLayerRewardsVault', ([appManager, voting, deployer, depos // NodeOperatorsRegistry proxyAddress = await newApp(dao, 'node-operators-registry', nodeOperatorsRegistryBase.address, appManager) - operators = await NodeOperatorsRegistry.at(proxyAddress) + const operators = await NodeOperatorsRegistry.at(proxyAddress) await operators.initialize(lido.address) // Init the BURN_ROLE role and assign in to voting await acl.createPermission(voting, lido.address, await lido.BURN_ROLE(), appManager, { from: appManager }) + elRewardsVault = await LidoELRewardsVault.new(lido.address, treasury.address, { from: deployer }) + // Initialize the app's proxy. - await lido.initialize(depositContract.address, oracle.address, operators.address) - treasuryAddr = await lido.getInsuranceFund() + await lido.initialize( + depositContract.address, + oracle.address, + operators.address, + treasury.address, + elRewardsVault.address, + ZERO_ADDRESS + ) + treasuryAddr = await lido.getTreasury() await oracle.setPool(lido.address) await depositContract.reset() - - elRewardsVault = await LidoELRewardsVault.new(lido.address, treasuryAddr, { from: deployer }) }) it('Addresses which are not Lido contract cannot withdraw from execution layer rewards vault', async () => { @@ -72,7 +72,6 @@ contract('LidoExecutionLayerRewardsVault', ([appManager, voting, deployer, depos }) it('Execution layer rewards vault refuses to receive Ether by transfers with call data', async () => { - const before = +(await web3.eth.getBalance(elRewardsVault.address)).toString() const amount = 0.02 await assertRevert( web3.eth.sendTransaction({ to: elRewardsVault.address, from: anotherAccount, value: ETH(amount), data: '0x12345678' }) @@ -120,16 +119,16 @@ contract('LidoExecutionLayerRewardsVault', ([appManager, voting, deployer, depos it(`can't recover stETH by recoverERC20`, async () => { // initial stETH balance is zero - assertBn(await lido.balanceOf(anotherAccount), stETH(0)) + assertBn(await lido.balanceOf(anotherAccount), StETH(0)) // submit 10 ETH to mint 10 stETH await web3.eth.sendTransaction({ from: anotherAccount, to: lido.address, value: ETH(10) }) // check 10 stETH minted on balance - assertBn(await lido.balanceOf(anotherAccount), stETH(10)) + assertBn(await lido.balanceOf(anotherAccount), StETH(10)) // transfer 5 stETH to the elRewardsVault account - await lido.transfer(elRewardsVault.address, stETH(5), { from: anotherAccount }) + await lido.transfer(elRewardsVault.address, StETH(5), { from: anotherAccount }) - assertBn(await lido.balanceOf(anotherAccount), stETH(5)) - assertBn(await lido.balanceOf(elRewardsVault.address), stETH(5)) + assertBn(await lido.balanceOf(anotherAccount), StETH(5)) + assertBn(await lido.balanceOf(elRewardsVault.address), StETH(5)) }) it(`recover some accidentally sent ERC20`, async () => { diff --git a/test/0.8.9/lidooraclenew.test.js b/test/0.8.9/lidooraclenew.test.js new file mode 100644 index 000000000..a8a2f6f2c --- /dev/null +++ b/test/0.8.9/lidooraclenew.test.js @@ -0,0 +1,1320 @@ +const { assert } = require('chai') +const { assertBn, assertRevert, assertEvent } = require('@aragon/contract-helpers-test/src/asserts') +const { toBN, assertRevertCustomError } = require('../helpers/utils') +const { ZERO_ADDRESS } = require('@aragon/contract-helpers-test') +const keccak256 = require('js-sha3').keccak_256 + +const LidoOracleNew = artifacts.require('LidoOracleNewMock.sol') +const Lido = artifacts.require('LidoMockForOracleNew.sol') +const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistryMockForLidoOracleNew') +const BeaconReportReceiver = artifacts.require('BeaconReportReceiverMock') +const BeaconReportReceiverWithoutERC165 = artifacts.require('BeaconReportReceiverMockWithoutERC165') + +const GENESIS_TIME = 1606824000 +const EPOCH_LENGTH = 32 * 12 +const DENOMINATION_OFFSET = 1e9 + +const ZERO_MEMBER_REPORT = { + stakingModules: [], + nodeOperatorsWithExitedValidators: [], + exitedValidatorsNumbers: [], + withdrawalVaultBalance: 0, + newDepositBufferWithdrawalsReserve: 0, + requestIdToFinalizeUpTo: [], + finalizationShareRates: [] +} + +const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000' +const MANAGE_MEMBERS_ROLE = '0x0f5709a131bd812d54bcbfe625c74b832e351421787d3b67d5015bdfc1658fbd' +const MANAGE_QUORUM_ROLE = '0x68f77d74579a6299ff72f8492235a983bb2d3dff83fe7b4c34c8da1127a1eb87' +const SET_BEACON_SPEC_ROLE = '0xf6d880c20d109428933defa2f109f143247bbe4c84784a6b140b33988b369b37' +const SET_REPORT_BOUNDARIES_ROLE = '0x391653625e4f1b50d601a46cb1d91cfe0d501de98c8e11a46cf55edf20942d7a' +const SET_BEACON_REPORT_RECEIVER_ROLE = '0xe976ee3edb892b8fc9edde1f74da6a8e094e84585a6ab054a2f1c630dba6ed94' + +function getAuthError(account, role) { + return `AccessControl: account ${account.toLowerCase()} is missing role ${role}` +} + +// initial pooled ether (it's required to smooth increase of balance +// if you jump from 30 to 60 in one epoch it's a huge annual relative jump over 9000% +// but if you jump from 1e12+30 to 1e12+60 then it's smooth small jump as in the real world. +const START_BALANCE = 1e12 + +contract('LidoOracleNew', ([voting, user1, user2, user3, user4, user5, user6, user7, nobody]) => { + let appLido, app, nodeOperatorsRegistry + + const assertExpectedEpochs = async (startEpoch, endEpoch) => { + assertBn(await app.getExpectedEpochId(), startEpoch) + assertBn(await app.getCurrentEpochId(), endEpoch) + } + + before('deploy base app', async () => { + // Deploy the app's base contract. + nodeOperatorsRegistry = await NodeOperatorsRegistry.new() + appLido = await Lido.new(nodeOperatorsRegistry.address) + }) + + beforeEach('deploy dao and app', async () => { + // Instantiate a proxy for the app, using the base contract as its logic implementation. + // TODO: use proxy + app = await LidoOracleNew.new({ from: voting }) + + // Initialize the app's proxy. + await app.setTime(GENESIS_TIME) + + assertBn(await app.getVersion(), 0) + await app.setVersion(1) + await assertRevertCustomError( + app.initialize(ZERO_ADDRESS, appLido.address, 1, 32, 12, GENESIS_TIME, 1000, 500), + 'CanInitializeOnlyOnZeroVersion' + ) + + await app.setVersion(0) + + // 1000 and 500 stand for 10% yearly increase, 5% moment decrease + await app.initialize(voting, appLido.address, 1, 32, 12, GENESIS_TIME, 1000, 500) + assertBn(await app.getVersion(), 1) + assertBn(await app.getRoleMemberCount(DEFAULT_ADMIN_ROLE), 1) + assert((await app.getRoleMember(DEFAULT_ADMIN_ROLE, 0)) === voting) + + // Set up the app's permissions. + await app.grantRole(await app.MANAGE_MEMBERS_ROLE(), voting, { from: voting }) + await app.grantRole(await app.MANAGE_QUORUM_ROLE(), voting, { from: voting }) + await app.grantRole(await app.SET_BEACON_SPEC_ROLE(), voting, { from: voting }) + await app.grantRole(await app.SET_REPORT_BOUNDARIES_ROLE(), voting, { from: voting }) + await app.grantRole(await app.SET_BEACON_REPORT_RECEIVER_ROLE(), voting, { from: voting }) + + assert((await app.MANAGE_MEMBERS_ROLE()) === MANAGE_MEMBERS_ROLE) + assert((await app.MANAGE_QUORUM_ROLE()) === MANAGE_QUORUM_ROLE) + assert((await app.SET_BEACON_SPEC_ROLE()) === SET_BEACON_SPEC_ROLE) + assert((await app.SET_REPORT_BOUNDARIES_ROLE()) === SET_REPORT_BOUNDARIES_ROLE) + assert((await app.SET_BEACON_REPORT_RECEIVER_ROLE()) === SET_BEACON_REPORT_RECEIVER_ROLE) + }) + + it('beaconSpec is correct', async () => { + const beaconSpec = await app.getBeaconSpec() + assertBn(beaconSpec.epochsPerFrame, 1) + assertBn(beaconSpec.slotsPerEpoch, 32) + assertBn(beaconSpec.secondsPerSlot, 12) + assertBn(beaconSpec.genesisTime, GENESIS_TIME) + }) + + it('setBeaconSpec works', async () => { + await assertRevertCustomError(app.setBeaconSpec(0, 1, 1, 1, { from: voting }), 'BadEpochsPerFrame') + await assertRevertCustomError(app.setBeaconSpec(1, 0, 1, 1, { from: voting }), 'BadSlotsPerEpoch') + await assertRevertCustomError(app.setBeaconSpec(1, 1, 0, 1, { from: voting }), 'BadSecondsPerSlot') + await assertRevertCustomError(app.setBeaconSpec(1, 1, 1, 0, { from: voting }), 'BadGenesisTime') + + const receipt = await app.setBeaconSpec(1, 1, 1, 1, { from: voting }) + assertEvent(receipt, 'BeaconSpecSet', { + expectedArgs: { + epochsPerFrame: 1, + slotsPerEpoch: 1, + secondsPerSlot: 1, + genesisTime: 1 + } + }) + const beaconSpec = await app.getBeaconSpec() + assertBn(beaconSpec.epochsPerFrame, 1) + assertBn(beaconSpec.slotsPerEpoch, 1) + assertBn(beaconSpec.secondsPerSlot, 1) + assertBn(beaconSpec.genesisTime, 1) + }) + + describe('Test utility functions:', function () { + this.timeout(60000) // addOracleMember edge-case is heavy on execution time + + beforeEach(async () => { + await app.setTime(GENESIS_TIME) + }) + + it('addOracleMember works', async () => { + await assertRevert(app.addOracleMember(user1, { from: user1 }), getAuthError(user1, MANAGE_MEMBERS_ROLE)) + await assertRevertCustomError( + app.addOracleMember('0x0000000000000000000000000000000000000000', { from: voting }), + 'ZeroMemberAddress' + ) + + await app.addOracleMember(user1, { from: voting }) + await assertRevert(app.addOracleMember(user2, { from: user2 }), getAuthError(user2, MANAGE_MEMBERS_ROLE)) + await assertRevert(app.addOracleMember(user3, { from: user2 }), getAuthError(user2, MANAGE_MEMBERS_ROLE)) + + await app.addOracleMember(user2, { from: voting }) + await app.addOracleMember(user3, { from: voting }) + + await assertRevertCustomError(app.addOracleMember(user1, { from: voting }), 'MemberExists') + await assertRevertCustomError(app.addOracleMember(user2, { from: voting }), 'MemberExists') + }) + + it('addOracleMember edge-case', async () => { + const promises = [] + const maxMembersCount = await app.MAX_MEMBERS() + for (let i = 0; i < maxMembersCount; ++i) { + const addr = '0x' + keccak256('member' + i).substring(0, 40) + promises.push(app.addOracleMember(addr, { from: voting })) + } + await Promise.all(promises) + + assertRevertCustomError(app.addOracleMember(user4, { from: voting }), 'TooManyMembers') + }) + + it('removeOracleMember works', async () => { + await app.addOracleMember(user1, { from: voting }) + + await assertRevert(app.removeOracleMember(user1, { from: user1 }), getAuthError(user1, MANAGE_MEMBERS_ROLE)) + await app.removeOracleMember(user1, { from: voting }) + assert.deepStrictEqual(await app.getOracleMembers(), []) + + await app.addOracleMember(user1, { from: voting }) + await app.addOracleMember(user2, { from: voting }) + await app.addOracleMember(user3, { from: voting }) + + await assertRevertCustomError(app.removeOracleMember(nobody, { from: voting }), 'MemberNotFound') + + await app.removeOracleMember(user1, { from: voting }) + await app.removeOracleMember(user2, { from: voting }) + + await assertRevert(app.removeOracleMember(user2, { from: user1 }), getAuthError(user1, MANAGE_MEMBERS_ROLE)) + assert.deepStrictEqual(await app.getOracleMembers(), [user3]) + }) + + it('updateQuorum works', async () => { + await app.addOracleMember(user1, { from: voting }) + await app.addOracleMember(user2, { from: voting }) + await app.addOracleMember(user3, { from: voting }) + + await assertRevert(app.updateQuorum(2, { from: user1 }), getAuthError(user1, MANAGE_QUORUM_ROLE)) + await assertRevertCustomError(app.updateQuorum(0, { from: voting }), 'QuorumWontBeMade') + await app.updateQuorum(4, { from: voting }) + assertBn(await app.getQuorum(), 4) + + await app.updateQuorum(3, { from: voting }) + assertBn(await app.getQuorum(), 3) + }) + + it('updateQuorum updates expectedEpochId and tries to push', async () => { + await app.addOracleMember(user1, { from: voting }) + await app.addOracleMember(user2, { from: voting }) + await app.addOracleMember(user3, { from: voting }) + + await app.updateQuorum(4, { from: voting }) + + await appLido.pretendTotalPooledEtherGweiForTest(32) + + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 31 }, + { from: user1 } + ) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user2 } + ) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user3 } + ) + await assertExpectedEpochs(1, 0) + + await app.updateQuorum(3, { from: voting }) + await assertExpectedEpochs(1, 0) + + const receipt = await app.updateQuorum(2, { from: voting }) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + await assertExpectedEpochs(2, 0) + }) + + it('getOracleMembers works', async () => { + await app.addOracleMember(user1, { from: voting }) + await app.addOracleMember(user2, { from: voting }) + await app.addOracleMember(user3, { from: voting }) + + assert.deepStrictEqual(await app.getOracleMembers(), [user1, user2, user3]) + + await app.removeOracleMember(user1, { from: voting }) + + assert.deepStrictEqual(await app.getOracleMembers(), [user3, user2]) + }) + + it('getCurrentEpochId works', async () => { + assertBn(await app.getCurrentEpochId(), 0) + await app.setTime(GENESIS_TIME + EPOCH_LENGTH - 1) + assertBn(await app.getCurrentEpochId(), 0) + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 123 + 1) + assertBn(await app.getCurrentEpochId(), 123) + }) + + it('getExpectedEpochId and getLastCompletedEpochId work', async () => { + assertBn(await app.getExpectedEpochId(), 1) + assertBn(await app.getLastCompletedEpochId(), 0) + + await app.setTime(GENESIS_TIME + EPOCH_LENGTH - 1) + assertBn(await app.getExpectedEpochId(), 1) + + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 123 + 1) + await app.updateQuorum(1, { from: voting }) + await app.addOracleMember(user1, { from: voting }) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 123, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user1 } + ) + + assertBn(await app.getExpectedEpochId(), 124) + assertBn(await app.getLastCompletedEpochId(), 123) + }) + + it('getCurrentFrame works', async () => { + await app.setBeaconSpec(10, 32, 12, GENESIS_TIME, { from: voting }) + + let result = await app.getCurrentFrame() + assertBn(result.frameEpochId, 0) + assertBn(result.frameStartTime, GENESIS_TIME) + assertBn(result.frameEndTime, GENESIS_TIME + EPOCH_LENGTH * 10 - 1) + + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 10 - 1) + result = await app.getCurrentFrame() + assertBn(result.frameEpochId, 0) + assertBn(result.frameStartTime, GENESIS_TIME) + assertBn(result.frameEndTime, GENESIS_TIME + EPOCH_LENGTH * 10 - 1) + + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 123) + result = await app.getCurrentFrame() + assertBn(result.frameEpochId, 120) + assertBn(result.frameStartTime, GENESIS_TIME + EPOCH_LENGTH * 120) + assertBn(result.frameEndTime, GENESIS_TIME + EPOCH_LENGTH * 130 - 1) + }) + }) + + describe('When there is single-member setup', function () { + describe('current epoch: 1', function () { + beforeEach(async () => { + await app.setTime(GENESIS_TIME) + await app.addOracleMember(user1, { from: voting }) + await appLido.pretendTotalPooledEtherGweiForTest(START_BALANCE) + }) + + it('if given old eth1 denominated balances just truncates them to 64 bits', async () => { + const BALANCE = toBN('183216444408705000000000') + const INT64_MASK = toBN('0xFFFFFFFFFFFFFFFF') + const BALANCE_TRUNCATED64_GWEI = BALANCE.and(INT64_MASK) + const BALANCE_TRUNCATED64_WEI = BALANCE_TRUNCATED64_GWEI.mul(toBN(DENOMINATION_OFFSET)) + await appLido.pretendTotalPooledEtherGweiForTest(BALANCE_TRUNCATED64_GWEI) + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 5692, beaconBalanceGwei: BALANCE.toString(10) }, + { from: user1 } + ) + assertEvent(receipt, 'CommitteeMemberReported', { + expectedArgs: { + epochId: 1, + beaconBalance: BALANCE_TRUNCATED64_WEI, + beaconValidators: 5692, + caller: user1 + } + }) + }) + + it('accepts new eth2 denominated balances, no trunc', async () => { + const BALANCE_GWEI = toBN('183216444408705') + const BALANCE_WEI = BALANCE_GWEI.mul(toBN(DENOMINATION_OFFSET)) + await appLido.pretendTotalPooledEtherGweiForTest(BALANCE_GWEI) + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 5692, beaconBalanceGwei: BALANCE_GWEI.toString(10) }, + { from: user1 } + ) + assertEvent(receipt, 'CommitteeMemberReported', { + expectedArgs: { epochId: 1, beaconBalance: BALANCE_WEI, beaconValidators: 5692, caller: user1 } + }) + }) + + it('reverts when trying to report from non-member', async () => { + for (const account of [user2, user3, user4, nobody]) { + await assertRevertCustomError( + app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 1, + beaconBalanceGwei: 32 + }, + { from: account } + ), + 'NotMemberReported' + ) + } + }) + + it('handleCommitteeMemberReport works and emits event, getLastCompletedReportDelta tracks last 2 reports', async () => { + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 1) // 1 epoch later + const prePooledEther = START_BALANCE + 32 + let receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: prePooledEther }, + { from: user1 } + ) + + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: prePooledEther * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + assertEvent(receipt, 'PostTotalShares', { + expectedArgs: { + postTotalPooledEther: prePooledEther * DENOMINATION_OFFSET, + preTotalPooledEther: START_BALANCE * DENOMINATION_OFFSET, + timeElapsed: EPOCH_LENGTH * 1, + totalShares: 42 + } + }) + await assertExpectedEpochs(2, 1) + + let res = await app.getLastCompletedReportDelta() + assertBn(res.postTotalPooledEther, toBN(prePooledEther).mul(toBN(DENOMINATION_OFFSET))) + assertBn(res.preTotalPooledEther, toBN(START_BALANCE).mul(toBN(DENOMINATION_OFFSET))) + assertBn(res.timeElapsed, EPOCH_LENGTH * 1) + + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 3) // 2 epochs later + const postPooledEther = prePooledEther + 99 + receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 3, beaconValidators: 3, beaconBalanceGwei: postPooledEther }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 3, beaconBalance: postPooledEther * DENOMINATION_OFFSET, beaconValidators: 3 } + }) + assertEvent(receipt, 'PostTotalShares', { + expectedArgs: { + postTotalPooledEther: postPooledEther * DENOMINATION_OFFSET, + preTotalPooledEther: prePooledEther * DENOMINATION_OFFSET, + timeElapsed: EPOCH_LENGTH * 2, + totalShares: 42 + } + }) + await assertExpectedEpochs(4, 3) + + res = await app.getLastCompletedReportDelta() + assertBn(res.postTotalPooledEther, toBN(postPooledEther).mul(toBN(DENOMINATION_OFFSET))) + assertBn(res.preTotalPooledEther, toBN(prePooledEther).mul(toBN(DENOMINATION_OFFSET))) + assertBn(res.timeElapsed, EPOCH_LENGTH * 2) + }) + + it('handleCommitteeMemberReport works OK on OK pooledEther increase', async () => { + const beginPooledEther = START_BALANCE + let receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: beginPooledEther }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: beginPooledEther * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + await assertExpectedEpochs(2, 0) + + const reward = Math.round((START_BALANCE * (768 / 365 / 24 / 3600) * 9) / 100) // annual increase by 9% + const nextPooledEther = beginPooledEther + reward + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 3) // 2 epochs later (timeElapsed = 768) + receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 3, beaconValidators: 3, beaconBalanceGwei: nextPooledEther }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 3, beaconBalance: nextPooledEther * DENOMINATION_OFFSET, beaconValidators: 3 } + }) + }) + + it('handleCommitteeMemberReport reverts on too high pooledEther increase', async () => { + const beginPooledEther = START_BALANCE + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: beginPooledEther }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: beginPooledEther * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + await assertExpectedEpochs(2, 0) + + const reward = Math.round((START_BALANCE * (768 / 365 / 24 / 3600) * 11) / 100) // annual increase by 11% + const nextPooledEther = beginPooledEther + reward + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 3) // 2 epochs later (timeElapsed = 768) + await assertRevertCustomError( + app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 3, beaconValidators: 3, beaconBalanceGwei: nextPooledEther }, + { from: user1 } + ), + 'AllowedBeaconBalanceIncreaseExceeded' + ) + }) + + it('handleCommitteeMemberReport works OK on OK pooledEther decrease', async () => { + const beginPooledEther = START_BALANCE + let receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: beginPooledEther }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: beginPooledEther * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + await assertExpectedEpochs(2, 0) + + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 3) // 2 epochs later (timeElapsed = 768) + const loss = Math.round((START_BALANCE * 4) / 100) // decrease by 4% + const nextPooledEther = beginPooledEther - loss + receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 3, beaconValidators: 3, beaconBalanceGwei: nextPooledEther }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 3, beaconBalance: nextPooledEther * DENOMINATION_OFFSET, beaconValidators: 3 } + }) + }) + + it('handleCommitteeMemberReport reverts on too high pooledEther decrease', async () => { + const beginPooledEther = START_BALANCE + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: beginPooledEther }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: beginPooledEther * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + await assertExpectedEpochs(2, 0) + + const loss = Math.round((START_BALANCE * 6) / 100) // decrease by 6% + const nextPooledEther = beginPooledEther - loss + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 3) // 2 epochs later (timeElapsed = 768) + await assertRevertCustomError( + app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 3, beaconValidators: 3, beaconBalanceGwei: nextPooledEther }, + { from: user1 } + ), + 'AllowedBeaconBalanceDecreaseExceeded' + ) + }) + + it('handleCommitteeMemberReport change increase limit works', async () => { + let res = await app.setAllowedBeaconBalanceAnnualRelativeIncrease(42, { from: voting }) + assertEvent(res, 'AllowedBeaconBalanceAnnualRelativeIncreaseSet', { expectedArgs: { value: 42 } }) + let limit = await app.getAllowedBeaconBalanceAnnualRelativeIncrease() + assertBn(limit, 42) + + res = await app.setAllowedBeaconBalanceAnnualRelativeIncrease(777, { from: voting }) + assertEvent(res, 'AllowedBeaconBalanceAnnualRelativeIncreaseSet', { expectedArgs: { value: 777 } }) + limit = await app.getAllowedBeaconBalanceAnnualRelativeIncrease() + assertBn(limit, 777) + }) + + it('handleCommitteeMemberReport change decrease limit works', async () => { + let res = await app.setAllowedBeaconBalanceRelativeDecrease(42, { from: voting }) + assertEvent(res, 'AllowedBeaconBalanceRelativeDecreaseSet', { expectedArgs: { value: 42 } }) + let limit = await app.getAllowedBeaconBalanceRelativeDecrease() + assertBn(limit, 42) + + res = await app.setAllowedBeaconBalanceRelativeDecrease(777, { from: voting }) + assertEvent(res, 'AllowedBeaconBalanceRelativeDecreaseSet', { expectedArgs: { value: 777 } }) + limit = await app.getAllowedBeaconBalanceRelativeDecrease() + assertBn(limit, 777) + }) + + it.skip('handleCommitteeMemberReport change increase limit affect sanity checks', async () => { + const beginPooledEther = START_BALANCE + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: beginPooledEther }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: beginPooledEther * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + await assertExpectedEpochs(2, 0) + + const reward = Math.round((START_BALANCE * (768 / 365 / 24 / 3600) * 11) / 100) // annual increase by 11% + const nextPooledEther = beginPooledEther + reward + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 3) // 2 epochs later (timeElapsed = 768) + + // check fails + await assertRevertCustomError( + app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 2, + beaconValidators: 3, + beaconBalanceGwei: nextPooledEther + }, + { from: user1 } + ), + 'AllowedBeaconBalanceIncreaseExceeded' + ) + + // set limit up to 12% + const res = await app.setAllowedBeaconBalanceAnnualRelativeIncrease(1200, { from: voting }) + assertEvent(res, 'AllowedBeaconBalanceAnnualRelativeIncreaseSet', { expectedArgs: { value: 1200 } }) + + // check OK + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 3, beaconValidators: 3, beaconBalanceGwei: nextPooledEther }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 3, beaconBalance: nextPooledEther * DENOMINATION_OFFSET, beaconValidators: 3 } + }) + }) + + it('handleCommitteeMemberReport change decrease limit affect sanity checks', async () => { + const beginPooledEther = START_BALANCE + let receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: beginPooledEther }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: beginPooledEther * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + await assertExpectedEpochs(2, 0) + + const loss = Math.round((START_BALANCE * 6) / 100) // decrease by 6% + const nextPooledEther = beginPooledEther - loss + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 3) // 2 epochs later (timeElapsed = 768) + + // check fails + await assertRevertCustomError( + app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 3, beaconValidators: 3, beaconBalanceGwei: nextPooledEther }, + { from: user1 } + ), + 'AllowedBeaconBalanceDecreaseExceeded' + ) + + // set limit up to 7% + const res = await app.setAllowedBeaconBalanceRelativeDecrease(700, { from: voting }) + assertEvent(res, 'AllowedBeaconBalanceRelativeDecreaseSet', { expectedArgs: { value: 700 } }) + + // check OK + receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 3, beaconValidators: 3, beaconBalanceGwei: nextPooledEther }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 3, beaconBalance: nextPooledEther * DENOMINATION_OFFSET, beaconValidators: 3 } + }) + }) + + it('handleCommitteeMemberReport time affect increase sanity checks', async () => { + const beginPooledEther = START_BALANCE + let receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: beginPooledEther }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: beginPooledEther * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + await assertExpectedEpochs(2, 0) + + const reward = Math.round((START_BALANCE * (768 / 365 / 24 / 3600) * 19) / 100) // annual increase by 19% + const nextPooledEther = beginPooledEther + reward + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 3) // 2 epochs later (timeElapsed = 768) + + // check fails + await assertRevertCustomError( + app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 3, beaconValidators: 3, beaconBalanceGwei: nextPooledEther }, + { from: user1 } + ), + 'AllowedBeaconBalanceIncreaseExceeded' + ) + + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 5) // 4 epochs later (timeElapsed = 768*2) + // check OK because 4 epochs passed + receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 5, beaconValidators: 3, beaconBalanceGwei: nextPooledEther }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 5, beaconBalance: nextPooledEther * DENOMINATION_OFFSET, beaconValidators: 3 } + }) + }) + + it('handleCommitteeMemberReport time does not affect decrease sanity checks', async () => { + const beginPooledEther = START_BALANCE + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: beginPooledEther }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: beginPooledEther * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + await assertExpectedEpochs(2, 0) + + const reward = Math.round(START_BALANCE * (6 / 100)) // annual increase by 6% + const nextPooledEther = beginPooledEther + reward + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 3) // 2 epochs later (timeElapsed = 768) + + // check fails + await assertRevertCustomError( + app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 3, beaconValidators: 3, beaconBalanceGwei: nextPooledEther }, + { from: user1 } + ), + 'AllowedBeaconBalanceIncreaseExceeded' + ) + + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 5) // 4 epochs later (timeElapsed = 768*2) + // check fails but 4 epochs passed + await assertRevertCustomError( + app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 5, beaconValidators: 3, beaconBalanceGwei: nextPooledEther }, + { from: user1 } + ), + 'AllowedBeaconBalanceIncreaseExceeded' + ) + }) + + it('setBeaconReportReceiver to 0x0', async () => { + const receipt = await app.setBeaconReportReceiver(ZERO_ADDRESS, { from: voting }) + assertEvent(receipt, 'BeaconReportReceiverSet', { expectedArgs: { callback: ZERO_ADDRESS } }) + assert((await app.getBeaconReportReceiver()) === ZERO_ADDRESS) + }) + + it('setBeaconReportReceiver failed auth', async () => { + await assertRevert(app.setBeaconReportReceiver(ZERO_ADDRESS, { from: user1 }), getAuthError(user1, SET_BEACON_REPORT_RECEIVER_ROLE)) + }) + + it('quorum receiver called with same arguments as getLastCompletedReportDelta', async () => { + const badMock = await BeaconReportReceiverWithoutERC165.new() + await assertRevertCustomError(app.setBeaconReportReceiver(badMock.address, { from: voting }), 'BadBeaconReportReceiver') + + const mock = await BeaconReportReceiver.new() + let receipt = await app.setBeaconReportReceiver(mock.address, { from: voting }) + assertEvent(receipt, 'BeaconReportReceiverSet', { expectedArgs: { callback: mock.address } }) + assert((await app.getBeaconReportReceiver()) === mock.address) + + receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: START_BALANCE + 35 }, + { from: user1 } + ) + + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: (START_BALANCE + 35) * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + await assertExpectedEpochs(2, 0) + + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 2) // 1 epochs later + receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 2, beaconValidators: 3, beaconBalanceGwei: START_BALANCE + 77 }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 2, beaconBalance: (START_BALANCE + 77) * DENOMINATION_OFFSET, beaconValidators: 3 } + }) + await assertExpectedEpochs(3, 2) + + assertBn(await mock.postTotalPooledEther(), toBN(START_BALANCE + 77).mul(toBN(DENOMINATION_OFFSET))) + assertBn(await mock.preTotalPooledEther(), toBN(START_BALANCE + 35).mul(toBN(DENOMINATION_OFFSET))) + assertBn(await mock.timeElapsed(), EPOCH_LENGTH) + + const res = await app.getLastCompletedReportDelta() + assertBn(res.postTotalPooledEther, toBN(START_BALANCE + 77).mul(toBN(DENOMINATION_OFFSET))) + assertBn(res.preTotalPooledEther, toBN(START_BALANCE + 35).mul(toBN(DENOMINATION_OFFSET))) + assertBn(res.timeElapsed, EPOCH_LENGTH) + }) + + it('reverts when trying to report this epoch again', async () => { + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: START_BALANCE }, + { from: user1 } + ) // got quorum + await assertExpectedEpochs(2, 0) + await assertRevertCustomError( + app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: START_BALANCE }, + { from: user1 } + ), + 'EpochIsTooOld' + ) + }) + + it('reverts when trying to report future epoch', async () => { + await assertRevertCustomError( + app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 2, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user1 } + ), + 'UnexpectedEpoch' + ) + }) + + describe(`current epoch: 5`, function () { + beforeEach(async () => { + await appLido.pretendTotalPooledEtherGweiForTest(32) + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 5) + await assertExpectedEpochs(1, 5) + }) + + it('reverts when trying to report stale epoch', async () => { + await assertRevertCustomError( + app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 0, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user1 } + ), + 'EpochIsTooOld' + ) + await assertExpectedEpochs(1, 5) + }) + + it('reverts when trying to report this epoch again from the same user', async () => { + await app.updateQuorum(2, { from: voting }) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 5, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user1 } + ) + await assertRevertCustomError( + app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 5, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user1 } + ), + 'MemberAlreadyReported' + ) + await assertExpectedEpochs(5, 5) + }) + + it('reverts when trying to report future epoch', async () => { + await assertRevertCustomError( + app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 10, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user1 } + ), + 'UnexpectedEpoch' + ) + }) + + it('handleCommitteeMemberReport works and emits event', async () => { + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 5, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 5, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + await assertExpectedEpochs(6, 5) + }) + }) + }) + }) + describe('When there is multi-member setup (7 members, default quorum is 7)', function () { + beforeEach(async () => { + await app.setTime(GENESIS_TIME + EPOCH_LENGTH * 1) + await assertExpectedEpochs(1, 1) + for (const account of [user1, user2, user3, user4, user5, user6, user7]) { + await app.addOracleMember(account, { from: voting }) + } + await app.updateQuorum(7, { from: voting }) + }) + + it('removeOracleMember updates expectedEpochId and clears current reporting', async () => { + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 0, beaconBalanceGwei: 0 }, + { from: user1 } + ) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user2 } + ) + await assertExpectedEpochs(1, 1) + assertBn(await app.getCurrentOraclesReportStatus(), 0b011) + assertBn(await app.getDistinctMemberReportsCount(), 2) + + await app.removeOracleMember(user1, { from: voting }) + await assertExpectedEpochs(1, 1) + assertBn(await app.getCurrentOraclesReportStatus(), 0b000) + assertBn(await app.getDistinctMemberReportsCount(), 0) + + // user2 reports again the same epoch + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user2 } + ) + await assertExpectedEpochs(1, 1) + assertBn(await app.getCurrentOraclesReportStatus(), 0b010) + assertBn(await app.getDistinctMemberReportsCount(), 1) + }) + + it('getCurrentOraclesReportStatus/VariantSize/Variant', async () => { + assertBn(await app.getCurrentOraclesReportStatus(), 0b000) + assertBn(await app.getDistinctMemberReportsCount(), 0) + + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user1 } + ) + assertBn(await app.getCurrentOraclesReportStatus(), 0b001) + assertBn(await app.getDistinctMemberReportsCount(), 1) + + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 11, beaconBalanceGwei: 101 }, + { from: user2 } + ) + assertBn(await app.getCurrentOraclesReportStatus(), 0b011) + assertBn(await app.getDistinctMemberReportsCount(), 2) + + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user3 } + ) + assertBn(await app.getCurrentOraclesReportStatus(), 0b111) + assertBn(await app.getDistinctMemberReportsCount(), 2) + + const firstKind = await app.getMemberReport(0) + + assertBn(firstKind.beaconBalanceGwei, 32) + assertBn(firstKind.beaconValidators, 1) + // TODO: restore the check somehow + // assertBn(firstKind.count, 2) + const secondKind = await app.getMemberReport(1) + assertBn(secondKind.beaconBalanceGwei, 101) + assertBn(secondKind.beaconValidators, 11) + // assertBn(secondKind.count, 1) + + // TODO: fix the check + const receipt = await app.updateQuorum(2, { from: voting }) + // assertEvent(receipt, 'ConsensusReached', { expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1 } }) + // assertBn(await app.getCurrentOraclesReportStatus(), 0b000) + // assertBn(await app.getDistinctMemberReportsCount(), 0) + }) + + describe('handleCommitteeMemberReport reaches quorum', function () { + it('handleCommitteeMemberReport works and emits event', async () => { + for (const acc of [user1, user2, user3, user4, user5, user6]) { + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: acc } + ) + await assertExpectedEpochs(1, 1) + assertEvent(receipt, 'CommitteeMemberReported', { + expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1, caller: acc } + }) + } + + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user7 } + ) + assertEvent(receipt, 'CommitteeMemberReported', { + expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1, caller: user7 } + }) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + }) + + it('reverts when trying to report this epoch again', async () => { + for (const account of [user1, user2, user3, user4, user5, user6]) { + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: account } + ) + } + await assertExpectedEpochs(1, 1) + + for (const account of [user1, user2, user3, user4, user5, user6]) { + await assertRevertCustomError( + app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: account } + ), + 'MemberAlreadyReported' + ) + } + await assertExpectedEpochs(1, 1) + }) + + it('6 oracles push alike, 1 miss', async () => { + for (const acc of [user1, user2, user3, user4, user5, user6]) { + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: acc } + ) + await assertExpectedEpochs(1, 1) + } + + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user7 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + }) + + it('oracles part 3+3, no quorum for 4', async () => { + await app.updateQuorum(4, { from: voting }) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 2, beaconBalanceGwei: 64 }, + { from: user1 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 2, beaconBalanceGwei: 64 }, + { from: user2 } + ) + + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 3, beaconBalanceGwei: 65 }, + { from: user3 } + ) + + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 3, beaconBalanceGwei: 65 }, + { from: user4 } + ) + + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 2, beaconBalanceGwei: 64 }, + { from: user5 } + ) + + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 3, beaconBalanceGwei: 65 }, + { from: user6 } + ) + + await assertExpectedEpochs(1, 1) + }) + + it('oracles part 3+3, got quorum for 3', async () => { + await app.updateQuorum(3, { from: voting }) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 2, beaconBalanceGwei: 64 }, + { from: user1 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user2 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user3 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 2, beaconBalanceGwei: 64 }, + { from: user4 } + ) + await assertExpectedEpochs(1, 1) + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user5 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + }) + + it('oracles part 4+3, got quorum for 4', async () => { + await app.updateQuorum(4, { from: voting }) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user1 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user2 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user3 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 3, beaconBalanceGwei: 65 }, + { from: user4 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 3, beaconBalanceGwei: 65 }, + { from: user5 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 3, beaconBalanceGwei: 65 }, + { from: user6 } + ) + await assertExpectedEpochs(1, 1) + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user7 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + }) + + it('oracles part 5+2, got quorum for 5', async () => { + await app.updateQuorum(5, { from: voting }) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 2, beaconBalanceGwei: 65 }, + { from: user1 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user2 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user3 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user4 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 3, beaconBalanceGwei: 65 }, + { from: user5 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user6 } + ) + await assertExpectedEpochs(1, 1) + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user7 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + }) + + it('only 1 report is enough in quorum l1', async () => { + await app.updateQuorum(1, { from: voting }) + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user1 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + }) + + it('only 2 alike report is enough in quorum 2', async () => { + await app.updateQuorum(2, { from: voting }) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user1 } + ) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 2, beaconBalanceGwei: 33 }, + { from: user2 } + ) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 3, beaconBalanceGwei: 34 }, + { from: user3 } + ) + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 0, beaconBalanceGwei: 0 }, + { from: user4 } + ) + const receipt = await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: user5 } + ) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + }) + }) + describe('updateQuorum lowering reaches quorum', function () { + it('6 oracles push alike, 1 miss', async () => { + for (const acc of [user1, user2, user3, user4, user5, user6]) { + await app.handleCommitteeMemberReport( + { ...ZERO_MEMBER_REPORT, epochId: 1, beaconValidators: 1, beaconBalanceGwei: 32 }, + { from: acc } + ) + await assertExpectedEpochs(1, 1) + } + + await app.updateQuorum(8, { from: voting }) // no quorum for 8 + await assertExpectedEpochs(1, 1) + + const receipt = await app.updateQuorum(6, { from: voting }) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + }) + + it('oracles part 3+3, no quorum here at all', async () => { + await app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 2, + beaconBalanceGwei: 64 + }, + { from: user1 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 2, + beaconBalanceGwei: 64 + }, + { from: user2 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 2, + beaconBalanceGwei: 64 + }, + { from: user3 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 3, + beaconBalanceGwei: 65 + }, + { from: user4 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 3, + beaconBalanceGwei: 65 + }, + { from: user5 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 3, + beaconBalanceGwei: 65 + }, + { from: user6 } + ) + await assertExpectedEpochs(1, 1) + + // decreasing quorum does not help because conflicting parts are equal + await app.updateQuorum(3, { from: voting }) + await assertExpectedEpochs(1, 1) + await app.updateQuorum(1, { from: voting }) + await assertExpectedEpochs(1, 1) + }) + + it('oracles part 4+3, quorum lowers to 4', async () => { + await app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 1, + beaconBalanceGwei: 32 + }, + { from: user1 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 1, + beaconBalanceGwei: 32 + }, + { from: user2 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 1, + beaconBalanceGwei: 32 + }, + { from: user3 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 3, + beaconBalanceGwei: 65 + }, + { from: user4 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 3, + beaconBalanceGwei: 65 + }, + { from: user5 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 3, + beaconBalanceGwei: 65 + }, + { from: user6 } + ) + await assertExpectedEpochs(1, 1) + await app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 1, + beaconBalanceGwei: 32 + }, + { from: user7 } + ) + await assertExpectedEpochs(1, 1) + + // decreasing quorum to 5 does not help + await app.updateQuorum(5, { from: voting }) + await assertExpectedEpochs(1, 1) + + receipt = await app.updateQuorum(4, { from: voting }) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + }) + + it('only 1 report is enough in quorum lowers to 1', async () => { + await app.handleCommitteeMemberReport( + { + ...ZERO_MEMBER_REPORT, + epochId: 1, + beaconValidators: 1, + beaconBalanceGwei: 32 + }, + { from: user1 } + ) + await assertExpectedEpochs(1, 1) + + const receipt = await app.updateQuorum(1, { from: voting }) + assertEvent(receipt, 'ConsensusReached', { + expectedArgs: { epochId: 1, beaconBalance: 32 * DENOMINATION_OFFSET, beaconValidators: 1 } + }) + }) + }) + }) +}) diff --git a/test/0.8.9/self-owned-steth-burner.test.js b/test/0.8.9/self-owned-steth-burner.test.js index 0b42336d7..9e7347bca 100644 --- a/test/0.8.9/self-owned-steth-burner.test.js +++ b/test/0.8.9/self-owned-steth-burner.test.js @@ -1,13 +1,14 @@ +const { assert } = require('chai') +const { artifacts } = require('hardhat') + const { assertBn, assertRevert, assertEvent, assertAmountOfEvents } = require('@aragon/contract-helpers-test/src/asserts') const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') const { newDao, newApp } = require('../0.4.24/helpers/dao') - -const { assert } = require('chai') +const { StETH, ETH } = require('../helpers/utils') const SelfOwnerStETHBurner = artifacts.require('SelfOwnedStETHBurner.sol') - -const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') - +const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry.sol') +const VaultMock = artifacts.require('VaultMock.sol') const LidoMock = artifacts.require('LidoMock.sol') const LidoOracleMock = artifacts.require('OracleMock.sol') const DepositContractMock = artifacts.require('DepositContractMock.sol') @@ -17,18 +18,17 @@ const CompositePostRebaseBeaconReceiver = artifacts.require('CompositePostRebase const ERC20OZMock = artifacts.require('ERC20OZMock.sol') const ERC721OZMock = artifacts.require('ERC721OZMock.sol') -const ETH = (value) => web3.utils.toWei(value + '', 'ether') // semantic aliases -const stETH = ETH const stETHShares = ETH -contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anotherAccount, ...otherAccounts]) => { +contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, anotherAccount]) => { let oracle, lido, burner let treasuryAddr let dao, acl, operators let compositeBeaconReceiver beforeEach('deploy lido with dao', async () => { + const treasury = await VaultMock.new() const lidoBase = await LidoMock.new({ from: deployer }) oracle = await LidoOracleMock.new({ from: deployer }) const depositContract = await DepositContractMock.new({ from: deployer }) @@ -52,8 +52,8 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot await acl.createPermission(voting, lido.address, await lido.BURN_ROLE(), appManager, { from: appManager }) // Initialize the app's proxy. - await lido.initialize(depositContract.address, oracle.address, operators.address) - treasuryAddr = await lido.getInsuranceFund() + await lido.initialize(depositContract.address, oracle.address, operators.address, treasury.address, ZERO_ADDRESS, ZERO_ADDRESS) + treasuryAddr = await lido.getTreasury() await oracle.setPool(lido.address) await depositContract.reset() @@ -71,7 +71,7 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot beforeEach(async () => { // initial balance is zero - assertBn(await lido.balanceOf(anotherAccount), stETH(0)) + assertBn(await lido.balanceOf(anotherAccount), StETH(0)) // stake ether to get an stETH in exchange await web3.eth.sendTransaction({ from: anotherAccount, to: lido.address, value: ETH(20) }) @@ -79,9 +79,9 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot await web3.eth.sendTransaction({ from: voting, to: lido.address, value: ETH(25) }) // check stETH balances - assertBn(await lido.balanceOf(anotherAccount), stETH(20)) - assertBn(await lido.balanceOf(deployer), stETH(30)) - assertBn(await lido.balanceOf(voting), stETH(25)) + assertBn(await lido.balanceOf(anotherAccount), StETH(20)) + assertBn(await lido.balanceOf(deployer), StETH(30)) + assertBn(await lido.balanceOf(voting), StETH(25)) // unlock oracle account (allow transactions originated from oracle.address) await ethers.provider.send('hardhat_impersonateAccount', [oracle.address]) @@ -143,63 +143,63 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot it(`reverts on zero stETH amount cover/non-cover request`, async () => { // provide non-zero allowance - await lido.approve(burner.address, stETH(1), { from: voting }) + await lido.approve(burner.address, StETH(1), { from: voting }) // but zero request on cover - assertRevert(burner.requestBurnMyStETHForCover(stETH(0), { from: voting }), `ZERO_BURN_AMOUNT`) + assertRevert(burner.requestBurnMyStETHForCover(StETH(0), { from: voting }), `ZERO_BURN_AMOUNT`) // and zero request on non-cover - assertRevert(burner.requestBurnMyStETH(stETH(0), { from: voting }), `ZERO_BURN_AMOUNT`) + assertRevert(burner.requestBurnMyStETH(StETH(0), { from: voting }), `ZERO_BURN_AMOUNT`) }) it(`reverts on burn request from non-voting address`, async () => { // provide allowance and request burn for cover - await lido.approve(burner.address, stETH(8), { from: anotherAccount }) + await lido.approve(burner.address, StETH(8), { from: anotherAccount }) // anotherAccount can't place burn request, only voting can - assertRevert(burner.requestBurnMyStETHForCover(stETH(8), { from: anotherAccount }), `MSG_SENDER_MUST_BE_VOTING`) + assertRevert(burner.requestBurnMyStETHForCover(StETH(8), { from: anotherAccount }), `MSG_SENDER_MUST_BE_VOTING`) - await lido.approve(burner.address, stETH(8), { from: deployer }) + await lido.approve(burner.address, StETH(8), { from: deployer }) // event deployer can't place burn request - assertRevert(burner.requestBurnMyStETH(stETH(8), { from: deployer }), `MSG_SENDER_MUST_BE_VOTING`) + assertRevert(burner.requestBurnMyStETH(StETH(8), { from: deployer }), `MSG_SENDER_MUST_BE_VOTING`) }) it(`request shares burn for cover works`, async () => { // allowance should be set explicitly to request burning - assertRevert(burner.requestBurnMyStETHForCover(stETH(8), { from: voting }), `TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE`) + assertRevert(burner.requestBurnMyStETHForCover(StETH(8), { from: voting }), `TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE`) // provide allowance and request burn for cover - const sharesAmount8StETH = await lido.getSharesByPooledEth(stETH(8)) - await lido.approve(burner.address, stETH(8), { from: voting }) - let receipt = await burner.requestBurnMyStETHForCover(stETH(8), { from: voting }) + const sharesAmount8StETH = await lido.getSharesByPooledEth(StETH(8)) + await lido.approve(burner.address, StETH(8), { from: voting }) + let receipt = await burner.requestBurnMyStETHForCover(StETH(8), { from: voting }) assertEvent(receipt, `StETHBurnRequested`, { - expectedArgs: { isCover: true, requestedBy: voting, amount: stETH(8), sharesAmount: sharesAmount8StETH } + expectedArgs: { isCover: true, requestedBy: voting, amount: StETH(8), sharesAmount: sharesAmount8StETH } }) // check stETH balances - assertBn(await lido.balanceOf(burner.address), stETH(8)) - assertBn(await lido.balanceOf(voting), stETH(17)) + assertBn(await lido.balanceOf(burner.address), StETH(8)) + assertBn(await lido.balanceOf(voting), StETH(17)) const sharesAmount12 = sharesAmount8StETH.mul(bn(3)).div(bn(2)) - await lido.approve(burner.address, stETH(13), { from: voting }) - receipt = await burner.requestBurnMyStETH(stETH(12), { from: voting }) + await lido.approve(burner.address, StETH(13), { from: voting }) + receipt = await burner.requestBurnMyStETH(StETH(12), { from: voting }) assertEvent(receipt, `StETHBurnRequested`, { - expectedArgs: { isCover: false, requestedBy: voting, amount: stETH(12), sharesAmount: sharesAmount12 } + expectedArgs: { isCover: false, requestedBy: voting, amount: StETH(12), sharesAmount: sharesAmount12 } }) // check stETH balances again, we didn't execute the actual burn - assertBn(await lido.balanceOf(burner.address), stETH(20)) - assertBn(await lido.balanceOf(voting), stETH(5)) + assertBn(await lido.balanceOf(burner.address), StETH(20)) + assertBn(await lido.balanceOf(voting), StETH(5)) }) it(`invoke an oracle without requested burn works`, async () => { // someone accidentally transferred stETH - await lido.transfer(burner.address, stETH(5.6), { from: deployer }) - await lido.transfer(burner.address, stETH(4.1), { from: anotherAccount }) + await lido.transfer(burner.address, StETH(5.6), { from: deployer }) + await lido.transfer(burner.address, StETH(4.1), { from: anotherAccount }) - assertBn(await lido.balanceOf(burner.address), stETH(9.7)) + assertBn(await lido.balanceOf(burner.address), StETH(9.7)) // only the Lido oracle can call this func, but there is nothing to burn await burner.processLidoOracleReport(ETH(10), ETH(12), bn(1000), { from: deployer }) @@ -211,22 +211,22 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot assertAmountOfEvents(receipt, `StETHBurnt`, { expectedAmount: 0 }) // the balance should be the same - assertBn(await lido.balanceOf(burner.address), stETH(9.7)) + assertBn(await lido.balanceOf(burner.address), StETH(9.7)) }) it(`invoke an oracle with the one type (cover/non-cover) pending requests works`, async () => { // someone accidentally transferred stETH - await lido.transfer(burner.address, stETH(3.1), { from: deployer }) - await lido.transfer(burner.address, stETH(4.0), { from: anotherAccount }) + await lido.transfer(burner.address, StETH(3.1), { from: deployer }) + await lido.transfer(burner.address, StETH(4.0), { from: anotherAccount }) // request non-cover burn (accidentally approved more than needed) - await lido.approve(burner.address, stETH(7), { from: voting }) - await burner.requestBurnMyStETH(stETH(6), { from: voting }) + await lido.approve(burner.address, StETH(7), { from: voting }) + await burner.requestBurnMyStETH(StETH(6), { from: voting }) const burnerShares = await lido.sharesOf(burner.address) - const sharesToBurn = await lido.getSharesByPooledEth(stETH(6)) + const sharesToBurn = await lido.getSharesByPooledEth(StETH(6)) - assertBn(await lido.balanceOf(burner.address), stETH(6 + 3.1 + 4.0)) + assertBn(await lido.balanceOf(burner.address), StETH(6 + 3.1 + 4.0)) assertRevert( // should revert @@ -244,7 +244,7 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot await acl.grantPermission(burner.address, lido.address, await lido.BURN_ROLE(), { from: appManager }) const receipt = await burner.processLidoOracleReport(ETH(10), ETH(12), bn(1000), { from: oracle.address }) - assertEvent(receipt, `StETHBurnt`, { expectedArgs: { isCover: false, amount: stETH(6), sharesAmount: sharesToBurn } }) + assertEvent(receipt, `StETHBurnt`, { expectedArgs: { isCover: false, amount: StETH(6), sharesAmount: sharesToBurn } }) assertAmountOfEvents(receipt, `StETHBurnt`, { expectedAmount: 1 }) @@ -254,30 +254,30 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot it(`invoke an oracle with requested cover AND non-cover burn works`, async () => { // someone accidentally transferred stETH - await lido.transfer(burner.address, stETH(2.1), { from: deployer }) - await lido.transfer(burner.address, stETH(3.1), { from: anotherAccount }) + await lido.transfer(burner.address, StETH(2.1), { from: deployer }) + await lido.transfer(burner.address, StETH(3.1), { from: anotherAccount }) - await lido.approve(burner.address, stETH(3), { from: voting }) + await lido.approve(burner.address, StETH(3), { from: voting }) - const sharesAmount0_5StETH = await lido.getSharesByPooledEth(stETH(0.5)) + const sharesAmount0_5StETH = await lido.getSharesByPooledEth(StETH(0.5)) const sharesAmount1_5StETH = sharesAmount0_5StETH.mul(bn(3)) - const receiptCover = await burner.requestBurnMyStETHForCover(stETH(1.5), { from: voting }) + const receiptCover = await burner.requestBurnMyStETHForCover(StETH(1.5), { from: voting }) assertEvent(receiptCover, `StETHBurnRequested`, { - expectedArgs: { isCover: true, requestedBy: voting, amount: stETH(1.5), sharesAmount: sharesAmount1_5StETH } + expectedArgs: { isCover: true, requestedBy: voting, amount: StETH(1.5), sharesAmount: sharesAmount1_5StETH } }) - const receiptNonCover = await burner.requestBurnMyStETH(stETH(0.5), { from: voting }) + const receiptNonCover = await burner.requestBurnMyStETH(StETH(0.5), { from: voting }) assertEvent(receiptNonCover, `StETHBurnRequested`, { - expectedArgs: { isCover: false, requestedBy: voting, amount: stETH(0.5), sharesAmount: sharesAmount0_5StETH } + expectedArgs: { isCover: false, requestedBy: voting, amount: StETH(0.5), sharesAmount: sharesAmount0_5StETH } }) const burnerShares = await lido.sharesOf(burner.address) const sharesToBurn = sharesAmount0_5StETH.add(sharesAmount1_5StETH) - assertBn(await lido.balanceOf(burner.address), stETH(7.2)) + assertBn(await lido.balanceOf(burner.address), StETH(7.2)) assertRevert(burner.processLidoOracleReport(ETH(9), ETH(10), bn(500), { from: deployer }), `APP_AUTH_FAILED`) @@ -292,12 +292,12 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot assertEvent(receipt, `StETHBurnt`, { index: 0, - expectedArgs: { isCover: true, amount: stETH(1.5), sharesAmount: sharesAmount1_5StETH } + expectedArgs: { isCover: true, amount: StETH(1.5), sharesAmount: sharesAmount1_5StETH } }) assertEvent(receipt, `StETHBurnt`, { index: 1, - expectedArgs: { isCover: false, amount: stETH(0.5), sharesAmount: sharesAmount0_5StETH } + expectedArgs: { isCover: false, amount: StETH(0.5), sharesAmount: sharesAmount0_5StETH } }) // cover + non-cover events @@ -351,25 +351,25 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot }) 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(25), { from: voting }) + await burner.requestBurnMyStETHForCover(StETH(25), { from: voting }) - assertBn(await lido.balanceOf(burner.address), stETH(25)) - assertBn(await lido.balanceOf(voting), stETH(0)) - assertBn(await lido.balanceOf(anotherAccount), stETH(20)) - assertBn(await lido.balanceOf(deployer), stETH(30)) + assertBn(await lido.balanceOf(burner.address), StETH(25)) + assertBn(await lido.balanceOf(voting), StETH(0)) + assertBn(await lido.balanceOf(anotherAccount), StETH(20)) + assertBn(await lido.balanceOf(deployer), StETH(30)) await acl.grantPermission(burner.address, lido.address, await lido.BURN_ROLE(), { from: appManager }) await burner.processLidoOracleReport(ETH(1), ETH(1), bn(100), { from: oracle.address }) - assertBn(await lido.balanceOf(burner.address), stETH(0)) - assertBn(await lido.balanceOf(voting), stETH(0)) + assertBn(await lido.balanceOf(burner.address), StETH(0)) + assertBn(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 - assertBn(await lido.balanceOf(deployer), bn(stETH(30)).mul(bn(3)).div(bn(2))) - assertBn(await lido.balanceOf(anotherAccount), bn(stETH(20)).mul(bn(3)).div(bn(2))) + assertBn(await lido.balanceOf(deployer), bn(StETH(30)).mul(bn(3)).div(bn(2))) + assertBn(await lido.balanceOf(anotherAccount), bn(StETH(20)).mul(bn(3)).div(bn(2))) }) it(`revert on illegal attempts to set the max burn amount per run`, async () => { @@ -392,13 +392,13 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot assertBn(await lido.getTotalShares(), stETHShares(75)) // so the max amount to burn per single run is 75*10^18 * 0.012 = 0.9*10^18 - await lido.approve(burner.address, stETH(25), { from: voting }) - await burner.requestBurnMyStETHForCover(stETH(0.9), { from: voting }) + await lido.approve(burner.address, StETH(25), { from: voting }) + await burner.requestBurnMyStETHForCover(StETH(0.9), { from: voting }) assertBn(await lido.sharesOf(burner.address), stETHShares(0.9)) const receipt = await burner.processLidoOracleReport(bn(1), bn(2), bn(3), { from: oracle.address }) assertBn(await lido.sharesOf(burner.address), stETHShares(0)) - assertEvent(receipt, `StETHBurnt`, { expectedArgs: { isCover: true, amount: stETH(0.9), sharesAmount: stETHShares(0.9) } }) + assertEvent(receipt, `StETHBurnt`, { expectedArgs: { isCover: true, amount: StETH(0.9), sharesAmount: stETHShares(0.9) } }) assertAmountOfEvents(receipt, `StETHBurnt`, { expectedAmount: 1 }) await burner.requestBurnMyStETHForCover(await lido.getPooledEthByShares(stETHShares(0.1)), { from: voting }) @@ -433,12 +433,12 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot assertBn(await lido.getTotalShares(), stETHShares(75)) // so the max amount to burn per single run is 75*10^18 * 0.012 = 0.9*10^18 - await lido.approve(burner.address, stETH(25), { from: voting }) - await burner.requestBurnMyStETH(stETH(0.9), { from: voting }) + await lido.approve(burner.address, StETH(25), { from: voting }) + await burner.requestBurnMyStETH(StETH(0.9), { from: voting }) assertBn(await lido.sharesOf(burner.address), stETHShares(0.9)) const receipt = await burner.processLidoOracleReport(bn(1), bn(2), bn(3), { from: oracle.address }) - assertEvent(receipt, `StETHBurnt`, { expectedArgs: { isCover: false, amount: stETH(0.9), sharesAmount: stETHShares(0.9) } }) + assertEvent(receipt, `StETHBurnt`, { expectedArgs: { isCover: false, amount: StETH(0.9), sharesAmount: stETHShares(0.9) } }) assertAmountOfEvents(receipt, `StETHBurnt`, { expectedAmount: 1 }) assertBn(await lido.sharesOf(burner.address), stETHShares(0)) @@ -470,18 +470,18 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot // grant permissions to the Lido.burnShares method await acl.grantPermission(burner.address, lido.address, await lido.BURN_ROLE(), { from: appManager }) - assertBn(await lido.getTotalShares(), stETH(75)) + assertBn(await lido.getTotalShares(), StETH(75)) // so the max amount to burn per single run is 75*10^18 * 0.012 = 0.9*10^18 - await lido.approve(burner.address, stETH(25), { from: voting }) - await burner.requestBurnMyStETH(stETH(0.8), { from: voting }) - await burner.requestBurnMyStETHForCover(stETH(0.1), { from: voting }) + await lido.approve(burner.address, StETH(25), { from: voting }) + await burner.requestBurnMyStETH(StETH(0.8), { from: voting }) + await burner.requestBurnMyStETHForCover(StETH(0.1), { from: voting }) assertBn(await lido.sharesOf(burner.address), stETHShares(0.9)) const receipt = await burner.processLidoOracleReport(bn(1), bn(2), bn(3), { from: oracle.address }) assertBn(await lido.sharesOf(burner.address), stETHShares(0)) - assertEvent(receipt, `StETHBurnt`, { index: 0, expectedArgs: { isCover: true, amount: stETH(0.1), sharesAmount: stETHShares(0.1) } }) - assertEvent(receipt, `StETHBurnt`, { index: 1, expectedArgs: { isCover: false, amount: stETH(0.8), sharesAmount: stETHShares(0.8) } }) + assertEvent(receipt, `StETHBurnt`, { index: 0, expectedArgs: { isCover: true, amount: StETH(0.1), sharesAmount: stETHShares(0.1) } }) + assertEvent(receipt, `StETHBurnt`, { index: 1, expectedArgs: { isCover: false, amount: StETH(0.8), sharesAmount: stETHShares(0.8) } }) assertAmountOfEvents(receipt, `StETHBurnt`, { expectedAmount: 2 }) assertBn(await burner.getCoverSharesBurnt(), stETHShares(0.1)) assertBn(await burner.getNonCoverSharesBurnt(), stETHShares(0.8)) @@ -527,9 +527,9 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot } beforeEach('Setup permissions', async () => { - assertBn(await lido.balanceOf(voting), stETH(0)) + assertBn(await lido.balanceOf(voting), StETH(0)) await web3.eth.sendTransaction({ from: voting, to: lido.address, value: ETH(21) }) - assertBn(await lido.balanceOf(voting), stETH(21)) + assertBn(await lido.balanceOf(voting), StETH(21)) await ethers.provider.send('hardhat_impersonateAccount', [oracle.address]) await ethers.provider.send('hardhat_impersonateAccount', [burner.address]) @@ -543,13 +543,13 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot const burnerRewarder = await RewardEmulatorMock.new(burner.address, { from: voting }) await burnerRewarder.reward({ from: voting, value: ETH(1) }) - await lido.approve(burner.address, stETH(10), { from: voting }) - await burner.requestBurnMyStETHForCover(stETH(10), { from: voting }) + await lido.approve(burner.address, StETH(10), { from: voting }) + await burner.requestBurnMyStETHForCover(StETH(10), { from: voting }) - assertBn(await lido.balanceOf(voting), stETH(11)) - assertBn(await lido.balanceOf(burner.address), stETH(10)) + assertBn(await lido.balanceOf(voting), StETH(11)) + assertBn(await lido.balanceOf(burner.address), StETH(10)) - sharesToBurn = await lido.getSharesByPooledEth(stETH(10)) + sharesToBurn = await lido.getSharesByPooledEth(StETH(10)) await acl.revokePermission(voting, lido.address, await lido.BURN_ROLE()) @@ -611,98 +611,98 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot describe('Recover excess stETH', () => { beforeEach(async () => { // initial stETH balance is zero - assertBn(await lido.balanceOf(voting), stETH(0)) + assertBn(await lido.balanceOf(voting), StETH(0)) // submit 10 ETH to mint 10 stETH await web3.eth.sendTransaction({ from: voting, to: lido.address, value: ETH(10) }) // check 10 stETH minted on balance - assertBn(await lido.balanceOf(voting), stETH(10)) + assertBn(await lido.balanceOf(voting), StETH(10)) }) it(`can't recover requested for burn stETH`, async () => { // request to burn 7.1 stETH - await lido.approve(burner.address, stETH(8), { from: voting }) - await burner.requestBurnMyStETHForCover(stETH(7.1), { from: voting }) + await lido.approve(burner.address, StETH(8), { from: voting }) + await burner.requestBurnMyStETHForCover(StETH(7.1), { from: voting }) // excess stETH amount should be zero - assertBn(await burner.getExcessStETH(), stETH(0)) - assertBn(await lido.balanceOf(treasuryAddr), stETH(0)) - assertBn(await lido.balanceOf(burner.address), stETH(7.1)) + assertBn(await burner.getExcessStETH(), StETH(0)) + assertBn(await lido.balanceOf(treasuryAddr), StETH(0)) + assertBn(await lido.balanceOf(burner.address), StETH(7.1)) // should change nothing const receipt = await burner.recoverExcessStETH() assertAmountOfEvents(receipt, `ExcessStETHRecovered`, { expectedAmount: 0 }) // excess stETH amount didn't changed - assertBn(await burner.getExcessStETH(), stETH(0)) + assertBn(await burner.getExcessStETH(), StETH(0)) // treasury and burner stETH balances are same - assertBn(await lido.balanceOf(treasuryAddr), stETH(0)) - assertBn(await lido.balanceOf(burner.address), stETH(7.1)) + assertBn(await lido.balanceOf(treasuryAddr), StETH(0)) + assertBn(await lido.balanceOf(burner.address), StETH(7.1)) }) it('recover some accidentally sent stETH', async () => { // 'accidentally' sent stETH from voting - await lido.transfer(burner.address, stETH(2.3), { from: voting }) + await lido.transfer(burner.address, StETH(2.3), { from: voting }) // check burner and treasury balances before recovery - assertBn(await lido.balanceOf(burner.address), stETH(2.3)) - assertBn(await lido.balanceOf(treasuryAddr), stETH(0)) + assertBn(await lido.balanceOf(burner.address), StETH(2.3)) + assertBn(await lido.balanceOf(treasuryAddr), StETH(0)) const sharesAmount2_3StETH = await lido.sharesOf(burner.address) const receipt = await burner.recoverExcessStETH({ from: deployer }) assertEvent(receipt, `ExcessStETHRecovered`, { - expectedArgs: { requestedBy: deployer, amount: stETH(2.3), sharesAmount: sharesAmount2_3StETH } + expectedArgs: { requestedBy: deployer, amount: StETH(2.3), sharesAmount: sharesAmount2_3StETH } }) // check burner and treasury balances after recovery - assertBn(await lido.balanceOf(burner.address), stETH(0)) - assertBn(await lido.balanceOf(treasuryAddr), stETH(2.3)) + assertBn(await lido.balanceOf(burner.address), StETH(0)) + assertBn(await lido.balanceOf(treasuryAddr), StETH(2.3)) }) it(`recover some accidentally sent stETH, while burning requests happened in the middle`, async () => { // 'accidentally' sent stETH from voting - await lido.transfer(burner.address, stETH(5), { from: voting }) + await lido.transfer(burner.address, StETH(5), { from: voting }) // check balances - assertBn(await lido.balanceOf(voting), stETH(5)) - assertBn(await lido.balanceOf(burner.address), stETH(5)) + assertBn(await lido.balanceOf(voting), StETH(5)) + assertBn(await lido.balanceOf(burner.address), StETH(5)) // all of the burner's current stETH amount (5) can be recovered - assertBn(await lido.balanceOf(burner.address), stETH(5)) - assertBn(await burner.getExcessStETH(), stETH(5)) + assertBn(await lido.balanceOf(burner.address), StETH(5)) + assertBn(await burner.getExcessStETH(), StETH(5)) // approve burn request and check actual transferred amount - await lido.approve(burner.address, stETH(3), { from: voting }) - await burner.requestBurnMyStETHForCover(stETH(3), { from: voting }) - assertBn(await lido.balanceOf(voting), stETH(2)) + await lido.approve(burner.address, StETH(3), { from: voting }) + await burner.requestBurnMyStETHForCover(StETH(3), { from: voting }) + assertBn(await lido.balanceOf(voting), StETH(2)) // excess stETH amount preserved - assertBn(await burner.getExcessStETH(), stETH(5)) + assertBn(await burner.getExcessStETH(), StETH(5)) // approve another burn request and check actual transferred amount - await lido.approve(burner.address, stETH(1), { from: voting }) - await burner.requestBurnMyStETH(stETH(1), { from: voting }) - assertBn(await lido.balanceOf(voting), stETH(1)) + await lido.approve(burner.address, StETH(1), { from: voting }) + await burner.requestBurnMyStETH(StETH(1), { from: voting }) + assertBn(await lido.balanceOf(voting), StETH(1)) // excess stETH amount preserved - assertBn(await burner.getExcessStETH(), stETH(5)) + assertBn(await burner.getExcessStETH(), StETH(5)) // finally burner balance is 5 stETH - assertBn(await lido.balanceOf(burner.address), stETH(9)) - assertBn(await lido.balanceOf(treasuryAddr), stETH(0)) + assertBn(await lido.balanceOf(burner.address), StETH(9)) + assertBn(await lido.balanceOf(treasuryAddr), StETH(0)) // run recovery process, excess stETH amount (5) // should be transferred to the treasury - const sharesAmount5stETH = await lido.getSharesByPooledEth(stETH(5)) + const sharesAmount5stETH = await lido.getSharesByPooledEth(StETH(5)) const receipt = await burner.recoverExcessStETH({ from: anotherAccount }) assertEvent(receipt, `ExcessStETHRecovered`, { - expectedArgs: { requestedBy: anotherAccount, amount: stETH(5), sharesAmount: sharesAmount5stETH } + expectedArgs: { requestedBy: anotherAccount, amount: StETH(5), sharesAmount: sharesAmount5stETH } }) - assertBn(await burner.getExcessStETH(), stETH(0)) + assertBn(await burner.getExcessStETH(), StETH(0)) - assertBn(await lido.balanceOf(treasuryAddr), stETH(5)) - assertBn(await lido.balanceOf(burner.address), stETH(4)) + assertBn(await lido.balanceOf(treasuryAddr), StETH(5)) + assertBn(await lido.balanceOf(burner.address), StETH(4)) }) }) @@ -747,24 +747,24 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot it(`can't recover stETH by recoverERC20`, async () => { // initial stETH balance is zero - assertBn(await lido.balanceOf(anotherAccount), stETH(0)) + assertBn(await lido.balanceOf(anotherAccount), StETH(0)) // submit 10 ETH to mint 10 stETH await web3.eth.sendTransaction({ from: anotherAccount, to: lido.address, value: ETH(10) }) // check 10 stETH minted on balance - assertBn(await lido.balanceOf(anotherAccount), stETH(10)) + assertBn(await lido.balanceOf(anotherAccount), StETH(10)) // transfer 5 stETH to the burner account - await lido.transfer(burner.address, stETH(5), { from: anotherAccount }) + await lido.transfer(burner.address, StETH(5), { from: anotherAccount }) - assertBn(await lido.balanceOf(anotherAccount), stETH(5)) - assertBn(await lido.balanceOf(burner.address), stETH(5)) + assertBn(await lido.balanceOf(anotherAccount), StETH(5)) + assertBn(await lido.balanceOf(burner.address), StETH(5)) // revert from anotherAccount // need to use recoverExcessStETH - assertRevert(burner.recoverERC20(lido.address, stETH(1), { from: anotherAccount }), `STETH_RECOVER_WRONG_FUNC`) + assertRevert(burner.recoverERC20(lido.address, StETH(1), { from: anotherAccount }), `STETH_RECOVER_WRONG_FUNC`) // revert from deployer // same reason - assertRevert(burner.recoverERC20(lido.address, stETH(1), { from: deployer }), `STETH_RECOVER_WRONG_FUNC`) + assertRevert(burner.recoverERC20(lido.address, StETH(1), { from: deployer }), `STETH_RECOVER_WRONG_FUNC`) }) it(`recover some accidentally sent ERC20`, async () => { @@ -806,40 +806,40 @@ contract('SelfOwnedStETHBurner', ([appManager, voting, deployer, depositor, anot it(`can't recover stETH via ERC721(NFT)`, async () => { // initial stETH balance is zero - assertBn(await lido.balanceOf(anotherAccount), stETH(0)) + assertBn(await lido.balanceOf(anotherAccount), StETH(0)) // submit 10 ETH to mint 10 stETH await web3.eth.sendTransaction({ from: anotherAccount, to: lido.address, value: ETH(10) }) // check 10 stETH minted on balance - assertBn(await lido.balanceOf(anotherAccount), stETH(10)) - // transfer 1 stETH to the burner account "accidentally" - await lido.transfer(burner.address, stETH(1), { from: anotherAccount }) - // transfer 9 stETH to voting (only voting is allowed to request actual burning) - await lido.transfer(voting, stETH(9), { from: anotherAccount }) + assertBn(await lido.balanceOf(anotherAccount), StETH(10)) + // transfer 1 StETH to the burner account "accidentally" + await lido.transfer(burner.address, StETH(1), { from: anotherAccount }) + // transfer 9 StETH to voting (only voting is allowed to request actual burning) + await lido.transfer(voting, StETH(9), { from: anotherAccount }) - // request 9 stETH to be burned later - await lido.approve(burner.address, stETH(9), { from: voting }) - await burner.requestBurnMyStETH(stETH(9), { from: voting }) + // request 9 StETH to be burned later + await lido.approve(burner.address, StETH(9), { from: voting }) + await burner.requestBurnMyStETH(StETH(9), { from: voting }) // check balances one last time - assertBn(await lido.balanceOf(anotherAccount), stETH(0)) - assertBn(await lido.balanceOf(voting), stETH(0)) - assertBn(await lido.balanceOf(burner.address), stETH(10)) + assertBn(await lido.balanceOf(anotherAccount), StETH(0)) + assertBn(await lido.balanceOf(voting), StETH(0)) + assertBn(await lido.balanceOf(burner.address), StETH(10)) - // ensure that excess amount is exactly 1 stETH - assertBn(await burner.getExcessStETH(), stETH(1)) + // ensure that excess amount is exactly 1 StETH + assertBn(await burner.getExcessStETH(), StETH(1)) // can't abuse recoverERC721 API to perform griefing-like attack - assertRevert(burner.recoverERC721(lido.address, stETH(1), { from: anotherAccount }), `TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE`) - assertRevert(burner.recoverERC721(lido.address, stETH(1), { from: deployer }), `TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE`) - assertRevert(burner.recoverERC721(lido.address, stETH(1), { from: voting }), `TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE`) + assertRevert(burner.recoverERC721(lido.address, StETH(1), { from: anotherAccount }), `TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE`) + assertRevert(burner.recoverERC721(lido.address, StETH(1), { from: deployer }), `TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE`) + assertRevert(burner.recoverERC721(lido.address, StETH(1), { from: voting }), `TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE`) const receipt = await burner.recoverExcessStETH({ from: anotherAccount }) assertEvent(receipt, `ExcessStETHRecovered`, { - expectedArgs: { requestedBy: anotherAccount, amount: stETH(1) } + expectedArgs: { requestedBy: anotherAccount, amount: StETH(1) } }) // ensure that excess amount is zero - assertBn(await burner.getExcessStETH(), stETH(0)) + assertBn(await burner.getExcessStETH(), StETH(0)) }) it(`can't recover zero-address ERC721(NFT)`, async () => { diff --git a/test/0.8.9/validator-exit-bus.test.js b/test/0.8.9/validator-exit-bus.test.js new file mode 100644 index 000000000..415fa6d80 --- /dev/null +++ b/test/0.8.9/validator-exit-bus.test.js @@ -0,0 +1,160 @@ +const { assertBn, assertRevert, assertEvent, assertAmountOfEvents } = require('@aragon/contract-helpers-test/src/asserts') +const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') +const { waitBlocks } = require('../helpers/blockchain') + +const { assert } = require('chai') + +const ValidatorExitBus = artifacts.require('ValidatorExitBusMock.sol') + +const ETH = (value) => web3.utils.toWei(value + '', 'ether') +// semantic aliases + +const e18 = 10 ** 18 +const blockDurationSeconds = 12 +const secondsInDay = 24 * 60 * 60 +const blocksInDay = secondsInDay / blockDurationSeconds + +const pad = (hex, bytesLength) => { + const absentZeroes = bytesLength * 2 + 2 - hex.length + if (absentZeroes > 0) hex = '0x' + '0'.repeat(absentZeroes) + hex.substr(2) + return hex +} + +function fromE18(value) { + return value / e18 +} + +function toE18(value) { + return bn(value.toString()).mul(bn(e18.toString())) +} + +function logE18(value) { + console.log(`${value / e18} (${value.toString()})`) +} + +function generateValidatorPubKey() { + const pubKeyLength = 48 + return pad('0x010203', pubKeyLength) +} + +const stakingModuleId = ZERO_ADDRESS + +function generateReportKeysArguments(numKeys, epochId) { + const stakingModuleIds = Array.from(Array(numKeys), () => stakingModuleId) + const validatorIds = Array.from(Array(numKeys), () => 123) + const nodeOperatorIds = Array.from(Array(numKeys), () => 1) + const keys = Array.from(Array(numKeys), () => generateValidatorPubKey()) + return [stakingModuleIds, nodeOperatorIds, validatorIds, keys, epochId] +} + +const maxRequestsPerDayE18 = toE18(2000 + 1) +const numRequestsLimitIncreasePerBlockE18 = maxRequestsPerDayE18.div(bn(blocksInDay)) + +function calcRateLimitParameters(maxRequestsPerDay) { + const maxRequestsPerDayE18 = toE18(maxRequestsPerDay) + return [toE18(maxRequestsPerDay), maxRequestsPerDayE18.div(bn(blocksInDay))] +} + +const GENESIS_TIME = 1606824000 + +contract.skip('ValidatorExitBus', ([deployer, member, owner]) => { + let bus = null + + beforeEach('deploy bus', async () => { + bus = await ValidatorExitBus.new({ from: deployer }) + + await bus.initialize(owner, ...calcRateLimitParameters(2000), 1, 32, 12, GENESIS_TIME, { from: owner }) + + await bus.setTime(GENESIS_TIME) + + // Set up the app's permissions. + await bus.grantRole(await bus.MANAGE_MEMBERS_ROLE(), owner, { from: owner }) + await bus.grantRole(await bus.MANAGE_QUORUM_ROLE(), owner, { from: owner }) + await bus.grantRole(await bus.SET_BEACON_SPEC_ROLE(), owner, { from: owner }) + + await bus.addOracleMember(member, { from: owner }) + await bus.updateQuorum(1, { from: owner }) + }) + + describe('Estimate gas usage', () => { + it.skip(`Calculate gas usages`, async () => { + let epochId = 1 + const gasUsage = {} + const amountsOfKeysToTry = [1, 3, 10, 40, 100] + let prevNumKeys = 0 + for (const numKeys of amountsOfKeysToTry) { + await waitBlocks(Math.ceil(prevNumKeys / fromE18(numRequestsLimitIncreasePerBlockE18))) + const args = generateReportKeysArguments(numKeys, epochId) + const result = await bus.handleCommitteeMemberReport(...args, { from: member }) + gasUsage[numKeys] = result.receipt.gasUsed + prevNumKeys = numKeys + epochId += 1 + } + + console.log(`==== Gas usage ====`) + for (const [numKeys, gasTotal] of Object.entries(gasUsage)) { + const usagePerKey = gasTotal / numKeys + console.log(`${numKeys}: ${usagePerKey} per key (${gasTotal} total)`) + } + console.log(`===================`) + }) + }) + + describe('Rate limit tests', () => { + it(`Report one key`, async () => { + const epochId = 1 + await bus.handleCommitteeMemberReport([stakingModuleId], [2], [123], [generateValidatorPubKey()], epochId, { from: member }) + }) + + it.skip(`Revert if length of arrays reported differ`, async () => { + // TODO + const epochId = 1 + await bus.handleCommitteeMemberReport([], [2], [generateValidatorPubKey()], epochId, { from: member }) + }) + + it(`Revert if exceeds limit after multiple consecutive tx`, async () => { + let epochId = 1 + const maxLimit = fromE18(await bus.getMaxLimit()) + let numKeysReportedTotal = 0 + const startBlockNumber = (await web3.eth.getBlock('latest')).number + + const keysPerIteration = 100 + while (maxLimit > numKeysReportedTotal) { + const numKeys = Math.min(keysPerIteration, maxLimit - numKeysReportedTotal) + await bus.handleCommitteeMemberReport(...generateReportKeysArguments(numKeys, epochId), { from: member }) + numKeysReportedTotal += numKeys + epochId += 1 + } + + const numBlocksPassed = (await web3.eth.getBlock('latest')).number - startBlockNumber + const numExcessKeys = Math.ceil(numBlocksPassed * fromE18(numRequestsLimitIncreasePerBlockE18)) + 1 + assertRevert(bus.handleCommitteeMemberReport(...generateReportKeysArguments(numExcessKeys, epochId), { from: member }), 'RATE_LIMIT') + }) + + it(`Report max amount of keys per tx`, async () => { + const epochId = 1 + await bus.setRateLimit(...calcRateLimitParameters(100)) + const maxLimit = fromE18(await bus.getMaxLimit()) + await bus.handleCommitteeMemberReport(...generateReportKeysArguments(maxLimit, epochId), { from: member }) + }) + + it.skip(`Revert if request to exit maxLimit+1 keys per tx`, async () => { + const epochId = 1 + await bus.setRateLimit(...calcRateLimitParameters(100)) + const maxRequestsPerDay = fromE18(await bus.getMaxLimit()) + assertRevert( + bus.handleCommitteeMemberReport(...generateReportKeysArguments(maxRequestsPerDay + 1, epochId), { from: member }), + 'RATE_LIMIT' + ) + }) + }) + + describe('Not responded validators tests', () => { + it(`Report not responded validator happy path`, async () => { + const epochId = 1 + await bus.setRateLimit(...calcRateLimitParameters(100)) + const maxLimit = fromE18(await bus.getMaxLimit()) + await bus.handleCommitteeMemberReport(...generateReportKeysArguments(maxLimit, epochId), { from: member }) + }) + }) +}) diff --git a/test/0.8.9/withdrawal-queue.test.js b/test/0.8.9/withdrawal-queue.test.js new file mode 100644 index 000000000..554be6051 --- /dev/null +++ b/test/0.8.9/withdrawal-queue.test.js @@ -0,0 +1,159 @@ +const { artifacts, contract } = require('hardhat') +const { bn } = require('@aragon/contract-helpers-test') +const { assertBn, assertRevert } = require('@aragon/contract-helpers-test/src/asserts') +const { assert } = require('chai') +const { ETH } = require('../helpers/utils') + +const WithdrawalQueue = artifacts.require('WithdrawalQueue.sol') +const Owner = artifacts.require('Owner.sol') +const StETHMock = artifacts.require('StETHMockForWithdrawalQueue.sol') +const WstETH = artifacts.require('WstETH.sol') +const OssifiableProxy = artifacts.require('OssifiableProxy.sol') + +contract('WithdrawalQueue', ([recipient, stranger, daoAgent]) => { + let withdrawal, withdrawalImpl, owner, steth, wsteth + + beforeEach('Deploy', async () => { + owner = (await Owner.new({ value: ETH(300) })).address + await ethers.provider.send('hardhat_impersonateAccount', [owner]) + steth = await StETHMock.new() + wsteth = await WstETH.new(steth.address) + + withdrawalImpl = (await WithdrawalQueue.new(owner, steth.address, wsteth.address)).address + console.log({ withdrawalImpl }) + const withdrawalProxy = await OssifiableProxy.new(withdrawalImpl, daoAgent, '0x') + withdrawal = await WithdrawalQueue.at(withdrawalProxy.address) + await withdrawal.initialize(daoAgent) + await withdrawal.resumeRequestsPlacement({ from: daoAgent }) + }) + + context('Enqueue', async () => { + let requestId + + beforeEach('Read some state', async () => { + requestId = await withdrawal.queueLength() + }) + + it('Owner can enqueue a request', async () => { + // await withdrawal.enqueue(recipient, ETH(1), 1, { from: owner }) + await withdrawal.requestWithdrawal(ETH(1), recipient, { from: owner }) + + assertBn(await withdrawal.queueLength(), +requestId + 1) + assert(requestId >= (await withdrawal.finalizedRequestsCounter())) + const request = await withdrawal.queue(requestId) + assert.equal(request.recipient, recipient) + assertBn(request.cumulativeEther, bn(ETH(1))) + assertBn(request.cumulativeShares, bn(1)) + assert.equal(request.claimed, false) + }) + + it('Only owner can enqueue a request', async () => { + await assertRevert(withdrawal.enqueue(recipient, 1, 1, { from: stranger }), 'NOT_OWNER') + + assertBn(await withdrawal.queueLength(), requestId) + }) + }) + + context('Finalization', async () => { + let requestId + const amount = ETH(100) + const shares = 1 + + beforeEach('Enqueue a request', async () => { + requestId = await withdrawal.queueLength() + await withdrawal.enqueue(recipient, amount, shares, { from: owner }) + }) + + it('Calculate one request batch', async () => { + const batch = await withdrawal.calculateFinalizationParams(0, ETH(100), 1) + + assertBn(bn(batch[0]), bn(ETH(100))) + assertBn(bn(batch[1]), bn(1)) + }) + + it('Only owner can finalize a request', async () => { + await withdrawal.finalize(0, amount, amount, shares, { from: owner, value: amount }) + await assertRevert(withdrawal.finalize(0, amount, amount, shares, { from: stranger, value: amount }), 'NOT_OWNER') + + assertBn(await withdrawal.lockedEtherAmount(), bn(amount)) + assertBn(await web3.eth.getBalance(withdrawal.address), bn(amount)) + }) + + it('One cannot finalize requests with no ether', async () => { + assertBn(await withdrawal.lockedEtherAmount(), bn(0)) + assertBn(await web3.eth.getBalance(withdrawal.address), bn(0)) + + await assertRevert( + withdrawal.finalize(0, amount, amount, shares, { from: owner, value: bn(ETH(100)).sub(bn(1)) }), + 'NOT_ENOUGH_ETHER' + ) + + assertBn(await withdrawal.lockedEtherAmount(), bn(0)) + assertBn(await web3.eth.getBalance(withdrawal.address), bn(0)) + }) + + it('One can finalize requests with discount', async () => { + await withdrawal.finalize(0, bn(amount / 2), amount, 2, { from: owner, value: amount / 2 }) + + assertBn(await withdrawal.lockedEtherAmount(), bn(amount / 2)) + }) + + it('One can finalize part of the queue', async () => { + await withdrawal.enqueue(recipient, amount, shares, { from: owner }) + + await withdrawal.finalize(0, amount, amount, shares, { from: owner, value: amount }) + + assertBn(await withdrawal.queueLength(), +requestId + 2) + assertBn(await withdrawal.finalizedRequestsCounter(), +requestId + 1) + assertBn(await withdrawal.lockedEtherAmount(), bn(amount)) + }) + + it('One can restake after finalization', async () => { + const balanceBefore = bn(await web3.eth.getBalance(owner)) + await withdrawal.finalize(0, amount, amount, shares, { from: owner, value: bn(amount).mul(bn(2)) }) + await withdrawal.restake(amount, { from: owner }) + + assertBn(await withdrawal.lockedEtherAmount(), bn(amount)) + assertBn(await web3.eth.getBalance(withdrawal.address), bn(amount)) + assertBn(await web3.eth.getBalance(owner), balanceBefore.sub(bn(amount))) + }) + }) + + context('Claim', async () => { + let requestId + const amount = ETH(100) + beforeEach('Enqueue a request', async () => { + requestId = await withdrawal.queueLength() + await withdrawal.enqueue(recipient, amount, 1, { from: owner }) + }) + + it('One cant claim not finalized request', async () => { + await assertRevert(withdrawal.claim(requestId, 0, { from: owner }), 'REQUEST_NOT_FINALIZED') + }) + + it('Anyone can claim a finalized token', async () => { + const balanceBefore = bn(await web3.eth.getBalance(recipient)) + await withdrawal.finalize(0, amount, amount, 1, { from: owner, value: amount }) + + await withdrawal.claim(requestId, 0, { from: stranger }) + + assertBn(await web3.eth.getBalance(recipient), balanceBefore.add(bn(amount))) + }) + + it('Cant withdraw token two times', async () => { + await withdrawal.finalize(0, amount, amount, 1, { from: owner, value: amount }) + await withdrawal.claim(requestId, 0) + + await assertRevert(withdrawal.claim(requestId, 0, { from: stranger }), 'REQUEST_ALREADY_CLAIMED') + }) + + it('Discounted withdrawals produce less eth', async () => { + const balanceBefore = bn(await web3.eth.getBalance(recipient)) + await withdrawal.finalize(0, ETH(50), ETH(100), 2, { from: owner, value: ETH(50) }) + + await withdrawal.claim(requestId, 0, { from: stranger }) + + assertBn(await web3.eth.getBalance(recipient), balanceBefore.add(bn(ETH(50)))) + }) + }) +}) diff --git a/test/deposit.test.js b/test/deposit.test.js index df18f7036..d086f3413 100644 --- a/test/deposit.test.js +++ b/test/deposit.test.js @@ -1,14 +1,16 @@ -const { newDao, newApp } = require('./0.4.24/helpers/dao') +const { artifacts } = require('hardhat') + const { assertBn, assertRevert } = require('@aragon/contract-helpers-test/src/asserts') const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') -const { BN } = require('bn.js') -const StETH = artifacts.require('StETH.sol') // we can just import due to StETH imported in test_helpers/Imports.sol -const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') +const { pad, hexConcat, ETH, changeEndianness } = require('./helpers/utils') +const { newDao, newApp } = require('./0.4.24/helpers/dao') +const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') const Lido = artifacts.require('LidoMock.sol') const OracleMock = artifacts.require('OracleMock.sol') const DepositContract = artifacts.require('DepositContract') +const VaultMock = artifacts.require('VaultMock.sol') const ADDRESS_1 = '0x0000000000000000000000000000000000000001' const ADDRESS_2 = '0x0000000000000000000000000000000000000002' @@ -17,45 +19,14 @@ const ADDRESS_4 = '0x0000000000000000000000000000000000000004' const UNLIMITED = 1000000000 -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 changeEndianness = (string) => { - string = string.replace('0x', '') - const result = [] - let len = string.length - 2 - while (len >= 0) { - result.push(string.substr(len, 2)) - len -= 2 - } - return '0x' + result.join('') -} - -// Divides a BN by 1e15 -const div15 = (bn) => bn.div(new BN(1000000)).div(new BN(1000000)).div(new BN(1000)) - -const ETH = (value) => web3.utils.toWei(value + '', 'ether') const tokens = ETH contract('Lido with official deposit contract', ([appManager, voting, user1, user2, user3, nobody, depositor]) => { - let appBase, stEthBase, nodeOperatorsRegistryBase, app, token, oracle, depositContract, operators - let treasuryAddr, insuranceAddr + let appBase, nodeOperatorsRegistryBase, app, token, oracle, depositContract, operators before('deploy base app', async () => { // Deploy the app's base contract. appBase = await Lido.new() - // stEthBase = await StETH.new() oracle = await OracleMock.new() nodeOperatorsRegistryBase = await NodeOperatorsRegistry.new() }) @@ -84,16 +55,12 @@ contract('Lido with official deposit contract', ([appManager, voting, user1, use await acl.createPermission(voting, app.address, await app.RESUME_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.MANAGE_FEE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.MANAGE_WITHDRAWAL_KEY(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.SET_EL_REWARDS_VAULT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.STAKING_CONTROL_ROLE(), appManager, { from: appManager }) - // await acl.createPermission(app.address, token.address, await token.MINT_ROLE(), appManager, { from: appManager }) - // await acl.createPermission(app.address, token.address, await token.BURN_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, operators.address, await operators.MANAGE_SIGNING_KEYS(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.ADD_NODE_OPERATOR_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.SET_NODE_OPERATOR_ACTIVE_ROLE(), appManager, { from: appManager }) @@ -108,12 +75,10 @@ contract('Lido with official deposit contract', ([appManager, voting, user1, use await acl.createPermission(depositor, app.address, await app.DEPOSIT_ROLE(), appManager, { from: appManager }) // Initialize the app's proxy. - await app.initialize(depositContract.address, oracle.address, operators.address) - treasuryAddr = await app.getTreasury() - insuranceAddr = await app.getInsuranceFund() + const treasury = await VaultMock.new() + await app.initialize(depositContract.address, oracle.address, operators.address, treasury.address, ZERO_ADDRESS, ZERO_ADDRESS) await oracle.setPool(app.address) - // await depositContract.reset() }) const checkStat = async ({ depositedValidators, beaconBalance }) => { @@ -122,23 +87,6 @@ contract('Lido with official deposit contract', ([appManager, voting, user1, use assertBn(stat.beaconBalance, beaconBalance, 'remote ether check') } - // Assert reward distribution. The values must be divided by 1e15. - const checkRewards = async ({ treasury, insurance, operator }) => { - const [treasury_b, insurance_b, operators_b, a1, a2, a3, a4] = await Promise.all([ - token.balanceOf(treasuryAddr), - token.balanceOf(insuranceAddr), - token.balanceOf(operators.address), - token.balanceOf(ADDRESS_1), - token.balanceOf(ADDRESS_2), - token.balanceOf(ADDRESS_3), - token.balanceOf(ADDRESS_4) - ]) - - assertBn(div15(treasury_b), treasury, 'treasury token balance check') - assertBn(div15(insurance_b), insurance, 'insurance fund token balance check') - assertBn(div15(operators_b.add(a1).add(a2).add(a3).add(a4)), operator, 'node operators token balance check') - } - it('deposit works', async () => { await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) @@ -277,7 +225,7 @@ contract('Lido with official deposit contract', ([appManager, voting, user1, use await operators.setNodeOperatorStakingLimit(3, UNLIMITED, { from: voting }) await app.setFee(5000, { from: voting }) - await app.setFeeDistribution(3000, 2000, 5000, { from: voting }) + await app.setFeeDistribution(3000, 7000, { from: voting }) // Deposit huge chunk await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(32 * 3 + 50) }) @@ -404,7 +352,7 @@ contract('Lido with official deposit contract', ([appManager, voting, user1, use await operators.setNodeOperatorStakingLimit(3, UNLIMITED, { from: voting }) await app.setFee(5000, { from: voting }) - await app.setFeeDistribution(3000, 2000, 5000, { from: voting }) + await app.setFeeDistribution(3000, 7000, { from: voting }) // Small deposits for (let i = 0; i < 14; i++) await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(10) }) @@ -534,7 +482,7 @@ contract('Lido with official deposit contract', ([appManager, voting, user1, use await operators.setNodeOperatorStakingLimit(3, UNLIMITED, { from: voting }) await app.setFee(5000, { from: voting }) - await app.setFeeDistribution(3000, 2000, 5000, { from: voting }) + await app.setFeeDistribution(3000, 7000, { from: voting }) // #1 and #0 get the funds await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(64) }) diff --git a/test/helpers/utils.js b/test/helpers/utils.js index bfb93399b..a2f2e4e81 100644 --- a/test/helpers/utils.js +++ b/test/helpers/utils.js @@ -1,4 +1,7 @@ const { BN } = require('bn.js') +const { isGeth } = require('@aragon/contract-helpers-test/src/node') +const { decodeErrorReasonFromTx } = require('@aragon/contract-helpers-test/src/decoding') +const { getEventAt } = require('@aragon/contract-helpers-test') const pad = (hex, bytesLength, fill = '0') => { const absentZeroes = bytesLength * 2 + 2 - hex.length @@ -59,6 +62,69 @@ function formatStEth(bn) { return ethers.utils.formatEther(ethers.utils.parseUnits(bn.toString(), 'wei'), { commify: true }) + ' stETH' } +// Copy paste from node_modules/@aragon/contract-helpers-test/src/asserts/assertThrow.js +async function assertThrows(blockOrPromise, expectedErrorCode, expectedReason, ctx) { + try { + typeof blockOrPromise === 'function' ? await blockOrPromise() : await blockOrPromise + } catch (error) { + if (await isGeth(ctx)) { + // With geth, we are only provided the transaction receipt and have to decode the failure + // ourselves. + const status = error.receipt.status + + assert.equal(status, '0x0', `Expected transaction to revert but it executed with status ${status}`) + if (!expectedReason.length) { + // Note that it is difficult to ascertain invalid jumps or out of gas scenarios + // and so we simply pass if no revert message is given + return + } + + const { tx } = error + assert.notEqual(tx, undefined, `Expected error to include transaction hash, cannot assert revert reason ${expectedReason}: ${error}`) + + error.reason = decodeErrorReasonFromTx(tx, ctx) + return error + } else { + const errorMatchesExpected = error.message.search(expectedErrorCode) > -1 + assert(errorMatchesExpected, `Expected error code "${expectedErrorCode}" but failed with "${error}" instead.`) + return error + } + } + // assert.fail() for some reason does not have its error string printed 🤷 + assert(false, `Expected "${expectedErrorCode}"${expectedReason ? ` (with reason: "${expectedReason}")` : ''} but it did not fail`) +} + +async function assertRevertCustomError(blockOrPromise, expectedError, ctx) { + const error = await assertThrows(blockOrPromise, 'revert', expectedError, ctx) + + if (!expectedError) { + return + } + + const expectedMessage = `VM Exception while processing transaction: reverted with custom error '${expectedError}()'` + assert.equal( + error.message, + expectedMessage, + `Expected revert with custom error "${expectedError}()" but failed with "${error.message}" instead.` + ) +} + +const assertNoEvent = (receipt, eventName, msg) => { + const event = getEventAt(receipt, eventName) + assert.equal(event, undefined, msg) +} + +const changeEndianness = (string) => { + string = string.replace('0x', '') + const result = [] + let len = string.length - 2 + while (len >= 0) { + result.push(string.substr(len, 2)) + len -= 2 + } + return '0x' + result.join('') +} + module.exports = { pad, hexConcat, @@ -66,8 +132,12 @@ module.exports = { toBN, div15, ETH, + StETH: ETH, tokens, getEthBalance, formatBN, - formatStEth: formatStEth + formatStEth, + assertRevertCustomError, + assertNoEvent, + changeEndianness } diff --git a/test/scenario/execution_layer_rewards_after_the_merge.js b/test/scenario/execution_layer_rewards_after_the_merge.js index 1f8199dac..6bfbf8891 100644 --- a/test/scenario/execution_layer_rewards_after_the_merge.js +++ b/test/scenario/execution_layer_rewards_after_the_merge.js @@ -3,16 +3,13 @@ const { BN } = require('bn.js') const { assertBn } = require('@aragon/contract-helpers-test/src/asserts') const { getEventArgument } = require('@aragon/contract-helpers-test') -const { pad, toBN, ETH, tokens, hexConcat } = require('../helpers/utils') +const { pad, toBN, ETH, tokens } = require('../helpers/utils') const { deployDaoAndPool } = require('./helpers/deploy') const { signDepositData } = require('../0.8.9/helpers/signatures') const { waitBlocks } = require('../helpers/blockchain') -const addresses = require('@aragon/contract-helpers-test/src/addresses') -const LidoELRewardsVault = artifacts.require('LidoExecutionLayerRewardsVault.sol') const RewardEmulatorMock = artifacts.require('RewardEmulatorMock.sol') - const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') const TOTAL_BASIS_POINTS = 10000 @@ -38,20 +35,15 @@ contract('Lido: merge acceptance', (addresses) => { let pool, nodeOperatorRegistry, token let oracleMock, depositContractMock - let treasuryAddr, insuranceAddr, guardians + let treasuryAddr, guardians let depositSecurityModule, depositRoot let rewarder, elRewardsVault + let withdrawalCredentials // Total fee is 1% const totalFeePoints = 0.01 * TOTAL_BASIS_POINTS - // Of this 1%, 30% goes to the treasury const treasuryFeePoints = 0.3 * TOTAL_BASIS_POINTS - // 20% goes to the insurance fund - const insuranceFeePoints = 0.2 * TOTAL_BASIS_POINTS - // 50% goes to node operators - const nodeOperatorsFeePoints = 0.5 * TOTAL_BASIS_POINTS - - const withdrawalCredentials = pad('0x0202', 32) + const nodeOperatorsFeePoints = 0.7 * TOTAL_BASIS_POINTS // 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 @@ -99,15 +91,12 @@ contract('Lido: merge acceptance', (addresses) => { // addresses treasuryAddr = deployed.treasuryAddr - insuranceAddr = deployed.insuranceAddr depositSecurityModule = deployed.depositSecurityModule guardians = deployed.guardians + elRewardsVault = deployed.elRewardsVault depositRoot = await depositContractMock.get_deposit_root() - elRewardsVault = await LidoELRewardsVault.new(pool.address, treasuryAddr) - await pool.setELRewardsVault(elRewardsVault.address, { from: voting }) - // At first go through tests assuming there is no withdrawal limit await pool.setELRewardsWithdrawalLimit(TOTAL_BASIS_POINTS, { from: voting }) @@ -119,7 +108,7 @@ contract('Lido: merge acceptance', (addresses) => { // Fee and its distribution are in basis points, 10000 corresponding to 100% await pool.setFee(totalFeePoints, { from: voting }) - await pool.setFeeDistribution(treasuryFeePoints, insuranceFeePoints, nodeOperatorsFeePoints, { from: voting }) + await pool.setFeeDistribution(treasuryFeePoints, nodeOperatorsFeePoints, { from: voting }) // Fee and distribution were set @@ -127,9 +116,9 @@ contract('Lido: merge acceptance', (addresses) => { const distribution = await pool.getFeeDistribution({ from: nobody }) assertBn(distribution.treasuryFeeBasisPoints, treasuryFeePoints, 'treasury fee') - assertBn(distribution.insuranceFeeBasisPoints, insuranceFeePoints, 'insurance fee') assertBn(distribution.operatorsFeeBasisPoints, nodeOperatorsFeePoints, 'node operators fee') + withdrawalCredentials = pad('0x0202', 32) await pool.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) // Withdrawal credentials were set @@ -410,9 +399,7 @@ contract('Lido: merge acceptance', (addresses) => { // Fee, in the form of minted tokens, was distributed between treasury, insurance fund // and node operators // treasuryTokenBalance ~= mintedAmount * treasuryFeePoints / 10000 - // insuranceTokenBalance ~= mintedAmount * insuranceFeePoints / 10000 assertBn(await token.balanceOf(treasuryAddr), new BN('123000000000000001'), 'treasury tokens') - assertBn(await token.balanceOf(insuranceAddr), new BN('81999999999999999'), 'insurance 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 @@ -421,8 +408,8 @@ contract('Lido: merge acceptance', (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), new BN('102499999999999999'), 'operator_1 tokens') - assertBn(await token.balanceOf(nodeOperator2.address), new BN('102499999999999999'), 'operator_2 tokens') + assertBn(await token.balanceOf(nodeOperator1.address), new BN('143499999999999998'), 'operator_1 tokens') + assertBn(await token.balanceOf(nodeOperator2.address), new BN('143499999999999998'), 'operator_2 tokens') // Real minted amount should be a bit less than calculated caused by round errors on mint and transfer operations assert( @@ -430,7 +417,6 @@ contract('Lido: merge acceptance', (addresses) => { .sub( new BN(0) .add(await token.balanceOf(treasuryAddr)) - .add(await token.balanceOf(insuranceAddr)) .add(await token.balanceOf(nodeOperator1.address)) .add(await token.balanceOf(nodeOperator2.address)) .add(await token.balanceOf(nodeOperatorRegistry.address)) @@ -498,9 +484,8 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(await token.balanceOf(user3), new BN('95385865829971612132'), 'user3 tokens') assertBn(await token.balanceOf(treasuryAddr), new BN('129239130434782610'), 'treasury tokens') - assertBn(await token.balanceOf(insuranceAddr), new BN('86159420289855071'), 'insurance tokens') - assertBn(await token.balanceOf(nodeOperator1.address), new BN('107699275362318839'), 'operator_1 tokens') - assertBn(await token.balanceOf(nodeOperator2.address), new BN('107699275362318839'), 'operator_2 tokens') + assertBn(await token.balanceOf(nodeOperator1.address), new BN('150778985507246375'), 'operator_1 tokens') + assertBn(await token.balanceOf(nodeOperator2.address), new BN('150778985507246375'), 'operator_2 tokens') }) it('collect another 5 ETH execution layer rewards to the vault', async () => { @@ -556,9 +541,8 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(await token.balanceOf(user3), new BN('97359366502315852383'), 'user3 tokens') assertBn(await token.balanceOf(treasuryAddr), new BN('131913043478260871'), 'treasury tokens') - assertBn(await token.balanceOf(insuranceAddr), new BN('87942028985507245'), 'insurance tokens') - assertBn(await token.balanceOf(nodeOperator1.address), new BN('109927536231884057'), 'operator_1 tokens') - assertBn(await token.balanceOf(nodeOperator2.address), new BN('109927536231884057'), 'operator_2 tokens') + assertBn(await token.balanceOf(nodeOperator1.address), new BN('153898550724637679'), 'operator_1 tokens') + assertBn(await token.balanceOf(nodeOperator2.address), new BN('153898550724637679'), 'operator_2 tokens') }) it('collect another 3 ETH execution layer rewards to the vault', async () => { @@ -612,9 +596,8 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(await token.balanceOf(user3), new BN('97359366502315852383'), 'user3 tokens') assertBn(await token.balanceOf(treasuryAddr), new BN('131913043478260871'), 'treasury tokens') - assertBn(await token.balanceOf(insuranceAddr), new BN('87942028985507245'), 'insurance tokens') - assertBn(await token.balanceOf(nodeOperator1.address), new BN('109927536231884057'), 'operator_1 tokens') - assertBn(await token.balanceOf(nodeOperator2.address), new BN('109927536231884057'), 'operator_2 tokens') + assertBn(await token.balanceOf(nodeOperator1.address), new BN('153898550724637679'), 'operator_1 tokens') + assertBn(await token.balanceOf(nodeOperator2.address), new BN('153898550724637679'), 'operator_2 tokens') }) it('collect another 2 ETH execution layer rewards to the vault', async () => { @@ -666,9 +649,8 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(await token.balanceOf(user3), new BN('93412365157627371881'), 'user3 tokens') assertBn(await token.balanceOf(treasuryAddr), new BN('126565217391304349'), 'treasury tokens') - assertBn(await token.balanceOf(insuranceAddr), new BN('84376811594202897'), 'insurance tokens') - assertBn(await token.balanceOf(nodeOperator1.address), new BN('105471014492753622'), 'operator_1 tokens') - assertBn(await token.balanceOf(nodeOperator2.address), new BN('105471014492753622'), 'operator_2 tokens') + assertBn(await token.balanceOf(nodeOperator1.address), new BN('147659420289855071'), 'operator_1 tokens') + assertBn(await token.balanceOf(nodeOperator2.address), new BN('147659420289855071'), 'operator_2 tokens') }) it('collect another 3 ETH execution layer rewards to the vault', async () => { @@ -723,8 +705,6 @@ contract('Lido: merge acceptance', (addresses) => { // and node operators // treasuryTokenBalance = (oldTreasuryShares + mintedRewardShares * treasuryFeePoints / 10000) * sharePrice assertBn((await token.balanceOf(treasuryAddr)).divn(10), new BN('14597717391304348'), 'treasury tokens') - // should preserver treasuryFeePoints/insuranceFeePoints ratio - assertBn((await token.balanceOf(insuranceAddr)).divn(10), new BN('9731811594202898'), 'insurance 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 @@ -732,8 +712,8 @@ contract('Lido: merge acceptance', (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)).divn(10), new BN('12164764492753623'), 'operator_1 tokens') - assertBn((await token.balanceOf(nodeOperator2.address)).divn(10), new BN('12164764492753623'), 'operator_2 tokens') + assertBn((await token.balanceOf(nodeOperator1.address)).divn(10), new BN('17030670289855072'), 'operator_1 tokens') + assertBn((await token.balanceOf(nodeOperator2.address)).divn(10), new BN('17030670289855072'), 'operator_2 tokens') }) it('collect 0.1 ETH execution layer rewards to elRewardsVault and withdraw it entirely by means of multiple oracle reports (+1 ETH)', async () => { diff --git a/test/scenario/helpers/deploy.js b/test/scenario/helpers/deploy.js index b6334e503..5ec1c6201 100644 --- a/test/scenario/helpers/deploy.js +++ b/test/scenario/helpers/deploy.js @@ -1,15 +1,16 @@ +const { ZERO_ADDRESS } = require('@aragon/contract-helpers-test') +const { artifacts } = require('hardhat') + const { newDao, newApp } = require('../../0.4.24/helpers/dao') const Lido = artifacts.require('LidoMock.sol') +const VaultMock = artifacts.require('VaultMock.sol') +const LidoELRewardsVault = artifacts.require('LidoExecutionLayerRewardsVault.sol') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') const OracleMock = artifacts.require('OracleMock.sol') const DepositContractMock = artifacts.require('DepositContractMock.sol') const DepositSecurityModule = artifacts.require('DepositSecurityModule.sol') -module.exports = { - deployDaoAndPool -} - const NETWORK_ID = 1000 const MAX_DEPOSITS_PER_BLOCK = 100 const MIN_DEPOSIT_BLOCK_DISTANCE = 20 @@ -74,7 +75,6 @@ async function deployDaoAndPool(appManager, voting) { DEPOSIT_ROLE, STAKING_PAUSE_ROLE, STAKING_CONTROL_ROLE, - SET_EL_REWARDS_VAULT_ROLE, SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE, NODE_OPERATOR_REGISTRY_MANAGE_SIGNING_KEYS, NODE_OPERATOR_REGISTRY_ADD_NODE_OPERATOR_ROLE, @@ -92,7 +92,6 @@ async function deployDaoAndPool(appManager, voting) { pool.DEPOSIT_ROLE(), pool.STAKING_PAUSE_ROLE(), pool.STAKING_CONTROL_ROLE(), - pool.SET_EL_REWARDS_VAULT_ROLE(), pool.SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE(), nodeOperatorRegistry.MANAGE_SIGNING_KEYS(), nodeOperatorRegistry.ADD_NODE_OPERATOR_ROLE(), @@ -112,7 +111,6 @@ async function deployDaoAndPool(appManager, voting) { acl.createPermission(voting, pool.address, POOL_BURN_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, STAKING_PAUSE_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, STAKING_CONTROL_ROLE, appManager, { from: appManager }), - acl.createPermission(voting, pool.address, SET_EL_REWARDS_VAULT_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE, appManager, { from: appManager }), // Allow depositor to deposit buffered ether @@ -142,13 +140,23 @@ async function deployDaoAndPool(appManager, voting) { }) ]) - await pool.initialize(depositContractMock.address, oracleMock.address, nodeOperatorRegistry.address) + treasury = await VaultMock.new() + elRewardsVault = await LidoELRewardsVault.new(pool.address, treasury.address) + + await pool.initialize( + depositContractMock.address, + oracleMock.address, + nodeOperatorRegistry.address, + treasury.address, + elRewardsVault.address, + ZERO_ADDRESS + ) await oracleMock.setPool(pool.address) await depositContractMock.reset() await depositContractMock.set_deposit_root(DEPOSIT_ROOT) - const [treasuryAddr, insuranceAddr] = await Promise.all([pool.getTreasury(), pool.getInsuranceFund()]) + const treasuryAddr = await pool.getTreasury() return { dao, @@ -159,7 +167,7 @@ async function deployDaoAndPool(appManager, voting) { pool, nodeOperatorRegistry, treasuryAddr, - insuranceAddr, + elRewardsVault, depositSecurityModule, guardians: { privateKeys: GUARDIAN_PRIVATE_KEYS, @@ -167,3 +175,7 @@ async function deployDaoAndPool(appManager, voting) { } } } + +module.exports = { + deployDaoAndPool +} diff --git a/test/scenario/lido_deposit_iteration_limit.js b/test/scenario/lido_deposit_iteration_limit.js index 281fc6d27..e0b302c28 100644 --- a/test/scenario/lido_deposit_iteration_limit.js +++ b/test/scenario/lido_deposit_iteration_limit.js @@ -48,7 +48,7 @@ contract('Lido: deposit loop iteration limit', (addresses) => { depositRoot = await depositContractMock.get_deposit_root() await pool.setFee(0.01 * 10000, { from: voting }) - await pool.setFeeDistribution(0.3 * 10000, 0.2 * 10000, 0.5 * 10000, { from: voting }) + await pool.setFeeDistribution(0.3 * 10000, 0.7 * 10000, { from: voting }) await pool.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) await depositSecurityModule.setMaxDeposits(10, { from: appManager }) assertBn(await depositSecurityModule.getMaxDeposits(), 10, 'invariant failed: max deposits') diff --git a/test/scenario/lido_happy_path.js b/test/scenario/lido_happy_path.js index 828e81212..6da075a8f 100644 --- a/test/scenario/lido_happy_path.js +++ b/test/scenario/lido_happy_path.js @@ -31,10 +31,11 @@ contract('Lido: happy path', (addresses) => { let pool, nodeOperatorRegistry, token let oracleMock, depositContractMock - let treasuryAddr, insuranceAddr, guardians + let treasuryAddr, guardians let depositSecurityModule, depositRoot + let withdrawalCredentials - it('DAO, node operators registry, token, pool and deposit security module are deployed and initialized', async () => { + before('DAO, node operators registry, token, pool and deposit security module are deployed and initialized', async () => { const deployed = await deployDaoAndPool(appManager, voting) // contracts/StETH.sol @@ -53,11 +54,14 @@ contract('Lido: happy path', (addresses) => { // addresses treasuryAddr = deployed.treasuryAddr - insuranceAddr = deployed.insuranceAddr depositSecurityModule = deployed.depositSecurityModule guardians = deployed.guardians depositRoot = await depositContractMock.get_deposit_root() + + withdrawalCredentials = pad('0x0202', 32) + + await pool.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) }) // Fee and its distribution are in basis points, 10000 corresponding to 100% @@ -65,16 +69,12 @@ contract('Lido: happy path', (addresses) => { // Total fee is 1% const totalFeePoints = 0.01 * 10000 - // Of this 1%, 30% goes to the treasury const treasuryFeePoints = 0.3 * 10000 - // 20% goes to the insurance fund - const insuranceFeePoints = 0.2 * 10000 - // 50% goes to node operators - const nodeOperatorsFeePoints = 0.5 * 10000 + const nodeOperatorsFeePoints = 0.7 * 10000 it('voting sets fee and its distribution', async () => { await pool.setFee(totalFeePoints, { from: voting }) - await pool.setFeeDistribution(treasuryFeePoints, insuranceFeePoints, nodeOperatorsFeePoints, { from: voting }) + await pool.setFeeDistribution(treasuryFeePoints, nodeOperatorsFeePoints, { from: voting }) // Fee and distribution were set @@ -82,12 +82,9 @@ contract('Lido: happy path', (addresses) => { const distribution = await pool.getFeeDistribution({ from: nobody }) assertBn(distribution.treasuryFeeBasisPoints, treasuryFeePoints, 'treasury fee') - assertBn(distribution.insuranceFeeBasisPoints, insuranceFeePoints, 'insurance fee') assertBn(distribution.operatorsFeeBasisPoints, nodeOperatorsFeePoints, 'node operators fee') }) - const withdrawalCredentials = pad('0x0202', 32) - it('voting sets withdrawal credentials', async () => { await pool.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) @@ -402,9 +399,7 @@ contract('Lido: happy path', (addresses) => { // Fee, in the form of minted tokens, was distributed between treasury, insurance fund // and node operators // treasuryTokenBalance ~= mintedAmount * treasuryFeePoints / 10000 - // insuranceTokenBalance ~= mintedAmount * insuranceFeePoints / 10000 - assertBn(await token.balanceOf(treasuryAddr), new BN('96000000000000001'), 'treasury tokens') - assertBn(await token.balanceOf(insuranceAddr), new BN('63999999999999998'), 'insurance tokens') + assertBn(await token.balanceOf(treasuryAddr), new BN('96000000000000000'), 'treasury tokens') // The node operators' fee is distributed between all active node operators, // proprotional to their effective stake (the amount of Ether staked by the operator's @@ -413,8 +408,8 @@ 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), new BN('79999999999999999'), 'operator_1 tokens') - assertBn(await token.balanceOf(nodeOperator2.address), new BN('79999999999999999'), 'operator_2 tokens') + assertBn(await token.balanceOf(nodeOperator1.address), new BN('111999999999999999'), 'operator_1 tokens') + assertBn(await token.balanceOf(nodeOperator2.address), new BN('111999999999999999'), 'operator_2 tokens') // Real minted amount should be a bit less than calculated caused by round errors on mint and transfer operations assert( @@ -422,7 +417,6 @@ contract('Lido: happy path', (addresses) => { .sub( new BN(0) .add(await token.balanceOf(treasuryAddr)) - .add(await token.balanceOf(insuranceAddr)) .add(await token.balanceOf(nodeOperator1.address)) .add(await token.balanceOf(nodeOperator2.address)) .add(await token.balanceOf(nodeOperatorRegistry.address)) diff --git a/test/scenario/lido_penalties_slashing.js b/test/scenario/lido_penalties_slashing.js index c3f092ccd..6a7fa5db5 100644 --- a/test/scenario/lido_penalties_slashing.js +++ b/test/scenario/lido_penalties_slashing.js @@ -30,8 +30,9 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { let oracleMock, depositContractMock let treasuryAddr, insuranceAddr, guardians let depositSecurityModule, depositRoot + let withdrawalCredentials - it('DAO, node operators registry, token, pool and deposit security module are deployed and initialized', async () => { + before('DAO, node operators registry, token, pool and deposit security module are deployed and initialized', async () => { const deployed = await deployDaoAndPool(appManager, voting, depositor) // contracts/StETH.sol @@ -55,6 +56,8 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { guardians = deployed.guardians depositRoot = await depositContractMock.get_deposit_root() + withdrawalCredentials = pad('0x0202', 32) + await pool.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) }) // Fee and its distribution are in basis points, 10000 corresponding to 100% @@ -62,19 +65,15 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { // Total fee is 1% const totalFeePoints = 0.01 * 10000 - // Of this 1%, 30% goes to the treasury const treasuryFeePoints = 0.3 * 10000 - // 20% goes to the insurance fund - const insuranceFeePoints = 0.2 * 10000 - // 50% goes to node operators - const nodeOperatorsFeePoints = 0.5 * 10000 + const nodeOperatorsFeePoints = 0.7 * 10000 let awaitingTotalShares = new BN(0) let awaitingUser1Balance = new BN(0) it('voting sets fee and its distribution', async () => { await pool.setFee(totalFeePoints, { from: voting }) - await pool.setFeeDistribution(treasuryFeePoints, insuranceFeePoints, nodeOperatorsFeePoints, { from: voting }) + await pool.setFeeDistribution(treasuryFeePoints, nodeOperatorsFeePoints, { from: voting }) // Fee and distribution were set @@ -82,12 +81,9 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { const distribution = await pool.getFeeDistribution({ from: nobody }) assertBn(distribution.treasuryFeeBasisPoints, treasuryFeePoints, 'treasury fee') - assertBn(distribution.insuranceFeeBasisPoints, insuranceFeePoints, 'insurance fee') assertBn(distribution.operatorsFeeBasisPoints, nodeOperatorsFeePoints, 'node operators fee') }) - const withdrawalCredentials = pad('0x0202', 32) - it('voting sets withdrawal credentials', async () => { await pool.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) @@ -286,7 +282,6 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { // No fees distributed yet assertBn(await token.balanceOf(treasuryAddr), new BN(0), 'treasury tokens') - assertBn(await token.balanceOf(insuranceAddr), new BN(0), 'insurance tokens') assertBn(await token.balanceOf(nodeOperator1.address), new BN(0), 'operator_1 tokens') }) @@ -333,7 +328,6 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { // No fees distributed yet assertBn(await token.balanceOf(treasuryAddr), new BN(0), 'no treasury tokens on no reward') - assertBn(await token.balanceOf(insuranceAddr), new BN(0), 'no insurance tokens on no reward') assertBn(await token.balanceOf(nodeOperator1.address), new BN(0), 'no operator_1 reward') }) @@ -475,7 +469,6 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { // No fees distributed yet assertBn(await token.balanceOf(treasuryAddr), new BN(0), 'no treasury tokens on no reward') - assertBn(await token.balanceOf(insuranceAddr), new BN(0), 'no insurance 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`) @@ -609,7 +602,6 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { it(`without active node operators node operator's fee is sent to treasury`, async () => { const nodeOperator1TokenSharesBefore = await token.sharesOf(nodeOperator1.address) const nodeOperator2TokenSharesBefore = await token.sharesOf(nodeOperator2.address) - const insuranceSharesBefore = await token.sharesOf(insuranceAddr) const treasurySharesBefore = await token.sharesOf(treasuryAddr) const prevTotalShares = await token.getTotalShares() @@ -617,7 +609,6 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { const nodeOperator1TokenSharesAfter = await token.sharesOf(nodeOperator1.address) const nodeOperator2TokenSharesAfter = await token.sharesOf(nodeOperator2.address) - const insuranceSharesAfter = await token.sharesOf(insuranceAddr) const treasurySharesAfter = await token.sharesOf(treasuryAddr) const totalPooledEther = await token.getTotalPooledEther() @@ -628,18 +619,16 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { const totalFeeToDistribute = new BN(ETH(2)).mul(new BN(totalFeePoints)).div(tenKBN) const sharesToMint = totalFeeToDistribute.mul(prevTotalShares).div(totalPooledEther.sub(totalFeeToDistribute)) - const insuranceFee = sharesToMint.mul(new BN(insuranceFeePoints)).div(tenKBN) - const treasuryFeeTotalMinusInsurance = sharesToMint.sub(insuranceFee) + const treasuryFeeTotalMinusInsurance = sharesToMint const treasuryFeeTotalTreasuryPlusNodeOperators = sharesToMint .mul(new BN(treasuryFeePoints).add(new BN(nodeOperatorsFeePoints))) .div(tenKBN) - assertBn(insuranceSharesAfter.sub(insuranceSharesBefore), insuranceFee, 'insurance got the regular fee') assertBn(treasurySharesAfter.sub(treasurySharesBefore), treasuryFeeTotalMinusInsurance, 'treasury got the total fee - insurance fee') assertBn( treasurySharesAfter.sub(treasurySharesBefore), - treasuryFeeTotalTreasuryPlusNodeOperators.add(new BN(1)), + treasuryFeeTotalTreasuryPlusNodeOperators, 'treasury got the regular fee + node operators fee' ) }) diff --git a/test/scenario/lido_rewards_distribution_math.js b/test/scenario/lido_rewards_distribution_math.js index 4756a95ed..cda09c663 100644 --- a/test/scenario/lido_rewards_distribution_math.js +++ b/test/scenario/lido_rewards_distribution_math.js @@ -19,10 +19,8 @@ const totalFeePoints = 0.01 * 10000 // Of this 1%, 30% goes to the treasury const treasuryFeePoints = 0.3 * 10000 -// 20% goes to the insurance fund -const insuranceFeePoints = 0.2 * 10000 // 50% goes to node operators -const nodeOperatorsFeePoints = 0.5 * 10000 +const nodeOperatorsFeePoints = 0.7 * 10000 contract('Lido: rewards distribution math', (addresses) => { const [ @@ -36,18 +34,15 @@ contract('Lido: rewards distribution math', (addresses) => { // users who deposit Ether to the pool user1, user2, - user3, // unrelated address nobody ] = addresses let pool, nodeOperatorRegistry, token let oracleMock - let treasuryAddr, insuranceAddr, guardians + let treasuryAddr, guardians let depositSecurityModule, depositRoot - const withdrawalCredentials = pad('0x0202', 32) - // 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 const nodeOperator1 = { @@ -100,20 +95,18 @@ contract('Lido: rewards distribution math', (addresses) => { // addresses treasuryAddr = deployed.treasuryAddr - insuranceAddr = deployed.insuranceAddr depositSecurityModule = deployed.depositSecurityModule guardians = deployed.guardians depositRoot = await deployed.depositContractMock.get_deposit_root() await pool.setFee(totalFeePoints, { from: voting }) - await pool.setFeeDistribution(treasuryFeePoints, insuranceFeePoints, nodeOperatorsFeePoints, { from: voting }) - await pool.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) + await pool.setFeeDistribution(treasuryFeePoints, nodeOperatorsFeePoints, { from: voting }) + await pool.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) }) - it(`initial treasury & insurance balances are zero`, async () => { + it(`initial treasury balance are zero`, async () => { assertBn(await token.balanceOf(treasuryAddr), new BN(0), 'treasury balance is zero') - assertBn(await token.balanceOf(insuranceAddr), new BN(0), 'insurance balance is zero') }) it(`registers one node operator with one key`, async () => { @@ -160,10 +153,6 @@ contract('Lido: rewards distribution math', (addresses) => { const depostitEthValue = 34 const depositAmount = ETH(depostitEthValue) - // const receipt = await web3.eth.sendTransaction({ value: depositAmount, from: user1, to: pool.address }) - // const transferValue = getEventArgument(receipt, 'Transfer', 'value', { decodeForAbi: Lido._json.abi }) - // const transferFrom = getEventArgument(receipt, 'Transfer', 'from', { decodeForAbi: Lido._json.abi }) - // const transferTo = getEventArgument(receipt, 'Transfer', 'to', { decodeForAbi: Lido._json.abi }) const receipt = await pool.submit(ZERO_ADDRESS, { value: depositAmount, from: user1 }) assertEvent(receipt, 'Transfer', { expectedArgs: { from: 0, to: user1, value: depositAmount } }) @@ -184,7 +173,6 @@ contract('Lido: rewards distribution math', (addresses) => { assertBn(await token.getTotalShares(), depositAmount, 'total shares') assertBn(await token.balanceOf(treasuryAddr), new BN(0), 'treasury balance is zero') - assertBn(await token.balanceOf(insuranceAddr), new BN(0), 'insurance balance is zero') assertBn(await token.balanceOf(nodeOperator1.address), new BN(0), 'nodeOperator1 balance is zero') }) @@ -217,7 +205,6 @@ contract('Lido: rewards distribution math', (addresses) => { assertBn(await token.totalSupply(), ETH(34), 'token total supply') assertBn(await token.balanceOf(treasuryAddr), ETH(0), 'treasury balance equals buffered value') - assertBn(await token.balanceOf(insuranceAddr), new BN(0), 'insurance balance is zero') assertBn(await token.balanceOf(nodeOperator1.address), new BN(0), 'nodeOperator1 balance is zero') }) @@ -227,49 +214,24 @@ contract('Lido: rewards distribution math', (addresses) => { const reportingValue = ETH(32 + profitAmountEth) const prevTotalShares = await pool.getTotalShares() // for some reason there's nothing in this receipt's log, so we're not going to use it - const [{ receipt }, deltas] = await getSharesTokenDeltas( - () => reportBeacon(1, reportingValue), - treasuryAddr, - insuranceAddr, - nodeOperator1.address, - user1 - ) + const [, deltas] = await getSharesTokenDeltas(() => reportBeacon(1, reportingValue), treasuryAddr, nodeOperator1.address) - const [ - treasuryTokenDelta, - treasurySharesDelta, - insuranceTokenDelta, - insuranceSharesDelta, - nodeOperator1TokenDelta, - nodeOperator1SharesDelta, - user1TokenDelta, - user1SharesDelta - ] = deltas + const [treasuryTokenDelta, treasurySharesDelta, nodeOperator1TokenDelta, nodeOperator1SharesDelta] = deltas const { reportedMintAmount, tos, values } = await readLastPoolEventLog() - const { - totalFeeToDistribute, - insuranceSharesToMint, - nodeOperatorsSharesToMint, - treasurySharesToMint, - insuranceFeeToMint, - nodeOperatorsFeeToMint, - treasuryFeeToMint - } = await getAwaitedFeesSharesTokensDeltas(profitAmount, prevTotalShares, 1) + const { totalFeeToDistribute, nodeOperatorsSharesToMint, treasurySharesToMint, nodeOperatorsFeeToMint, treasuryFeeToMint } = + await getAwaitedFeesSharesTokensDeltas(profitAmount, prevTotalShares, 1) - assertBn(insuranceSharesDelta, insuranceSharesToMint, 'insurance shares are correct') assertBn(nodeOperator1SharesDelta, nodeOperatorsSharesToMint, 'nodeOperator1 shares are correct') assertBn(treasurySharesDelta, treasurySharesToMint, 'treasury shares are correct') - assertBn(insuranceFeeToMint.add(nodeOperatorsFeeToMint).add(treasuryFeeToMint), reportedMintAmount, 'reported the expected total fee') + assertBn(nodeOperatorsFeeToMint.add(treasuryFeeToMint), reportedMintAmount, 'reported the expected total fee') - assert.equal(tos[0], insuranceAddr, 'first transfer to insurance address') - assertBn(values[0], insuranceFeeToMint, 'insurance transfer amount is correct') - assert.equal(tos[1], nodeOperator1.address, 'second transfer to node operator') - assertBn(values[1], nodeOperatorsFeeToMint, 'operator transfer amount is correct') - assert.equal(tos[2], treasuryAddr, 'third transfer to treasury address') - assertBn(values[2], treasuryFeeToMint, 'treasury transfer amount is correct') + assert.equal(tos[0], nodeOperator1.address, 'second transfer to node operator') + assertBn(values[0], nodeOperatorsFeeToMint, 'operator transfer amount is correct') + assert.equal(tos[1], treasuryAddr, 'third transfer to treasury address') + assertBn(values[1], treasuryFeeToMint, 'treasury transfer amount is correct') // URURU assertBn( await token.balanceOf(user1), @@ -282,8 +244,6 @@ contract('Lido: rewards distribution math', (addresses) => { assertBn(await token.balanceOf(treasuryAddr), treasuryFeeToMint, 'treasury balance = fee') assertBn(treasuryTokenDelta, treasuryFeeToMint, 'treasury balance = fee') - assertBn(await token.balanceOf(insuranceAddr), insuranceFeeToMint, 'insurance balance = fee') - assertBn(insuranceTokenDelta, insuranceFeeToMint, 'insurance balance = fee') assertBn(await token.balanceOf(nodeOperator1.address), nodeOperatorsFeeToMint, 'nodeOperator1 balance = fee') assertBn(nodeOperator1TokenDelta, nodeOperatorsFeeToMint, 'nodeOperator1 balance = fee') }) @@ -369,7 +329,6 @@ contract('Lido: rewards distribution math', (addresses) => { const [_, deltas] = await getSharesTokenDeltas( () => depositSecurityModule.depositBufferedEther(depositRoot, keysOpIndex, block.number, block.hash, signatures), treasuryAddr, - insuranceAddr, nodeOperator1.address, nodeOperator2.address, user1, @@ -386,7 +345,6 @@ contract('Lido: rewards distribution math', (addresses) => { const [_, deltas] = await getSharesTokenDeltas( () => reportBeacon(2, ETH(32 + 1 + 32)), treasuryAddr, - insuranceAddr, nodeOperator1.address, nodeOperator2.address, user1, @@ -411,7 +369,6 @@ contract('Lido: rewards distribution math', (addresses) => { const [{ valuesBefore, valuesAfter }, deltas] = await getSharesTokenDeltas( () => reportBeacon(2, reportingValue), treasuryAddr, - insuranceAddr, nodeOperator1.address, nodeOperator2.address, user1, @@ -421,8 +378,6 @@ contract('Lido: rewards distribution math', (addresses) => { const [ treasuryTokenDelta, treasurySharesDelta, - insuranceTokenDelta, - insuranceSharesDelta, nodeOperator1TokenDelta, nodeOperator1SharesDelta, nodeOperator2TokenDelta, @@ -435,33 +390,23 @@ contract('Lido: rewards distribution math', (addresses) => { const { reportedMintAmount, tos, values } = await readLastPoolEventLog() - const { - sharesToMint, - insuranceSharesToMint, - nodeOperatorsSharesToMint, - treasurySharesToMint, - insuranceFeeToMint, - nodeOperatorsFeeToMint, - treasuryFeeToMint - } = await getAwaitedFeesSharesTokensDeltas(profitAmount, prevTotalShares, 2) + const { sharesToMint, nodeOperatorsSharesToMint, treasurySharesToMint, nodeOperatorsFeeToMint, treasuryFeeToMint } = + await getAwaitedFeesSharesTokensDeltas(profitAmount, prevTotalShares, 2) // events are ok - assert.equal(tos[0], insuranceAddr, 'first transfer to insurance address') - assert.equal(tos[1], nodeOperator1.address, 'second transfer to node operator 1') - assert.equal(tos[2], nodeOperator2.address, 'third transfer to node operator 2') - assert.equal(tos[3], treasuryAddr, 'third transfer to treasury address') + assert.equal(tos[0], nodeOperator1.address, 'second transfer to node operator 1') + assert.equal(tos[1], nodeOperator2.address, 'third transfer to node operator 2') + assert.equal(tos[2], treasuryAddr, 'third transfer to treasury address') - assertBn(values[0], insuranceFeeToMint, 'insurance transfer amount is correct') - assertBn(values[1].add(values[2]), nodeOperatorsFeeToMint, 'operator transfer amount is correct') - assertBn(values[3], treasuryFeeToMint, 'treasury transfer amount is correct') + assertBn(values[0].add(values[1]), nodeOperatorsFeeToMint, 'operator transfer amount is correct') + assertBn(values[2], treasuryFeeToMint, 'treasury transfer amount is correct') - const totalFeeToMint = insuranceFeeToMint.add(nodeOperatorsFeeToMint).add(treasuryFeeToMint) + const totalFeeToMint = nodeOperatorsFeeToMint.add(treasuryFeeToMint) assertBn(totalFeeToMint, reportedMintAmount, 'reported the expected total fee') assertBn(nodeOperator2SharesDelta, await pool.sharesOf(nodeOperator2.address), 'node operator 2 got only fee on balance') - assertBn(insuranceSharesDelta, insuranceSharesToMint, 'insurance shares are correct') assertBn(nodeOperator1SharesDelta.add(nodeOperator2SharesDelta), nodeOperatorsSharesToMint, 'nodeOperator1 shares are correct') assertBn(treasurySharesDelta, treasurySharesToMint, 'treasury shares are correct') @@ -474,25 +419,18 @@ contract('Lido: rewards distribution math', (addresses) => { const treasuryBalanceAfter = valuesAfter[0] const treasuryShareBefore = valuesBefore[1] - const insuranceBalanceAfter = valuesAfter[2] - const insuraceShareBefore = valuesBefore[3] - const nodeOperator1BalanceAfter = valuesAfter[4] - const nodeOperator1ShareBefore = valuesBefore[5] - const nodeOperator2BalanceAfter = valuesAfter[6] - const nodeOperator2ShareBefore = valuesBefore[7] - const user1BalanceAfter = valuesAfter[8] - const user1SharesBefore = valuesBefore[9] - const user2BalanceAfter = valuesAfter[10] - const user2SharesBefore = valuesBefore[11] + const nodeOperator1BalanceAfter = valuesAfter[2] + const nodeOperator1ShareBefore = valuesBefore[3] + const nodeOperator2BalanceAfter = valuesAfter[4] + const nodeOperator2ShareBefore = valuesBefore[5] + const user1BalanceAfter = valuesAfter[6] + const user1SharesBefore = valuesBefore[7] + const user2BalanceAfter = valuesAfter[8] + const user2SharesBefore = valuesBefore[9] const singleNodeOperatorFeeShare = nodeOperatorsSharesToMint.div(new BN(2)) const awaitingTotalShares = prevTotalShares.add(sharesToMint) - assertBn( - insuranceBalanceAfter, - insuraceShareBefore.add(insuranceSharesToMint).mul(totalSupply).div(awaitingTotalShares), - 'insurance token balance changed correctly' - ) assertBn( nodeOperator1BalanceAfter, nodeOperator1ShareBefore.add(singleNodeOperatorFeeShare).mul(totalSupply).div(awaitingTotalShares), @@ -521,11 +459,8 @@ contract('Lido: rewards distribution math', (addresses) => { const totalFeeToDistribute = new BN(profitAmount).mul(new BN(totalFeePoints)).div(tenKBN) const sharesToMint = totalFeeToDistribute.mul(prevTotalShares).div(totalPooledEther.sub(totalFeeToDistribute)) - const insuranceSharesToMint = sharesToMint.mul(new BN(insuranceFeePoints)).div(tenKBN) const nodeOperatorsSharesToMint = sharesToMint.mul(new BN(nodeOperatorsFeePoints)).div(tenKBN) - const treasurySharesToMint = sharesToMint.sub(insuranceSharesToMint).sub(nodeOperatorsSharesToMint) - - const insuranceFeeToMint = insuranceSharesToMint.mul(totalPooledEther).div(totalShares) + const treasurySharesToMint = sharesToMint.sub(nodeOperatorsSharesToMint) const validatorsCountBN = new BN(validatorsCount) @@ -541,10 +476,8 @@ contract('Lido: rewards distribution math', (addresses) => { totalShares, totalFeeToDistribute, sharesToMint, - insuranceSharesToMint, nodeOperatorsSharesToMint, treasurySharesToMint, - insuranceFeeToMint, nodeOperatorsFeeToMint, treasuryFeeToMint } diff --git a/test/scenario/lido_withdrawals.js b/test/scenario/lido_withdrawals.js new file mode 100644 index 000000000..137d62f68 --- /dev/null +++ b/test/scenario/lido_withdrawals.js @@ -0,0 +1,112 @@ +const { assert } = require('chai') +const { assertEvent, assertRevert, assertBn } = require('@aragon/contract-helpers-test/src/asserts') + +const { web3, artifacts } = require('hardhat') +const { pad, hexConcat, StETH, ETH } = require('../helpers/utils') +const { deployDaoAndPool } = require('./helpers/deploy') +const { ZERO_ADDRESS } = require('@aragon/contract-helpers-test') + +const WithdrawalQueue = artifacts.require('WithdrawalQueue.sol') +const WstETH = artifacts.require('WstETH.sol') + +contract('Lido: withdrawals', (addresses) => { + const [ + // the root account which deployed the DAO + appManager, + // the address which we use to simulate the voting DAO application + voting, + // address that withdaws ether from the pool + recipient + ] = addresses + + let pool, token, wsteth + let oracle + let withdrawalCredentials, withdrawalQueue + + before('DAO deployed', async () => { + const deployed = await deployDaoAndPool(appManager, voting) + + // contracts/StETH.sol + token = deployed.pool + + // contracts/Lido.sol + pool = deployed.pool + wsteth = await WstETH.new(pool.address) + await pool.resumeProtocolAndStaking() + + // mocks + oracle = deployed.oracleMock + // unlock oracle account (allow transactions originated from oracle.address) + await ethers.provider.send('hardhat_impersonateAccount', [oracle.address]) + + await web3.eth.sendTransaction({ to: pool.address, from: recipient, value: ETH(3) }) + + withdrawalQueue = await WithdrawalQueue.new(pool.address, pool.address, wsteth.address) + withdrawalCredentials = hexConcat('0x01', pad(withdrawalQueue.address, 31)).toLowerCase() + }) + + it('setWithdrawalCredentials', async () => { + assert.equal(await pool.getWithdrawalVaultAddress(), ZERO_ADDRESS) + assertRevert(pool.requestWithdrawal(StETH(3), { from: recipient }), 'ZERO_WITHDRAWAL_ADDRESS') + assertRevert(pool.claimWithdrawal(0, 0), 'ZERO_WITHDRAWAL_ADDRESS') + + await pool.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) + assert.equal(await pool.getWithdrawalCredentials(), withdrawalCredentials) + assert.equal(await pool.getWithdrawalVaultAddress(), withdrawalQueue.address) + }) + + context('requestWithdrawal', async () => { + const amount = StETH(1) + + it('put one request', async () => { + const receipt = await pool.requestWithdrawal(amount, { from: recipient }) + + const id = (await withdrawalQueue.queueLength()) - 1 + + assertEvent(receipt, 'WithdrawalRequested', { + expectedArgs: { + recipient: recipient, + ethAmount: amount, + sharesAmount: await pool.getSharesByPooledEth(amount), + requestId: id + } + }) + + const status = await pool.withdrawalRequestStatus(id) + + assert.equal(status.recipient, recipient) + assert.equal(status.isClaimed, false) + assert.equal(status.isFinalized, false) + assertBn(status.etherToWithdraw, amount) + }) + + it('cant claim no-finalized', async () => { + assertRevert(pool.claimWithdrawal(0, 0), 'REQUEST_NOT_FINALIZED') + }) + + it('another two requests', async () => { + await Promise.all([pool.requestWithdrawal(amount, { from: recipient }), pool.requestWithdrawal(amount, { from: recipient })]) + + assert.equal(await withdrawalQueue.queueLength(), 3) + + const [one, two, three] = await Promise.all([ + await pool.withdrawalRequestStatus(0), + await pool.withdrawalRequestStatus(1), + await pool.withdrawalRequestStatus(2) + ]) + + assert.ok(one.requestBlockNumber < two.requestBlockNumber) + assert.ok(two.requestBlockNumber < three.requestBlockNumber) + }) + }) + + context('handleOracleReport', async () => { + it('auth', async () => { + assertRevert(pool.handleOracleReport(0, 0, 0, 0, [], []), 'APP_AUTH_FAILED') + }) + + it('zero report', async () => { + await pool.handleOracleReport(0, 0, 0, 0, [], [], { from: oracle.address }) + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index 4208f7e24..f9a3e8809 100644 --- a/yarn.lock +++ b/yarn.lock @@ -49,8 +49,8 @@ __metadata: "@aragon/apps-lido@lidofinance/aragon-apps#master": version: 1.0.0 - resolution: "@aragon/apps-lido@https://github.com/lidofinance/aragon-apps.git#commit=1433962e399fbdc8bd772c69ad21315c3cd34a7b" - checksum: 83b54f0e9c400e8e07e6457002baa3d38d9eb298e19b3bec5ef8e86c04f12e7e078f57909948f80b4cf3d89c18b4c35fb760504e7e77087eadd33cbd5a6dd5d9 + resolution: "@aragon/apps-lido@https://github.com/lidofinance/aragon-apps.git#commit=b09834d29c0db211ddd50f50905cbeff257fc8e0" + checksum: 7a6339f01377ab2ed2d5fbd7c335970585513a84999202700d010be27d89355d1ada2668f4af399c75d269bf473ba94fd56fb534cb1fb497b54e3faf570b83d5 languageName: node linkType: hard @@ -1720,6 +1720,25 @@ __metadata: languageName: node linkType: hard +"@electron/get@npm:^2.0.0": + version: 2.0.2 + resolution: "@electron/get@npm:2.0.2" + dependencies: + debug: ^4.1.1 + env-paths: ^2.2.0 + fs-extra: ^8.1.0 + global-agent: ^3.0.0 + got: ^11.8.5 + progress: ^2.0.3 + semver: ^6.2.0 + sumchecker: ^3.0.1 + dependenciesMeta: + global-agent: + optional: true + checksum: fb2e52d5ba14e40ab04e341f584be4f3f8b7b02b5b1325b6bf3c00efe3f3b48e538c5e8d64e36f60ed74eb0752cdc54fcdcae9976a1280ab4e991567d6fa6ff1 + languageName: node + linkType: hard + "@emotion/is-prop-valid@npm:^0.8.8": version: 0.8.8 resolution: "@emotion/is-prop-valid@npm:0.8.8" @@ -1898,7 +1917,24 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abi@npm:5.0.7, @ethersproject/abi@npm:^5.0.0-beta.146, @ethersproject/abi@npm:^5.0.5": +"@ethersproject/abi@npm:5.7.0, @ethersproject/abi@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/abi@npm:5.7.0" + dependencies: + "@ethersproject/address": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/hash": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + checksum: 230f2ed6dec5a81516cde5851c0c68777a5aafbc7b2e9a11d7c7b954e14193014916c7826b2bbb2f4d0c5cd3a0cd8c56699fa67ce4a8ff579ba96e2a90f4d580 + languageName: node + linkType: hard + +"@ethersproject/abi@npm:^5.0.0-beta.146": version: 5.0.7 resolution: "@ethersproject/abi@npm:5.0.7" dependencies: @@ -1932,18 +1968,18 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abstract-provider@npm:5.0.5, @ethersproject/abstract-provider@npm:^5.0.4": - version: 5.0.5 - resolution: "@ethersproject/abstract-provider@npm:5.0.5" +"@ethersproject/abstract-provider@npm:5.7.0, @ethersproject/abstract-provider@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/abstract-provider@npm:5.7.0" dependencies: - "@ethersproject/bignumber": ^5.0.7 - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/logger": ^5.0.5 - "@ethersproject/networks": ^5.0.3 - "@ethersproject/properties": ^5.0.3 - "@ethersproject/transactions": ^5.0.5 - "@ethersproject/web": ^5.0.6 - checksum: e5ae08cf76261681b79a9a0199c49390d72b0f0e70bca918de192d1db0edc4d11ae4ba7cd132917b914f713e204603849492b803b4225d0146823c9d8092039c + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/networks": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + "@ethersproject/web": ^5.7.0 + checksum: 419a46490b4e46d7b1e46c46d112be087637e07650351dc77136b5c62e17cc17fe604bb68ff35a1029ae6abd5c2d76ce85ffa1b402e0950e8b06815d4a1414e8 languageName: node linkType: hard @@ -1962,16 +1998,16 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abstract-signer@npm:5.0.7, @ethersproject/abstract-signer@npm:^5.0.4, @ethersproject/abstract-signer@npm:^5.0.6": - version: 5.0.7 - resolution: "@ethersproject/abstract-signer@npm:5.0.7" +"@ethersproject/abstract-signer@npm:5.7.0, @ethersproject/abstract-signer@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/abstract-signer@npm:5.7.0" dependencies: - "@ethersproject/abstract-provider": ^5.0.4 - "@ethersproject/bignumber": ^5.0.7 - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/logger": ^5.0.5 - "@ethersproject/properties": ^5.0.3 - checksum: 09a54474e3e283cfba37ddc8393a9d8e3a72f23e441d92e045061356a14a107d68a98161b037cc7f48d3d3f8b8dbc139f8905d28d6d1756b8f4e7031af8513dc + "@ethersproject/abstract-provider": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + checksum: 4d9ba03489b428e093ee514240234dcb2b1b85788880daa9d089f94052eb0e100da341a5f45a7d851b042fe7c0c31bf58d0a90aae2060f80149be5590b28a964 languageName: node linkType: hard @@ -1988,7 +2024,20 @@ __metadata: languageName: node linkType: hard -"@ethersproject/address@npm:5.0.5, @ethersproject/address@npm:>=5.0.0-beta.128, @ethersproject/address@npm:^5.0.4, @ethersproject/address@npm:^5.0.5": +"@ethersproject/address@npm:5.7.0, @ethersproject/address@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/address@npm:5.7.0" + dependencies: + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/rlp": ^5.7.0 + checksum: f7c7c8c64f8ad6669ea9ccd97c3f8362afea68a2fd701050dbef1f7c7b585e48f177d648b008e41b5c142b9a9ec93e9f99fdfe9ec87a8278667284e627f50b4d + languageName: node + linkType: hard + +"@ethersproject/address@npm:>=5.0.0-beta.128, @ethersproject/address@npm:^5.0.4": version: 5.0.5 resolution: "@ethersproject/address@npm:5.0.5" dependencies: @@ -2028,12 +2077,12 @@ __metadata: languageName: node linkType: hard -"@ethersproject/base64@npm:5.0.4, @ethersproject/base64@npm:^5.0.3": - version: 5.0.4 - resolution: "@ethersproject/base64@npm:5.0.4" +"@ethersproject/base64@npm:5.7.0, @ethersproject/base64@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/base64@npm:5.7.0" dependencies: - "@ethersproject/bytes": ^5.0.4 - checksum: e055ec1fcaa7dd1684f37505f99201a5a7144cf17577f5652403bbed94280ae47798cfb546f51d6f06fb6bcc33d0e9a96d6f32e531c75193775c3cfed3779ba7 + "@ethersproject/bytes": ^5.7.0 + checksum: ee994aab596a9de12f71616bb7c5cb16b8fcb5e5f86279befefe5465d53ab10e78241d1dc6a6daf202aed80a8b3515da6f7dcabb05d4bd15a29f75999759b755 languageName: node linkType: hard @@ -2046,17 +2095,28 @@ __metadata: languageName: node linkType: hard -"@ethersproject/basex@npm:5.0.4, @ethersproject/basex@npm:^5.0.3": - version: 5.0.4 - resolution: "@ethersproject/basex@npm:5.0.4" +"@ethersproject/basex@npm:5.7.0, @ethersproject/basex@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/basex@npm:5.7.0" dependencies: - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/properties": ^5.0.3 - checksum: 075ae16f18640efcb2463b4a03b4cfe493000204a7a85e9e1dc30f8982bd1dd8f56cb4ccc77fe0ec662e66bffb24e37aff01296e10fa848688ac53f43158dfda + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + checksum: 4cedf00cac26e021c27ba218ce9268476fba6a5c87ab90a9ffd4c2f0dc481bf1e39f16a07605605035c6e7182fda30b3d56482d6b10c7829bb9d5c8dae922cb7 languageName: node linkType: hard -"@ethersproject/bignumber@npm:5.0.8, @ethersproject/bignumber@npm:>=5.0.0-beta.130, @ethersproject/bignumber@npm:^5.0.7, @ethersproject/bignumber@npm:^5.0.8": +"@ethersproject/bignumber@npm:5.7.0, @ethersproject/bignumber@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/bignumber@npm:5.7.0" + dependencies: + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + bn.js: ^5.2.1 + checksum: 98983fe18e3d484f4879951f9c4bc01afc82c9627016e4ecec29ddb4dffdc4580b2bc6b0870ac4b0395f082b53c7cf49b144f15807435d654ab3796f2b8aa26d + languageName: node + linkType: hard + +"@ethersproject/bignumber@npm:>=5.0.0-beta.130, @ethersproject/bignumber@npm:^5.0.7": version: 5.0.8 resolution: "@ethersproject/bignumber@npm:5.0.8" dependencies: @@ -2089,7 +2149,16 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bytes@npm:5.0.5, @ethersproject/bytes@npm:>=5.0.0-beta.129, @ethersproject/bytes@npm:^5.0.4": +"@ethersproject/bytes@npm:5.7.0, @ethersproject/bytes@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/bytes@npm:5.7.0" + dependencies: + "@ethersproject/logger": ^5.7.0 + checksum: 6b1454482018a24512126be078538c3db5b46390385dbfb3cd9999cbdd20268b32dc27dca3053fcc8df250f44a2a8dbec92ad9f2221c7ec8a6355f0c994ecad9 + languageName: node + linkType: hard + +"@ethersproject/bytes@npm:>=5.0.0-beta.129, @ethersproject/bytes@npm:^5.0.4": version: 5.0.5 resolution: "@ethersproject/bytes@npm:5.0.5" dependencies: @@ -2107,7 +2176,16 @@ __metadata: languageName: node linkType: hard -"@ethersproject/constants@npm:5.0.5, @ethersproject/constants@npm:>=5.0.0-beta.128, @ethersproject/constants@npm:^5.0.4": +"@ethersproject/constants@npm:5.7.0, @ethersproject/constants@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/constants@npm:5.7.0" + dependencies: + "@ethersproject/bignumber": ^5.7.0 + checksum: 490107e84d071a8ff8ef8587ce6ec92adc3fd53fb0a81cb8de564c29f08500f5c37a5a5d0481c597856c9108aa912ce8bc30146c5cfd7c1a45b32a9b1f372dbf + languageName: node + linkType: hard + +"@ethersproject/constants@npm:>=5.0.0-beta.128, @ethersproject/constants@npm:^5.0.4": version: 5.0.5 resolution: "@ethersproject/constants@npm:5.0.5" dependencies: @@ -2125,36 +2203,38 @@ __metadata: languageName: node linkType: hard -"@ethersproject/contracts@npm:5.0.5": - version: 5.0.5 - resolution: "@ethersproject/contracts@npm:5.0.5" +"@ethersproject/contracts@npm:5.7.0": + version: 5.7.0 + resolution: "@ethersproject/contracts@npm:5.7.0" dependencies: - "@ethersproject/abi": ^5.0.5 - "@ethersproject/abstract-provider": ^5.0.4 - "@ethersproject/abstract-signer": ^5.0.4 - "@ethersproject/address": ^5.0.4 - "@ethersproject/bignumber": ^5.0.7 - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/constants": ^5.0.4 - "@ethersproject/logger": ^5.0.5 - "@ethersproject/properties": ^5.0.3 - checksum: 0d52a47896a7bcbc1370ad7d3d60a93582ac89dac0ae6e92dad96a46bc977c56202510c35eab40f31530ae26dda7f0a0fdbba03dd620082d6bd9e81b452572fa + "@ethersproject/abi": ^5.7.0 + "@ethersproject/abstract-provider": ^5.7.0 + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + checksum: 10280eabd018504538a526a2d79ffcd9f22b813b91a4145204fff956d660b66b31a26e094be51f32ba42456f3023cd92bbf2b197af0aad948218698bc4c1fe94 languageName: node linkType: hard -"@ethersproject/hash@npm:5.0.6": - version: 5.0.6 - resolution: "@ethersproject/hash@npm:5.0.6" +"@ethersproject/hash@npm:5.7.0, @ethersproject/hash@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/hash@npm:5.7.0" dependencies: - "@ethersproject/abstract-signer": ^5.0.6 - "@ethersproject/address": ^5.0.5 - "@ethersproject/bignumber": ^5.0.8 - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/keccak256": ^5.0.3 - "@ethersproject/logger": ^5.0.5 - "@ethersproject/properties": ^5.0.4 - "@ethersproject/strings": ^5.0.4 - checksum: 52d47bd537db9c86e67f495c0a4304a76c4d7c3a8009d80b7e4d0cb01611b6cea05d4a733aa90a8865243f18097c7b56b6835d72ce0476a395480cf18c8d81e5 + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/base64": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + checksum: 63b3dc44512ccf9b10c0d5fb98aadacc5012a01937129a088a874cc81a66f9ca2ece8488d9b32fa0ce9b188d9a46778447a2de6c11c7481aa1b9a2ec77339f51 languageName: node linkType: hard @@ -2186,48 +2266,58 @@ __metadata: languageName: node linkType: hard -"@ethersproject/hdnode@npm:5.0.5, @ethersproject/hdnode@npm:^5.0.4": - version: 5.0.5 - resolution: "@ethersproject/hdnode@npm:5.0.5" - dependencies: - "@ethersproject/abstract-signer": ^5.0.4 - "@ethersproject/basex": ^5.0.3 - "@ethersproject/bignumber": ^5.0.7 - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/logger": ^5.0.5 - "@ethersproject/pbkdf2": ^5.0.3 - "@ethersproject/properties": ^5.0.3 - "@ethersproject/sha2": ^5.0.3 - "@ethersproject/signing-key": ^5.0.4 - "@ethersproject/strings": ^5.0.4 - "@ethersproject/transactions": ^5.0.5 - "@ethersproject/wordlists": ^5.0.4 - checksum: cb757944f4fd11cf68365c3c26a515af50daf4e6ad11404af5d796595b776de05ce07e33ee96361535a1d3e0c18c25fec32b4b8aace96b3676140c47f6477848 +"@ethersproject/hdnode@npm:5.7.0, @ethersproject/hdnode@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/hdnode@npm:5.7.0" + dependencies: + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/basex": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/pbkdf2": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/sha2": ^5.7.0 + "@ethersproject/signing-key": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + "@ethersproject/wordlists": ^5.7.0 + checksum: 40ed1d4c7e8a02ef8eed03b0c3d0f4f805aab4c74437f53f50b572f733db5e7ea70175db50c666227bf65a2bda053bb1ef31ede4c79489a1613186fb70756a81 + languageName: node + linkType: hard + +"@ethersproject/json-wallets@npm:5.7.0, @ethersproject/json-wallets@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/json-wallets@npm:5.7.0" + dependencies: + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/hdnode": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/pbkdf2": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/random": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + aes-js: 3.0.0 + scrypt-js: 3.0.1 + checksum: 5ea3c081df3f7ccca5c8bf0f5b24732ab07ffdea8bbd361de1810f83d57c77a219fa45645b10ce1db9d4f934a5d2d5b6293ebc44ec4d9dc10e3cef74cca3aaff languageName: node linkType: hard -"@ethersproject/json-wallets@npm:5.0.7, @ethersproject/json-wallets@npm:^5.0.6": - version: 5.0.7 - resolution: "@ethersproject/json-wallets@npm:5.0.7" +"@ethersproject/keccak256@npm:5.7.0, @ethersproject/keccak256@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/keccak256@npm:5.7.0" dependencies: - "@ethersproject/abstract-signer": ^5.0.4 - "@ethersproject/address": ^5.0.4 - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/hdnode": ^5.0.4 - "@ethersproject/keccak256": ^5.0.3 - "@ethersproject/logger": ^5.0.5 - "@ethersproject/pbkdf2": ^5.0.3 - "@ethersproject/properties": ^5.0.3 - "@ethersproject/random": ^5.0.3 - "@ethersproject/strings": ^5.0.4 - "@ethersproject/transactions": ^5.0.5 - aes-js: 3.0.0 - scrypt-js: 3.0.1 - checksum: 67aa0e0aeae28ad97456a189fe28254dad3f510df006b9ada3e95eb5d2a02fc1ee271d12d46becd9f85c08cba1427608dbb837c053b8f4b669d5bb8343a5e76b + "@ethersproject/bytes": ^5.7.0 + js-sha3: 0.8.0 + checksum: da5a71e40a57dfa3bacc791779b9eb77269fe08cd0fcc91aeb7d35762b40715f4f88f8a7a08b4b5fab9f2cef529d782ac61008fc75ac3b33e0640b95fb166a6e languageName: node linkType: hard -"@ethersproject/keccak256@npm:5.0.4, @ethersproject/keccak256@npm:>=5.0.0-beta.127, @ethersproject/keccak256@npm:^5.0.3": +"@ethersproject/keccak256@npm:>=5.0.0-beta.127, @ethersproject/keccak256@npm:^5.0.3": version: 5.0.4 resolution: "@ethersproject/keccak256@npm:5.0.4" dependencies: @@ -2247,7 +2337,14 @@ __metadata: languageName: node linkType: hard -"@ethersproject/logger@npm:5.0.6, @ethersproject/logger@npm:>=5.0.0-beta.129, @ethersproject/logger@npm:^5.0.5": +"@ethersproject/logger@npm:5.7.0, @ethersproject/logger@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/logger@npm:5.7.0" + checksum: f4a598cc3d1307081edfc594c72499dc03092664ad01234a99947648f0a5b1f5ca42c467f8b8ae2788ccdcf67bd7847387cbe68332716690c92e4181c30a2a04 + languageName: node + linkType: hard + +"@ethersproject/logger@npm:>=5.0.0-beta.129, @ethersproject/logger@npm:^5.0.5": version: 5.0.6 resolution: "@ethersproject/logger@npm:5.0.6" checksum: 44982a2df0084a5a053b84fe0a010f990c9b95f675413be9e357e27fa31cbae76b60d5dc4449a6b30cca38c4cb77743154f586cf4fea9b39b45b8874a94e553d @@ -2261,12 +2358,12 @@ __metadata: languageName: node linkType: hard -"@ethersproject/networks@npm:5.0.4, @ethersproject/networks@npm:^5.0.3": - version: 5.0.4 - resolution: "@ethersproject/networks@npm:5.0.4" +"@ethersproject/networks@npm:5.7.1, @ethersproject/networks@npm:^5.7.0": + version: 5.7.1 + resolution: "@ethersproject/networks@npm:5.7.1" dependencies: - "@ethersproject/logger": ^5.0.5 - checksum: 06fb103169028c4883b7f18cf0c6fcf7b314d171c6559a15bf0d81551632b1fa9d76c3b58e7104cc4203703ceb3a7363552bf8825bf41f9d71fd6a53a44847a6 + "@ethersproject/logger": ^5.7.0 + checksum: e5da2d51e1bf4b25515f8256fbeb3a52c224afa1c73c724a33ad13b3b1eaaba75b9a654963d6b714149975f351a9b691b2d51b3435e57efd820125ee5a4541ad languageName: node linkType: hard @@ -2279,17 +2376,26 @@ __metadata: languageName: node linkType: hard -"@ethersproject/pbkdf2@npm:5.0.4, @ethersproject/pbkdf2@npm:^5.0.3": - version: 5.0.4 - resolution: "@ethersproject/pbkdf2@npm:5.0.4" +"@ethersproject/pbkdf2@npm:5.7.0, @ethersproject/pbkdf2@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/pbkdf2@npm:5.7.0" dependencies: - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/sha2": ^5.0.3 - checksum: b61f8f9ba98f654776f75cf94c55521a2b6dbacfff742300aa4b9c0ed3b8166dcf5804192612479d4775b34bad59e80373629cc6fef6fa4ee7439d4938195a11 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/sha2": ^5.7.0 + checksum: 096865f1c6702f1c8e0e30e1179396adcedcf12a1f22c9de4f846046dbd40239c62ce42506db87fe9848ab54cdfbef9cb768c374d0229c5ddcb05f9b3e62f04b + languageName: node + linkType: hard + +"@ethersproject/properties@npm:5.7.0, @ethersproject/properties@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/properties@npm:5.7.0" + dependencies: + "@ethersproject/logger": ^5.7.0 + checksum: 71a4e4a74b4efad2fdb9c0b6fe1d826138cf8a6d55f1e52534d5dbf2935857b15297ec2bbda128541fa68d35e2ac83db15978346bfbe91928e1f9ac0bb218488 languageName: node linkType: hard -"@ethersproject/properties@npm:5.0.4, @ethersproject/properties@npm:>=5.0.0-beta.131, @ethersproject/properties@npm:^5.0.3, @ethersproject/properties@npm:^5.0.4": +"@ethersproject/properties@npm:>=5.0.0-beta.131, @ethersproject/properties@npm:^5.0.3": version: 5.0.4 resolution: "@ethersproject/properties@npm:5.0.4" dependencies: @@ -2307,44 +2413,55 @@ __metadata: languageName: node linkType: hard -"@ethersproject/providers@npm:5.0.14": - version: 5.0.14 - resolution: "@ethersproject/providers@npm:5.0.14" - dependencies: - "@ethersproject/abstract-provider": ^5.0.4 - "@ethersproject/abstract-signer": ^5.0.4 - "@ethersproject/address": ^5.0.4 - "@ethersproject/basex": ^5.0.3 - "@ethersproject/bignumber": ^5.0.7 - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/constants": ^5.0.4 - "@ethersproject/hash": ^5.0.4 - "@ethersproject/logger": ^5.0.5 - "@ethersproject/networks": ^5.0.3 - "@ethersproject/properties": ^5.0.3 - "@ethersproject/random": ^5.0.3 - "@ethersproject/rlp": ^5.0.3 - "@ethersproject/sha2": ^5.0.3 - "@ethersproject/strings": ^5.0.4 - "@ethersproject/transactions": ^5.0.5 - "@ethersproject/web": ^5.0.6 +"@ethersproject/providers@npm:5.7.2": + version: 5.7.2 + resolution: "@ethersproject/providers@npm:5.7.2" + dependencies: + "@ethersproject/abstract-provider": ^5.7.0 + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/base64": ^5.7.0 + "@ethersproject/basex": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/hash": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/networks": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/random": ^5.7.0 + "@ethersproject/rlp": ^5.7.0 + "@ethersproject/sha2": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + "@ethersproject/web": ^5.7.0 bech32: 1.1.4 - ws: 7.2.3 - checksum: e54877d8c2a2108005388c24003048a6129308c3e3908238ba4db4782c6e4eb3119dadf1fdd8b7875c5d49396d2d77e21de9755dbfd3b64fae406ae8065cfb3b + ws: 7.4.6 + checksum: f9015bc79450281d8cbb17b6a7dd6ea242a2ce6cf5b997674eb6647151c844a70ce8299e8d530b2aab77a8b204208865d5232b701d2b91d956a30db10b3f88fc languageName: node linkType: hard -"@ethersproject/random@npm:5.0.4, @ethersproject/random@npm:^5.0.3": - version: 5.0.4 - resolution: "@ethersproject/random@npm:5.0.4" +"@ethersproject/random@npm:5.7.0, @ethersproject/random@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/random@npm:5.7.0" dependencies: - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/logger": ^5.0.5 - checksum: b44380c067e5ef5a23cf7f9536428211d0adcfb2e4be67c9cc1be7865905da8be7a480b6af92670fca7f2902c918d9a8b765691dccf9409f4f85ef7d8f1a225c + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + checksum: fbfce323fbbcb5baa4644d9908dc162385ca825f22f7136879176feab90fa26d4b4cc3bc5c0699e70b25695c555675b9eece5e6599af9c2218d3701779ac470b + languageName: node + linkType: hard + +"@ethersproject/rlp@npm:5.7.0, @ethersproject/rlp@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/rlp@npm:5.7.0" + dependencies: + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + checksum: e50a87706fc01d06f07a21d3cdb83f9fbe272fbb1f8b50d34caa2d4c8ab26793a4861cdc1c36098a75b24a48a0d96585126ca3a065ceaabc8aac1a7e50e881c2 languageName: node linkType: hard -"@ethersproject/rlp@npm:5.0.4, @ethersproject/rlp@npm:^5.0.3": +"@ethersproject/rlp@npm:^5.0.3": version: 5.0.4 resolution: "@ethersproject/rlp@npm:5.0.4" dependencies: @@ -2364,18 +2481,32 @@ __metadata: languageName: node linkType: hard -"@ethersproject/sha2@npm:5.0.4, @ethersproject/sha2@npm:^5.0.3": - version: 5.0.4 - resolution: "@ethersproject/sha2@npm:5.0.4" +"@ethersproject/sha2@npm:5.7.0, @ethersproject/sha2@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/sha2@npm:5.7.0" dependencies: - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/logger": ^5.0.5 - hash.js: 1.1.3 - checksum: 3bec7fbc65c304db0fcd9ea3cb67c31d40e002b3305d28e44830921a73720389a9bdb064483829929500aec1e0636be4466e5ec83329260d151b19b268c114ac + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + hash.js: 1.1.7 + checksum: 141b1dd319ed3f6f281029794e0f721bda65440e449bfa4aac59402f2ecc09622e44c2c9350c1fb50f8610921ea9226e5d1964adfcf3bd88caf93f54a37d2f19 + languageName: node + linkType: hard + +"@ethersproject/signing-key@npm:5.7.0, @ethersproject/signing-key@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/signing-key@npm:5.7.0" + dependencies: + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + bn.js: ^5.2.1 + elliptic: 6.5.4 + hash.js: 1.1.7 + checksum: 0a6f82a70c82632d55b34e950da38aace218cce13843db2410d271dee83b9ea6c1e18b57e1bb857be6418ddaa9789460d9f19c9282d59faaf151cf0d7d620edc languageName: node linkType: hard -"@ethersproject/signing-key@npm:5.0.5, @ethersproject/signing-key@npm:^5.0.4": +"@ethersproject/signing-key@npm:^5.0.4": version: 5.0.5 resolution: "@ethersproject/signing-key@npm:5.0.5" dependencies: @@ -2401,20 +2532,32 @@ __metadata: languageName: node linkType: hard -"@ethersproject/solidity@npm:5.0.5": - version: 5.0.5 - resolution: "@ethersproject/solidity@npm:5.0.5" +"@ethersproject/solidity@npm:5.7.0": + version: 5.7.0 + resolution: "@ethersproject/solidity@npm:5.7.0" dependencies: - "@ethersproject/bignumber": ^5.0.7 - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/keccak256": ^5.0.3 - "@ethersproject/sha2": ^5.0.3 - "@ethersproject/strings": ^5.0.4 - checksum: a09665b1456a3db9b8827c1de50b1d271a21a76a8eb3d92030461316ffd9d54f87324baa397615bba8508874727abc4a197e2b0b4d4b47525aa91a1f369e6e7d + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/sha2": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + checksum: ab68686d3dabc5d2f4f91002a26fdeb271659d0af2f057f0a5b31bfb8c3fc732c37a14e73c08d97c01dc9392f8bc11bc3b5937049ad44865d8587e7434b5db9e languageName: node linkType: hard -"@ethersproject/strings@npm:5.0.5, @ethersproject/strings@npm:>=5.0.0-beta.130, @ethersproject/strings@npm:^5.0.4": +"@ethersproject/strings@npm:5.7.0, @ethersproject/strings@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/strings@npm:5.7.0" + dependencies: + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + checksum: 5047c6beecd3ec7ae0afa402ffd906c9fac776b39f2c8559253b55015473762a36fe47f74d966d33c3f2cc6923c1f6dc531df86fb64ac145982ebb6ad705241c + languageName: node + linkType: hard + +"@ethersproject/strings@npm:>=5.0.0-beta.130, @ethersproject/strings@npm:^5.0.4": version: 5.0.5 resolution: "@ethersproject/strings@npm:5.0.5" dependencies: @@ -2436,7 +2579,24 @@ __metadata: languageName: node linkType: hard -"@ethersproject/transactions@npm:5.0.6, @ethersproject/transactions@npm:^5.0.0-beta.135, @ethersproject/transactions@npm:^5.0.5": +"@ethersproject/transactions@npm:5.7.0, @ethersproject/transactions@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/transactions@npm:5.7.0" + dependencies: + "@ethersproject/address": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/rlp": ^5.7.0 + "@ethersproject/signing-key": ^5.7.0 + checksum: c3840b0ae812acba2abbc1da346f7802e7bc54034b0773e0a0bca55ae07887cf30fd4cb759049a40d387f1221ad597cebefa16a466bc37bf1e5c3d00682766e2 + languageName: node + linkType: hard + +"@ethersproject/transactions@npm:^5.0.0-beta.135": version: 5.0.6 resolution: "@ethersproject/transactions@npm:5.0.6" dependencies: @@ -2470,50 +2630,50 @@ __metadata: languageName: node linkType: hard -"@ethersproject/units@npm:5.0.6": - version: 5.0.6 - resolution: "@ethersproject/units@npm:5.0.6" +"@ethersproject/units@npm:5.7.0": + version: 5.7.0 + resolution: "@ethersproject/units@npm:5.7.0" dependencies: - "@ethersproject/bignumber": ^5.0.7 - "@ethersproject/constants": ^5.0.4 - "@ethersproject/logger": ^5.0.5 - checksum: a566653f0752189dcb16ec96a1376645954d31d1860f9f1933596f9a36accf6e699502dcb48ed6db835551b3164cdbcb560c98c174fd746f2ae55898021db99c + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + checksum: 8515a2ef75d41a5c0cfe7111d4479a73398aa83abdcd215bcb5083e001e402ea9ce6860272d185f1e848d7efbf4cc290195e5eb5cfedb372fa4bee8a2d63946d languageName: node linkType: hard -"@ethersproject/wallet@npm:5.0.7": - version: 5.0.7 - resolution: "@ethersproject/wallet@npm:5.0.7" +"@ethersproject/wallet@npm:5.7.0": + version: 5.7.0 + resolution: "@ethersproject/wallet@npm:5.7.0" dependencies: - "@ethersproject/abstract-provider": ^5.0.4 - "@ethersproject/abstract-signer": ^5.0.4 - "@ethersproject/address": ^5.0.4 - "@ethersproject/bignumber": ^5.0.7 - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/hash": ^5.0.4 - "@ethersproject/hdnode": ^5.0.4 - "@ethersproject/json-wallets": ^5.0.6 - "@ethersproject/keccak256": ^5.0.3 - "@ethersproject/logger": ^5.0.5 - "@ethersproject/properties": ^5.0.3 - "@ethersproject/random": ^5.0.3 - "@ethersproject/signing-key": ^5.0.4 - "@ethersproject/transactions": ^5.0.5 - "@ethersproject/wordlists": ^5.0.4 - checksum: 2d423512e33e9c70b4efa59172afe4d88928bc995a49183909471a3d05e1490b65a542e274a86a4ea387a469adcd63297936635773029531f13962a4bdf20e29 + "@ethersproject/abstract-provider": ^5.7.0 + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/hash": ^5.7.0 + "@ethersproject/hdnode": ^5.7.0 + "@ethersproject/json-wallets": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/random": ^5.7.0 + "@ethersproject/signing-key": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + "@ethersproject/wordlists": ^5.7.0 + checksum: d191b6d2c006d699c746ba1239d6b1d0d42c7d2e3c29f9dd51c4069dadf673d0b617e5d2433ca298aa1d77c328f2f1eb60dac1e488b08eeeac7d9f0989fabe73 languageName: node linkType: hard -"@ethersproject/web@npm:5.0.9, @ethersproject/web@npm:^5.0.6": - version: 5.0.9 - resolution: "@ethersproject/web@npm:5.0.9" +"@ethersproject/web@npm:5.7.1, @ethersproject/web@npm:^5.7.0": + version: 5.7.1 + resolution: "@ethersproject/web@npm:5.7.1" dependencies: - "@ethersproject/base64": ^5.0.3 - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/logger": ^5.0.5 - "@ethersproject/properties": ^5.0.3 - "@ethersproject/strings": ^5.0.4 - checksum: 72204490fedec8751a563f83c476a2462a6ae4a3c98e5b52bc85f49a5eaea9c4a9065db6c136b272a0e4341b38c7c434cce9c9ec661c3b35370eb6745341a0c7 + "@ethersproject/base64": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + checksum: ae6dc5c9f3b273f4d85f4acaee6f9b8b98fbcb43a070889ca66c8d59fff233dc92d962b0d2d834444f6825aedd7017b8cfa64cb61f0ecb1740ab6188f73c40c4 languageName: node linkType: hard @@ -2530,16 +2690,16 @@ __metadata: languageName: node linkType: hard -"@ethersproject/wordlists@npm:5.0.5, @ethersproject/wordlists@npm:^5.0.4": - version: 5.0.5 - resolution: "@ethersproject/wordlists@npm:5.0.5" +"@ethersproject/wordlists@npm:5.7.0, @ethersproject/wordlists@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/wordlists@npm:5.7.0" dependencies: - "@ethersproject/bytes": ^5.0.4 - "@ethersproject/hash": ^5.0.4 - "@ethersproject/logger": ^5.0.5 - "@ethersproject/properties": ^5.0.3 - "@ethersproject/strings": ^5.0.4 - checksum: a6fa81e0874d15d388b311eb958027bd2b6ba31b43c2991cbd8dd2c141548129afba6294964c6a31889f149fe7d4da1580cd6a6cf3d509677b81475a3cb2cd82 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/hash": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + checksum: 544cead6084efb81ed288b38d2d6acd26a8e47e788fe3ede6bcc03cec358adaf7b30454af139151b9a651b9ae5cc5db0d0a42f5b003dafeb84c59ebe5a8fd321 languageName: node linkType: hard @@ -3524,6 +3684,7 @@ __metadata: chai: ^4.2.0 concurrently: ^6.4.0 dotenv: ^8.2.0 + electron: ^22.0.0 eslint: ^7.10.0 eslint-config-prettier: ^6.12.0 eslint-config-standard: ^14.1.1 @@ -3536,11 +3697,11 @@ __metadata: eth-gas-reporter: latest ethereumjs-testrpc-sc: ^6.5.1-sc.1 ethereumjs-util: ^7.0.8 - ethers: ^5.0.19 + ethers: ^5.1.4 hardhat: 2.9.9 hardhat-contract-sizer: ^2.5.0 hardhat-gas-reporter: 1.0.8 - husky: ^4.3.0 + husky: ^8.0.2 ipfs-http-client: ^55.0.0 lerna: ^3.22.1 lint-staged: ">=10" @@ -3548,11 +3709,11 @@ __metadata: openzeppelin-solidity: 2.0.0 patch-package: ^6.4.7 prettier: ^2.1.2 - solhint: ^3.2.2 + prettier-plugin-solidity: ^1.1.0 + solhint: ^3.3.7 solhint-plugin-lido: ^0.0.4 solidity-bytes-utils: 0.0.6 solidity-coverage: ^0.7.18 - solium: ^1.2.5 truffle: ^5.1.43 truffle-extract: ^1.2.1 truffle-flattener: ^1.5.0 @@ -4270,6 +4431,13 @@ __metadata: languageName: node linkType: hard +"@sindresorhus/is@npm:^4.0.0": + version: 4.6.0 + resolution: "@sindresorhus/is@npm:4.6.0" + checksum: 9a7a8cacfcb49534a1416d2d0a3c6ae6e892bd6e22544a49334a5f846437498b80fbe3dae2d2bd454f0b25e3acc9db3b202b2bbe69052ef1e6d2b26d6d386cb8 + languageName: node + linkType: hard + "@solidity-parser/parser@npm:^0.12.2": version: 0.12.2 resolution: "@solidity-parser/parser@npm:0.12.2" @@ -4295,7 +4463,7 @@ __metadata: languageName: node linkType: hard -"@solidity-parser/parser@npm:^0.14.1": +"@solidity-parser/parser@npm:^0.14.1, @solidity-parser/parser@npm:^0.14.5": version: 0.14.5 resolution: "@solidity-parser/parser@npm:0.14.5" dependencies: @@ -4318,7 +4486,7 @@ __metadata: languageName: node linkType: hard -"@solidity-parser/parser@npm:^0.8.0, @solidity-parser/parser@npm:^0.8.1": +"@solidity-parser/parser@npm:^0.8.0": version: 0.8.1 resolution: "@solidity-parser/parser@npm:0.8.1" checksum: c16f7d947277d41cbef0b93df899e09ce1032a1ac37a2af9d9e3b805f38f232ae7516e3b41a4a81a2787cdc84dfe554d86aa6038faa899bac3254a653df68e39 @@ -4341,6 +4509,15 @@ __metadata: languageName: node linkType: hard +"@szmarczak/http-timer@npm:^4.0.5": + version: 4.0.6 + resolution: "@szmarczak/http-timer@npm:4.0.6" + dependencies: + defer-to-connect: ^2.0.0 + checksum: 51297e5f70d4cbc1ceb6751fc26f6e9668d2ee448062d198a97b0ab022bd90a0e957f23ec6b2344966bba6c3dfd44231883bacc106070f95e69e5e99e9372f5f + languageName: node + linkType: hard + "@tootallnate/once@npm:1": version: 1.1.2 resolution: "@tootallnate/once@npm:1.1.2" @@ -4774,6 +4951,18 @@ __metadata: languageName: node linkType: hard +"@types/cacheable-request@npm:^6.0.1": + version: 6.0.3 + resolution: "@types/cacheable-request@npm:6.0.3" + dependencies: + "@types/http-cache-semantics": "*" + "@types/keyv": ^3.1.4 + "@types/node": "*" + "@types/responselike": ^1.0.0 + checksum: db1d436d7f5f23ab4ca97c2fb72286d5791a0720a2c7b6e0eaf19840361ab24acbd2bdabc52d99d8343e0ea39b7fc6b3c367fb97c07c80984b0e84ae60fd9a7c + languageName: node + linkType: hard + "@types/chai@npm:^4.2.0": version: 4.2.13 resolution: "@types/chai@npm:4.2.13" @@ -4809,6 +4998,13 @@ __metadata: languageName: node linkType: hard +"@types/http-cache-semantics@npm:*": + version: 4.0.1 + resolution: "@types/http-cache-semantics@npm:4.0.1" + checksum: 16b8d0c7ea707bda68eeb009fe3c43b5af806882d31b13c436e23477e511f0b1a234f11f0c707293d649489e2f21941d11af06f091ce35d8a3dc8e8ac9a52afe + languageName: node + linkType: hard + "@types/json5@npm:^0.0.29": version: 0.0.29 resolution: "@types/json5@npm:0.0.29" @@ -4825,6 +5021,15 @@ __metadata: languageName: node linkType: hard +"@types/keyv@npm:^3.1.4": + version: 3.1.4 + resolution: "@types/keyv@npm:3.1.4" + dependencies: + "@types/node": "*" + checksum: 64029e1d843a3487329e18e126c2d78c46a18ea5d33a3eea6a99d704e62b1c36a74df3015634c667e5d2b723e6b6688ce5162e36922c96373a0dd63e6d0b91c6 + languageName: node + linkType: hard + "@types/level-errors@npm:*": version: 3.0.0 resolution: "@types/level-errors@npm:3.0.0" @@ -4906,6 +5111,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^16.11.26": + version: 16.18.11 + resolution: "@types/node@npm:16.18.11" + checksum: 7bb2977569de27c1ee1f48b13866b8b68ace9eed3adeb74172062b775ffce4989554ae7c6d8437f4a2e0025332fc3f7f0fb0279db1b9f906de49ca19ee3f4b1b + languageName: node + linkType: hard + "@types/node@npm:^8.0.0": version: 8.10.64 resolution: "@types/node@npm:8.10.64" @@ -4985,6 +5197,15 @@ __metadata: languageName: node linkType: hard +"@types/yauzl@npm:^2.9.1": + version: 2.10.0 + resolution: "@types/yauzl@npm:2.10.0" + dependencies: + "@types/node": "*" + checksum: dd6a99e97db987be1a64a352cc4eeaf15a2f20207f829693fedcb642d97fd203082410d2d74c4c75798cfc5fd30db6445809991889850100267e0723a1645084 + 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" @@ -5290,18 +5511,6 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^5.2.2": - version: 5.5.2 - resolution: "ajv@npm:5.5.2" - dependencies: - co: ^4.6.0 - fast-deep-equal: ^1.0.0 - fast-json-stable-stringify: ^2.0.0 - json-schema-traverse: ^0.3.0 - checksum: 15cb5986bf9450846a83e6e6528204eaf42b181e92864d2ff374a9a32ec1ffea0612e0d405e16a38540dc2e866b93f0927bcc8ec85a6a568d87dbeb69b754e66 - languageName: node - linkType: hard - "ajv@npm:^6.10.0, ajv@npm:^6.10.2, ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.6.1, ajv@npm:^6.9.1": version: 6.12.6 resolution: "ajv@npm:6.12.6" @@ -5498,16 +5707,6 @@ __metadata: languageName: node linkType: hard -"anymatch@npm:^1.3.0": - version: 1.3.2 - resolution: "anymatch@npm:1.3.2" - dependencies: - micromatch: ^2.1.5 - normalize-path: ^2.0.0 - checksum: 5fc22bb83e6bee0f2df85b28cfba884b5761f51c0ac07306ce52ce41a91481d5949f316782ba71b2cfc670cef0b398ebaca06b24d7b028daed013e83e20c0d30 - languageName: node - linkType: hard - "anymatch@npm:^2.0.0": version: 2.0.0 resolution: "anymatch@npm:2.0.0" @@ -5627,15 +5826,6 @@ __metadata: languageName: node linkType: hard -"arr-diff@npm:^2.0.0": - version: 2.0.0 - resolution: "arr-diff@npm:2.0.0" - dependencies: - arr-flatten: ^1.0.1 - checksum: b7daea7336ccf39294dd47df03312af58a9d201c6964f8497715c5e56369ed227e1eacbe5236315cc3a8832705fb2ec74b114e44dfcae402fb7add7393ec6bee - languageName: node - linkType: hard - "arr-diff@npm:^4.0.0": version: 4.0.0 resolution: "arr-diff@npm:4.0.0" @@ -5643,7 +5833,7 @@ __metadata: languageName: node linkType: hard -"arr-flatten@npm:^1.0.1, arr-flatten@npm:^1.1.0": +"arr-flatten@npm:^1.1.0": version: 1.1.0 resolution: "arr-flatten@npm:1.1.0" checksum: 564dc9c32cb20a1b5bc6eeea3b7a7271fcc5e9f1f3d7648b9db145b7abf68815562870267010f9f4976d788f3f79d2ccf176e94cee69af7da48943a71041ab57 @@ -5733,13 +5923,6 @@ __metadata: languageName: node linkType: hard -"array-unique@npm:^0.2.1": - version: 0.2.1 - resolution: "array-unique@npm:0.2.1" - checksum: d27ef6bed9515bb6d507398e4f968d4643aa8a82eb5e52222a478798649b2fe634b8d65adb3394e52e3b39ac511f6c07e1b4265e17ceb7c766aca8529e3d02bc - languageName: node - linkType: hard - "array-unique@npm:^0.3.2": version: 0.3.2 resolution: "array-unique@npm:0.3.2" @@ -5891,7 +6074,7 @@ __metadata: languageName: node linkType: hard -"async-each@npm:^1.0.0, async-each@npm:^1.0.1": +"async-each@npm:^1.0.1": version: 1.0.3 resolution: "async-each@npm:1.0.3" checksum: 0cf01982ae42db5ce591aab153e45e77aa7c813c4fb282f1e7cac2259f90949f82542e82a33f73ef308e0126c9a8bc702ee117a87614549fe88840cf5a44aec4 @@ -7080,6 +7263,13 @@ __metadata: languageName: node linkType: hard +"bn.js@npm:^5.2.1": + version: 5.2.1 + resolution: "bn.js@npm:5.2.1" + checksum: 4693b52187524b856422b133cb2168ab5d593981891cb213e0565f5355008539f3887291f69c5ae2b254743c6c8d062ab7b69983e19bd00060023517d0a7ca8b + languageName: node + linkType: hard + "body-parser@npm:1.19.0, body-parser@npm:^1.16.0": version: 1.19.0 resolution: "body-parser@npm:1.19.0" @@ -7105,6 +7295,13 @@ __metadata: languageName: node linkType: hard +"boolean@npm:^3.0.1": + version: 3.2.0 + resolution: "boolean@npm:3.2.0" + checksum: 279daf3e536db745179365e412ac02741f0dc398dbbeac126c5bfc342bda2f80e572f6bcb3056320230cd7c22c55b18768001ecf7a071b51a0edcb1d2c3fc47f + languageName: node + linkType: hard + "borc@npm:^2.1.2": version: 2.1.2 resolution: "borc@npm:2.1.2" @@ -7166,17 +7363,6 @@ __metadata: languageName: node linkType: hard -"braces@npm:^1.8.2": - version: 1.8.5 - resolution: "braces@npm:1.8.5" - dependencies: - expand-range: ^1.8.1 - preserve: ^0.2.0 - repeat-element: ^1.1.2 - checksum: b2a9c621e1f3c44f44618c0b132fdcf043e1dc364a31d9e787caae368d5023fee2fa1080b7aaf374ff437593fb13d1f165bf210e935ed02aaed14cc87cb3170f - languageName: node - linkType: hard - "braces@npm:^2.3.1, braces@npm:^2.3.2": version: 2.3.2 resolution: "braces@npm:2.3.2" @@ -7239,13 +7425,6 @@ __metadata: languageName: node linkType: hard -"browser-stdout@npm:1.3.0": - version: 1.3.0 - resolution: "browser-stdout@npm:1.3.0" - checksum: 8501b6fa861e463b30da1f86ad8b2b4a5d84e19ba473addfca2d2db3e8fb3f7756b457197773e620c3950744fb5fd7080e7d944d52db2d96859880f863e1ee66 - languageName: node - linkType: hard - "browser-stdout@npm:1.3.1": version: 1.3.1 resolution: "browser-stdout@npm:1.3.1" @@ -7626,6 +7805,13 @@ __metadata: languageName: node linkType: hard +"cacheable-lookup@npm:^5.0.3": + version: 5.0.4 + resolution: "cacheable-lookup@npm:5.0.4" + checksum: cb5849f5841e37f007aeaea2516ecf2cb0a9730667694d131331a04413f6c3bf2587391d55003cc2b95ef59085b5f50ac9887a0b7c673fc0c8102bcc69b6d73d + languageName: node + linkType: hard + "cacheable-request@npm:^2.1.1": version: 2.1.4 resolution: "cacheable-request@npm:2.1.4" @@ -7656,6 +7842,21 @@ __metadata: languageName: node linkType: hard +"cacheable-request@npm:^7.0.2": + version: 7.0.2 + resolution: "cacheable-request@npm:7.0.2" + dependencies: + clone-response: ^1.0.2 + get-stream: ^5.1.0 + http-cache-semantics: ^4.0.0 + keyv: ^4.0.0 + lowercase-keys: ^2.0.0 + normalize-url: ^6.0.1 + responselike: ^2.0.0 + checksum: 176a1fceb987f1fee8b512ee7908445854a0c75854a11710f0d8de104cf840fd92e3c94ecd1f9144e57a25e17f5d72056591e5b33aabb8775061f906b0696a50 + languageName: node + linkType: hard + "cachedown@npm:1.0.0": version: 1.0.0 resolution: "cachedown@npm:1.0.0" @@ -8015,26 +8216,6 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^1.6.0": - version: 1.7.0 - resolution: "chokidar@npm:1.7.0" - dependencies: - anymatch: ^1.3.0 - async-each: ^1.0.0 - fsevents: ^1.0.0 - glob-parent: ^2.0.0 - inherits: ^2.0.1 - is-binary-path: ^1.0.0 - is-glob: ^2.0.0 - path-is-absolute: ^1.0.0 - readdirp: ^2.0.0 - dependenciesMeta: - fsevents: - optional: true - checksum: e0ff584d6d0acdc07983c8d6f493b53b036cc8abde4f36f75ca14771340a3b1bd5920287dbc45c96d25b61a4b0d2e493734314c1467181d61253211e176202f9 - languageName: node - linkType: hard - "chokidar@npm:^2.0.4, chokidar@npm:^2.1.5": version: 2.1.8 resolution: "chokidar@npm:2.1.8" @@ -8383,13 +8564,6 @@ __metadata: languageName: node linkType: hard -"co@npm:^4.6.0": - version: 4.6.0 - resolution: "co@npm:4.6.0" - checksum: 3f22dbbe0f413ff72831d087d853a81d1137093e12e8ec90b4da2bde5c67bc6bff11b6adeb38ca9fa8704b8cd40dba294948bda3c271bccb74669972b840cc1a - languageName: node - linkType: hard - "coa@npm:^2.0.2": version: 2.0.2 resolution: "coa@npm:2.0.2" @@ -8541,13 +8715,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:2.11.0": - version: 2.11.0 - resolution: "commander@npm:2.11.0" - checksum: e3e937252eaa4ab130578c0fa57418f5b56b9eab24d08bb28c21fcb7669b88e8e6ab133f5286e7ccdfbddde92226184f1d77e64e5e0a0f5a87603c37dc92b4f2 - languageName: node - linkType: hard - "commander@npm:2.18.0": version: 2.18.0 resolution: "commander@npm:2.18.0" @@ -8562,7 +8729,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^2.11.0, commander@npm:^2.15.0, commander@npm:^2.19.0, commander@npm:^2.20.0, commander@npm:^2.8.1, commander@npm:^2.9.0": +"commander@npm:^2.11.0, commander@npm:^2.15.0, commander@npm:^2.19.0, commander@npm:^2.20.0, commander@npm:^2.8.1": version: 2.20.3 resolution: "commander@npm:2.20.3" checksum: b73428e97de7624323f81ba13f8ed9271de487017432d18b4da3f07cfc528ad754bbd199004bd5d14e0ccd67d1fdfe0ec8dbbd4c438b401df3c4cc387bfd1daa @@ -8614,13 +8781,6 @@ __metadata: languageName: node linkType: hard -"compare-versions@npm:^3.6.0": - version: 3.6.0 - resolution: "compare-versions@npm:3.6.0" - checksum: 09525264502bda1f6667ad2429eaf5520b543d997e79e7a94b66a5896df8921cdc3a97140dfff75af6c9ba1859c872de1921c3cf8a6c48ed807bbf9f582cf093 - languageName: node - linkType: hard - "component-emitter@npm:^1.2.1": version: 1.3.0 resolution: "component-emitter@npm:1.3.0" @@ -9111,17 +9271,6 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^5.0.1": - version: 5.1.0 - resolution: "cross-spawn@npm:5.1.0" - dependencies: - lru-cache: ^4.0.1 - shebang-command: ^1.2.0 - which: ^1.2.9 - checksum: 96018c42a94a2f69e27c11688db638c343109e4eda5cc6586a83a1d2f102ef2ef4d184919593036748d386ddb67cc3e66658fefec85a4659958cde792f1a9ddc - languageName: node - linkType: hard - "cross-spawn@npm:^6.0.0, cross-spawn@npm:^6.0.4, cross-spawn@npm:^6.0.5": version: 6.0.5 resolution: "cross-spawn@npm:6.0.5" @@ -9642,6 +9791,15 @@ __metadata: languageName: node linkType: hard +"decompress-response@npm:^6.0.0": + version: 6.0.0 + resolution: "decompress-response@npm:6.0.0" + dependencies: + mimic-response: ^3.1.0 + checksum: bb8b8c42be7767994764d27f91a3949e3dc9008da82f1aaeab1de40f1ebb50d7abf17b31b2e4000f8d267a1e75f76052efd58d4419124c04bf430e184c164fad + languageName: node + linkType: hard + "decompress-tar@npm:^4.0.0, decompress-tar@npm:^4.1.0, decompress-tar@npm:^4.1.1": version: 4.1.1 resolution: "decompress-tar@npm:4.1.1" @@ -9772,6 +9930,13 @@ __metadata: languageName: node linkType: hard +"defer-to-connect@npm:^2.0.0": + version: 2.0.1 + resolution: "defer-to-connect@npm:2.0.1" + checksum: 6641e6377732f3066e5f101ae4f22de6b85c45fda3ff0cd710412901af7570cfbb77c9c25cb6dcd5d1b52b816e37fccfc013c9ec7f1f6a95823773625e8be6c5 + languageName: node + linkType: hard + "deferred-leveldown@npm:~1.2.1": version: 1.2.2 resolution: "deferred-leveldown@npm:1.2.2" @@ -9951,6 +10116,13 @@ __metadata: languageName: node linkType: hard +"detect-node@npm:^2.0.4": + version: 2.1.0 + resolution: "detect-node@npm:2.1.0" + checksum: 5100c924d74bdc2cf861af88dce6618abbbb95e3b71047f1eac7e475981416aa2c208c7153fd830df372f8ce324a207adb1c9f56884fa94bcb820b8406dd9e6f + languageName: node + linkType: hard + "detect-port@npm:^1.3.0": version: 1.3.0 resolution: "detect-port@npm:1.3.0" @@ -9974,14 +10146,7 @@ __metadata: languageName: node linkType: hard -"diff@npm:3.3.1": - version: 3.3.1 - resolution: "diff@npm:3.3.1" - checksum: 8f293e2dfcb01a5c2e83dc017b84e638a897a54c3b5efb856b46986be0b48d25f1bd65be6f51c9bb6e14ead512880fbd1388fb5a550bb3eae24d604b3c113d57 - languageName: node - linkType: hard - -"diff@npm:3.5.0, diff@npm:^3.5.0": +"diff@npm:3.5.0": version: 3.5.0 resolution: "diff@npm:3.5.0" checksum: b975b73d7e8fa867cc9e68c293c664e14f11391203603e3f4518689c19fe8e391b4d9e4df3df5b3e51adc6cd81bcb414c80c1666e2f6cf66067f60177eec01d1 @@ -10305,6 +10470,19 @@ __metadata: languageName: node linkType: hard +"electron@npm:^22.0.0": + version: 22.0.0 + resolution: "electron@npm:22.0.0" + dependencies: + "@electron/get": ^2.0.0 + "@types/node": ^16.11.26 + extract-zip: ^2.0.1 + bin: + electron: cli.js + checksum: e6009da32a6317304f8fd7ddd0f6438f131d6556b7fc5086b3783a6b5b23892fd10c29f99b4a105a3d173d1622f62d5861fa0b73b5f87e27d279fa5aceaa64a9 + languageName: node + linkType: hard + "elliptic@npm:6.3.3": version: 6.3.3 resolution: "elliptic@npm:6.3.3" @@ -10361,6 +10539,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^10.2.1": + version: 10.2.1 + resolution: "emoji-regex@npm:10.2.1" + checksum: 6a15ddd92b5782a99dec6a0700c4caab9ebc92bdcc90d1fc59e298a1628a694c465739f18a9103446687192bec89406b2910b6f22f4de48cf74f82f86194a3d8 + languageName: node + linkType: hard + "emoji-regex@npm:^7.0.1": version: 7.0.3 resolution: "emoji-regex@npm:7.0.3" @@ -10471,13 +10656,6 @@ __metadata: languageName: node linkType: hard -"eol@npm:^0.9.1": - version: 0.9.1 - resolution: "eol@npm:0.9.1" - checksum: c77ee68dbe5505f06bb3033d2294437385e579175248123613b36fce9ee707e0f94fae111e7beafb75f656794b5e96b709b9de2dfb2ac5d6ad10cda4cf6de99c - languageName: node - linkType: hard - "equal-length@npm:^1.0.0": version: 1.0.1 resolution: "equal-length@npm:1.0.1" @@ -10609,6 +10787,13 @@ __metadata: languageName: node linkType: hard +"es6-error@npm:^4.1.1": + version: 4.1.1 + resolution: "es6-error@npm:4.1.1" + checksum: d7343d3f47834d71912278b5a7476028b7ef3db4ee5c8b7184d7204d2c3a48dd4ce68d197a14116f0d16c85f85d3d8ed1d8c137cf5bc9f33f672646755289688 + languageName: node + linkType: hard + "es6-iterator@npm:~2.0.3": version: 2.0.3 resolution: "es6-iterator@npm:2.0.3" @@ -11961,41 +12146,41 @@ __metadata: languageName: node linkType: hard -"ethers@npm:^5.0.19": - version: 5.0.19 - resolution: "ethers@npm:5.0.19" - dependencies: - "@ethersproject/abi": 5.0.7 - "@ethersproject/abstract-provider": 5.0.5 - "@ethersproject/abstract-signer": 5.0.7 - "@ethersproject/address": 5.0.5 - "@ethersproject/base64": 5.0.4 - "@ethersproject/basex": 5.0.4 - "@ethersproject/bignumber": 5.0.8 - "@ethersproject/bytes": 5.0.5 - "@ethersproject/constants": 5.0.5 - "@ethersproject/contracts": 5.0.5 - "@ethersproject/hash": 5.0.6 - "@ethersproject/hdnode": 5.0.5 - "@ethersproject/json-wallets": 5.0.7 - "@ethersproject/keccak256": 5.0.4 - "@ethersproject/logger": 5.0.6 - "@ethersproject/networks": 5.0.4 - "@ethersproject/pbkdf2": 5.0.4 - "@ethersproject/properties": 5.0.4 - "@ethersproject/providers": 5.0.14 - "@ethersproject/random": 5.0.4 - "@ethersproject/rlp": 5.0.4 - "@ethersproject/sha2": 5.0.4 - "@ethersproject/signing-key": 5.0.5 - "@ethersproject/solidity": 5.0.5 - "@ethersproject/strings": 5.0.5 - "@ethersproject/transactions": 5.0.6 - "@ethersproject/units": 5.0.6 - "@ethersproject/wallet": 5.0.7 - "@ethersproject/web": 5.0.9 - "@ethersproject/wordlists": 5.0.5 - checksum: fe016c52170cf50fc9c8ae2544d5963870055e34131c2b87d2619283a054d6da7d078e5c4c84225bc181aa8150a010f457e9266eaf3d718ad4b7d7b60afcfb4a +"ethers@npm:^5.1.4": + version: 5.7.2 + resolution: "ethers@npm:5.7.2" + dependencies: + "@ethersproject/abi": 5.7.0 + "@ethersproject/abstract-provider": 5.7.0 + "@ethersproject/abstract-signer": 5.7.0 + "@ethersproject/address": 5.7.0 + "@ethersproject/base64": 5.7.0 + "@ethersproject/basex": 5.7.0 + "@ethersproject/bignumber": 5.7.0 + "@ethersproject/bytes": 5.7.0 + "@ethersproject/constants": 5.7.0 + "@ethersproject/contracts": 5.7.0 + "@ethersproject/hash": 5.7.0 + "@ethersproject/hdnode": 5.7.0 + "@ethersproject/json-wallets": 5.7.0 + "@ethersproject/keccak256": 5.7.0 + "@ethersproject/logger": 5.7.0 + "@ethersproject/networks": 5.7.1 + "@ethersproject/pbkdf2": 5.7.0 + "@ethersproject/properties": 5.7.0 + "@ethersproject/providers": 5.7.2 + "@ethersproject/random": 5.7.0 + "@ethersproject/rlp": 5.7.0 + "@ethersproject/sha2": 5.7.0 + "@ethersproject/signing-key": 5.7.0 + "@ethersproject/solidity": 5.7.0 + "@ethersproject/strings": 5.7.0 + "@ethersproject/transactions": 5.7.0 + "@ethersproject/units": 5.7.0 + "@ethersproject/wallet": 5.7.0 + "@ethersproject/web": 5.7.1 + "@ethersproject/wordlists": 5.7.0 + checksum: 1ba7f7bb7a1b589fa7e5d4c94d75705cb1feb6bdb33caa55633e38150fd7ab0636a7380c92f067243a1d512d06b79196a07720ec048d3b4ccca06fad4f23245a languageName: node linkType: hard @@ -12188,21 +12373,6 @@ __metadata: languageName: node linkType: hard -"execa@npm:^0.7.0": - version: 0.7.0 - resolution: "execa@npm:0.7.0" - dependencies: - cross-spawn: ^5.0.1 - get-stream: ^3.0.0 - is-stream: ^1.1.0 - npm-run-path: ^2.0.0 - p-finally: ^1.0.0 - signal-exit: ^3.0.0 - strip-eof: ^1.0.0 - checksum: 7210f5334e5da185365eccc129bedb2f7dc6e5872fb1f09f36fc603e32790d79bfad61ddc6219d057d7fa65c69c17025cdb51b859e7d5a64e94d261ddbbbf260 - languageName: node - linkType: hard - "execa@npm:^1.0.0": version: 1.0.0 resolution: "execa@npm:1.0.0" @@ -12290,15 +12460,6 @@ __metadata: languageName: node linkType: hard -"expand-brackets@npm:^0.1.4": - version: 0.1.5 - resolution: "expand-brackets@npm:0.1.5" - dependencies: - is-posix-bracket: ^0.1.0 - checksum: 927f4818e15f0a09a9ba66aa02568d1ae0dca272bd431f20caaeb19cd8b0b5a926910ade7bb92350d7ac025594222c5c0af79c7917d12b728917e08dc165282d - languageName: node - linkType: hard - "expand-brackets@npm:^2.1.4": version: 2.1.4 resolution: "expand-brackets@npm:2.1.4" @@ -12314,15 +12475,6 @@ __metadata: languageName: node linkType: hard -"expand-range@npm:^1.8.1": - version: 1.8.2 - resolution: "expand-range@npm:1.8.2" - dependencies: - fill-range: ^2.1.0 - checksum: 0df22f2b18552a67384722c53f348a8d886a05bcc5813dc1ae642d89e7d42f7606ae5b2f41b097da684fae1621e3de35640fb4bc96bf64383865a668e778dd15 - languageName: node - linkType: hard - "express@npm:^4.14.0": version: 4.17.1 resolution: "express@npm:4.17.1" @@ -12426,15 +12578,6 @@ __metadata: languageName: node linkType: hard -"extglob@npm:^0.3.1": - version: 0.3.2 - resolution: "extglob@npm:0.3.2" - dependencies: - is-extglob: ^1.0.0 - checksum: 3ca658afd4f980b29a1b49eda8f527916383982111a62edf7624a1c84ce1617e23f5312f2e5b65edeae6a494c2a0c72a6a7cfd9e3dac2b8fc501cd9f667525d3 - languageName: node - linkType: hard - "extglob@npm:^2.0.4": version: 2.0.4 resolution: "extglob@npm:2.0.4" @@ -12451,6 +12594,23 @@ __metadata: languageName: node linkType: hard +"extract-zip@npm:^2.0.1": + version: 2.0.1 + resolution: "extract-zip@npm:2.0.1" + dependencies: + "@types/yauzl": ^2.9.1 + debug: ^4.1.1 + get-stream: ^5.1.0 + yauzl: ^2.10.0 + dependenciesMeta: + "@types/yauzl": + optional: true + bin: + extract-zip: cli.js + checksum: 1217e48d659bf589a7ffaf6fa01fd868d619d1be46ef3dd6526cd17614a2d3a7d1bd5d5ebef81461000cd86fb32a0c9827b466650e98d55efc1fb5ee85f4716a + languageName: node + linkType: hard + "extsprintf@npm:1.3.0, extsprintf@npm:^1.2.0": version: 1.3.0 resolution: "extsprintf@npm:1.3.0" @@ -12479,13 +12639,6 @@ __metadata: languageName: node linkType: hard -"fast-deep-equal@npm:^1.0.0": - version: 1.1.0 - resolution: "fast-deep-equal@npm:1.1.0" - checksum: 2004bd98393210a75e54bb5aa34efc0d768cadc0b0ce5830a4f816246ddbbfa19fd1d9164949735e1c97fdde024addea3ae028821656b7e216ba45b28b3aefb6 - languageName: node - linkType: hard - "fast-deep-equal@npm:^3.1.1": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -12682,13 +12835,6 @@ __metadata: languageName: node linkType: hard -"filename-regex@npm:^2.0.0": - version: 2.0.1 - resolution: "filename-regex@npm:2.0.1" - checksum: 1ea8f335e516698dac9bd010078b47607e393e9c082160d1963f97b04f474ec298fac046388ddc3f59ddcff054e1e8b02e9fa093f422bebbca3b449857b158a8 - languageName: node - linkType: hard - "filename-reserved-regex@npm:^2.0.0": version: 2.0.0 resolution: "filename-reserved-regex@npm:2.0.0" @@ -12714,19 +12860,6 @@ __metadata: languageName: node linkType: hard -"fill-range@npm:^2.1.0": - version: 2.2.4 - resolution: "fill-range@npm:2.2.4" - dependencies: - is-number: ^2.1.0 - isobject: ^2.0.0 - randomatic: ^3.0.0 - repeat-element: ^1.1.2 - repeat-string: ^1.5.2 - checksum: bef48341c63659cd53be98b9afe2e4a4662816b2e6e3bf7874486922e8b69ce072fefb2b40b04b9d7e25105ac31bf4ac8b93a3f7c061557c43c6551d081f471b - languageName: node - linkType: hard - "fill-range@npm:^4.0.0": version: 4.0.0 resolution: "fill-range@npm:4.0.0" @@ -12822,15 +12955,6 @@ __metadata: languageName: node linkType: hard -"find-versions@npm:^3.2.0": - version: 3.2.0 - resolution: "find-versions@npm:3.2.0" - dependencies: - semver-regex: ^2.0.0 - checksum: 2ddc16b4265184e2b7ab68bfd9d84835178fef4193abd957ebe328e0de98e8ca3b31e2a19201c1c8308e24786faa295aab46c0bc21fa89440e2a1bc8174987f0 - languageName: node - linkType: hard - "find-yarn-workspace-root@npm:^1.2.1": version: 1.2.1 resolution: "find-yarn-workspace-root@npm:1.2.1" @@ -12942,22 +13066,13 @@ __metadata: languageName: node linkType: hard -"for-in@npm:^1.0.1, for-in@npm:^1.0.2": +"for-in@npm:^1.0.2": version: 1.0.2 resolution: "for-in@npm:1.0.2" checksum: e8d7280a654216e9951103e407d1655c2dfa67178ad468cb0b35701df6b594809ccdc66671b3478660d0e6c4bca9d038b1f1fc032716a184c19d67319550c554 languageName: node linkType: hard -"for-own@npm:^0.1.4": - version: 0.1.5 - resolution: "for-own@npm:0.1.5" - dependencies: - for-in: ^1.0.1 - checksum: 7b9778a9197ab519e2c94aec35b44efb467d1867c181cea5a28d7a819480ce5ffcae0b4ae63f15d42f16312d72e63c3cdb1acbc407528ea0ba27afb9df4c958a - languageName: node - linkType: hard - "foreach@npm:^2.0.5": version: 2.0.5 resolution: "foreach@npm:2.0.5" @@ -13179,7 +13294,7 @@ __metadata: languageName: node linkType: hard -"fsevents@^1.0.0, fsevents@^1.2.7": +fsevents@^1.2.7: version: 1.2.13 resolution: "fsevents@npm:1.2.13" dependencies: @@ -13189,7 +13304,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@^1.0.0#builtin, fsevents@patch:fsevents@^1.2.7#builtin": +"fsevents@patch:fsevents@^1.2.7#builtin": version: 1.2.13 resolution: "fsevents@patch:fsevents@npm%3A1.2.13#builtin::version=1.2.13&hash=11e9ea" dependencies: @@ -13668,25 +13783,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"glob-base@npm:^0.3.0": - version: 0.3.0 - resolution: "glob-base@npm:0.3.0" - dependencies: - glob-parent: ^2.0.0 - is-glob: ^2.0.0 - checksum: 9a464f8b5a97ee2a524f7534a2ef42b731a22b37849925d831052ed7afc5b50827e524da5cc1f1961e574bcf9ffcde99b9161fc75e44c7bf397aad1f93fe5d6c - languageName: node - linkType: hard - -"glob-parent@npm:^2.0.0": - version: 2.0.0 - resolution: "glob-parent@npm:2.0.0" - dependencies: - is-glob: ^2.0.0 - checksum: d3d0bc909b973b361ccd20cf82907a19ade72554c1caffee982fad3ac4d0cbfeabe9609fe7188aab6c4dfdf68af96f35623fe1453195baf5414e2a1834b44c59 - languageName: node - linkType: hard - "glob-parent@npm:^3.1.0": version: 3.1.0 resolution: "glob-parent@npm:3.1.0" @@ -13722,20 +13818,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"glob@npm:7.1.2": - version: 7.1.2 - resolution: "glob@npm:7.1.2" - dependencies: - fs.realpath: ^1.0.0 - inflight: ^1.0.4 - inherits: 2 - minimatch: ^3.0.4 - once: ^1.3.0 - path-is-absolute: ^1.0.0 - checksum: eeb466da847c75811d6ba5ceb0e1fd857dbecfdef647eba8e71c4047b1882bf2d9b4def072382fa328283b3afb802de64ab240e9a169c1fede3aec53906520bc - languageName: node - linkType: hard - "glob@npm:7.1.3": version: 7.1.3 resolution: "glob@npm:7.1.3" @@ -13791,6 +13873,20 @@ fsevents@~2.3.2: languageName: node linkType: hard +"global-agent@npm:^3.0.0": + version: 3.0.0 + resolution: "global-agent@npm:3.0.0" + dependencies: + boolean: ^3.0.1 + es6-error: ^4.1.1 + matcher: ^3.0.0 + roarr: ^2.15.3 + semver: ^7.3.2 + serialize-error: ^7.0.1 + checksum: bdb023256a346e411a1a9dc71e9f168fae99a89a965d5e4f7adbc97395f7d6a4659ca24733db179e1af41d18ad512ee6ef13d401179cb18cc21a78be1f5ad6f0 + languageName: node + linkType: hard + "global-dirs@npm:^0.1.1": version: 0.1.1 resolution: "global-dirs@npm:0.1.1" @@ -13862,6 +13958,15 @@ fsevents@~2.3.2: languageName: node linkType: hard +"globalthis@npm:^1.0.1": + version: 1.0.3 + resolution: "globalthis@npm:1.0.3" + dependencies: + define-properties: ^1.1.3 + checksum: 9de957314aa9d3874599b49f995cbc1cfc211d5697c9b1d8c3a85998d1ac6c072cf892b0c9d8327b1deb9adac55b20428d9ff3f99338956eb879c6127960b736 + languageName: node + linkType: hard + "globby@npm:^10.0.1": version: 10.0.2 resolution: "globby@npm:10.0.2" @@ -13941,6 +14046,25 @@ fsevents@~2.3.2: languageName: node linkType: hard +"got@npm:^11.8.5": + version: 11.8.6 + resolution: "got@npm:11.8.6" + dependencies: + "@sindresorhus/is": ^4.0.0 + "@szmarczak/http-timer": ^4.0.5 + "@types/cacheable-request": ^6.0.1 + "@types/responselike": ^1.0.0 + cacheable-lookup: ^5.0.3 + cacheable-request: ^7.0.2 + decompress-response: ^6.0.0 + http2-wrapper: ^1.0.0-beta.5.2 + lowercase-keys: ^2.0.0 + p-cancelable: ^2.0.0 + responselike: ^2.0.0 + checksum: 44ca54c1ec20c42b76a461fc68670ffc63e6b4fea9a714cdf38fea654a02dd89de14cb1175b3cf04e46f0bf1bcb2fa2ba9003b5d8cc7de15d393caf91eafcaaa + languageName: node + linkType: hard + "got@npm:^7.1.0": version: 7.1.0 resolution: "got@npm:7.1.0" @@ -14012,13 +14136,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"growl@npm:1.10.3": - version: 1.10.3 - resolution: "growl@npm:1.10.3" - checksum: f731b6ea4684c25892b2c8ed683a837502904f2b0baf447d3e3dd6e4da65429fbda40cb74ca28bc118d7c95cf057530de52bab51871aebd493b4a8ca6da75240 - languageName: node - linkType: hard - "growl@npm:1.10.5": version: 1.10.5 resolution: "growl@npm:1.10.5" @@ -14175,13 +14292,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"has-flag@npm:^2.0.0": - version: 2.0.0 - resolution: "has-flag@npm:2.0.0" - checksum: bdc20630dfa70841dc00ab80d673a6801c685f4a3609f79c52c6755cc9aff950ceb39789ee63210e8a45e959f26c4496b0c9a9509b3d7017512743b8f1e741b4 - languageName: node - linkType: hard - "has-flag@npm:^3.0.0": version: 3.0.0 resolution: "has-flag@npm:3.0.0" @@ -14312,15 +14422,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"he@npm:1.1.1": - version: 1.1.1 - resolution: "he@npm:1.1.1" - bin: - he: bin/he - checksum: b2b167171cf3eeec7db1c016fc650bc588771841b51f0d04538e700412932a1dc710d150bc629acab25b0ecc6ecf6646014dbf10160782b089651b984a3125fc - languageName: node - linkType: hard - "he@npm:1.2.0, he@npm:^1.1.1": version: 1.2.0 resolution: "he@npm:1.2.0" @@ -14620,6 +14721,16 @@ fsevents@~2.3.2: languageName: node linkType: hard +"http2-wrapper@npm:^1.0.0-beta.5.2": + version: 1.0.3 + resolution: "http2-wrapper@npm:1.0.3" + dependencies: + quick-lru: ^5.1.1 + resolve-alpn: ^1.0.0 + checksum: 2fc0140a69558cf1352372ed6cdf94eb6d108b2755ca087a5626044667033ca9fd6d0e5e04db3c3d2129aadff99b9b07b5bcf3952f5b7138926cb7a1d3128c6e + languageName: node + linkType: hard + "https-browserify@npm:^1.0.0": version: 1.0.0 resolution: "https-browserify@npm:1.0.0" @@ -14681,24 +14792,12 @@ fsevents@~2.3.2: languageName: node linkType: hard -"husky@npm:^4.3.0": - version: 4.3.0 - resolution: "husky@npm:4.3.0" - dependencies: - chalk: ^4.0.0 - ci-info: ^2.0.0 - compare-versions: ^3.6.0 - cosmiconfig: ^7.0.0 - find-versions: ^3.2.0 - opencollective-postinstall: ^2.0.2 - pkg-dir: ^4.2.0 - please-upgrade-node: ^3.2.0 - slash: ^3.0.0 - which-pm-runs: ^1.0.0 +"husky@npm:^8.0.2": + version: 8.0.2 + resolution: "husky@npm:8.0.2" bin: - husky-run: bin/run.js - husky-upgrade: lib/upgrader/bin.js - checksum: c212d9732de84cbd7c25d907b874f7844503f85e28c0512518cddbac9854c54f1c569e81c5b70387f1e3c27d35c2b43256c811cf06fdad066565c5fc178f33f7 + husky: lib/bin.js + checksum: 199dca5b9f805cbbaebcedbaac23c098019dc5140a95ed235d267f0d0d672e02ca912de98d3964256a09a24a69c7809de3ebafff0d2458d7aad255939dfee319 languageName: node linkType: hard @@ -15704,13 +15803,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"is-dotfile@npm:^1.0.0": - version: 1.0.3 - resolution: "is-dotfile@npm:1.0.3" - checksum: 82be54d6d57710d393c2275a63f4c60b33bfe5e21080899073b4ef315f13c9017891aed3477c2c1ecfc43b2a1c2180151fad8fab02aba930473e88b00393501f - languageName: node - linkType: hard - "is-electron@npm:^2.2.0": version: 2.2.0 resolution: "is-electron@npm:2.2.0" @@ -15718,15 +15810,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"is-equal-shallow@npm:^0.1.3": - version: 0.1.3 - resolution: "is-equal-shallow@npm:0.1.3" - dependencies: - is-primitive: ^2.0.0 - checksum: 44c7156c3fcdf08aee3422000e133c8281228e1d0f9a55c20f8db123ad10554000aeb862505188d279a1d93faea96977cc603254ab4986b25746e7cd8a1fedf2 - languageName: node - linkType: hard - "is-error@npm:^2.2.2": version: 2.2.2 resolution: "is-error@npm:2.2.2" @@ -15750,13 +15833,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"is-extglob@npm:^1.0.0": - version: 1.0.0 - resolution: "is-extglob@npm:1.0.0" - checksum: 77073b0ebe962261395f4f72e594ca53157cdb14e41070fd856aca1422f0c1c49a26a55dabdf3c66559c98ca1a6a1ac8b9342034c5577714008b21fd314595a4 - languageName: node - linkType: hard - "is-extglob@npm:^2.1.0, is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -15808,15 +15884,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"is-glob@npm:^2.0.0, is-glob@npm:^2.0.1": - version: 2.0.1 - resolution: "is-glob@npm:2.0.1" - dependencies: - is-extglob: ^1.0.0 - checksum: b3190fc9ca6ad047f6e1856bb80b5b7de740c727025300b078a5557a27c5d1d25594baf8bd582529963eda61cc73c5d8cb546dba8e8afeaeef58012343c52600 - languageName: node - linkType: hard - "is-glob@npm:^3.1.0": version: 3.1.0 resolution: "is-glob@npm:3.1.0" @@ -15922,15 +15989,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"is-number@npm:^2.1.0": - version: 2.1.0 - resolution: "is-number@npm:2.1.0" - dependencies: - kind-of: ^3.0.2 - checksum: 54ecb5cc8e6c262a40adc5e0c40b6a4b209070ce8b83e436697938b3ce550185ec56d30a1fdcc84381fb34b150b326eb09f72a6aa2e587aaeaf098a894090950 - languageName: node - linkType: hard - "is-number@npm:^3.0.0": version: 3.0.0 resolution: "is-number@npm:3.0.0" @@ -15940,13 +15998,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"is-number@npm:^4.0.0": - version: 4.0.0 - resolution: "is-number@npm:4.0.0" - checksum: dda8d33df5fac78f0ce1723a995f0c4a630f59d62390665c52797f39fa9aabaeb1ce8179b29fc02c00cd339da629827e64a6ecc3e2d7619e0b787ea302d88db2 - languageName: node - linkType: hard - "is-number@npm:^7.0.0": version: 7.0.0 resolution: "is-number@npm:7.0.0" @@ -16053,20 +16104,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"is-posix-bracket@npm:^0.1.0": - version: 0.1.1 - resolution: "is-posix-bracket@npm:0.1.1" - checksum: 631615d7c84800acaa71536359115f4d47ce25e0512b342b18f1790609b40a2e71ded019bd3f8d3395ae9422a420a4bcf517298d52a5559f29d68ebcf787b348 - languageName: node - linkType: hard - -"is-primitive@npm:^2.0.0": - version: 2.0.0 - resolution: "is-primitive@npm:2.0.0" - checksum: 887f209dcefc7c5e78aaddb73d6083cd92fbfbc90b6b3b5e06dd64bdfc6a57bcee58eb48718fa7b4abe96cc641e365dccdc14a43789fc147c319e4fdebd7d4df - languageName: node - linkType: hard - "is-promise@npm:^4.0.0": version: 4.0.0 resolution: "is-promise@npm:4.0.0" @@ -16730,6 +16767,13 @@ fsevents@~2.3.2: languageName: node linkType: hard +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 78011309cb53c19195702ece9e282c8c58d7facd8d6e286857fd4daf511f0bd93424498898d0b9ecfde6ab8e87a2ab0c0a654fba4b1a4ec81fa51f2c48a5ddba + languageName: node + linkType: hard + "json-parse-better-errors@npm:^1.0.0, json-parse-better-errors@npm:^1.0.1": version: 1.0.2 resolution: "json-parse-better-errors@npm:1.0.2" @@ -16784,13 +16828,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"json-schema-traverse@npm:^0.3.0": - version: 0.3.1 - resolution: "json-schema-traverse@npm:0.3.1" - checksum: 0e3c0664cfd1a2416c80d941144d83e37a01bb95c6e19b4ba27eda0c75b95cd2256456a9434b2830a7ef6bbeccd90cfec7cdac06ef7bd4d240d31d97b36f5abe - languageName: node - linkType: hard - "json-schema-traverse@npm:^0.4.1": version: 0.4.1 resolution: "json-schema-traverse@npm:0.4.1" @@ -17010,6 +17047,15 @@ fsevents@~2.3.2: languageName: node linkType: hard +"keyv@npm:^4.0.0": + version: 4.5.2 + resolution: "keyv@npm:4.5.2" + dependencies: + json-buffer: 3.0.1 + checksum: 4e9422315c4f87cf73f1ab1ae5f8238f244e5593c7df5502781def202c35d98d54b783dfd4effcef904d0a115131132e4e9eef5a6ecd0790768c260d39f837dd + languageName: node + linkType: hard + "kind-of@npm:^3.0.2, kind-of@npm:^3.0.3, kind-of@npm:^3.2.0": version: 3.2.2 resolution: "kind-of@npm:3.2.2" @@ -17779,7 +17825,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"lodash@npm:4.17.20, lodash@npm:^4.14.2, lodash@npm:^4.15.0, lodash@npm:^4.17.11, lodash@npm:^4.17.12, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.4, lodash@npm:^4.2.1": +"lodash@npm:4.17.20, lodash@npm:^4.15.0, lodash@npm:^4.17.11, lodash@npm:^4.17.12, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.4, lodash@npm:^4.2.1": version: 4.17.20 resolution: "lodash@npm:4.17.20" checksum: c62101d2500c383b5f174a7e9e6fe8098149ddd6e9ccfa85f36d4789446195f5c4afd3cfba433026bcaf3da271256566b04a2bf2618e5a39f6e67f8c12030cb6 @@ -17936,16 +17982,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"lru-cache@npm:^4.0.1": - version: 4.1.5 - resolution: "lru-cache@npm:4.1.5" - dependencies: - pseudomap: ^1.0.2 - yallist: ^2.1.2 - checksum: 6a098d23629357451d4324e1e4fefccdd6df316df29e25571c6148220ced923258381ebeafdf919f90e28c780b650427390582618c1d5fe097873e656d062511 - languageName: node - linkType: hard - "lru-cache@npm:^6.0.0": version: 6.0.0 resolution: "lru-cache@npm:6.0.0" @@ -18144,13 +18180,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"math-random@npm:^1.0.1": - version: 1.0.4 - resolution: "math-random@npm:1.0.4" - checksum: 84d091e9b24325802d78cae20e7ba249fec8dd21b158f93c36c636a188bee8ae5866cf0743b7f5c2429663735f8ac41bc60fc8b9a1c7f71f79f791d24c7eb893 - languageName: node - linkType: hard - "maximatch@npm:^0.1.0": version: 0.1.0 resolution: "maximatch@npm:0.1.0" @@ -18211,15 +18240,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"mem@npm:^1.1.0": - version: 1.1.0 - resolution: "mem@npm:1.1.0" - dependencies: - mimic-fn: ^1.0.0 - checksum: 53739528aa635af40ad240c35d6e3d31f830aa6841f6886e7fc20f1f54808d9cf8fe100777e06be557b2ff4b066426a56a6ed1eb84db570d8355a9a0075c7069 - languageName: node - linkType: hard - "mem@npm:^4.0.0": version: 4.3.0 resolution: "mem@npm:4.3.0" @@ -18459,27 +18479,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"micromatch@npm:^2.1.5": - version: 2.3.11 - resolution: "micromatch@npm:2.3.11" - dependencies: - arr-diff: ^2.0.0 - array-unique: ^0.2.1 - braces: ^1.8.2 - expand-brackets: ^0.1.4 - extglob: ^0.3.1 - filename-regex: ^2.0.0 - is-extglob: ^1.0.0 - is-glob: ^2.0.1 - kind-of: ^3.0.2 - normalize-path: ^2.0.1 - object.omit: ^2.0.0 - parse-glob: ^3.0.4 - regex-cache: ^0.4.2 - checksum: a08e4977c85a2d954cc91641ce039334eef45e67707658931643359eddeba4c7d65fba5db5ec241beb782c32446ddb8af12d91424921d4e3d3787ae0979eeb34 - languageName: node - linkType: hard - "micromatch@npm:^3.0.4, micromatch@npm:^3.1.10, micromatch@npm:^3.1.4": version: 3.1.10 resolution: "micromatch@npm:3.1.10" @@ -18576,6 +18575,13 @@ fsevents@~2.3.2: languageName: node linkType: hard +"mimic-response@npm:^3.1.0": + version: 3.1.0 + resolution: "mimic-response@npm:3.1.0" + checksum: cfbf19f66de6ad46df7481d9e8c1a7f30b6fa77dd771ad4a72a0443265041a39768182bde6d1de39001c2774168635bc74f42902e401c8ba33db55d69b773004 + languageName: node + linkType: hard + "min-document@npm:^2.19.0": version: 2.19.0 resolution: "min-document@npm:2.19.0" @@ -18652,13 +18658,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"minimist@npm:0.0.8": - version: 0.0.8 - resolution: "minimist@npm:0.0.8" - checksum: d71c4684bce92f9c0500e103498adb5e45bbda551763132a703306c2dab6f3a1f69eb6448c3ff3ea73fb562285dfd6ee3a354d5c0e5dd52e3d5f3037c82c0935 - languageName: node - linkType: hard - "minimist@npm:^1.1.3, minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:~1.2.5": version: 1.2.5 resolution: "minimist@npm:1.2.5" @@ -18810,17 +18809,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"mkdirp@npm:0.5.1": - version: 0.5.1 - resolution: "mkdirp@npm:0.5.1" - dependencies: - minimist: 0.0.8 - bin: - mkdirp: bin/cmd.js - checksum: 8ef65f4f0c7642b2f6e7af417eb9f3f24e8d1e4d612eddc5b1ee3b0ef974ccfaafb38bba6cc9178510c5aae82a6ef9ad85037448c9856b2fb8308162a7c8987e - languageName: node - linkType: hard - "mkdirp@npm:0.5.5, mkdirp@npm:0.5.x, mkdirp@npm:^0.5.0, mkdirp@npm:^0.5.1, mkdirp@npm:~0.5.1": version: 0.5.5 resolution: "mkdirp@npm:0.5.5" @@ -18877,27 +18865,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"mocha@npm:^4.0.1": - version: 4.1.0 - resolution: "mocha@npm:4.1.0" - dependencies: - browser-stdout: 1.3.0 - commander: 2.11.0 - debug: 3.1.0 - diff: 3.3.1 - escape-string-regexp: 1.0.5 - glob: 7.1.2 - growl: 1.10.3 - he: 1.1.1 - mkdirp: 0.5.1 - supports-color: 4.4.0 - bin: - _mocha: ./bin/_mocha - mocha: ./bin/mocha - checksum: 7d3088ddf883cc14636d0c0545eb7ae44a0118ced899a78f09effb8d8601b891d4beb77602045a1b864d7f06c28ce605558df72fa65e5ba442ec99ef0dee5691 - languageName: node - linkType: hard - "mocha@npm:^7.1.1, mocha@npm:^7.1.2": version: 7.2.0 resolution: "mocha@npm:7.2.0" @@ -19753,7 +19720,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"normalize-path@npm:^2.0.0, normalize-path@npm:^2.0.1, normalize-path@npm:^2.1.1": +"normalize-path@npm:^2.1.1": version: 2.1.1 resolution: "normalize-path@npm:2.1.1" dependencies: @@ -19794,6 +19761,13 @@ fsevents@~2.3.2: languageName: node linkType: hard +"normalize-url@npm:^6.0.1": + version: 6.1.0 + resolution: "normalize-url@npm:6.1.0" + checksum: 5fb69e98c149f4a54a7bb0f1904cc524627c0d23327a9feafacacf135d01d9595c65e80ced6f27c17c1959541ea732815b604ff8a6ec52ec3fe7a391b92cfba9 + languageName: node + linkType: hard + "npm-bundled@npm:^1.0.1": version: 1.1.1 resolution: "npm-bundled@npm:1.1.1" @@ -20081,16 +20055,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"object.omit@npm:^2.0.0": - version: 2.0.1 - resolution: "object.omit@npm:2.0.1" - dependencies: - for-own: ^0.1.4 - is-extendable: ^0.1.1 - checksum: 15f149ba748f2573f76116e390ee72ad761d666577b259f032d512f173ad0953d6b2cba96df0ff7a3e0fda4b20862221f00dc70f0edcbdd6f7ad5a7cf1974ad9 - languageName: node - linkType: hard - "object.pick@npm:^1.3.0": version: 1.3.0 resolution: "object.pick@npm:1.3.0" @@ -20216,15 +20180,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"opencollective-postinstall@npm:^2.0.2": - version: 2.0.3 - resolution: "opencollective-postinstall@npm:2.0.3" - bin: - opencollective-postinstall: index.js - checksum: d75b06b80eb426aaf099307ca4398f3119c8c86ff3806a95cfe234b979b80c07080040734fe2dc3c51fed5b15bd98dae88340807980bdc74aa1ebf045c74ef06 - languageName: node - linkType: hard - "openzeppelin-solidity@npm:2.0.0": version: 2.0.0 resolution: "openzeppelin-solidity@npm:2.0.0" @@ -20347,17 +20302,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"os-locale@npm:^2.0.0": - version: 2.1.0 - resolution: "os-locale@npm:2.1.0" - dependencies: - execa: ^0.7.0 - lcid: ^1.0.0 - mem: ^1.1.0 - checksum: f491d24b3ed8edcd2a74363668ddcf9c6e0ed938f70eac27772ad6f7a0de315cc7d267be9a8acb8bdbe7ce8224491348adaeff9547e5384056c7ea6a16ca09dd - languageName: node - linkType: hard - "os-locale@npm:^3.0.0, os-locale@npm:^3.1.0": version: 3.1.0 resolution: "os-locale@npm:3.1.0" @@ -20417,6 +20361,13 @@ fsevents@~2.3.2: languageName: node linkType: hard +"p-cancelable@npm:^2.0.0": + version: 2.1.1 + resolution: "p-cancelable@npm:2.1.1" + checksum: 0ce643f3c9701514b1e831900b94912d1f365bb4a600b586a85fc41ed15fde46ad221793e1a1ba92452df4b5a062f6a0e3840a9812bc068082d1288d15e886af + languageName: node + linkType: hard + "p-defer@npm:^1.0.0": version: 1.0.0 resolution: "p-defer@npm:1.0.0" @@ -20797,18 +20748,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"parse-glob@npm:^3.0.4": - version: 3.0.4 - resolution: "parse-glob@npm:3.0.4" - dependencies: - glob-base: ^0.3.0 - is-dotfile: ^1.0.0 - is-extglob: ^1.0.0 - is-glob: ^2.0.0 - checksum: bc9f7a8ed61b8005cce9b6f63130f9080e7034472b3e0c48cc28bfbad8c1290cca25b4fefddc7cd96d6f44e5bc2bace9e0c1f26e665cb2f693a13c2c7fcd5ff2 - languageName: node - linkType: hard - "parse-headers@npm:^2.0.0": version: 2.0.3 resolution: "parse-headers@npm:2.0.3" @@ -21102,15 +21041,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"pegjs@npm:^0.10.0": - version: 0.10.0 - resolution: "pegjs@npm:0.10.0" - bin: - pegjs: bin/pegjs - checksum: 3d3c011257f35f33357185489f12e99150b25d1f6bd9fcc62d2cee62ece97749d46549dead090b4e16e1173a1ce3264989d56cb4454ae9045512debfdd66d09e - languageName: node - linkType: hard - "pend@npm:~1.2.0": version: 1.2.0 resolution: "pend@npm:1.2.0" @@ -21762,13 +21692,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"preserve@npm:^0.2.0": - version: 0.2.0 - resolution: "preserve@npm:0.2.0" - checksum: b402f0bcfb307f4e19cef52966a8b6b93e398ff91e9b3011ee3986b60c5b42ac2fa955b1287f62ae2150945919ca936fd388cd622bb878cae5e2de9a33de42e8 - languageName: node - linkType: hard - "prettier-linter-helpers@npm:^1.0.0": version: 1.0.0 resolution: "prettier-linter-helpers@npm:1.0.0" @@ -21778,6 +21701,22 @@ fsevents@~2.3.2: languageName: node linkType: hard +"prettier-plugin-solidity@npm:^1.1.0": + version: 1.1.0 + resolution: "prettier-plugin-solidity@npm:1.1.0" + dependencies: + "@solidity-parser/parser": ^0.14.5 + emoji-regex: ^10.2.1 + escape-string-regexp: ^4.0.0 + semver: ^7.3.8 + solidity-comments-extractor: ^0.0.7 + string-width: ^4.2.3 + peerDependencies: + prettier: ^2.3.0 + checksum: 5abaf1480a46b270e6fca948768cc1fd203b8a142ca8efd5ee4687d8599cfbe8fa5991c84ac66aefebe924ea425b4f69b5662277cc7d6d0ce99350f490aa7d1c + languageName: node + linkType: hard + "prettier@npm:^1.14.3": version: 1.19.1 resolution: "prettier@npm:1.19.1" @@ -21788,11 +21727,11 @@ fsevents@~2.3.2: linkType: hard "prettier@npm:^2.1.2": - version: 2.1.2 - resolution: "prettier@npm:2.1.2" + version: 2.8.2 + resolution: "prettier@npm:2.8.2" bin: prettier: bin-prettier.js - checksum: bedc24c568efbcfe45f255558900777e2fabb36bf28ed4c22d2456eb22dfb012d12e566a46472ea5b857a4e9f263e888f7e316527d00bb58188f1820b0b0031a + checksum: 6f85fc14fb0cbdc8b247edff2168fcc5a2bc79fc58b7e9433e245f959781f56dd62202857c896c33e6c20a6d465baabd395201af91dcbb3023bedda07c728f31 languageName: node linkType: hard @@ -21842,7 +21781,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"progress@npm:^2.0.0": +"progress@npm:^2.0.0, progress@npm:^2.0.3": version: 2.0.3 resolution: "progress@npm:2.0.3" checksum: c46ef5a1de4d527dfd32fe56a7df0c1c8b420a4c02617196813bf7f10ac7c2a929afc265d44fdd68f5c439a7e7cb3d70d569716c82d6b4148ec72089860a1312 @@ -22045,7 +21984,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"pseudomap@npm:^1.0.1, pseudomap@npm:^1.0.2": +"pseudomap@npm:^1.0.1": version: 1.0.2 resolution: "pseudomap@npm:1.0.2" checksum: 1ad1802645e830d99f9c1db97efc6902d2316b660454633229f636dd59e751d00498dd325d3b18d49f2be990a2c9d28f8bfe6f9b544a8220a5faa2bfb4694bb7 @@ -22284,6 +22223,13 @@ fsevents@~2.3.2: languageName: node linkType: hard +"quick-lru@npm:^5.1.1": + version: 5.1.1 + resolution: "quick-lru@npm:5.1.1" + checksum: fafb2b2fa1a948d6f2e88d4a60571be70b316d9b0be857d24fba0ac28fc31acebf535b643fe968473d689f8c655bcb2a0e4da67912f571059a4e4eb15740b021 + languageName: node + linkType: hard + "quote-stream@npm:^1.0.1, quote-stream@npm:~1.0.2": version: 1.0.2 resolution: "quote-stream@npm:1.0.2" @@ -22297,17 +22243,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"randomatic@npm:^3.0.0": - version: 3.1.1 - resolution: "randomatic@npm:3.1.1" - dependencies: - is-number: ^4.0.0 - kind-of: ^6.0.0 - math-random: ^1.0.1 - checksum: a70d5cc7b09eebe5964dd7e0cf37faa328ab744bcdbb171b529af12a1174c8b8024c2174bf23e6d80504e69e9ddbabce4c1d3984509ff9db86e91d4d161d2cae - languageName: node - linkType: hard - "randombytes@npm:^2.0.0, randombytes@npm:^2.0.1, randombytes@npm:^2.0.5, randombytes@npm:^2.0.6, randombytes@npm:^2.1.0": version: 2.1.0 resolution: "randombytes@npm:2.1.0" @@ -22657,7 +22592,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"readdirp@npm:^2.0.0, readdirp@npm:^2.2.1": +"readdirp@npm:^2.2.1": version: 2.2.1 resolution: "readdirp@npm:2.2.1" dependencies: @@ -22836,15 +22771,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"regex-cache@npm:^0.4.2": - version: 0.4.4 - resolution: "regex-cache@npm:0.4.4" - dependencies: - is-equal-shallow: ^0.1.3 - checksum: e4d3dd07bd1ae9160b4a79440a96a4e9400ea7f3e284c73b072310c62e98eec06e41dd6bae464370de41ab3bfdb664b9ebc261b7a17bc9fa73f39d439f03da75 - languageName: node - linkType: hard - "regex-not@npm:^1.0.0, regex-not@npm:^1.0.2": version: 1.0.2 resolution: "regex-not@npm:1.0.2" @@ -22972,7 +22898,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"repeat-string@npm:^1.5.2, repeat-string@npm:^1.6.1": +"repeat-string@npm:^1.6.1": version: 1.6.1 resolution: "repeat-string@npm:1.6.1" checksum: 99c431ba7bef7a5d39819d562ebca89206368b45f73213677a3b562e25b5dd272d9e6a2ca8105001df14b6fc8cc71f0b10258c86e16cf8a256318fac1ddc8a77 @@ -23100,6 +23026,13 @@ fsevents@~2.3.2: languageName: node linkType: hard +"resolve-alpn@npm:^1.0.0": + version: 1.2.1 + resolution: "resolve-alpn@npm:1.2.1" + checksum: d009cb9e77362c480d771f0159abcb011ddaabba32912c50d802d7132439f1eb2bdd6f5e6159fb630f142ed9e0d2c034d0bed8e0dea6ce9e04c14a6a225ec738 + languageName: node + linkType: hard + "resolve-cwd@npm:^2.0.0": version: 2.0.0 resolution: "resolve-cwd@npm:2.0.0" @@ -23187,6 +23120,15 @@ resolve@1.1.x: languageName: node linkType: hard +"responselike@npm:^2.0.0": + version: 2.0.1 + resolution: "responselike@npm:2.0.1" + dependencies: + lowercase-keys: ^2.0.0 + checksum: 057894a352b589fe07205c3b145c00de243454ff6a52838098f103ab560482b841311c8156fa13fec41e0b337a59b81c95b8bd1a21a04c3a55f69f212bdf8535 + languageName: node + linkType: hard + "restore-cursor@npm:^2.0.0": version: 2.0.0 resolution: "restore-cursor@npm:2.0.0" @@ -23326,6 +23268,20 @@ resolve@1.1.x: languageName: node linkType: hard +"roarr@npm:^2.15.3": + version: 2.15.4 + resolution: "roarr@npm:2.15.4" + dependencies: + boolean: ^3.0.1 + detect-node: ^2.0.4 + globalthis: ^1.0.1 + json-stringify-safe: ^5.0.1 + semver-compare: ^1.0.0 + sprintf-js: ^1.1.2 + checksum: 14f4920920e9e39717ce9cd2203694a0cfed06723d16328990c1507d768daed6b9256f58cc61e96ccebc93378a6f50af7982debee03bf88c04eddf8bf45d1897 + languageName: node + linkType: hard + "run-async@npm:^2.2.0": version: 2.4.1 resolution: "run-async@npm:2.4.1" @@ -23588,13 +23544,6 @@ resolve@1.1.x: languageName: node linkType: hard -"semver-regex@npm:^2.0.0": - version: 2.0.0 - resolution: "semver-regex@npm:2.0.0" - checksum: 9b96cc8bd559c1d46968b334ccc88115a2d9d2f7a2125d6838471114ed0c52057e77aae760fbe4932aee06687584733b32aed6d2c9654b2db33e383bfb8f26ce - languageName: node - linkType: hard - "semver@npm:2 || 3 || 4 || 5, semver@npm:2.x || 3.x || 4 || 5, semver@npm:^5.3.0, semver@npm:^5.4.1, semver@npm:^5.5.0, semver@npm:^5.5.1, semver@npm:^5.6.0, semver@npm:^5.7.0, semver@npm:^5.7.1": version: 5.7.1 resolution: "semver@npm:5.7.1" @@ -23662,6 +23611,17 @@ resolve@1.1.x: languageName: node linkType: hard +"semver@npm:^7.3.8": + version: 7.3.8 + resolution: "semver@npm:7.3.8" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: cfb9c2101dae4ee93c415471f48797c750174d65def4e06ff691bf910194cc6ca92793e597be8302175ceb640100a3da36451e7656320da53b51167eeaf11eb5 + languageName: node + linkType: hard + "semver@npm:~5.4.1": version: 5.4.1 resolution: "semver@npm:5.4.1" @@ -23699,6 +23659,15 @@ resolve@1.1.x: languageName: node linkType: hard +"serialize-error@npm:^7.0.1": + version: 7.0.1 + resolution: "serialize-error@npm:7.0.1" + dependencies: + type-fest: ^0.13.1 + checksum: 2ed46997358204fa3bfef142104b0c9bfe3530acccd709caad4e89dac1dd6390bf54b2edec0dcab0242fc7814e73a9456813998e7ecc5b734cf2b9b92287c742 + languageName: node + linkType: hard + "serialize-javascript@npm:4.0.0": version: 4.0.0 resolution: "serialize-javascript@npm:4.0.0" @@ -24113,20 +24082,6 @@ resolve@1.1.x: languageName: node linkType: hard -"sol-digger@npm:0.0.2": - version: 0.0.2 - resolution: "sol-digger@npm:0.0.2" - checksum: 6d461e1d43bb664ccefcbf152ef8421e2a6f3f935ac6a3a0fd4dd8cde6d3e5ac0be3b080824b051b9d4d42089aef79800c1ce23526c59cecb0d9f79b86826d17 - languageName: node - linkType: hard - -"sol-explore@npm:1.6.1": - version: 1.6.1 - resolution: "sol-explore@npm:1.6.1" - checksum: 859d3c4210dd31aa3c4b93c6efd2c439de81cd2535f430fdd070e0b774d1f79b970c2f89246e4a798a031aaf0bd226c92195dd52b1f51286f91a0d2bca73a7d5 - languageName: node - linkType: hard - "solc@npm:0.6.8": version: 0.6.8 resolution: "solc@npm:0.6.8" @@ -24186,11 +24141,11 @@ resolve@1.1.x: languageName: node linkType: hard -"solhint@npm:^3.2.2": - version: 3.2.2 - resolution: "solhint@npm:3.2.2" +"solhint@npm:^3.3.7": + version: 3.3.7 + resolution: "solhint@npm:3.3.7" dependencies: - "@solidity-parser/parser": ^0.8.1 + "@solidity-parser/parser": ^0.14.1 ajv: ^6.6.1 antlr4: 4.7.1 ast-parents: 0.0.1 @@ -24210,7 +24165,7 @@ resolve@1.1.x: optional: true bin: solhint: solhint.js - checksum: 4b77b5d6490233ec49899149b103ebec33518fcb5eee4a2078a363e8b7d8247c9edeed3650bce37e180fe777da88eba8abdbb946d3933fa1558fb1d1a809ebce + checksum: b0a3e0806bef88be734002ee5b6634fa261d612d946688ddc803cf582995b9b3abc8debe35beb10d4201dacf97870b783f703c0f5b3c5b3ebf195f5d061e7f98 languageName: node linkType: hard @@ -24223,6 +24178,13 @@ resolve@1.1.x: languageName: node linkType: hard +"solidity-comments-extractor@npm:^0.0.7": + version: 0.0.7 + resolution: "solidity-comments-extractor@npm:0.0.7" + checksum: b3a463999290067e87f65335e0397d5932b21734c3b0867cad7dc710968120ec58e9f9a633001724b60a2bfb0c5d2aa90a7038fe2234fb130563564c5526200d + languageName: node + linkType: hard + "solidity-coverage@npm:^0.7.18": version: 0.7.18 resolution: "solidity-coverage@npm:0.7.18" @@ -24252,51 +24214,6 @@ resolve@1.1.x: languageName: node linkType: hard -"solium-plugin-security@npm:0.1.1": - version: 0.1.1 - resolution: "solium-plugin-security@npm:0.1.1" - peerDependencies: - solium: ^1.0.0 - checksum: 0e8cf592e033d07fe67ac17f8fd676213edf37ee3a13973ab7825ff77e0a7f680e07f5d6af4261beee45847300c364584b228975f690f13d66d95b224caa78af - languageName: node - linkType: hard - -"solium@npm:1.2.5, solium@npm:^1.2.5": - version: 1.2.5 - resolution: "solium@npm:1.2.5" - dependencies: - ajv: ^5.2.2 - chokidar: ^1.6.0 - colors: ^1.1.2 - commander: ^2.9.0 - diff: ^3.5.0 - eol: ^0.9.1 - js-string-escape: ^1.0.1 - lodash: ^4.14.2 - sol-digger: 0.0.2 - sol-explore: 1.6.1 - solium-plugin-security: 0.1.1 - solparse: 2.2.8 - text-table: ^0.2.0 - bin: - solium: ./bin/solium.js - checksum: 51163495020c6666245ebbeb1a50e0cc2201b194d70cf8396f3b8937a7151561697d7a7cce10e161007ff7e80df07af13bc9f2e48ca9aa72ceabb70d1b0ed482 - languageName: node - linkType: hard - -"solparse@npm:2.2.8": - version: 2.2.8 - resolution: "solparse@npm:2.2.8" - dependencies: - mocha: ^4.0.1 - pegjs: ^0.10.0 - yargs: ^10.0.3 - bin: - solidity-parser: ./cli.js - checksum: e8a4a8e63170cb1a9c64386c74df491506276f2e2f1b7731203d6276841c51ce8b9e973115981b751fc5889715291b374d1889cef6b036362260596b44d2696c - languageName: node - linkType: hard - "sort-keys-length@npm:^1.0.0": version: 1.0.1 resolution: "sort-keys-length@npm:1.0.1" @@ -24473,6 +24390,13 @@ resolve@1.1.x: languageName: node linkType: hard +"sprintf-js@npm:^1.1.2": + version: 1.1.2 + resolution: "sprintf-js@npm:1.1.2" + checksum: 50d2008328a3cafac658a40de7fb7d3d899b8e12906b3784113a97199618531bc4237ef2779cc5a9cd6df06b58036fedf4578648a5bbfd01825ecb56546e3982 + languageName: node + linkType: hard + "sprintf-js@npm:~1.0.2": version: 1.0.3 resolution: "sprintf-js@npm:1.0.3" @@ -25034,6 +24958,15 @@ resolve@1.1.x: languageName: node linkType: hard +"sumchecker@npm:^3.0.1": + version: 3.0.1 + resolution: "sumchecker@npm:3.0.1" + dependencies: + debug: ^4.1.0 + checksum: 7f4f9276f77124e688cc469bf77e57bba79e33246c9b053e97be2ef6c1d1ac4fd84ffddbc85bfbaab2c40b3fccadf85d0895b6761eb5a68121916d325d9d9921 + languageName: node + linkType: hard + "super-split@npm:^1.1.0": version: 1.1.0 resolution: "super-split@npm:1.1.0" @@ -25054,15 +24987,6 @@ resolve@1.1.x: languageName: node linkType: hard -"supports-color@npm:4.4.0": - version: 4.4.0 - resolution: "supports-color@npm:4.4.0" - dependencies: - has-flag: ^2.0.0 - checksum: 72b8b3a4af443ff81bfe0fc9a278f83e31bcd7e2b57d2af89f3200d7d03847d0cf74fd9688731efd92a3078831cb1190bb3aa8fe5f512a83711100eb2d8218d7 - languageName: node - linkType: hard - "supports-color@npm:6.0.0": version: 6.0.0 resolution: "supports-color@npm:6.0.0" @@ -26143,11 +26067,11 @@ resolve@1.1.x: linkType: hard "undici@npm:^5.4.0": - version: 5.14.0 - resolution: "undici@npm:5.14.0" + version: 5.13.0 + resolution: "undici@npm:5.13.0" dependencies: busboy: ^1.6.0 - checksum: 5bd25bc29444c862932f8c1e27b3ecf84eb4815941e0010bf3a7f0cd0d375e9c4a32a8e79483cce57d444701ba811c59bb79c5dc7b4c558b60a76cc6945cdd0e + checksum: 80e4c4476b89b2b49120b97a00a491a5b77d5d4ad4c82910d9b965f66663ee42d5cde27b713f64766868a4512a77bdba17a86ef30b81aafae759b143530460d7 languageName: node linkType: hard @@ -28034,13 +27958,6 @@ resolve@1.1.x: languageName: node linkType: hard -"which-pm-runs@npm:^1.0.0": - version: 1.0.0 - resolution: "which-pm-runs@npm:1.0.0" - checksum: 0bb79a782e98955afec8f35a3ae95c4711fdd3d0743772ee98211da67c2421fdd4c92c95c93532cc0b4dcc085d8e27f3ad2f8a9173cb632692379bd3d2818821 - languageName: node - linkType: hard - "which@npm:1.3.1, which@npm:^1.1.1, which@npm:^1.2.9, which@npm:^1.3.1": version: 1.3.1 resolution: "which@npm:1.3.1" @@ -28292,9 +28209,9 @@ resolve@1.1.x: languageName: node linkType: hard -"ws@npm:7.2.3": - version: 7.2.3 - resolution: "ws@npm:7.2.3" +"ws@npm:7.4.6": + version: 7.4.6 + resolution: "ws@npm:7.4.6" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ^5.0.2 @@ -28303,7 +28220,7 @@ resolve@1.1.x: optional: true utf-8-validate: optional: true - checksum: fedf178c29b5f7c4589c88bd1064dea03ba0d761bfcc0562abf4ddd64421fb1767095e140cb3ef8b57c3315d7e1fa1f3cf872e365a399c69fb3566ecdfbfacee + checksum: ffeb626d92f14aa3a67aa3784824c1d290131e25d225f4ca6b1b6b6d7ea754fca67694d89a5b99b144382365e1bccf1f7ec2f92df56f0d25f44939b070452f06 languageName: node linkType: hard @@ -28504,13 +28421,6 @@ resolve@1.1.x: languageName: node linkType: hard -"yallist@npm:^2.1.2": - version: 2.1.2 - resolution: "yallist@npm:2.1.2" - checksum: f83e3d18eeba68a0276be2ab09260be3f2a300307e84b1565c620ef71f03f106c3df9bec4c3a91e5fa621a038f8826c19b3786804d3795dd4f999e5b6be66ea3 - languageName: node - linkType: hard - "yallist@npm:^3.0.0, yallist@npm:^3.0.2, yallist@npm:^3.0.3": version: 3.1.1 resolution: "yallist@npm:3.1.1" @@ -28603,15 +28513,6 @@ resolve@1.1.x: languageName: node linkType: hard -"yargs-parser@npm:^8.1.0": - version: 8.1.0 - resolution: "yargs-parser@npm:8.1.0" - dependencies: - camelcase: ^4.1.0 - checksum: a8c3b2ba185d3451736d0f42d7e6ec828242da38055d9541b8bc431251f765a6038a0b4aff73414e8d900ce49c857a766b702192d1ef5c91b292273987515972 - languageName: node - linkType: hard - "yargs-unparser@npm:1.6.0": version: 1.6.0 resolution: "yargs-unparser@npm:1.6.0" @@ -28700,26 +28601,6 @@ resolve@1.1.x: languageName: node linkType: hard -"yargs@npm:^10.0.3": - version: 10.1.2 - resolution: "yargs@npm:10.1.2" - dependencies: - cliui: ^4.0.0 - decamelize: ^1.1.1 - find-up: ^2.1.0 - get-caller-file: ^1.0.1 - os-locale: ^2.0.0 - require-directory: ^2.1.1 - require-main-filename: ^1.0.1 - set-blocking: ^2.0.0 - string-width: ^2.0.0 - which-module: ^2.0.0 - y18n: ^3.2.1 - yargs-parser: ^8.1.0 - checksum: 965891b04be27b07ec25e6775ce03099441fbc5d7240ee9b049529db7033e9952b0f5e4e64f0b63b77e34cee67bc7932f3884acdc5cb0f8700ddae08bf1787c9 - languageName: node - linkType: hard - "yargs@npm:^12.0.1": version: 12.0.5 resolution: "yargs@npm:12.0.5" @@ -28830,7 +28711,7 @@ resolve@1.1.x: languageName: node linkType: hard -"yauzl@npm:^2.4.2": +"yauzl@npm:^2.10.0, yauzl@npm:^2.4.2": version: 2.10.0 resolution: "yauzl@npm:2.10.0" dependencies: