Source Code
Overview
ETH Balance
0 ETH
More Info
ContractCreator
Multichain Info
N/A
| Transaction Hash |
Method
|
Block
|
From
|
To
|
Amount
|
||||
|---|---|---|---|---|---|---|---|---|---|
Latest 25 internal transactions (View All)
Advanced mode:
| Parent Transaction Hash | Method | Block |
From
|
To
|
Amount
|
||
|---|---|---|---|---|---|---|---|
| Handle Oracle Re... | 1498581 | 42 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1498240 | 42 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1497895 | 42 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1497561 | 42 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1497222 | 42 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1496871 | 42 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1496528 | 42 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1496185 | 42 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1495843 | 42 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1495498 | 42 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1495148 | 42 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1494799 | 42 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1494454 | 42 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1494122 | 43 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1493773 | 43 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1493415 | 43 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1493066 | 43 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1492709 | 43 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1492369 | 43 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1492023 | 43 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1491682 | 43 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1491334 | 43 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1490982 | 43 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1490640 | 43 days ago | 0 ETH | ||||
| Handle Oracle Re... | 1490292 | 43 days ago | 0 ETH |
Loading...
Loading
Loading...
Loading
Contract Name:
Accounting
Compiler Version
v0.8.9+commit.e5eed63a
Optimization Enabled:
Yes with 200 runs
Other Settings:
istanbul EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.9; import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol"; import {IBurner} from "contracts/common/interfaces/IBurner.sol"; import {IOracleReportSanityChecker} from "contracts/common/interfaces/IOracleReportSanityChecker.sol"; import {ILido} from "contracts/common/interfaces/ILido.sol"; import {ReportValues} from "contracts/common/interfaces/ReportValues.sol"; import {IVaultHub} from "contracts/common/interfaces/IVaultHub.sol"; import {IPostTokenRebaseReceiver} from "./interfaces/IPostTokenRebaseReceiver.sol"; import {WithdrawalQueue} from "./WithdrawalQueue.sol"; import {StakingRouter} from "./StakingRouter.sol"; /// @title Lido Accounting contract /// @author folkyatina /// @notice contract is responsible for handling accounting oracle reports /// calculating all the state changes that is required to apply the report /// and distributing calculated values to relevant parts of the protocol contract Accounting { struct Contracts { address accountingOracle; IOracleReportSanityChecker oracleReportSanityChecker; IBurner burner; WithdrawalQueue withdrawalQueue; IPostTokenRebaseReceiver postTokenRebaseReceiver; StakingRouter stakingRouter; IVaultHub vaultHub; } struct PreReportState { uint256 clValidators; uint256 clBalance; uint256 totalPooledEther; uint256 totalShares; uint256 depositedValidators; uint256 externalShares; uint256 externalEther; uint256 badDebtToInternalize; } /// @notice precalculated values that is used to change the state of the protocol during the report struct CalculatedValues { /// @notice amount of ether to collect from WithdrawalsVault to the buffer uint256 withdrawals; /// @notice amount of ether to collect from ELRewardsVault to the buffer uint256 elRewards; /// @notice amount of ether to transfer to WithdrawalQueue to finalize requests uint256 etherToFinalizeWQ; /// @notice number of stETH shares to transfer to Burner because of WQ finalization uint256 sharesToFinalizeWQ; /// @notice number of stETH shares transferred from WQ that will be burned this (to be removed) uint256 sharesToBurnForWithdrawals; /// @notice number of stETH shares that will be burned from Burner this report uint256 totalSharesToBurn; /// @notice number of stETH shares to mint as a protocol fee uint256 sharesToMintAsFees; /// @notice amount of NO fees to transfer to each module StakingRewardsDistribution rewardDistribution; /// @notice amount of CL ether that is not rewards earned during this report period /// the sum of CL balance on the previous report and the amount of fresh deposits since then uint256 principalClBalance; /// @notice total number of stETH shares before the report is applied uint256 preTotalShares; /// @notice amount of ether under the protocol before the report is applied uint256 preTotalPooledEther; /// @notice total number of internal (not backed by vaults) stETH shares after the report is applied uint256 postInternalShares; /// @notice amount of ether under the protocol after the report is applied uint256 postInternalEther; /// @notice total number of stETH shares after the report is applied uint256 postTotalShares; /// @notice amount of ether under the protocol after the report is applied uint256 postTotalPooledEther; } struct StakingRewardsDistribution { address[] recipients; uint256[] moduleIds; uint96[] modulesFees; uint96 totalFee; uint256 precisionPoints; } error NotAuthorized(string operation, address addr); /// @notice deposit size in wei (for pre-maxEB accounting) uint256 private constant DEPOSIT_SIZE = 32 ether; /// @notice Lido Locator contract ILidoLocator public immutable LIDO_LOCATOR; /// @notice Lido contract ILido public immutable LIDO; /// @param _lidoLocator Lido Locator contract /// @param _lido Lido contract constructor( ILidoLocator _lidoLocator, ILido _lido ) { LIDO_LOCATOR = _lidoLocator; LIDO = _lido; } /// @notice calculates all the state changes that is required to apply the report /// @param _report report values /// @param _withdrawalShareRate maximum share rate used for withdrawal finalization /// if _withdrawalShareRate == 0, no withdrawals are /// simulated function simulateOracleReport( ReportValues calldata _report, uint256 _withdrawalShareRate ) public view returns (CalculatedValues memory update) { Contracts memory contracts = _loadOracleReportContracts(); PreReportState memory pre = _snapshotPreReportState(contracts); return _simulateOracleReport(contracts, pre, _report, _withdrawalShareRate); } /// @notice Updates accounting stats, collects EL rewards and distributes collected rewards /// if beacon balance increased, performs withdrawal requests finalization /// @dev periodically called by the AccountingOracle contract function handleOracleReport(ReportValues calldata _report) external { Contracts memory contracts = _loadOracleReportContracts(); if (msg.sender != contracts.accountingOracle) revert NotAuthorized("handleOracleReport", msg.sender); ( PreReportState memory pre, CalculatedValues memory update, uint256 withdrawalsShareRate ) = _calculateOracleReportContext(contracts, _report); _applyOracleReportContext(contracts, _report, pre, update, withdrawalsShareRate); } /// @dev prepare all the required data to process the report function _calculateOracleReportContext( Contracts memory _contracts, ReportValues calldata _report ) internal view returns (PreReportState memory pre, CalculatedValues memory update, uint256 withdrawalsShareRate) { pre = _snapshotPreReportState(_contracts); CalculatedValues memory updateNoWithdrawals = _simulateOracleReport(_contracts, pre, _report, 0); withdrawalsShareRate = (updateNoWithdrawals.postTotalPooledEther * 1e27) / updateNoWithdrawals.postTotalShares; update = _simulateOracleReport(_contracts, pre, _report, withdrawalsShareRate); } /// @dev reads the current state of the protocol to the memory function _snapshotPreReportState(Contracts memory _contracts) internal view returns (PreReportState memory pre) { (pre.depositedValidators, pre.clValidators, pre.clBalance) = LIDO.getBeaconStat(); pre.totalPooledEther = LIDO.getTotalPooledEther(); pre.totalShares = LIDO.getTotalShares(); pre.externalShares = LIDO.getExternalShares(); pre.externalEther = LIDO.getExternalEther(); pre.badDebtToInternalize = _contracts.vaultHub.badDebtToInternalizeAsOfLastRefSlot(); } /// @dev calculates all the state changes that is required to apply the report /// @dev if _withdrawalsShareRate == 0, no withdrawals are simulated function _simulateOracleReport( Contracts memory _contracts, PreReportState memory _pre, ReportValues calldata _report, uint256 _withdrawalsShareRate ) internal view returns (CalculatedValues memory update) { update.preTotalShares = _pre.totalShares; update.preTotalPooledEther = _pre.totalPooledEther; update.rewardDistribution = _getStakingRewardsDistribution(_contracts.stakingRouter); if (_withdrawalsShareRate != 0) { // Get the ether to lock for withdrawal queue and shares to move to Burner to finalize requests (update.etherToFinalizeWQ, update.sharesToFinalizeWQ) = _calculateWithdrawals( _contracts, _report, _withdrawalsShareRate ); } // Principal CL balance is the sum of the current CL balance and // validator deposits during this report // TODO: to support maxEB we need to get rid of validator counting update.principalClBalance = _pre.clBalance + (_report.clValidators - _pre.clValidators) * DEPOSIT_SIZE; // Limit the rebase to avoid oracle frontrunning // by leaving some ether to sit in EL rewards vault or withdrawals vault // and/or leaving some shares unburnt on Burner to be processed on future reports ( update.withdrawals, update.elRewards, update.sharesToBurnForWithdrawals, update.totalSharesToBurn // shares to burn from Burner balance ) = _contracts.oracleReportSanityChecker.smoothenTokenRebase( _pre.totalPooledEther, _pre.totalShares, update.principalClBalance, _report.clBalance, _report.withdrawalVaultBalance, _report.elRewardsVaultBalance, _report.sharesRequestedToBurn, update.etherToFinalizeWQ, update.sharesToFinalizeWQ ); uint256 postInternalSharesBeforeFees = _pre.totalShares - _pre.externalShares // internal shares before - update.totalSharesToBurn; // shares to be burned for withdrawals and cover update.postInternalEther = _pre.totalPooledEther - _pre.externalEther // internal ether before + _report.clBalance + update.withdrawals - update.principalClBalance // total cl rewards (or penalty) + update.elRewards // MEV and tips - update.etherToFinalizeWQ; // withdrawals // Pre-calculate total amount of protocol fees as the amount of shares that will be minted to pay it update.sharesToMintAsFees = _calculateLidoProtocolFeeShares(_report, update, postInternalSharesBeforeFees, update.postInternalEther); update.postInternalShares = postInternalSharesBeforeFees + update.sharesToMintAsFees + _pre.badDebtToInternalize; uint256 postExternalShares = _pre.externalShares - _pre.badDebtToInternalize; // can't underflow by design update.postTotalShares = update.postInternalShares + postExternalShares; update.postTotalPooledEther = update.postInternalEther + postExternalShares * update.postInternalEther / update.postInternalShares; } /// @dev return amount to lock on withdrawal queue and shares to burn depending on the finalization batch parameters function _calculateWithdrawals( Contracts memory _contracts, ReportValues calldata _report, uint256 _simulatedShareRate ) internal view returns (uint256 etherToLock, uint256 sharesToBurn) { if (_report.withdrawalFinalizationBatches.length != 0 && !_contracts.withdrawalQueue.isPaused()) { (etherToLock, sharesToBurn) = _contracts.withdrawalQueue.prefinalize( _report.withdrawalFinalizationBatches, _simulatedShareRate ); } } /// @dev calculates shares that are minted as the protocol fees function _calculateLidoProtocolFeeShares( ReportValues calldata _report, CalculatedValues memory _update, uint256 _internalSharesBeforeFees, uint256 _internalEther ) internal pure returns (uint256 sharesToMintAsFees) { // we are calculating the share rate equal to the post-rebase share rate // but with fees taken as ether deduction instead of minting shares // to learn the amount of shares we need to mint to compensate for this fee uint256 unifiedClBalance = _report.clBalance + _update.withdrawals; // Don't mint/distribute any protocol fee on the non-profitable Lido oracle report // (when consensus layer balance delta is zero or negative). // See LIP-12 for details: // https://research.lido.fi/t/lip-12-on-chain-part-of-the-rewards-distribution-after-the-merge/1625 if (unifiedClBalance > _update.principalClBalance) { uint256 totalRewards = unifiedClBalance - _update.principalClBalance + _update.elRewards; uint256 totalFee = _update.rewardDistribution.totalFee; uint256 precision = _update.rewardDistribution.precisionPoints; // amount of fees in ether uint256 feeEther = (totalRewards * totalFee) / precision; // but we won't pay fees in ether, so we need to calculate how many shares we need to mint as fees // using the share rate that takes fees into account // the share rate is the same as the post-rebase share rate // but with fees taken as ether deduction instead of minting shares // to learn the amount of shares we need to mint to compensate for this fee sharesToMintAsFees = (feeEther * _internalSharesBeforeFees) / (_internalEther - feeEther); } } /// @dev applies the precalculated changes to the protocol state function _applyOracleReportContext( Contracts memory _contracts, ReportValues calldata _report, PreReportState memory _pre, CalculatedValues memory _update, uint256 _withdrawalShareRate ) internal { _checkAccountingOracleReport(_contracts, _report, _pre, _update); uint256 lastWithdrawalRequestToFinalize; if (_update.sharesToFinalizeWQ > 0) { _contracts.burner.requestBurnShares(address(_contracts.withdrawalQueue), _update.sharesToFinalizeWQ); lastWithdrawalRequestToFinalize = _report.withdrawalFinalizationBatches[ _report.withdrawalFinalizationBatches.length - 1 ]; } LIDO.processClStateUpdate( _report.timestamp, _pre.clValidators, _report.clValidators, _report.clBalance ); if (_pre.badDebtToInternalize > 0) { _contracts.vaultHub.decreaseInternalizedBadDebt(_pre.badDebtToInternalize); LIDO.internalizeExternalBadDebt(_pre.badDebtToInternalize); } if (_update.totalSharesToBurn > 0) { _contracts.burner.commitSharesToBurn(_update.totalSharesToBurn); } // Distribute protocol fee (treasury & node operators) if (_update.sharesToMintAsFees > 0) { _distributeFee(_contracts.stakingRouter, _update.rewardDistribution, _update.sharesToMintAsFees); } LIDO.collectRewardsAndProcessWithdrawals( _report.timestamp, _report.clBalance, _update.principalClBalance, _update.withdrawals, _update.elRewards, lastWithdrawalRequestToFinalize, _withdrawalShareRate, _update.etherToFinalizeWQ ); _notifyRebaseObserver(_contracts.postTokenRebaseReceiver, _report, _pre, _update); LIDO.emitTokenRebase( _report.timestamp, _report.timeElapsed, _pre.totalShares, _pre.totalPooledEther, _update.postTotalShares, _update.postTotalPooledEther, _update.postInternalShares, _update.postInternalEther, _update.sharesToMintAsFees ); } /// @dev checks the provided oracle data internally and against the sanity checker contract /// reverts if a check fails function _checkAccountingOracleReport( Contracts memory _contracts, ReportValues calldata _report, PreReportState memory _pre, CalculatedValues memory _update ) internal { if (_report.timestamp >= block.timestamp) revert IncorrectReportTimestamp(_report.timestamp, block.timestamp); if (_report.clValidators < _pre.clValidators || _report.clValidators > _pre.depositedValidators) { revert IncorrectReportValidators(_report.clValidators, _pre.clValidators, _pre.depositedValidators); } _contracts.oracleReportSanityChecker.checkAccountingOracleReport( _report.timeElapsed, _update.principalClBalance, _report.clBalance, _report.withdrawalVaultBalance, _report.elRewardsVaultBalance, _report.sharesRequestedToBurn, _pre.clValidators, _report.clValidators ); if (_report.withdrawalFinalizationBatches.length > 0) { _contracts.oracleReportSanityChecker.checkWithdrawalQueueOracleReport( _report.withdrawalFinalizationBatches[_report.withdrawalFinalizationBatches.length - 1], _report.timestamp ); } } /// @dev Notify observer about the completed token rebase. function _notifyRebaseObserver( IPostTokenRebaseReceiver _postTokenRebaseReceiver, ReportValues calldata _report, PreReportState memory _pre, CalculatedValues memory _update ) internal { if (address(_postTokenRebaseReceiver) != address(0)) { _postTokenRebaseReceiver.handlePostTokenRebase( _report.timestamp, _report.timeElapsed, _pre.totalShares, _pre.totalPooledEther, _update.postTotalShares, _update.postTotalPooledEther, _update.sharesToMintAsFees ); } } /// @dev mints protocol fees to the treasury and node operators function _distributeFee( StakingRouter _stakingRouter, StakingRewardsDistribution memory _rewardsDistribution, uint256 _sharesToMintAsFees ) internal { (uint256[] memory moduleFees, uint256 totalModuleFees) = _mintModuleFees( _rewardsDistribution.recipients, _rewardsDistribution.modulesFees, _rewardsDistribution.totalFee, _sharesToMintAsFees ); _mintTreasuryFees(_sharesToMintAsFees - totalModuleFees); _stakingRouter.reportRewardsMinted(_rewardsDistribution.moduleIds, moduleFees); } /// @dev mint rewards to the StakingModule recipients function _mintModuleFees( address[] memory _recipients, uint96[] memory _modulesFees, uint256 _totalFee, uint256 _totalFees ) internal returns (uint256[] memory moduleFees, uint256 totalModuleFees) { moduleFees = new uint256[](_recipients.length); for (uint256 i; i < _recipients.length; ++i) { if (_modulesFees[i] > 0) { uint256 iModuleFees = (_totalFees * _modulesFees[i]) / _totalFee; moduleFees[i] = iModuleFees; LIDO.mintShares(_recipients[i], iModuleFees); totalModuleFees = totalModuleFees + iModuleFees; } } } /// @dev mints treasury fees function _mintTreasuryFees(uint256 _amount) internal { address treasury = LIDO_LOCATOR.treasury(); LIDO.mintShares(treasury, _amount); } /// @dev loads the required contracts from the LidoLocator to the struct in the memory function _loadOracleReportContracts() internal view returns (Contracts memory) { ( address accountingOracle, address oracleReportSanityChecker, address burner, address withdrawalQueue, address postTokenRebaseReceiver, address stakingRouter, address vaultHub ) = LIDO_LOCATOR.oracleReportComponents(); return Contracts( accountingOracle, IOracleReportSanityChecker(oracleReportSanityChecker), IBurner(burner), WithdrawalQueue(withdrawalQueue), IPostTokenRebaseReceiver(postTokenRebaseReceiver), StakingRouter(payable(stakingRouter)), IVaultHub(payable(vaultHub)) ); } /// @dev loads the staking rewards distribution to the struct in the memory function _getStakingRewardsDistribution( StakingRouter _stakingRouter ) internal view returns (StakingRewardsDistribution memory ret) { (ret.recipients, ret.moduleIds, ret.modulesFees, ret.totalFee, ret.precisionPoints) = _stakingRouter .getStakingRewardsDistribution(); if (ret.recipients.length != ret.modulesFees.length) revert UnequalArrayLengths(ret.recipients.length, ret.modulesFees.length); if (ret.moduleIds.length != ret.modulesFees.length) revert UnequalArrayLengths(ret.moduleIds.length, ret.modulesFees.length); } error UnequalArrayLengths(uint256 firstArrayLength, uint256 secondArrayLength); error IncorrectReportTimestamp(uint256 reportTimestamp, uint256 upperBoundTimestamp); error IncorrectReportValidators(uint256 reportValidators, uint256 minValidators, uint256 maxValidators); }
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)
pragma solidity ^0.8.0;
import "./IAccessControl.sol";
/**
* @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
*/
interface IAccessControlEnumerable is IAccessControl {
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) external view returns (address);
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) external view returns (uint256);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)
pragma solidity ^0.8.0;
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _HEX_SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)
pragma solidity ^0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastvalue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastvalue;
// Update the index for the moved value
set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
return _values(set._inner);
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
assembly {
result := store
}
return result;
}
}// SPDX-FileCopyrightText: 2023 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.9; import {MemUtils} from "../common/lib/MemUtils.sol"; interface IDepositContract { function get_deposit_root() external view returns (bytes32 rootHash); function deposit( bytes calldata pubkey, // 48 bytes bytes calldata withdrawal_credentials, // 32 bytes bytes calldata signature, // 96 bytes bytes32 deposit_data_root ) external payable; } contract BeaconChainDepositor { uint256 internal constant PUBLIC_KEY_LENGTH = 48; uint256 internal constant SIGNATURE_LENGTH = 96; uint256 internal constant DEPOSIT_SIZE = 32 ether; /// @dev deposit amount 32eth in gweis converted to little endian uint64 /// DEPOSIT_SIZE_IN_GWEI_LE64 = toLittleEndian64(32 ether / 1 gwei) uint64 internal constant DEPOSIT_SIZE_IN_GWEI_LE64 = 0x0040597307000000; IDepositContract public immutable DEPOSIT_CONTRACT; constructor(address _depositContract) { if (_depositContract == address(0)) revert DepositContractZeroAddress(); DEPOSIT_CONTRACT = IDepositContract(_depositContract); } /// @dev Invokes a deposit call to the official Beacon Deposit contract /// @param _keysCount amount of keys to deposit /// @param _withdrawalCredentials Commitment to a public key for withdrawals /// @param _publicKeysBatch A BLS12-381 public keys batch /// @param _signaturesBatch A BLS12-381 signatures batch function _makeBeaconChainDeposits32ETH( uint256 _keysCount, bytes memory _withdrawalCredentials, bytes memory _publicKeysBatch, bytes memory _signaturesBatch ) internal { if (_publicKeysBatch.length != PUBLIC_KEY_LENGTH * _keysCount) { revert InvalidPublicKeysBatchLength(_publicKeysBatch.length, PUBLIC_KEY_LENGTH * _keysCount); } if (_signaturesBatch.length != SIGNATURE_LENGTH * _keysCount) { revert InvalidSignaturesBatchLength(_signaturesBatch.length, SIGNATURE_LENGTH * _keysCount); } bytes memory publicKey = MemUtils.unsafeAllocateBytes(PUBLIC_KEY_LENGTH); bytes memory signature = MemUtils.unsafeAllocateBytes(SIGNATURE_LENGTH); for (uint256 i; i < _keysCount;) { MemUtils.copyBytes(_publicKeysBatch, publicKey, i * PUBLIC_KEY_LENGTH, 0, PUBLIC_KEY_LENGTH); MemUtils.copyBytes(_signaturesBatch, signature, i * SIGNATURE_LENGTH, 0, SIGNATURE_LENGTH); DEPOSIT_CONTRACT.deposit{value: DEPOSIT_SIZE}( publicKey, _withdrawalCredentials, signature, _computeDepositDataRoot(_withdrawalCredentials, publicKey, signature) ); unchecked { ++i; } } } /// @dev computes the deposit_root_hash required by official Beacon Deposit contract /// @param _publicKey A BLS12-381 public key. /// @param _signature A BLS12-381 signature function _computeDepositDataRoot(bytes memory _withdrawalCredentials, bytes memory _publicKey, bytes memory _signature) private pure returns (bytes32) { // Compute deposit data root (`DepositData` hash tree root) according to deposit_contract.sol bytes memory sigPart1 = MemUtils.unsafeAllocateBytes(64); bytes memory sigPart2 = MemUtils.unsafeAllocateBytes(SIGNATURE_LENGTH - 64); MemUtils.copyBytes(_signature, sigPart1, 0, 0, 64); MemUtils.copyBytes(_signature, sigPart2, 64, 0, SIGNATURE_LENGTH - 64); bytes32 publicKeyRoot = sha256(abi.encodePacked(_publicKey, bytes16(0))); bytes32 signatureRoot = sha256(abi.encodePacked(sha256(abi.encodePacked(sigPart1)), sha256(abi.encodePacked(sigPart2, bytes32(0))))); return sha256( abi.encodePacked( sha256(abi.encodePacked(publicKeyRoot, _withdrawalCredentials)), sha256(abi.encodePacked(DEPOSIT_SIZE_IN_GWEI_LE64, bytes24(0), signatureRoot)) ) ); } error DepositContractZeroAddress(); error InvalidPublicKeysBatchLength(uint256 actual, uint256 expected); error InvalidSignaturesBatchLength(uint256 actual, uint256 expected); }
// SPDX-FileCopyrightText: 2024 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.9; /// @notice An interface to subscribe on the `stETH` token rebases (defined in the `Lido` core contract) interface IPostTokenRebaseReceiver { /// @notice Is called in the context of `Lido.handleOracleReport` to notify the subscribers about each token rebase function handlePostTokenRebase( uint256 _reportTimestamp, uint256 _timeElapsed, uint256 _preTotalShares, uint256 _preTotalEther, uint256 _postTotalShares, uint256 _postTotalEther, uint256 _sharesMintedAsFees ) external; }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.9; /// @title Lido's Staking Module interface interface IStakingModule { /// @dev Event to be emitted on StakingModule's nonce change event NonceChanged(uint256 nonce); /// @dev Event to be emitted when a signing key is added to the StakingModule event SigningKeyAdded(uint256 indexed nodeOperatorId, bytes pubkey); /// @dev Event to be emitted when a signing key is removed from the StakingModule event SigningKeyRemoved(uint256 indexed nodeOperatorId, bytes pubkey); /// @notice Handles tracking and penalization logic for a node operator who failed to exit their validator within the defined exit window. /// @dev This function is called by the StakingRouter to report the current exit-related status of a validator /// belonging to a specific node operator. It accepts a validator's public key, associated /// with the duration (in seconds) it was eligible to exit but has not exited. /// This data could be used to trigger penalties for the node operator if the validator has exceeded the allowed exit window. /// @param _nodeOperatorId The ID of the node operator whose validator's status is being delivered. /// @param _proofSlotTimestamp The timestamp (slot time) when the validator was last known to be in an active ongoing state. /// @param _publicKey The public key of the validator being reported. /// @param _eligibleToExitInSec The duration (in seconds) indicating how long the validator has been eligible to exit after request but has not exited. function reportValidatorExitDelay( uint256 _nodeOperatorId, uint256 _proofSlotTimestamp, bytes calldata _publicKey, uint256 _eligibleToExitInSec ) external; /// @notice Handles the triggerable exit event for a validator belonging to a specific node operator. /// @dev This function is called by the StakingRouter when a validator is triggered to exit using the triggerable /// exit request on the Execution Layer (EL). /// @param _nodeOperatorId The ID of the node operator. /// @param _publicKey The public key of the validator being reported. /// @param _withdrawalRequestPaidFee Fee amount paid to send a withdrawal request on the Execution Layer (EL). /// @param _exitType The type of exit being performed. /// This parameter may be interpreted differently across various staking modules, depending on their specific implementation. function onValidatorExitTriggered( uint256 _nodeOperatorId, bytes calldata _publicKey, uint256 _withdrawalRequestPaidFee, uint256 _exitType ) external; /// @notice Determines whether a validator's exit status should be updated and will have an effect on the Node Operator. /// @param _nodeOperatorId The ID of the node operator. /// @param _proofSlotTimestamp The timestamp (slot time) when the validator was last known to be in an active ongoing state. /// @param _publicKey The public key of the validator. /// @param _eligibleToExitInSec The number of seconds the validator was eligible to exit but did not. /// @return bool Returns true if the contract should receive the updated status of the validator. function isValidatorExitDelayPenaltyApplicable( uint256 _nodeOperatorId, uint256 _proofSlotTimestamp, bytes calldata _publicKey, uint256 _eligibleToExitInSec ) external view returns (bool); /// @notice Returns the number of seconds after which a validator is considered late for specified node operator. /// @param _nodeOperatorId The ID of the node operator. /// @return The exit deadline threshold in seconds. function exitDeadlineThreshold(uint256 _nodeOperatorId) external view returns (uint256); /// @notice Returns the type of the staking module function getType() external view returns (bytes32); /// @notice Returns all-validators summary in the staking module /// @return totalExitedValidators total number of validators in the EXITED state /// on the Consensus Layer. This value can't decrease in normal conditions /// @return totalDepositedValidators total number of validators deposited via the /// official Deposit Contract. This value is a cumulative counter: even when the validator /// goes into EXITED state this counter is not decreasing /// @return depositableValidatorsCount number of validators in the set available for deposit function getStakingModuleSummary() external view returns ( uint256 totalExitedValidators, uint256 totalDepositedValidators, uint256 depositableValidatorsCount ); /// @notice Returns all-validators summary belonging to the node operator with the given id /// @param _nodeOperatorId id of the operator to return report for /// @return targetLimitMode shows whether the current target limit applied to the node operator (0 = disabled, 1 = soft mode, 2 = boosted mode) /// @return targetValidatorsCount relative target active validators limit for operator /// @return stuckValidatorsCount number of validators with an expired request to exit time /// @return refundedValidatorsCount number of validators that can't be withdrawn, but deposit /// costs were compensated to the Lido by the node operator /// @return stuckPenaltyEndTimestamp time when the penalty for stuck validators stops applying /// to node operator rewards /// @return totalExitedValidators total number of validators in the EXITED state /// on the Consensus Layer. This value can't decrease in normal conditions /// @return totalDepositedValidators total number of validators deposited via the official /// Deposit Contract. This value is a cumulative counter: even when the validator goes into /// EXITED state this counter is not decreasing /// @return depositableValidatorsCount number of validators in the set available for deposit function getNodeOperatorSummary(uint256 _nodeOperatorId) external view returns ( uint256 targetLimitMode, uint256 targetValidatorsCount, uint256 stuckValidatorsCount, uint256 refundedValidatorsCount, uint256 stuckPenaltyEndTimestamp, uint256 totalExitedValidators, uint256 totalDepositedValidators, uint256 depositableValidatorsCount ); /// @notice Returns a counter that MUST change its value whenever the deposit data set changes. /// Below is the typical list of actions that requires an update of the nonce: /// 1. a node operator's deposit data is added /// 2. a node operator's deposit data is removed /// 3. a node operator's ready-to-deposit data size is changed /// 4. a node operator was activated/deactivated /// 5. a node operator's deposit data is used for the deposit /// Note: Depending on the StakingModule implementation above list might be extended /// @dev In some scenarios, it's allowed to update nonce without actual change of the deposit /// data subset, but it MUST NOT lead to the DOS of the staking module via continuous /// update of the nonce by the malicious actor function getNonce() external view returns (uint256); /// @notice Returns total number of node operators function getNodeOperatorsCount() external view returns (uint256); /// @notice Returns number of active node operators function getActiveNodeOperatorsCount() external view returns (uint256); /// @notice Returns if the node operator with given id is active /// @param _nodeOperatorId Id of the node operator function getNodeOperatorIsActive(uint256 _nodeOperatorId) external view returns (bool); /// @notice Returns up to `_limit` node operator ids starting from the `_offset`. The order of /// the returned ids is not defined and might change between calls. /// @dev This view must not revert in case of invalid data passed. When `_offset` exceeds the /// total node operators count or when `_limit` is equal to 0 MUST be returned empty array. function getNodeOperatorIds(uint256 _offset, uint256 _limit) external view returns (uint256[] memory nodeOperatorIds); /// @notice Called by StakingRouter to signal that stETH rewards were minted for this module. /// @param _totalShares Amount of stETH shares that were minted to reward all node operators. /// @dev IMPORTANT: this method SHOULD revert with empty error data ONLY because of "out of gas". /// Details about error data: https://docs.soliditylang.org/en/v0.8.9/control-structures.html#error-handling-assert-require-revert-and-exceptions function onRewardsMinted(uint256 _totalShares) external; /// @notice Called by StakingRouter to decrease the number of vetted keys for node operator with given id /// @param _nodeOperatorIds bytes packed array of the node operators id /// @param _vettedSigningKeysCounts bytes packed array of the new number of vetted keys for the node operators function decreaseVettedSigningKeysCount( bytes calldata _nodeOperatorIds, bytes calldata _vettedSigningKeysCounts ) external; /// @notice Updates the number of the validators in the EXITED state for node operator with given id /// @param _nodeOperatorIds bytes packed array of the node operators id /// @param _exitedValidatorsCounts bytes packed array of the new number of EXITED validators for the node operators function updateExitedValidatorsCount( bytes calldata _nodeOperatorIds, bytes calldata _exitedValidatorsCounts ) external; /// @notice Updates the number of the refunded validators for node operator with the given id /// @param _nodeOperatorId Id of the node operator /// @param _refundedValidatorsCount New number of refunded validators of the node operator function updateRefundedValidatorsCount(uint256 _nodeOperatorId, uint256 _refundedValidatorsCount) external; /// @notice Updates the limit of the validators that can be used for deposit /// @param _nodeOperatorId Id of the node operator /// @param _targetLimitMode target limit mode /// @param _targetLimit Target limit of the node operator function updateTargetValidatorsLimits( uint256 _nodeOperatorId, uint256 _targetLimitMode, uint256 _targetLimit ) external; /// @notice Unsafely updates the number of validators in the EXITED/STUCK states for node operator with given id /// 'unsafely' means that this method can both increase and decrease exited and stuck counters /// @param _nodeOperatorId Id of the node operator /// @param _exitedValidatorsCount New number of EXITED validators for the node operator function unsafeUpdateValidatorsCount( uint256 _nodeOperatorId, uint256 _exitedValidatorsCount ) external; /// @notice Obtains deposit data to be used by StakingRouter to deposit to the Ethereum Deposit /// contract /// @dev The method MUST revert when the staking module has not enough deposit data items /// @param _depositsCount Number of deposits to be done /// @param _depositCalldata Staking module defined data encoded as bytes. /// IMPORTANT: _depositCalldata MUST NOT modify the deposit data set of the staking module /// @return publicKeys Batch of the concatenated public validators keys /// @return signatures Batch of the concatenated deposit signatures for returned public keys function obtainDepositData(uint256 _depositsCount, bytes calldata _depositCalldata) external returns (bytes memory publicKeys, bytes memory signatures); /// @notice Called by StakingRouter after it finishes updating exited and stuck validators /// counts for this module's node operators. /// /// Guaranteed to be called after an oracle report is applied, regardless of whether any node /// operator in this module has actually received any updated counts as a result of the report /// but given that the total number of exited validators returned from getStakingModuleSummary /// is the same as StakingRouter expects based on the total count received from the oracle. /// /// @dev IMPORTANT: this method SHOULD revert with empty error data ONLY because of "out of gas". /// Details about error data: https://docs.soliditylang.org/en/v0.8.9/control-structures.html#error-handling-assert-require-revert-and-exceptions function onExitedAndStuckValidatorsCountsUpdated() external; /// @notice Called by StakingRouter when withdrawal credentials are changed. /// @dev This method MUST discard all StakingModule's unused deposit data cause they become /// invalid after the withdrawal credentials are changed /// /// @dev IMPORTANT: this method SHOULD revert with empty error data ONLY because of "out of gas". /// Details about error data: https://docs.soliditylang.org/en/v0.8.9/control-structures.html#error-handling-assert-require-revert-and-exceptions function onWithdrawalCredentialsChanged() external; }
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.9;
/**
* @notice Aragon Unstructured Storage library
*/
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) }
}
}// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 /* See contracts/COMPILERS.md */ pragma solidity 0.8.9; import {AccessControlEnumerable} from "./utils/access/AccessControlEnumerable.sol"; import {IStakingModule} from "./interfaces/IStakingModule.sol"; import {Math256} from "../common/lib/Math256.sol"; import {UnstructuredStorage} from "./lib/UnstructuredStorage.sol"; import {MinFirstAllocationStrategy} from "../common/lib/MinFirstAllocationStrategy.sol"; import {BeaconChainDepositor} from "./BeaconChainDepositor.sol"; import {Versioned} from "./utils/Versioned.sol"; contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Versioned { using UnstructuredStorage for bytes32; /// @dev Events event StakingModuleAdded(uint256 indexed stakingModuleId, address stakingModule, string name, address createdBy); event StakingModuleShareLimitSet(uint256 indexed stakingModuleId, uint256 stakeShareLimit, uint256 priorityExitShareThreshold, address setBy); event StakingModuleFeesSet(uint256 indexed stakingModuleId, uint256 stakingModuleFee, uint256 treasuryFee, address setBy); event StakingModuleStatusSet(uint256 indexed stakingModuleId, StakingModuleStatus status, address setBy); event StakingModuleExitedValidatorsIncompleteReporting(uint256 indexed stakingModuleId, uint256 unreportedExitedValidatorsCount); event StakingModuleMaxDepositsPerBlockSet( uint256 indexed stakingModuleId, uint256 maxDepositsPerBlock, address setBy ); event StakingModuleMinDepositBlockDistanceSet( uint256 indexed stakingModuleId, uint256 minDepositBlockDistance, address setBy ); event WithdrawalCredentialsSet(bytes32 withdrawalCredentials, address setBy); event WithdrawalsCredentialsChangeFailed(uint256 indexed stakingModuleId, bytes lowLevelRevertData); event ExitedAndStuckValidatorsCountsUpdateFailed(uint256 indexed stakingModuleId, bytes lowLevelRevertData); event RewardsMintedReportFailed(uint256 indexed stakingModuleId, bytes lowLevelRevertData); /// Emitted when the StakingRouter received ETH event StakingRouterETHDeposited(uint256 indexed stakingModuleId, uint256 amount); event StakingModuleExitNotificationFailed( uint256 indexed stakingModuleId, uint256 indexed nodeOperatorId, bytes _publicKey ); /// @dev Errors error ZeroAddressLido(); error ZeroAddressAdmin(); error ZeroAddressStakingModule(); error InvalidStakeShareLimit(); error InvalidFeeSum(); error StakingModuleNotActive(); error EmptyWithdrawalsCredentials(); error DirectETHTransfer(); error InvalidReportData(uint256 code); error ExitedValidatorsCountCannotDecrease(); error ReportedExitedValidatorsExceedDeposited( uint256 reportedExitedValidatorsCount, uint256 depositedValidatorsCount ); error StakingModulesLimitExceeded(); error StakingModuleUnregistered(); error AppAuthLidoFailed(); error StakingModuleStatusTheSame(); error StakingModuleWrongName(); error UnexpectedCurrentValidatorsCount( uint256 currentModuleExitedValidatorsCount, uint256 currentNodeOpExitedValidatorsCount ); error UnexpectedFinalExitedValidatorsCount ( uint256 newModuleTotalExitedValidatorsCount, uint256 newModuleTotalExitedValidatorsCountInStakingRouter ); error InvalidDepositsValue(uint256 etherValue, uint256 depositsCount); error StakingModuleAddressExists(); error ArraysLengthMismatch(uint256 firstArrayLength, uint256 secondArrayLength); error UnrecoverableModuleError(); error InvalidPriorityExitShareThreshold(); error InvalidMinDepositBlockDistance(); error InvalidMaxDepositPerBlockValue(); enum StakingModuleStatus { Active, // deposits and rewards allowed DepositsPaused, // deposits NOT allowed, rewards allowed Stopped // deposits and rewards NOT allowed } struct StakingModule { /// @notice Unique id of the staking module. uint24 id; /// @notice Address of the staking module. address stakingModuleAddress; /// @notice Part of the fee taken from staking rewards that goes to the staking module. uint16 stakingModuleFee; /// @notice Part of the fee taken from staking rewards that goes to the treasury. uint16 treasuryFee; /// @notice Maximum stake share that can be allocated to a module, in BP. /// @dev Formerly known as `targetShare`. uint16 stakeShareLimit; /// @notice Staking module status if staking module can not accept the deposits or can /// participate in further reward distribution. uint8 status; /// @notice Name of the staking module. string name; /// @notice block.timestamp of the last deposit of the staking module. /// @dev NB: lastDepositAt gets updated even if the deposit value was 0 and no actual deposit happened. uint64 lastDepositAt; /// @notice block.number of the last deposit of the staking module. /// @dev NB: lastDepositBlock gets updated even if the deposit value was 0 and no actual deposit happened. uint256 lastDepositBlock; /// @notice Number of exited validators. uint256 exitedValidatorsCount; /// @notice Module's share threshold, upon crossing which, exits of validators from the module will be prioritized, in BP. uint16 priorityExitShareThreshold; /// @notice The maximum number of validators that can be deposited in a single block. /// @dev Must be harmonized with `OracleReportSanityChecker.appearedValidatorsPerDayLimit`. /// See docs for the `OracleReportSanityChecker.setAppearedValidatorsPerDayLimit` function. uint64 maxDepositsPerBlock; /// @notice The minimum distance between deposits in blocks. /// @dev Must be harmonized with `OracleReportSanityChecker.appearedValidatorsPerDayLimit`. /// See docs for the `OracleReportSanityChecker.setAppearedValidatorsPerDayLimit` function). uint64 minDepositBlockDistance; } struct StakingModuleCache { address stakingModuleAddress; uint24 stakingModuleId; uint16 stakingModuleFee; uint16 treasuryFee; uint16 stakeShareLimit; StakingModuleStatus status; uint256 activeValidatorsCount; uint256 availableValidatorsCount; } struct ValidatorExitData { uint256 stakingModuleId; uint256 nodeOperatorId; bytes pubkey; } bytes32 public constant MANAGE_WITHDRAWAL_CREDENTIALS_ROLE = keccak256("MANAGE_WITHDRAWAL_CREDENTIALS_ROLE"); bytes32 public constant STAKING_MODULE_MANAGE_ROLE = keccak256("STAKING_MODULE_MANAGE_ROLE"); bytes32 public constant STAKING_MODULE_UNVETTING_ROLE = keccak256("STAKING_MODULE_UNVETTING_ROLE"); bytes32 public constant REPORT_EXITED_VALIDATORS_ROLE = keccak256("REPORT_EXITED_VALIDATORS_ROLE"); bytes32 public constant REPORT_VALIDATOR_EXITING_STATUS_ROLE = keccak256("REPORT_VALIDATOR_EXITING_STATUS_ROLE"); bytes32 public constant REPORT_VALIDATOR_EXIT_TRIGGERED_ROLE = keccak256("REPORT_VALIDATOR_EXIT_TRIGGERED_ROLE"); bytes32 public constant UNSAFE_SET_EXITED_VALIDATORS_ROLE = keccak256("UNSAFE_SET_EXITED_VALIDATORS_ROLE"); bytes32 public constant REPORT_REWARDS_MINTED_ROLE = keccak256("REPORT_REWARDS_MINTED_ROLE"); bytes32 internal constant LIDO_POSITION = keccak256("lido.StakingRouter.lido"); /// @dev Credentials to withdraw ETH on Consensus Layer side. bytes32 internal constant WITHDRAWAL_CREDENTIALS_POSITION = keccak256("lido.StakingRouter.withdrawalCredentials"); /// @dev Total count of staking modules. bytes32 internal constant STAKING_MODULES_COUNT_POSITION = keccak256("lido.StakingRouter.stakingModulesCount"); /// @dev Id of the last added staking module. This counter grow on staking modules adding. bytes32 internal constant LAST_STAKING_MODULE_ID_POSITION = keccak256("lido.StakingRouter.lastStakingModuleId"); /// @dev Mapping is used instead of array to allow to extend the StakingModule. bytes32 internal constant STAKING_MODULES_MAPPING_POSITION = keccak256("lido.StakingRouter.stakingModules"); /// @dev Position of the staking modules in the `_stakingModules` map, plus 1 because /// index 0 means a value is not in the set. bytes32 internal constant STAKING_MODULE_INDICES_MAPPING_POSITION = keccak256("lido.StakingRouter.stakingModuleIndicesOneBased"); uint256 public constant FEE_PRECISION_POINTS = 10 ** 20; // 100 * 10 ** 18 uint256 public constant TOTAL_BASIS_POINTS = 10000; uint256 public constant MAX_STAKING_MODULES_COUNT = 32; /// @dev Restrict the name size with 31 bytes to storage in a single slot. uint256 public constant MAX_STAKING_MODULE_NAME_LENGTH = 31; constructor(address _depositContract) BeaconChainDepositor(_depositContract) {} /// @notice Initializes the contract. /// @param _admin Lido DAO Aragon agent contract address. /// @param _lido Lido address. /// @param _withdrawalCredentials Credentials to withdraw ETH on Consensus Layer side. /// @dev Proxy initialization method. function initialize(address _admin, address _lido, bytes32 _withdrawalCredentials) external { if (_admin == address(0)) revert ZeroAddressAdmin(); if (_lido == address(0)) revert ZeroAddressLido(); _initializeContractVersionTo(3); _setupRole(DEFAULT_ADMIN_ROLE, _admin); LIDO_POSITION.setStorageAddress(_lido); WITHDRAWAL_CREDENTIALS_POSITION.setStorageBytes32(_withdrawalCredentials); emit WithdrawalCredentialsSet(_withdrawalCredentials, msg.sender); } /// @dev Prohibit direct transfer to contract. receive() external payable { revert DirectETHTransfer(); } /// @notice A function to finalize upgrade to v2 (from v1). Removed and no longer used. /// @dev https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-10.md /// See historical usage in commit: https://github.com/lidofinance/core/blob/c19480aa3366b26aa6eac17f85a6efae8b9f4f72/contracts/0.8.9/StakingRouter.sol#L190 // function finalizeUpgrade_v2( // uint256[] memory _priorityExitShareThresholds, // uint256[] memory _maxDepositsPerBlock, // uint256[] memory _minDepositBlockDistances // ) external /// @notice Finalizes upgrade to v3 (from v2). Can be called only once. function finalizeUpgrade_v3() external { _checkContractVersion(2); _updateContractVersion(3); } /// @notice Returns Lido contract address. /// @return Lido contract address. function getLido() public view returns (address) { return LIDO_POSITION.getStorageAddress(); } /// @notice Registers a new staking module. /// @param _name Name of staking module. /// @param _stakingModuleAddress Address of staking module. /// @param _stakeShareLimit Maximum share that can be allocated to a module. /// @param _priorityExitShareThreshold Module's priority exit share threshold. /// @param _stakingModuleFee Fee of the staking module taken from the staking rewards. /// @param _treasuryFee Treasury fee. /// @param _maxDepositsPerBlock The maximum number of validators that can be deposited in a single block. /// @param _minDepositBlockDistance The minimum distance between deposits in blocks. /// @dev The function is restricted to the `STAKING_MODULE_MANAGE_ROLE` role. function addStakingModule( string calldata _name, address _stakingModuleAddress, uint256 _stakeShareLimit, uint256 _priorityExitShareThreshold, uint256 _stakingModuleFee, uint256 _treasuryFee, uint256 _maxDepositsPerBlock, uint256 _minDepositBlockDistance ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { if (_stakingModuleAddress == address(0)) revert ZeroAddressStakingModule(); if (bytes(_name).length == 0 || bytes(_name).length > MAX_STAKING_MODULE_NAME_LENGTH) revert StakingModuleWrongName(); uint256 newStakingModuleIndex = getStakingModulesCount(); if (newStakingModuleIndex >= MAX_STAKING_MODULES_COUNT) revert StakingModulesLimitExceeded(); for (uint256 i; i < newStakingModuleIndex; ) { if (_stakingModuleAddress == _getStakingModuleByIndex(i).stakingModuleAddress) revert StakingModuleAddressExists(); unchecked { ++i; } } StakingModule storage newStakingModule = _getStakingModuleByIndex(newStakingModuleIndex); uint24 newStakingModuleId = uint24(LAST_STAKING_MODULE_ID_POSITION.getStorageUint256()) + 1; newStakingModule.id = newStakingModuleId; newStakingModule.name = _name; newStakingModule.stakingModuleAddress = _stakingModuleAddress; /// @dev Since `enum` is `uint8` by nature, so the `status` is stored as `uint8` to avoid /// possible problems when upgrading. But for human readability, we use `enum` as /// function parameter type. More about conversion in the docs: /// https://docs.soliditylang.org/en/v0.8.17/types.html#enums newStakingModule.status = uint8(StakingModuleStatus.Active); /// @dev Simulate zero value deposit to prevent real deposits into the new StakingModule via /// DepositSecurityModule just after the addition. _updateModuleLastDepositState(newStakingModule, newStakingModuleId, 0); _setStakingModuleIndexById(newStakingModuleId, newStakingModuleIndex); LAST_STAKING_MODULE_ID_POSITION.setStorageUint256(newStakingModuleId); STAKING_MODULES_COUNT_POSITION.setStorageUint256(newStakingModuleIndex + 1); emit StakingModuleAdded(newStakingModuleId, _stakingModuleAddress, _name, msg.sender); _updateStakingModule( newStakingModule, newStakingModuleId, _stakeShareLimit, _priorityExitShareThreshold, _stakingModuleFee, _treasuryFee, _maxDepositsPerBlock, _minDepositBlockDistance ); } /// @notice Updates staking module params. /// @param _stakingModuleId Staking module id. /// @param _stakeShareLimit Target total stake share. /// @param _priorityExitShareThreshold Module's priority exit share threshold. /// @param _stakingModuleFee Fee of the staking module taken from the staking rewards. /// @param _treasuryFee Treasury fee. /// @param _maxDepositsPerBlock The maximum number of validators that can be deposited in a single block. /// @param _minDepositBlockDistance The minimum distance between deposits in blocks. /// @dev The function is restricted to the `STAKING_MODULE_MANAGE_ROLE` role. function updateStakingModule( uint256 _stakingModuleId, uint256 _stakeShareLimit, uint256 _priorityExitShareThreshold, uint256 _stakingModuleFee, uint256 _treasuryFee, uint256 _maxDepositsPerBlock, uint256 _minDepositBlockDistance ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { StakingModule storage stakingModule = _getStakingModuleByIndex(_getStakingModuleIndexById(_stakingModuleId)); _updateStakingModule( stakingModule, _stakingModuleId, _stakeShareLimit, _priorityExitShareThreshold, _stakingModuleFee, _treasuryFee, _maxDepositsPerBlock, _minDepositBlockDistance ); } function _updateStakingModule( StakingModule storage stakingModule, uint256 _stakingModuleId, uint256 _stakeShareLimit, uint256 _priorityExitShareThreshold, uint256 _stakingModuleFee, uint256 _treasuryFee, uint256 _maxDepositsPerBlock, uint256 _minDepositBlockDistance ) internal { if (_stakeShareLimit > TOTAL_BASIS_POINTS) revert InvalidStakeShareLimit(); if (_priorityExitShareThreshold > TOTAL_BASIS_POINTS) revert InvalidPriorityExitShareThreshold(); if (_stakeShareLimit > _priorityExitShareThreshold) revert InvalidPriorityExitShareThreshold(); if (_stakingModuleFee + _treasuryFee > TOTAL_BASIS_POINTS) revert InvalidFeeSum(); if (_minDepositBlockDistance == 0 || _minDepositBlockDistance > type(uint64).max) revert InvalidMinDepositBlockDistance(); if (_maxDepositsPerBlock > type(uint64).max) revert InvalidMaxDepositPerBlockValue(); stakingModule.stakeShareLimit = uint16(_stakeShareLimit); stakingModule.priorityExitShareThreshold = uint16(_priorityExitShareThreshold); stakingModule.treasuryFee = uint16(_treasuryFee); stakingModule.stakingModuleFee = uint16(_stakingModuleFee); stakingModule.maxDepositsPerBlock = uint64(_maxDepositsPerBlock); stakingModule.minDepositBlockDistance = uint64(_minDepositBlockDistance); emit StakingModuleShareLimitSet(_stakingModuleId, _stakeShareLimit, _priorityExitShareThreshold, msg.sender); emit StakingModuleFeesSet(_stakingModuleId, _stakingModuleFee, _treasuryFee, msg.sender); emit StakingModuleMaxDepositsPerBlockSet(_stakingModuleId, _maxDepositsPerBlock, msg.sender); emit StakingModuleMinDepositBlockDistanceSet(_stakingModuleId, _minDepositBlockDistance, msg.sender); } /// @notice Updates the limit of the validators that can be used for deposit. /// @param _stakingModuleId Id of the staking module. /// @param _nodeOperatorId Id of the node operator. /// @param _targetLimitMode Target limit mode. /// @param _targetLimit Target limit of the node operator. /// @dev The function is restricted to the `STAKING_MODULE_MANAGE_ROLE` role. function updateTargetValidatorsLimits( uint256 _stakingModuleId, uint256 _nodeOperatorId, uint256 _targetLimitMode, uint256 _targetLimit ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { _getIStakingModuleById(_stakingModuleId).updateTargetValidatorsLimits( _nodeOperatorId, _targetLimitMode, _targetLimit ); } /// @notice Updates the number of the refunded validators in the staking module with the given /// node operator id. /// @param _stakingModuleId Id of the staking module. /// @param _nodeOperatorId Id of the node operator. /// @param _refundedValidatorsCount New number of refunded validators of the node operator. /// @dev The function is restricted to the `STAKING_MODULE_MANAGE_ROLE` role. function updateRefundedValidatorsCount( uint256 _stakingModuleId, uint256 _nodeOperatorId, uint256 _refundedValidatorsCount ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { _getIStakingModuleById(_stakingModuleId).updateRefundedValidatorsCount( _nodeOperatorId, _refundedValidatorsCount ); } /// @notice Reports the minted rewards to the staking modules with the specified ids. /// @param _stakingModuleIds Ids of the staking modules. /// @param _totalShares Total shares minted for the staking modules. /// @dev The function is restricted to the `REPORT_REWARDS_MINTED_ROLE` role. function reportRewardsMinted(uint256[] calldata _stakingModuleIds, uint256[] calldata _totalShares) external onlyRole(REPORT_REWARDS_MINTED_ROLE) { _validateEqualArrayLengths(_stakingModuleIds.length, _totalShares.length); for (uint256 i = 0; i < _stakingModuleIds.length; ) { if (_totalShares[i] > 0) { try _getIStakingModuleById(_stakingModuleIds[i]).onRewardsMinted(_totalShares[i]) {} catch (bytes memory lowLevelRevertData) { /// @dev This check is required to prevent incorrect gas estimation of the method. /// Without it, Ethereum nodes that use binary search for gas estimation may /// return an invalid value when the onRewardsMinted() reverts because of the /// "out of gas" error. Here we assume that the onRewardsMinted() method doesn't /// have reverts with empty error data except "out of gas". if (lowLevelRevertData.length == 0) revert UnrecoverableModuleError(); emit RewardsMintedReportFailed( _stakingModuleIds[i], lowLevelRevertData ); } } unchecked { ++i; } } } /// @notice Updates total numbers of exited validators for staking modules with the specified module ids. /// @param _stakingModuleIds Ids of the staking modules to be updated. /// @param _exitedValidatorsCounts New counts of exited validators for the specified staking modules. /// @return The total increase in the aggregate number of exited validators across all updated modules. /// /// @dev The total numbers are stored in the staking router and can differ from the totals obtained by calling /// `IStakingModule.getStakingModuleSummary()`. The overall process of updating validator counts is the following: /// /// 1. In the first data submission phase, the oracle calls `updateExitedValidatorsCountByStakingModule` on the /// staking router, passing the totals by module. The staking router stores these totals and uses them to /// distribute new stake and staking fees between the modules. There can only be single call of this function /// per oracle reporting frame. /// /// 2. In the second part of the second data submission phase, the oracle calls /// `StakingRouter.reportStakingModuleExitedValidatorsCountByNodeOperator` on the staking router which passes /// the counts by node operator to the staking module by calling `IStakingModule.updateExitedValidatorsCount`. /// This can be done multiple times for the same module, passing data for different subsets of node /// operators. /// /// 3. At the end of the second data submission phase, it's expected for the aggregate exited validators count /// across all module's node operators (stored in the module) to match the total count for this module /// (stored in the staking router). However, it might happen that the second phase of data submission doesn't /// finish until the new oracle reporting frame is started, in which case staking router will emit a warning /// event `StakingModuleExitedValidatorsIncompleteReporting` when the first data submission phase is performed /// for a new reporting frame. This condition will result in the staking module having an incomplete data about /// the exited validator counts during the whole reporting frame. Handling this condition is /// the responsibility of each staking module. /// /// 4. When the second reporting phase is finished, i.e. when the oracle submitted the complete data on the exited /// validator counts per node operator for the current reporting frame, the oracle calls /// `StakingRouter.onValidatorsCountsByNodeOperatorReportingFinished` which, in turn, calls /// `IStakingModule.onExitedAndStuckValidatorsCountsUpdated` on all modules. /// /// @dev The function is restricted to the `REPORT_EXITED_VALIDATORS_ROLE` role. function updateExitedValidatorsCountByStakingModule( uint256[] calldata _stakingModuleIds, uint256[] calldata _exitedValidatorsCounts ) external onlyRole(REPORT_EXITED_VALIDATORS_ROLE) returns (uint256) { _validateEqualArrayLengths(_stakingModuleIds.length, _exitedValidatorsCounts.length); uint256 newlyExitedValidatorsCount; for (uint256 i = 0; i < _stakingModuleIds.length; ) { uint256 stakingModuleId = _stakingModuleIds[i]; StakingModule storage stakingModule = _getStakingModuleByIndex(_getStakingModuleIndexById(stakingModuleId)); uint256 prevReportedExitedValidatorsCount = stakingModule.exitedValidatorsCount; if (_exitedValidatorsCounts[i] < prevReportedExitedValidatorsCount) { revert ExitedValidatorsCountCannotDecrease(); } ( uint256 totalExitedValidators, uint256 totalDepositedValidators, /* uint256 depositableValidatorsCount */ ) = _getStakingModuleSummary(IStakingModule(stakingModule.stakingModuleAddress)); if (_exitedValidatorsCounts[i] > totalDepositedValidators) { revert ReportedExitedValidatorsExceedDeposited( _exitedValidatorsCounts[i], totalDepositedValidators ); } newlyExitedValidatorsCount += _exitedValidatorsCounts[i] - prevReportedExitedValidatorsCount; if (totalExitedValidators < prevReportedExitedValidatorsCount) { // not all of the exited validators were async reported to the module emit StakingModuleExitedValidatorsIncompleteReporting( stakingModuleId, prevReportedExitedValidatorsCount - totalExitedValidators ); } stakingModule.exitedValidatorsCount = _exitedValidatorsCounts[i]; unchecked { ++i; } } return newlyExitedValidatorsCount; } /// @notice Updates exited validators counts per node operator for the staking module with /// the specified id. See the docs for `updateExitedValidatorsCountByStakingModule` for the /// description of the overall update process. /// /// @param _stakingModuleId The id of the staking modules to be updated. /// @param _nodeOperatorIds Ids of the node operators to be updated. /// @param _exitedValidatorsCounts New counts of exited validators for the specified node operators. /// /// @dev The function is restricted to the `REPORT_EXITED_VALIDATORS_ROLE` role. function reportStakingModuleExitedValidatorsCountByNodeOperator( uint256 _stakingModuleId, bytes calldata _nodeOperatorIds, bytes calldata _exitedValidatorsCounts ) external onlyRole(REPORT_EXITED_VALIDATORS_ROLE) { _checkValidatorsByNodeOperatorReportData(_nodeOperatorIds, _exitedValidatorsCounts); _getIStakingModuleById(_stakingModuleId).updateExitedValidatorsCount(_nodeOperatorIds, _exitedValidatorsCounts); } struct ValidatorsCountsCorrection { /// @notice The expected current number of exited validators of the module that is /// being corrected. uint256 currentModuleExitedValidatorsCount; /// @notice The expected current number of exited validators of the node operator /// that is being corrected. uint256 currentNodeOperatorExitedValidatorsCount; /// @notice The corrected number of exited validators of the module. uint256 newModuleExitedValidatorsCount; /// @notice The corrected number of exited validators of the node operator. uint256 newNodeOperatorExitedValidatorsCount; } /// @notice Sets exited validators count for the given module and given node operator in that module /// without performing critical safety checks, e.g. that exited validators count cannot decrease. /// /// Should only be used by the DAO in extreme cases and with sufficient precautions to correct invalid /// data reported by the oracle committee due to a bug in the oracle daemon. /// /// @param _stakingModuleId Id of the staking module. /// @param _nodeOperatorId Id of the node operator. /// @param _triggerUpdateFinish Whether to call `onExitedAndStuckValidatorsCountsUpdated` on the module /// after applying the corrections. /// @param _correction See the docs for the `ValidatorsCountsCorrection` struct. /// /// @dev Reverts if the current numbers of exited validators of the module and node operator /// don't match the supplied expected current values. /// /// @dev The function is restricted to the `UNSAFE_SET_EXITED_VALIDATORS_ROLE` role. function unsafeSetExitedValidatorsCount( uint256 _stakingModuleId, uint256 _nodeOperatorId, bool _triggerUpdateFinish, ValidatorsCountsCorrection memory _correction ) external onlyRole(UNSAFE_SET_EXITED_VALIDATORS_ROLE) { StakingModule storage stakingModuleState = _getStakingModuleByIndex(_getStakingModuleIndexById(_stakingModuleId)); IStakingModule stakingModule = IStakingModule(stakingModuleState.stakingModuleAddress); ( /* uint256 targetLimitMode */, /* uint256 targetValidatorsCount */, /* uint256 stuckValidatorsCount, */, /* uint256 refundedValidatorsCount */, /* uint256 stuckPenaltyEndTimestamp */, uint256 totalExitedValidators, /* uint256 totalDepositedValidators */, /* uint256 depositableValidatorsCount */ ) = stakingModule.getNodeOperatorSummary(_nodeOperatorId); if (_correction.currentModuleExitedValidatorsCount != stakingModuleState.exitedValidatorsCount || _correction.currentNodeOperatorExitedValidatorsCount != totalExitedValidators ) { revert UnexpectedCurrentValidatorsCount( stakingModuleState.exitedValidatorsCount, totalExitedValidators ); } stakingModuleState.exitedValidatorsCount = _correction.newModuleExitedValidatorsCount; stakingModule.unsafeUpdateValidatorsCount( _nodeOperatorId, _correction.newNodeOperatorExitedValidatorsCount ); ( uint256 moduleTotalExitedValidators, uint256 moduleTotalDepositedValidators, ) = _getStakingModuleSummary(stakingModule); if (_correction.newModuleExitedValidatorsCount > moduleTotalDepositedValidators) { revert ReportedExitedValidatorsExceedDeposited( _correction.newModuleExitedValidatorsCount, moduleTotalDepositedValidators ); } if (_triggerUpdateFinish) { if (moduleTotalExitedValidators != _correction.newModuleExitedValidatorsCount) { revert UnexpectedFinalExitedValidatorsCount( moduleTotalExitedValidators, _correction.newModuleExitedValidatorsCount ); } stakingModule.onExitedAndStuckValidatorsCountsUpdated(); } } /// @notice Finalizes the reporting of the exited validators counts for the current /// reporting frame. /// /// @dev Called by the oracle when the second phase of data reporting finishes, i.e. when the /// oracle submitted the complete data on the exited validator counts per node operator /// for the current reporting frame. See the docs for `updateExitedValidatorsCountByStakingModule` /// for the description of the overall update process. /// /// @dev The function is restricted to the `REPORT_EXITED_VALIDATORS_ROLE` role. function onValidatorsCountsByNodeOperatorReportingFinished() external onlyRole(REPORT_EXITED_VALIDATORS_ROLE) { uint256 stakingModulesCount = getStakingModulesCount(); StakingModule storage stakingModule; IStakingModule moduleContract; for (uint256 i; i < stakingModulesCount; ) { stakingModule = _getStakingModuleByIndex(i); moduleContract = IStakingModule(stakingModule.stakingModuleAddress); (uint256 exitedValidatorsCount, , ) = _getStakingModuleSummary(moduleContract); if (exitedValidatorsCount == stakingModule.exitedValidatorsCount) { // oracle finished updating exited validators for all node ops try moduleContract.onExitedAndStuckValidatorsCountsUpdated() {} catch (bytes memory lowLevelRevertData) { /// @dev This check is required to prevent incorrect gas estimation of the method. /// Without it, Ethereum nodes that use binary search for gas estimation may /// return an invalid value when the onExitedAndStuckValidatorsCountsUpdated() /// reverts because of the "out of gas" error. Here we assume that the /// onExitedAndStuckValidatorsCountsUpdated() method doesn't have reverts with /// empty error data except "out of gas". if (lowLevelRevertData.length == 0) revert UnrecoverableModuleError(); emit ExitedAndStuckValidatorsCountsUpdateFailed( stakingModule.id, lowLevelRevertData ); } } unchecked { ++i; } } } /// @notice Decreases vetted signing keys counts per node operator for the staking module with /// the specified id. /// @param _stakingModuleId The id of the staking module to be updated. /// @param _nodeOperatorIds Ids of the node operators to be updated. /// @param _vettedSigningKeysCounts New counts of vetted signing keys for the specified node operators. /// @dev The function is restricted to the `STAKING_MODULE_UNVETTING_ROLE` role. function decreaseStakingModuleVettedKeysCountByNodeOperator( uint256 _stakingModuleId, bytes calldata _nodeOperatorIds, bytes calldata _vettedSigningKeysCounts ) external onlyRole(STAKING_MODULE_UNVETTING_ROLE) { _checkValidatorsByNodeOperatorReportData(_nodeOperatorIds, _vettedSigningKeysCounts); _getIStakingModuleById(_stakingModuleId).decreaseVettedSigningKeysCount(_nodeOperatorIds, _vettedSigningKeysCounts); } /// @notice Returns all registered staking modules. /// @return res Array of staking modules. function getStakingModules() external view returns (StakingModule[] memory res) { uint256 stakingModulesCount = getStakingModulesCount(); res = new StakingModule[](stakingModulesCount); for (uint256 i; i < stakingModulesCount; ) { res[i] = _getStakingModuleByIndex(i); unchecked { ++i; } } } /// @notice Returns the ids of all registered staking modules. /// @return stakingModuleIds Array of staking module ids. function getStakingModuleIds() public view returns (uint256[] memory stakingModuleIds) { uint256 stakingModulesCount = getStakingModulesCount(); stakingModuleIds = new uint256[](stakingModulesCount); for (uint256 i; i < stakingModulesCount; ) { stakingModuleIds[i] = _getStakingModuleByIndex(i).id; unchecked { ++i; } } } /// @notice Returns the staking module by its id. /// @param _stakingModuleId Id of the staking module. /// @return Staking module data. function getStakingModule(uint256 _stakingModuleId) public view returns (StakingModule memory) { return _getStakingModuleByIndex(_getStakingModuleIndexById(_stakingModuleId)); } /// @notice Returns total number of staking modules. /// @return Total number of staking modules. function getStakingModulesCount() public view returns (uint256) { return STAKING_MODULES_COUNT_POSITION.getStorageUint256(); } /// @notice Returns true if staking module with the given id was registered via `addStakingModule`, false otherwise. /// @param _stakingModuleId Id of the staking module. /// @return True if staking module with the given id was registered, false otherwise. function hasStakingModule(uint256 _stakingModuleId) external view returns (bool) { return _getStorageStakingIndicesMapping()[_stakingModuleId] != 0; } /// @notice Returns status of staking module. /// @param _stakingModuleId Id of the staking module. /// @return Status of the staking module. function getStakingModuleStatus(uint256 _stakingModuleId) public view returns (StakingModuleStatus) { return StakingModuleStatus(_getStakingModuleByIndex(_getStakingModuleIndexById(_stakingModuleId)).status); } /// @notice A summary of the staking module's validators. struct StakingModuleSummary { /// @notice The total number of validators in the EXITED state on the Consensus Layer. /// @dev This value can't decrease in normal conditions. uint256 totalExitedValidators; /// @notice The total number of validators deposited via the official Deposit Contract. /// @dev This value is a cumulative counter: even when the validator goes into EXITED state this /// counter is not decreasing. uint256 totalDepositedValidators; /// @notice The number of validators in the set available for deposit uint256 depositableValidatorsCount; } /// @notice A summary of node operator and its validators. struct NodeOperatorSummary { /// @notice Shows whether the current target limit applied to the node operator. uint256 targetLimitMode; /// @notice Relative target active validators limit for operator. uint256 targetValidatorsCount; /// @notice The number of validators with an expired request to exit time. /// @dev [deprecated] Stuck key processing has been removed, this field is no longer used. uint256 stuckValidatorsCount; /// @notice The number of validators that can't be withdrawn, but deposit costs were /// compensated to the Lido by the node operator. uint256 refundedValidatorsCount; /// @notice A time when the penalty for stuck validators stops applying to node operator rewards. /// @dev [deprecated] Stuck key processing has been removed, this field is no longer used. uint256 stuckPenaltyEndTimestamp; /// @notice The total number of validators in the EXITED state on the Consensus Layer. /// @dev This value can't decrease in normal conditions. uint256 totalExitedValidators; /// @notice The total number of validators deposited via the official Deposit Contract. /// @dev This value is a cumulative counter: even when the validator goes into EXITED state this /// counter is not decreasing. uint256 totalDepositedValidators; /// @notice The number of validators in the set available for deposit. uint256 depositableValidatorsCount; } /// @notice Returns all-validators summary in the staking module. /// @param _stakingModuleId Id of the staking module to return summary for. /// @return summary Staking module summary. function getStakingModuleSummary(uint256 _stakingModuleId) public view returns (StakingModuleSummary memory summary) { IStakingModule stakingModule = IStakingModule(getStakingModule(_stakingModuleId).stakingModuleAddress); ( summary.totalExitedValidators, summary.totalDepositedValidators, summary.depositableValidatorsCount ) = _getStakingModuleSummary(stakingModule); } /// @notice Returns node operator summary from the staking module. /// @param _stakingModuleId Id of the staking module where node operator is onboarded. /// @param _nodeOperatorId Id of the node operator to return summary for. /// @return summary Node operator summary. function getNodeOperatorSummary(uint256 _stakingModuleId, uint256 _nodeOperatorId) public view returns (NodeOperatorSummary memory summary) { IStakingModule stakingModule = IStakingModule(getStakingModule(_stakingModuleId).stakingModuleAddress); /// @dev using intermediate variables below due to "Stack too deep" error in case of /// assigning directly into the NodeOperatorSummary struct ( uint256 targetLimitMode, uint256 targetValidatorsCount, /* uint256 stuckValidatorsCount */, uint256 refundedValidatorsCount, /* uint256 stuckPenaltyEndTimestamp */, uint256 totalExitedValidators, uint256 totalDepositedValidators, uint256 depositableValidatorsCount ) = stakingModule.getNodeOperatorSummary(_nodeOperatorId); summary.targetLimitMode = targetLimitMode; summary.targetValidatorsCount = targetValidatorsCount; summary.refundedValidatorsCount = refundedValidatorsCount; summary.totalExitedValidators = totalExitedValidators; summary.totalDepositedValidators = totalDepositedValidators; summary.depositableValidatorsCount = depositableValidatorsCount; } /// @notice A collection of the staking module data stored across the StakingRouter and the /// staking module contract. /// /// @dev This data, first of all, is designed for off-chain usage and might be redundant for /// on-chain calls. Give preference for dedicated methods for gas-efficient on-chain calls. struct StakingModuleDigest { /// @notice The number of node operators registered in the staking module. uint256 nodeOperatorsCount; /// @notice The number of node operators registered in the staking module in active state. uint256 activeNodeOperatorsCount; /// @notice The current state of the staking module taken from the StakingRouter. StakingModule state; /// @notice A summary of the staking module's validators. StakingModuleSummary summary; } /// @notice A collection of the node operator data stored in the staking module. /// @dev This data, first of all, is designed for off-chain usage and might be redundant for /// on-chain calls. Give preference for dedicated methods for gas-efficient on-chain calls. struct NodeOperatorDigest { /// @notice Id of the node operator. uint256 id; /// @notice Shows whether the node operator is active or not. bool isActive; /// @notice A summary of node operator and its validators. NodeOperatorSummary summary; } /// @notice Returns staking module digest for each staking module registered in the staking router. /// @return Array of staking module digests. /// @dev WARNING: This method is not supposed to be used for onchain calls due to high gas costs /// for data aggregation. function getAllStakingModuleDigests() external view returns (StakingModuleDigest[] memory) { return getStakingModuleDigests(getStakingModuleIds()); } /// @notice Returns staking module digest for passed staking module ids. /// @param _stakingModuleIds Ids of the staking modules to return data for. /// @return digests Array of staking module digests. /// @dev WARNING: This method is not supposed to be used for onchain calls due to high gas costs /// for data aggregation. function getStakingModuleDigests(uint256[] memory _stakingModuleIds) public view returns (StakingModuleDigest[] memory digests) { digests = new StakingModuleDigest[](_stakingModuleIds.length); for (uint256 i = 0; i < _stakingModuleIds.length; ) { StakingModule memory stakingModuleState = getStakingModule(_stakingModuleIds[i]); IStakingModule stakingModule = IStakingModule(stakingModuleState.stakingModuleAddress); digests[i] = StakingModuleDigest({ nodeOperatorsCount: stakingModule.getNodeOperatorsCount(), activeNodeOperatorsCount: stakingModule.getActiveNodeOperatorsCount(), state: stakingModuleState, summary: getStakingModuleSummary(_stakingModuleIds[i]) }); unchecked { ++i; } } } /// @notice Returns node operator digest for each node operator registered in the given staking module. /// @param _stakingModuleId Id of the staking module to return data for. /// @return Array of node operator digests. /// @dev WARNING: This method is not supposed to be used for onchain calls due to high gas costs /// for data aggregation. function getAllNodeOperatorDigests(uint256 _stakingModuleId) external view returns (NodeOperatorDigest[] memory) { return getNodeOperatorDigests( _stakingModuleId, 0, _getIStakingModuleById(_stakingModuleId).getNodeOperatorsCount() ); } /// @notice Returns node operator digest for passed node operator ids in the given staking module. /// @param _stakingModuleId Id of the staking module where node operators registered. /// @param _offset Node operators offset starting with 0. /// @param _limit The max number of node operators to return. /// @return Array of node operator digests. /// @dev WARNING: This method is not supposed to be used for onchain calls due to high gas costs /// for data aggregation. function getNodeOperatorDigests( uint256 _stakingModuleId, uint256 _offset, uint256 _limit ) public view returns (NodeOperatorDigest[] memory) { return getNodeOperatorDigests( _stakingModuleId, _getIStakingModuleById(_stakingModuleId).getNodeOperatorIds(_offset, _limit) ); } /// @notice Returns node operator digest for a slice of node operators registered in the given /// staking module. /// @param _stakingModuleId Id of the staking module where node operators registered. /// @param _nodeOperatorIds Ids of the node operators to return data for. /// @return digests Array of node operator digests. /// @dev WARNING: This method is not supposed to be used for onchain calls due to high gas costs /// for data aggregation. function getNodeOperatorDigests(uint256 _stakingModuleId, uint256[] memory _nodeOperatorIds) public view returns (NodeOperatorDigest[] memory digests) { IStakingModule stakingModule = _getIStakingModuleById(_stakingModuleId); digests = new NodeOperatorDigest[](_nodeOperatorIds.length); for (uint256 i = 0; i < _nodeOperatorIds.length; ) { digests[i] = NodeOperatorDigest({ id: _nodeOperatorIds[i], isActive: stakingModule.getNodeOperatorIsActive(_nodeOperatorIds[i]), summary: getNodeOperatorSummary(_stakingModuleId, _nodeOperatorIds[i]) }); unchecked { ++i; } } } /// @notice Sets the staking module status flag for participation in further deposits and/or reward distribution. /// @param _stakingModuleId Id of the staking module to be updated. /// @param _status New status of the staking module. /// @dev The function is restricted to the `STAKING_MODULE_MANAGE_ROLE` role. function setStakingModuleStatus( uint256 _stakingModuleId, StakingModuleStatus _status ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { StakingModule storage stakingModule = _getStakingModuleByIndex(_getStakingModuleIndexById(_stakingModuleId)); if (StakingModuleStatus(stakingModule.status) == _status) revert StakingModuleStatusTheSame(); _setStakingModuleStatus(stakingModule, _status); } /// @notice Returns whether the staking module is stopped. /// @param _stakingModuleId Id of the staking module. /// @return True if the staking module is stopped, false otherwise. function getStakingModuleIsStopped(uint256 _stakingModuleId) external view returns (bool) { return getStakingModuleStatus(_stakingModuleId) == StakingModuleStatus.Stopped; } /// @notice Returns whether the deposits are paused for the staking module. /// @param _stakingModuleId Id of the staking module. /// @return True if the deposits are paused, false otherwise. function getStakingModuleIsDepositsPaused(uint256 _stakingModuleId) external view returns (bool) { return getStakingModuleStatus(_stakingModuleId) == StakingModuleStatus.DepositsPaused; } /// @notice Returns whether the staking module is active. /// @param _stakingModuleId Id of the staking module. /// @return True if the staking module is active, false otherwise. function getStakingModuleIsActive(uint256 _stakingModuleId) external view returns (bool) { return getStakingModuleStatus(_stakingModuleId) == StakingModuleStatus.Active; } /// @notice Returns staking module nonce. /// @param _stakingModuleId Id of the staking module. /// @return Staking module nonce. function getStakingModuleNonce(uint256 _stakingModuleId) external view returns (uint256) { return _getIStakingModuleById(_stakingModuleId).getNonce(); } /// @notice Returns the last deposit block for the staking module. /// @param _stakingModuleId Id of the staking module. /// @return Last deposit block for the staking module. function getStakingModuleLastDepositBlock(uint256 _stakingModuleId) external view returns (uint256) { return _getStakingModuleByIndex(_getStakingModuleIndexById(_stakingModuleId)).lastDepositBlock; } /// @notice Returns the min deposit block distance for the staking module. /// @param _stakingModuleId Id of the staking module. /// @return Min deposit block distance for the staking module. function getStakingModuleMinDepositBlockDistance(uint256 _stakingModuleId) external view returns (uint256) { return _getStakingModuleByIndex(_getStakingModuleIndexById(_stakingModuleId)).minDepositBlockDistance; } /// @notice Returns the max deposits count per block for the staking module. /// @param _stakingModuleId Id of the staking module. /// @return Max deposits count per block for the staking module. function getStakingModuleMaxDepositsPerBlock(uint256 _stakingModuleId) external view returns (uint256) { return _getStakingModuleByIndex(_getStakingModuleIndexById(_stakingModuleId)).maxDepositsPerBlock; } /// @notice Returns active validators count for the staking module. /// @param _stakingModuleId Id of the staking module. /// @return activeValidatorsCount Active validators count for the staking module. function getStakingModuleActiveValidatorsCount(uint256 _stakingModuleId) external view returns (uint256 activeValidatorsCount) { StakingModule storage stakingModule = _getStakingModuleByIndex(_getStakingModuleIndexById(_stakingModuleId)); ( uint256 totalExitedValidators, uint256 totalDepositedValidators, /* uint256 depositableValidatorsCount */ ) = _getStakingModuleSummary(IStakingModule(stakingModule.stakingModuleAddress)); activeValidatorsCount = totalDepositedValidators - Math256.max( stakingModule.exitedValidatorsCount, totalExitedValidators ); } /// @notice Returns the max count of deposits which the staking module can provide data for based /// on the passed `_maxDepositsValue` amount. /// @param _stakingModuleId Id of the staking module to be deposited. /// @param _maxDepositsValue Max amount of ether that might be used for deposits count calculation. /// @return Max number of deposits might be done using the given staking module. function getStakingModuleMaxDepositsCount(uint256 _stakingModuleId, uint256 _maxDepositsValue) public view returns (uint256) { ( /* uint256 allocated */, uint256[] memory newDepositsAllocation, StakingModuleCache[] memory stakingModulesCache ) = _getDepositsAllocation(_maxDepositsValue / DEPOSIT_SIZE); uint256 stakingModuleIndex = _getStakingModuleIndexById(_stakingModuleId); return newDepositsAllocation[stakingModuleIndex] - stakingModulesCache[stakingModuleIndex].activeValidatorsCount; } /// @notice Returns the aggregate fee distribution proportion. /// @return modulesFee Modules aggregate fee in base precision. /// @return treasuryFee Treasury fee in base precision. /// @return basePrecision Base precision: a value corresponding to the full fee. function getStakingFeeAggregateDistribution() public view returns ( uint96 modulesFee, uint96 treasuryFee, uint256 basePrecision ) { uint96[] memory moduleFees; uint96 totalFee; (, , moduleFees, totalFee, basePrecision) = getStakingRewardsDistribution(); for (uint256 i; i < moduleFees.length; ) { modulesFee += moduleFees[i]; unchecked { ++i; } } treasuryFee = totalFee - modulesFee; } /// @notice Return shares table. /// @return recipients Rewards recipient addresses corresponding to each module. /// @return stakingModuleIds Module IDs. /// @return stakingModuleFees Fee of each recipient. /// @return totalFee Total fee to mint for each staking module and treasury. /// @return precisionPoints Base precision number, which constitutes 100% fee. function getStakingRewardsDistribution() public view returns ( address[] memory recipients, uint256[] memory stakingModuleIds, uint96[] memory stakingModuleFees, uint96 totalFee, uint256 precisionPoints ) { (uint256 totalActiveValidators, StakingModuleCache[] memory stakingModulesCache) = _loadStakingModulesCache(); uint256 stakingModulesCount = stakingModulesCache.length; /// @dev Return empty response if there are no staking modules or active validators yet. if (stakingModulesCount == 0 || totalActiveValidators == 0) { return (new address[](0), new uint256[](0), new uint96[](0), 0, FEE_PRECISION_POINTS); } precisionPoints = FEE_PRECISION_POINTS; stakingModuleIds = new uint256[](stakingModulesCount); recipients = new address[](stakingModulesCount); stakingModuleFees = new uint96[](stakingModulesCount); uint256 rewardedStakingModulesCount = 0; uint256 stakingModuleValidatorsShare; uint96 stakingModuleFee; for (uint256 i; i < stakingModulesCount; ) { /// @dev Skip staking modules which have no active validators. if (stakingModulesCache[i].activeValidatorsCount > 0) { stakingModuleIds[rewardedStakingModulesCount] = stakingModulesCache[i].stakingModuleId; stakingModuleValidatorsShare = ((stakingModulesCache[i].activeValidatorsCount * precisionPoints) / totalActiveValidators); recipients[rewardedStakingModulesCount] = address(stakingModulesCache[i].stakingModuleAddress); stakingModuleFee = uint96((stakingModuleValidatorsShare * stakingModulesCache[i].stakingModuleFee) / TOTAL_BASIS_POINTS); /// @dev If the staking module has the `Stopped` status for some reason, then /// the staking module's rewards go to the treasury, so that the DAO has ability /// to manage them (e.g. to compensate the staking module in case of an error, etc.) if (stakingModulesCache[i].status != StakingModuleStatus.Stopped) { stakingModuleFees[rewardedStakingModulesCount] = stakingModuleFee; } // Else keep stakingModuleFees[rewardedStakingModulesCount] = 0, but increase totalFee. totalFee += (uint96((stakingModuleValidatorsShare * stakingModulesCache[i].treasuryFee) / TOTAL_BASIS_POINTS) + stakingModuleFee); unchecked { rewardedStakingModulesCount++; } } unchecked { ++i; } } // Total fee never exceeds 100%. assert(totalFee <= precisionPoints); /// @dev Shrink arrays. if (rewardedStakingModulesCount < stakingModulesCount) { assembly { mstore(stakingModuleIds, rewardedStakingModulesCount) mstore(recipients, rewardedStakingModulesCount) mstore(stakingModuleFees, rewardedStakingModulesCount) } } } /// @notice Returns the same as getStakingRewardsDistribution() but in reduced, 1e4 precision (DEPRECATED). /// @dev Helper only for Lido contract. Use getStakingRewardsDistribution() instead. /// @return totalFee Total fee to mint for each staking module and treasury in reduced, 1e4 precision. function getTotalFeeE4Precision() external view returns (uint16 totalFee) { /// @dev The logic is placed here but in Lido contract to save Lido bytecode. (, , , uint96 totalFeeInHighPrecision, uint256 precision) = getStakingRewardsDistribution(); // Here we rely on (totalFeeInHighPrecision <= precision). totalFee = _toE4Precision(totalFeeInHighPrecision, precision); } /// @notice Returns the same as getStakingFeeAggregateDistribution() but in reduced, 1e4 precision (DEPRECATED). /// @dev Helper only for Lido contract. Use getStakingFeeAggregateDistribution() instead. /// @return modulesFee Modules aggregate fee in reduced, 1e4 precision. /// @return treasuryFee Treasury fee in reduced, 1e4 precision. function getStakingFeeAggregateDistributionE4Precision() external view returns (uint16 modulesFee, uint16 treasuryFee) { /// @dev The logic is placed here but in Lido contract to save Lido bytecode. ( uint256 modulesFeeHighPrecision, uint256 treasuryFeeHighPrecision, uint256 precision ) = getStakingFeeAggregateDistribution(); // Here we rely on ({modules,treasury}FeeHighPrecision <= precision). modulesFee = _toE4Precision(modulesFeeHighPrecision, precision); treasuryFee = _toE4Precision(treasuryFeeHighPrecision, precision); } /// @notice Returns new deposits allocation after the distribution of the `_depositsCount` deposits. /// @param _depositsCount The maximum number of deposits to be allocated. /// @return allocated Number of deposits allocated to the staking modules. /// @return allocations Array of new deposits allocation to the staking modules. function getDepositsAllocation(uint256 _depositsCount) external view returns (uint256 allocated, uint256[] memory allocations) { (allocated, allocations, ) = _getDepositsAllocation(_depositsCount); } /// @notice Invokes a deposit call to the official Deposit contract. /// @param _depositsCount Number of deposits to make. /// @param _stakingModuleId Id of the staking module to be deposited. /// @param _depositCalldata Staking module calldata. /// @dev Only the Lido contract is allowed to call this method. function deposit( uint256 _depositsCount, uint256 _stakingModuleId, bytes calldata _depositCalldata ) external payable { if (msg.sender != LIDO_POSITION.getStorageAddress()) revert AppAuthLidoFailed(); bytes32 withdrawalCredentials = getWithdrawalCredentials(); if (withdrawalCredentials == 0) revert EmptyWithdrawalsCredentials(); StakingModule storage stakingModule = _getStakingModuleByIndex(_getStakingModuleIndexById(_stakingModuleId)); if (StakingModuleStatus(stakingModule.status) != StakingModuleStatus.Active) revert StakingModuleNotActive(); /// @dev Firstly update the local state of the contract to prevent a reentrancy attack /// even though the staking modules are trusted contracts. uint256 depositsValue = msg.value; if (depositsValue != _depositsCount * DEPOSIT_SIZE) revert InvalidDepositsValue(depositsValue, _depositsCount); _updateModuleLastDepositState(stakingModule, _stakingModuleId, depositsValue); if (_depositsCount > 0) { (bytes memory publicKeysBatch, bytes memory signaturesBatch) = IStakingModule(stakingModule.stakingModuleAddress) .obtainDepositData(_depositsCount, _depositCalldata); uint256 etherBalanceBeforeDeposits = address(this).balance; _makeBeaconChainDeposits32ETH( _depositsCount, abi.encodePacked(withdrawalCredentials), publicKeysBatch, signaturesBatch ); uint256 etherBalanceAfterDeposits = address(this).balance; /// @dev All sent ETH must be deposited and self balance stay the same. assert(etherBalanceBeforeDeposits - etherBalanceAfterDeposits == depositsValue); } } /// @notice Set credentials to withdraw ETH on Consensus Layer side. /// @param _withdrawalCredentials withdrawal credentials field as defined in the Consensus Layer specs. /// @dev Note that setWithdrawalCredentials discards all unused deposits data as the signatures are invalidated. /// @dev The function is restricted to the `MANAGE_WITHDRAWAL_CREDENTIALS_ROLE` role. function setWithdrawalCredentials(bytes32 _withdrawalCredentials) external onlyRole(MANAGE_WITHDRAWAL_CREDENTIALS_ROLE) { WITHDRAWAL_CREDENTIALS_POSITION.setStorageBytes32(_withdrawalCredentials); uint256 stakingModulesCount = getStakingModulesCount(); for (uint256 i; i < stakingModulesCount; ) { StakingModule storage stakingModule = _getStakingModuleByIndex(i); unchecked { ++i; } try IStakingModule(stakingModule.stakingModuleAddress) .onWithdrawalCredentialsChanged() {} catch (bytes memory lowLevelRevertData) { /// @dev This check is required to prevent incorrect gas estimation of the method. /// Without it, Ethereum nodes that use binary search for gas estimation may /// return an invalid value when the onWithdrawalCredentialsChanged() /// reverts because of the "out of gas" error. Here we assume that the /// onWithdrawalCredentialsChanged() method doesn't have reverts with /// empty error data except "out of gas". if (lowLevelRevertData.length == 0) revert UnrecoverableModuleError(); _setStakingModuleStatus(stakingModule, StakingModuleStatus.DepositsPaused); emit WithdrawalsCredentialsChangeFailed(stakingModule.id, lowLevelRevertData); } } emit WithdrawalCredentialsSet(_withdrawalCredentials, msg.sender); } /// @notice Returns current credentials to withdraw ETH on Consensus Layer side. /// @return Withdrawal credentials. function getWithdrawalCredentials() public view returns (bytes32) { return WITHDRAWAL_CREDENTIALS_POSITION.getStorageBytes32(); } function _checkValidatorsByNodeOperatorReportData( bytes calldata _nodeOperatorIds, bytes calldata _validatorsCounts ) internal pure { if (_nodeOperatorIds.length % 8 != 0 || _validatorsCounts.length % 16 != 0) { revert InvalidReportData(3); } uint256 nodeOperatorsCount = _nodeOperatorIds.length / 8; if (_validatorsCounts.length / 16 != nodeOperatorsCount) { revert InvalidReportData(2); } if (nodeOperatorsCount == 0) { revert InvalidReportData(1); } } /// @dev Save the last deposit state for the staking module and emit the event /// @param stakingModule staking module storage ref /// @param stakingModuleId id of the staking module to be deposited /// @param depositsValue value to deposit function _updateModuleLastDepositState( StakingModule storage stakingModule, uint256 stakingModuleId, uint256 depositsValue ) internal { stakingModule.lastDepositAt = uint64(block.timestamp); stakingModule.lastDepositBlock = block.number; emit StakingRouterETHDeposited(stakingModuleId, depositsValue); } /// @dev Loads modules into a memory cache. /// @return totalActiveValidators Total active validators across all modules. /// @return stakingModulesCache Array of StakingModuleCache structs. function _loadStakingModulesCache() internal view returns ( uint256 totalActiveValidators, StakingModuleCache[] memory stakingModulesCache ) { uint256 stakingModulesCount = getStakingModulesCount(); stakingModulesCache = new StakingModuleCache[](stakingModulesCount); for (uint256 i; i < stakingModulesCount; ) { stakingModulesCache[i] = _loadStakingModulesCacheItem(i); totalActiveValidators += stakingModulesCache[i].activeValidatorsCount; unchecked { ++i; } } } function _loadStakingModulesCacheItem(uint256 _stakingModuleIndex) internal view returns (StakingModuleCache memory cacheItem) { StakingModule storage stakingModuleData = _getStakingModuleByIndex(_stakingModuleIndex); cacheItem.stakingModuleAddress = stakingModuleData.stakingModuleAddress; cacheItem.stakingModuleId = stakingModuleData.id; cacheItem.stakingModuleFee = stakingModuleData.stakingModuleFee; cacheItem.treasuryFee = stakingModuleData.treasuryFee; cacheItem.stakeShareLimit = stakingModuleData.stakeShareLimit; cacheItem.status = StakingModuleStatus(stakingModuleData.status); ( uint256 totalExitedValidators, uint256 totalDepositedValidators, uint256 depositableValidatorsCount ) = _getStakingModuleSummary(IStakingModule(cacheItem.stakingModuleAddress)); cacheItem.availableValidatorsCount = cacheItem.status == StakingModuleStatus.Active ? depositableValidatorsCount : 0; // The module might not receive all exited validators data yet => we need to replacing // the exitedValidatorsCount with the one that the staking router is aware of. cacheItem.activeValidatorsCount = totalDepositedValidators - Math256.max(totalExitedValidators, stakingModuleData.exitedValidatorsCount); } function _setStakingModuleStatus(StakingModule storage _stakingModule, StakingModuleStatus _status) internal { StakingModuleStatus prevStatus = StakingModuleStatus(_stakingModule.status); if (prevStatus != _status) { _stakingModule.status = uint8(_status); emit StakingModuleStatusSet(_stakingModule.id, _status, msg.sender); } } function _getDepositsAllocation( uint256 _depositsToAllocate ) internal view returns (uint256 allocated, uint256[] memory allocations, StakingModuleCache[] memory stakingModulesCache) { // Calculate total used validators for operators. uint256 totalActiveValidators; (totalActiveValidators, stakingModulesCache) = _loadStakingModulesCache(); uint256 stakingModulesCount = stakingModulesCache.length; allocations = new uint256[](stakingModulesCount); if (stakingModulesCount > 0) { /// @dev New estimated active validators count. totalActiveValidators += _depositsToAllocate; uint256[] memory capacities = new uint256[](stakingModulesCount); uint256 targetValidators; for (uint256 i; i < stakingModulesCount; ) { allocations[i] = stakingModulesCache[i].activeValidatorsCount; targetValidators = (stakingModulesCache[i].stakeShareLimit * totalActiveValidators) / TOTAL_BASIS_POINTS; capacities[i] = Math256.min(targetValidators, stakingModulesCache[i].activeValidatorsCount + stakingModulesCache[i].availableValidatorsCount); unchecked { ++i; } } (allocated, allocations) = MinFirstAllocationStrategy.allocate(allocations, capacities, _depositsToAllocate); } } function _getStakingModuleIndexById(uint256 _stakingModuleId) internal view returns (uint256) { mapping(uint256 => uint256) storage _stakingModuleIndicesOneBased = _getStorageStakingIndicesMapping(); uint256 indexOneBased = _stakingModuleIndicesOneBased[_stakingModuleId]; if (indexOneBased == 0) revert StakingModuleUnregistered(); return indexOneBased - 1; } function _setStakingModuleIndexById(uint256 _stakingModuleId, uint256 _stakingModuleIndex) internal { mapping(uint256 => uint256) storage _stakingModuleIndicesOneBased = _getStorageStakingIndicesMapping(); _stakingModuleIndicesOneBased[_stakingModuleId] = _stakingModuleIndex + 1; } function _getIStakingModuleById(uint256 _stakingModuleId) internal view returns (IStakingModule) { return IStakingModule(_getStakingModuleAddressById(_stakingModuleId)); } function _getStakingModuleByIndex(uint256 _stakingModuleIndex) internal view returns (StakingModule storage) { mapping(uint256 => StakingModule) storage _stakingModules = _getStorageStakingModulesMapping(); return _stakingModules[_stakingModuleIndex]; } function _getStakingModuleAddressById(uint256 _stakingModuleId) internal view returns (address) { return _getStakingModuleByIndex(_getStakingModuleIndexById(_stakingModuleId)).stakingModuleAddress; } function _getStorageStakingModulesMapping() internal pure returns (mapping(uint256 => StakingModule) storage result) { bytes32 position = STAKING_MODULES_MAPPING_POSITION; assembly { result.slot := position } } function _getStorageStakingIndicesMapping() internal pure returns (mapping(uint256 => uint256) storage result) { bytes32 position = STAKING_MODULE_INDICES_MAPPING_POSITION; assembly { result.slot := position } } function _toE4Precision(uint256 _value, uint256 _precision) internal pure returns (uint16) { return uint16((_value * TOTAL_BASIS_POINTS) / _precision); } function _validateEqualArrayLengths(uint256 firstArrayLength, uint256 secondArrayLength) internal pure { if (firstArrayLength != secondArrayLength) { revert ArraysLengthMismatch(firstArrayLength, secondArrayLength); } } /// @dev Optimizes contract deployment size by wrapping the 'stakingModule.getStakingModuleSummary' function. function _getStakingModuleSummary(IStakingModule stakingModule) internal view returns (uint256, uint256, uint256) { return stakingModule.getStakingModuleSummary(); } /// @notice Handles tracking and penalization logic for a node operator who failed to exit their validator within the defined exit window. /// @dev This function is called to report the current exit-related status of a validator belonging to a specific node operator. /// It accepts a validator's public key, associated with the duration (in seconds) it was eligible to exit but has not exited. /// This data could be used to trigger penalties for the node operator if the validator has been non-exiting for too long. /// @param _stakingModuleId The ID of the staking module. /// @param _nodeOperatorId The ID of the node operator whose validator status is being delivered. /// @param _proofSlotTimestamp The timestamp (slot time) when the validator was last known to be in an active ongoing state. /// @param _publicKey The public key of the validator being reported. /// @param _eligibleToExitInSec The duration (in seconds) indicating how long the validator has been eligible to exit after request but has not exited. function reportValidatorExitDelay( uint256 _stakingModuleId, uint256 _nodeOperatorId, uint256 _proofSlotTimestamp, bytes calldata _publicKey, uint256 _eligibleToExitInSec ) external onlyRole(REPORT_VALIDATOR_EXITING_STATUS_ROLE) { _getIStakingModuleById(_stakingModuleId).reportValidatorExitDelay( _nodeOperatorId, _proofSlotTimestamp, _publicKey, _eligibleToExitInSec ); } /// @notice Handles the triggerable exit event for a set of validators. /// @dev This function is called when validators are exited using triggerable exit requests on the Execution Layer. /// @param validatorExitData An array of `ValidatorExitData` structs, each representing a validator /// for which a triggerable exit was requested. Each entry includes: /// - `stakingModuleId`: ID of the staking module. /// - `nodeOperatorId`: ID of the node operator. /// - `pubkey`: Validator public key, 48 bytes length. /// @param _withdrawalRequestPaidFee Fee amount paid to send a withdrawal request on the Execution Layer (EL). /// @param _exitType The type of exit being performed. /// This parameter may be interpreted differently across various staking modules depending on their specific implementation. function onValidatorExitTriggered( ValidatorExitData[] calldata validatorExitData, uint256 _withdrawalRequestPaidFee, uint256 _exitType ) external onlyRole(REPORT_VALIDATOR_EXIT_TRIGGERED_ROLE) { ValidatorExitData calldata data; for (uint256 i = 0; i < validatorExitData.length; ++i) { data = validatorExitData[i]; try _getIStakingModuleById(data.stakingModuleId).onValidatorExitTriggered( data.nodeOperatorId, data.pubkey, _withdrawalRequestPaidFee, _exitType ) {} catch (bytes memory lowLevelRevertData) { /// @dev This check is required to prevent incorrect gas estimation of the method. /// Without it, Ethereum nodes that use binary search for gas estimation may /// return an invalid value when the onValidatorExitTriggered() /// reverts because of the "out of gas" error. Here we assume that the /// onValidatorExitTriggered() method doesn't have reverts with /// empty error data except "out of gas". if (lowLevelRevertData.length == 0) revert UnrecoverableModuleError(); emit StakingModuleExitNotificationFailed(data.stakingModuleId, data.nodeOperatorId, data.pubkey); } } } }
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/AccessControl.sol)
//
// A modified AccessControl contract using unstructured storage. Copied from tree:
// https://github.com/OpenZeppelin/openzeppelin-contracts/tree/6bd6b76/contracts/access
//
/* See contracts/COMPILERS.md */
pragma solidity 0.8.9;
import "@openzeppelin/contracts-v4.4/access/IAccessControl.sol";
import "@openzeppelin/contracts-v4.4/utils/Context.sol";
import "@openzeppelin/contracts-v4.4/utils/Strings.sol";
import "@openzeppelin/contracts-v4.4/utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
/// @dev Storage slot: mapping(bytes32 => RoleData) _roles
bytes32 private constant ROLES_POSITION = keccak256("openzeppelin.AccessControl._roles");
function _storageRoles() private pure returns (mapping(bytes32 => RoleData) storage _roles) {
bytes32 position = ROLES_POSITION;
assembly { _roles.slot := position }
}
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*
* _Available since v4.1._
*/
modifier onlyRole(bytes32 role) {
_checkRole(role, _msgSender());
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view override returns (bool) {
return _storageRoles()[role].members[account];
}
/**
* @dev Revert with a standard message if `account` is missing `role`.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*/
function _checkRole(bytes32 role, address account) internal view {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(uint160(account), 20),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)
)
);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view override returns (bytes32) {
return _storageRoles()[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event. Note that unlike {grantRole}, this function doesn't perform any
* checks on the calling account.
*
* [WARNING]
* ====
* This function should only be called from the constructor when setting
* up the initial roles for the system.
*
* Using this function in any other way is effectively circumventing the admin
* system imposed by {AccessControl}.
* ====
*
* NOTE: This function is deprecated in favor of {_grantRole}.
*/
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_storageRoles()[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Grants `role` to `account`.
*
* Internal function without access restriction.
*/
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_storageRoles()[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
/**
* @dev Revokes `role` from `account`.
*
* Internal function without access restriction.
*/
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_storageRoles()[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/AccessControlEnumerable.sol)
//
// A modified AccessControlEnumerable contract using unstructured storage. Copied from tree:
// https://github.com/OpenZeppelin/openzeppelin-contracts/tree/6bd6b76/contracts/access
//
/* See contracts/COMPILERS.md */
pragma solidity 0.8.9;
import "@openzeppelin/contracts-v4.4/access/IAccessControlEnumerable.sol";
import "@openzeppelin/contracts-v4.4/utils/structs/EnumerableSet.sol";
import "./AccessControl.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
using EnumerableSet for EnumerableSet.AddressSet;
/// @dev Storage slot: mapping(bytes32 => EnumerableSet.AddressSet) _roleMembers
bytes32 private constant ROLE_MEMBERS_POSITION = keccak256("openzeppelin.AccessControlEnumerable._roleMembers");
function _storageRoleMembers() private pure returns (
mapping(bytes32 => EnumerableSet.AddressSet) storage _roleMembers
) {
bytes32 position = ROLE_MEMBERS_POSITION;
assembly { _roleMembers.slot := position }
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view override returns (address) {
return _storageRoleMembers()[role].at(index);
}
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view override returns (uint256) {
return _storageRoleMembers()[role].length();
}
/**
* @dev Overload {_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override {
super._grantRole(role, account);
_storageRoleMembers()[role].add(account);
}
/**
* @dev Overload {_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override {
super._revokeRole(role, account);
_storageRoleMembers()[role].remove(account);
}
}// SPDX-FileCopyrightText: 2023 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.9; import "../lib/UnstructuredStorage.sol"; contract PausableUntil { using UnstructuredStorage for bytes32; /// Contract resume/pause control storage slot bytes32 internal constant RESUME_SINCE_TIMESTAMP_POSITION = keccak256("lido.PausableUntil.resumeSinceTimestamp"); /// Special value for the infinite pause uint256 public constant PAUSE_INFINITELY = type(uint256).max; /// @notice Emitted when paused by the `pauseFor` or `pauseUntil` call event Paused(uint256 duration); /// @notice Emitted when resumed by the `resume` call event Resumed(); error ZeroPauseDuration(); error PausedExpected(); error ResumedExpected(); error PauseUntilMustBeInFuture(); /// @notice Reverts when resumed modifier whenPaused() { _checkPaused(); _; } /// @notice Reverts when paused modifier whenResumed() { _checkResumed(); _; } function _checkPaused() internal view { if (!isPaused()) { revert PausedExpected(); } } function _checkResumed() internal view { if (isPaused()) { revert ResumedExpected(); } } /// @notice Returns whether the contract is paused function isPaused() public view returns (bool) { return block.timestamp < RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256(); } /// @notice Returns one of: /// - PAUSE_INFINITELY if paused infinitely returns /// - first second when get contract get resumed if paused for specific duration /// - some timestamp in past if not paused function getResumeSinceTimestamp() external view returns (uint256) { return RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256(); } function _resume() internal { _checkPaused(); RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(block.timestamp); emit Resumed(); } function _pauseFor(uint256 _duration) internal { _checkResumed(); if (_duration == 0) revert ZeroPauseDuration(); uint256 resumeSince; if (_duration == PAUSE_INFINITELY) { resumeSince = PAUSE_INFINITELY; } else { resumeSince = block.timestamp + _duration; } _setPausedState(resumeSince); } function _pauseUntil(uint256 _pauseUntilInclusive) internal { _checkResumed(); if (_pauseUntilInclusive < block.timestamp) revert PauseUntilMustBeInFuture(); uint256 resumeSince; if (_pauseUntilInclusive != PAUSE_INFINITELY) { resumeSince = _pauseUntilInclusive + 1; } else { resumeSince = PAUSE_INFINITELY; } _setPausedState(resumeSince); } function _setPausedState(uint256 _resumeSince) internal { RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(_resumeSince); if (_resumeSince == PAUSE_INFINITELY) { emit Paused(PAUSE_INFINITELY); } else { emit Paused(_resumeSince - block.timestamp); } } }
// SPDX-FileCopyrightText: 2022 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.9; import "../lib/UnstructuredStorage.sol"; contract Versioned { using UnstructuredStorage for bytes32; event ContractVersionSet(uint256 version); error NonZeroContractVersionOnInit(); error InvalidContractVersionIncrement(); error UnexpectedContractVersion(uint256 expected, uint256 received); /// @dev Storage slot: uint256 version /// Version of the initialized contract storage. /// The version stored in CONTRACT_VERSION_POSITION equals to: /// - 0 right after the deployment, before an initializer is invoked (and only at that moment); /// - N after calling initialize(), where N is the initially deployed contract version; /// - N after upgrading contract by calling finalizeUpgrade_vN(). bytes32 internal constant CONTRACT_VERSION_POSITION = keccak256("lido.Versioned.contractVersion"); uint256 internal constant PETRIFIED_VERSION_MARK = type(uint256).max; constructor() { // lock version in the implementation's storage to prevent initialization CONTRACT_VERSION_POSITION.setStorageUint256(PETRIFIED_VERSION_MARK); } /// @notice Returns the current contract version. function getContractVersion() public view returns (uint256) { return CONTRACT_VERSION_POSITION.getStorageUint256(); } function _checkContractVersion(uint256 version) internal view { uint256 expectedVersion = getContractVersion(); if (version != expectedVersion) { revert UnexpectedContractVersion(expectedVersion, version); } } /// @dev Sets the contract version to N. Should be called from the initialize() function. function _initializeContractVersionTo(uint256 version) internal { if (getContractVersion() != 0) revert NonZeroContractVersionOnInit(); _setContractVersion(version); } /// @dev Updates the contract version. Should be called from a finalizeUpgrade_vN() function. function _updateContractVersion(uint256 newVersion) internal { if (newVersion != getContractVersion() + 1) revert InvalidContractVersionIncrement(); _setContractVersion(newVersion); } function _setContractVersion(uint256 version) private { CONTRACT_VERSION_POSITION.setStorageUint256(version); emit ContractVersionSet(version); } }
// SPDX-FileCopyrightText: 2023 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 /* See contracts/COMPILERS.md */ pragma solidity 0.8.9; import {WithdrawalQueueBase} from "./WithdrawalQueueBase.sol"; import {IERC20} from "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; import {IERC20Permit} from "@openzeppelin/contracts-v4.4/token/ERC20/extensions/draft-IERC20Permit.sol"; import {EnumerableSet} from "@openzeppelin/contracts-v4.4/utils/structs/EnumerableSet.sol"; import {AccessControlEnumerable} from "./utils/access/AccessControlEnumerable.sol"; import {UnstructuredStorage} from "./lib/UnstructuredStorage.sol"; import {PausableUntil} from "./utils/PausableUntil.sol"; import {Versioned} from "./utils/Versioned.sol"; /// @notice Interface defining a Lido liquid staking pool /// @dev see also [Lido liquid staking pool core contract](https://docs.lido.fi/contracts/lido) interface IStETH is IERC20, IERC20Permit { function getSharesByPooledEth(uint256 _pooledEthAmount) external view returns (uint256); } /// @notice Interface defining a Lido liquid staking pool wrapper /// @dev see WstETH.sol for full docs interface IWstETH is IERC20, IERC20Permit { function unwrap(uint256 _wstETHAmount) external returns (uint256); function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256); function stETH() external view returns (IStETH); } /// @title A contract for handling stETH withdrawal request queue within the Lido protocol /// @author folkyatina abstract contract WithdrawalQueue is AccessControlEnumerable, PausableUntil, WithdrawalQueueBase, Versioned { using UnstructuredStorage for bytes32; using EnumerableSet for EnumerableSet.UintSet; /// Bunker mode activation timestamp bytes32 internal constant BUNKER_MODE_SINCE_TIMESTAMP_POSITION = keccak256("lido.WithdrawalQueue.bunkerModeSinceTimestamp"); /// Special value for timestamp when bunker mode is inactive (i.e., protocol in turbo mode) uint256 public constant BUNKER_MODE_DISABLED_TIMESTAMP = type(uint256).max; // ACL bytes32 public constant PAUSE_ROLE = keccak256("PAUSE_ROLE"); bytes32 public constant RESUME_ROLE = keccak256("RESUME_ROLE"); bytes32 public constant FINALIZE_ROLE = keccak256("FINALIZE_ROLE"); bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE"); /// @notice minimal amount of stETH that is possible to withdraw uint256 public constant MIN_STETH_WITHDRAWAL_AMOUNT = 100; /// @notice maximum amount of stETH 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, it's recommended to split it to several requests uint256 public constant MAX_STETH_WITHDRAWAL_AMOUNT = 1000 * 1e18; /// @notice Lido stETH token address IStETH public immutable STETH; /// @notice Lido wstETH token address IWstETH public immutable WSTETH; event InitializedV1(address _admin); event BunkerModeEnabled(uint256 _sinceTimestamp); event BunkerModeDisabled(); error AdminZeroAddress(); error RequestAmountTooSmall(uint256 _amountOfStETH); error RequestAmountTooLarge(uint256 _amountOfStETH); error InvalidReportTimestamp(); error RequestIdsNotSorted(); error ZeroRecipient(); error ArraysLengthMismatch(uint256 _firstArrayLength, uint256 _secondArrayLength); /// @param _wstETH address of WstETH contract constructor(IWstETH _wstETH) { // init immutables WSTETH = _wstETH; STETH = WSTETH.stETH(); } /// @notice Initialize the contract storage explicitly. /// @param _admin admin address that can change every role. /// @dev Reverts if `_admin` equals to `address(0)` /// @dev NB! It's initialized in paused state by default and should be resumed explicitly to start /// @dev NB! Bunker mode is disabled by default function initialize(address _admin) external { if (_admin == address(0)) revert AdminZeroAddress(); _initialize(_admin); } /// @notice Resume withdrawal requests placement and finalization /// Contract is deployed in paused state and should be resumed explicitly function resume() external { _checkRole(RESUME_ROLE, msg.sender); _resume(); } /// @notice Pause withdrawal requests placement and finalization. Claiming finalized requests will still be available /// @param _duration pause duration in seconds (use `PAUSE_INFINITELY` for unlimited) /// @dev Reverts if contract is already paused /// @dev Reverts reason if sender has no `PAUSE_ROLE` /// @dev Reverts if zero duration is passed function pauseFor(uint256 _duration) external onlyRole(PAUSE_ROLE) { _pauseFor(_duration); } /// @notice Pause withdrawal requests placement and finalization. Claiming finalized requests will still be available /// @param _pauseUntilInclusive the last second to pause until inclusive /// @dev Reverts if the timestamp is in the past /// @dev Reverts if sender has no `PAUSE_ROLE` /// @dev Reverts if contract is already paused function pauseUntil(uint256 _pauseUntilInclusive) external onlyRole(PAUSE_ROLE) { _pauseUntil(_pauseUntilInclusive); } /// @notice Request the batch of stETH for withdrawal. Approvals for the passed amounts should be done before. /// @param _amounts an array of stETH amount values. /// The standalone withdrawal request will be created for each item in the passed list. /// @param _owner address that will be able to manage the created requests. /// If `address(0)` is passed, `msg.sender` will be used as owner. /// @return requestIds an array of the created withdrawal request ids function requestWithdrawals(uint256[] calldata _amounts, address _owner) public returns (uint256[] memory requestIds) { _checkResumed(); if (_owner == address(0)) _owner = msg.sender; requestIds = new uint256[](_amounts.length); for (uint256 i = 0; i < _amounts.length; ++i) { _checkWithdrawalRequestAmount(_amounts[i]); requestIds[i] = _requestWithdrawal(_amounts[i], _owner); } } /// @notice Request the batch of wstETH for withdrawal. Approvals for the passed amounts should be done before. /// @param _amounts an array of wstETH amount values. /// The standalone withdrawal request will be created for each item in the passed list. /// @param _owner address that will be able to manage the created requests. /// If `address(0)` is passed, `msg.sender` will be used as an owner. /// @return requestIds an array of the created withdrawal request ids function requestWithdrawalsWstETH(uint256[] calldata _amounts, address _owner) public returns (uint256[] memory requestIds) { _checkResumed(); if (_owner == address(0)) _owner = msg.sender; requestIds = new uint256[](_amounts.length); for (uint256 i = 0; i < _amounts.length; ++i) { requestIds[i] = _requestWithdrawalWstETH(_amounts[i], _owner); } } struct PermitInput { uint256 value; uint256 deadline; uint8 v; bytes32 r; bytes32 s; } /// @notice Request the batch of stETH for withdrawal using EIP-2612 Permit /// @param _amounts an array of stETH amount values /// The standalone withdrawal request will be created for each item in the passed list. /// @param _owner address that will be able to manage the created requests. /// If `address(0)` is passed, `msg.sender` will be used as an owner. /// @param _permit data required for the stETH.permit() method to set the allowance /// @return requestIds an array of the created withdrawal request ids function requestWithdrawalsWithPermit(uint256[] calldata _amounts, address _owner, PermitInput calldata _permit) external returns (uint256[] memory requestIds) { STETH.permit(msg.sender, address(this), _permit.value, _permit.deadline, _permit.v, _permit.r, _permit.s); return requestWithdrawals(_amounts, _owner); } /// @notice Request the batch of wstETH for withdrawal using EIP-2612 Permit /// @param _amounts an array of wstETH amount values /// The standalone withdrawal request will be created for each item in the passed list. /// @param _owner address that will be able to manage the created requests. /// If `address(0)` is passed, `msg.sender` will be used as an owner. /// @param _permit data required for the wtETH.permit() method to set the allowance /// @return requestIds an array of the created withdrawal request ids function requestWithdrawalsWstETHWithPermit( uint256[] calldata _amounts, address _owner, PermitInput calldata _permit ) external returns (uint256[] memory requestIds) { WSTETH.permit(msg.sender, address(this), _permit.value, _permit.deadline, _permit.v, _permit.r, _permit.s); return requestWithdrawalsWstETH(_amounts, _owner); } /// @notice Returns all withdrawal requests that belongs to the `_owner` address /// /// WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed /// to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that /// this function has an unbounded cost, and using it as part of a state-changing function may render the function /// uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. function getWithdrawalRequests(address _owner) external view returns (uint256[] memory requestsIds) { return _getRequestsByOwner()[_owner].values(); } /// @notice Returns status for requests with provided ids /// @param _requestIds array of withdrawal request ids function getWithdrawalStatus(uint256[] calldata _requestIds) external view returns (WithdrawalRequestStatus[] memory statuses) { statuses = new WithdrawalRequestStatus[](_requestIds.length); for (uint256 i = 0; i < _requestIds.length; ++i) { statuses[i] = _getStatus(_requestIds[i]); } } /// @notice Returns amount of ether available for claim for each provided request id /// @param _requestIds array of request ids /// @param _hints checkpoint hints. can be found with `findCheckpointHints(_requestIds, 1, getLastCheckpointIndex())` /// @return claimableEthValues amount of claimable ether for each request, amount is equal to 0 if request /// is not finalized or already claimed function getClaimableEther(uint256[] calldata _requestIds, uint256[] calldata _hints) external view returns (uint256[] memory claimableEthValues) { claimableEthValues = new uint256[](_requestIds.length); for (uint256 i = 0; i < _requestIds.length; ++i) { claimableEthValues[i] = _getClaimableEther(_requestIds[i], _hints[i]); } } /// @notice Claim a batch of withdrawal requests if they are finalized sending ether to `_recipient` /// @param _requestIds array of request ids to claim /// @param _hints checkpoint hint for each id. Can be obtained with `findCheckpointHints()` /// @param _recipient address where claimed ether will be sent to /// @dev /// Reverts if recipient is equal to zero /// Reverts if requestIds and hints arrays length differs /// Reverts if any requestId or hint in arguments are not valid /// Reverts if any request is not finalized or already claimed /// Reverts if msg sender is not an owner of the requests function claimWithdrawalsTo(uint256[] calldata _requestIds, uint256[] calldata _hints, address _recipient) external { if (_recipient == address(0)) revert ZeroRecipient(); if (_requestIds.length != _hints.length) { revert ArraysLengthMismatch(_requestIds.length, _hints.length); } for (uint256 i = 0; i < _requestIds.length; ++i) { _claim(_requestIds[i], _hints[i], _recipient); _emitTransfer(msg.sender, address(0), _requestIds[i]); } } /// @notice Claim a batch of withdrawal requests if they are finalized sending locked ether to the owner /// @param _requestIds array of request ids to claim /// @param _hints checkpoint hint for each id. Can be obtained with `findCheckpointHints()` /// @dev /// Reverts if requestIds and hints arrays length differs /// Reverts if any requestId or hint in arguments are not valid /// Reverts if any request is not finalized or already claimed /// Reverts if msg sender is not an owner of the requests function claimWithdrawals(uint256[] calldata _requestIds, uint256[] calldata _hints) external { if (_requestIds.length != _hints.length) { revert ArraysLengthMismatch(_requestIds.length, _hints.length); } for (uint256 i = 0; i < _requestIds.length; ++i) { _claim(_requestIds[i], _hints[i], msg.sender); _emitTransfer(msg.sender, address(0), _requestIds[i]); } } /// @notice Claim one`_requestId` request once finalized sending locked ether to the owner /// @param _requestId request id to claim /// @dev use unbounded loop to find a hint, which can lead to OOG /// @dev /// Reverts if requestId or hint are not valid /// Reverts if request is not finalized or already claimed /// Reverts if msg sender is not an owner of request function claimWithdrawal(uint256 _requestId) external { _claim(_requestId, _findCheckpointHint(_requestId, 1, getLastCheckpointIndex()), msg.sender); _emitTransfer(msg.sender, address(0), _requestId); } /// @notice Finds the list of hints for the given `_requestIds` searching among the checkpoints with indices /// in the range `[_firstIndex, _lastIndex]`. /// NB! Array of request ids should be sorted /// NB! `_firstIndex` should be greater than 0, because checkpoint list is 1-based array /// Usage: findCheckpointHints(_requestIds, 1, getLastCheckpointIndex()) /// @param _requestIds ids of the requests sorted in the ascending order to get hints for /// @param _firstIndex left boundary of the search range. Should be greater than 0 /// @param _lastIndex right boundary of the search range. Should be less than or equal to getLastCheckpointIndex() /// @return hintIds array of hints used to find required checkpoint for the request function findCheckpointHints(uint256[] calldata _requestIds, uint256 _firstIndex, uint256 _lastIndex) external view returns (uint256[] memory hintIds) { hintIds = new uint256[](_requestIds.length); uint256 prevRequestId = 0; for (uint256 i = 0; i < _requestIds.length; ++i) { if (_requestIds[i] < prevRequestId) revert RequestIdsNotSorted(); hintIds[i] = _findCheckpointHint(_requestIds[i], _firstIndex, _lastIndex); _firstIndex = hintIds[i]; prevRequestId = _requestIds[i]; } } /// @notice Update bunker mode state and last report timestamp on oracle report /// @dev should be called by oracle /// /// @param _isBunkerModeNow is bunker mode reported by oracle /// @param _bunkerStartTimestamp timestamp of start of the bunker mode /// @param _currentReportTimestamp timestamp of the current report ref slot function onOracleReport(bool _isBunkerModeNow, uint256 _bunkerStartTimestamp, uint256 _currentReportTimestamp) external { _checkRole(ORACLE_ROLE, msg.sender); if (_bunkerStartTimestamp >= block.timestamp) revert InvalidReportTimestamp(); if (_currentReportTimestamp >= block.timestamp) revert InvalidReportTimestamp(); _setLastReportTimestamp(_currentReportTimestamp); bool isBunkerModeWasSetBefore = isBunkerModeActive(); // on bunker mode state change if (_isBunkerModeNow != isBunkerModeWasSetBefore) { // write previous timestamp to enable bunker or max uint to disable if (_isBunkerModeNow) { BUNKER_MODE_SINCE_TIMESTAMP_POSITION.setStorageUint256(_bunkerStartTimestamp); emit BunkerModeEnabled(_bunkerStartTimestamp); } else { BUNKER_MODE_SINCE_TIMESTAMP_POSITION.setStorageUint256(BUNKER_MODE_DISABLED_TIMESTAMP); emit BunkerModeDisabled(); } } } /// @notice Check if bunker mode is active function isBunkerModeActive() public view returns (bool) { return bunkerModeSinceTimestamp() < BUNKER_MODE_DISABLED_TIMESTAMP; } /// @notice Get bunker mode activation timestamp /// @dev returns `BUNKER_MODE_DISABLED_TIMESTAMP` if bunker mode is disable (i.e., protocol in turbo mode) function bunkerModeSinceTimestamp() public view returns (uint256) { return BUNKER_MODE_SINCE_TIMESTAMP_POSITION.getStorageUint256(); } /// @notice Should emit ERC721 Transfer event in the inheriting contract function _emitTransfer(address from, address to, uint256 _requestId) internal virtual; /// @dev internal initialization helper. Doesn't check provided addresses intentionally function _initialize(address _admin) internal { _initializeQueue(); _pauseFor(PAUSE_INFINITELY); _initializeContractVersionTo(1); _grantRole(DEFAULT_ADMIN_ROLE, _admin); BUNKER_MODE_SINCE_TIMESTAMP_POSITION.setStorageUint256(BUNKER_MODE_DISABLED_TIMESTAMP); emit InitializedV1(_admin); } function _requestWithdrawal(uint256 _amountOfStETH, address _owner) internal returns (uint256 requestId) { STETH.transferFrom(msg.sender, address(this), _amountOfStETH); uint256 amountOfShares = STETH.getSharesByPooledEth(_amountOfStETH); requestId = _enqueue(uint128(_amountOfStETH), uint128(amountOfShares), _owner); _emitTransfer(address(0), _owner, requestId); } function _requestWithdrawalWstETH(uint256 _amountOfWstETH, address _owner) internal returns (uint256 requestId) { WSTETH.transferFrom(msg.sender, address(this), _amountOfWstETH); uint256 amountOfStETH = WSTETH.unwrap(_amountOfWstETH); _checkWithdrawalRequestAmount(amountOfStETH); uint256 amountOfShares = STETH.getSharesByPooledEth(amountOfStETH); requestId = _enqueue(uint128(amountOfStETH), uint128(amountOfShares), _owner); _emitTransfer(address(0), _owner, requestId); } function _checkWithdrawalRequestAmount(uint256 _amountOfStETH) internal pure { if (_amountOfStETH < MIN_STETH_WITHDRAWAL_AMOUNT) { revert RequestAmountTooSmall(_amountOfStETH); } if (_amountOfStETH > MAX_STETH_WITHDRAWAL_AMOUNT) { revert RequestAmountTooLarge(_amountOfStETH); } } /// @notice returns claimable ether under the request. Returns 0 if request is not finalized or claimed function _getClaimableEther(uint256 _requestId, uint256 _hint) internal view returns (uint256) { if (_requestId == 0 || _requestId > getLastRequestId()) revert InvalidRequestId(_requestId); if (_requestId > getLastFinalizedRequestId()) return 0; WithdrawalRequest storage request = _getQueue()[_requestId]; if (request.claimed) return 0; return _calculateClaimableEther(request, _requestId, _hint); } }
// SPDX-FileCopyrightText: 2023 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 /* See contracts/COMPILERS.md */ pragma solidity 0.8.9; import "@openzeppelin/contracts-v4.4/utils/structs/EnumerableSet.sol"; import {UnstructuredStorage} from "./lib/UnstructuredStorage.sol"; /// @title Queue to store and manage WithdrawalRequests. /// @dev Use an optimizations to store max share rates for finalized requests heavily inspired /// by Aragon MiniMe token https://github.com/aragon/aragon-minime/blob/master/contracts/MiniMeToken.sol /// /// @author folkyatina abstract contract WithdrawalQueueBase { using EnumerableSet for EnumerableSet.UintSet; using UnstructuredStorage for bytes32; /// @dev maximal length of the batch array provided for prefinalization. See `prefinalize()` uint256 public constant MAX_BATCHES_LENGTH = 36; /// @notice precision base for share rate uint256 internal constant E27_PRECISION_BASE = 1e27; /// @dev return value for the `find...` methods in case of no result uint256 internal constant NOT_FOUND = 0; /// @dev queue for withdrawal requests, indexes (requestId) start from 1 bytes32 internal constant QUEUE_POSITION = keccak256("lido.WithdrawalQueue.queue"); /// @dev last index in request queue bytes32 internal constant LAST_REQUEST_ID_POSITION = keccak256("lido.WithdrawalQueue.lastRequestId"); /// @dev last index of finalized request in the queue bytes32 internal constant LAST_FINALIZED_REQUEST_ID_POSITION = keccak256("lido.WithdrawalQueue.lastFinalizedRequestId"); /// @dev finalization rate history, indexes start from 1 bytes32 internal constant CHECKPOINTS_POSITION = keccak256("lido.WithdrawalQueue.checkpoints"); /// @dev last index in checkpoints array bytes32 internal constant LAST_CHECKPOINT_INDEX_POSITION = keccak256("lido.WithdrawalQueue.lastCheckpointIndex"); /// @dev amount of eth locked on contract for further claiming bytes32 internal constant LOCKED_ETHER_AMOUNT_POSITION = keccak256("lido.WithdrawalQueue.lockedEtherAmount"); /// @dev withdrawal requests mapped to the owners bytes32 internal constant REQUEST_BY_OWNER_POSITION = keccak256("lido.WithdrawalQueue.requestsByOwner"); /// @dev timestamp of the last oracle report bytes32 internal constant LAST_REPORT_TIMESTAMP_POSITION = keccak256("lido.WithdrawalQueue.lastReportTimestamp"); /// @notice structure representing a request for withdrawal struct WithdrawalRequest { /// @notice sum of the all stETH submitted for withdrawals including this request uint128 cumulativeStETH; /// @notice sum of the all shares locked for withdrawal including this request uint128 cumulativeShares; /// @notice address that can claim or transfer the request address owner; /// @notice block.timestamp when the request was created uint40 timestamp; /// @notice flag if the request was claimed bool claimed; /// @notice timestamp of last oracle report for this request uint40 reportTimestamp; } /// @notice structure to store discounts for requests that are affected by negative rebase struct Checkpoint { uint256 fromRequestId; uint256 maxShareRate; } /// @notice output format struct for `_getWithdrawalStatus()` method struct WithdrawalRequestStatus { /// @notice stETH token amount that was locked on withdrawal queue for this request uint256 amountOfStETH; /// @notice amount of stETH shares locked on withdrawal queue for this request uint256 amountOfShares; /// @notice address that can claim or transfer this request address owner; /// @notice timestamp of when the request was created, in seconds uint256 timestamp; /// @notice true, if request is finalized bool isFinalized; /// @notice true, if request is claimed. Request is claimable if (isFinalized && !isClaimed) bool isClaimed; } /// @dev Contains both stETH token amount and its corresponding shares amount event WithdrawalRequested( uint256 indexed requestId, address indexed requestor, address indexed owner, uint256 amountOfStETH, uint256 amountOfShares ); event WithdrawalsFinalized( uint256 indexed from, uint256 indexed to, uint256 amountOfETHLocked, uint256 sharesToBurn, uint256 timestamp ); event WithdrawalClaimed( uint256 indexed requestId, address indexed owner, address indexed receiver, uint256 amountOfETH ); error ZeroAmountOfETH(); error ZeroShareRate(); error ZeroTimestamp(); error TooMuchEtherToFinalize(uint256 sent, uint256 maxExpected); error NotOwner(address _sender, address _owner); error InvalidRequestId(uint256 _requestId); error InvalidRequestIdRange(uint256 startId, uint256 endId); error InvalidState(); error BatchesAreNotSorted(); error EmptyBatches(); error RequestNotFoundOrNotFinalized(uint256 _requestId); error NotEnoughEther(); error RequestAlreadyClaimed(uint256 _requestId); error InvalidHint(uint256 _hint); error CantSendValueRecipientMayHaveReverted(); /// @notice id of the last request /// NB! requests are indexed from 1, so it returns 0 if there is no requests in the queue function getLastRequestId() public view returns (uint256) { return LAST_REQUEST_ID_POSITION.getStorageUint256(); } /// @notice id of the last finalized request /// NB! requests are indexed from 1, so it returns 0 if there is no finalized requests in the queue function getLastFinalizedRequestId() public view returns (uint256) { return LAST_FINALIZED_REQUEST_ID_POSITION.getStorageUint256(); } /// @notice amount of ETH on this contract balance that is locked for withdrawal and available to claim function getLockedEtherAmount() public view returns (uint256) { return LOCKED_ETHER_AMOUNT_POSITION.getStorageUint256(); } /// @notice length of the checkpoint array. Last possible value for the hint. /// NB! checkpoints are indexed from 1, so it returns 0 if there is no checkpoints function getLastCheckpointIndex() public view returns (uint256) { return LAST_CHECKPOINT_INDEX_POSITION.getStorageUint256(); } /// @notice return the number of unfinalized requests in the queue function unfinalizedRequestNumber() external view returns (uint256) { return getLastRequestId() - getLastFinalizedRequestId(); } /// @notice Returns the amount of stETH in the queue yet to be finalized function unfinalizedStETH() external view returns (uint256) { return _getQueue()[getLastRequestId()].cumulativeStETH - _getQueue()[getLastFinalizedRequestId()].cumulativeStETH; } // // FINALIZATION FLOW // // Process when protocol is fixing the withdrawal request value and lock the required amount of ETH. // The value of a request after finalization can be: // - nominal (when the amount of eth locked for this request are equal to the request's stETH) // - discounted (when the amount of eth will be lower, because the protocol share rate dropped // before request is finalized, so it will be equal to `request's shares` * `protocol share rate`) // The parameters that are required for finalization are: // - current share rate of the protocol // - id of the last request that can be finalized // - the amount of eth that must be locked for these requests // To calculate the eth amount we'll need to know which requests in the queue will be finalized as nominal // and which as discounted and the exact value of the discount. It's impossible to calculate without the unbounded // loop over the unfinalized part of the queue. So, we need to extract a part of the algorithm off-chain, bring the // result with oracle report and check it later and check the result later. // So, we came to this solution: // Off-chain // 1. Oracle iterates over the queue off-chain and calculate the id of the latest finalizable request // in the queue. Then it splits all the requests that will be finalized into batches the way, // that requests in a batch are all nominal or all discounted. // And passes them in the report as the array of the ending ids of these batches. So it can be reconstructed like // `[lastFinalizedRequestId+1, batches[0]], [batches[0]+1, batches[1]] ... [batches[n-2], batches[n-1]]` // 2. Contract checks the validity of the batches on-chain and calculate the amount of eth required to // finalize them. It can be done without unbounded loop using partial sums that are calculated on request enqueueing. // 3. Contract marks the request's as finalized and locks the eth for claiming. It also, // set's the discount checkpoint for these request's if required that will be applied on claim for each request's // individually depending on request's share rate. /// @notice transient state that is used to pass intermediate results between several `calculateFinalizationBatches` // invocations struct BatchesCalculationState { /// @notice amount of ether available in the protocol that can be used to finalize withdrawal requests /// Will decrease on each call and will be equal to the remainder when calculation is finished /// Should be set before the first call uint256 remainingEthBudget; /// @notice flag that is set to `true` if returned state is final and `false` if more calls are required bool finished; /// @notice static array to store last request id in each batch uint256[MAX_BATCHES_LENGTH] batches; /// @notice length of the filled part of `batches` array uint256 batchesLength; } /// @notice Offchain view for the oracle daemon that calculates how many requests can be finalized within /// the given budget, time period and share rate limits. Returned requests are split into batches. /// Each batch consist of the requests that all have the share rate below the `_maxShareRate` or above it. /// Below you can see an example how 14 requests with different share rates will be split into 5 batches by /// this method /// /// ^ share rate /// | /// | • • /// | • • • • • /// |----------------------•------ _maxShareRate /// | • • • • • /// | • /// +-------------------------------> requestId /// | 1st| 2nd |3| 4th | 5th | /// /// @param _maxShareRate current share rate of the protocol (1e27 precision) /// @param _maxTimestamp max timestamp of the request that can be finalized /// @param _maxRequestsPerCall max request number that can be processed per call. /// @param _state structure that accumulates the state across multiple invocations to overcome gas limits. /// To start calculation you should pass `state.remainingEthBudget` and `state.finished == false` and then invoke /// the function with returned `state` until it returns a state with `finished` flag set /// @return state that is changing on each call and should be passed to the next call until `state.finished` is true function calculateFinalizationBatches( uint256 _maxShareRate, uint256 _maxTimestamp, uint256 _maxRequestsPerCall, BatchesCalculationState memory _state ) external view returns (BatchesCalculationState memory) { if (_state.finished || _state.remainingEthBudget == 0) revert InvalidState(); uint256 currentId; WithdrawalRequest memory prevRequest; uint256 prevRequestShareRate; if (_state.batchesLength == 0) { currentId = getLastFinalizedRequestId() + 1; prevRequest = _getQueue()[currentId - 1]; } else { uint256 lastHandledRequestId = _state.batches[_state.batchesLength - 1]; currentId = lastHandledRequestId + 1; prevRequest = _getQueue()[lastHandledRequestId]; (prevRequestShareRate,,) = _calcBatch(_getQueue()[lastHandledRequestId - 1], prevRequest); } uint256 nextCallRequestId = currentId + _maxRequestsPerCall; uint256 queueLength = getLastRequestId() + 1; while (currentId < queueLength && currentId < nextCallRequestId) { WithdrawalRequest memory request = _getQueue()[currentId]; if (request.timestamp > _maxTimestamp) break; // max timestamp break (uint256 requestShareRate, uint256 ethToFinalize, uint256 shares) = _calcBatch(prevRequest, request); if (requestShareRate > _maxShareRate) { // discounted ethToFinalize = (shares * _maxShareRate) / E27_PRECISION_BASE; } if (ethToFinalize > _state.remainingEthBudget) break; // budget break _state.remainingEthBudget -= ethToFinalize; if (_state.batchesLength != 0 && ( // share rate of requests in the same batch can differ by 1-2 wei because of the rounding error // (issue: https://github.com/lidofinance/lido-dao/issues/442 ) // so we're taking requests that are placed during the same report // as equal even if their actual share rate are different prevRequest.reportTimestamp == request.reportTimestamp || // both requests are below the line prevRequestShareRate <= _maxShareRate && requestShareRate <= _maxShareRate || // both requests are above the line prevRequestShareRate > _maxShareRate && requestShareRate > _maxShareRate )) { _state.batches[_state.batchesLength - 1] = currentId; // extend the last batch } else { // to be able to check batches on-chain we need array to have limited length if (_state.batchesLength == MAX_BATCHES_LENGTH) break; // create a new batch _state.batches[_state.batchesLength] = currentId; ++_state.batchesLength; } prevRequestShareRate = requestShareRate; prevRequest = request; unchecked{ ++currentId; } } _state.finished = currentId == queueLength || currentId < nextCallRequestId; return _state; } /// @notice Checks finalization batches, calculates required ether and the amount of shares to burn /// @param _batches finalization batches calculated offchain using `calculateFinalizationBatches()` /// @param _maxShareRate max share rate that will be used for request finalization (1e27 precision) /// @return ethToLock amount of ether that should be sent with `finalize()` method /// @return sharesToBurn amount of shares that belongs to requests that will be finalized function prefinalize(uint256[] calldata _batches, uint256 _maxShareRate) external view returns (uint256 ethToLock, uint256 sharesToBurn) { if (_maxShareRate == 0) revert ZeroShareRate(); if (_batches.length == 0) revert EmptyBatches(); if (_batches[0] <= getLastFinalizedRequestId()) revert InvalidRequestId(_batches[0]); if (_batches[_batches.length - 1] > getLastRequestId()) revert InvalidRequestId(_batches[_batches.length - 1]); uint256 currentBatchIndex; uint256 prevBatchEndRequestId = getLastFinalizedRequestId(); WithdrawalRequest memory prevBatchEnd = _getQueue()[prevBatchEndRequestId]; while (currentBatchIndex < _batches.length) { uint256 batchEndRequestId = _batches[currentBatchIndex]; if (batchEndRequestId <= prevBatchEndRequestId) revert BatchesAreNotSorted(); WithdrawalRequest memory batchEnd = _getQueue()[batchEndRequestId]; (uint256 batchShareRate, uint256 stETH, uint256 shares) = _calcBatch(prevBatchEnd, batchEnd); if (batchShareRate > _maxShareRate) { // discounted ethToLock += shares * _maxShareRate / E27_PRECISION_BASE; } else { // nominal ethToLock += stETH; } sharesToBurn += shares; prevBatchEndRequestId = batchEndRequestId; prevBatchEnd = batchEnd; unchecked{ ++currentBatchIndex; } } } /// @dev Finalize requests in the queue /// Emits WithdrawalsFinalized event. function _finalize(uint256 _lastRequestIdToBeFinalized, uint256 _amountOfETH, uint256 _maxShareRate) internal { if (_lastRequestIdToBeFinalized > getLastRequestId()) revert InvalidRequestId(_lastRequestIdToBeFinalized); uint256 lastFinalizedRequestId = getLastFinalizedRequestId(); if (_lastRequestIdToBeFinalized <= lastFinalizedRequestId) revert InvalidRequestId(_lastRequestIdToBeFinalized); WithdrawalRequest memory lastFinalizedRequest = _getQueue()[lastFinalizedRequestId]; WithdrawalRequest memory requestToFinalize = _getQueue()[_lastRequestIdToBeFinalized]; uint128 stETHToFinalize = requestToFinalize.cumulativeStETH - lastFinalizedRequest.cumulativeStETH; if (_amountOfETH > stETHToFinalize) revert TooMuchEtherToFinalize(_amountOfETH, stETHToFinalize); uint256 firstRequestIdToFinalize = lastFinalizedRequestId + 1; uint256 lastCheckpointIndex = getLastCheckpointIndex(); // add a new checkpoint with current finalization max share rate _getCheckpoints()[lastCheckpointIndex + 1] = Checkpoint(firstRequestIdToFinalize, _maxShareRate); _setLastCheckpointIndex(lastCheckpointIndex + 1); _setLockedEtherAmount(getLockedEtherAmount() + _amountOfETH); _setLastFinalizedRequestId(_lastRequestIdToBeFinalized); emit WithdrawalsFinalized( firstRequestIdToFinalize, _lastRequestIdToBeFinalized, _amountOfETH, requestToFinalize.cumulativeShares - lastFinalizedRequest.cumulativeShares, block.timestamp ); } /// @dev creates a new `WithdrawalRequest` in the queue /// Emits WithdrawalRequested event function _enqueue(uint128 _amountOfStETH, uint128 _amountOfShares, address _owner) internal returns (uint256 requestId) { uint256 lastRequestId = getLastRequestId(); WithdrawalRequest memory lastRequest = _getQueue()[lastRequestId]; uint128 cumulativeShares = lastRequest.cumulativeShares + _amountOfShares; uint128 cumulativeStETH = lastRequest.cumulativeStETH + _amountOfStETH; requestId = lastRequestId + 1; _setLastRequestId(requestId); WithdrawalRequest memory newRequest = WithdrawalRequest( cumulativeStETH, cumulativeShares, _owner, uint40(block.timestamp), false, uint40(_getLastReportTimestamp()) ); _getQueue()[requestId] = newRequest; assert(_getRequestsByOwner()[_owner].add(requestId)); emit WithdrawalRequested(requestId, msg.sender, _owner, _amountOfStETH, _amountOfShares); } /// @dev Returns the status of the withdrawal request with `_requestId` id function _getStatus(uint256 _requestId) internal view returns (WithdrawalRequestStatus memory status) { if (_requestId == 0 || _requestId > getLastRequestId()) revert InvalidRequestId(_requestId); WithdrawalRequest memory request = _getQueue()[_requestId]; WithdrawalRequest memory previousRequest = _getQueue()[_requestId - 1]; status = WithdrawalRequestStatus( request.cumulativeStETH - previousRequest.cumulativeStETH, request.cumulativeShares - previousRequest.cumulativeShares, request.owner, request.timestamp, _requestId <= getLastFinalizedRequestId(), request.claimed ); } /// @dev View function to find a checkpoint hint to use in `claimWithdrawal()` and `getClaimableEther()` /// Search will be performed in the range of `[_firstIndex, _lastIndex]` /// /// @param _requestId request id to search the checkpoint for /// @param _start index of the left boundary of the search range, should be greater than 0 /// @param _end index of the right boundary of the search range, should be less than or equal /// to `getLastCheckpointIndex()` /// /// @return hint for later use in other methods or 0 if hint not found in the range function _findCheckpointHint(uint256 _requestId, uint256 _start, uint256 _end) internal view returns (uint256) { if (_requestId == 0 || _requestId > getLastRequestId()) revert InvalidRequestId(_requestId); uint256 lastCheckpointIndex = getLastCheckpointIndex(); if (_start == 0 || _end > lastCheckpointIndex) revert InvalidRequestIdRange(_start, _end); if (lastCheckpointIndex == 0 || _requestId > getLastFinalizedRequestId() || _start > _end) return NOT_FOUND; // Right boundary if (_requestId >= _getCheckpoints()[_end].fromRequestId) { // it's the last checkpoint, so it's valid if (_end == lastCheckpointIndex) return _end; // it fits right before the next checkpoint if (_requestId < _getCheckpoints()[_end + 1].fromRequestId) return _end; return NOT_FOUND; } // Left boundary if (_requestId < _getCheckpoints()[_start].fromRequestId) { return NOT_FOUND; } // Binary search uint256 min = _start; uint256 max = _end - 1; while (max > min) { uint256 mid = (max + min + 1) / 2; if (_getCheckpoints()[mid].fromRequestId <= _requestId) { min = mid; } else { max = mid - 1; } } return min; } /// @dev Claim the request and transfer locked ether to `_recipient`. /// Emits WithdrawalClaimed event /// @param _requestId id of the request to claim /// @param _hint hint the checkpoint to use. Can be obtained by calling `findCheckpointHint()` /// @param _recipient address to send ether to function _claim(uint256 _requestId, uint256 _hint, address _recipient) internal { if (_requestId == 0) revert InvalidRequestId(_requestId); if (_requestId > getLastFinalizedRequestId()) revert RequestNotFoundOrNotFinalized(_requestId); WithdrawalRequest storage request = _getQueue()[_requestId]; if (request.claimed) revert RequestAlreadyClaimed(_requestId); if (request.owner != msg.sender) revert NotOwner(msg.sender, request.owner); request.claimed = true; assert(_getRequestsByOwner()[request.owner].remove(_requestId)); uint256 ethWithDiscount = _calculateClaimableEther(request, _requestId, _hint); // because of the stETH rounding issue // (issue: https://github.com/lidofinance/lido-dao/issues/442 ) // some dust (1-2 wei per request) will be accumulated upon claiming _setLockedEtherAmount(getLockedEtherAmount() - ethWithDiscount); _sendValue(_recipient, ethWithDiscount); emit WithdrawalClaimed(_requestId, msg.sender, _recipient, ethWithDiscount); } /// @dev Calculates ether value for the request using the provided hint. Checks if hint is valid /// @return claimableEther discounted eth for `_requestId` function _calculateClaimableEther(WithdrawalRequest storage _request, uint256 _requestId, uint256 _hint) internal view returns (uint256 claimableEther) { if (_hint == 0) revert InvalidHint(_hint); uint256 lastCheckpointIndex = getLastCheckpointIndex(); if (_hint > lastCheckpointIndex) revert InvalidHint(_hint); Checkpoint memory checkpoint = _getCheckpoints()[_hint]; // Reverts if requestId is not in range [checkpoint[hint], checkpoint[hint+1]) // ______(>______ // ^ hint if (_requestId < checkpoint.fromRequestId) revert InvalidHint(_hint); if (_hint < lastCheckpointIndex) { // ______(>______(>________ // hint hint+1 ^ Checkpoint memory nextCheckpoint = _getCheckpoints()[_hint + 1]; if (nextCheckpoint.fromRequestId <= _requestId) revert InvalidHint(_hint); } WithdrawalRequest memory prevRequest = _getQueue()[_requestId - 1]; (uint256 batchShareRate, uint256 eth, uint256 shares) = _calcBatch(prevRequest, _request); if (batchShareRate > checkpoint.maxShareRate) { eth = shares * checkpoint.maxShareRate / E27_PRECISION_BASE; } return eth; } /// @dev quazi-constructor function _initializeQueue() internal { // setting dummy zero structs in checkpoints and queue beginning // to avoid uint underflows and related if-branches // 0-index is reserved as 'not_found' response in the interface everywhere _getQueue()[0] = WithdrawalRequest(0, 0, address(0), uint40(block.timestamp), true, 0); _getCheckpoints()[getLastCheckpointIndex()] = Checkpoint(0, 0); } function _sendValue(address _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(); } /// @dev calculate batch stats (shareRate, stETH and shares) for the range of `(_preStartRequest, _endRequest]` function _calcBatch(WithdrawalRequest memory _preStartRequest, WithdrawalRequest memory _endRequest) internal pure returns (uint256 shareRate, uint256 stETH, uint256 shares) { stETH = _endRequest.cumulativeStETH - _preStartRequest.cumulativeStETH; shares = _endRequest.cumulativeShares - _preStartRequest.cumulativeShares; shareRate = stETH * E27_PRECISION_BASE / shares; } // // Internal getters and setters for unstructured storage // function _getQueue() internal pure returns (mapping(uint256 => WithdrawalRequest) storage queue) { bytes32 position = QUEUE_POSITION; assembly { queue.slot := position } } function _getCheckpoints() internal pure returns (mapping(uint256 => Checkpoint) storage checkpoints) { bytes32 position = CHECKPOINTS_POSITION; assembly { checkpoints.slot := position } } function _getRequestsByOwner() internal pure returns (mapping(address => EnumerableSet.UintSet) storage requestsByOwner) { bytes32 position = REQUEST_BY_OWNER_POSITION; assembly { requestsByOwner.slot := position } } function _getLastReportTimestamp() internal view returns (uint256) { return LAST_REPORT_TIMESTAMP_POSITION.getStorageUint256(); } function _setLastRequestId(uint256 _lastRequestId) internal { LAST_REQUEST_ID_POSITION.setStorageUint256(_lastRequestId); } function _setLastFinalizedRequestId(uint256 _lastFinalizedRequestId) internal { LAST_FINALIZED_REQUEST_ID_POSITION.setStorageUint256(_lastFinalizedRequestId); } function _setLastCheckpointIndex(uint256 _lastCheckpointIndex) internal { LAST_CHECKPOINT_INDEX_POSITION.setStorageUint256(_lastCheckpointIndex); } function _setLockedEtherAmount(uint256 _lockedEtherAmount) internal { LOCKED_ETHER_AMOUNT_POSITION.setStorageUint256(_lockedEtherAmount); } function _setLastReportTimestamp(uint256 _lastReportTimestamp) internal { LAST_REPORT_TIMESTAMP_POSITION.setStorageUint256(_lastReportTimestamp); } }
// SPDX-FileCopyrightText: 2023 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line pragma solidity >=0.4.24 <0.9.0; interface IBurner { function REQUEST_BURN_MY_STETH_ROLE() external view returns (bytes32); function REQUEST_BURN_SHARES_ROLE() external view returns (bytes32); /** * Commit cover/non-cover burning requests and logs cover/non-cover shares amount just burnt. * * NB: The real burn enactment to be invoked after the call (via internal Lido._burnShares()) */ function commitSharesToBurn(uint256 _stETHSharesToBurn) external; /** * Request burn shares */ function requestBurnShares(address _from, uint256 _sharesAmount) external; function requestBurnMyShares(uint256 _sharesAmountToBurn) external; /** * Returns the current amount of shares locked on the contract to be burnt. */ function getSharesRequestedToBurn() external view returns (uint256 coverShares, uint256 nonCoverShares); /** * Returns the total cover shares ever burnt. */ function getCoverSharesBurnt() external view returns (uint256); /** * Returns the total non-cover shares ever burnt. */ function getNonCoverSharesBurnt() external view returns (uint256); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line lido/fixed-compiler-version pragma solidity >=0.8.0; import {IERC20} from "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; import {IVersioned} from "contracts/common/interfaces/IVersioned.sol"; interface ILido is IERC20, IVersioned { function sharesOf(address) external view returns (uint256); function getSharesByPooledEth(uint256) external view returns (uint256); function getPooledEthByShares(uint256) external view returns (uint256); function getPooledEthBySharesRoundUp(uint256) external view returns (uint256); function transferSharesFrom(address, address, uint256) external returns (uint256); function transferShares(address, uint256) external returns (uint256); function rebalanceExternalEtherToInternal() external payable; function getTotalPooledEther() external view returns (uint256); function getExternalEther() external view returns (uint256); function getExternalShares() external view returns (uint256); function mintExternalShares(address, uint256) external; function burnExternalShares(uint256) external; function getTotalShares() external view returns (uint256); function getBeaconStat() external view returns (uint256 depositedValidators, uint256 beaconValidators, uint256 beaconBalance); function processClStateUpdate( uint256 _reportTimestamp, uint256 _preClValidators, uint256 _reportClValidators, uint256 _reportClBalance ) external; function collectRewardsAndProcessWithdrawals( uint256 _reportTimestamp, uint256 _reportClBalance, uint256 _adjustedPreCLBalance, uint256 _withdrawalsToWithdraw, uint256 _elRewardsToWithdraw, uint256 _lastWithdrawalRequestToFinalize, uint256 _simulatedShareRate, uint256 _etherToLockOnWithdrawalQueue ) external; function emitTokenRebase( uint256 _reportTimestamp, uint256 _timeElapsed, uint256 _preTotalShares, uint256 _preTotalEther, uint256 _postTotalShares, uint256 _postTotalEther, uint256 _postInternalShares, uint256 _postInternalEther, uint256 _sharesMintedAsFees ) external; function mintShares(address _recipient, uint256 _sharesAmount) external; function internalizeExternalBadDebt(uint256 _amountOfShares) external; }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line lido/fixed-compiler-version pragma solidity >=0.4.24 <0.9.0; interface ILidoLocator { function accountingOracle() external view returns(address); function depositSecurityModule() external view returns(address); function elRewardsVault() external view returns(address); function lido() external view returns(address); function oracleReportSanityChecker() external view returns(address); function burner() external view returns(address); function stakingRouter() external view returns(address); function treasury() external view returns(address); function validatorsExitBusOracle() external view returns(address); function withdrawalQueue() external view returns(address); function withdrawalVault() external view returns(address); function postTokenRebaseReceiver() external view returns(address); function oracleDaemonConfig() external view returns(address); function validatorExitDelayVerifier() external view returns (address); function triggerableWithdrawalsGateway() external view returns (address); function accounting() external view returns (address); function predepositGuarantee() external view returns (address); function wstETH() external view returns (address); function vaultHub() external view returns (address); function vaultFactory() external view returns (address); function lazyOracle() external view returns (address); function operatorGrid() external view returns (address); /// @notice Returns core Lido protocol component addresses in a single call /// @dev This function provides a gas-efficient way to fetch multiple component addresses in a single call function coreComponents() external view returns( address elRewardsVault, address oracleReportSanityChecker, address stakingRouter, address treasury, address withdrawalQueue, address withdrawalVault ); /// @notice Returns addresses of components involved in processing oracle reports in the Lido contract /// @dev This function provides a gas-efficient way to fetch multiple component addresses in a single call function oracleReportComponents() external view returns( address accountingOracle, address oracleReportSanityChecker, address burner, address withdrawalQueue, address postTokenRebaseReceiver, address stakingRouter, address vaultHub ); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line lido/fixed-compiler-version pragma solidity >=0.4.24; interface IOracleReportSanityChecker { function smoothenTokenRebase( uint256 _preTotalPooledEther, uint256 _preTotalShares, uint256 _preCLBalance, uint256 _postCLBalance, uint256 _withdrawalVaultBalance, uint256 _elRewardsVaultBalance, uint256 _sharesRequestedToBurn, uint256 _etherToLockForWithdrawals, uint256 _newSharesToBurnForWithdrawals ) external view returns (uint256 withdrawals, uint256 elRewards, uint256 sharesFromWQToBurn, uint256 sharesToBurn); // function checkAccountingOracleReport( uint256 _timeElapsed, uint256 _preCLBalance, uint256 _postCLBalance, uint256 _withdrawalVaultBalance, uint256 _elRewardsVaultBalance, uint256 _sharesRequestedToBurn, uint256 _preCLValidators, uint256 _postCLValidators ) external; // function checkWithdrawalQueueOracleReport( uint256 _lastFinalizableRequestId, uint256 _reportTimestamp ) external view; }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line lido/fixed-compiler-version pragma solidity >=0.5.0; interface IVaultHub { function badDebtToInternalizeAsOfLastRefSlot() external view returns (uint256); function decreaseInternalizedBadDebt(uint256 _amountOfShares) external; }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line pragma solidity >=0.4.24; interface IVersioned { /// @notice Returns the current contract version. function getContractVersion() external view returns (uint256); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line lido/fixed-compiler-version pragma solidity >=0.5.0; struct ReportValues { /// @notice timestamp of the block the report is based on. All provided report values is actual on this timestamp uint256 timestamp; /// @notice seconds elapsed since the previous report uint256 timeElapsed; /// @notice total number of Lido validators on Consensus Layers (exited included) uint256 clValidators; /// @notice sum of all Lido validators' balances on Consensus Layer uint256 clBalance; /// @notice withdrawal vault balance uint256 withdrawalVaultBalance; /// @notice elRewards vault balance uint256 elRewardsVaultBalance; /// @notice stETH shares requested to burn through Burner uint256 sharesRequestedToBurn; /// @notice the ascendingly-sorted array of withdrawal request IDs obtained by calling /// WithdrawalQueue.calculateFinalizationBatches. Can be empty array if no withdrawal to finalize uint256[] withdrawalFinalizationBatches; }
// SPDX-FileCopyrightText: 2023 Lido <[email protected]> // SPDX-License-Identifier: MIT // Copied from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/0457042d93d9dfd760dbaa06a4d2f1216fdbe297/contracts/utils/math/Math.sol // See contracts/COMPILERS.md // solhint-disable-next-line pragma solidity >=0.4.24 <0.9.0; library Math256 { /// @dev Returns the largest of two numbers. function max(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a : b; } /// @dev Returns the smallest of two numbers. function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } /// @dev Returns the largest of two numbers. function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; } /// @dev Returns the smallest of two numbers. function min(int256 a, int256 b) internal pure returns (int256) { return a < b ? a : b; } /// @dev Returns the ceiling of the division of two numbers. /// /// This differs from standard division with `/` in that it rounds up instead /// of rounding down. function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b - 1) / b can overflow on addition, so we distribute. return a == 0 ? 0 : (a - 1) / b + 1; } /// @dev Returns absolute difference of two numbers. function absDiff(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a - b : b - a; } }
// SPDX-FileCopyrightText: 2023 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 /* See contracts/COMPILERS.md */ // solhint-disable-next-line lido/fixed-compiler-version pragma solidity >=0.4.24 <0.9.0; library MemUtils { /** * @dev Allocates a memory byte array of `_len` bytes without zeroing it out. */ function unsafeAllocateBytes(uint256 _len) internal pure returns (bytes memory result) { assembly { result := mload(0x40) mstore(result, _len) let freeMemPtr := add(add(result, 32), _len) // align free mem ptr to 32 bytes as the compiler does now mstore(0x40, and(add(freeMemPtr, 31), not(31))) } } /** * Performs a memory copy of `_len` bytes from position `_src` to position `_dst`. */ function memcpy(uint256 _src, uint256 _dst, uint256 _len) internal pure { assembly { // while al least 32 bytes left, copy in 32-byte chunks for { } gt(_len, 31) { } { mstore(_dst, mload(_src)) _src := add(_src, 32) _dst := add(_dst, 32) _len := sub(_len, 32) } if gt(_len, 0) { // read the next 32-byte chunk from _dst, replace the first N bytes // with those left in the _src, and write the transformed chunk back let mask := sub(shl(mul(8, sub(32, _len)), 1), 1) // 2 ** (8 * (32 - _len)) - 1 let srcMasked := and(mload(_src), not(mask)) let dstMasked := and(mload(_dst), mask) mstore(_dst, or(dstMasked, srcMasked)) } } } /** * Copies `_len` bytes from `_src`, starting at position `_srcStart`, into `_dst`, starting at position `_dstStart` into `_dst`. */ function copyBytes(bytes memory _src, bytes memory _dst, uint256 _srcStart, uint256 _dstStart, uint256 _len) internal pure { require(_srcStart + _len <= _src.length && _dstStart + _len <= _dst.length, "BYTES_ARRAY_OUT_OF_BOUNDS"); uint256 srcStartPos; uint256 dstStartPos; assembly { srcStartPos := add(add(_src, 32), _srcStart) dstStartPos := add(add(_dst, 32), _dstStart) } memcpy(srcStartPos, dstStartPos, _len); } /** * Copies bytes from `_src` to `_dst`, starting at position `_dstStart` into `_dst`. */ function copyBytes(bytes memory _src, bytes memory _dst, uint256 _dstStart) internal pure { copyBytes(_src, _dst, 0, _dstStart, _src.length); } }
// SPDX-FileCopyrightText: 2023 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 /* See contracts/COMPILERS.md */ // solhint-disable-next-line pragma solidity >=0.4.24 <0.9.0; import {Math256} from "./Math256.sol"; /// @notice Library with methods to calculate "proportional" allocations among buckets with different /// capacity and level of filling. /// @dev The current implementation favors buckets with the least fill factor library MinFirstAllocationStrategy { uint256 private constant MAX_UINT256 = 2**256 - 1; /// @notice Allocates passed maxAllocationSize among the buckets. The resulting allocation doesn't exceed the /// capacities of the buckets. An algorithm starts filling from the least populated buckets to equalize the fill factor. /// For example, for buckets: [9998, 70, 0], capacities: [10000, 101, 100], and maxAllocationSize: 101, the allocation happens /// following way: /// 1. top up the bucket with index 2 on 70. Intermediate state of the buckets: [9998, 70, 70]. According to the definition, /// the rest allocation must be proportionally split among the buckets with the same values. /// 2. top up the bucket with index 1 on 15. Intermediate state of the buckets: [9998, 85, 70]. /// 3. top up the bucket with index 2 on 15. Intermediate state of the buckets: [9998, 85, 85]. /// 4. top up the bucket with index 1 on 1. Nothing to distribute. The final state of the buckets: [9998, 86, 85] /// @dev Method modifies the passed buckets array to reduce the gas costs on memory allocation. /// @param buckets The array of current allocations in the buckets /// @param capacities The array of capacities of the buckets /// @param allocationSize The desired value to allocate among the buckets /// @return allocated The total value allocated among the buckets. Can't exceed the allocationSize value function allocate( uint256[] memory buckets, uint256[] memory capacities, uint256 allocationSize ) public pure returns (uint256 allocated, uint256[] memory) { uint256 allocatedToBestCandidate = 0; while (allocated < allocationSize) { allocatedToBestCandidate = allocateToBestCandidate(buckets, capacities, allocationSize - allocated); if (allocatedToBestCandidate == 0) { break; } allocated += allocatedToBestCandidate; } return (allocated, buckets); } /// @notice Allocates the max allowed value not exceeding allocationSize to the bucket with the least value. /// The candidate search happens according to the following algorithm: /// 1. Find the first least filled bucket which has free space. Count the number of such buckets. /// 2. If no buckets are found terminate the search - no free buckets /// 3. Find the first bucket with free space, which has the least value greater /// than the bucket found in step 1. To preserve proportional allocation the resulting allocation can't exceed this value. /// 4. Calculate the allocation size as: /// min( /// (count of least filling buckets > 1 ? ceilDiv(allocationSize, count of least filling buckets) : allocationSize), /// fill factor of the bucket found in step 3, /// free space of the least filled bucket /// ) /// @dev Method modifies the passed buckets array to reduce the gas costs on memory allocation. /// @param buckets The array of current allocations in the buckets /// @param capacities The array of capacities of the buckets /// @param allocationSize The desired value to allocate to the bucket /// @return allocated The total value allocated to the bucket. Can't exceed the allocationSize value function allocateToBestCandidate( uint256[] memory buckets, uint256[] memory capacities, uint256 allocationSize ) internal pure returns (uint256 allocated) { uint256 bestCandidateIndex = buckets.length; uint256 bestCandidateAllocation = MAX_UINT256; uint256 bestCandidatesCount = 0; if (allocationSize == 0) { return 0; } for (uint256 i = 0; i < buckets.length; ++i) { if (buckets[i] >= capacities[i]) { continue; } else if (bestCandidateAllocation > buckets[i]) { bestCandidateIndex = i; bestCandidatesCount = 1; bestCandidateAllocation = buckets[i]; } else if (bestCandidateAllocation == buckets[i]) { bestCandidatesCount += 1; } } if (bestCandidatesCount == 0) { return 0; } // cap the allocation by the smallest larger allocation than the found best one uint256 allocationSizeUpperBound = MAX_UINT256; for (uint256 j = 0; j < buckets.length; ++j) { if (buckets[j] >= capacities[j]) { continue; } else if (buckets[j] > bestCandidateAllocation && buckets[j] < allocationSizeUpperBound) { allocationSizeUpperBound = buckets[j]; } } allocated = Math256.min( bestCandidatesCount > 1 ? Math256.ceilDiv(allocationSize, bestCandidatesCount) : allocationSize, Math256.min(allocationSizeUpperBound, capacities[bestCandidateIndex]) - bestCandidateAllocation ); buckets[bestCandidateIndex] += allocated; } }
{
"optimizer": {
"enabled": true,
"runs": 200
},
"evmVersion": "istanbul",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"libraries": {}
}Contract ABI
API[{"inputs":[{"internalType":"contract ILidoLocator","name":"_lidoLocator","type":"address"},{"internalType":"contract ILido","name":"_lido","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"reportTimestamp","type":"uint256"},{"internalType":"uint256","name":"upperBoundTimestamp","type":"uint256"}],"name":"IncorrectReportTimestamp","type":"error"},{"inputs":[{"internalType":"uint256","name":"reportValidators","type":"uint256"},{"internalType":"uint256","name":"minValidators","type":"uint256"},{"internalType":"uint256","name":"maxValidators","type":"uint256"}],"name":"IncorrectReportValidators","type":"error"},{"inputs":[{"internalType":"string","name":"operation","type":"string"},{"internalType":"address","name":"addr","type":"address"}],"name":"NotAuthorized","type":"error"},{"inputs":[{"internalType":"uint256","name":"firstArrayLength","type":"uint256"},{"internalType":"uint256","name":"secondArrayLength","type":"uint256"}],"name":"UnequalArrayLengths","type":"error"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"contract ILido","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIDO_LOCATOR","outputs":[{"internalType":"contract ILidoLocator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"uint256","name":"timeElapsed","type":"uint256"},{"internalType":"uint256","name":"clValidators","type":"uint256"},{"internalType":"uint256","name":"clBalance","type":"uint256"},{"internalType":"uint256","name":"withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"sharesRequestedToBurn","type":"uint256"},{"internalType":"uint256[]","name":"withdrawalFinalizationBatches","type":"uint256[]"}],"internalType":"struct ReportValues","name":"_report","type":"tuple"}],"name":"handleOracleReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"uint256","name":"timeElapsed","type":"uint256"},{"internalType":"uint256","name":"clValidators","type":"uint256"},{"internalType":"uint256","name":"clBalance","type":"uint256"},{"internalType":"uint256","name":"withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"sharesRequestedToBurn","type":"uint256"},{"internalType":"uint256[]","name":"withdrawalFinalizationBatches","type":"uint256[]"}],"internalType":"struct ReportValues","name":"_report","type":"tuple"},{"internalType":"uint256","name":"_withdrawalShareRate","type":"uint256"}],"name":"simulateOracleReport","outputs":[{"components":[{"internalType":"uint256","name":"withdrawals","type":"uint256"},{"internalType":"uint256","name":"elRewards","type":"uint256"},{"internalType":"uint256","name":"etherToFinalizeWQ","type":"uint256"},{"internalType":"uint256","name":"sharesToFinalizeWQ","type":"uint256"},{"internalType":"uint256","name":"sharesToBurnForWithdrawals","type":"uint256"},{"internalType":"uint256","name":"totalSharesToBurn","type":"uint256"},{"internalType":"uint256","name":"sharesToMintAsFees","type":"uint256"},{"components":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"moduleIds","type":"uint256[]"},{"internalType":"uint96[]","name":"modulesFees","type":"uint96[]"},{"internalType":"uint96","name":"totalFee","type":"uint96"},{"internalType":"uint256","name":"precisionPoints","type":"uint256"}],"internalType":"struct Accounting.StakingRewardsDistribution","name":"rewardDistribution","type":"tuple"},{"internalType":"uint256","name":"principalClBalance","type":"uint256"},{"internalType":"uint256","name":"preTotalShares","type":"uint256"},{"internalType":"uint256","name":"preTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"postInternalShares","type":"uint256"},{"internalType":"uint256","name":"postInternalEther","type":"uint256"},{"internalType":"uint256","name":"postTotalShares","type":"uint256"},{"internalType":"uint256","name":"postTotalPooledEther","type":"uint256"}],"internalType":"struct Accounting.CalculatedValues","name":"update","type":"tuple"}],"stateMutability":"view","type":"function"}]Contract Creation Code
60c06040523480156200001157600080fd5b506040516200218138038062002181833981016040819052620000349162000065565b6001600160a01b039182166080521660a052620000a4565b6001600160a01b03811681146200006257600080fd5b50565b600080604083850312156200007957600080fd5b825162000086816200004c565b602084015190925062000099816200004c565b809150509250929050565b60805160a05161205e62000123600039600081816094015281816102ef015281816103ac01528181610431015281816104cb0152818161056501528181610a9601528181610b8a01528181610ce301528181610dd40152818161157a015261170b01526000818160d3015281816102030152611653015261205e6000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806302aea763146100515780634e9b09251461007a5780638b21f1701461008f578063dbba4b48146100ce575b600080fd5b61006461005f36600461187a565b6100f5565b60405161007191906119de565b60405180910390f35b61008d610088366004611aa5565b61012b565b005b6100b67f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610071565b6100b67f000000000000000000000000000000000000000000000000000000000000000081565b6100fd61176d565b60006101076101bd565b90506000610114826102e5565b90506101228282878761067c565b95945050505050565b60006101356101bd565b80519091506001600160a01b0316331461019457604080516373e3652560e01b8152600481019190915260126044820152711a185b991b1953dc9858db1954995c1bdc9d60721b60648201523360248201526084015b60405180910390fd5b60008060006101a38486610925565b9250925092506101b68486858585610998565b5050505050565b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915260008060008060008060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b2ad11046040518163ffffffff1660e01b815260040160e06040518083038186803b15801561025a57600080fd5b505afa15801561026e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102929190611af9565b6040805160e0810182526001600160a01b039889168152968816602088015294871694860194909452918516606085015284166080840152831660a083015290911660c082015298975050505050505050565b6102ed61181c565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ae2e35386040518163ffffffff1660e01b815260040160606040518083038186803b15801561034657600080fd5b505afa15801561035a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061037e9190611b7f565b602080850191909152908352608083019190915260408051631be7ed6560e11b815290516001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016926337cfdaca9260048082019391829003018186803b1580156103ee57600080fd5b505afa158015610402573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104269190611bad565b8160400181815250507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d5002f2e6040518163ffffffff1660e01b815260040160206040518083038186803b15801561048857600080fd5b505afa15801561049c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104c09190611bad565b8160600181815250507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166363021d8b6040518163ffffffff1660e01b815260040160206040518083038186803b15801561052257600080fd5b505afa158015610536573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061055a9190611bad565b8160a00181815250507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663e16a90656040518163ffffffff1660e01b815260040160206040518083038186803b1580156105bc57600080fd5b505afa1580156105d0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105f49190611bad565b8160c00181815250508160c001516001600160a01b0316632809b50c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561063a57600080fd5b505afa15801561064e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106729190611bad565b60e0820152919050565b61068461176d565b6060840151610120820152604084015161014082015260a08501516106a890610e39565b60e082015281156106c9576106be858484610f85565b606083015260408201525b83516801bc16d674ec800000906106e4906040860135611bdc565b6106ee9190611bf3565b84602001516106fd9190611c12565b610100820181905260208601516040868101516060808901518387015182880151945163b8498a3960e01b815260048101949094526024840191909152604483019590955287013560648201526080870135608482015260a087013560a482015260c087013560c482015260e48101939093526101048301526001600160a01b03169063b8498a39906101240160806040518083038186803b1580156107a257600080fd5b505afa1580156107b6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107da9190611c2a565b60a0808601829052608086019290925260208501929092529183529085015160608601516000929161080b91611bdc565b6108159190611bdc565b905081604001518260200151836101000151846000015187606001358960c001518a604001516108459190611bdc565b61084f9190611c12565b6108599190611c12565b6108639190611bdc565b61086d9190611c12565b6108779190611bdc565b610180830181905261088e908590849084906110b7565b60c0830181905260e0860151906108a59083611c12565b6108af9190611c12565b61016083015260e085015160a08601516000916108cb91611bdc565b9050808361016001516108de9190611c12565b6101a08401526101608301516101808401516108fa9083611bf3565b6109049190611c60565b8361018001516109149190611c12565b6101c0840152509095945050505050565b61092d61181c565b61093561176d565b6000610940856102e5565b92506000610951868587600061067c565b9050806101a00151816101c001516b033b2e3c9fd0803ce80000006109769190611bf3565b6109809190611c60565b915061098e8685878561067c565b9250509250925092565b6109a485858585611161565b606082015160009015610a63576040808701516060808901519086015192516308c2292560e31b81526001600160a01b03918216600482015260248101939093521690634611492890604401600060405180830381600087803b158015610a0a57600080fd5b505af1158015610a1e573d6000803e3d6000fd5b50610a309250505060e0860186611c82565b6001610a3f60e0890189611c82565b610a4a929150611bdc565b818110610a5957610a59611cd3565b9050602002013590505b83516040805163603540cd60e01b81528735600482015260248101929092528601356044820152606086013560648201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063603540cd90608401600060405180830381600087803b158015610ae257600080fd5b505af1158015610af6573d6000803e3d6000fd5b5050505060e084015115610bf55760c086015160e0850151604051633c3c36b760e21b81526001600160a01b039092169163f0f0dadc91610b3d9160040190815260200190565b600060405180830381600087803b158015610b5757600080fd5b505af1158015610b6b573d6000803e3d6000fd5b50505060e085015160405163648c51e760e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016925063648c51e791610bc29160040190815260200190565b600060405180830381600087803b158015610bdc57600080fd5b505af1158015610bf0573d6000803e3d6000fd5b505050505b60a083015115610c685785604001516001600160a01b031663636e6b668460a001516040518263ffffffff1660e01b8152600401610c3591815260200190565b600060405180830381600087803b158015610c4f57600080fd5b505af1158015610c63573d6000803e3d6000fd5b505050505b60c083015115610c8957610c898660a001518460e001518560c0015161134c565b610100830151835160208501516040808701519051634938f1f360e11b81528935600482015260608a0135602482015260448101949094526064840192909252608483015260a4820183905260c4820184905260e48201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690639271e3e69061010401600060405180830381600087803b158015610d3057600080fd5b505af1158015610d44573d6000803e3d6000fd5b50505050610d5886608001518686866113f2565b60608401516040808601516101a08601516101c087015161016088015161018089015160c08a01519551631b25009760e01b81528c35600482015260208d0135602482015260448101979097526064870194909452608486019290925260a485015260c484015260e48301526101048201526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690631b2500979061012401600060405180830381600087803b158015610e1957600080fd5b505af1158015610e2d573d6000803e3d6000fd5b50505050505050505050565b610e746040518060a0016040528060608152602001606081526020016060815260200160006001600160601b03168152602001600081525090565b816001600160a01b031663ba21ccae6040518163ffffffff1660e01b815260040160006040518083038186803b158015610ead57600080fd5b505afa158015610ec1573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610ee99190810190611e38565b60808601526001600160601b0316606085015260408401819052602084019190915281835251905114610f415780515160408083015151905163df9ccef560e01b81526004810192909252602482015260440161018b565b80604001515181602001515114610f805760208101515160408083015151905163df9ccef560e01b81526004810192909252602482015260440161018b565b919050565b600080610f9560e0850185611c82565b1580159150611016575084606001516001600160a01b031663b187bd266040518163ffffffff1660e01b815260040160206040518083038186803b158015610fdc57600080fd5b505afa158015610ff0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110149190611f3a565b155b156110af5760608501516001600160a01b031663a52e9c9f61103b60e0870187611c82565b866040518463ffffffff1660e01b815260040161105a93929190611f63565b604080518083038186803b15801561107157600080fd5b505afa158015611085573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110a99190611fa9565b90925090505b935093915050565b825160009081906110cc906060880135611c12565b90508461010001518111156111585760008560200151866101000151836110f39190611bdc565b6110fd9190611c12565b60e087015160608101516080909101519192506001600160601b0316906000816111278486611bf3565b6111319190611c60565b905061113d8188611bdc565b6111478983611bf3565b6111519190611c60565b9550505050505b50949350505050565b4283351061118b576040516356def4b360e11b81528335600482015242602482015260440161018b565b8151604084013510806111a5575081608001518360400135115b156111dd578151608083015160408051631556536560e01b81529086013560048201526024810192909252604482015260640161018b565b602080850151610100830151845160408051638024cca160e01b81529488013560048601526024850192909252606087013560448501526080870135606485015260a0870135608485015260c087013560a485015260c484015285013560e48301526001600160a01b031690638024cca19061010401600060405180830381600087803b15801561126d57600080fd5b505af1158015611281573d6000803e3d6000fd5b506000925061129691505060e0850185611c82565b905011156113465760208401516001600160a01b0316636a84f2fd6112be60e0860186611c82565b60016112cd60e0890189611c82565b6112d8929150611bdc565b8181106112e7576112e7611cd3565b6040516001600160e01b031960e086901b168152602090910292909201356004830152508535602482015260440160006040518083038186803b15801561132d57600080fd5b505afa158015611341573d6000803e3d6000fd5b505050505b50505050565b6000806113708460000151856040015186606001516001600160601b03168661149b565b90925090506113876113828285611bdc565b61164f565b602084015160405163af12409760e01b81526001600160a01b0387169163af124097916113b991908690600401611fcd565b600060405180830381600087803b1580156113d357600080fd5b505af11580156113e7573d6000803e3d6000fd5b505050505050505050565b6001600160a01b038416156113465760608201516040808401516101a08401516101c085015160c086015193516302244dbb60e61b8152883560048201526020890135602482015260448101959095526064850192909252608484015260a483015260c48201526001600160a01b038516906389136ec09060e401600060405180830381600087803b15801561148757600080fd5b505af1158015611341573d6000803e3d6000fd5b60606000855167ffffffffffffffff8111156114b9576114b9611ce9565b6040519080825280602002602001820160405280156114e2578160200160208202803683370190505b50915060005b865181101561164557600086828151811061150557611505611cd3565b60200260200101516001600160601b031611156116355760008587838151811061153157611531611cd3565b60200260200101516001600160601b03168661154d9190611bf3565b6115579190611c60565b90508084838151811061156c5761156c611cd3565b6020026020010181815250507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663528c198a8984815181106115b9576115b9611cd3565b6020026020010151836040518363ffffffff1660e01b81526004016115f39291906001600160a01b03929092168252602082015260400190565b600060405180830381600087803b15801561160d57600080fd5b505af1158015611621573d6000803e3d6000fd5b5050505080836116319190611c12565b9250505b61163e81611ff2565b90506114e8565b5094509492505050565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166361d027b36040518163ffffffff1660e01b815260040160206040518083038186803b1580156116aa57600080fd5b505afa1580156116be573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116e2919061200d565b6040516329460cc560e11b81526001600160a01b038083166004830152602482018590529192507f00000000000000000000000000000000000000000000000000000000000000009091169063528c198a90604401600060405180830381600087803b15801561175157600080fd5b505af1158015611765573d6000803e3d6000fd5b505050505050565b604051806101e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016117e56040518060a0016040528060608152602001606081526020016060815260200160006001600160601b03168152602001600081525090565b8152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b60405180610100016040528060008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6000610100828403121561187457600080fd5b50919050565b6000806040838503121561188d57600080fd5b823567ffffffffffffffff8111156118a457600080fd5b6118b085828601611861565b95602094909401359450505050565b600081518084526020808501945080840160005b838110156118ef578151875295820195908201906001016118d3565b509495945050505050565b600081518084526020808501945080840160005b838110156118ef5781516001600160601b03168752958201959082019060010161190e565b805160a080845281519084018190526000916020919082019060c0860190845b818110156119785783516001600160a01b031683529284019291840191600101611953565b50508285015191508581038387015261199181836118bf565b92505050604083015184820360408601526119ac82826118fa565b91505060608301516119c960608601826001600160601b03169052565b50608083015160808501528091505092915050565b6020815281516020820152602082015160408201526040820151606082015260608201516080820152608082015160a082015260a082015160c082015260c082015160e0820152600060e08301516101e06101008181860152611a45610200860184611933565b90860151610120868101919091528601516101408087019190915286015161016080870191909152860151610180808701919091528601516101a0808701919091528601516101c080870191909152909501519301929092525090919050565b600060208284031215611ab757600080fd5b813567ffffffffffffffff811115611ace57600080fd5b611ada84828501611861565b949350505050565b80516001600160a01b0381168114610f8057600080fd5b600080600080600080600060e0888a031215611b1457600080fd5b611b1d88611ae2565b9650611b2b60208901611ae2565b9550611b3960408901611ae2565b9450611b4760608901611ae2565b9350611b5560808901611ae2565b9250611b6360a08901611ae2565b9150611b7160c08901611ae2565b905092959891949750929550565b600080600060608486031215611b9457600080fd5b8351925060208401519150604084015190509250925092565b600060208284031215611bbf57600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b600082821015611bee57611bee611bc6565b500390565b6000816000190483118215151615611c0d57611c0d611bc6565b500290565b60008219821115611c2557611c25611bc6565b500190565b60008060008060808587031215611c4057600080fd5b505082516020840151604085015160609095015191969095509092509050565b600082611c7d57634e487b7160e01b600052601260045260246000fd5b500490565b6000808335601e19843603018112611c9957600080fd5b83018035915067ffffffffffffffff821115611cb457600080fd5b6020019150600581901b3603821315611ccc57600080fd5b9250929050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff81118282101715611d2857611d28611ce9565b604052919050565b600067ffffffffffffffff821115611d4a57611d4a611ce9565b5060051b60200190565b600082601f830112611d6557600080fd5b81516020611d7a611d7583611d30565b611cff565b82815260059290921b84018101918181019086841115611d9957600080fd5b8286015b84811015611db45780518352918301918301611d9d565b509695505050505050565b80516001600160601b0381168114610f8057600080fd5b600082601f830112611de757600080fd5b81516020611df7611d7583611d30565b82815260059290921b84018101918181019086841115611e1657600080fd5b8286015b84811015611db457611e2b81611dbf565b8352918301918301611e1a565b600080600080600060a08688031215611e5057600080fd5b855167ffffffffffffffff80821115611e6857600080fd5b818801915088601f830112611e7c57600080fd5b81516020611e8c611d7583611d30565b82815260059290921b8401810191818101908c841115611eab57600080fd5b948201945b83861015611ed057611ec186611ae2565b82529482019490820190611eb0565b918b0151919950909350505080821115611ee957600080fd5b611ef589838a01611d54565b95506040880151915080821115611f0b57600080fd5b50611f1888828901611dd6565b935050611f2760608701611dbf565b9150608086015190509295509295909350565b600060208284031215611f4c57600080fd5b81518015158114611f5c57600080fd5b9392505050565b6040808252810183905260006001600160fb1b03841115611f8357600080fd5b8360051b8086606085013760009083016060019081526020909201929092529392505050565b60008060408385031215611fbc57600080fd5b505080516020909101519092909150565b604081526000611fe060408301856118bf565b828103602084015261012281856118bf565b600060001982141561200657612006611bc6565b5060010190565b60006020828403121561201f57600080fd5b611f5c82611ae256fea26469706673582212207089622b6f23f083befced13b3571dc3f3ed7c6ac6735d7818faa36a0583b5bf64736f6c63430008090033000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea2919800000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba1
Deployed Bytecode
0x608060405234801561001057600080fd5b506004361061004c5760003560e01c806302aea763146100515780634e9b09251461007a5780638b21f1701461008f578063dbba4b48146100ce575b600080fd5b61006461005f36600461187a565b6100f5565b60405161007191906119de565b60405180910390f35b61008d610088366004611aa5565b61012b565b005b6100b67f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba181565b6040516001600160a01b039091168152602001610071565b6100b67f000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea29198081565b6100fd61176d565b60006101076101bd565b90506000610114826102e5565b90506101228282878761067c565b95945050505050565b60006101356101bd565b80519091506001600160a01b0316331461019457604080516373e3652560e01b8152600481019190915260126044820152711a185b991b1953dc9858db1954995c1bdc9d60721b60648201523360248201526084015b60405180910390fd5b60008060006101a38486610925565b9250925092506101b68486858585610998565b5050505050565b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915260008060008060008060007f000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea2919806001600160a01b031663b2ad11046040518163ffffffff1660e01b815260040160e06040518083038186803b15801561025a57600080fd5b505afa15801561026e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102929190611af9565b6040805160e0810182526001600160a01b039889168152968816602088015294871694860194909452918516606085015284166080840152831660a083015290911660c082015298975050505050505050565b6102ed61181c565b7f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba16001600160a01b031663ae2e35386040518163ffffffff1660e01b815260040160606040518083038186803b15801561034657600080fd5b505afa15801561035a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061037e9190611b7f565b602080850191909152908352608083019190915260408051631be7ed6560e11b815290516001600160a01b037f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba116926337cfdaca9260048082019391829003018186803b1580156103ee57600080fd5b505afa158015610402573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104269190611bad565b8160400181815250507f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba16001600160a01b031663d5002f2e6040518163ffffffff1660e01b815260040160206040518083038186803b15801561048857600080fd5b505afa15801561049c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104c09190611bad565b8160600181815250507f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba16001600160a01b03166363021d8b6040518163ffffffff1660e01b815260040160206040518083038186803b15801561052257600080fd5b505afa158015610536573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061055a9190611bad565b8160a00181815250507f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba16001600160a01b031663e16a90656040518163ffffffff1660e01b815260040160206040518083038186803b1580156105bc57600080fd5b505afa1580156105d0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105f49190611bad565b8160c00181815250508160c001516001600160a01b0316632809b50c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561063a57600080fd5b505afa15801561064e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106729190611bad565b60e0820152919050565b61068461176d565b6060840151610120820152604084015161014082015260a08501516106a890610e39565b60e082015281156106c9576106be858484610f85565b606083015260408201525b83516801bc16d674ec800000906106e4906040860135611bdc565b6106ee9190611bf3565b84602001516106fd9190611c12565b610100820181905260208601516040868101516060808901518387015182880151945163b8498a3960e01b815260048101949094526024840191909152604483019590955287013560648201526080870135608482015260a087013560a482015260c087013560c482015260e48101939093526101048301526001600160a01b03169063b8498a39906101240160806040518083038186803b1580156107a257600080fd5b505afa1580156107b6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107da9190611c2a565b60a0808601829052608086019290925260208501929092529183529085015160608601516000929161080b91611bdc565b6108159190611bdc565b905081604001518260200151836101000151846000015187606001358960c001518a604001516108459190611bdc565b61084f9190611c12565b6108599190611c12565b6108639190611bdc565b61086d9190611c12565b6108779190611bdc565b610180830181905261088e908590849084906110b7565b60c0830181905260e0860151906108a59083611c12565b6108af9190611c12565b61016083015260e085015160a08601516000916108cb91611bdc565b9050808361016001516108de9190611c12565b6101a08401526101608301516101808401516108fa9083611bf3565b6109049190611c60565b8361018001516109149190611c12565b6101c0840152509095945050505050565b61092d61181c565b61093561176d565b6000610940856102e5565b92506000610951868587600061067c565b9050806101a00151816101c001516b033b2e3c9fd0803ce80000006109769190611bf3565b6109809190611c60565b915061098e8685878561067c565b9250509250925092565b6109a485858585611161565b606082015160009015610a63576040808701516060808901519086015192516308c2292560e31b81526001600160a01b03918216600482015260248101939093521690634611492890604401600060405180830381600087803b158015610a0a57600080fd5b505af1158015610a1e573d6000803e3d6000fd5b50610a309250505060e0860186611c82565b6001610a3f60e0890189611c82565b610a4a929150611bdc565b818110610a5957610a59611cd3565b9050602002013590505b83516040805163603540cd60e01b81528735600482015260248101929092528601356044820152606086013560648201527f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba16001600160a01b03169063603540cd90608401600060405180830381600087803b158015610ae257600080fd5b505af1158015610af6573d6000803e3d6000fd5b5050505060e084015115610bf55760c086015160e0850151604051633c3c36b760e21b81526001600160a01b039092169163f0f0dadc91610b3d9160040190815260200190565b600060405180830381600087803b158015610b5757600080fd5b505af1158015610b6b573d6000803e3d6000fd5b50505060e085015160405163648c51e760e01b81526001600160a01b037f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba116925063648c51e791610bc29160040190815260200190565b600060405180830381600087803b158015610bdc57600080fd5b505af1158015610bf0573d6000803e3d6000fd5b505050505b60a083015115610c685785604001516001600160a01b031663636e6b668460a001516040518263ffffffff1660e01b8152600401610c3591815260200190565b600060405180830381600087803b158015610c4f57600080fd5b505af1158015610c63573d6000803e3d6000fd5b505050505b60c083015115610c8957610c898660a001518460e001518560c0015161134c565b610100830151835160208501516040808701519051634938f1f360e11b81528935600482015260608a0135602482015260448101949094526064840192909252608483015260a4820183905260c4820184905260e48201527f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba16001600160a01b031690639271e3e69061010401600060405180830381600087803b158015610d3057600080fd5b505af1158015610d44573d6000803e3d6000fd5b50505050610d5886608001518686866113f2565b60608401516040808601516101a08601516101c087015161016088015161018089015160c08a01519551631b25009760e01b81528c35600482015260208d0135602482015260448101979097526064870194909452608486019290925260a485015260c484015260e48301526101048201526001600160a01b037f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba11690631b2500979061012401600060405180830381600087803b158015610e1957600080fd5b505af1158015610e2d573d6000803e3d6000fd5b50505050505050505050565b610e746040518060a0016040528060608152602001606081526020016060815260200160006001600160601b03168152602001600081525090565b816001600160a01b031663ba21ccae6040518163ffffffff1660e01b815260040160006040518083038186803b158015610ead57600080fd5b505afa158015610ec1573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610ee99190810190611e38565b60808601526001600160601b0316606085015260408401819052602084019190915281835251905114610f415780515160408083015151905163df9ccef560e01b81526004810192909252602482015260440161018b565b80604001515181602001515114610f805760208101515160408083015151905163df9ccef560e01b81526004810192909252602482015260440161018b565b919050565b600080610f9560e0850185611c82565b1580159150611016575084606001516001600160a01b031663b187bd266040518163ffffffff1660e01b815260040160206040518083038186803b158015610fdc57600080fd5b505afa158015610ff0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110149190611f3a565b155b156110af5760608501516001600160a01b031663a52e9c9f61103b60e0870187611c82565b866040518463ffffffff1660e01b815260040161105a93929190611f63565b604080518083038186803b15801561107157600080fd5b505afa158015611085573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110a99190611fa9565b90925090505b935093915050565b825160009081906110cc906060880135611c12565b90508461010001518111156111585760008560200151866101000151836110f39190611bdc565b6110fd9190611c12565b60e087015160608101516080909101519192506001600160601b0316906000816111278486611bf3565b6111319190611c60565b905061113d8188611bdc565b6111478983611bf3565b6111519190611c60565b9550505050505b50949350505050565b4283351061118b576040516356def4b360e11b81528335600482015242602482015260440161018b565b8151604084013510806111a5575081608001518360400135115b156111dd578151608083015160408051631556536560e01b81529086013560048201526024810192909252604482015260640161018b565b602080850151610100830151845160408051638024cca160e01b81529488013560048601526024850192909252606087013560448501526080870135606485015260a0870135608485015260c087013560a485015260c484015285013560e48301526001600160a01b031690638024cca19061010401600060405180830381600087803b15801561126d57600080fd5b505af1158015611281573d6000803e3d6000fd5b506000925061129691505060e0850185611c82565b905011156113465760208401516001600160a01b0316636a84f2fd6112be60e0860186611c82565b60016112cd60e0890189611c82565b6112d8929150611bdc565b8181106112e7576112e7611cd3565b6040516001600160e01b031960e086901b168152602090910292909201356004830152508535602482015260440160006040518083038186803b15801561132d57600080fd5b505afa158015611341573d6000803e3d6000fd5b505050505b50505050565b6000806113708460000151856040015186606001516001600160601b03168661149b565b90925090506113876113828285611bdc565b61164f565b602084015160405163af12409760e01b81526001600160a01b0387169163af124097916113b991908690600401611fcd565b600060405180830381600087803b1580156113d357600080fd5b505af11580156113e7573d6000803e3d6000fd5b505050505050505050565b6001600160a01b038416156113465760608201516040808401516101a08401516101c085015160c086015193516302244dbb60e61b8152883560048201526020890135602482015260448101959095526064850192909252608484015260a483015260c48201526001600160a01b038516906389136ec09060e401600060405180830381600087803b15801561148757600080fd5b505af1158015611341573d6000803e3d6000fd5b60606000855167ffffffffffffffff8111156114b9576114b9611ce9565b6040519080825280602002602001820160405280156114e2578160200160208202803683370190505b50915060005b865181101561164557600086828151811061150557611505611cd3565b60200260200101516001600160601b031611156116355760008587838151811061153157611531611cd3565b60200260200101516001600160601b03168661154d9190611bf3565b6115579190611c60565b90508084838151811061156c5761156c611cd3565b6020026020010181815250507f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba16001600160a01b031663528c198a8984815181106115b9576115b9611cd3565b6020026020010151836040518363ffffffff1660e01b81526004016115f39291906001600160a01b03929092168252602082015260400190565b600060405180830381600087803b15801561160d57600080fd5b505af1158015611621573d6000803e3d6000fd5b5050505080836116319190611c12565b9250505b61163e81611ff2565b90506114e8565b5094509492505050565b60007f000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea2919806001600160a01b03166361d027b36040518163ffffffff1660e01b815260040160206040518083038186803b1580156116aa57600080fd5b505afa1580156116be573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116e2919061200d565b6040516329460cc560e11b81526001600160a01b038083166004830152602482018590529192507f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba19091169063528c198a90604401600060405180830381600087803b15801561175157600080fd5b505af1158015611765573d6000803e3d6000fd5b505050505050565b604051806101e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016117e56040518060a0016040528060608152602001606081526020016060815260200160006001600160601b03168152602001600081525090565b8152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b60405180610100016040528060008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6000610100828403121561187457600080fd5b50919050565b6000806040838503121561188d57600080fd5b823567ffffffffffffffff8111156118a457600080fd5b6118b085828601611861565b95602094909401359450505050565b600081518084526020808501945080840160005b838110156118ef578151875295820195908201906001016118d3565b509495945050505050565b600081518084526020808501945080840160005b838110156118ef5781516001600160601b03168752958201959082019060010161190e565b805160a080845281519084018190526000916020919082019060c0860190845b818110156119785783516001600160a01b031683529284019291840191600101611953565b50508285015191508581038387015261199181836118bf565b92505050604083015184820360408601526119ac82826118fa565b91505060608301516119c960608601826001600160601b03169052565b50608083015160808501528091505092915050565b6020815281516020820152602082015160408201526040820151606082015260608201516080820152608082015160a082015260a082015160c082015260c082015160e0820152600060e08301516101e06101008181860152611a45610200860184611933565b90860151610120868101919091528601516101408087019190915286015161016080870191909152860151610180808701919091528601516101a0808701919091528601516101c080870191909152909501519301929092525090919050565b600060208284031215611ab757600080fd5b813567ffffffffffffffff811115611ace57600080fd5b611ada84828501611861565b949350505050565b80516001600160a01b0381168114610f8057600080fd5b600080600080600080600060e0888a031215611b1457600080fd5b611b1d88611ae2565b9650611b2b60208901611ae2565b9550611b3960408901611ae2565b9450611b4760608901611ae2565b9350611b5560808901611ae2565b9250611b6360a08901611ae2565b9150611b7160c08901611ae2565b905092959891949750929550565b600080600060608486031215611b9457600080fd5b8351925060208401519150604084015190509250925092565b600060208284031215611bbf57600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b600082821015611bee57611bee611bc6565b500390565b6000816000190483118215151615611c0d57611c0d611bc6565b500290565b60008219821115611c2557611c25611bc6565b500190565b60008060008060808587031215611c4057600080fd5b505082516020840151604085015160609095015191969095509092509050565b600082611c7d57634e487b7160e01b600052601260045260246000fd5b500490565b6000808335601e19843603018112611c9957600080fd5b83018035915067ffffffffffffffff821115611cb457600080fd5b6020019150600581901b3603821315611ccc57600080fd5b9250929050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff81118282101715611d2857611d28611ce9565b604052919050565b600067ffffffffffffffff821115611d4a57611d4a611ce9565b5060051b60200190565b600082601f830112611d6557600080fd5b81516020611d7a611d7583611d30565b611cff565b82815260059290921b84018101918181019086841115611d9957600080fd5b8286015b84811015611db45780518352918301918301611d9d565b509695505050505050565b80516001600160601b0381168114610f8057600080fd5b600082601f830112611de757600080fd5b81516020611df7611d7583611d30565b82815260059290921b84018101918181019086841115611e1657600080fd5b8286015b84811015611db457611e2b81611dbf565b8352918301918301611e1a565b600080600080600060a08688031215611e5057600080fd5b855167ffffffffffffffff80821115611e6857600080fd5b818801915088601f830112611e7c57600080fd5b81516020611e8c611d7583611d30565b82815260059290921b8401810191818101908c841115611eab57600080fd5b948201945b83861015611ed057611ec186611ae2565b82529482019490820190611eb0565b918b0151919950909350505080821115611ee957600080fd5b611ef589838a01611d54565b95506040880151915080821115611f0b57600080fd5b50611f1888828901611dd6565b935050611f2760608701611dbf565b9150608086015190509295509295909350565b600060208284031215611f4c57600080fd5b81518015158114611f5c57600080fd5b9392505050565b6040808252810183905260006001600160fb1b03841115611f8357600080fd5b8360051b8086606085013760009083016060019081526020909201929092529392505050565b60008060408385031215611fbc57600080fd5b505080516020909101519092909150565b604081526000611fe060408301856118bf565b828103602084015261012281856118bf565b600060001982141561200657612006611bc6565b5060010190565b60006020828403121561201f57600080fd5b611f5c82611ae256fea26469706673582212207089622b6f23f083befced13b3571dc3f3ed7c6ac6735d7818faa36a0583b5bf64736f6c63430008090033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea2919800000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba1
-----Decoded View---------------
Arg [0] : _lidoLocator (address): 0xD7c1B80fA86965B48cCA3aDcCB08E1DAEa291980
Arg [1] : _lido (address): 0x2C220A2a91602dd93bEAC7b3A1773cdADE369ba1
-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea291980
Arg [1] : 0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba1
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.