Source Code
Overview
ETH Balance
0 ETH
More Info
ContractCreator
Multichain Info
N/A
Latest 9 from a total of 9 transactions
| Transaction Hash |
Method
|
Block
|
From
|
To
|
Amount
|
||||
|---|---|---|---|---|---|---|---|---|---|
| Unguaranteed Dep... | 941171 | 95 days ago | IN | 0 ETH | 0.00024115 | ||||
| Unguaranteed Dep... | 941157 | 95 days ago | IN | 0 ETH | 0.00021129 | ||||
| Unguaranteed Dep... | 941138 | 95 days ago | IN | 0 ETH | 0.00034181 | ||||
| Unguaranteed Dep... | 941082 | 95 days ago | IN | 0 ETH | 0.00013717 | ||||
| Unguaranteed Dep... | 941052 | 95 days ago | IN | 0 ETH | 0.00010393 | ||||
| Unguaranteed Dep... | 941045 | 95 days ago | IN | 0 ETH | 0.00010756 | ||||
| Unguaranteed Dep... | 941034 | 95 days ago | IN | 0 ETH | 0.00007446 | ||||
| Unguaranteed Dep... | 941020 | 95 days ago | IN | 0 ETH | 0.00008021 | ||||
| Transfer Vault O... | 640380 | 141 days ago | IN | 0 ETH | 0.00003791 |
Latest 25 internal transactions (View All)
Advanced mode:
| Parent Transaction Hash | Method | Block |
From
|
To
|
Amount
|
||
|---|---|---|---|---|---|---|---|
| Revoke Role | 1553420 | 3 days ago | 0 ETH | ||||
| DEFAULT_ADMIN_RO... | 1553420 | 3 days ago | 0 ETH | ||||
| Grant Role | 1553420 | 3 days ago | 0 ETH | ||||
| DEFAULT_ADMIN_RO... | 1553420 | 3 days ago | 0 ETH | ||||
| Connect To Vault... | 1553420 | 3 days ago | 1 ETH | ||||
| Initialize | 1553420 | 3 days ago | 0 ETH | ||||
| Revoke Role | 1526352 | 7 days ago | 0 ETH | ||||
| DEFAULT_ADMIN_RO... | 1526352 | 7 days ago | 0 ETH | ||||
| Grant Role | 1526352 | 7 days ago | 0 ETH | ||||
| DEFAULT_ADMIN_RO... | 1526352 | 7 days ago | 0 ETH | ||||
| Connect To Vault... | 1526352 | 7 days ago | 1 ETH | ||||
| Initialize | 1526352 | 7 days ago | 0 ETH | ||||
| Revoke Role | 1525404 | 7 days ago | 0 ETH | ||||
| DEFAULT_ADMIN_RO... | 1525404 | 7 days ago | 0 ETH | ||||
| Grant Role | 1525404 | 7 days ago | 0 ETH | ||||
| DEFAULT_ADMIN_RO... | 1525404 | 7 days ago | 0 ETH | ||||
| Connect To Vault... | 1525404 | 7 days ago | 1 ETH | ||||
| Initialize | 1525404 | 7 days ago | 0 ETH | ||||
| Trigger Validato... | 1520285 | 8 days ago | 200 wei | ||||
| Trigger Validato... | 1520265 | 8 days ago | 200 wei | ||||
| Trigger Validato... | 1520263 | 8 days ago | 200 wei | ||||
| Trigger Validato... | 1520262 | 8 days ago | 200 wei | ||||
| Trigger Validato... | 1520256 | 8 days ago | 100 wei | ||||
| Trigger Validato... | 1520251 | 8 days ago | 200 wei | ||||
| Trigger Validato... | 1520249 | 8 days ago | 100 wei |
Loading...
Loading
Loading...
Loading
Contract Source Code Verified (Exact Match)
Contract Name:
Dashboard
Compiler Version
v0.8.25+commit.b61c2a91
Optimization Enabled:
Yes with 200 runs
Other Settings:
cancun 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.25; import {SafeERC20} from "@openzeppelin/contracts-v5.2/token/ERC20/utils/SafeERC20.sol"; import {IERC20} from "@openzeppelin/contracts-v5.2/token/ERC20/IERC20.sol"; import {IERC721} from "@openzeppelin/contracts-v5.2/token/ERC721/IERC721.sol"; import {Math256} from "contracts/common/lib/Math256.sol"; import {ILido as IStETH} from "contracts/common/interfaces/ILido.sol"; import {IDepositContract} from "contracts/common/interfaces/IDepositContract.sol"; import {IStakingVault} from "../interfaces/IStakingVault.sol"; import {IPredepositGuarantee} from "../interfaces/IPredepositGuarantee.sol"; import {NodeOperatorFee} from "./NodeOperatorFee.sol"; import {VaultHub} from "../VaultHub.sol"; interface IWstETH is IERC20 { function wrap(uint256) external returns (uint256); function unwrap(uint256) external returns (uint256); } /** * @title Dashboard * @notice This contract is a UX-layer for StakingVault and meant to be used as its owner. * This contract improves the vault UX by bundling all functions from the StakingVault and VaultHub * in this single contract. It provides administrative functions for managing the StakingVault, * including funding, withdrawing, minting, burning, and rebalancing operations. */ contract Dashboard is NodeOperatorFee { bytes32 public constant RECOVER_ASSETS_ROLE = keccak256("vaults.Dashboard.RecoverAssets"); /** * @notice The stETH token contract */ IStETH public immutable STETH; /** * @notice The wstETH token contract */ IWstETH public immutable WSTETH; /** * @notice ETH address convention per EIP-7528 */ address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /** * @notice Slot for the fund-on-receive flag * keccak256("vaults.Dashboard.fundOnReceive") */ bytes32 public constant FUND_ON_RECEIVE_FLAG_SLOT = 0x7408b7b034fda7051615c19182918ecb91d753231cffd86f81a45d996d63e038; /** * @notice Constructor sets the stETH, and WSTETH token addresses, * and passes the address of the vault hub up the inheritance chain. * @param _stETH Address of the stETH token contract. * @param _wstETH Address of the wstETH token contract. * @param _vaultHub Address of the vault hub contract. * @param _lidoLocator Address of the Lido locator contract. */ constructor( address _stETH, address _wstETH, address _vaultHub, address _lidoLocator ) NodeOperatorFee(_vaultHub, _lidoLocator) { _requireNotZero(_stETH); _requireNotZero(_wstETH); // stETH and wstETH are cached as immutable to save gas for main operations STETH = IStETH(_stETH); WSTETH = IWstETH(_wstETH); } /** * @notice Calls the parent's initializer and approves the max allowance for WSTETH for gas savings * @param _defaultAdmin The address of the default admin * @param _nodeOperatorManager The address of the node operator manager * @param _nodeOperatorFeeBP The node operator fee in basis points * @param _confirmExpiry The confirmation expiry time in seconds */ function initialize( address _defaultAdmin, address _nodeOperatorManager, uint256 _nodeOperatorFeeBP, uint256 _confirmExpiry ) external { super._initialize(_defaultAdmin, _nodeOperatorManager, _nodeOperatorFeeBP, _confirmExpiry); // reduces gas cost for `mintWsteth` // invariant: dashboard does not hold stETH on its balance STETH.approve(address(WSTETH), type(uint256).max); } // ==================== View Functions ==================== /** * @notice Returns the vault connection data for the staking vault. * @return VaultConnection struct containing vault data */ function vaultConnection() public view returns (VaultHub.VaultConnection memory) { return VAULT_HUB.vaultConnection(address(_stakingVault())); } /** * @notice Returns the stETH share limit of the vault */ function shareLimit() external view returns (uint256) { return vaultConnection().shareLimit; } /** * @notice Returns the number of stETH shares minted */ function liabilityShares() public view returns (uint256) { return VAULT_HUB.liabilityShares(address(_stakingVault())); } /** * @notice Returns the reserve ratio of the vault in basis points */ function reserveRatioBP() public view returns (uint16) { return vaultConnection().reserveRatioBP; } /** * @notice Returns the rebalance threshold of the vault in basis points. */ function forcedRebalanceThresholdBP() external view returns (uint16) { return vaultConnection().forcedRebalanceThresholdBP; } /** * @notice Returns the infra fee basis points. */ function infraFeeBP() external view returns (uint16) { return vaultConnection().infraFeeBP; } /** * @notice Returns the liquidity fee basis points. */ function liquidityFeeBP() external view returns (uint16) { return vaultConnection().liquidityFeeBP; } /** * @notice Returns the reservation fee basis points. */ function reservationFeeBP() external view returns (uint16) { return vaultConnection().reservationFeeBP; } /** * @notice Returns the total value of the vault in ether. */ function totalValue() public view returns (uint256) { return VAULT_HUB.totalValue(address(_stakingVault())); } /** * @notice Returns the overall unsettled obligations of the vault in ether * @dev includes the node operator fee */ function unsettledObligations() external view returns (uint256) { VaultHub.VaultObligations memory obligations = VAULT_HUB.vaultObligations(address(_stakingVault())); return uint256(obligations.unsettledLidoFees) + uint256(obligations.redemptions) + nodeOperatorDisbursableFee(); } /** * @notice Returns the locked amount of ether for the vault */ function locked() public view returns (uint256) { return VAULT_HUB.locked(address(_stakingVault())); } /** * @notice Returns the max total lockable amount of ether for the vault (excluding the Lido and node operator fees) */ function maxLockableValue() public view returns (uint256) { uint256 maxLockableValue_ = VAULT_HUB.maxLockableValue(address(_stakingVault())); uint256 nodeOperatorFee = nodeOperatorDisbursableFee(); return maxLockableValue_ > nodeOperatorFee ? maxLockableValue_ - nodeOperatorFee : 0; } /** * @notice Returns the overall capacity for stETH shares that can be minted by the vault */ function totalMintingCapacityShares() public view returns (uint256) { uint256 effectiveShareLimit = _operatorGrid().effectiveShareLimit(address(_stakingVault())); return Math256.min(effectiveShareLimit, _mintableShares(maxLockableValue())); } /** * @notice Returns the remaining capacity for stETH shares that can be minted * by the vault if additional ether is funded * @param _etherToFund the amount of ether to be funded, can be zero * @return the number of shares that can be minted using additional ether */ function remainingMintingCapacityShares(uint256 _etherToFund) public view returns (uint256) { uint256 effectiveShareLimit = _operatorGrid().effectiveShareLimit(address(_stakingVault())); uint256 vaultMintableSharesByRR = _mintableShares(maxLockableValue() + _etherToFund); uint256 vaultLiabilityShares = liabilityShares(); return Math256.min( effectiveShareLimit > vaultLiabilityShares ? effectiveShareLimit - vaultLiabilityShares : 0, vaultMintableSharesByRR > vaultLiabilityShares ? vaultMintableSharesByRR - vaultLiabilityShares : 0 ); } /** * @notice Returns the amount of ether that can be instantly withdrawn from the staking vault. * @dev This is the amount of ether that is not locked in the StakingVault and not reserved for fees and obligations. */ function withdrawableValue() public view returns (uint256) { // On pending disconnect, the vault does not allow any withdrawals, so need to return 0 here if (VAULT_HUB.vaultConnection(address(_stakingVault())).pendingDisconnect) return 0; uint256 withdrawable = VAULT_HUB.withdrawableValue(address(_stakingVault())); uint256 nodeOperatorFee = nodeOperatorDisbursableFee(); return withdrawable > nodeOperatorFee ? withdrawable - nodeOperatorFee : 0; } // ==================== Vault Management Functions ==================== /** * @dev Automatically funds the staking vault with ether */ receive() external payable { if (_shouldFundOnReceive()) _fund(msg.value); } /** * @notice Transfers the ownership of the underlying StakingVault from this contract to a new owner * without disconnecting it from the hub * @param _newOwner Address of the new owner. */ function transferVaultOwnership(address _newOwner) external { _transferVaultOwnership(_newOwner); } /** * @notice Disconnects the underlying StakingVault from the hub and passing its ownership to Dashboard. * After receiving the final report, one can call reconnectToVaultHub() to reconnect to the hub * or abandonDashboard() to transfer the ownership to a new owner. */ function voluntaryDisconnect() external { disburseNodeOperatorFee(); _voluntaryDisconnect(); } /** * @notice Accepts the ownership over the StakingVault transferred from VaultHub on disconnect * and immediately transfers it to a new pending owner. This new owner will have to accept the ownership * on the StakingVault contract. * @param _newOwner The address to transfer the StakingVault ownership to. */ function abandonDashboard(address _newOwner) external { if (VAULT_HUB.isVaultConnected(address(_stakingVault()))) revert ConnectedToVaultHub(); if (_newOwner == address(this)) revert DashboardNotAllowed(); _acceptOwnership(); _transferOwnership(_newOwner); } /** * @notice Accepts the ownership over the StakingVault and connects to VaultHub. Can be called to reconnect * to the hub after voluntaryDisconnect() */ function reconnectToVaultHub() external { _acceptOwnership(); connectToVaultHub(); } /** * @notice Connects to VaultHub, transferring ownership to VaultHub. */ function connectToVaultHub() public payable { if (msg.value > 0) _stakingVault().fund{value: msg.value}(); _transferOwnership(address(VAULT_HUB)); VAULT_HUB.connectVault(address(_stakingVault())); } /** * @notice Changes the tier of the vault and connects to VaultHub * @param _tierId The tier to change to * @param _requestedShareLimit The requested share limit */ function connectAndAcceptTier(uint256 _tierId, uint256 _requestedShareLimit) external payable { connectToVaultHub(); if (!_changeTier(_tierId, _requestedShareLimit)) { revert TierChangeNotConfirmed(); } } /** * @notice Funds the staking vault with ether */ function fund() external payable { _fund(msg.value); } /** * @notice Withdraws ether from the staking vault to a recipient * @param _recipient Address of the recipient * @param _ether Amount of ether to withdraw */ function withdraw(address _recipient, uint256 _ether) external { uint256 withdrawableEther = withdrawableValue(); if (_ether > withdrawableEther) { revert ExceedsWithdrawable(_ether, withdrawableEther); } _withdraw(_recipient, _ether); } /** * @notice Mints stETH shares backed by the vault to the recipient. * @param _recipient Address of the recipient * @param _amountOfShares Amount of stETH shares to mint */ function mintShares(address _recipient, uint256 _amountOfShares) external payable fundable { _mintSharesWithinMintingCapacity(_recipient, _amountOfShares); } /** * @notice Mints stETH tokens backed by the vault to the recipient. * !NB: this will revert with`VaultHub.ZeroArgument("_amountOfShares")` if the amount of stETH is less than 1 share * @param _recipient Address of the recipient * @param _amountOfStETH Amount of stETH to mint */ function mintStETH(address _recipient, uint256 _amountOfStETH) external payable fundable { _mintSharesWithinMintingCapacity(_recipient, _getSharesByPooledEth(_amountOfStETH)); } /** * @notice Mints wstETH tokens backed by the vault to a recipient. * @param _recipient Address of the recipient * @param _amountOfWstETH Amount of tokens to mint */ function mintWstETH(address _recipient, uint256 _amountOfWstETH) external payable fundable { _mintSharesWithinMintingCapacity(address(this), _amountOfWstETH); uint256 mintedStETH = STETH.getPooledEthBySharesRoundUp(_amountOfWstETH); uint256 wrappedWstETH = WSTETH.wrap(mintedStETH); SafeERC20.safeTransfer(WSTETH, _recipient, wrappedWstETH); } /** * @notice Burns stETH shares from the sender backed by the vault. * Expects corresponding amount of stETH approved to this contract. * @param _amountOfShares Amount of stETH shares to burn */ function burnShares(uint256 _amountOfShares) external { STETH.transferSharesFrom(msg.sender, address(VAULT_HUB), _amountOfShares); _burnShares(_amountOfShares); } /** * @notice Burns stETH tokens from the sender backed by the vault. Expects stETH amount approved to this contract. * !NB: this will revert with `VaultHub.ZeroArgument("_amountOfShares")` if the amount of stETH is less than 1 share * @param _amountOfStETH Amount of stETH tokens to burn */ function burnStETH(uint256 _amountOfStETH) external { _burnStETH(_amountOfStETH); } /** * @notice Burns wstETH tokens from the sender backed by the vault. Expects wstETH amount approved to this contract. * !NB: this will revert with `VaultHub.ZeroArgument("_amountOfShares")` on 1 wei of wstETH due to rounding inside wstETH unwrap method * @param _amountOfWstETH Amount of wstETH tokens to burn */ function burnWstETH(uint256 _amountOfWstETH) external { _burnWstETH(_amountOfWstETH); } /** * @notice Rebalances StakingVault by withdrawing ether to VaultHub corresponding to shares amount provided * @param _shares amount of shares to rebalance */ function rebalanceVaultWithShares(uint256 _shares) external { _rebalanceVault(_shares); } /** * @notice Rebalances the vault by transferring ether given the shares amount * @param _ether amount of ether to rebalance */ function rebalanceVaultWithEther(uint256 _ether) external payable fundable { _rebalanceVault(_getSharesByPooledEth(_ether)); } /** * @notice Withdraws ether from vault and deposits directly to provided validators bypassing the default PDG process, * allowing validators to be proven post-factum via `proveUnknownValidatorsToPDG` * clearing them for future deposits via `PDG.depositToBeaconChain` * @param _deposits array of IStakingVault.Deposit structs containing deposit data * @return totalAmount total amount of ether deposited to beacon chain * @dev requires the caller to have the `UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE` * @dev can be used as PDG shortcut if the node operator is trusted to not frontrun provided deposits */ function unguaranteedDepositToBeaconChain( IStakingVault.Deposit[] calldata _deposits ) external returns (uint256 totalAmount) { IStakingVault stakingVault_ = _stakingVault(); IDepositContract depositContract = stakingVault_.DEPOSIT_CONTRACT(); for (uint256 i = 0; i < _deposits.length; i++) { totalAmount += _deposits[i].amount; } uint256 withdrawableEther = withdrawableValue(); if (totalAmount > withdrawableEther) { revert ExceedsWithdrawable(totalAmount, withdrawableEther); } _disableFundOnReceive(); _withdrawForUnguaranteedDepositToBeaconChain(totalAmount); // Instead of relying on auto-reset at the end of the transaction, // re-enable fund-on-receive manually to restore the default receive() behavior in the same transaction _enableFundOnReceive(); _setRewardsAdjustment(rewardsAdjustment.amount + totalAmount); bytes memory withdrawalCredentials = bytes.concat(stakingVault_.withdrawalCredentials()); IStakingVault.Deposit calldata deposit; for (uint256 i = 0; i < _deposits.length; i++) { deposit = _deposits[i]; depositContract.deposit{value: deposit.amount}( deposit.pubkey, withdrawalCredentials, deposit.signature, deposit.depositDataRoot ); } emit UnguaranteedDeposits(address(stakingVault_), _deposits.length, totalAmount); } /** * @notice Proves validators with correct vault WC if they are unknown to PDG * @param _witnesses array of IPredepositGuarantee.ValidatorWitness structs containing proof data for validators * @dev requires the caller to have the `PDG_PROVE_VALIDATOR_ROLE` */ function proveUnknownValidatorsToPDG(IPredepositGuarantee.ValidatorWitness[] calldata _witnesses) external { _proveUnknownValidatorsToPDG(_witnesses); } /** * @notice Compensates ether of disproven validator's predeposit from PDG to the recipient. * Can be called if validator which was predeposited via `PDG.predeposit` with vault funds * was frontrun by NO's with non-vault WC (effectively NO's stealing the predeposit) and then * proof of the validator's invalidity has been provided via `PDG.proveInvalidValidatorWC`. * @param _pubkey of validator that was proven invalid in PDG * @param _recipient address to receive the `PDG.PREDEPOSIT_AMOUNT` * @dev PDG will revert if _recipient is vault address, use fund() instead to return ether to vault * @dev requires the caller to have the `PDG_COMPENSATE_PREDEPOSIT_ROLE` */ function compensateDisprovenPredepositFromPDG(bytes calldata _pubkey, address _recipient) external { _compensateDisprovenPredepositFromPDG(_pubkey, _recipient); } /** * @notice Recovers ERC20 tokens or ether from the dashboard contract to sender * @param _token Address of the token to recover or 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee for ether * @param _recipient Address of the recovery recipient */ function recoverERC20( address _token, address _recipient, uint256 _amount ) external onlyRoleMemberOrAdmin(RECOVER_ASSETS_ROLE) { _requireNotZero(_token); _requireNotZero(_recipient); _requireNotZero(_amount); if (_token == ETH) { (bool success,) = payable(_recipient).call{value: _amount}(""); if (!success) revert EthTransferFailed(_recipient, _amount); } else { SafeERC20.safeTransfer(IERC20(_token), _recipient, _amount); } emit ERC20Recovered(_recipient, _token, _amount); } /** * @notice Transfers a given token_id of an ERC721-compatible NFT (defined by the token contract address) * from the dashboard contract to sender * * @param _token an ERC721-compatible token * @param _tokenId token id to recover * @param _recipient Address of the recovery recipient */ function recoverERC721( address _token, uint256 _tokenId, address _recipient ) external onlyRoleMemberOrAdmin(RECOVER_ASSETS_ROLE) { _requireNotZero(_token); _requireNotZero(_recipient); IERC721(_token).safeTransferFrom(address(this), _recipient, _tokenId); emit ERC721Recovered(_recipient, _token, _tokenId); } /** * @notice Pauses beacon chain deposits on the StakingVault. */ function pauseBeaconChainDeposits() external { _pauseBeaconChainDeposits(); } /** * @notice Resumes beacon chain deposits on the StakingVault. */ function resumeBeaconChainDeposits() external { _resumeBeaconChainDeposits(); } /** * @notice Signals to node operators that specific validators should exit from the beacon chain. It DOES NOT * directly trigger the exit - node operators must monitor for request events and handle the exits. * @param _pubkeys Concatenated validator public keys (48 bytes each). * @dev Emits `ValidatorExitRequested` event for each validator public key through the `StakingVault`. * This is a voluntary exit request - node operators can choose whether to act on it or not. */ function requestValidatorExit(bytes calldata _pubkeys) external { _requestValidatorExit(_pubkeys); } /** * @notice Initiates a withdrawal from validator(s) on the beacon chain using EIP-7002 triggerable withdrawals * Both partial withdrawals (disabled for if vault is unhealthy) and full validator exits are supported. * @param _pubkeys Concatenated validator public keys (48 bytes each). * @param _amounts Withdrawal amounts in wei for each validator key and must match _pubkeys length. * Set amount to 0 for a full validator exit. * For partial withdrawals, amounts will be trimmed to keep MIN_ACTIVATION_BALANCE on the validator to avoid deactivation * @param _refundRecipient Address to receive any fee refunds, if zero, refunds go to msg.sender. * @dev A withdrawal fee must be paid via msg.value. * Use `StakingVault.calculateValidatorWithdrawalFee()` to determine the required fee for the current block. */ function triggerValidatorWithdrawals( bytes calldata _pubkeys, uint64[] calldata _amounts, address _refundRecipient ) external payable { _triggerValidatorWithdrawals(_pubkeys, _amounts, _refundRecipient); } /** * @notice Requests a change of tier on the OperatorGrid. * @param _tierId The tier to change to. * @param _requestedShareLimit The requested share limit. * @return bool Whether the tier change was confirmed. */ function changeTier(uint256 _tierId, uint256 _requestedShareLimit) external returns (bool) { return _changeTier(_tierId, _requestedShareLimit); } // ==================== Internal Functions ==================== /** * @dev Modifier to fund the staking vault if msg.value > 0 */ modifier fundable() { if (msg.value > 0) { _fund(msg.value); } _; } /** * @notice Mints shares within the mintable capacity, * and reverts if the resulting backing is greater than the mintable capacity. * @param _recipient The address of the recipient. * @param _amountOfShares The amount of shares to mint. */ function _mintSharesWithinMintingCapacity(address _recipient, uint256 _amountOfShares) internal { uint256 remainingShares = remainingMintingCapacityShares(0); if (_amountOfShares > remainingShares) revert ExceedsMintingCapacity(_amountOfShares, remainingShares); _mintShares(_recipient, _amountOfShares); } /** * @dev Burns stETH tokens from the sender backed by the vault * @param _amountOfStETH Amount of tokens to burn */ function _burnStETH(uint256 _amountOfStETH) internal { uint256 _amountOfShares = _getSharesByPooledEth(_amountOfStETH); STETH.transferSharesFrom(msg.sender, address(VAULT_HUB), _amountOfShares); _burnShares(_amountOfShares); } /** * @dev Burns wstETH tokens from the sender backed by the vault * @param _amountOfWstETH Amount of tokens to burn */ function _burnWstETH(uint256 _amountOfWstETH) internal { SafeERC20.safeTransferFrom(WSTETH, msg.sender, address(this), _amountOfWstETH); uint256 unwrappedStETH = WSTETH.unwrap(_amountOfWstETH); uint256 unwrappedShares = _getSharesByPooledEth(unwrappedStETH); STETH.transferShares(address(VAULT_HUB), unwrappedShares); _burnShares(unwrappedShares); } /// @notice Calculates the total number of shares that can be minted by the vault /// @param _ether The amount of ether to consider for minting function _mintableShares(uint256 _ether) internal view returns (uint256) { uint256 mintableStETH = (_ether * (TOTAL_BASIS_POINTS - reserveRatioBP())) / TOTAL_BASIS_POINTS; return _getSharesByPooledEth(mintableStETH); } /// @notice Converts the given amount of stETH to shares function _getSharesByPooledEth(uint256 _amountOfStETH) internal view returns (uint256) { return STETH.getSharesByPooledEth(_amountOfStETH); } // @dev The logic is inverted, 0 means fund-on-receive is enabled, // so that fund-on-receive is enabled by default function _shouldFundOnReceive() internal view returns (bool shouldFund) { assembly { shouldFund := iszero(tload(FUND_ON_RECEIVE_FLAG_SLOT)) } } function _enableFundOnReceive() internal { assembly { tstore(FUND_ON_RECEIVE_FLAG_SLOT, 0) } } function _disableFundOnReceive() internal { assembly { tstore(FUND_ON_RECEIVE_FLAG_SLOT, 1) } } // ==================== Events ==================== /** * @notice Emitted when ether was withdrawn from the staking vault and deposited to validators directly bypassing PDG * @param stakingVault the address of owned staking vault * @param deposits the number of deposits * @param totalAmount the total amount of ether deposited to beacon chain */ event UnguaranteedDeposits(address indexed stakingVault, uint256 deposits, uint256 totalAmount); /** * @notice Emitted when the ERC20 `token` or ether is recovered (i.e. transferred) * @param to The address of the recovery recipient * @param token The address of the recovered ERC20 token (0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee for ether) * @param amount The amount of the token recovered */ event ERC20Recovered(address indexed to, address indexed token, uint256 amount); /** * @notice Emitted when the ERC721-compatible `token` (NFT) recovered (i.e. transferred) * @param to The address of the recovery recipient * @param token The address of the recovered ERC721 token * @param tokenId id of token recovered */ event ERC721Recovered(address indexed to, address indexed token, uint256 tokenId); // ==================== Errors ==================== /** * @notice Emitted when the withdrawable amount of ether is exceeded * @param amount The amount of ether that was attempted to be withdrawn * @param withdrawableValue The amount of withdrawable ether available */ error ExceedsWithdrawable(uint256 amount, uint256 withdrawableValue); /** * @notice Error thrown when minting capacity is exceeded */ error ExceedsMintingCapacity(uint256 requestedShares, uint256 remainingShares); /** * @notice Error thrown when recovery of ETH fails on transfer to recipient */ error EthTransferFailed(address recipient, uint256 amount); /** * @notice Error when the StakingVault is still connected to the VaultHub. */ error ConnectedToVaultHub(); /** * @notice Error thrown when attempting to connect to VaultHub without confirmed tier change */ error TierChangeNotConfirmed(); /** * @notice Error when attempting to abandon the Dashboard contract itself. */ error DashboardNotAllowed(); }
// 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 (last updated v5.0.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../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:
*
* ```solidity
* 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}:
*
* ```solidity
* 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. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @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 virtual returns (bool) {
return _roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @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 virtual returns (bytes32) {
return _roles[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.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual 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.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual 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 `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/AccessControlEnumerable.sol)
pragma solidity ^0.8.20;
import {IAccessControlEnumerable} from "./IAccessControlEnumerable.sol";
import {AccessControl} from "../AccessControl.sol";
import {EnumerableSet} from "../../utils/structs/EnumerableSet.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
using EnumerableSet for EnumerableSet.AddressSet;
mapping(bytes32 role => EnumerableSet.AddressSet) private _roleMembers;
/**
* @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 virtual returns (address) {
return _roleMembers[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 virtual returns (uint256) {
return _roleMembers[role].length();
}
/**
* @dev Return all accounts that have `role`
*
* 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 getRoleMembers(bytes32 role) public view virtual returns (address[] memory) {
return _roleMembers[role].values();
}
/**
* @dev Overload {AccessControl-_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
bool granted = super._grantRole(role, account);
if (granted) {
_roleMembers[role].add(account);
}
return granted;
}
/**
* @dev Overload {AccessControl-_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
bool revoked = super._revokeRole(role, account);
if (revoked) {
_roleMembers[role].remove(account);
}
return revoked;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/IAccessControlEnumerable.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "../IAccessControl.sol";
/**
* @dev External interface of AccessControlEnumerable declared to support ERC-165 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 (last updated v5.1.0) (access/IAccessControl.sol)
pragma solidity ^0.8.20;
/**
* @dev External interface of AccessControl declared to support ERC-165 detection.
*/
interface IAccessControl {
/**
* @dev The `account` is missing a role.
*/
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
/**
* @dev The caller of a function is not the expected one.
*
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
*/
error AccessControlBadConfirmation();
/**
* @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.
*/
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. This account bears the admin role (for the granted role).
* Expected in cases where the role was granted using the internal {AccessControl-_grantRole}.
*/
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 `callerConfirmation`.
*/
function renounceRole(bytes32 role, address callerConfirmation) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/Clones.sol)
pragma solidity ^0.8.20;
import {Create2} from "../utils/Create2.sol";
import {Errors} from "../utils/Errors.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*/
library Clones {
error CloneArgumentsTooLong();
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create opcode, which should never revert.
*/
function clone(address implementation) internal returns (address instance) {
return clone(implementation, 0);
}
/**
* @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
* to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function clone(address implementation, uint256 value) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create(value, 0x09, 0x37)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple times will revert, since
* the clones cannot be deployed twice at the same address.
*/
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
return cloneDeterministic(implementation, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
* a `value` parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministic(
address implementation,
bytes32 salt,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create2(value, 0x09, 0x37, salt)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(add(ptr, 0x38), deployer)
mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
mstore(add(ptr, 0x14), implementation)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
mstore(add(ptr, 0x58), salt)
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddress(implementation, salt, address(this));
}
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create opcode, which should never revert.
*/
function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
return cloneWithImmutableArgs(implementation, args, 0);
}
/**
* @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
* parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneWithImmutableArgs(
address implementation,
bytes memory args,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
assembly ("memory-safe") {
instance := create(value, add(bytecode, 0x20), mload(bytecode))
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
* `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
* at the same address.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal returns (address instance) {
return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
* but with a `value` parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
uint256 value
) internal returns (address instance) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.deploy(value, salt, bytecode);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.computeAddress(salt, keccak256(bytecode), deployer);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this));
}
/**
* @dev Get the immutable args attached to a clone.
*
* - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this
* function will return an empty array.
* - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or
* `cloneDeterministicWithImmutableArgs`, this function will return the args array used at
* creation.
* - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This
* function should only be used to check addresses that are known to be clones.
*/
function fetchCloneArgs(address instance) internal view returns (bytes memory) {
bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
assembly ("memory-safe") {
extcodecopy(instance, add(result, 32), 45, mload(result))
}
return result;
}
/**
* @dev Helper that prepares the initcode of the proxy with immutable args.
*
* An assembly variant of this function requires copying the `args` array, which can be efficiently done using
* `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using
* abi.encodePacked is more expensive but also more portable and easier to review.
*
* NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes.
* With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes.
*/
function _cloneCodeWithImmutableArgs(
address implementation,
bytes memory args
) private pure returns (bytes memory) {
if (args.length > 24531) revert CloneArgumentsTooLong();
return
abi.encodePacked(
hex"61",
uint16(args.length + 45),
hex"3d81600a3d39f3363d3d373d3d3d363d73",
implementation,
hex"5af43d82803e903d91602b57fd5bf3",
args
);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @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);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) 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 a `value` amount of tokens 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 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC-721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC-721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
* {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the address zero.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @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;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
* `CREATE2` can be used to compute in advance the address where a smart
* contract will be deployed, which allows for interesting new mechanisms known
* as 'counterfactual interactions'.
*
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
* information.
*/
library Create2 {
/**
* @dev There's no code to deploy.
*/
error Create2EmptyBytecode();
/**
* @dev Deploys a contract using `CREATE2`. The address where the contract
* will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `amount`.
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
if (bytecode.length == 0) {
revert Create2EmptyBytecode();
}
assembly ("memory-safe") {
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
// if no address was created, and returndata is not empty, bubble revert
if and(iszero(addr), not(iszero(returndatasize()))) {
let p := mload(0x40)
returndatacopy(p, 0, returndatasize())
revert(p, returndatasize())
}
}
if (addr == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
* `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
return computeAddress(salt, bytecodeHash, address(this));
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
assembly ("memory-safe") {
let ptr := mload(0x40) // Get free memory pointer
// | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... |
// |-------------------|---------------------------------------------------------------------------|
// | bytecodeHash | CCCCCCCCCCCCC...CC |
// | salt | BBBBBBBBBBBBB...BB |
// | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA |
// | 0xFF | FF |
// |-------------------|---------------------------------------------------------------------------|
// | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
// | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |
mstore(add(ptr, 0x40), bytecodeHash)
mstore(add(ptr, 0x20), salt)
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
mstore8(start, 0xff)
addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/Hashes.sol)
pragma solidity ^0.8.20;
/**
* @dev Library of standard hash functions.
*
* _Available since v5.1._
*/
library Hashes {
/**
* @dev Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
*
* NOTE: Equivalent to the `standardNodeHash` in our https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
*/
function commutativeKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32) {
return a < b ? _efficientKeccak256(a, b) : _efficientKeccak256(b, a);
}
/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function _efficientKeccak256(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
assembly ("memory-safe") {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol)
// This file was procedurally generated from scripts/generate/templates/MerkleProof.js.
pragma solidity ^0.8.20;
import {Hashes} from "./Hashes.sol";
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the Merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates Merkle trees that are safe
* against this attack out of the box.
*
* IMPORTANT: Consider memory side-effects when using custom hashing functions
* that access memory in an unsafe way.
*
* NOTE: This library supports proof verification for merkle trees built using
* custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving
* leaf inclusion in trees built using non-commutative hashing functions requires
* additional logic that is not supported by this library.
*/
library MerkleProof {
/**
*@dev The multiproof provided is not valid.
*/
error MerkleProofInvalidMultiproof();
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in memory with the default hashing function.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in memory with the default hashing function.
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in memory with a custom hashing function.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processProof(proof, leaf, hasher) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in memory with a custom hashing function.
*/
function processProof(
bytes32[] memory proof,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = hasher(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with the default hashing function.
*/
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with the default hashing function.
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with a custom hashing function.
*/
function verifyCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processProofCalldata(proof, leaf, hasher) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with a custom hashing function.
*/
function processProofCalldata(
bytes32[] calldata proof,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = hasher(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in memory with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProof}.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in memory with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = Hashes.commutativeKeccak256(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in memory with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProof}.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processMultiProof(proof, proofFlags, leaves, hasher) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in memory with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = hasher(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in calldata with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in calldata with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = Hashes.commutativeKeccak256(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in calldata with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves, hasher) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in calldata with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = hasher(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC-165 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);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* 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[ERC 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 (last updated v5.1.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @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.
*
* ```solidity
* 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.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
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 is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @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._positions[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 cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 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 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[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._positions[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) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
// 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 ("memory-safe") {
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 in 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 ("memory-safe") {
result := store
}
return result;
}
}// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {AccessControlEnumerable} from "@openzeppelin/contracts-v5.2/access/extensions/AccessControlEnumerable.sol"; import {Confirmations} from "./Confirmations.sol"; /** * @title AccessControlConfirmable * @author Lido * @notice An extension of AccessControlEnumerable that allows executing functions by mutual confirmation. * @dev This contract extends Confirmations and AccessControlEnumerable and adds a confirmation mechanism. */ abstract contract AccessControlConfirmable is AccessControlEnumerable, Confirmations { constructor() { __Confirmations_init(); } function _isValidConfirmer(bytes32 _role) internal view override returns (bool) { return hasRole(_role, msg.sender); } }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {Confirmations} from "./Confirmations.sol"; /** * @title Confirmable2Addresses * @author Lido * @notice An extension of Confirmations that allows exectuing functions by mutual confirmation. * @dev In this implementation, roles are treated as addresses. */ abstract contract Confirmable2Addresses is Confirmations { function _collectAndCheckConfirmations(bytes calldata _calldata, address _role1, address _role2) internal returns (bool) { bytes32[] memory roles = new bytes32[](2); roles[0] = bytes32(uint256(uint160(_role1))); roles[1] = bytes32(uint256(uint160(_role2))); return _collectAndCheckConfirmations(_calldata, roles); } function _isValidConfirmer(bytes32 _roleAsAddress) internal view override returns (bool) { return _roleAsAddress == bytes32(uint256(uint160(msg.sender))); } }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; /** * @title Confirmations * @author Lido * @notice A contract that allows executing functions by mutual confirmation. */ abstract contract Confirmations { /** * @notice Tracks confirmations * @dev We cannot set confirmExpiry to 0 because this means that all confirmations have to be in the same block, * which can never be guaranteed. And, more importantly, if the `_setConfirmExpiry` is restricted by * the `onlyConfirmed` modifier, the confirmation expiry will be tricky to change. * This is why confirmExpiry is private, set to a default value of 1 hour and cannot be set to 0. * * Storage layout: * - callData: msg.data of the call (selector + arguments) * - role: role that confirmed the action * - expiryTimestamp: timestamp of the confirmation * * - confirmExpiry: confirmation expiry period in seconds */ struct ConfirmationStorage { mapping(bytes callData => mapping(bytes32 role => uint256 expiryTimestamp)) confirmations; uint256 confirmExpiry; } /** * @notice Storage offset slot for ERC-7201 namespace * The storage namespace is used to prevent upgrade collisions * keccak256(abi.encode(uint256(keccak256("Lido.Vaults.storage.Confirmations")) - 1)) & ~bytes32(uint256(0xff)) */ bytes32 private constant CONFIRMATIONS_STORAGE_LOCATION = 0x1b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb0900; /** * @notice Minimal confirmation expiry in seconds. */ uint256 public constant MIN_CONFIRM_EXPIRY = 1 hours; /** * @notice Maximal confirmation expiry in seconds. */ uint256 public constant MAX_CONFIRM_EXPIRY = 30 days; function __Confirmations_init() internal { _setConfirmExpiry(1 days); } /** * @notice Returns the confirmation expiry. * @return The confirmation expiry in seconds. */ function getConfirmExpiry() public view returns (uint256) { return _getConfirmationsStorage().confirmExpiry; } /** * @notice Returns the confirmation expiry for a given call data and confirmer. * @param _callData The call data of the function. * @param _role The role of the confirmer. * @return The confirmation expiration timestamp or 0 if there was no confirmation from this role to this _callData */ function confirmation(bytes memory _callData, bytes32 _role) external view returns (uint256) { return _getConfirmationsStorage().confirmations[_callData][_role]; } /** * @dev Processes a confirmation from the current caller and checks if all required confirmations are present. * Confirmation, in this context, is a call to the same function with the same arguments. * This is a one-off operation that either: * - Collects the current caller's confirmation and returns false if not enough confirmations * - Or clears all confirmations and returns true if all required confirmations are present * * The confirmation process works as follows: * 1. When a role member calls the function: * - Their confirmation is counted immediately * - If not enough confirmations exist, their confirmation is recorded * - If they're not a member of any of the specified roles, the call reverts * * 2. Confirmation counting: * - Counts the current caller's confirmations if they're a member of any of the specified roles * - Counts existing confirmations that are not expired, i.e. expiry is not exceeded * * 3. Confirmation Management: * - If all members of the specified roles have confirmed: * a. Clears all confirmations for this call * b. Returns true to indicate that the function can be executed * - If not enough confirmations: * a. Stores the current confirmations * b. Returns false to indicate that the function cannot be executed yet * - Thus, if the caller has all the roles, returns true immediately * * 4. Gas Optimization: * - Confirmations are stored in a deferred manner using a memory array * - Confirmation storage writes only occur if the function cannot be executed immediately * - This prevents unnecessary storage writes when all confirmations are present, * because the confirmations are cleared anyway after the function is executed, * - i.e. this optimization is beneficial for the deciding caller and * saves 1 storage write for each role the deciding caller has * * @param _calldata msg.data of the call (selector + arguments) * @param _roles Array of role identifiers that must confirm the call in order to execute it * @return bool True if all required confirmations are present and the function can be executed, false otherwise * * @notice Confirmations past their expiry are not counted and must be recast * @notice Only members of the specified roles can submit confirmations * @notice The order of confirmations does not matter * */ function _collectAndCheckConfirmations(bytes calldata _calldata, bytes32[] memory _roles) internal returns (bool) { if (_roles.length == 0) revert ZeroConfirmingRoles(); uint256 numberOfRoles = _roles.length; uint256 numberOfConfirms = 0; bool[] memory deferredConfirms = new bool[](numberOfRoles); bool isRoleMember = false; ConfirmationStorage storage $ = _getConfirmationsStorage(); uint256 expiryTimestamp = block.timestamp + $.confirmExpiry; for (uint256 i = 0; i < numberOfRoles; ++i) { bytes32 role = _roles[i]; if (_isValidConfirmer(role)) { isRoleMember = true; numberOfConfirms++; deferredConfirms[i] = true; emit RoleMemberConfirmed(msg.sender, role, block.timestamp, expiryTimestamp, msg.data); } else if ($.confirmations[_calldata][role] >= block.timestamp) { numberOfConfirms++; } } if (!isRoleMember) revert SenderNotMember(); if (numberOfConfirms == numberOfRoles) { for (uint256 i = 0; i < numberOfRoles; ++i) { bytes32 role = _roles[i]; delete $.confirmations[_calldata][role]; } return true; } else { for (uint256 i = 0; i < numberOfRoles; ++i) { if (deferredConfirms[i]) { bytes32 role = _roles[i]; $.confirmations[_calldata][role] = expiryTimestamp; } } return false; } } /** * @notice Checks if the caller is a valid confirmer * @param _role The role to check * @return bool True if the caller is a valid confirmer */ function _isValidConfirmer(bytes32 _role) internal view virtual returns (bool); /** * @dev Sets the confirmation expiry. * Confirmation expiry is a period during which the confirmation is counted. Once expired, * the confirmation no longer counts and must be recasted for the confirmation to go through. * @dev Does not retroactively apply to existing confirmations. * @param _newConfirmExpiry The new confirmation expiry in seconds. */ function _setConfirmExpiry(uint256 _newConfirmExpiry) internal { _validateConfirmExpiry(_newConfirmExpiry); ConfirmationStorage storage $ = _getConfirmationsStorage(); uint256 oldConfirmExpiry = $.confirmExpiry; $.confirmExpiry = _newConfirmExpiry; emit ConfirmExpirySet(msg.sender, oldConfirmExpiry, _newConfirmExpiry); } function _validateConfirmExpiry(uint256 _newConfirmExpiry) internal pure { if (_newConfirmExpiry < MIN_CONFIRM_EXPIRY || _newConfirmExpiry > MAX_CONFIRM_EXPIRY) revert ConfirmExpiryOutOfBounds(); } function _getConfirmationsStorage() private pure returns (ConfirmationStorage storage $) { assembly { $.slot := CONFIRMATIONS_STORAGE_LOCATION } } /** * @dev Emitted when the confirmation expiry is set. * @param oldConfirmExpiry The old confirmation expiry. * @param newConfirmExpiry The new confirmation expiry. */ event ConfirmExpirySet(address indexed sender, uint256 oldConfirmExpiry, uint256 newConfirmExpiry); /** * @dev Emitted when a role member confirms. * @param member The address of the confirming member. * @param role The role of the confirming member. * @param confirmTimestamp The timestamp of the confirmation. * @param expiryTimestamp The timestamp when this confirmation expires. * @param data The msg.data of the confirmation (selector + arguments). */ event RoleMemberConfirmed(address indexed member, bytes32 indexed role, uint256 confirmTimestamp, uint256 expiryTimestamp, bytes data); /** * @dev Thrown when attempting to set confirmation expiry out of bounds. */ error ConfirmExpiryOutOfBounds(); /** * @dev Thrown when a caller without a required role attempts to confirm. */ error SenderNotMember(); /** * @dev Thrown when the roles array is empty. */ error ZeroConfirmingRoles(); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {PausableUntil} from "contracts/common/utils/PausableUntil.sol"; /** * @title PausableUntilWithRoles * @notice a `PausableUntil` implementation using OpenZeppelin's `AccessControlEnumerableUpgradeable` * @dev the inheriting contract must use `whenNotPaused` modifier from `PausableUntil` to block some functions on pause */ abstract contract PausableUntilWithRoles is PausableUntil, AccessControlEnumerableUpgradeable { /// @notice role that allows to pause the contract bytes32 public constant PAUSE_ROLE = keccak256("PausableUntilWithRoles.PauseRole"); /// @notice role that allows to resume the contract bytes32 public constant RESUME_ROLE = keccak256("PausableUntilWithRoles.ResumeRole"); /** * @notice Resume the contract * @dev Reverts if contracts is not paused * @dev Reverts if sender has no `RESUME_ROLE` */ function resume() external onlyRole(RESUME_ROLE) { _resume(); } /** * @notice Pause the contract for a specified period * @param _duration pause duration in seconds (use `PAUSE_INFINITELY` for unlimited) * @dev Reverts if contract is already paused * @dev Reverts 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 the contract until a specified timestamp * @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); } }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {VaultHub} from "../VaultHub.sol"; import {LazyOracle} from "../LazyOracle.sol"; import {Permissions} from "./Permissions.sol"; /** * @title NodeOperatorFee * @author Lido * @notice An accounting contract for a vault's node operator fee: * • Calculates the node operator's share of each reward period, * • Ignores any vault value changes that aren't true rewards, * • Permissionless on-demand fee disbursement, * • Critical parameter changes require vault-owner<>node operator approval. */ contract NodeOperatorFee is Permissions { /** * @notice Total basis points; 1bp = 0.01%, 100_00bp = 100.00%. */ uint256 internal constant TOTAL_BASIS_POINTS = 100_00; /** * @notice Maximum value that can be set via manual adjustment */ uint256 public constant MANUAL_REWARDS_ADJUSTMENT_LIMIT = 10_000_000 ether; /** * @notice Node operator manager role: * - confirms confirm expiry; * - confirms node operator fee changes; * - confirms the transfer of the StakingVault ownership; * - sets the node operator fee recipient. */ bytes32 public constant NODE_OPERATOR_MANAGER_ROLE = keccak256("vaults.NodeOperatorFee.NodeOperatorManagerRole"); /** * @notice Adjusts rewards to allow fee correction during side deposits or consolidations */ bytes32 public constant NODE_OPERATOR_REWARDS_ADJUST_ROLE = keccak256("vaults.NodeOperatorFee.RewardsAdjustRole"); /** * @notice Node operator fee in basis points; cannot exceed 100.00%. * The node operator's disbursable fee in ether is returned by `nodeOperatorDisbursableFee()`. */ uint256 public nodeOperatorFeeRate; /** * @notice The last report for which node operator fee was disbursed. Updated on each disbursement. */ VaultHub.Report public feePeriodStartReport; /** * @notice The address of the node operator fee recipient. */ address public nodeOperatorFeeRecipient; struct RewardsAdjustment { uint128 amount; uint64 latestTimestamp; } /** * @notice Adjustment to allow fee correction during side deposits or consolidations. * - can be increased manually by `increaseRewardsAdjustment` by NODE_OPERATOR_REWARDS_ADJUST_ROLE * - can be set via `setRewardsAdjustment` by `confirmingRoles()` * - increased automatically with `unguaranteedDepositToBeaconChain` by total ether amount of deposits * - reset to zero after `disburseNodeOperatorFee` * This amount will be deducted from rewards during NO fee calculation and can be used effectively write off NO's accrued fees. * */ RewardsAdjustment public rewardsAdjustment; /** * @notice Passes the address of the vault hub up the inheritance chain. * @param _vaultHub The address of the vault hub. * @param _lidoLocator The address of the Lido locator. */ constructor(address _vaultHub, address _lidoLocator) Permissions(_vaultHub, _lidoLocator) {} /** * @dev Calls the parent's initializer, sets the node operator fee, assigns the node operator manager role, * and makes the node operator manager the admin for the node operator roles. * @param _defaultAdmin The address of the default admin * @param _nodeOperatorManager The address of the node operator manager * @param _nodeOperatorFeeRate The node operator fee rate * @param _confirmExpiry The confirmation expiry time in seconds */ function _initialize( address _defaultAdmin, address _nodeOperatorManager, uint256 _nodeOperatorFeeRate, uint256 _confirmExpiry ) internal { _requireNotZero(_nodeOperatorManager); super._initialize(_defaultAdmin, _confirmExpiry); _validateNodeOperatorFeeRate(_nodeOperatorFeeRate); _setNodeOperatorFeeRate(_nodeOperatorFeeRate); _setNodeOperatorFeeRecipient(_nodeOperatorManager); _grantRole(NODE_OPERATOR_MANAGER_ROLE, _nodeOperatorManager); _setRoleAdmin(NODE_OPERATOR_MANAGER_ROLE, NODE_OPERATOR_MANAGER_ROLE); _setRoleAdmin(NODE_OPERATOR_REWARDS_ADJUST_ROLE, NODE_OPERATOR_MANAGER_ROLE); } /** * @notice Returns the roles that can: * - change the confirm expiry; * - set the node operator fee; * - set a new owner of the StakingVault. * @return roles is an array of roles that form the confirming roles. */ function confirmingRoles() public pure override returns (bytes32[] memory roles) { roles = new bytes32[](2); roles[0] = DEFAULT_ADMIN_ROLE; roles[1] = NODE_OPERATOR_MANAGER_ROLE; } /** * @notice Returns the latest report data containing the total value and in-out delta. * @return report The latest report. */ function latestReport() public view returns (VaultHub.Report memory) { return VAULT_HUB.latestReport(address(_stakingVault())); } /** * @notice Calculates the node operator's disbursable fee. * * The fee presently owed to the node-operator, * computed as a portion of staking rewards accrued between * `feePeriodStartReport` and `latestReport()`. * * Staking rewards for an accounting interval are derived as: * rewards = Δ(totalValue) − Δ(inOutDelta) − rewardsAdjustment * * where * • Δ(totalValue) — change in totalValue (CL + EL balances) between reports; * • Δ(inOutDelta) — net funds/withdrawals in the same interval; * • rewardsAdjustment — rewards offset that excludes side deposits and consolidations * (e.g. CL topups that are not subject to node operator fee). * * If the rewards are negative, for the purposes of fee calculation, they are considered to be zero. * The node-operator's fee is therefore: * fee = max(0, rewards) × nodeOperatorFeeBP / TOTAL_BASIS_POINTS * * @return fee The node operator's disbursable fee. */ function nodeOperatorDisbursableFee() public view returns (uint256) { VaultHub.Report memory periodStart = feePeriodStartReport; VaultHub.Report memory periodEnd = latestReport(); int256 adjustment = _toSignedClamped(rewardsAdjustment.amount); // the total increase/decrease of the vault value during the fee period int256 growth = int112(periodEnd.totalValue) - int112(periodStart.totalValue) - (periodEnd.inOutDelta - periodStart.inOutDelta); // the actual rewards that are subject to the fee int256 rewards = growth - adjustment; return rewards <= 0 ? 0 : (uint256(rewards) * nodeOperatorFeeRate) / TOTAL_BASIS_POINTS; } /** * @notice Transfers the node-operator's accrued fee (if any). * Steps: * • Compute the current fee via `nodeOperatorDisbursableFee()`. * • If there are no rewards, do nothing. * • Otherwise, move `feePeriodStartReport` to `latestReport()`, * reset `rewardsAdjustment` and transfer `fee` wei to `nodeOperatorFeeRecipient`. */ function disburseNodeOperatorFee() public { uint256 fee = nodeOperatorDisbursableFee(); // it's important not to revert here if there is no fee, // because the fee is automatically disbursed during `voluntaryDisconnect` if (fee == 0) return; if (rewardsAdjustment.amount != 0) _setRewardsAdjustment(0); feePeriodStartReport = latestReport(); VAULT_HUB.withdraw(address(_stakingVault()), nodeOperatorFeeRecipient, fee); emit NodeOperatorFeeDisbursed(msg.sender, fee); } /** * @notice Updates the node-operator's fee rate (basis-points share). * @param _newNodeOperatorFeeRate The new node operator fee rate. * @return bool Whether the node operator fee rate was set. */ function setNodeOperatorFeeRate(uint256 _newNodeOperatorFeeRate) external returns (bool) { // The report must be fresh so that the total value of the vault is up to date // and all the node operator fees are paid out fairly up to the moment of the latest fresh report if (!VAULT_HUB.isReportFresh(address(_stakingVault()))) revert ReportStale(); // Latest adjustment must be earlier than the latest fresh report timestamp if (rewardsAdjustment.latestTimestamp >= _lazyOracle().latestReportTimestamp()) revert AdjustmentNotReported(); // Adjustment must be settled before the fee rate change if (rewardsAdjustment.amount != 0) revert AdjustmentNotSettled(); // If the vault is quarantined, the total value is reduced and may not reflect the adjustment if (_lazyOracle().vaultQuarantine(address(_stakingVault())).isActive) revert VaultQuarantined(); // Validate fee rate before collecting confirmations _validateNodeOperatorFeeRate(_newNodeOperatorFeeRate); // store the caller's confirmation; only proceed if the required number of confirmations is met. if (!_collectAndCheckConfirmations(msg.data, confirmingRoles())) return false; // To follow the check-effects-interaction pattern, we need to remember the fee here // because the fee calculation variables will be reset in the following lines uint256 fee = nodeOperatorDisbursableFee(); // Start a new fee period feePeriodStartReport = latestReport(); _setNodeOperatorFeeRate(_newNodeOperatorFeeRate); if (fee > 0) { VAULT_HUB.withdraw(address(_stakingVault()), nodeOperatorFeeRecipient, fee); emit NodeOperatorFeeDisbursed(msg.sender, fee); } return true; } /** * @notice Sets the confirm expiry. * Confirm expiry is a period during which the confirm is counted. Once the period is over, * the confirm is considered expired, no longer counts and must be recasted. * @param _newConfirmExpiry The new confirm expiry in seconds. * @return bool Whether the confirm expiry was set. */ function setConfirmExpiry(uint256 _newConfirmExpiry) external returns (bool) { _validateConfirmExpiry(_newConfirmExpiry); if (!_collectAndCheckConfirmations(msg.data, confirmingRoles())) return false; _setConfirmExpiry(_newConfirmExpiry); return true; } /** * @notice Sets the node operator fee recipient. * @param _newNodeOperatorFeeRecipient The address of the new node operator fee recipient. */ function setNodeOperatorFeeRecipient( address _newNodeOperatorFeeRecipient ) external onlyRoleMemberOrAdmin(NODE_OPERATOR_MANAGER_ROLE) { _setNodeOperatorFeeRecipient(_newNodeOperatorFeeRecipient); } /** * @notice Increases rewards adjustment to correct fee calculation due to non-rewards ether on CL * @param _adjustmentIncrease amount to increase adjustment by * @dev will revert if final adjustment is more than `MANUAL_REWARDS_ADJUSTMENT_LIMIT` */ function increaseRewardsAdjustment( uint256 _adjustmentIncrease ) external onlyRoleMemberOrAdmin(NODE_OPERATOR_REWARDS_ADJUST_ROLE) { uint256 newAdjustment = rewardsAdjustment.amount + _adjustmentIncrease; // sanity check, though value will be cast safely during fee calculation if (newAdjustment > MANUAL_REWARDS_ADJUSTMENT_LIMIT) revert IncreasedOverLimit(); _setRewardsAdjustment(newAdjustment); } /** * @notice set `rewardsAdjustment` to a new proposed value if `confirmingRoles()` agree * @param _proposedAdjustment new adjustment amount * @param _expectedAdjustment current adjustment value for invalidating old confirmations * @return bool Whether the rewards adjustment was set. * @dev will revert if new adjustment is more than `MANUAL_REWARDS_ADJUSTMENT_LIMIT` */ function setRewardsAdjustment( uint256 _proposedAdjustment, uint256 _expectedAdjustment ) external returns (bool) { if (rewardsAdjustment.amount != _expectedAdjustment) revert InvalidatedAdjustmentVote(rewardsAdjustment.amount, _expectedAdjustment); if (_proposedAdjustment > MANUAL_REWARDS_ADJUSTMENT_LIMIT) revert IncreasedOverLimit(); if (!_collectAndCheckConfirmations(msg.data, confirmingRoles())) return false; _setRewardsAdjustment(_proposedAdjustment); return true; } function _setNodeOperatorFeeRate(uint256 _newNodeOperatorFeeRate) internal { _validateNodeOperatorFeeRate(_newNodeOperatorFeeRate); uint256 oldNodeOperatorFeeRate = nodeOperatorFeeRate; nodeOperatorFeeRate = _newNodeOperatorFeeRate; emit NodeOperatorFeeRateSet(msg.sender, oldNodeOperatorFeeRate, _newNodeOperatorFeeRate); } function _setNodeOperatorFeeRecipient(address _newNodeOperatorFeeRecipient) internal { _requireNotZero(_newNodeOperatorFeeRecipient); if (_newNodeOperatorFeeRecipient == nodeOperatorFeeRecipient) revert SameRecipient(); address oldNodeOperatorFeeRecipient = nodeOperatorFeeRecipient; nodeOperatorFeeRecipient = _newNodeOperatorFeeRecipient; emit NodeOperatorFeeRecipientSet(msg.sender, oldNodeOperatorFeeRecipient, _newNodeOperatorFeeRecipient); } /** * @notice sets InOut adjustment for correct fee calculation * @param _newAdjustment new adjustment value */ function _setRewardsAdjustment(uint256 _newAdjustment) internal { uint256 oldAdjustment = rewardsAdjustment.amount; if (_newAdjustment == oldAdjustment) revert SameAdjustment(); rewardsAdjustment.amount = uint128(_newAdjustment); rewardsAdjustment.latestTimestamp = uint64(block.timestamp); emit RewardsAdjustmentSet(_newAdjustment, oldAdjustment); } function _toSignedClamped(uint128 _adjustment) internal pure returns (int128) { if (_adjustment > uint128(type(int128).max)) return type(int128).max; return int128(_adjustment); } /** * @notice Validates that the node operator fee rate is within acceptable bounds * @param _nodeOperatorFeeRate The fee rate to validate */ function _validateNodeOperatorFeeRate(uint256 _nodeOperatorFeeRate) internal pure { if (_nodeOperatorFeeRate > TOTAL_BASIS_POINTS) revert FeeValueExceed100Percent(); } function _lazyOracle() internal view returns (LazyOracle) { return LazyOracle(LIDO_LOCATOR.lazyOracle()); } // ==================== Events ==================== /** * @dev Emitted when the node operator fee is set. * @param oldNodeOperatorFeeRate The old node operator fee rate. * @param newNodeOperatorFeeRate The new node operator fee rate. */ event NodeOperatorFeeRateSet(address indexed sender, uint256 oldNodeOperatorFeeRate, uint256 newNodeOperatorFeeRate); /** * @dev Emitted when the node operator fee is disbursed. * @param fee the amount of disbursed fee. */ event NodeOperatorFeeDisbursed(address indexed sender, uint256 fee); /** * @dev Emitted when the new rewards adjustment is set. * @param newAdjustment the new adjustment value * @param oldAdjustment previous adjustment value */ event RewardsAdjustmentSet(uint256 newAdjustment, uint256 oldAdjustment); /** * @dev Emitted when the node operator fee recipient is set. * @param sender the address of the sender who set the recipient * @param oldNodeOperatorFeeRecipient the old node operator fee recipient * @param newNodeOperatorFeeRecipient the new node operator fee recipient */ event NodeOperatorFeeRecipientSet(address indexed sender, address oldNodeOperatorFeeRecipient, address newNodeOperatorFeeRecipient); // ==================== Errors ==================== /** * @dev Error emitted when the combined feeBPs exceed 100%. */ error FeeValueExceed100Percent(); /** * @dev Error emitted when the increased adjustment exceeds the `MANUAL_REWARDS_ADJUSTMENT_LIMIT`. */ error IncreasedOverLimit(); /** * @dev Error emitted when the adjustment setting vote is not valid due to changed state */ error InvalidatedAdjustmentVote(uint256 currentAdjustment, uint256 currentAtPropositionAdjustment); /** * @dev Error emitted when trying to set same value for adjustment */ error SameAdjustment(); /** * @dev Error emitted when trying to set same value for recipient */ error SameRecipient(); /** * @dev Error emitted when the report is stale. */ error ReportStale(); /** * @dev Error emitted when the adjustment has not been reported yet. */ error AdjustmentNotReported(); /** * @dev Error emitted when the adjustment is not settled. */ error AdjustmentNotSettled(); /** * @dev Error emitted when the vault is quarantined. */ error VaultQuarantined(); }
// SPDX-License-Identifier: GPL-3.0 // SPDX-FileCopyrightText: 2025 Lido <[email protected]> // See contracts/COMPILERS.md pragma solidity 0.8.25; import {Clones} from "@openzeppelin/contracts-v5.2/proxy/Clones.sol"; import {AccessControlConfirmable} from "contracts/0.8.25/utils/AccessControlConfirmable.sol"; import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol"; import {IStakingVault} from "../interfaces/IStakingVault.sol"; import {IPredepositGuarantee} from "../interfaces/IPredepositGuarantee.sol"; import {OperatorGrid} from "../OperatorGrid.sol"; import {VaultHub} from "../VaultHub.sol"; /** * @title Permissions * @author Lido * @notice Provides granular permissions for StakingVault operations. */ abstract contract Permissions is AccessControlConfirmable { /** * @notice Struct containing an account and a role for granting/revoking roles. */ struct RoleAssignment { address account; bytes32 role; } /** * @notice Permission for funding the StakingVault. */ bytes32 public constant FUND_ROLE = keccak256("vaults.Permissions.Fund"); /** * @notice Permission for withdrawing funds from the StakingVault. */ bytes32 public constant WITHDRAW_ROLE = keccak256("vaults.Permissions.Withdraw"); /** * @notice Permission for minting stETH shares backed by the StakingVault. */ bytes32 public constant MINT_ROLE = keccak256("vaults.Permissions.Mint"); /** * @notice Permission for burning stETH shares backed by the StakingVault. */ bytes32 public constant BURN_ROLE = keccak256("vaults.Permissions.Burn"); /** * @notice Permission for rebalancing the StakingVault. */ bytes32 public constant REBALANCE_ROLE = keccak256("vaults.Permissions.Rebalance"); /** * @notice Permission for pausing beacon chain deposits on the StakingVault. */ bytes32 public constant PAUSE_BEACON_CHAIN_DEPOSITS_ROLE = keccak256("vaults.Permissions.PauseDeposits"); /** * @notice Permission for resuming beacon chain deposits on the StakingVault. */ bytes32 public constant RESUME_BEACON_CHAIN_DEPOSITS_ROLE = keccak256("vaults.Permissions.ResumeDeposits"); /** * @notice Permission for requesting validator exit from the StakingVault. */ bytes32 public constant REQUEST_VALIDATOR_EXIT_ROLE = keccak256("vaults.Permissions.RequestValidatorExit"); /** * @notice Permission for triggering validator withdrawal from the StakingVault using EIP-7002 triggerable exit. */ bytes32 public constant TRIGGER_VALIDATOR_WITHDRAWAL_ROLE = keccak256("vaults.Permissions.TriggerValidatorWithdrawal"); /** * @notice Permission for voluntary disconnecting the StakingVault. */ bytes32 public constant VOLUNTARY_DISCONNECT_ROLE = keccak256("vaults.Permissions.VoluntaryDisconnect"); /** * @notice Permission for getting compensation for disproven validator predeposit from PDG */ bytes32 public constant PDG_COMPENSATE_PREDEPOSIT_ROLE = keccak256("vaults.Permissions.PDGCompensatePredeposit"); /** * @notice Permission for proving valid vault validators unknown to the PDG */ bytes32 public constant PDG_PROVE_VALIDATOR_ROLE = keccak256("vaults.Permissions.PDGProveValidator"); /** * @notice Permission for unguaranteed deposit to trusted validators */ bytes32 public constant UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE = keccak256("vaults.Permissions.UnguaranteedBeaconChainDeposit"); /** * @dev Permission for requesting change of tier on the OperatorGrid. */ bytes32 public constant CHANGE_TIER_ROLE = keccak256("vaults.Permissions.ChangeTier"); /** * @notice Address of the implementation contract * @dev Used to prevent initialization in the implementation */ address private immutable _SELF; VaultHub public immutable VAULT_HUB; ILidoLocator public immutable LIDO_LOCATOR; /** * @notice Indicates whether the contract has been initialized */ bool public initialized; constructor(address _vaultHub, address _lidoLocator) { _requireNotZero(_vaultHub); _requireNotZero(_lidoLocator); _SELF = address(this); // @dev vaultHub is cached as immutable to save gas for main operations VAULT_HUB = VaultHub(payable(_vaultHub)); LIDO_LOCATOR = ILidoLocator(_lidoLocator); } /** * @notice Modifier to prevent reinitialization of the contract. * @dev Extracted to modifier to avoid Slither warning. */ modifier initializer() { if (initialized) revert AlreadyInitialized(); if (address(this) == _SELF) revert NonProxyCallsForbidden(); initialized = true; _; emit Initialized(); } /** * @dev Sets the ACL default admin and confirmation expiry time. * @param _defaultAdmin The address of the default admin * @param _confirmExpiry The confirmation expiry time in seconds */ function _initialize(address _defaultAdmin, uint256 _confirmExpiry) internal initializer { _requireNotZero(_defaultAdmin); _grantRole(DEFAULT_ADMIN_ROLE, _defaultAdmin); _validateConfirmExpiry(_confirmExpiry); _setConfirmExpiry(_confirmExpiry); } /** * @notice Returns the address of the underlying StakingVault. * @return The address of the StakingVault. */ function stakingVault() external view returns (IStakingVault) { return _stakingVault(); } // ==================== Role Management Functions ==================== /** * @notice Mass-grants multiple roles to multiple accounts. * @param _assignments An array of role assignments. * @dev Performs the role admin checks internally. * @dev If an account is already a member of a role, doesn't revert, emits no events. */ function grantRoles(RoleAssignment[] calldata _assignments) external { _requireNotZero(_assignments.length); for (uint256 i = 0; i < _assignments.length; i++) { grantRole(_assignments[i].role, _assignments[i].account); } } /** * @notice Mass-revokes multiple roles from multiple accounts. * @param _assignments An array of role assignments. * @dev Performs the role admin checks internally. * @dev If an account is not a member of a role, doesn't revert, emits no events. */ function revokeRoles(RoleAssignment[] calldata _assignments) external { if (_assignments.length == 0) revert ZeroArgument(); for (uint256 i = 0; i < _assignments.length; i++) { revokeRole(_assignments[i].role, _assignments[i].account); } } /** * @dev Returns an array of roles that need to confirm the call * used for the `onlyConfirmed` modifier. * @return The roles that need to confirm the call. */ function confirmingRoles() public pure virtual returns (bytes32[] memory); /** * @dev A custom modifier that checks if the caller has a role or the admin role for a given role. * @param _role The role to check. */ modifier onlyRoleMemberOrAdmin(bytes32 _role) { if (hasRole(_role, msg.sender) || hasRole(getRoleAdmin(_role), msg.sender)) { _; } else { revert AccessControlUnauthorizedAccount(msg.sender, _role); } } /** * @dev Checks the FUND_ROLE and funds the StakingVault. * @param _ether The amount of ether to fund the StakingVault with. */ function _fund(uint256 _ether) internal onlyRoleMemberOrAdmin(FUND_ROLE) { VAULT_HUB.fund{value: _ether}(address(_stakingVault())); } /** * @dev Checks the WITHDRAW_ROLE and withdraws funds from the StakingVault. * @param _recipient The address to withdraw the funds to. * @param _ether The amount of ether to withdraw from the StakingVault. * @dev The zero checks for recipient and ether are performed in the StakingVault contract. */ function _withdraw(address _recipient, uint256 _ether) internal virtual onlyRoleMemberOrAdmin(WITHDRAW_ROLE) { VAULT_HUB.withdraw(address(_stakingVault()), _recipient, _ether); } /** * @dev Checks the MINT_ROLE and mints shares backed by the StakingVault. * @param _recipient The address to mint the shares to. * @param _shares The amount of shares to mint. * @dev The zero checks for parameters are performed in the VaultHub contract. */ function _mintShares(address _recipient, uint256 _shares) internal onlyRoleMemberOrAdmin(MINT_ROLE) { VAULT_HUB.mintShares(address(_stakingVault()), _recipient, _shares); } /** * @dev Checks the BURN_ROLE and burns shares backed by the StakingVault. * @param _shares The amount of shares to burn. * @dev The zero check for parameters is performed in the VaultHub contract. */ function _burnShares(uint256 _shares) internal onlyRoleMemberOrAdmin(BURN_ROLE) { VAULT_HUB.burnShares(address(_stakingVault()), _shares); } /** * @dev Checks the REBALANCE_ROLE and rebalances the StakingVault. * @param _shares The amount of shares to rebalance the StakingVault with. * @dev The zero check for parameters is performed in the StakingVault contract. */ function _rebalanceVault(uint256 _shares) internal onlyRoleMemberOrAdmin(REBALANCE_ROLE) { VAULT_HUB.rebalance(address(_stakingVault()), _shares); } /** * @dev Checks the PAUSE_BEACON_CHAIN_DEPOSITS_ROLE and pauses beacon chain deposits on the StakingVault. */ function _pauseBeaconChainDeposits() internal onlyRoleMemberOrAdmin(PAUSE_BEACON_CHAIN_DEPOSITS_ROLE) { VAULT_HUB.pauseBeaconChainDeposits(address(_stakingVault())); } /** * @dev Checks the RESUME_BEACON_CHAIN_DEPOSITS_ROLE and resumes beacon chain deposits on the StakingVault. */ function _resumeBeaconChainDeposits() internal onlyRoleMemberOrAdmin(RESUME_BEACON_CHAIN_DEPOSITS_ROLE) { VAULT_HUB.resumeBeaconChainDeposits(address(_stakingVault())); } /** * @dev Checks the REQUEST_VALIDATOR_EXIT_ROLE and requests validator exit on the StakingVault. * @dev The zero check for _pubkeys is performed in the StakingVault contract. */ function _requestValidatorExit(bytes calldata _pubkeys) internal onlyRoleMemberOrAdmin(REQUEST_VALIDATOR_EXIT_ROLE) { VAULT_HUB.requestValidatorExit(address(_stakingVault()), _pubkeys); } /** * @dev Checks the TRIGGER_VALIDATOR_WITHDRAWAL_ROLE and triggers validator withdrawal on the StakingVault using EIP-7002 triggerable exit. * @dev The zero checks for parameters are performed in the StakingVault contract. */ function _triggerValidatorWithdrawals( bytes calldata _pubkeys, uint64[] calldata _amounts, address _refundRecipient ) internal onlyRoleMemberOrAdmin(TRIGGER_VALIDATOR_WITHDRAWAL_ROLE) { VAULT_HUB.triggerValidatorWithdrawals{value: msg.value}(address(_stakingVault()), _pubkeys, _amounts, _refundRecipient); } /** * @dev Checks the VOLUNTARY_DISCONNECT_ROLE and voluntarily disconnects the StakingVault. */ function _voluntaryDisconnect() internal onlyRoleMemberOrAdmin(VOLUNTARY_DISCONNECT_ROLE) { VAULT_HUB.voluntaryDisconnect(address(_stakingVault())); } /** * @dev Checks the DEFAULT_ADMIN_ROLE and transfers the StakingVault ownership. * @param _newOwner The address to transfer the ownership to. */ function _transferOwnership(address _newOwner) internal onlyRole(DEFAULT_ADMIN_ROLE) { _stakingVault().transferOwnership(_newOwner); } /** * @dev Checks the DEFAULT_ADMIN_ROLE and accepts the StakingVault ownership. */ function _acceptOwnership() internal onlyRole(DEFAULT_ADMIN_ROLE) { _stakingVault().acceptOwnership(); } /** * @dev Checks the PDG_COMPENSATE_PREDEPOSIT_ROLE and claims disproven predeposit from PDG. * @param _pubkey The pubkey of the validator. * @param _recipient The address to compensate the disproven validator predeposit to. * @return The amount of ether compensated. */ function _compensateDisprovenPredepositFromPDG( bytes calldata _pubkey, address _recipient ) internal onlyRoleMemberOrAdmin(PDG_COMPENSATE_PREDEPOSIT_ROLE) returns (uint256) { return VAULT_HUB.compensateDisprovenPredepositFromPDG(address(_stakingVault()), _pubkey, _recipient); } /** * @dev Proves validators unknown to PDG that have correct vault WC */ function _proveUnknownValidatorsToPDG( IPredepositGuarantee.ValidatorWitness[] calldata _witnesses ) internal onlyRoleMemberOrAdmin(PDG_PROVE_VALIDATOR_ROLE) { for (uint256 i = 0; i < _witnesses.length; i++) { VAULT_HUB.proveUnknownValidatorToPDG(address(_stakingVault()), _witnesses[i]); } } /** * @dev Withdraws ether from vault to this contract for unguaranteed deposit to validators */ function _withdrawForUnguaranteedDepositToBeaconChain( uint256 _ether ) internal onlyRoleMemberOrAdmin(UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE) { VAULT_HUB.withdraw(address(_stakingVault()), address(this), _ether); } /** * @dev Checks the confirming roles and sets the owner on the StakingVault. * @param _newOwner The address to set the owner to. */ function _transferVaultOwnership(address _newOwner) internal { if (!_collectAndCheckConfirmations(msg.data, confirmingRoles())) return; VAULT_HUB.transferVaultOwnership(address(_stakingVault()), _newOwner); } /** * @dev Checks the CHANGE_TIER_ROLE and requests a change of the tier on the OperatorGrid. * @param _tierId The tier to change to. * @param _requestedShareLimit The requested share limit. * @return bool Whether the tier change was confirmed. */ function _changeTier(uint256 _tierId, uint256 _requestedShareLimit) internal onlyRoleMemberOrAdmin(CHANGE_TIER_ROLE) returns (bool) { return _operatorGrid().changeTier(address(_stakingVault()), _tierId, _requestedShareLimit); } /** * @dev Loads the address of the underlying StakingVault. * @return addr The address of the StakingVault. */ function _stakingVault() internal view returns (IStakingVault) { bytes memory args = Clones.fetchCloneArgs(address(this)); address stakingVaultAddress; assembly { stakingVaultAddress := mload(add(args, 32)) } return IStakingVault(stakingVaultAddress); } function _operatorGrid() internal view returns (OperatorGrid) { return OperatorGrid(LIDO_LOCATOR.operatorGrid()); } function _requireNotZero(uint256 _value) internal pure { if (_value == 0) revert ZeroArgument(); } function _requireNotZero(address _address) internal pure { if (_address == address(0)) revert ZeroAddress(); } /** * @notice Emitted when the contract is initialized */ event Initialized(); /** * @notice Error when direct calls to the implementation are forbidden */ error NonProxyCallsForbidden(); /** * @notice Error when the contract is already initialized. */ error AlreadyInitialized(); /** * @notice Error thrown for when a given value cannot be zero */ error ZeroArgument(); /** * @notice Error thrown for when a given address cannot be zero */ error ZeroAddress(); }
// 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 {IStakingVault} from "./IStakingVault.sol"; /** * @title IPredepositGuarantee * @author Lido * @notice Interface for the `PredepositGuarantee` contract */ interface IPredepositGuarantee { /** * @notice user input for validator proof verification * @custom:proof array of merkle proofs from parent(pubkey,wc) node to Beacon block root * @custom:pubkey of validator to prove * @custom:validatorIndex of validator in CL state tree * @custom:childBlockTimestamp of EL block that has parent block beacon root in BEACON_ROOTS contract * @custom:slot of the beacon block for which the proof is generated * @custom:proposerIndex of the beacon block for which the proof is generated */ struct ValidatorWitness { bytes32[] proof; bytes pubkey; uint256 validatorIndex; uint64 childBlockTimestamp; uint64 slot; uint64 proposerIndex; } function compensateDisprovenPredeposit( bytes calldata _validatorPubkey, address _recipient ) external returns (uint256 compensatedEther); function proveUnknownValidator(ValidatorWitness calldata _witness, IStakingVault _stakingVault) 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.8.0; import {IDepositContract} from "contracts/common/interfaces/IDepositContract.sol"; /** * @title IStakingVault * @author Lido * @notice Interface for the `StakingVault` contract */ interface IStakingVault { /** * @notice validator deposit from the `StakingVault` to the beacon chain * @dev withdrawal credentials are provided by the vault * @custom:pubkey The validator's BLS public key (48 bytes) * @custom:signature BLS signature of the deposit data (96 bytes) * @custom:amount Amount of ETH to deposit in wei (must be a multiple of 1 ETH) * @custom:depositDataRoot The root hash of the deposit data per ETH beacon spec */ struct Deposit { bytes pubkey; bytes signature; uint256 amount; bytes32 depositDataRoot; } function DEPOSIT_CONTRACT() external view returns (IDepositContract); function initialize(address _owner, address _nodeOperator, address _depositor) external; function version() external pure returns (uint64); function getInitializedVersion() external view returns (uint64); function withdrawalCredentials() external view returns (bytes32); function owner() external view returns (address); function pendingOwner() external view returns (address); function acceptOwnership() external; function transferOwnership(address _newOwner) external; function nodeOperator() external view returns (address); function depositor() external view returns (address); function isOssified() external view returns (bool); function calculateValidatorWithdrawalFee(uint256 _keysCount) external view returns (uint256); function fund() external payable; function withdraw(address _recipient, uint256 _ether) external; function beaconChainDepositsPaused() external view returns (bool); function pauseBeaconChainDeposits() external; function resumeBeaconChainDeposits() external; function depositToBeaconChain(Deposit[] calldata _deposits) external; function requestValidatorExit(bytes calldata _pubkeys) external; function triggerValidatorWithdrawals(bytes calldata _pubkeys, uint64[] calldata _amounts, address _refundRecipient) external payable; function ejectValidators(bytes calldata _pubkeys, address _refundRecipient) external payable; function setDepositor(address _depositor) external; function ossify() external; }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {MerkleProof} from "@openzeppelin/contracts-v5.2/utils/cryptography/MerkleProof.sol"; import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {Math256} from "contracts/common/lib/Math256.sol"; import {ILazyOracle} from "contracts/common/interfaces/ILazyOracle.sol"; import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol"; import {ILido} from "contracts/common/interfaces/ILido.sol"; import {IHashConsensus} from "contracts/common/interfaces/IHashConsensus.sol"; import {VaultHub} from "./VaultHub.sol"; import {OperatorGrid} from "./OperatorGrid.sol"; import {IStakingVault} from "./interfaces/IStakingVault.sol"; contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable { /// @custom:storage-location erc7201:LazyOracle struct Storage { /// @notice root of the vaults data tree bytes32 vaultsDataTreeRoot; /// @notice CID of the vaults data tree string vaultsDataReportCid; /// @notice timestamp of the vaults data uint64 vaultsDataTimestamp; /// @notice total value increase quarantine period uint64 quarantinePeriod; /// @notice max reward ratio for refSlot-observed total value, basis points uint16 maxRewardRatioBP; /// @notice deposit quarantines for each vault mapping(address vault => Quarantine) vaultQuarantines; } /* A quarantine is a timelock applied to any sudden jump in a vault's reported total value that cannot be immediately confirmed on-chain (via the inOutDelta difference). If the reported total value exceeds the expected routine EL/CL rewards, the excess is pushed into a quarantine buffer for a predefined cooldown period. Only after this delay is the quarantined value released into VaultHub's total value. Normal top-ups — where the vault owner funds the contract directly using the `fund()` function — do not go through quarantine, as they can be verified on-chain via the inOutDelta value. These direct fundings are reflected immediately. In contrast, consolidations or deposits that bypass the vault's balance must sit in quarantine. Example flow: Time 0: Total Value = 100 ETH ┌────────────────────────────────────┐ │ 100 ETH Active │ └────────────────────────────────────┘ Time 1: Sudden jump of +50 ETH → start quarantine for 50 ETH ┌────────────────────────────────────┐ │ 100 ETH Active │ │ 50 ETH Quarantined │ └────────────────────────────────────┘ Time 2: Another jump of +70 ETH → wait for current quarantine to expire ┌────────────────────────────────────┐ │ 100 ETH Active │ │ 50 ETH Quarantined │ │ 70 ETH Quarantine Queue │ └────────────────────────────────────┘ Time 3: First quarantine expires → add 50 ETH to active value, start new quarantine for 70 ETH ┌────────────────────────────────────┐ │ 150 ETH Active │ │ 70 ETH Quarantined │ └────────────────────────────────────┘ Time 4: Second quarantine expires → add 70 ETH to active value ┌────────────────────────────────────┐ │ 220 ETH Active │ └────────────────────────────────────┘ */ struct Quarantine { uint128 pendingTotalValueIncrease; uint64 startTimestamp; } struct QuarantineInfo { bool isActive; uint256 pendingTotalValueIncrease; uint256 startTimestamp; uint256 endTimestamp; } struct VaultInfo { address vault; uint96 vaultIndex; uint256 balance; bytes32 withdrawalCredentials; uint256 liabilityShares; uint256 mintableStETH; uint96 shareLimit; uint16 reserveRatioBP; uint16 forcedRebalanceThresholdBP; uint16 infraFeeBP; uint16 liquidityFeeBP; uint16 reservationFeeBP; bool pendingDisconnect; } // keccak256(abi.encode(uint256(keccak256("LazyOracle")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant LAZY_ORACLE_STORAGE_LOCATION = 0xe5459f2b48ec5df2407caac4ec464a5cb0f7f31a1f22f649728a9579b25c1d00; bytes32 public constant UPDATE_SANITY_PARAMS_ROLE = keccak256("UPDATE_SANITY_PARAMS_ROLE"); ILidoLocator public immutable LIDO_LOCATOR; /// @dev basis points base uint256 private constant TOTAL_BASIS_POINTS = 100_00; uint256 private constant MAX_SANE_TOTAL_VALUE = type(uint96).max; constructor(address _lidoLocator) { LIDO_LOCATOR = ILidoLocator(payable(_lidoLocator)); _disableInitializers(); } /// @notice Initializes the contract /// @param _admin Address of the admin /// @param _quarantinePeriod the quarantine period, seconds /// @param _maxRewardRatioBP the max reward ratio, basis points function initialize(address _admin, uint64 _quarantinePeriod, uint16 _maxRewardRatioBP) external initializer { if (_admin == address(0)) revert AdminCannotBeZero(); _grantRole(DEFAULT_ADMIN_ROLE, _admin); _updateSanityParams(_quarantinePeriod, _maxRewardRatioBP); } /// @notice returns the latest report data /// @return timestamp of the report /// @return treeRoot merkle root of the report /// @return reportCid IPFS CID for the report JSON file function latestReportData() external view returns (uint64 timestamp, bytes32 treeRoot, string memory reportCid) { Storage storage $ = _storage(); return ($.vaultsDataTimestamp, $.vaultsDataTreeRoot, $.vaultsDataReportCid); } /// @notice returns the latest report timestamp function latestReportTimestamp() external view returns (uint64) { return _storage().vaultsDataTimestamp; } /// @notice returns the quarantine period function quarantinePeriod() external view returns (uint64) { return _storage().quarantinePeriod; } /// @notice returns the max reward ratio for refSlot total value, basis points function maxRewardRatioBP() external view returns (uint16) { return _storage().maxRewardRatioBP; } /// @notice returns the quarantine info for the vault /// @param _vault the address of the vault // @dev returns zeroed structure if there is no active quarantine function vaultQuarantine(address _vault) external view returns (QuarantineInfo memory) { Quarantine storage q = _storage().vaultQuarantines[_vault]; if (q.pendingTotalValueIncrease == 0) { return QuarantineInfo(false, 0, 0, 0); } return QuarantineInfo({ isActive: true, pendingTotalValueIncrease: q.pendingTotalValueIncrease, startTimestamp: q.startTimestamp, endTimestamp: q.startTimestamp + _storage().quarantinePeriod }); } /// @notice returns batch of vaults info /// @param _offset in the vaults list [0, vaultsCount) /// @param _limit maximum number of vaults to return /// @return batch of vaults info function batchVaultsInfo(uint256 _offset, uint256 _limit) external view returns (VaultInfo[] memory) { VaultHub vaultHub = _vaultHub(); uint256 vaultCount = vaultHub.vaultsCount(); uint256 batchSize; if (_offset > vaultCount) { batchSize = 0; } else { batchSize = _offset + _limit > vaultCount ? vaultCount - _offset : _limit; } VaultInfo[] memory batch = new VaultInfo[](batchSize); for (uint256 i = 0; i < batchSize; i++) { address vaultAddress = vaultHub.vaultByIndex(_offset + i + 1); IStakingVault vault = IStakingVault(vaultAddress); VaultHub.VaultConnection memory connection = vaultHub.vaultConnection(vaultAddress); VaultHub.VaultRecord memory record = vaultHub.vaultRecord(vaultAddress); batch[i] = VaultInfo( vaultAddress, connection.vaultIndex, address(vault).balance, vault.withdrawalCredentials(), record.liabilityShares, _mintableStETH(vaultAddress), connection.shareLimit, connection.reserveRatioBP, connection.forcedRebalanceThresholdBP, connection.infraFeeBP, connection.liquidityFeeBP, connection.reservationFeeBP, connection.pendingDisconnect ); } return batch; } /// @notice update the sanity parameters /// @param _quarantinePeriod the quarantine period /// @param _maxRewardRatioBP the max EL CL rewards function updateSanityParams( uint64 _quarantinePeriod, uint16 _maxRewardRatioBP ) external onlyRole(UPDATE_SANITY_PARAMS_ROLE) { _updateSanityParams(_quarantinePeriod, _maxRewardRatioBP); } /// @notice Store the report root and its meta information /// @param _vaultsDataTimestamp the timestamp of the report /// @param _vaultsDataTreeRoot the root of the report /// @param _vaultsDataReportCid the CID of the report function updateReportData( uint256 _vaultsDataTimestamp, bytes32 _vaultsDataTreeRoot, string memory _vaultsDataReportCid ) external override(ILazyOracle) { if (msg.sender != LIDO_LOCATOR.accountingOracle()) revert NotAuthorized(); Storage storage $ = _storage(); $.vaultsDataTimestamp = uint64(_vaultsDataTimestamp); $.vaultsDataTreeRoot = _vaultsDataTreeRoot; $.vaultsDataReportCid = _vaultsDataReportCid; emit VaultsReportDataUpdated(_vaultsDataTimestamp, _vaultsDataTreeRoot, _vaultsDataReportCid); } /// @notice Permissionless update of the vault data /// @param _vault the address of the vault /// @param _totalValue the total value of the vault /// @param _cumulativeLidoFees the cumulative Lido fees accrued on the vault (nominated in ether) /// @param _liabilityShares the liabilityShares of the vault /// @param _proof the proof of the reported data function updateVaultData( address _vault, uint256 _totalValue, uint256 _cumulativeLidoFees, uint256 _liabilityShares, uint256 _slashingReserve, bytes32[] calldata _proof ) external { bytes32 leaf = keccak256( bytes.concat( keccak256( abi.encode( _vault, _totalValue, _cumulativeLidoFees, _liabilityShares, _slashingReserve ) ) ) ); if (!MerkleProof.verify(_proof, _storage().vaultsDataTreeRoot, leaf)) revert InvalidProof(); int256 inOutDelta; (_totalValue, inOutDelta) = _handleSanityChecks(_vault, _totalValue); _vaultHub().applyVaultReport( _vault, _storage().vaultsDataTimestamp, _totalValue, inOutDelta, _cumulativeLidoFees, _liabilityShares, _slashingReserve ); } /// @notice handle sanity checks for the vault lazy report data /// @param _vault the address of the vault /// @param _totalValue the total value of the vault in refSlot /// @return totalValueWithoutQuarantine the smoothed total value of the vault after sanity checks /// @return inOutDeltaOnRefSlot the inOutDelta in the refSlot function _handleSanityChecks( address _vault, uint256 _totalValue ) public returns (uint256 totalValueWithoutQuarantine, int256 inOutDeltaOnRefSlot) { VaultHub vaultHub = _vaultHub(); VaultHub.VaultRecord memory record = vaultHub.vaultRecord(_vault); // 1. Calculate inOutDelta in the refSlot int256 currentInOutDelta = record.inOutDelta.value; inOutDeltaOnRefSlot = vaultHub.inOutDeltaAsOfLastRefSlot(_vault); // 2. Sanity check for total value increase totalValueWithoutQuarantine = _processTotalValue(_vault, _totalValue, inOutDeltaOnRefSlot, record); // 3. Sanity check for dynamic total value underflow if (int256(totalValueWithoutQuarantine) + currentInOutDelta - inOutDeltaOnRefSlot < 0) { revert UnderflowInTotalValueCalculation(); } } function _processTotalValue( address _vault, uint256 _reportedTotalValue, int256 _inOutDeltaOnRefSlot, VaultHub.VaultRecord memory record ) internal returns (uint256 totalValueWithoutQuarantine) { if (_reportedTotalValue > MAX_SANE_TOTAL_VALUE) { revert TotalValueTooLarge(); } Storage storage $ = _storage(); // total value from the previous report with inOutDelta correction till the current refSlot // it does not include CL difference and EL rewards for the period uint256 onchainTotalValueOnRefSlot = uint256(int256(uint256(record.report.totalValue)) + _inOutDeltaOnRefSlot - record.report.inOutDelta); // some percentage of funds hasn't passed through the vault's balance is allowed for the EL and CL rewards handling uint256 maxSaneTotalValue = onchainTotalValueOnRefSlot * (TOTAL_BASIS_POINTS + $.maxRewardRatioBP) / TOTAL_BASIS_POINTS; if (_reportedTotalValue > maxSaneTotalValue) { Quarantine storage q = $.vaultQuarantines[_vault]; uint64 reportTs = $.vaultsDataTimestamp; uint128 quarDelta = q.pendingTotalValueIncrease; uint128 delta = uint128(_reportedTotalValue - onchainTotalValueOnRefSlot); if (quarDelta == 0) { // first overlimit report _reportedTotalValue = onchainTotalValueOnRefSlot; q.pendingTotalValueIncrease = delta; q.startTimestamp = reportTs; emit QuarantinedDeposit(_vault, delta); } else if (reportTs - q.startTimestamp < $.quarantinePeriod) { // quarantine not expired _reportedTotalValue = onchainTotalValueOnRefSlot; } else if (delta <= quarDelta + onchainTotalValueOnRefSlot * $.maxRewardRatioBP / TOTAL_BASIS_POINTS) { // quarantine expired q.pendingTotalValueIncrease = 0; emit QuarantineExpired(_vault, delta); } else { // start new quarantine _reportedTotalValue = onchainTotalValueOnRefSlot + quarDelta; q.pendingTotalValueIncrease = delta - quarDelta; q.startTimestamp = reportTs; emit QuarantinedDeposit(_vault, delta - quarDelta); } } return _reportedTotalValue; } function _updateSanityParams(uint64 _quarantinePeriod, uint16 _maxRewardRatioBP) internal { Storage storage $ = _storage(); $.quarantinePeriod = _quarantinePeriod; $.maxRewardRatioBP = _maxRewardRatioBP; emit SanityParamsUpdated(_quarantinePeriod, _maxRewardRatioBP); } function _mintableStETH(address _vault) internal view returns (uint256) { VaultHub vaultHub = _vaultHub(); uint256 maxLockableValue = vaultHub.maxLockableValue(_vault); uint256 reserveRatioBP = vaultHub.vaultConnection(_vault).reserveRatioBP; uint256 mintableStETHByRR = maxLockableValue * (TOTAL_BASIS_POINTS - reserveRatioBP) / TOTAL_BASIS_POINTS; uint256 effectiveShareLimit = _operatorGrid().effectiveShareLimit(_vault); uint256 mintableStEthByShareLimit = ILido(LIDO_LOCATOR.lido()).getPooledEthBySharesRoundUp(effectiveShareLimit); return Math256.min(mintableStETHByRR, mintableStEthByShareLimit); } function _storage() internal pure returns (Storage storage $) { assembly { $.slot := LAZY_ORACLE_STORAGE_LOCATION } } function _vaultHub() internal view returns (VaultHub) { return VaultHub(payable(LIDO_LOCATOR.vaultHub())); } function _operatorGrid() internal view returns (OperatorGrid) { return OperatorGrid(LIDO_LOCATOR.operatorGrid()); } event VaultsReportDataUpdated(uint256 indexed timestamp, bytes32 indexed root, string cid); event QuarantinedDeposit(address indexed vault, uint128 delta); event SanityParamsUpdated(uint64 quarantinePeriod, uint16 maxRewardRatioBP); event QuarantineExpired(address indexed vault, uint128 delta); error AdminCannotBeZero(); error NotAuthorized(); error InvalidProof(); error UnderflowInTotalValueCalculation(); error TotalValueTooLarge(); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {IHashConsensus} from "contracts/common/interfaces/IHashConsensus.sol"; library RefSlotCache { struct Int112WithRefSlotCache { int112 value; int112 valueOnRefSlot; uint32 refSlot; } struct Uint112WithRefSlotCache { uint112 value; uint112 valueOnRefSlot; uint32 refSlot; } /// @notice Increases the value and caches the previous value for the current refSlot /// @param _storage The storage slot to update /// @param _consensus The consensus contract to get the current refSlot /// @param _increment increment the value by this amount /// @return the updated struct to be saved in storage function withValueIncrease( Uint112WithRefSlotCache storage _storage, IHashConsensus _consensus, uint112 _increment ) internal view returns (Uint112WithRefSlotCache memory) { (uint256 refSlot, ) = _consensus.getCurrentFrame(); Uint112WithRefSlotCache memory newStorage = _storage; if (newStorage.refSlot != uint32(refSlot)) { // 32 bits is enough precision for this kind of comparison newStorage.valueOnRefSlot = _storage.value; newStorage.refSlot = uint32(refSlot); } newStorage.value += _increment; return newStorage; } /// @notice Increases the value and caches the previous value for the current refSlot /// @param _storage The storage slot to update /// @param _consensus The consensus contract to get the current refSlot /// @param _increment increment the value by this amount /// @return the updated struct to be saved in storage function withValueIncrease( Int112WithRefSlotCache storage _storage, IHashConsensus _consensus, int112 _increment ) internal view returns (Int112WithRefSlotCache memory) { (uint256 refSlot, ) = _consensus.getCurrentFrame(); Int112WithRefSlotCache memory newStorage = _storage; if (newStorage.refSlot != uint32(refSlot)) { // 32 bits is enough precision for this kind of comparison newStorage.valueOnRefSlot = _storage.value; newStorage.refSlot = uint32(refSlot); } newStorage.value += _increment; return newStorage; } /// @notice Returns the value for the current refSlot /// @param _storage the storage pointer for the cached value /// @param _consensus the consensus contract to get the current refSlot /// @return the cached value if it's changed since the last refSlot, the current value otherwise function getValueForLastRefSlot( Uint112WithRefSlotCache storage _storage, IHashConsensus _consensus ) internal view returns (uint112) { (uint256 refSlot, ) = _consensus.getCurrentFrame(); if (uint32(refSlot) > _storage.refSlot) { return _storage.value; } else { return _storage.valueOnRefSlot; } } /// @notice Returns the value for the current refSlot /// @param _storage the storage pointer for the cached value /// @param _consensus the consensus contract to get the current refSlot /// @return the cached value if it's changed since the last refSlot, the current value otherwise function getValueForLastRefSlot( Int112WithRefSlotCache storage _storage, IHashConsensus _consensus ) internal view returns (int112) { (uint256 refSlot, ) = _consensus.getCurrentFrame(); if (uint32(refSlot) > _storage.refSlot) { return _storage.value; } else { return _storage.valueOnRefSlot; } } }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {Math256} from "contracts/common/lib/Math256.sol"; import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol"; import {Confirmable2Addresses} from "../utils/Confirmable2Addresses.sol"; import {IStakingVault} from "./interfaces/IStakingVault.sol"; import {VaultHub} from "./VaultHub.sol"; struct TierParams { uint256 shareLimit; uint256 reserveRatioBP; uint256 forcedRebalanceThresholdBP; uint256 infraFeeBP; uint256 liquidityFeeBP; uint256 reservationFeeBP; } /** * @title OperatorGrid * @author loga4 * @notice * OperatorGrid is a contract that manages mint parameters for vaults when they are connected to the VaultHub. * These parameters include: * - shareLimit: maximum amount of shares that can be minted * - reserveRatioBP: reserve ratio in basis points * - forcedRebalanceThresholdBP: forced rebalance threshold in basis points * - infraFeeBP: infra fee in basis points * - liquidityFeeBP: liquidity fee in basis points * - reservationFeeBP: reservation fee in basis points * * These parameters are determined by the Tier in which the Vault is registered. * */ contract OperatorGrid is AccessControlEnumerableUpgradeable, Confirmable2Addresses { /* Key concepts: 1. Default Registration: - All Vaults initially have default tier (DEFAULT_TIER_ID = 0) - The default tier has no group DEFAULT_TIER_ID = 0 ┌──────────────────────┐ │ Tier 1 │ │ tierShareLimit = z │ │ Vault_1 ... Vault_m │ └──────────────────────┘ 2. Tier Change Process: - To predefine vaults tier or modify the existing vault's connection parameters to VaultHub, a tier change must be requested - Both vault owner and node operator must confirm the change (doesn't matter who confirms first) - The confirmation has an expiry time (default 1 hour) 3. Tier Reset: - When a vault is disconnected from VaultHub, its tier is automatically reset to the default tier (DEFAULT_TIER_ID) 4. Tier Capacity: - Tiers are not limited by the number of vaults - Tiers are limited by the sum of vaults' liability shares ┌──────────────────────────────────────────────────────┐ │ Group 1 = operator 1 │ │ ┌────────────────────────────────────────────────┐ │ │ │ groupShareLimit = 1kk │ │ │ └────────────────────────────────────────────────┘ │ │ ┌──────────────────────┐ ┌──────────────────────┐ │ │ │ Tier 1 │ │ Tier 2 │ │ │ │ tierShareLimit = x │ │ tierShareLimit = y │ │ │ │ Vault_2 ... Vault_k │ │ │ │ │ └──────────────────────┘ └──────────────────────┘ │ └──────────────────────────────────────────────────────┘ */ bytes32 public constant REGISTRY_ROLE = keccak256("vaults.OperatorsGrid.Registry"); /// @notice Lido Locator contract ILidoLocator public immutable LIDO_LOCATOR; uint256 public constant DEFAULT_TIER_ID = 0; // Special address to denote that default tier is not linked to any real operator address public constant DEFAULT_TIER_OPERATOR = address(uint160(type(uint160).max)); /// @dev basis points base uint256 internal constant TOTAL_BASIS_POINTS = 100_00; /// @dev max value for fees in basis points - it's about 650% uint256 internal constant MAX_FEE_BP = type(uint16).max; // ----------------------------- // STRUCTS // ----------------------------- struct Group { address operator; uint96 shareLimit; uint96 liabilityShares; uint256[] tierIds; } struct Tier { address operator; uint96 shareLimit; uint96 liabilityShares; uint16 reserveRatioBP; uint16 forcedRebalanceThresholdBP; uint16 infraFeeBP; uint16 liquidityFeeBP; uint16 reservationFeeBP; } /** * @notice ERC-7201 storage namespace for the OperatorGrid * @dev ERC-7201 namespace is used to prevent upgrade collisions * @custom:storage-location erc7201:Lido.Vaults.OperatorGrid * @custom:tiers Tiers * @custom:vaultTier Vault tier * @custom:groups Groups * @custom:nodeOperators Node operators */ struct ERC7201Storage { Tier[] tiers; mapping(address vault => uint256 tierId) vaultTier; mapping(address nodeOperator => Group) groups; address[] nodeOperators; } /** * @notice Storage offset slot for ERC-7201 namespace * The storage namespace is used to prevent upgrade collisions * keccak256(abi.encode(uint256(keccak256("Lido.Vaults.OperatorGrid")) - 1)) & ~bytes32(uint256(0xff)) */ bytes32 private constant OPERATOR_GRID_STORAGE_LOCATION = 0x6b64617c951381e2c1eff2be939fe368ab6d76b7d335df2e47ba2309eba1c700; /// @notice Initializes the contract with a LidoLocator /// @param _locator LidoLocator contract constructor(ILidoLocator _locator) { LIDO_LOCATOR = _locator; _disableInitializers(); } /// @notice Initializes the contract with an admin /// @param _admin Address of the admin /// @param _defaultTierParams Default tier params for the default tier function initialize(address _admin, TierParams calldata _defaultTierParams) external initializer { if (_admin == address(0)) revert ZeroArgument("_admin"); __AccessControlEnumerable_init(); __Confirmations_init(); _grantRole(DEFAULT_ADMIN_ROLE, _admin); ERC7201Storage storage $ = _getStorage(); //create default tier with default share limit $.tiers.push( Tier({ operator: DEFAULT_TIER_OPERATOR, shareLimit: uint96(_defaultTierParams.shareLimit), reserveRatioBP: uint16(_defaultTierParams.reserveRatioBP), forcedRebalanceThresholdBP: uint16(_defaultTierParams.forcedRebalanceThresholdBP), infraFeeBP: uint16(_defaultTierParams.infraFeeBP), liquidityFeeBP: uint16(_defaultTierParams.liquidityFeeBP), reservationFeeBP: uint16(_defaultTierParams.reservationFeeBP), liabilityShares: 0 }) ); } /// @notice Registers a new group /// @param _nodeOperator address of the node operator /// @param _shareLimit Maximum share limit for the group function registerGroup(address _nodeOperator, uint256 _shareLimit) external onlyRole(REGISTRY_ROLE) { if (_nodeOperator == address(0)) revert ZeroArgument("_nodeOperator"); ERC7201Storage storage $ = _getStorage(); if ($.groups[_nodeOperator].operator != address(0)) revert GroupExists(); $.groups[_nodeOperator] = Group({ operator: _nodeOperator, shareLimit: uint96(_shareLimit), liabilityShares: 0, tierIds: new uint256[](0) }); $.nodeOperators.push(_nodeOperator); emit GroupAdded(_nodeOperator, uint96(_shareLimit)); } /// @notice Updates the share limit of a group /// @param _nodeOperator address of the node operator /// @param _shareLimit New share limit value function updateGroupShareLimit(address _nodeOperator, uint256 _shareLimit) external onlyRole(REGISTRY_ROLE) { if (_nodeOperator == address(0)) revert ZeroArgument("_nodeOperator"); ERC7201Storage storage $ = _getStorage(); Group storage group_ = $.groups[_nodeOperator]; if (group_.operator == address(0)) revert GroupNotExists(); group_.shareLimit = uint96(_shareLimit); emit GroupShareLimitUpdated(_nodeOperator, uint96(_shareLimit)); } /// @notice Returns a group by node operator address /// @param _nodeOperator address of the node operator /// @return Group function group(address _nodeOperator) external view returns (Group memory) { return _getStorage().groups[_nodeOperator]; } /// @notice Returns a node operator address by index /// @param _index index of the node operator /// @return Node operator address function nodeOperatorAddress(uint256 _index) external view returns (address) { ERC7201Storage storage $ = _getStorage(); if (_index >= $.nodeOperators.length) revert NodeOperatorNotExists(); return $.nodeOperators[_index]; } /// @notice Returns a node operator count /// @return Node operator count function nodeOperatorCount() external view returns (uint256) { return _getStorage().nodeOperators.length; } /// @notice Registers a new tier /// @param _nodeOperator address of the node operator /// @param _tiers array of tiers to register function registerTiers( address _nodeOperator, TierParams[] calldata _tiers ) external onlyRole(REGISTRY_ROLE) { if (_nodeOperator == address(0)) revert ZeroArgument("_nodeOperator"); ERC7201Storage storage $ = _getStorage(); Group storage group_ = $.groups[_nodeOperator]; if (group_.operator == address(0)) revert GroupNotExists(); uint256 tierId = $.tiers.length; uint256 length = _tiers.length; for (uint256 i = 0; i < length; i++) { _validateParams( tierId, _tiers[i].reserveRatioBP, _tiers[i].forcedRebalanceThresholdBP, _tiers[i].infraFeeBP, _tiers[i].liquidityFeeBP, _tiers[i].reservationFeeBP ); Tier memory tier_ = Tier({ operator: _nodeOperator, shareLimit: uint96(_tiers[i].shareLimit), reserveRatioBP: uint16(_tiers[i].reserveRatioBP), forcedRebalanceThresholdBP: uint16(_tiers[i].forcedRebalanceThresholdBP), infraFeeBP: uint16(_tiers[i].infraFeeBP), liquidityFeeBP: uint16(_tiers[i].liquidityFeeBP), reservationFeeBP: uint16(_tiers[i].reservationFeeBP), liabilityShares: 0 }); $.tiers.push(tier_); group_.tierIds.push(tierId); emit TierAdded( _nodeOperator, tierId, uint96(tier_.shareLimit), uint16(tier_.reserveRatioBP), uint16(tier_.forcedRebalanceThresholdBP), uint16(tier_.infraFeeBP), uint16(tier_.liquidityFeeBP), uint16(tier_.reservationFeeBP) ); tierId++; } } /// @notice Returns a tier by ID /// @param _tierId id of the tier /// @return Tier function tier(uint256 _tierId) external view returns (Tier memory) { ERC7201Storage storage $ = _getStorage(); if (_tierId >= $.tiers.length) revert TierNotExists(); return $.tiers[_tierId]; } /// @notice Returns a tiers count /// @return Tiers count function tiersCount() external view returns (uint256) { return _getStorage().tiers.length; } /// @notice Alters multiple tiers /// @dev We do not enforce to update old vaults with the new tier params, only new ones. /// @param _tierIds array of tier ids to alter /// @param _tierParams array of new tier params function alterTiers( uint256[] calldata _tierIds, TierParams[] calldata _tierParams ) external onlyRole(REGISTRY_ROLE) { if (_tierIds.length != _tierParams.length) revert ArrayLengthMismatch(); ERC7201Storage storage $ = _getStorage(); uint256 length = _tierIds.length; uint256 tiersLength = $.tiers.length; for (uint256 i = 0; i < length; i++) { if (_tierIds[i] >= tiersLength) revert TierNotExists(); _validateParams( _tierIds[i], _tierParams[i].reserveRatioBP, _tierParams[i].forcedRebalanceThresholdBP, _tierParams[i].infraFeeBP, _tierParams[i].liquidityFeeBP, _tierParams[i].reservationFeeBP ); Tier storage tier_ = $.tiers[_tierIds[i]]; tier_.shareLimit = uint96(_tierParams[i].shareLimit); tier_.reserveRatioBP = uint16(_tierParams[i].reserveRatioBP); tier_.forcedRebalanceThresholdBP = uint16(_tierParams[i].forcedRebalanceThresholdBP); tier_.infraFeeBP = uint16(_tierParams[i].infraFeeBP); tier_.liquidityFeeBP = uint16(_tierParams[i].liquidityFeeBP); tier_.reservationFeeBP = uint16(_tierParams[i].reservationFeeBP); emit TierUpdated( _tierIds[i], tier_.shareLimit, tier_.reserveRatioBP, tier_.forcedRebalanceThresholdBP, tier_.infraFeeBP, tier_.liquidityFeeBP, tier_.reservationFeeBP ); } } /// @notice Vault tier change with multi-role confirmation /// @param _vault address of the vault /// @param _requestedTierId id of the tier /// @param _requestedShareLimit share limit to set /// @return bool Whether the tier change was confirmed. /* Legend: V = Vault1.liabilityShares LS = liabilityShares Scheme1 - transfer Vault from default tier to Tier2 ┌──────────────────────────────┐ │ Group 1 │ │ │ ┌────────────────────┐ │ ┌─────────┐ ┌───────────┐ │ │ Tier 1 (default) │ confirm │ │ Tier 2 │ │ Tier 3 │ │ │ LS: -V │ ─────> │ │ LS:+V │ │ │ │ └────────────────────┘ │ └─────────┘ └───────────┘ │ │ │ │ Group1.liabilityShares: +V │ └──────────────────────────────┘ After confirmation: - Tier 1.liabilityShares = -V - Tier 2.liabilityShares = +V - Group1.liabilityShares = +V -------------------------------------------------------------------------- Scheme2 - transfer Vault from Tier2 to Tier3, no need to change group minted shares ┌────────────────────────────────┐ ┌────────────────────────────────┐ │ Group 1 │ │ Group 2 │ │ │ │ │ │ ┌───────────┐ ┌───────────┐ │ │ ┌───────────┐ │ │ │ Tier 2 │ │ Tier 3 │ │ │ │ Tier 4 │ │ │ │ LS:-V │ │ LS:+V │ │ │ │ │ │ │ └───────────┘ └───────────┘ │ │ └───────────┘ │ │ operator1 │ │ operator2 │ └────────────────────────────────┘ └────────────────────────────────┘ After confirmation: - Tier 2.liabilityShares = -V - Tier 3.liabilityShares = +V NB: Cannot change from Tier2 to Tier1, because Tier1 has no group NB: Cannot change from Tier2 to Tier4, because Tier4 has different operator. */ function changeTier(address _vault, uint256 _requestedTierId, uint256 _requestedShareLimit) external returns (bool) { if (_vault == address(0)) revert ZeroArgument("_vault"); ERC7201Storage storage $ = _getStorage(); if (_requestedTierId >= $.tiers.length) revert TierNotExists(); if (_requestedTierId == DEFAULT_TIER_ID) revert CannotChangeToDefaultTier(); VaultHub vaultHub = _vaultHub(); bool isVaultConnected = vaultHub.isVaultConnected(_vault); address vaultOwner = isVaultConnected ? vaultHub.vaultConnection(_vault).owner : IStakingVault(_vault).owner(); address nodeOperator = IStakingVault(_vault).nodeOperator(); uint256 vaultTierId = $.vaultTier[_vault]; if (vaultTierId == _requestedTierId) revert TierAlreadySet(); Tier storage requestedTier = $.tiers[_requestedTierId]; if (nodeOperator != requestedTier.operator) revert TierNotInOperatorGroup(); if (_requestedShareLimit > requestedTier.shareLimit) revert RequestedShareLimitTooHigh(_requestedShareLimit, requestedTier.shareLimit); // store the caller's confirmation; only proceed if the required number of confirmations is met. if (!_collectAndCheckConfirmations(msg.data, vaultOwner, nodeOperator)) return false; uint256 vaultLiabilityShares = vaultHub.liabilityShares(_vault); //check if tier limit is exceeded if (requestedTier.liabilityShares + vaultLiabilityShares > requestedTier.shareLimit) revert TierLimitExceeded(); // if the vault was in the default tier: // - that mean that the vault has no group, so we decrease only the minted shares of the default tier // - but need to check requested group limit exceeded if (vaultTierId == DEFAULT_TIER_ID) { Group storage requestedGroup = $.groups[nodeOperator]; if (requestedGroup.liabilityShares + vaultLiabilityShares > requestedGroup.shareLimit) { revert GroupLimitExceeded(); } requestedGroup.liabilityShares += uint96(vaultLiabilityShares); } Tier storage currentTier = $.tiers[vaultTierId]; currentTier.liabilityShares -= uint96(vaultLiabilityShares); requestedTier.liabilityShares += uint96(vaultLiabilityShares); $.vaultTier[_vault] = _requestedTierId; // Vault may not be connected to VaultHub yet. // There are two possible flows: // 1. Vault is created and connected to VaultHub immediately with the default tier. // In this case, `VaultConnection` is non-zero and updateConnection must be called. // 2. Vault is created, its tier is changed before connecting to VaultHub. // In this case, `VaultConnection` is still zero, and updateConnection must be skipped. // Hence, we update the VaultHub connection only if the vault is already connected. vaultHub.updateConnection( _vault, _requestedShareLimit, requestedTier.reserveRatioBP, requestedTier.forcedRebalanceThresholdBP, requestedTier.infraFeeBP, requestedTier.liquidityFeeBP, requestedTier.reservationFeeBP ); emit TierChanged(_vault, _requestedTierId, _requestedShareLimit); return true; } /// @notice Reset vault's tier to default /// @param _vault address of the vault /// @dev Requires vault's liabilityShares to be zero before resetting the tier function resetVaultTier(address _vault) external { if (msg.sender != LIDO_LOCATOR.vaultHub()) revert NotAuthorized("resetVaultTier", msg.sender); ERC7201Storage storage $ = _getStorage(); if ($.vaultTier[_vault] != DEFAULT_TIER_ID) { $.vaultTier[_vault] = DEFAULT_TIER_ID; emit TierChanged(_vault, DEFAULT_TIER_ID, $.tiers[DEFAULT_TIER_ID].shareLimit); } } // ----------------------------- // MINT / BURN // ----------------------------- /// @notice Mint shares limit check /// @param _vault address of the vault /// @param _amount amount of shares will be minted function onMintedShares( address _vault, uint256 _amount ) external { if (msg.sender != LIDO_LOCATOR.vaultHub()) revert NotAuthorized("onMintedShares", msg.sender); ERC7201Storage storage $ = _getStorage(); uint256 tierId = $.vaultTier[_vault]; Tier storage tier_ = $.tiers[tierId]; uint96 tierLiabilityShares = tier_.liabilityShares; if (tierLiabilityShares + _amount > tier_.shareLimit) revert TierLimitExceeded(); tier_.liabilityShares = tierLiabilityShares + uint96(_amount); if (tierId != DEFAULT_TIER_ID) { Group storage group_ = $.groups[tier_.operator]; uint96 groupMintedShares = group_.liabilityShares; if (groupMintedShares + _amount > group_.shareLimit) revert GroupLimitExceeded(); group_.liabilityShares = groupMintedShares + uint96(_amount); } } /// @notice Burn shares limit check /// @param _vault address of the vault /// @param _amount amount of shares to burn function onBurnedShares( address _vault, uint256 _amount ) external { if (msg.sender != LIDO_LOCATOR.vaultHub()) revert NotAuthorized("burnShares", msg.sender); ERC7201Storage storage $ = _getStorage(); uint256 tierId = $.vaultTier[_vault]; Tier storage tier_ = $.tiers[tierId]; // we skip the check for minted shared underflow, because it's done in the VaultHub.burnShares() tier_.liabilityShares -= uint96(_amount); if (tierId != DEFAULT_TIER_ID) { Group storage group_ = $.groups[tier_.operator]; group_.liabilityShares -= uint96(_amount); } } /// @notice Get vault limits /// @param _vault address of the vault /// @return nodeOperator node operator of the vault /// @return tierId tier id of the vault /// @return shareLimit share limit of the vault /// @return reserveRatioBP reserve ratio of the vault /// @return forcedRebalanceThresholdBP forced rebalance threshold of the vault /// @return infraFeeBP infra fee of the vault /// @return liquidityFeeBP liquidity fee of the vault /// @return reservationFeeBP reservation fee of the vault function vaultInfo(address _vault) external view returns ( address nodeOperator, uint256 tierId, uint256 shareLimit, uint256 reserveRatioBP, uint256 forcedRebalanceThresholdBP, uint256 infraFeeBP, uint256 liquidityFeeBP, uint256 reservationFeeBP ) { ERC7201Storage storage $ = _getStorage(); tierId = $.vaultTier[_vault]; Tier memory t = $.tiers[tierId]; nodeOperator = t.operator; shareLimit = t.shareLimit; reserveRatioBP = t.reserveRatioBP; forcedRebalanceThresholdBP = t.forcedRebalanceThresholdBP; infraFeeBP = t.infraFeeBP; liquidityFeeBP = t.liquidityFeeBP; reservationFeeBP = t.reservationFeeBP; } /// @notice Returns the effective share limit of a vault according to the OperatorGrid and vault share limits /// @param _vault address of the vault /// @return shareLimit effective share limit of the vault function effectiveShareLimit(address _vault) public view returns (uint256) { VaultHub vaultHub = _vaultHub(); uint256 shareLimit = vaultHub.vaultConnection(_vault).shareLimit; uint256 liabilityShares = vaultHub.liabilityShares(_vault); uint256 gridShareLimit = _gridRemainingShareLimit(_vault) + liabilityShares; return Math256.min(gridShareLimit, shareLimit); } /// @notice Returns the remaining share limit in a given tier and group /// @param _vault address of the vault /// @return remaining share limit /// @dev remaining share limit inherits the limits of the vault tier and group, /// and accounts liabilities of other vaults belonging to the same tier and group function _gridRemainingShareLimit(address _vault) internal view returns (uint256) { ERC7201Storage storage $ = _getStorage(); uint256 tierId = $.vaultTier[_vault]; Tier storage t = $.tiers[tierId]; uint256 tierLimit = t.shareLimit; uint256 tierRemaining = tierLimit > t.liabilityShares ? tierLimit - t.liabilityShares : 0; if (tierId == DEFAULT_TIER_ID) return tierRemaining; Group storage g = $.groups[t.operator]; uint256 groupLimit = g.shareLimit; uint256 groupRemaining = groupLimit > g.liabilityShares ? groupLimit - g.liabilityShares : 0; return Math256.min(tierRemaining, groupRemaining); } /// @notice Validates tier parameters /// @param _reserveRatioBP Reserve ratio /// @param _forcedRebalanceThresholdBP Forced rebalance threshold /// @param _infraFeeBP Infra fee /// @param _liquidityFeeBP Liquidity fee /// @param _reservationFeeBP Reservation fee function _validateParams( uint256 _tierId, uint256 _reserveRatioBP, uint256 _forcedRebalanceThresholdBP, uint256 _infraFeeBP, uint256 _liquidityFeeBP, uint256 _reservationFeeBP ) internal pure { if (_reserveRatioBP == 0) revert ZeroArgument("_reserveRatioBP"); if (_reserveRatioBP > TOTAL_BASIS_POINTS) revert ReserveRatioTooHigh(_tierId, _reserveRatioBP, TOTAL_BASIS_POINTS); if (_forcedRebalanceThresholdBP == 0) revert ZeroArgument("_forcedRebalanceThresholdBP"); if (_forcedRebalanceThresholdBP > _reserveRatioBP) revert ForcedRebalanceThresholdTooHigh(_tierId, _forcedRebalanceThresholdBP, _reserveRatioBP); if (_infraFeeBP > MAX_FEE_BP) revert InfraFeeTooHigh(_tierId, _infraFeeBP, MAX_FEE_BP); if (_liquidityFeeBP > MAX_FEE_BP) revert LiquidityFeeTooHigh(_tierId, _liquidityFeeBP, MAX_FEE_BP); if (_reservationFeeBP > MAX_FEE_BP) revert ReservationFeeTooHigh(_tierId, _reservationFeeBP, MAX_FEE_BP); } function _vaultHub() internal view returns (VaultHub) { return VaultHub(payable(LIDO_LOCATOR.vaultHub())); } function _getStorage() private pure returns (ERC7201Storage storage $) { assembly { $.slot := OPERATOR_GRID_STORAGE_LOCATION } } // ----------------------------- // EVENTS // ----------------------------- event GroupAdded(address indexed nodeOperator, uint256 shareLimit); event GroupShareLimitUpdated(address indexed nodeOperator, uint256 shareLimit); event TierAdded( address indexed nodeOperator, uint256 indexed tierId, uint256 shareLimit, uint256 reserveRatioBP, uint256 forcedRebalanceThresholdBP, uint256 infraFeeBP, uint256 liquidityFeeBP, uint256 reservationFeeBP ); event TierChanged(address indexed vault, uint256 indexed tierId, uint256 shareLimit); event TierUpdated( uint256 indexed tierId, uint256 shareLimit, uint256 reserveRatioBP, uint256 forcedRebalanceThresholdBP, uint256 infraFeeBP, uint256 liquidityFeeBP, uint256 reservationFeeBP ); // ----------------------------- // ERRORS // ----------------------------- error NotAuthorized(string operation, address sender); error ZeroArgument(string argument); error GroupExists(); error GroupNotExists(); error GroupLimitExceeded(); error NodeOperatorNotExists(); error TierLimitExceeded(); error TierNotExists(); error TierAlreadySet(); error TierNotInOperatorGroup(); error CannotChangeToDefaultTier(); error ReserveRatioTooHigh(uint256 tierId, uint256 reserveRatioBP, uint256 maxReserveRatioBP); error ForcedRebalanceThresholdTooHigh(uint256 tierId, uint256 forcedRebalanceThresholdBP, uint256 reserveRatioBP); error InfraFeeTooHigh(uint256 tierId, uint256 infraFeeBP, uint256 maxInfraFeeBP); error LiquidityFeeTooHigh(uint256 tierId, uint256 liquidityFeeBP, uint256 maxLiquidityFeeBP); error ReservationFeeTooHigh(uint256 tierId, uint256 reservationFeeBP, uint256 maxReservationFeeBP); error ArrayLengthMismatch(); error RequestedShareLimitTooHigh(uint256 requestedShareLimit, uint256 tierShareLimit); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {Math256} from "contracts/common/lib/Math256.sol"; import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol"; import {ILido} from "contracts/common/interfaces/ILido.sol"; import {IHashConsensus} from "contracts/common/interfaces/IHashConsensus.sol"; import {IStakingVault} from "./interfaces/IStakingVault.sol"; import {IPredepositGuarantee} from "./interfaces/IPredepositGuarantee.sol"; import {OperatorGrid} from "./OperatorGrid.sol"; import {LazyOracle} from "./LazyOracle.sol"; import {PausableUntilWithRoles} from "../utils/PausableUntilWithRoles.sol"; import {RefSlotCache} from "./lib/RefSlotCache.sol"; /// @notice VaultHub is a contract that manages StakingVaults connected to the Lido protocol /// It allows to connect and disconnect vaults, mint and burn stETH using vaults as collateral /// Also, it facilitates the individual per-vault reports from the lazy oracle to the vaults and charges Lido fees /// @author folkyatina contract VaultHub is PausableUntilWithRoles { using RefSlotCache for RefSlotCache.Uint112WithRefSlotCache; using RefSlotCache for RefSlotCache.Int112WithRefSlotCache; // ----------------------------- // STORAGE STRUCTS // ----------------------------- /// @custom:storage-location erc7201:VaultHub struct Storage { /// @notice vault proxy contract codehashes allowed for connecting mapping(bytes32 codehash => bool allowed) codehashes; /// @notice accounting records for each vault mapping(address vault => VaultRecord) records; /// @notice connection parameters for each vault mapping(address vault => VaultConnection) connections; /// @notice obligation values for each vault mapping(address vault => VaultObligations) obligations; /// @notice 1-based array of vaults connected to the hub. index 0 is reserved for not connected vaults address[] vaults; /// @notice amount of bad debt that was internalized from the vault to become the protocol loss RefSlotCache.Uint112WithRefSlotCache badDebtToInternalize; } struct VaultConnection { // ### 1st slot /// @notice address of the vault owner address owner; /// @notice maximum number of stETH shares that can be minted by vault owner uint96 shareLimit; // ### 2nd slot /// @notice index of the vault in the list of vaults. Indexes is guaranteed to be stable only if there was no deletions. /// @dev vaultIndex is always greater than 0 uint96 vaultIndex; /// @notice if true, vault is disconnected and fee is not accrued bool pendingDisconnect; /// @notice share of ether that is locked on the vault as an additional reserve /// e.g RR=30% means that for 1stETH minted 1/(1-0.3)=1.428571428571428571 ETH is locked on the vault uint16 reserveRatioBP; /// @notice if vault's reserve decreases to this threshold, it should be force rebalanced uint16 forcedRebalanceThresholdBP; /// @notice infra fee in basis points uint16 infraFeeBP; /// @notice liquidity fee in basis points uint16 liquidityFeeBP; /// @notice reservation fee in basis points uint16 reservationFeeBP; /// @notice if true, vault owner manually paused the beacon chain deposits bool isBeaconDepositsManuallyPaused; /// 64 bits gap } struct VaultRecord { // ### 1st slot /// @notice latest report for the vault Report report; // ### 2nd slot /// @notice amount of ether that is locked from withdrawal on the vault uint128 locked; /// @notice liability shares of the vault uint96 liabilityShares; // ### 3rd slot /// @notice inOutDelta of the vault (all deposits - all withdrawals) RefSlotCache.Int112WithRefSlotCache inOutDelta; } struct Report { /// @notice total value of the vault uint112 totalValue; /// @notice inOutDelta of the report int112 inOutDelta; /// @notice last 32 bits of the timestamp (in seconds) uint32 timestamp; } /** * Obligations of the vaults towards the Lido protocol. * While any part of those obligations remains unsettled, VaultHub may want to limit what the vault can do. * * Obligations have two types: * 1. Redemptions. Under extreme conditions Lido protocol may rebalance the part of the vault's liability to serve * the Lido Core withdrawal queue requests to guarantee that every stETH is redeemable. Calculated in ether. * 2. Lido fees. Record of infra, liquidity and reservation fees charged to the vault. Charged in ether on every * oracle report. * * Obligations settlement: * - Lido fees are settled by transferring ether to the Lido protocol treasury * - Redemptions are settled by rebalancing the vault or by burning stETH on the vault * - Obligations may be settled manually using the `settleVaultObligations` function * - Obligations try to automatically settle: * - every time oracle report is applied to the vault * - on resume of the beacon chain deposits * - on disconnect initiation * - Lido fees are automatically settled on the final report that completes the disconnection process * * Constraints until obligations settled: * - Beacon chain deposits are paused while unsettled obligations ≥ OBLIGATIONS_THRESHOLD (1 ETH) * - Unsettled obligations can't be withdrawn * - Minting new stETH is limited by unsettled Lido fees (NB: redemptions do not affect minting capacity) * - Vault disconnect is refused until both unsettled redemptions and Lido fees obligations hit zero * * @dev NB: Under extreme conditions, Lido protocol may trigger validator exits to withdraw ether to the vault and * rebalance it to settle redemptions. */ struct VaultObligations { /// @notice cumulative value for Lido fees that were settled on the vault uint128 settledLidoFees; /// @notice current unsettled Lido fees amount uint128 unsettledLidoFees; /// @notice current unsettled redemptions amount uint128 redemptions; } // ----------------------------- // CONSTANTS // ----------------------------- // keccak256(abi.encode(uint256(keccak256("VaultHub")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant STORAGE_LOCATION = 0xb158a1a9015c52036ff69e7937a7bb424e82a8c4cbec5c5309994af06d825300; /// @notice role that allows to connect vaults to the hub bytes32 public constant VAULT_MASTER_ROLE = keccak256("vaults.VaultHub.VaultMasterRole"); /// @notice role that allows to set allowed codehashes bytes32 public constant VAULT_CODEHASH_SET_ROLE = keccak256("vaults.VaultHub.VaultCodehashSetRole"); /// @notice role that allows to accrue Lido Core redemptions on the vault bytes32 public constant REDEMPTION_MASTER_ROLE = keccak256("vaults.VaultHub.RedemptionMasterRole"); /// @notice role that allows to trigger validator exits under extreme conditions bytes32 public constant VALIDATOR_EXIT_ROLE = keccak256("vaults.VaultHub.ValidatorExitRole"); /// @notice role that allows to bail out vaults with bad debt bytes32 public constant BAD_DEBT_MASTER_ROLE = keccak256("vaults.VaultHub.BadDebtMasterRole"); /// @notice amount of ETH that is locked on the vault on connect and can be withdrawn on disconnect only uint256 public constant CONNECT_DEPOSIT = 1 ether; /// @notice The time delta for report freshness check uint256 public constant REPORT_FRESHNESS_DELTA = 2 days; /// @dev basis points base uint256 internal constant TOTAL_BASIS_POINTS = 100_00; /// @notice length of the validator pubkey in bytes uint256 internal constant PUBLIC_KEY_LENGTH = 48; /// @dev max value for fees in basis points - it's about 650% uint256 internal constant MAX_FEE_BP = type(uint16).max; /// @notice codehash of the account with no code bytes32 private constant EMPTY_CODEHASH = keccak256(""); /// @notice no limit for the unsettled obligations on settlement uint256 internal constant MAX_UNSETTLED_ALLOWED = type(uint256).max; /// @notice threshold for the unsettled obligations that will activate the beacon chain deposits pause uint256 internal constant UNSETTLED_THRESHOLD = 1 ether; /// @notice no unsettled obligations allowed on settlement uint256 internal constant NO_UNSETTLED_ALLOWED = 0; // ----------------------------- // IMMUTABLES // ----------------------------- /// @notice limit for a single vault share limit relative to Lido TVL in basis points uint256 public immutable MAX_RELATIVE_SHARE_LIMIT_BP; ILido public immutable LIDO; ILidoLocator public immutable LIDO_LOCATOR; IHashConsensus public immutable CONSENSUS_CONTRACT; /// @param _locator Lido Locator contract /// @param _lido Lido stETH contract /// @param _consensusContract Hash consensus contract /// @param _maxRelativeShareLimitBP Maximum share limit relative to TVL in basis points constructor(ILidoLocator _locator, ILido _lido, IHashConsensus _consensusContract, uint256 _maxRelativeShareLimitBP) { _requireNotZero(_maxRelativeShareLimitBP); _requireLessThanBP(_maxRelativeShareLimitBP, TOTAL_BASIS_POINTS); MAX_RELATIVE_SHARE_LIMIT_BP = _maxRelativeShareLimitBP; LIDO_LOCATOR = _locator; LIDO = _lido; CONSENSUS_CONTRACT = _consensusContract; _disableInitializers(); } /// @dev used to perform rebalance operations receive() external payable {} /// @notice initialize the vault hub /// @param _admin default admin address function initialize(address _admin) external initializer { _requireNotZero(_admin); __AccessControlEnumerable_init(); // the stone in the elevator. index 0 is reserved for not connected vaults _storage().vaults.push(address(0)); _grantRole(DEFAULT_ADMIN_ROLE, _admin); } /// @notice returns the number of vaults connected to the hub /// @dev since index 0 is reserved for not connected vaults, it's always 1 less than the vaults array length function vaultsCount() external view returns (uint256) { return _storage().vaults.length - 1; } /// @notice returns the vault address by its index /// @param _index index of the vault in the 1-based list of vaults. possible range [1, vaultsCount()] /// @dev Indexes is guaranteed to be stable only in one transaction. function vaultByIndex(uint256 _index) external view returns (address) { _requireNotZero(_index); return _storage().vaults[_index]; } /// @return connection parameters struct for the given vault /// @dev it returns empty struct if the vault is not connected to the hub /// @dev it may return connection even if it's pending to be disconnected function vaultConnection(address _vault) external view returns (VaultConnection memory) { return _vaultConnection(_vault); } /// @return the accounting record struct for the given vault /// @dev it returns empty struct if the vault is not connected to the hub function vaultRecord(address _vault) external view returns (VaultRecord memory) { return _vaultRecord(_vault); } /// @return the obligations struct for the given vault /// @dev returns empty struct if the vault is not connected to the hub function vaultObligations(address _vault) external view returns (VaultObligations memory) { return _vaultObligations(_vault); } /// @return true if the vault is connected to the hub function isVaultConnected(address _vault) external view returns (bool) { return _vaultConnection(_vault).vaultIndex != 0; } /// @return total value of the vault /// @dev returns 0 if the vault is not connected function totalValue(address _vault) external view returns (uint256) { return _totalValue(_vaultRecord(_vault)); } /// @return liability shares of the vault /// @dev returns 0 if the vault is not connected function liabilityShares(address _vault) external view returns (uint256) { return _vaultRecord(_vault).liabilityShares; } /// @return locked amount of ether for the vault /// @dev returns 0 if the vault is not connected function locked(address _vault) external view returns (uint256) { return _vaultRecord(_vault).locked; } /// @return the amount of ether that can be locked in the vault given the current total value /// @dev returns 0 if the vault is not connected function maxLockableValue(address _vault) external view returns (uint256) { return _maxLockableValue(_vaultRecord(_vault), _vaultObligations(_vault)); } /// @return the amount of ether that can be instantly withdrawn from the staking vault /// @dev returns 0 if the vault is not connected /// @dev check for `pendingDisconnect = false` before using this function to avoid reverts function withdrawableValue(address _vault) external view returns (uint256) { return _withdrawableValue(_vault, _vaultRecord(_vault)); } /// @return latest report for the vault /// @dev returns empty struct if the vault is not connected function latestReport(address _vault) external view returns (Report memory) { return _vaultRecord(_vault).report; } /// @return true if the report for the vault is fresh, false otherwise /// @dev returns false if the vault is not connected function isReportFresh(address _vault) external view returns (bool) { return _isReportFresh(_vaultRecord(_vault)); } /// @notice checks if the vault is healthy by comparing its total value after applying forced rebalance threshold /// against current liability shares /// @param _vault vault address /// @return true if vault is healthy, false otherwise /// @dev returns true if the vault is not connected function isVaultHealthy(address _vault) external view returns (bool) { return _isVaultHealthy(_vaultConnection(_vault), _vaultRecord(_vault)); } /// @notice calculate shares amount to make the vault healthy using rebalance /// @param _vault vault address /// @return amount of shares to rebalance or UINT256_MAX if it's impossible to make the vault healthy using rebalance /// @dev returns 0 if the vault is not connected function rebalanceShortfall(address _vault) external view returns (uint256) { return _rebalanceShortfall(_vaultConnection(_vault), _vaultRecord(_vault)); } /// @notice amount of bad debt to be internalized to become the protocol loss function badDebtToInternalizeAsOfLastRefSlot() external view returns (uint256) { return _storage().badDebtToInternalize.getValueForLastRefSlot(CONSENSUS_CONTRACT); } /// @notice inOutDelta of the vault as of the last refSlot /// @param _vault vault address /// @return inOutDelta of the vault as of the last refSlot /// @dev returns 0 if the vault is not connected function inOutDeltaAsOfLastRefSlot(address _vault) external view returns (int256) { return _vaultRecord(_vault).inOutDelta.getValueForLastRefSlot(CONSENSUS_CONTRACT); } /// @notice Set if a vault proxy codehash is allowed to be connected to the hub /// @param _codehash vault proxy codehash /// @param _allowed true to add, false to remove /// @dev msg.sender must have VAULT_CODEHASH_SET_ROLE function setAllowedCodehash(bytes32 _codehash, bool _allowed) external onlyRole(VAULT_CODEHASH_SET_ROLE) { _requireNotZero(uint256(_codehash)); if (_codehash == EMPTY_CODEHASH) revert ZeroCodehash(); _storage().codehashes[_codehash] = _allowed; emit AllowedCodehashUpdated(_codehash, _allowed); } /// @notice connects a vault to the hub in permissionless way, get limits from the Operator Grid /// @param _vault vault address /// @dev vault should have transferred ownership to the VaultHub contract function connectVault(address _vault) external whenResumed { _requireNotZero(_vault); IStakingVault vault_ = IStakingVault(_vault); if (vault_.pendingOwner() != address(this)) revert VaultHubNotPendingOwner(_vault); if (vault_.isOssified()) revert VaultOssified(_vault); if (vault_.depositor() != address(_predepositGuarantee())) revert PDGNotDepositor(_vault); ( , // nodeOperatorInTier , // tierId uint256 shareLimit, uint256 reserveRatioBP, uint256 forcedRebalanceThresholdBP, uint256 infraFeeBP, uint256 liquidityFeeBP, uint256 reservationFeeBP ) = _operatorGrid().vaultInfo(_vault); _connectVault(_vault, shareLimit, reserveRatioBP, forcedRebalanceThresholdBP, infraFeeBP, liquidityFeeBP, reservationFeeBP ); IStakingVault(_vault).acceptOwnership(); emit VaultConnected({ vault: _vault, shareLimit: shareLimit, reserveRatioBP: reserveRatioBP, forcedRebalanceThresholdBP: forcedRebalanceThresholdBP, infraFeeBP: infraFeeBP, liquidityFeeBP: liquidityFeeBP, reservationFeeBP: reservationFeeBP }); } /// @notice updates share limit for the vault /// Setting share limit to zero actually pause the vault's ability to mint /// @param _vault vault address /// @param _shareLimit new share limit /// @dev msg.sender must have VAULT_MASTER_ROLE function updateShareLimit(address _vault, uint256 _shareLimit) external onlyRole(VAULT_MASTER_ROLE) { _requireNotZero(_vault); _requireSaneShareLimit(_shareLimit); VaultConnection storage connection = _checkConnection(_vault); connection.shareLimit = uint96(_shareLimit); emit VaultShareLimitUpdated(_vault, _shareLimit); } /// @notice updates fees for the vault /// @param _vault vault address /// @param _infraFeeBP new infra fee in basis points /// @param _liquidityFeeBP new liquidity fee in basis points /// @param _reservationFeeBP new reservation fee in basis points /// @dev msg.sender must have VAULT_MASTER_ROLE function updateVaultFees( address _vault, uint256 _infraFeeBP, uint256 _liquidityFeeBP, uint256 _reservationFeeBP ) external onlyRole(VAULT_MASTER_ROLE) { _requireNotZero(_vault); _requireLessThanBP(_infraFeeBP, MAX_FEE_BP); _requireLessThanBP(_liquidityFeeBP, MAX_FEE_BP); _requireLessThanBP(_reservationFeeBP, MAX_FEE_BP); VaultConnection storage connection = _checkConnection(_vault); uint16 preInfraFeeBP = connection.infraFeeBP; uint16 preLiquidityFeeBP = connection.liquidityFeeBP; uint16 preReservationFeeBP = connection.reservationFeeBP; connection.infraFeeBP = uint16(_infraFeeBP); connection.liquidityFeeBP = uint16(_liquidityFeeBP); connection.reservationFeeBP = uint16(_reservationFeeBP); emit VaultFeesUpdated({ vault: _vault, preInfraFeeBP: preInfraFeeBP, preLiquidityFeeBP: preLiquidityFeeBP, preReservationFeeBP: preReservationFeeBP, infraFeeBP: _infraFeeBP, liquidityFeeBP: _liquidityFeeBP, reservationFeeBP: _reservationFeeBP }); } /// @notice updates the vault's connection parameters /// @dev Reverts if the vault is not healthy as of latest report /// @param _vault vault address /// @param _shareLimit new share limit /// @param _reserveRatioBP new reserve ratio /// @param _forcedRebalanceThresholdBP new forced rebalance threshold /// @param _infraFeeBP new infra fee /// @param _liquidityFeeBP new liquidity fee /// @param _reservationFeeBP new reservation fee function updateConnection( address _vault, uint256 _shareLimit, uint256 _reserveRatioBP, uint256 _forcedRebalanceThresholdBP, uint256 _infraFeeBP, uint256 _liquidityFeeBP, uint256 _reservationFeeBP ) external { _requireSender(address(_operatorGrid())); _requireSaneShareLimit(_shareLimit); VaultConnection storage connection = _checkConnection(_vault); VaultRecord storage record = _vaultRecord(_vault); uint256 totalValue_ = _totalValue(record); uint256 liabilityShares_ = record.liabilityShares; if (_isThresholdBreached(totalValue_, liabilityShares_, _reserveRatioBP)) { revert VaultMintingCapacityExceeded(_vault, totalValue_, liabilityShares_, _reserveRatioBP); } connection.shareLimit = uint96(_shareLimit); connection.reserveRatioBP = uint16(_reserveRatioBP); connection.forcedRebalanceThresholdBP = uint16(_forcedRebalanceThresholdBP); connection.infraFeeBP = uint16(_infraFeeBP); connection.liquidityFeeBP = uint16(_liquidityFeeBP); connection.reservationFeeBP = uint16(_reservationFeeBP); emit VaultConnectionUpdated({ vault: _vault, shareLimit: _shareLimit, reserveRatioBP: _reserveRatioBP, forcedRebalanceThresholdBP: _forcedRebalanceThresholdBP, infraFeeBP: _infraFeeBP, liquidityFeeBP: _liquidityFeeBP, reservationFeeBP: _reservationFeeBP }); } /// @notice disconnect a vault from the hub /// @param _vault vault address /// @dev msg.sender must have VAULT_MASTER_ROLE /// @dev vault's `liabilityShares` should be zero function disconnect(address _vault) external onlyRole(VAULT_MASTER_ROLE) { _initiateDisconnection(_vault, _checkConnection(_vault), _vaultRecord(_vault)); emit VaultDisconnectInitiated(_vault); } /// @notice update of the vault data by the lazy oracle report /// @param _vault the address of the vault /// @param _reportTimestamp the timestamp of the report (last 32 bits of it) /// @param _reportTotalValue the total value of the vault /// @param _reportInOutDelta the inOutDelta of the vault /// @param _reportCumulativeLidoFees the cumulative Lido fees of the vault /// @param _reportLiabilityShares the liabilityShares of the vault function applyVaultReport( address _vault, uint256 _reportTimestamp, uint256 _reportTotalValue, int256 _reportInOutDelta, uint256 _reportCumulativeLidoFees, uint256 _reportLiabilityShares, uint256 _reportSlashingReserve ) external whenResumed { _requireSender(address(_lazyOracle())); VaultConnection storage connection = _vaultConnection(_vault); VaultRecord storage record = _vaultRecord(_vault); VaultObligations storage obligations = _vaultObligations(_vault); _checkAndUpdateLidoFeesObligations(_vault, obligations, _reportCumulativeLidoFees); if (connection.pendingDisconnect) { if (_reportSlashingReserve == 0 && record.liabilityShares == 0) { _settleObligations(_vault, record, obligations, NO_UNSETTLED_ALLOWED); IStakingVault(_vault).transferOwnership(connection.owner); _deleteVault(_vault, connection); emit VaultDisconnectCompleted(_vault); return; } else { // we abort the disconnect process as there is a slashing conflict yet to be resolved connection.pendingDisconnect = false; emit VaultDisconnectAborted(_vault, _reportSlashingReserve); } } _applyVaultReport( record, connection, _reportTimestamp, _reportTotalValue, _reportLiabilityShares, _reportInOutDelta ); emit VaultReportApplied({ vault: _vault, reportTimestamp: _reportTimestamp, reportTotalValue: _reportTotalValue, reportInOutDelta: _reportInOutDelta, reportCumulativeLidoFees: _reportCumulativeLidoFees, reportLiabilityShares: _reportLiabilityShares, reportSlashingReserve: _reportSlashingReserve }); _settleObligations(_vault, record, obligations, MAX_UNSETTLED_ALLOWED); _checkAndUpdateBeaconChainDepositsPause(_vault, connection, record); } /// @notice Transfer the bad debt from the donor vault to the acceptor vault /// @param _badDebtVault address of the vault that has the bad debt /// @param _vaultAcceptor address of the vault that will accept the bad debt /// @param _maxSharesToSocialize maximum amount of shares to socialize /// @dev msg.sender must have BAD_DEBT_MASTER_ROLE function socializeBadDebt( address _badDebtVault, address _vaultAcceptor, uint256 _maxSharesToSocialize ) external onlyRole(BAD_DEBT_MASTER_ROLE) { _requireNotZero(_badDebtVault); _requireNotZero(_vaultAcceptor); _requireNotZero(_maxSharesToSocialize); if (_nodeOperator(_vaultAcceptor) != _nodeOperator(_badDebtVault)) revert BadDebtSocializationNotAllowed(); VaultConnection storage badDebtConnection = _vaultConnection(_badDebtVault); _requireConnected(badDebtConnection, _badDebtVault); // require connected but may be pending disconnect uint256 badDebtToSocialize = _writeOffBadDebt({ _vault: _badDebtVault, _record: _vaultRecord(_badDebtVault), _maxSharesToWriteOff: _maxSharesToSocialize }); VaultConnection storage connectionAcceptor = _vaultConnection(_vaultAcceptor); _requireConnected(connectionAcceptor, _vaultAcceptor); VaultRecord storage recordAcceptor = _vaultRecord(_vaultAcceptor); _increaseLiability({ _vault: _vaultAcceptor, _record: recordAcceptor, _amountOfShares: badDebtToSocialize, _reserveRatioBP: connectionAcceptor.reserveRatioBP, _maxMintableRatioBP: TOTAL_BASIS_POINTS, // maxMintableRatio up to 100% of total value _shareLimit: _getSharesByPooledEth(recordAcceptor.locked) // we can occupy all the locked amount }); emit BadDebtSocialized(_badDebtVault, _vaultAcceptor, badDebtToSocialize); } /// @notice Internalize the bad debt to the protocol /// @param _badDebtVault address of the vault that has the bad debt /// @param _maxSharesToInternalize maximum amount of shares to internalize /// @dev msg.sender must have BAD_DEBT_MASTER_ROLE function internalizeBadDebt( address _badDebtVault, uint256 _maxSharesToInternalize ) external onlyRole(BAD_DEBT_MASTER_ROLE) { _requireNotZero(_badDebtVault); _requireNotZero(_maxSharesToInternalize); VaultConnection storage badDebtConnection = _vaultConnection(_badDebtVault); _requireConnected(badDebtConnection, _badDebtVault); uint256 badDebtToInternalize = _writeOffBadDebt({ _vault: _badDebtVault, _record: _vaultRecord(_badDebtVault), _maxSharesToWriteOff: _maxSharesToInternalize }); // internalize the bad debt to the protocol _storage().badDebtToInternalize = _storage().badDebtToInternalize.withValueIncrease({ _consensus: CONSENSUS_CONTRACT, _increment: uint112(badDebtToInternalize) }); emit BadDebtWrittenOffToBeInternalized(_badDebtVault, badDebtToInternalize); } /// @notice Reset the internalized bad debt to zero /// @dev msg.sender must be the accounting contract function decreaseInternalizedBadDebt(uint256 _amountOfShares) external { _requireSender(LIDO_LOCATOR.accounting()); // don't cache previous value, we don't need it for sure _storage().badDebtToInternalize.value -= uint112(_amountOfShares); } /// @notice transfer the ownership of the vault to a new owner without disconnecting it from the hub /// @param _vault vault address /// @param _newOwner new owner address /// @dev msg.sender should be vault's owner function transferVaultOwnership(address _vault, address _newOwner) external { _requireNotZero(_newOwner); VaultConnection storage connection = _checkConnection(_vault); address oldOwner = connection.owner; _requireSender(oldOwner); connection.owner = _newOwner; emit VaultOwnershipTransferred({ vault: _vault, newOwner: _newOwner, oldOwner: oldOwner }); } /// @notice disconnects a vault from the hub /// @param _vault vault address /// @dev msg.sender should be vault's owner /// @dev vault's `liabilityShares` should be zero function voluntaryDisconnect(address _vault) external whenResumed { VaultConnection storage connection = _checkConnectionAndOwner(_vault); _initiateDisconnection(_vault, connection, _vaultRecord(_vault)); emit VaultDisconnectInitiated(_vault); } /// @notice funds the vault passing ether as msg.value /// @param _vault vault address /// @dev msg.sender should be vault's owner function fund(address _vault) external payable whenResumed { _requireNotZero(_vault); VaultConnection storage connection = _vaultConnection(_vault); if (connection.vaultIndex == 0) revert NotConnectedToHub(_vault); if (msg.sender != connection.owner) revert NotAuthorized(); _updateInOutDelta(_vault, _vaultRecord(_vault), int112(int256(msg.value))); IStakingVault(_vault).fund{value: msg.value}(); } /// @notice withdraws ether from the vault to the recipient address /// @param _vault vault address /// @param _recipient recipient address /// @param _ether amount of ether to withdraw /// @dev msg.sender should be vault's owner function withdraw(address _vault, address _recipient, uint256 _ether) external whenResumed { _checkConnectionAndOwner(_vault); VaultRecord storage record = _vaultRecord(_vault); _requireFreshReport(_vault, record); uint256 withdrawable = _withdrawableValue(_vault, record); if (_ether > withdrawable) revert AmountExceedsWithdrawableValue(_vault, withdrawable, _ether); _withdraw(_vault, record, _recipient, _ether); } /// @notice Rebalances StakingVault by withdrawing ether to VaultHub /// @param _vault vault address /// @param _shares amount of shares to rebalance /// @dev msg.sender should be vault's owner function rebalance(address _vault, uint256 _shares) external whenResumed { _requireNotZero(_shares); _checkConnectionAndOwner(_vault); _rebalance(_vault, _vaultRecord(_vault), _shares); } /// @notice mint StETH shares backed by vault external balance to the receiver address /// @param _vault vault address /// @param _recipient address of the receiver /// @param _amountOfShares amount of stETH shares to mint function mintShares(address _vault, address _recipient, uint256 _amountOfShares) external whenResumed { _requireNotZero(_recipient); _requireNotZero(_amountOfShares); VaultConnection storage connection = _checkConnectionAndOwner(_vault); VaultRecord storage record = _vaultRecord(_vault); _requireFreshReport(_vault, record); uint256 reserveRatioBP = connection.reserveRatioBP; _increaseLiability({ _vault: _vault, _record: record, _amountOfShares: _amountOfShares, _reserveRatioBP: reserveRatioBP, _maxMintableRatioBP: TOTAL_BASIS_POINTS - reserveRatioBP, _shareLimit: connection.shareLimit }); LIDO.mintExternalShares(_recipient, _amountOfShares); emit MintedSharesOnVault(_vault, _amountOfShares, record.locked); } /// @notice burn steth shares from the balance of the VaultHub contract /// @param _vault vault address /// @param _amountOfShares amount of shares to burn /// @dev msg.sender should be vault's owner /// @dev this function is designed to be used by the smart contract, for EOA see `transferAndBurnShares` function burnShares(address _vault, uint256 _amountOfShares) public whenResumed { _requireNotZero(_amountOfShares); _checkConnectionAndOwner(_vault); VaultRecord storage record = _vaultRecord(_vault); _decreaseLiability(_vault, record, _amountOfShares); LIDO.burnExternalShares(_amountOfShares); emit BurnedSharesOnVault(_vault, _amountOfShares); } /// @notice separate burn function for EOA vault owners; requires vaultHub to be approved to transfer stETH /// @param _vault vault address /// @param _amountOfShares amount of shares to transfer and burn /// @dev msg.sender should be vault's owner function transferAndBurnShares(address _vault, uint256 _amountOfShares) external { LIDO.transferSharesFrom(msg.sender, address(this), _amountOfShares); burnShares(_vault, _amountOfShares); } /// @notice pauses beacon chain deposits for the vault /// @param _vault vault address /// @dev msg.sender should be vault's owner function pauseBeaconChainDeposits(address _vault) external { VaultConnection storage connection = _checkConnectionAndOwner(_vault); connection.isBeaconDepositsManuallyPaused = true; IStakingVault(_vault).pauseBeaconChainDeposits(); } /// @notice resumes beacon chain deposits for the vault /// @param _vault vault address /// @dev msg.sender should be vault's owner function resumeBeaconChainDeposits(address _vault) external { VaultConnection storage connection = _checkConnectionAndOwner(_vault); VaultRecord storage record = _vaultRecord(_vault); if (!_isVaultHealthy(connection, record)) revert UnhealthyVaultCannotDeposit(_vault); _settleObligations(_vault, record, _vaultObligations(_vault), UNSETTLED_THRESHOLD); connection.isBeaconDepositsManuallyPaused = false; IStakingVault(_vault).resumeBeaconChainDeposits(); } /// @notice Emits a request event for the node operator to perform validator exit /// @param _vault vault address /// @param _pubkeys array of public keys of the validators to exit /// @dev msg.sender should be vault's owner function requestValidatorExit(address _vault, bytes calldata _pubkeys) external { _checkConnectionAndOwner(_vault); IStakingVault(_vault).requestValidatorExit(_pubkeys); } /// @notice Triggers validator withdrawals for the vault using EIP-7002 /// @param _vault vault address /// @param _pubkeys array of public keys of the validators to withdraw from /// @param _amounts array of amounts to withdraw from each validator (0 for full withdrawal) /// @param _refundRecipient address that will receive the refund for transaction costs /// @dev msg.sender should be vault's owner function triggerValidatorWithdrawals( address _vault, bytes calldata _pubkeys, uint64[] calldata _amounts, address _refundRecipient ) external payable { VaultConnection storage connection = _checkConnectionAndOwner(_vault); VaultRecord storage record = _vaultRecord(_vault); VaultObligations storage obligations = _vaultObligations(_vault); /// @dev NB: Disallow partial withdrawals when the vault is unhealthy or has redemptions over the threshold /// in order to prevent the vault owner from clogging the consensus layer withdrawal queue /// front-running and delaying the forceful validator exits required for rebalancing the vault, /// unless the requested amount of withdrawals is enough to recover the vault to healthy state and /// settle the unsettled obligations if (!_isVaultHealthy(connection, record) || obligations.redemptions >= UNSETTLED_THRESHOLD) { uint256 minPartialAmount = type(uint256).max; for (uint256 i = 0; i < _amounts.length; i++) { if (_amounts[i] > 0 && _amounts[i] < minPartialAmount) minPartialAmount = _amounts[i]; } if (minPartialAmount < type(uint256).max) { uint256 currentVaultBalance = _vault.balance; uint256 required = _totalUnsettledObligations(obligations) + _rebalanceShortfall(connection, record); uint256 amountToCover = required > currentVaultBalance ? required - currentVaultBalance : 0; if (minPartialAmount < amountToCover) revert PartialValidatorWithdrawalNotAllowed(); } } IStakingVault(_vault).triggerValidatorWithdrawals{value: msg.value}(_pubkeys, _amounts, _refundRecipient); } /// @notice Triggers validator full withdrawals for the vault using EIP-7002 permissionlessly if the vault is /// unhealthy or has redemptions obligation over the threshold /// @param _vault address of the vault to exit validators from /// @param _pubkeys array of public keys of the validators to exit /// @param _refundRecipient address that will receive the refund for transaction costs /// @dev When the vault becomes unhealthy, trusted actor with the role can force its validators to exit the beacon chain /// This returns the vault's deposited ETH back to vault's balance and allows to rebalance the vault function forceValidatorExit( address _vault, bytes calldata _pubkeys, address _refundRecipient ) external payable onlyRole(VALIDATOR_EXIT_ROLE) { VaultConnection storage connection = _checkConnection(_vault); VaultRecord storage record = _vaultRecord(_vault); if ( _isVaultHealthy(connection, record) && // Check if the vault has redemptions under the threshold, or enough balance to cover the redemptions fully _vaultObligations(_vault).redemptions < Math256.max(UNSETTLED_THRESHOLD, _vault.balance) ) { revert ForcedValidatorExitNotAllowed(); } uint64[] memory amounts = new uint64[](0); IStakingVault(_vault).triggerValidatorWithdrawals{value: msg.value}(_pubkeys, amounts, _refundRecipient); emit ForcedValidatorExitTriggered(_vault, _pubkeys, _refundRecipient); } /// @notice Permissionless rebalance for unhealthy vaults /// @param _vault vault address /// @dev rebalance all available amount of ether until the vault is healthy function forceRebalance(address _vault) external { VaultConnection storage connection = _checkConnection(_vault); VaultRecord storage record = _vaultRecord(_vault); uint256 sharesToRebalance = Math256.min( _rebalanceShortfall(connection, record), _getSharesByPooledEth(_vault.balance) ); if (sharesToRebalance == 0) revert AlreadyHealthy(_vault); _rebalance(_vault, record, sharesToRebalance); } /// @notice Accrues a redemption obligation on the vault under extreme conditions /// @param _vault The address of the vault /// @param _redemptionsValue The value of the redemptions obligation function setVaultRedemptions(address _vault, uint256 _redemptionsValue) external onlyRole(REDEMPTION_MASTER_ROLE) { VaultRecord storage record = _vaultRecord(_vault); uint256 liabilityShares_ = record.liabilityShares; // This function may intentionally perform no action in some cases, as these are EasyTrack motions if (liabilityShares_ > 0) { uint256 newRedemptions = Math256.min(_redemptionsValue, _getPooledEthBySharesRoundUp(liabilityShares_)); _vaultObligations(_vault).redemptions = uint128(newRedemptions); emit RedemptionsUpdated(_vault, newRedemptions); _checkAndUpdateBeaconChainDepositsPause(_vault, _vaultConnection(_vault), record); } else { emit RedemptionsNotSet(_vault, _redemptionsValue); } } /// @notice Allows permissionless full or partial settlement of unsettled obligations on the vault /// @param _vault The address of the vault function settleVaultObligations(address _vault) external whenResumed { if (_vault.balance == 0) revert ZeroBalance(); VaultRecord storage record = _vaultRecord(_vault); _settleObligations(_vault, record, _vaultObligations(_vault), MAX_UNSETTLED_ALLOWED); _checkAndUpdateBeaconChainDepositsPause(_vault, _vaultConnection(_vault), record); } /// @notice Proves that validators unknown to PDG have correct WC to participate in the vault /// @param _vault vault address /// @param _witness ValidatorWitness struct proving validator WC belonging to staking vault function proveUnknownValidatorToPDG( address _vault, IPredepositGuarantee.ValidatorWitness calldata _witness ) external { _checkConnectionAndOwner(_vault); _predepositGuarantee().proveUnknownValidator(_witness, IStakingVault(_vault)); } /// @notice Compensates disproven predeposit from PDG to the recipient /// @param _vault vault address /// @param _pubkey pubkey of the validator /// @param _recipient address to compensate the disproven validator predeposit to /// @return amount of compensated ether function compensateDisprovenPredepositFromPDG( address _vault, bytes calldata _pubkey, address _recipient ) external returns (uint256) { _checkConnectionAndOwner(_vault); return _predepositGuarantee().compensateDisprovenPredeposit(_pubkey, _recipient); } function _connectVault( address _vault, uint256 _shareLimit, uint256 _reserveRatioBP, uint256 _forcedRebalanceThresholdBP, uint256 _infraFeeBP, uint256 _liquidityFeeBP, uint256 _reservationFeeBP ) internal { _requireSaneShareLimit(_shareLimit); _requireNotZero(_reserveRatioBP); _requireLessThanBP(_reserveRatioBP, TOTAL_BASIS_POINTS); _requireNotZero(_forcedRebalanceThresholdBP); _requireLessThanBP(_forcedRebalanceThresholdBP, _reserveRatioBP); _requireLessThanBP(_infraFeeBP, MAX_FEE_BP); _requireLessThanBP(_liquidityFeeBP, MAX_FEE_BP); _requireLessThanBP(_reservationFeeBP, MAX_FEE_BP); VaultConnection memory connection = _vaultConnection(_vault); if (connection.pendingDisconnect) revert VaultIsDisconnecting(_vault); if (connection.vaultIndex != 0) revert AlreadyConnected(_vault, connection.vaultIndex); bytes32 codehash = address(_vault).codehash; if (!_storage().codehashes[codehash]) revert CodehashNotAllowed(_vault, codehash); uint256 vaultBalance = _vault.balance; if (vaultBalance < CONNECT_DEPOSIT) revert VaultInsufficientBalance(_vault, vaultBalance, CONNECT_DEPOSIT); // Connecting a new vault with totalValue == balance VaultRecord memory record = VaultRecord({ report: Report({ totalValue: uint112(vaultBalance), inOutDelta: int112(int256(vaultBalance)), timestamp: uint32(_lazyOracle().latestReportTimestamp()) }), locked: uint128(CONNECT_DEPOSIT), liabilityShares: 0, inOutDelta: RefSlotCache.Int112WithRefSlotCache({ value: int112(int256(vaultBalance)), valueOnRefSlot: 0, refSlot: 0 }) }); connection = VaultConnection({ owner: IStakingVault(_vault).owner(), shareLimit: uint96(_shareLimit), vaultIndex: uint96(_storage().vaults.length), pendingDisconnect: false, reserveRatioBP: uint16(_reserveRatioBP), forcedRebalanceThresholdBP: uint16(_forcedRebalanceThresholdBP), infraFeeBP: uint16(_infraFeeBP), liquidityFeeBP: uint16(_liquidityFeeBP), reservationFeeBP: uint16(_reservationFeeBP), isBeaconDepositsManuallyPaused: false }); _addVault(_vault, connection, record); } function _initiateDisconnection( address _vault, VaultConnection storage _connection, VaultRecord storage _record ) internal { uint256 liabilityShares_ = _record.liabilityShares; if (liabilityShares_ > 0) { revert NoLiabilitySharesShouldBeLeft(_vault, liabilityShares_); } _record.locked = 0; // unlock the connection deposit to allow fees settlement _settleObligations(_vault, _record, _vaultObligations(_vault), NO_UNSETTLED_ALLOWED); _connection.pendingDisconnect = true; _operatorGrid().resetVaultTier(_vault); } function _applyVaultReport( VaultRecord storage _record, VaultConnection storage _connection, uint256 _reportTimestamp, uint256 _reportTotalValue, uint256 _reportLiabilityShares, int256 _reportInOutDelta ) internal { uint256 liabilityShares_ = Math256.max(_record.liabilityShares, _reportLiabilityShares); uint256 liability = _getPooledEthBySharesRoundUp(liabilityShares_); uint256 lockedEther = Math256.max( liability * TOTAL_BASIS_POINTS / (TOTAL_BASIS_POINTS - _connection.reserveRatioBP), CONNECT_DEPOSIT ); _record.locked = uint128(lockedEther); _record.report = Report({ totalValue: uint112(_reportTotalValue), inOutDelta: int112(_reportInOutDelta), timestamp: uint32(_reportTimestamp) }); } function _rebalance(address _vault, VaultRecord storage _record, uint256 _shares) internal { uint256 valueToRebalance = _getPooledEthBySharesRoundUp(_shares); uint256 totalValue_ = _totalValue(_record); if (valueToRebalance > totalValue_) revert RebalanceAmountExceedsTotalValue(totalValue_, valueToRebalance); _decreaseLiability(_vault, _record, _shares); _withdraw(_vault, _record, address(this), valueToRebalance); _rebalanceExternalEtherToInternal(valueToRebalance); emit VaultRebalanced(_vault, _shares, valueToRebalance); } function _withdraw( address _vault, VaultRecord storage _record, address _recipient, uint256 _amount ) internal { _updateInOutDelta(_vault, _record, -int112(int256(_amount))); IStakingVault(_vault).withdraw(_recipient, _amount); } function _increaseLiability( address _vault, VaultRecord storage _record, uint256 _amountOfShares, uint256 _reserveRatioBP, uint256 _maxMintableRatioBP, uint256 _shareLimit ) internal { uint256 sharesAfterMint = _record.liabilityShares + _amountOfShares; if (sharesAfterMint > _shareLimit) revert ShareLimitExceeded(_vault, sharesAfterMint, _shareLimit); uint256 stETHAfterMint = _getPooledEthBySharesRoundUp(sharesAfterMint); uint256 maxLockableValue_ = _maxLockableValue(_record, _vaultObligations(_vault)); uint256 maxMintableEther = (maxLockableValue_ * _maxMintableRatioBP) / TOTAL_BASIS_POINTS; if (stETHAfterMint > maxMintableEther) { revert InsufficientValueToMint(_vault, maxLockableValue_); } // Calculate the minimum ETH that needs to be locked in the vault to maintain the reserve ratio uint256 etherToLock = (stETHAfterMint * TOTAL_BASIS_POINTS) / (TOTAL_BASIS_POINTS - _reserveRatioBP); if (etherToLock > _record.locked) { _record.locked = uint128(etherToLock); } _record.liabilityShares = uint96(sharesAfterMint); _operatorGrid().onMintedShares(_vault, _amountOfShares); } function _decreaseLiability(address _vault, VaultRecord storage _record, uint256 _amountOfShares) internal { uint256 liabilityShares_ = _record.liabilityShares; if (liabilityShares_ < _amountOfShares) revert InsufficientSharesToBurn(_vault, liabilityShares_); _record.liabilityShares = uint96(liabilityShares_ - _amountOfShares); _decreaseRedemptions(_vault, _amountOfShares); _operatorGrid().onBurnedShares(_vault, _amountOfShares); } function _writeOffBadDebt( address _vault, VaultRecord storage _record, uint256 _maxSharesToWriteOff ) internal returns (uint256 badDebtWrittenOff) { uint256 liabilityShares_ = _record.liabilityShares; uint256 totalValueShares = _getSharesByPooledEth(_totalValue(_record)); if (totalValueShares > liabilityShares_) { revert NoBadDebtToWriteOff(_vault, totalValueShares, liabilityShares_); } badDebtWrittenOff = Math256.min(liabilityShares_ - totalValueShares, _maxSharesToWriteOff); _decreaseLiability(_vault, _record, badDebtWrittenOff); } function _rebalanceShortfall( VaultConnection storage _connection, VaultRecord storage _record ) internal view returns (uint256) { uint256 totalValue_ = _totalValue(_record); uint256 liabilityShares_ = _record.liabilityShares; bool isHealthy = !_isThresholdBreached( totalValue_, liabilityShares_, _connection.forcedRebalanceThresholdBP ); // Health vault do not need to rebalance if (isHealthy) { return 0; } uint256 reserveRatioBP = _connection.reserveRatioBP; uint256 maxMintableRatio = (TOTAL_BASIS_POINTS - reserveRatioBP); uint256 sharesByTotalValue = _getSharesByPooledEth(totalValue_); // Impossible to rebalance a vault with bad debt if (liabilityShares_ >= sharesByTotalValue) { // return MAX_UINT_256 return type(uint256).max; } // Solve the equation for X: // LS - liabilityShares, TV - sharesByTotalValue // MR - maxMintableRatio, 100 - TOTAL_BASIS_POINTS, RR - reserveRatio // X - amount of shares that should be withdrawn (TV - X) and used to repay the debt (LS - X) // to reduce the LS/TVS ratio back to MR // (LS - X) / (TV - X) = MR / 100 // (LS - X) * 100 = (TV - X) * MR // LS * 100 - X * 100 = TV * MR - X * MR // X * MR - X * 100 = TV * MR - LS * 100 // X * (MR - 100) = TV * MR - LS * 100 // X = (TV * MR - LS * 100) / (MR - 100) // X = (LS * 100 - TV * MR) / (100 - MR) // RR = 100 - MR // X = (LS * 100 - TV * MR) / RR return (liabilityShares_ * TOTAL_BASIS_POINTS - sharesByTotalValue * maxMintableRatio) / reserveRatioBP; } function _totalValue(VaultRecord storage _record) internal view returns (uint256) { Report memory report = _record.report; return uint256(int256(uint256(report.totalValue)) + _record.inOutDelta.value - report.inOutDelta); } function _maxLockableValue(VaultRecord storage _record, VaultObligations storage _obligations) internal view returns (uint256) { return _totalValue(_record) - _obligations.unsettledLidoFees; } function _isReportFresh(VaultRecord storage _record) internal view returns (bool) { uint256 latestReportTimestamp = _lazyOracle().latestReportTimestamp(); return // check if AccountingOracle brought fresh report uint32(latestReportTimestamp) == _record.report.timestamp && // if Accounting Oracle stop bringing the report, last report is fresh for 2 days block.timestamp - latestReportTimestamp < REPORT_FRESHNESS_DELTA; } function _isVaultHealthy( VaultConnection storage _connection, VaultRecord storage _record ) internal view returns (bool) { return !_isThresholdBreached( _totalValue(_record), _record.liabilityShares, _connection.forcedRebalanceThresholdBP ); } /// @dev Returns true if the vault liability breached the given threshold (inverted) function _isThresholdBreached( uint256 _vaultTotalValue, uint256 _vaultLiabilityShares, uint256 _thresholdBP ) internal view returns (bool) { uint256 liability = _getPooledEthBySharesRoundUp(_vaultLiabilityShares); return liability > _vaultTotalValue * (TOTAL_BASIS_POINTS - _thresholdBP) / TOTAL_BASIS_POINTS; } function _addVault(address _vault, VaultConnection memory _connection, VaultRecord memory _record) internal { Storage storage $ = _storage(); $.vaults.push(_vault); $.connections[_vault] = _connection; $.records[_vault] = _record; } function _deleteVault(address _vault, VaultConnection storage _connection) internal { Storage storage $ = _storage(); uint96 vaultIndex = _connection.vaultIndex; address lastVault = $.vaults[$.vaults.length - 1]; $.connections[lastVault].vaultIndex = vaultIndex; $.vaults[vaultIndex] = lastVault; $.vaults.pop(); delete $.connections[_vault]; delete $.records[_vault]; delete $.obligations[_vault]; } function _checkConnectionAndOwner(address _vault) internal view returns (VaultConnection storage connection) { connection = _checkConnection(_vault); _requireSender(connection.owner); } function _checkConnection(address _vault) internal view returns (VaultConnection storage) { _requireNotZero(_vault); VaultConnection storage connection = _vaultConnection(_vault); _requireConnected(connection, _vault); if (connection.pendingDisconnect) revert VaultIsDisconnecting(_vault); return connection; } /// @dev Caches the inOutDelta of the latest refSlot and updates the value function _updateInOutDelta(address _vault, VaultRecord storage _record, int112 _increment) internal { _record.inOutDelta = _record.inOutDelta.withValueIncrease({ _consensus: CONSENSUS_CONTRACT, _increment: _increment }); emit VaultInOutDeltaUpdated(_vault, _record.inOutDelta.value); } /** * @notice Updates the unsettled Lido fees obligations based on the report cumulative Lido fees * @param _vault The address of the vault * @param _reportCumulativeLidoFees The cumulative Lido fees reported in the report */ function _checkAndUpdateLidoFeesObligations( address _vault, VaultObligations storage _obligations, uint256 _reportCumulativeLidoFees ) internal { uint256 cumulativeSettledLidoFees = _obligations.settledLidoFees; uint256 cumulativeLidoFees = cumulativeSettledLidoFees + _obligations.unsettledLidoFees; if (_reportCumulativeLidoFees < cumulativeLidoFees) { revert InvalidFees(_vault, _reportCumulativeLidoFees, cumulativeLidoFees); } // update unsettled lido fees uint256 unsettledLidoFees = _reportCumulativeLidoFees - cumulativeSettledLidoFees; if (unsettledLidoFees != _obligations.unsettledLidoFees) { _obligations.unsettledLidoFees = uint128(unsettledLidoFees); emit LidoFeesUpdated(_vault, unsettledLidoFees, cumulativeSettledLidoFees); } } /** * @notice Calculates a settlement plan based on vault balance and obligations * @param _vault The address of the vault * @param _record The record of the vault * @param _obligations The obligations of the vault to be settled * @return valueToRebalance The ETH amount to be rebalanced for redemptions * @return sharesToRebalance The shares to be rebalanced for redemptions * @return valueToTransferToLido The ETH amount to be sent to the Lido * @return unsettledRedemptions The remaining redemptions after the planned settlement * @return unsettledLidoFees The remaining Lido fees after the planned settlement * @return totalUnsettled The total ETH value of obligations remaining after the planned settlement */ function _planSettlement( address _vault, VaultRecord storage _record, VaultObligations storage _obligations ) internal view returns ( uint256 valueToRebalance, uint256 sharesToRebalance, uint256 valueToTransferToLido, uint256 unsettledRedemptions, uint256 unsettledLidoFees, uint256 totalUnsettled ) { (valueToRebalance, sharesToRebalance, unsettledRedemptions) = _planRebalance(_vault, _record, _obligations); (valueToTransferToLido, unsettledLidoFees) = _planLidoTransfer(_vault, _record, _obligations, valueToRebalance); totalUnsettled = unsettledRedemptions + unsettledLidoFees; } /** * @notice Plans the amounts and shares to rebalance for redemptions * @param _vault The address of the vault * @param _record The record of the vault * @param _obligations The obligations of the vault * @return valueToRebalance The ETH amount to be rebalanced for redemptions * @return sharesToRebalance The shares to be rebalanced for redemptions * @return unsettledRedemptions The remaining redemptions after the planned settlement */ function _planRebalance( address _vault, VaultRecord storage _record, VaultObligations storage _obligations ) internal view returns (uint256 valueToRebalance, uint256 sharesToRebalance, uint256 unsettledRedemptions) { uint256 redemptionShares = _getSharesByPooledEth(_obligations.redemptions); uint256 maxRedemptionsValue = _getPooledEthBySharesRoundUp(redemptionShares); // if the max redemptions value is less than the redemptions, we need to round up the redemptions shares if (maxRedemptionsValue < _obligations.redemptions) redemptionShares += 1; uint256 cappedRedemptionsShares = Math256.min(_record.liabilityShares, redemptionShares); sharesToRebalance = Math256.min(cappedRedemptionsShares, _getSharesByPooledEth(_vault.balance)); valueToRebalance = _getPooledEthBySharesRoundUp(sharesToRebalance); unsettledRedemptions = _getPooledEthBySharesRoundUp(redemptionShares - sharesToRebalance); } /** * @notice Plans the amount to transfer to Lido for fees * @param _vault The address of the vault * @param _record The record of the vault * @param _obligations The obligations of the vault * @param _valueToRebalance The ETH amount already allocated for rebalancing * @return valueToTransferToLido The ETH amount to be sent to the Lido * @return unsettledLidoFees The remaining Lido fees after the planned settlement */ function _planLidoTransfer( address _vault, VaultRecord storage _record, VaultObligations storage _obligations, uint256 _valueToRebalance ) internal view returns (uint256 valueToTransferToLido, uint256 unsettledLidoFees) { uint256 vaultBalance = _vault.balance; uint256 remainingBalance = vaultBalance - _valueToRebalance; if (_vaultConnection(_vault).pendingDisconnect) { /// @dev connection deposit is unlocked, so it's available for fees valueToTransferToLido = Math256.min(_obligations.unsettledLidoFees, remainingBalance); } else { /// @dev connection deposit is permanently locked, so it's not available for fees /// @dev NB: Fees are deducted from the vault's current balance, which reduces the total value, so the /// current locked value must be considered to prevent the vault from entering an unhealthy state uint256 lockedValue = _record.locked; uint256 totalValue_ = _totalValue(_record); uint256 unlockedValue = totalValue_ > lockedValue ? totalValue_ - lockedValue : 0; uint256 availableForFees = Math256.min( unlockedValue > _valueToRebalance ? unlockedValue - _valueToRebalance : 0, remainingBalance ); valueToTransferToLido = Math256.min(_obligations.unsettledLidoFees, availableForFees); } unsettledLidoFees = _obligations.unsettledLidoFees - valueToTransferToLido; } /** * @notice Settles redemptions and Lido fee obligations for a vault * @param _vault The address of the vault to settle obligations for * @param _record The record of the vault to settle obligations for * @param _obligations The obligations of the vault to be settled * @param _allowedUnsettled The maximum allowable unsettled obligations post-settlement (triggers reverts) */ function _settleObligations( address _vault, VaultRecord storage _record, VaultObligations storage _obligations, uint256 _allowedUnsettled ) internal { ( uint256 valueToRebalance, uint256 sharesToRebalance, uint256 valueToTransferToLido, uint256 unsettledRedemptions, uint256 unsettledLidoFees, uint256 totalUnsettled ) = _planSettlement(_vault, _record, _obligations); // Enforce requirement for settlement completeness if (totalUnsettled > _allowedUnsettled) { revert VaultHasUnsettledObligations(_vault, totalUnsettled, _allowedUnsettled); } // Skip if no changes to obligations if (valueToTransferToLido == 0 && valueToRebalance == 0) { return; } if (valueToRebalance > 0) { _decreaseLiability(_vault, _record, sharesToRebalance); _withdraw(_vault, _record, address(this), valueToRebalance); _rebalanceExternalEtherToInternal(valueToRebalance); } if (valueToTransferToLido > 0) { _withdraw(_vault, _record, LIDO_LOCATOR.treasury(), valueToTransferToLido); _obligations.settledLidoFees += uint128(valueToTransferToLido); } _obligations.redemptions = uint128(unsettledRedemptions); _obligations.unsettledLidoFees = uint128(unsettledLidoFees); emit VaultObligationsSettled({ vault: _vault, rebalanced: valueToRebalance, transferredToLido: valueToTransferToLido, unsettledRedemptions: unsettledRedemptions, unsettledLidoFees: unsettledLidoFees, settledLidoFees: _obligations.settledLidoFees }); } function _decreaseRedemptions(address _vault, uint256 _shares) internal { VaultObligations storage obligations = _vaultObligations(_vault); if (obligations.redemptions > 0) { uint256 redemptionsValue = _getPooledEthBySharesRoundUp(_shares); uint256 decrease = Math256.min(obligations.redemptions, redemptionsValue); if (decrease > 0) { obligations.redemptions -= uint128(decrease); emit RedemptionsUpdated(_vault, obligations.redemptions); } } } function _totalUnsettledObligations(VaultObligations storage _obligations) internal view returns (uint256) { return _obligations.unsettledLidoFees + _obligations.redemptions; } function _checkAndUpdateBeaconChainDepositsPause( address _vault, VaultConnection storage _connection, VaultRecord storage _record ) internal { IStakingVault vault_ = IStakingVault(_vault); bool isHealthy = _isVaultHealthy(_connection, _record); bool isBeaconDepositsPaused = vault_.beaconChainDepositsPaused(); if (_totalUnsettledObligations(_vaultObligations(_vault)) >= UNSETTLED_THRESHOLD || !isHealthy) { if (!isBeaconDepositsPaused) vault_.pauseBeaconChainDeposits(); } else if (!_connection.isBeaconDepositsManuallyPaused) { if (isBeaconDepositsPaused) vault_.resumeBeaconChainDeposits(); } } /// @return the amount of ether that can be instantly withdrawn from the staking vault /// @dev this amount already accounts locked value and unsettled obligations function _withdrawableValue( address _vault, VaultRecord storage _record ) internal view returns (uint256) { uint256 totalValue_ = _totalValue(_record); uint256 lockedPlusUnsettled = _record.locked + _totalUnsettledObligations(_vaultObligations(_vault)); return Math256.min( _vault.balance, totalValue_ > lockedPlusUnsettled ? totalValue_ - lockedPlusUnsettled : 0 ); } function _storage() internal pure returns (Storage storage $) { assembly { $.slot := STORAGE_LOCATION } } function _vaultConnection(address _vault) internal view returns (VaultConnection storage) { return _storage().connections[_vault]; } function _vaultRecord(address _vault) internal view returns (VaultRecord storage) { return _storage().records[_vault]; } function _vaultObligations(address _vault) internal view returns (VaultObligations storage) { return _storage().obligations[_vault]; } function _operatorGrid() internal view returns (OperatorGrid) { return OperatorGrid(LIDO_LOCATOR.operatorGrid()); } function _lazyOracle() internal view returns (LazyOracle) { return LazyOracle(LIDO_LOCATOR.lazyOracle()); } function _predepositGuarantee() internal view returns (IPredepositGuarantee) { return IPredepositGuarantee(LIDO_LOCATOR.predepositGuarantee()); } function _getSharesByPooledEth(uint256 _ether) internal view returns (uint256) { return LIDO.getSharesByPooledEth(_ether); } function _getPooledEthByShares(uint256 _ether) internal view returns (uint256) { return LIDO.getPooledEthByShares(_ether); } function _getPooledEthBySharesRoundUp(uint256 _shares) internal view returns (uint256) { return LIDO.getPooledEthBySharesRoundUp(_shares); } function _rebalanceExternalEtherToInternal(uint256 _ether) internal { LIDO.rebalanceExternalEtherToInternal{value: _ether}(); } function _nodeOperator(address _vault) internal view returns (address) { return IStakingVault(_vault).nodeOperator(); } function _requireNotZero(uint256 _value) internal pure { if (_value == 0) revert ZeroArgument(); } function _requireNotZero(address _address) internal pure { if (_address == address(0)) revert ZeroAddress(); } function _requireSender(address _sender) internal view { if (msg.sender != _sender) revert NotAuthorized(); } function _requireLessThanBP(uint256 _valueBP, uint256 _maxValueBP) internal pure { if (_valueBP > _maxValueBP) revert InvalidBasisPoints(_valueBP, _maxValueBP); } function _requireSaneShareLimit(uint256 _shareLimit) internal view { uint256 maxSaneShareLimit = (LIDO.getTotalShares() * MAX_RELATIVE_SHARE_LIMIT_BP) / TOTAL_BASIS_POINTS; if (_shareLimit > maxSaneShareLimit) revert ShareLimitTooHigh(_shareLimit, maxSaneShareLimit); } function _requireConnected(VaultConnection storage _connection, address _vault) internal view { if (_connection.vaultIndex == 0) revert NotConnectedToHub(_vault); } function _requireFreshReport(address _vault, VaultRecord storage _record) internal view { if (!_isReportFresh(_record)) revert VaultReportStale(_vault); } // ----------------------------- // EVENTS // ----------------------------- event AllowedCodehashUpdated(bytes32 indexed codehash, bool allowed); event VaultConnected( address indexed vault, uint256 shareLimit, uint256 reserveRatioBP, uint256 forcedRebalanceThresholdBP, uint256 infraFeeBP, uint256 liquidityFeeBP, uint256 reservationFeeBP ); event VaultConnectionUpdated( address indexed vault, uint256 shareLimit, uint256 reserveRatioBP, uint256 forcedRebalanceThresholdBP, uint256 infraFeeBP, uint256 liquidityFeeBP, uint256 reservationFeeBP ); event VaultShareLimitUpdated(address indexed vault, uint256 newShareLimit); event VaultFeesUpdated( address indexed vault, uint256 preInfraFeeBP, uint256 preLiquidityFeeBP, uint256 preReservationFeeBP, uint256 infraFeeBP, uint256 liquidityFeeBP, uint256 reservationFeeBP ); event VaultDisconnectInitiated(address indexed vault); event VaultDisconnectCompleted(address indexed vault); event VaultDisconnectAborted(address indexed vault, uint256 slashingReserve); event VaultReportApplied( address indexed vault, uint256 reportTimestamp, uint256 reportTotalValue, int256 reportInOutDelta, uint256 reportCumulativeLidoFees, uint256 reportLiabilityShares, uint256 reportSlashingReserve ); event MintedSharesOnVault(address indexed vault, uint256 amountOfShares, uint256 lockedAmount); event BurnedSharesOnVault(address indexed vault, uint256 amountOfShares); event VaultRebalanced(address indexed vault, uint256 sharesBurned, uint256 etherWithdrawn); event VaultInOutDeltaUpdated(address indexed vault, int112 inOutDelta); event ForcedValidatorExitTriggered(address indexed vault, bytes pubkeys, address refundRecipient); /** * @notice Emitted when the manager is set * @param vault The address of the vault * @param newOwner The address of the new owner * @param oldOwner The address of the old owner */ event VaultOwnershipTransferred(address indexed vault, address indexed newOwner, address indexed oldOwner); event LidoFeesUpdated(address indexed vault, uint256 unsettledLidoFees, uint256 settledLidoFees); event RedemptionsUpdated(address indexed vault, uint256 unsettledRedemptions); event RedemptionsNotSet(address indexed vault, uint256 redemptionsValue); event VaultObligationsSettled( address indexed vault, uint256 rebalanced, uint256 transferredToLido, uint256 unsettledRedemptions, uint256 unsettledLidoFees, uint256 settledLidoFees ); // ----------------------------- // ERRORS // ----------------------------- event BadDebtSocialized(address indexed vaultDonor, address indexed vaultAcceptor, uint256 badDebtShares); event BadDebtWrittenOffToBeInternalized(address indexed vault, uint256 badDebtShares); error ZeroBalance(); /** * @notice Thrown when attempting to rebalance more ether than the current total value of the vault * @param totalValue Current total value of the vault * @param rebalanceAmount Amount attempting to rebalance (in ether) */ error RebalanceAmountExceedsTotalValue(uint256 totalValue, uint256 rebalanceAmount); /** * @notice Thrown when attempting to withdraw more ether than the available value of the vault * @param vault The address of the vault * @param withdrawable The available value of the vault * @param requested The amount attempting to withdraw */ error AmountExceedsWithdrawableValue(address vault, uint256 withdrawable, uint256 requested); error AlreadyHealthy(address vault); error VaultMintingCapacityExceeded( address vault, uint256 totalValue, uint256 liabilityShares, uint256 newRebalanceThresholdBP ); error InsufficientSharesToBurn(address vault, uint256 amount); error ShareLimitExceeded(address vault, uint256 expectedSharesAfterMint, uint256 shareLimit); error AlreadyConnected(address vault, uint256 index); error NotConnectedToHub(address vault); error NotAuthorized(); error ZeroAddress(); error ZeroArgument(); error InvalidBasisPoints(uint256 valueBP, uint256 maxValueBP); error ShareLimitTooHigh(uint256 shareLimit, uint256 maxShareLimit); error InsufficientValueToMint(address vault, uint256 maxLockableValue); error NoLiabilitySharesShouldBeLeft(address vault, uint256 liabilityShares); error CodehashNotAllowed(address vault, bytes32 codehash); error InvalidFees(address vault, uint256 newFees, uint256 oldFees); error VaultOssified(address vault); error VaultInsufficientBalance(address vault, uint256 currentBalance, uint256 expectedBalance); error VaultReportStale(address vault); error PDGNotDepositor(address vault); error ZeroCodehash(); error VaultHubNotPendingOwner(address vault); error UnhealthyVaultCannotDeposit(address vault); error VaultIsDisconnecting(address vault); error VaultHasUnsettledObligations(address vault, uint256 unsettledObligations, uint256 allowedUnsettled); error PartialValidatorWithdrawalNotAllowed(); error ForcedValidatorExitNotAllowed(); error NoBadDebtToWriteOff(address vault, uint256 totalValueShares, uint256 liabilityShares); error BadDebtSocializationNotAllowed(); }
// 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 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; }
// 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 IHashConsensus { function getIsMember(address addr) external view returns (bool); function getCurrentFrame() external view returns ( uint256 refSlot, uint256 reportProcessingDeadlineSlot ); function getChainConfig() external view returns ( uint256 slotsPerEpoch, uint256 secondsPerSlot, uint256 genesisTime ); function getFrameConfig() external view returns (uint256 initialEpoch, uint256 epochsPerFrame); function getInitialRefSlot() 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; /** * Interface to connect AccountingOracle with LazyOracle and force type consistency */ interface ILazyOracle { function updateReportData( uint256 _timestamp, bytes32 _vaultsDataTreeRoot, string memory _vaultsDataReportCid ) 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.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 pragma solidity >=0.4.24; interface IVersioned { /// @notice Returns the current contract version. function getContractVersion() external view returns (uint256); }
// 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]>, Aragon // SPDX-License-Identifier: MIT // solhint-disable-next-line lido/fixed-compiler-version pragma solidity ^0.8.9; library UnstructuredStorage { function getStorageBool(bytes32 position) internal view returns (bool data) { assembly { data := sload(position) } } function getStorageAddress(bytes32 position) internal view returns (address data) { assembly { data := sload(position) } } function getStorageBytes32(bytes32 position) internal view returns (bytes32 data) { assembly { data := sload(position) } } function getStorageUint256(bytes32 position) internal view returns (uint256 data) { assembly { data := sload(position) } } function setStorageBool(bytes32 position, bool data) internal { assembly { sstore(position, data) } } function setStorageAddress(bytes32 position, address data) internal { assembly { sstore(position, data) } } function setStorageBytes32(bytes32 position, bytes32 data) internal { assembly { sstore(position, data) } } function setStorageUint256(bytes32 position, uint256 data) internal { assembly { sstore(position, data) } } }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // solhint-disable-next-line lido/fixed-compiler-version pragma solidity ^0.8.9; import {UnstructuredStorage} from "contracts/common/lib/UnstructuredStorage.sol"; /** * @title PausableUntil * @notice allows to pause the contract for a specific duration or indefinitely */ abstract 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 if paused modifier whenResumed() { _checkResumed(); _; } /// @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 /// - the timestamp when the 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 _checkPaused() internal view { if (!isPaused()) { revert PausedExpected(); } } function _checkResumed() internal view { if (isPaused()) { revert ResumedExpected(); } } 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-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "@openzeppelin/contracts-v5.2/access/IAccessControl.sol";
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {ERC165Upgradeable} from "../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.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:
*
* ```solidity
* 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}:
*
* ```solidity
* 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. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControl, ERC165Upgradeable {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/// @custom:storage-location erc7201:openzeppelin.storage.AccessControl
struct AccessControlStorage {
mapping(bytes32 role => RoleData) _roles;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControl")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant AccessControlStorageLocation = 0x02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800;
function _getAccessControlStorage() private pure returns (AccessControlStorage storage $) {
assembly {
$.slot := AccessControlStorageLocation
}
}
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
function __AccessControl_init() internal onlyInitializing {
}
function __AccessControl_init_unchained() internal onlyInitializing {
}
/**
* @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 virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
return $._roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @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 virtual returns (bytes32) {
AccessControlStorage storage $ = _getAccessControlStorage();
return $._roles[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.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual 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.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual 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 `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
AccessControlStorage storage $ = _getAccessControlStorage();
bytes32 previousAdminRole = getRoleAdmin(role);
$._roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
if (!hasRole(role, account)) {
$._roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
if (hasRole(role, account)) {
$._roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/AccessControlEnumerable.sol)
pragma solidity ^0.8.20;
import {IAccessControlEnumerable} from "@openzeppelin/contracts-v5.2/access/extensions/IAccessControlEnumerable.sol";
import {AccessControlUpgradeable} from "../AccessControlUpgradeable.sol";
import {EnumerableSet} from "@openzeppelin/contracts-v5.2/utils/structs/EnumerableSet.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerableUpgradeable is Initializable, IAccessControlEnumerable, AccessControlUpgradeable {
using EnumerableSet for EnumerableSet.AddressSet;
/// @custom:storage-location erc7201:openzeppelin.storage.AccessControlEnumerable
struct AccessControlEnumerableStorage {
mapping(bytes32 role => EnumerableSet.AddressSet) _roleMembers;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControlEnumerable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant AccessControlEnumerableStorageLocation = 0xc1f6fe24621ce81ec5827caf0253cadb74709b061630e6b55e82371705932000;
function _getAccessControlEnumerableStorage() private pure returns (AccessControlEnumerableStorage storage $) {
assembly {
$.slot := AccessControlEnumerableStorageLocation
}
}
function __AccessControlEnumerable_init() internal onlyInitializing {
}
function __AccessControlEnumerable_init_unchained() internal onlyInitializing {
}
/**
* @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 virtual returns (address) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[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 virtual returns (uint256) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].length();
}
/**
* @dev Return all accounts that have `role`
*
* 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 getRoleMembers(bytes32 role) public view virtual returns (address[] memory) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].values();
}
/**
* @dev Overload {AccessControl-_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
bool granted = super._grantRole(role, account);
if (granted) {
$._roleMembers[role].add(account);
}
return granted;
}
/**
* @dev Overload {AccessControl-_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
bool revoked = super._revokeRole(role, account);
if (revoked) {
$._roleMembers[role].remove(account);
}
return revoked;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
assembly {
$.slot := INITIALIZABLE_STORAGE
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @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 ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "@openzeppelin/contracts-v5.2/utils/introspection/IERC165.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC-165 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);
* }
* ```
*/
abstract contract ERC165Upgradeable is Initializable, IERC165 {
function __ERC165_init() internal onlyInitializing {
}
function __ERC165_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}{
"optimizer": {
"enabled": true,
"runs": 200
},
"viaIR": true,
"evmVersion": "cancun",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"libraries": {}
}Contract ABI
API[{"inputs":[{"internalType":"address","name":"_stETH","type":"address"},{"internalType":"address","name":"_wstETH","type":"address"},{"internalType":"address","name":"_vaultHub","type":"address"},{"internalType":"address","name":"_lidoLocator","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccessControlBadConfirmation","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"neededRole","type":"bytes32"}],"name":"AccessControlUnauthorizedAccount","type":"error"},{"inputs":[],"name":"AdjustmentNotReported","type":"error"},{"inputs":[],"name":"AdjustmentNotSettled","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"ConfirmExpiryOutOfBounds","type":"error"},{"inputs":[],"name":"ConnectedToVaultHub","type":"error"},{"inputs":[],"name":"DashboardNotAllowed","type":"error"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EthTransferFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"requestedShares","type":"uint256"},{"internalType":"uint256","name":"remainingShares","type":"uint256"}],"name":"ExceedsMintingCapacity","type":"error"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"withdrawableValue","type":"uint256"}],"name":"ExceedsWithdrawable","type":"error"},{"inputs":[],"name":"FeeValueExceed100Percent","type":"error"},{"inputs":[],"name":"IncreasedOverLimit","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentAdjustment","type":"uint256"},{"internalType":"uint256","name":"currentAtPropositionAdjustment","type":"uint256"}],"name":"InvalidatedAdjustmentVote","type":"error"},{"inputs":[],"name":"NonProxyCallsForbidden","type":"error"},{"inputs":[],"name":"ReportStale","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"SameAdjustment","type":"error"},{"inputs":[],"name":"SameRecipient","type":"error"},{"inputs":[],"name":"SenderNotMember","type":"error"},{"inputs":[],"name":"TierChangeNotConfirmed","type":"error"},{"inputs":[],"name":"VaultQuarantined","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"ZeroArgument","type":"error"},{"inputs":[],"name":"ZeroConfirmingRoles","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"oldConfirmExpiry","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newConfirmExpiry","type":"uint256"}],"name":"ConfirmExpirySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721Recovered","type":"event"},{"anonymous":false,"inputs":[],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"NodeOperatorFeeDisbursed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"oldNodeOperatorFeeRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newNodeOperatorFeeRate","type":"uint256"}],"name":"NodeOperatorFeeRateSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"oldNodeOperatorFeeRecipient","type":"address"},{"indexed":false,"internalType":"address","name":"newNodeOperatorFeeRecipient","type":"address"}],"name":"NodeOperatorFeeRecipientSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newAdjustment","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"oldAdjustment","type":"uint256"}],"name":"RewardsAdjustmentSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"},{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"confirmTimestamp","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"expiryTimestamp","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"RoleMemberConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"stakingVault","type":"address"},{"indexed":false,"internalType":"uint256","name":"deposits","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalAmount","type":"uint256"}],"name":"UnguaranteedDeposits","type":"event"},{"inputs":[],"name":"BURN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANGE_TIER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FUND_ON_RECEIVE_FLAG_SLOT","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FUND_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIDO_LOCATOR","outputs":[{"internalType":"contract ILidoLocator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANUAL_REWARDS_ADJUSTMENT_LIMIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_CONFIRM_EXPIRY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_CONFIRM_EXPIRY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NODE_OPERATOR_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NODE_OPERATOR_REWARDS_ADJUST_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_BEACON_CHAIN_DEPOSITS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PDG_COMPENSATE_PREDEPOSIT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PDG_PROVE_VALIDATOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REBALANCE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RECOVER_ASSETS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REQUEST_VALIDATOR_EXIT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESUME_BEACON_CHAIN_DEPOSITS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STETH","outputs":[{"internalType":"contract ILido","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TRIGGER_VALIDATOR_WITHDRAWAL_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VAULT_HUB","outputs":[{"internalType":"contract VaultHub","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VOLUNTARY_DISCONNECT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WSTETH","outputs":[{"internalType":"contract IWstETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"abandonDashboard","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountOfShares","type":"uint256"}],"name":"burnShares","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"burnStETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountOfWstETH","type":"uint256"}],"name":"burnWstETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tierId","type":"uint256"},{"internalType":"uint256","name":"_requestedShareLimit","type":"uint256"}],"name":"changeTier","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_pubkey","type":"bytes"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"compensateDisprovenPredepositFromPDG","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_callData","type":"bytes"},{"internalType":"bytes32","name":"_role","type":"bytes32"}],"name":"confirmation","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"confirmingRoles","outputs":[{"internalType":"bytes32[]","name":"roles","type":"bytes32[]"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tierId","type":"uint256"},{"internalType":"uint256","name":"_requestedShareLimit","type":"uint256"}],"name":"connectAndAcceptTier","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"connectToVaultHub","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"disburseNodeOperatorFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feePeriodStartReport","outputs":[{"internalType":"uint112","name":"totalValue","type":"uint112"},{"internalType":"int112","name":"inOutDelta","type":"int112"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"forcedRebalanceThresholdBP","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fund","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"getConfirmExpiry","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMembers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"role","type":"bytes32"}],"internalType":"struct Permissions.RoleAssignment[]","name":"_assignments","type":"tuple[]"}],"name":"grantRoles","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_adjustmentIncrease","type":"uint256"}],"name":"increaseRewardsAdjustment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"infraFeeBP","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_defaultAdmin","type":"address"},{"internalType":"address","name":"_nodeOperatorManager","type":"address"},{"internalType":"uint256","name":"_nodeOperatorFeeBP","type":"uint256"},{"internalType":"uint256","name":"_confirmExpiry","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestReport","outputs":[{"components":[{"internalType":"uint112","name":"totalValue","type":"uint112"},{"internalType":"int112","name":"inOutDelta","type":"int112"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"internalType":"struct VaultHub.Report","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"liabilityShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"liquidityFeeBP","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"locked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxLockableValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_amountOfShares","type":"uint256"}],"name":"mintShares","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"mintStETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_amountOfWstETH","type":"uint256"}],"name":"mintWstETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"nodeOperatorDisbursableFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nodeOperatorFeeRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nodeOperatorFeeRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pauseBeaconChainDeposits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"},{"internalType":"bytes","name":"pubkey","type":"bytes"},{"internalType":"uint256","name":"validatorIndex","type":"uint256"},{"internalType":"uint64","name":"childBlockTimestamp","type":"uint64"},{"internalType":"uint64","name":"slot","type":"uint64"},{"internalType":"uint64","name":"proposerIndex","type":"uint64"}],"internalType":"struct IPredepositGuarantee.ValidatorWitness[]","name":"_witnesses","type":"tuple[]"}],"name":"proveUnknownValidatorsToPDG","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ether","type":"uint256"}],"name":"rebalanceVaultWithEther","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_shares","type":"uint256"}],"name":"rebalanceVaultWithShares","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reconnectToVaultHub","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"recoverERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_etherToFund","type":"uint256"}],"name":"remainingMintingCapacityShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"callerConfirmation","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_pubkeys","type":"bytes"}],"name":"requestValidatorExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reservationFeeBP","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"reserveRatioBP","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"resumeBeaconChainDeposits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"role","type":"bytes32"}],"internalType":"struct Permissions.RoleAssignment[]","name":"_assignments","type":"tuple[]"}],"name":"revokeRoles","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardsAdjustment","outputs":[{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"uint64","name":"latestTimestamp","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newConfirmExpiry","type":"uint256"}],"name":"setConfirmExpiry","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newNodeOperatorFeeRate","type":"uint256"}],"name":"setNodeOperatorFeeRate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newNodeOperatorFeeRecipient","type":"address"}],"name":"setNodeOperatorFeeRecipient","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_proposedAdjustment","type":"uint256"},{"internalType":"uint256","name":"_expectedAdjustment","type":"uint256"}],"name":"setRewardsAdjustment","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"shareLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingVault","outputs":[{"internalType":"contract IStakingVault","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalMintingCapacityShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"transferVaultOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_pubkeys","type":"bytes"},{"internalType":"uint64[]","name":"_amounts","type":"uint64[]"},{"internalType":"address","name":"_refundRecipient","type":"address"}],"name":"triggerValidatorWithdrawals","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"pubkey","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes32","name":"depositDataRoot","type":"bytes32"}],"internalType":"struct IStakingVault.Deposit[]","name":"_deposits","type":"tuple[]"}],"name":"unguaranteedDepositToBeaconChain","outputs":[{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unsettledObligations","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vaultConnection","outputs":[{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint96","name":"shareLimit","type":"uint96"},{"internalType":"uint96","name":"vaultIndex","type":"uint96"},{"internalType":"bool","name":"pendingDisconnect","type":"bool"},{"internalType":"uint16","name":"reserveRatioBP","type":"uint16"},{"internalType":"uint16","name":"forcedRebalanceThresholdBP","type":"uint16"},{"internalType":"uint16","name":"infraFeeBP","type":"uint16"},{"internalType":"uint16","name":"liquidityFeeBP","type":"uint16"},{"internalType":"uint16","name":"reservationFeeBP","type":"uint16"},{"internalType":"bool","name":"isBeaconDepositsManuallyPaused","type":"bool"}],"internalType":"struct VaultHub.VaultConnection","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"voluntaryDisconnect","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_ether","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawableValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]Contract Creation Code
6101203461025857601f61618338819003918201601f19168301916001600160401b0383118484101761025c578084926080946040528339810103126102585761004881610270565b61005460208301610270565b9161006d606061006660408401610270565b9201610270565b917f1b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb09018054906201518080915560405191825260208201527f437dc1a3e26abf0b8381463abca48d1e7d322e803c2b160d3ee75c04a492a18260403392a26100d382610284565b6100dc83610284565b306080526001600160a01b0391821660a05291811660c05290819061010081610284565b61010984610284565b1660e05261010091168152604051615edc91826102a7833960805182612b6f015260a051828181610f1c015281816111c60152818161179d015281816118cd01528181611ded015281816126450152818161285f01528181612f2c0152818161337d015281816134f7015281816137620152818161381f01528181613a6801528181613bcf01528181613ed6015281816140770152818161440b0152818161466e015281816147600152818161497a01528181614de601528181614fe6015281816153f40152818161546a01528181615596015281816156a201528181615742015281816157b00152615b14015260c051828181612922015281816145cf01526154eb015260e05182818161120d01528181611e2f0152818161296601528181612c5801528181613632015261503601525181818161114b015281816127dd01528181612c13015261366b0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b038216820361025857565b6001600160a01b03161561029457565b60405163d92e233d60e01b8152600490fdfe60806040526004361015610022575b3615610018575f80fd5b610020614317565b005b5f3560e01c806301ffc9a714610631578063032d9b301461062c5780630a4a3ed2146106275780630b91990d146106225780630bff88811461061d5780630c7c92461461061857806310741552146106135780631171bda91461060e57806313e40efb14610609578063158ef93e14610604578063196b9e06146105ff578063248a9ca3146105fa57806324e7964a146105f55780632806f5e0146105f05780632959843a146105eb5780632ae87c3e146105e65780632c0772b4146105e15780632f2ff15d146105dc5780633046bf47146105d757806332cd7ebf146105d257806333885111146105cd57806335763d2b146105c857806336568abe146105c35780633a04d4f3146105be5780633a284985146105b95780633b37990e146105b45780633be75aa3146105af5780633e619a6c146105aa5780633efd8597146105a55780634156bbf4146105a057806343c710571461059b578063487e9d4e146105965780634bc368401461059157806350d99dd41461058c578063520107ad146105875780635251ebf914610582578063528c198a1461057d5780635680e145146105785780635bc7136314610573578063626c5ebc1461056e578063634bd91a14610569578063699d79bd146105645780636ab158361461055f5780636af6eeed1461055a5780636d4755661461055557806370b24b6914610550578063722adad21461054b57806373b257bc146105465780637b108d92146105415780638322fff21461053c578063845d0a3a14610537578063846f4d7f14610532578063853c637d1461052d5780638860be4114610528578063899896cb146105235780638c5d3e341461051e5780639010d07c1461051957806391d1485414610514578063956311051461050f57806399bce5e71461050a5780639c908158146105055780639c9708a914610500578063a217fddf146104fb578063a3246ad3146104f6578063a6218709146104f1578063b1003ec3146104ec578063b35869ed146104e7578063b60d4288146104e2578063b930908f146104dd578063bccad58f146104d8578063bddf300c146104d3578063be807b28146104ce578063c70cb120146104c9578063ca15c873146104c4578063ca8317af146104bf578063cb8fbf98146104ba578063cf309012146104b5578063d4c3eea0146104b0578063d547741f146104ab578063d877a3fc146104a6578063d9fb643a146104a1578063dadb623d1461049c578063dbba4b4814610497578063e00bfe5014610492578063e02023a11461048d578063e071c0ca14610488578063e55d864b14610483578063e5bca8af1461047e578063e6c772d414610479578063e9a9c85014610474578063eb990c591461046f578063ef914a091461046a578063f0e9fcd114610465578063f3fef3a314610460578063f634dcbd1461045b578063f6c76318146104565763ff108ccb0361000e57612ef2565b612ed6565b612e6c565b612d95565b612ce0565b612cc1565b612b20565b612ae6565b612a49565b612a2f565b6129f4565b6129cf565b612995565b612951565b61290d565b61280c565b6127c8565b612738565b6126fe565b6126a8565b6125eb565b6125c6565b61250c565b6124e2565b6124a8565b61246e565b6123d9565b612340565b612306565b61229b565b612261565b612227565b61218e565b61211b565b6120bd565b6120aa565b61208c565b612074565b611fdf565b611f8e565b611f49565b611f28565b611f0b565b611eec565b611dba565b611d9a565b611d5f565b611d31565b611cd5565b611cb6565b611c33565b611bbd565b611b83565b611b3b565b611b01565b611ac7565b611aa0565b611a66565b611a3f565b611a05565b61196e565b611863565b611728565b611691565b611677565b611640565b611606565b611522565b611504565b61146d565b61144a565b61141f565b6113e5565b6113be565b611373565b6112f5565b611281565b611129565b611072565b611033565b610ff9565b610fdf565b610fc5565b610fa6565b610f77565b610f4b565b610f07565b610ee5565b610dea565b610d34565b610c4c565b610b2b565b610b0e565b610ae5565b61084e565b610753565b346106a15760203660031901126106a15760043563ffffffff60e01b81168091036106a157602090635a05180f60e01b8114908115610676575b506040519015158152f35b637965db0b60e01b811491508115610690575b505f61066b565b6301ffc9a760e01b1490505f610689565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b6001600160401b0381116106cc57604052565b6106a5565b606081019081106001600160401b038211176106cc57604052565b61014081019081106001600160401b038211176106cc57604052565b90601f801991011681019081106001600160401b038211176106cc57604052565b60405190610736826106ec565b565b6001600160401b0381116106cc57601f01601f191660200190565b346106a15760403660031901126106a1576004356001600160401b0381116106a157366023820112156106a15780600401359061078f82610738565b61079c6040519182610708565b82815236602484840101116106a1575f6020846107ec9560246107db960183860137830101526107ce60243591612f6c565b905f5260205260405f2090565b546040519081529081906020820190565b0390f35b9181601f840112156106a1578235916001600160401b0383116106a1576020808501948460051b0101116106a157565b60206003198201126106a157600435906001600160401b0382116106a15761084a916004016107f0565b9091565b346106a15761085c36610820565b5f9060209061088b61087f8361087130615824565b01516001600160a01b031690565b6001600160a01b031690565b6040928351636b96736b60e01b815260048282600481875afa918215610a38575f92610aac575b505f5b88868210610a8a5750506108c761337b565b808811610a6a57506108d761438e565b6108e0876143a1565b6108e86144b1565b61091a610915886109106109046006546001600160801b031690565b6001600160801b031690565b61301c565b61453c565b855163266bcf0560e11b81528381600481885afa8015610a3857610945915f91610a3d575b5061303d565b5f5b8681106109a1578751878152602081018a90526107ec908a908a906001600160a01b038a16907f7ca089edbf098275c3dd9807ef0d5e5f7a4c4ed5a4ed4b865def78c7260f6b7f90604090a2519081529081906020820190565b6109ac81888c612fe1565b6001600160a01b03851691906109c28180613066565b916109cf89820182613066565b939095803b156106a1578d8894610a028b5f9884519b8c998a9889966304512a2360e31b885260608c01359588016130b8565b03930135905af1918215610a3857600192610a1f575b5001610947565b80610a2c610a32926106b9565b80610adb565b5f610a18565b612fc2565b610a5d9150853d8711610a63575b610a558183610708565b81019061302e565b5f61093f565b503d610a4b565b8651633cbb525b60e21b8152600481018990526024810191909152604490fd5b819888610a9d6001948a610aa595612fe1565b01359061301c565b97016108b5565b610acd919250833d8511610ad4575b610ac58183610708565b810190612faa565b905f6108b2565b503d610abb565b5f9103126106a157565b346106a1575f3660031901126106a15760205f80516020615e6783398151915254604051908152f35b346106a1575f3660031901126106a157602060405162278d008152f35b346106a1576020806003193601126106a157610b8881610b4c61087f6145b1565b610b5c61087f8361087130615824565b604051630131592760e61b81526001600160a01b03909116600482015292839190829081906024820190565b03915afa8015610a3857610be8915f91610c02575b50610bb4610baf6004356109106134c5565b614621565b610bbc613730565b918280821115610bf957610bcf91613113565b915b80821115610bf057610be291613113565b90614652565b604051908152f35b50505f90614652565b50505f91610bd1565b610c199150833d8511610a6357610a558183610708565b5f610b9d565b9181601f840112156106a1578235916001600160401b0383116106a157602083818601950101116106a157565b346106a15760203660031901126106a1576004356001600160401b0381116106a157610c7c903690600401610c1f565b335f9081527fb263cec6d0d87327feaa8f63140311e533b781670aff412d50297f0882a7530460205260409020547f32d0d6546e21c13ff633616141dc9daad87d248d1d37c56bf493d06d627ecb7b929060ff168015610d05575b15610ce6576100209250614664565b60405163e2517d3f60e01b815233600482015260248101849052604490fd5b50825f525f602052610d1e33600160405f2001546139cd565b610cd7565b6001600160a01b038116036106a157565b346106a15760603660031901126106a157600435610d5181610d23565b60243590610d5e82610d23565b335f9081527f8e22673d1bc81fb3bd6ed1d58f34ebd725bafdd9d714cf3052b13a12f254fcff60205260409020547fa38b301640bddfd3e6a9d2a11d13551d53ef81526347ff09d798738fcc5a49d4929060ff168015610dcc575b15610ce657610020925060443591613170565b50825f525f602052610de533600160405f2001546139cd565b610db9565b60603660031901126106a1576001600160401b036004358181116106a157610e16903690600401610c1f565b90916024359081116106a157610e309036906004016107f0565b9060443592610e3e84610d23565b335f9081527f894c422fd63ef7348a50b435343ac685160997c7193187427501d7dfbbf7b59760205260409020547fea19d3b23bd90fdd52445ad672f2b6fb1fef7230d49c6a827c1cd288d02994d5959060ff168015610ec7575b15610ea8576100209550614754565b60405163e2517d3f60e01b815233600482015260248101879052604490fd5b50855f525f602052610ee033600160405f2001546139cd565b610e99565b346106a1575f3660031901126106a157602060ff600254166040519015158152f35b346106a1575f3660031901126106a1576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346106a15760203660031901126106a1576004355f525f6020526020600160405f200154604051908152f35b346106a1575f3660031901126106a15760206001600160a01b0381610f9b30615824565b015116604051908152f35b346106a1575f3660031901126106a157602061ffff60a0610f9b613feb565b346106a1575f3660031901126106a1576020610be861337b565b346106a1575f3660031901126106a1576020610be86134c5565b346106a1575f3660031901126106a15760206040517fa90c7030a27f389f9fc8ed21a0556f40c88130cc14a80db936bed68261819b2c8152f35b346106a15760403660031901126106a15761002060243560043561105682610d23565b805f525f60205261106d600160405f200154614899565b61493a565b346106a15760203660031901126106a157335f9081527fe23a8bac6ee120cc0f31e97b679e63d8a06b6b0f7ec6a1eaa96912d2415af2e060205260409020547f3f82ecf462ddac43fc17ba11472c35f18b7760b4f5a5fc50b9625f9b5a22cf629060ff16801561110b575b156110ed57610020600435614970565b6044906040519063e2517d3f60e01b82523360048301526024820152fd5b50805f525f60205261112433600160405f2001546139cd565b6110dd565b346106a1576020806003193601126106a1576001600160a01b03905f8161119c7f0000000000000000000000000000000000000000000000000000000000000000851660043561117b81303385615a43565b604051948580948193636f074d1f60e11b8352600483019190602083019252565b03925af18015610a38576111b7915f91611264575b50615019565b604051638fcb4e5b60e01b81527f000000000000000000000000000000000000000000000000000000000000000084166001600160a01b0316600482015260248101829052929091908190849060449082905f907f0000000000000000000000000000000000000000000000000000000000000000165af1928315610a385761002093611246575b5050615363565b8161125c92903d10610a6357610a558183610708565b505f8061123f565b61127b9150833d8511610a6357610a558183610708565b5f6111b1565b346106a1575f3660031901126106a1576005546040516001600160a01b039091168152602090f35b9060206003198301126106a1576004356001600160401b03928382116106a157806023830112156106a15781600401359384116106a15760248460061b830101116106a1576024019190565b346106a157611303366112a9565b908115611361575f5b82811061131557005b60019061135a602080611329848888613563565b01355f611337858989613563565b359261134284610d23565b828252526113558560405f200154614899565b6149e6565b500161130c565b60405163b7852ebb60e01b8152600490fd5b346106a15760403660031901126106a15760243561139081610d23565b336001600160a01b038216036113ac57610020906004356149e6565b60405163334bd91960e11b8152600490fd5b346106a1575f3660031901126106a15760206040515f80516020615e278339815191528152f35b346106a1575f3660031901126106a15760206040517fa38b301640bddfd3e6a9d2a11d13551d53ef81526347ff09d798738fcc5a49d48152f35b346106a15760403660031901126106a1576020611440602435600435613573565b6040519015158152f35b346106a1575f3660031901126106a15760206001600160601b0381610f9b613feb565b60403660031901126106a15760043561148581610d23565b34611498575b61002090602435906135f0565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff1680156114e6575b156110ed5750610020906114df34614ddc565b905061148b565b50805f525f6020526114ff33600160405f2001546139cd565b6114cc565b346106a15760203660031901126106a1576020611440600435613708565b346106a15760403660031901126106a1576004356001600160401b0381116106a157611552903690600401610c1f565b6024359061155f82610d23565b335f9081527fb23b36340bdad7eb67d390e1f617f5a66ce55ba02098689f188a1df60c41421060205260409020547f17960a6b137243c888669d93712b617414dd6d1ab55d5eca488ccb1d0894cd5c939060ff1680156115e8575b156115c9576100209350614f8f565b60405163e2517d3f60e01b815233600482015260248101859052604490fd5b50835f525f60205261160133600160405f2001546139cd565b6115ba565b346106a1575f3660031901126106a15760206040517f59d005e32db662b94335d6bedfeb453fd2202b9f0cc7a6ed498d9098171744b08152f35b346106a1575f3660031901126106a15760406006546001600160401b038251916001600160801b038116835260801c166020820152f35b346106a1575f3660031901126106a1576020610be8613730565b60403660031901126106a1576004356116a981610d23565b346116bc575b61002090602435906137ae565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff16801561170a575b156110ed57506100209061170334614ddc565b90506116af565b50805f525f60205261172333600160405f2001546139cd565b6116f0565b346106a15761173636610820565b335f9081527f67416b44d24f10cfdaeae95ddaabfedeacd76567f63dd48b7ad4e79941f9b8586020908152604091829020547fb850402129bccae797798069a8cf3147a0cb7c3193f70558a75f7df0b8651c309060ff168015611845575b156110ed5750917f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316905f5b8181106117d157005b6117e161087f8661087130615824565b906117ed818489615071565b91843b156106a157855163015004fd60e61b8152925f91849182916118169190600484016150e8565b038183885af1918215610a3857600192611832575b50016117c8565b80610a2c61183f926106b9565b5f61182b565b50805f525f60205261185e33600160405f2001546139cd565b611794565b346106a15760203660031901126106a15760043561188081610d23565b6001600160a01b036118c8602061189d61087f8261087130615824565b604051600161da2560e61b031981526001600160a01b03909116600482015291829081906024820190565b0381857f0000000000000000000000000000000000000000000000000000000000000000165afa908115610a38575f9161193f575b5061192d578116301461191b57610020906119166151d0565b61521a565b604051633b07e30760e11b8152600490fd5b6040516309e1b82160e21b8152600490fd5b611961915060203d602011611967575b6119598183610708565b8101906137c0565b5f6118fd565b503d61194f565b60403660031901126106a15760043561198681610d23565b34611999575b6100209060243590614e48565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff1680156119e7575b156110ed5750610020906119e034614ddc565b905061198c565b50805f525f602052611a0033600160405f2001546139cd565b6119cd565b346106a1575f3660031901126106a15760206040517f3f82ecf462ddac43fc17ba11472c35f18b7760b4f5a5fc50b9625f9b5a22cf628152f35b346106a1575f3660031901126106a15760206040515f80516020615e478339815191528152f35b346106a1575f3660031901126106a15760206040517fb850402129bccae797798069a8cf3147a0cb7c3193f70558a75f7df0b8651c308152f35b346106a1575f3660031901126106a15760206040515f80516020615e878339815191528152f35b346106a1575f3660031901126106a15760206040517f32d0d6546e21c13ff633616141dc9daad87d248d1d37c56bf493d06d627ecb7b8152f35b346106a1575f3660031901126106a15760206040517fea19d3b23bd90fdd52445ad672f2b6fb1fef7230d49c6a827c1cd288d02994d58152f35b346106a1575f3660031901126106a1576060611b556137d4565b63ffffffff60408051926001600160701b0381511684526020810151600d0b60208501520151166040820152f35b346106a1575f3660031901126106a15760206040517f9586321ac05f110e4b4a0a42aba899709345af0ca78910e8832ddfd71fed2bf48152f35b346106a1575f806003193601126106a157611bd6614843565b6001600160a01b036020611be930615824565b015116803b156106a1575f80916004604051809481936379ba509760e01b83525af18015610a3857611c22575b50611c1f613ece565b80f35b611c2c91506106b9565b5f80611c16565b60203660031901126106a15734611c50575b6100206004356138cc565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff168015611c98575b156110ed5750611c9334614ddc565b611c45565b50805f525f602052611cb133600160405f2001546139cd565b611c84565b346106a1575f3660031901126106a157602061ffff60c0610f9b613feb565b346106a1575f3660031901126106a157611ced613987565b604051809160208083016020845282518091526020604085019301915f5b828110611d1a57505050500390f35b835185528695509381019392810192600101611d0b565b346106a1575f3660031901126106a157602060405173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee8152f35b60403660031901126106a157611d73613ece565b611d8160243560043561526e565b15611d8857005b60405163ce2a477560e01b8152600490fd5b346106a1575f3660031901126106a157602061ffff610100610f9b613feb565b346106a1576020806003193601126106a157604051636d78045960e01b8152336004808301919091526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116602484015290356044830181905292919082908290815f81606481015b03927f0000000000000000000000000000000000000000000000000000000000000000165af18015610a3857611ecf575b507f689f0a569be0c9b6cd2c11c81cb0add722272abdae6b649fdb1e05f1d9bb8a2f5f8181528083526040808220338352602052902090919060ff905416908115611eb1575b50156110ed57610020826153ea565b5f915082825252611ec933600160405f2001546139cd565b5f611ea2565b611ee590823d8411610a6357610a558183610708565b505f611e5c565b346106a1575f3660031901126106a157602061ffff6080610f9b613feb565b346106a1575f3660031901126106a1576020600354604051908152f35b346106a15760403660031901126106a157602061144060243560043561526e565b346106a15760403660031901126106a1576004355f5260016020526020611f7560243560405f20615b88565b905460405160039290921b1c6001600160a01b03168152f35b346106a15760403660031901126106a157602060ff611fd3602435611fb281610d23565b6004355f525f845260405f209060018060a01b03165f5260205260405f2090565b54166040519015158152f35b346106a1575f3660031901126106a157335f9081527f3f3dda3fcd93f9e4551f2c87aa737426b704aa48dbb418e3deea799b35c4558960205260409020547f59d005e32db662b94335d6bedfeb453fd2202b9f0cc7a6ed498d9098171744b09060ff168015612056575b156110ed57610020615460565b50805f525f60205261206f33600160405f2001546139cd565b612049565b346106a1575f3660031901126106a1576100206139f4565b346106a15760203660031901126106a1576020611440600435613bcd565b5f3660031901126106a157610020613ece565b346106a1575f3660031901126106a15760206040515f8152f35b60209060206040818301928281528551809452019301915f5b8281106120fe575050505090565b83516001600160a01b0316855293810193928101926001016120f0565b346106a1576020806003193601126106a1576004355f5260018060205260405f20916040519182602085549182815201945f5260205f20925f905b828210612179576107ec8661216d818a0382610708565b604051918291826120d7565b84548752958601959383019390830190612156565b346106a1575f3660031901126106a1576121d260206001600160a01b03610b5c816121b76145b1565b1691836121c330615824565b0151166001600160a01b031690565b03915afa908115610a38576107ec916121f8915f91612208575b50610be2610baf6134c5565b6040519081529081906020820190565b612221915060203d602011610a6357610a558183610708565b5f6121ec565b346106a1575f3660031901126106a15760206040517f655996ffb2e08cf642b4ce5cc56668761aed116cdfcb21f71ff9fe7fbcbd450e8152f35b346106a1575f3660031901126106a15760206040517fe0b9915a7819e810f29b50730662441fec3443eb363b7e7c90c77fada416f2768152f35b5f3660031901126106a157335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff1680156122e8575b156110ed5761002034614ddc565b50805f525f60205261230133600160405f2001546139cd565b6122da565b346106a1575f3660031901126106a15760206040517f689f0a569be0c9b6cd2c11c81cb0add722272abdae6b649fdb1e05f1d9bb8a2f8152f35b346106a15760203660031901126106a157335f9081527fff5c6f7275dcc6faef7a8ce369f5d9cdfe4b73e6d3ca76b92d14e1011cfad56860205260409020547fe0b9915a7819e810f29b50730662441fec3443eb363b7e7c90c77fada416f2769060ff1680156123bb575b156110ed57610020600435613fba565b50805f525f6020526123d433600160405f2001546139cd565b6123ab565b346106a1575f3660031901126106a157335f9081527f081a651349dfbf423ffb5b045a53f71668561e554402e2db71c1db19bfb815a560205260409020547fa90c7030a27f389f9fc8ed21a0556f40c88130cc14a80db936bed68261819b2c9060ff168015612450575b156110ed5761002061558c565b50805f525f60205261246933600160405f2001546139cd565b612443565b346106a1575f3660031901126106a15760206040517f17960a6b137243c888669d93712b617414dd6d1ab55d5eca488ccb1d0894cd5c8152f35b346106a1575f3660031901126106a15760206040517fea6487df651bb740150364c496e1c7403dd62063c96e44906cc98c6a919a9d888152f35b346106a15760203660031901126106a1576004355f526001602052602060405f2054604051908152f35b346106a1575f3660031901126106a157610140612527613feb565b60405181516001600160a01b03168152906125c4906020818101516001600160601b0316908401526040818101516001600160601b03169084015260608181015115159084015260808181015161ffff169084015260a08181015161ffff169084015260c08181015161ffff169084015260e08181015161ffff16908401526101008181015161ffff169084015261012090810151151590830152565bf35b346106a1575f3660031901126106a15760206040516a084595161401484a0000008152f35b346106a1575f3660031901126106a15761264160206001600160a01b03808261261330615824565b015160405163cbf9fe5f60e01b815291166001600160a01b0316600482015292839190829081906024820190565b03917f0000000000000000000000000000000000000000000000000000000000000000165afa8015610a38576107ec915f91612689575b506040519081529081906020820190565b6126a2915060203d602011610a6357610a558183610708565b5f612678565b346106a1575f3660031901126106a15761264160206001600160a01b0380826126d030615824565b01516040516330b0680b60e01b815291166001600160a01b0316600482015292839190829081906024820190565b346106a15760403660031901126106a15761002060243560043561272182610d23565b805f525f602052611355600160405f200154614899565b346106a15760203660031901126106a15760043561275581610d23565b335f9081527fb888a84a969c69ee493013b8ca47e9ecebe24196f231bedf425581252c3c62e360205260409020545f80516020615e278339815191529060ff1680156127aa575b156110ed57610020826155f9565b50805f525f6020526127c333600160405f2001546139cd565b61279c565b346106a1575f3660031901126106a1576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346106a1575f3660031901126106a15761285b606061283261087f602061087130615824565b604051635637c1d960e11b81526001600160a01b03909116600482015291829081906024820190565b03817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa8015610a38576128d66121f8916107ec935f916128de575b506128d061090460406128c161090460208601516001600160801b031690565b9301516001600160801b031690565b9061301c565b6128d0614181565b612900915060603d606011612906575b6128f88183610708565b8101906140dd565b5f6128a1565b503d6128ee565b346106a1575f3660031901126106a1576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346106a1575f3660031901126106a1576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346106a1575f3660031901126106a15760206040517f355caf1c2580ed8185acb5ea3573b71f85186b41bdf69e3eb8f1fcd122a562df8152f35b346106a15760203660031901126106a1576100206004356129ef81610d23565b615688565b346106a1575f3660031901126106a1576060600454604051906001600160701b03811682528060701c600d0b602083015260e01c6040820152f35b346106a1575f3660031901126106a1576020610be8614181565b346106a1575f3660031901126106a157612a616139f4565b335f9081527f6691f8833b7b4761748530debf638b71868148ba67d9725d03a36fa8c55c585960205260409020547f9586321ac05f110e4b4a0a42aba899709345af0ca78910e8832ddfd71fed2bf49060ff168015612ac8575b156110ed57610020615738565b50805f525f602052612ae133600160405f2001546139cd565b612abb565b346106a1575f3660031901126106a15760206040517fe996ac9b332538bb1fa3cd6743aa47011623cdb94bd964a494ee9d371e4a27d38152f35b346106a15760803660031901126106a157600435612b3d81610d23565b60243590612b4a82610d23565b60443591612b57816146d8565b6002549160ff8316612cb0576001600160a01b0393307f0000000000000000000000000000000000000000000000000000000000000000861614612c9e57612bb3612be5926001612bf39660ff19161760025560643590615b9d565b7f5daa87a0e9463431830481fd4b6e3403442dfb9a12b9c07597e9f61d50b633c85f80a1612be081615520565b61553c565b612bee816155f9565b6148db565b50612bfc615c0a565b612c04615c52565b60405163095ea7b360e01b81527f000000000000000000000000000000000000000000000000000000000000000082166001600160a01b031660048201525f19602482015290602090829060449082905f907f0000000000000000000000000000000000000000000000000000000000000000165af18015610a3857612c8657005b6100209060203d602011611967576119598183610708565b60405163f9cc8ff960e01b8152600490fd5b60405162dc149f60e41b8152600490fd5b346106a1575f3660031901126106a157602061ffff60e0610f9b613feb565b346106a15760603660031901126106a157600435612cfd81610d23565b604435612d0981610d23565b335f9081527f8e22673d1bc81fb3bd6ed1d58f34ebd725bafdd9d714cf3052b13a12f254fcff60205260409020547fa38b301640bddfd3e6a9d2a11d13551d53ef81526347ff09d798738fcc5a49d4929060ff168015612d77575b15610ce657610020925060243590614273565b50825f525f602052612d9033600160405f2001546139cd565b612d64565b346106a15760403660031901126106a157600435612db281610d23565b602435612dbd61337b565b808211612e4d5750335f9081527fd86621a8c2273efca305981f63eac7b1eb8bb1b258c36bf730770cdcb2e9c89a60205260409020547f355caf1c2580ed8185acb5ea3573b71f85186b41bdf69e3eb8f1fcd122a562df929060ff168015612e2f575b15610ce65761002092506157a5565b50825f525f602052612e4833600160405f2001546139cd565b612e20565b604051633cbb525b60e21b815260048101929092526024820152604490fd5b346106a157612e7a366112a9565b90612e84826146fa565b5f5b828110612e8f57005b600190612ecf602080612ea3848888613563565b01355f612eb1858989613563565b3592612ebc84610d23565b8282525261106d8560405f200154614899565b5001612e86565b346106a1575f3660031901126106a1576020604051610e108152f35b346106a1576020806003193601126106a157612f0f600435615019565b604051636d78045960e01b81523360048201526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660248301526044820183905291929182908290815f8160648101611e2b565b60208091604051928184925191829101835e81017f1b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb090081520301902090565b908160209103126106a15751612fbf81610d23565b90565b6040513d5f823e3d90fd5b634e487b7160e01b5f52603260045260245ffd5b91908110156130035760051b81013590607e19813603018212156106a1570190565b612fcd565b634e487b7160e01b5f52601160045260245ffd5b9190820180921161302957565b613008565b908160209103126106a1575190565b9060405191602083015260208252604082018281106001600160401b038211176106cc57604052565b903590601e19813603018212156106a157018035906001600160401b0382116106a1576020019181360383136106a157565b908060209392818452848401375f828201840152601f01601f1916010190565b96959490926130d661310e946060969460808b5260808b0191613098565b602080928a8303828c01528051918291828552018484015e5f828201840152601f01601f191601888103820160408a01520191613098565b930152565b9190820391821161302957565b6131306020610b4c61087f6145b1565b03915afa8015610a3857612fbf915f91613151575b50610bb4610baf6134c5565b61316a915060203d602011610a6357610a558183610708565b5f613145565b91909161317c816146d8565b613185836146d8565b61318e826146fa565b6001600160a01b039081169273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee840361324f575f808080868686165af13d1561324a573d6131cf81610738565b906131dd6040519283610708565b81525f60203d92013e5b1561321e57907faca8fb252cde442184e5f10e0f2e6e4029e8cd7717cae63559079610702436aa915b6040519384521691602090a3565b604051636f54afdd60e11b81526001600160a01b03919091166004820152602481019290925250604490fd5b6131e7565b908161327d847faca8fb252cde442184e5f10e0f2e6e4029e8cd7717cae63559079610702436aa9487614701565b613210565b519061073682610d23565b51906001600160601b03821682036106a157565b519081151582036106a157565b519061ffff821682036106a157565b90816101409103126106a1576132d1610729565b906132db81613282565b82526132e96020820161328d565b60208301526132fa6040820161328d565b604083015261330b606082016132a1565b606083015261331c608082016132ae565b608083015261332d60a082016132ae565b60a083015261333e60c082016132ae565b60c083015261334f60e082016132ae565b60e08301526101006133628183016132ae565b908301526133746101208092016132a1565b9082015290565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166133b661087f602061087130615824565b60405163280be2f560e01b81526001600160a01b039190911660048201526101408082602481865afa8015610a38576133fc926060925f92613498575b50500151151590565b6134935760206134429161341661087f8361087130615824565b604051633a4952a360e01b81526001600160a01b03909116600482015292839190829081906024820190565b03915afa908115610a38575f91613474575b5061345d614181565b8082111561346e57612fbf91613113565b50505f90565b61348d915060203d602011610a6357610a558183610708565b5f613454565b505f90565b6134b79250803d106134be575b6134af8183610708565b8101906132bd565b5f806133f3565b503d6134a5565b6001600160a01b03602081816134da30615824565b015116602460405180948193632de0e07560e01b835260048301527f0000000000000000000000000000000000000000000000000000000000000000165afa908115610a38575f91613544575b50613530614181565b8082111561346e5781039081116130295790565b61355d915060203d602011610a6357610a558183610708565b5f613527565b91908110156130035760061b0190565b906001600160801b0360065416908082036135d25750506a084595161401484a00000081116135c0576135ad6135a7613987565b36614ae2565b15613493576135bb9061453c565b600190565b60405163f579fde760e01b8152600490fd5b6044925060405191637f8c068160e11b835260048301526024820152fd5b9061362d916135ff8230614e48565b6040516240cd2760e21b815260048101929092526020926001600160a01b0392908490829081906024820190565b0381867f0000000000000000000000000000000000000000000000000000000000000000165afa908115610a38576136ab9385915f936136e9575b507f0000000000000000000000000000000000000000000000000000000000000000169160405180958192630ea598cb60e41b8352600483019190602083019252565b03815f855af1928315610a3857610736945f946136ca575b5050614701565b6136e1929450803d10610a6357610a558183610708565b915f806136c3565b613701919350823d8411610a6357610a558183610708565b915f613668565b61371181614f02565b61372261371c613987565b36614c7c565b15613493576135bb90614f35565b6001600160a01b036020818161374530615824565b01511660246040518094819363329ce48960e21b835260048301527f0000000000000000000000000000000000000000000000000000000000000000165afa908115610a38575f91613795575090565b612fbf915060203d602011610a6357610a558183610708565b6137ba61073692615019565b90614e48565b908160209103126106a157612fbf906132a1565b60405f8180516137e3816106d1565b828152826020820152015260018060a01b03606081602061380330615824565b015116602484518094819363f9c821a760e01b835260048301527f0000000000000000000000000000000000000000000000000000000000000000165afa918215610a38575f9261385357505090565b9091506060823d6060116138c4575b8161386f60609383610708565b810103126106a157805191613883836106d1565b80516001600160701b03811681036106a157835260208101519081600d0b82036106a1578291602085015201519063ffffffff821682036106a15782015290565b3d9150613862565b6138d590615019565b335f9081527fe23a8bac6ee120cc0f31e97b679e63d8a06b6b0f7ec6a1eaa96912d2415af2e060205260409020547f3f82ecf462ddac43fc17ba11472c35f18b7760b4f5a5fc50b9625f9b5a22cf629060ff16801561393e575b156110ed575061073690614970565b50805f525f60205261395733600160405f2001546139cd565b61392f565b6001600160401b0381116106cc5760051b60200190565b80518210156130035760209160051b010190565b60405190613994826106d1565b60028252602082019160403684378092815115613003575f90528051600110156130035760405f80516020615e27833981519152910152565b5f908152602081815260408083206001600160a01b0390941683529290522060ff90541690565b6139fc614181565b8015613b4f576001600160801b03613a1c6006546001600160801b031690565b16613b42575b613a66613a2d6137d4565b6001600160701b03815116602082015160701b91604063ffffffff60e01b91015160e01b16916001600160701b0360701b161717600455565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316613aa161087f602061087130615824565b6005546001600160a01b031690823b156106a157604051636ce5768960e11b81526001600160a01b0391821660048201529116602482015260448101839052905f908290606490829084905af18015610a3857613b2f575b5060405190815233907f66a284d7a46e07e3f2d18b3955d275877589083787a7f656543d82c49a5db9c09080602081015b0390a2565b80610a2c613b3c926106b9565b5f613af9565b613b4a6144c3565b613a22565b50565b6001600160401b038116036106a157565b908160209103126106a15751612fbf81613b52565b908160809103126106a1576040519060808201908282106001600160401b038311176106cc57606091604052613bad816132a1565b835260208101516020840152604081015160408401520151606082015290565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166020613c0961087f8261087130615824565b60408051634a2264ef60e01b81526001600160a01b0392909216600480840191909152909290918181602481885afa908115610a38575f91613eb1575b5015613ea2576006546001600160401b038383613c6461087f6154cd565b875163c6290e3560e01b815292839182905afa8015610a385782915f91613e75575b5016908260801c161015613e65576001600160801b0316613e5657613ced6080613cb161087f6154cd565b613cc161087f8561087130615824565b86516329938c8360e21b81526001600160a01b0390911686820190815290938492918391829160200190565b03915afa8015610a3857613d09915f91613e27575b5051151590565b613e1857613d1685615520565b613d28613d2461371c613987565b1590565b613e0f57613d43613d37614181565b95612be0613a2d6137d4565b84613d53575b5050505050600190565b61087f613d639161087130615824565b6005549093906001600160a01b031690803b156106a1578351636ce5768960e11b81526001600160a01b03958616938101938452949091166020830152604082018590525f9184919082908490829060600103925af1918215610a38577f66a284d7a46e07e3f2d18b3955d275877589083787a7f656543d82c49a5db9c092613dfc575b50519182523391602090a25f80808080613d49565b80610a2c613e09926106b9565b5f613de7565b50505050505f90565b50905163a9d1c85160e01b8152fd5b613e49915060803d608011613e4f575b613e418183610708565b810190613b78565b5f613d02565b503d613e37565b509051635abae25f60e01b8152fd5b8351632a68326f60e11b81528390fd5b613e959150853d8711613e9b575b613e8d8183610708565b810190613b63565b5f613c86565b503d613e83565b5090516366ad59df60e11b8152fd5b613ec89150823d8411611967576119598183610708565b5f613c46565b34613f65575b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316613f078161521a565b613f1861087f602061087130615824565b90803b156106a15760405163135c2b5960e31b81526001600160a01b039290921660048301525f908290602490829084905af18015610a3857613f585750565b80610a2c610736926106b9565b613f7661087f602061087130615824565b803b156106a1575f600491604051928380926316c1a85160e31b825234905af18015610a3857613fa7575b50613ed4565b80610a2c613fb4926106b9565b5f613fa1565b6001600160801b0360065416908101809111613029576a084595161401484a00000081116135c0576107369061453c565b5f610120604051613ffb816106ec565b8281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e082015282610100820152015261407361404561087f602061087130615824565b60405163280be2f560e01b81526001600160a01b039091166004820152610140918290829081906024820190565b03817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa918215610a38575f926140b357505090565b612fbf9250803d106134be576134af8183610708565b51906001600160801b03821682036106a157565b908160609103126106a15761411b60408051926140f9846106d1565b614102816140c9565b8452614110602082016140c9565b6020850152016140c9565b604082015290565b600d91820b910b03906d7fffffffffffffffffffffffffff1982126d7fffffffffffffffffffffffffff83131761302957565b81810392915f13801582851316918412161761302957565b8181029291811591840414171561302957565b61424f604051614190816106d1565b61424a6142446004546001600160701b038116845260208401908060701c600d0b825260e01c604085015261422d6141c66137d4565b9161422d61423c60206142336141f56141ef6141ea6006546001600160801b031690565b615710565b600f0b90565b9961422d61421061421f6142108b516001600160701b031690565b6001600160701b0316600d0b90565b92516001600160701b031690565b90614123565b950151600d0b90565b9151600d0b90565b600d0b90565b614156565b5f811361425b57505f90565b61426b612fbf916003549061416e565b612710900490565b61427c816146d8565b614285836146d8565b6001600160a01b0390811692833b156106a157604051632142170760e11b81523060048201526001600160a01b038216602482015260448101849052915f8360648183895af1918215610a38577f6a30e6784464f0d1f4158aa4cb65ae9239b0fa87c7f2c083ee6dde44ba97b5e693602093614308575b506040519485521692a3565b614311906106b9565b5f6142fc565b5f80516020615e878339815191525c1561432d57565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff168015614370575b156110ed575061073634614ddc565b50805f525f60205261438933600160405f2001546139cd565b614361565b60015f80516020615e878339815191525d565b335f9081527f445ddf9e128c79eca57440455d965dbaaca76bdc8b6662aee0bd18f61bc6486160205260409020547fea6487df651bb740150364c496e1c7403dd62063c96e44906cc98c6a919a9d889060ff168015614493575b156110ed57506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169190602061443930615824565b01511690823b156106a157604051636ce5768960e11b81526001600160a01b0390921660048301523060248301526044820152905f908290818381606481015b03925af18015610a385761448a5750565b610736906106b9565b50805f525f6020526144ac33600160405f2001546139cd565b6143fb565b5f5f80516020615e878339815191525d565b6006546001600160801b03811690811561452a576001600160c01b0319164260801b67ffffffffffffffff60801b1617600655604080515f815260208101929092527f6294ce24c5cb1a9fd25acf9dc37a0c6cdd144f222c80f43b5582bc86a882564091a1565b6040516318a04aa160e01b8152600490fd5b600654906001600160801b03908183169182821461452a576001600160c01b031993909316928116929092174260801b67ffffffffffffffff60801b16176006556040805192835260208301919091527f6294ce24c5cb1a9fd25acf9dc37a0c6cdd144f222c80f43b5582bc86a882564091a1565b604051631734070560e31b81526001600160a01b03906020816004817f000000000000000000000000000000000000000000000000000000000000000086165afa908115610a38575f9161460457501690565b61461d915060203d602011610ad457610ac58183610708565b1690565b61ffff608061462e613feb565b0151166127109081039181831161302957612fbf9261464c9161416e565b04615019565b908082101561465f575090565b905090565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169190602061469c30615824565b01511691803b156106a157614479935f80946040519687958694859363015a8f6160e61b85526004850152604060248501526044840191613098565b6001600160a01b0316156146e857565b60405163d92e233d60e01b8152600490fd5b1561136157565b60405163a9059cbb60e01b60208201526001600160a01b03909216602483015260448201929092526107369161474482606481015b03601f198101845283610708565b61586a565b359061073682613b52565b90936001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116949092602090848261479230615824565b01511697873b156106a1579795949392906147cf6020916040519a8b99632f00a7e160e01b8b5260048b0152608060248b015260848a0191613098565b8781036003190160448901528281520194915f5b82811061480e57505050505f949284928392166064830152039134905af18015610a3857613f585750565b9193949583975090806001926001600160401b0382963561482e81613b52565b168152019701910191889695949391926147e3565b335f9081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5602052604090205460ff161561487b57565b60405163e2517d3f60e01b81523360048201525f6024820152604490fd5b5f8181526020818152604080832033845290915290205460ff16156148bb5750565b60405163e2517d3f60e01b81523360048201526024810191909152604490fd5b6148e4816158c4565b90816148ee575090565b5f80516020615e278339815191525f526001602052614936906001600160a01b03167fa5a481183f76091139902c30e9c198fa5bc995beffa115a7cdc58a48752719ad615cbc565b5090565b6149448282615a1c565b918261494f57505090565b5f91825260016020526040909120614936916001600160a01b031690615cbc565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116919060206149a830615824565b01511690823b156106a1576040516303da9b9d60e41b81526001600160a01b0390921660048301526024820152905f90829081838160448101614479565b6149f08282615a85565b91826149fb57505090565b5f91825260016020526040909120614936916001600160a01b031690615d68565b90614a268261395c565b614a336040519182610708565b8281528092614a44601f199161395c565b0190602036910137565b60206040518281935f833781017f1b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb090081520301902090565b5f1981146130295760010190565b91608093918352602083015260606040830152806060830152805f848401375f828201840152601f01601f1916010190565b91614ade9183549060031b91821b915f19901b19161790565b9055565b815115614c6a578151915f614af684614a1c565b5f614b0f5f80516020615e67833981519152544261301c565b925f5b878110614bb957509015614ba7578503614b5c5750505f5b838110614b3a5750505050600190565b805f614b55614b4b60019486613973565b516107ce87614a4e565b5501614b2a565b5f9391935b858110614b72575050505050505f90565b80614b88614b8260019385613973565b51151590565b614b93575b01614b61565b85614ba1614b4b8388613973565b55614b8d565b60405163dd16cb2360e01b8152600490fd5b614bc38187613973565b5192614bcf33856139cd565b15614c365750600190614be28293614a85565b93614bf6614bf08388613973565b60019052565b6040517f52a98ceea5bf73fc6482a17b325efa51b21372c60578c2fa4cac0c2fa780035a339180614c29368c4284614a93565b0390a35b01919091614b12565b9192614c4642916107ce8a614a4e565b541015614c56575b600190614c2d565b91614c62600191614a85565b929050614c4e565b60405163f814a95b60e01b8152600490fd5b815115614c6a578151915f614c9084614a1c565b5f614ca95f80516020615e67833981519152544261301c565b925f5b878110614d3157509015614ba7578503614cec5750505f5b838110614cd45750505050600190565b805f614ce5614b4b60019486613973565b5501614cc4565b5f9391935b858110614d02575050505050505f90565b80614d12614b8260019385613973565b614d1d575b01614cf1565b85614d2b614b4b8388613973565b55614d17565b614d3b8187613973565b5192614d4733856139cd565b15614da85750600190614d5a8293614a85565b93614d68614bf08388613973565b6040517f52a98ceea5bf73fc6482a17b325efa51b21372c60578c2fa4cac0c2fa780035a339180614d9b368c4284614a93565b0390a35b01919091614cac565b9192614db842916107ce8a614a4e565b541015614dc8575b600190614d9f565b91614dd4600191614a85565b929050614dc0565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116906020614e1330615824565b01511690803b156106a15760245f926040519485938492630460488160e31b845260048401525af18015610a385761448a5750565b9190614e52613120565b808211614ee35750335f9081527fd8ab213733a751ec9ca51242fa42dd7f07dc61105cb44f3595d018b75a32f4f560205260409020547fe996ac9b332538bb1fa3cd6743aa47011623cdb94bd964a494ee9d371e4a27d3939060ff168015614ec5575b156115c957610736929350615b09565b50835f525f602052614ede33600160405f2001546139cd565b614eb5565b60405163087df69d60e41b815260048101929092526024820152604490fd5b610e108110908115614f28575b50614f1657565b60405163bbc3a3e360e01b8152600490fd5b62278d009150115f614f0f565b614f3e81614f02565b5f80516020615e6783398151915280549082905560408051918252602082019290925233917f437dc1a3e26abf0b8381463abca48d1e7d322e803c2b160d3ee75c04a492a182919081908101613b2a565b614fdb926020926001600160a01b03915f908386614fac30615824565b015116908460405198899788968795636c1d053960e11b87526004870152606060248701526064860191613098565b9116604483015203927f0000000000000000000000000000000000000000000000000000000000000000165af1908115610a38575f91613795575090565b604051631920845160e01b815260048101919091526020816024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610a38575f91613795575090565b91908110156130035760051b8101359060be19813603018212156106a1570190565b81835290916001600160fb1b0383116106a15760209260051b809284830137010190565b9035601e19823603018112156106a15701602081359101916001600160401b0382116106a15781360383136106a157565b6001600160a01b03909116815260406020820152813536839003601e19018112156106a157820190602082359201916001600160401b0381116106a1578060051b360383136106a1576151c260a0615174615155612fbf9660e09560c06040890152610100880191615093565b61516260208901896150b7565b878303603f1901606089015290613098565b956040810135608086015261519d61518e60608301614749565b6001600160401b031686840152565b6151bc6151ac60808301614749565b6001600160401b031660c0870152565b01614749565b6001600160401b0316910152565b6151d8614843565b6001600160a01b0360206151eb30615824565b015116803b156106a1575f80916004604051809481936379ba509760e01b83525af18015610a385761448a5750565b615222614843565b6001600160a01b039081602061523730615824565b01511691823b156106a15760245f9283604051958694859363f2fde38b60e01b85521660048401525af18015610a385761448a5750565b335f9081527f33325f4ccadc9d27525cbee4f590af3a5c5c236abe27b43530b1a65b898523876020908152604090912054909291907f655996ffb2e08cf642b4ce5cc56668761aed116cdfcb21f71ff9fe7fbcbd450e9060ff168015615346575b156110ed5750829060646001600160a01b035f816152eb6145b1565b1691856152f730615824565b0151169160405196879586946321d5a4bf60e11b86526004860152602485015260448401525af1918215610a38575f9261533057505090565b612fbf9250803d10611967576119598183610708565b50805f525f845261535e33600160405f2001546139cd565b6152cf565b335f9081527fa2e2d001ecdfead4b8d27592f8b4686eb9b6cd4f5dec9f06f76377b2d0a1a1bb60205260409020547f689f0a569be0c9b6cd2c11c81cb0add722272abdae6b649fdb1e05f1d9bb8a2f9060ff1680156153cc575b156110ed5750610736906153ea565b50805f525f6020526153e533600160405f2001546139cd565b6153bd565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169190602061542230615824565b01511690823b156106a157604051633b9e9f0160e21b81526001600160a01b0390921660048301526024820152905f90829081838160448101614479565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811690602061549730615824565b015116813b156106a1575f9160248392604051948593849263ee762f2b60e01b845260048401525af18015610a385761448a5750565b604051630b8e411760e21b81526001600160a01b03906020816004817f000000000000000000000000000000000000000000000000000000000000000086165afa908115610a38575f9161460457501690565b6127101061552a57565b60405163137b7b8960e31b8152600490fd5b61554581615520565b7fce7788c577af89829bf8edd40fe3bf5511a2a01bd1ceb67cf4b7a1eecd8cd0c7613b2a600354928060035560405191829133958360209093929193604081019481520152565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169060206155c330615824565b015116813b156106a1575f91602483926040519485938492633ffa490d60e01b845260048401525af18015610a385761448a5750565b615602816146d8565b6005546001600160a01b03808216929081169190828414615676576001600160a01b0319909116909117600555604080516001600160a01b03938416815292909116602083015233917f50c7826394d934be76de5ce7a66c3bc33795e8c3e613982f8cc792cbbcea3b129181908101613b2a565b60405163f012261f60e01b8152600490fd5b61569361371c613987565b15613b4f576001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116919060206156d030615824565b01511690823b156106a15760405163fdc10daf60e01b81526001600160a01b03928316600482015291166024820152905f90829081838160448101614479565b6001600160801b03166f7fffffffffffffffffffffffffffffff80821161465f5750600f0b90565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811690602061576f30615824565b015116813b156106a1575f91602483926040519485938492632c4422a760e11b845260048401525af18015610a385761448a5750565b906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116919060206157de30615824565b01511691803b156106a157604051636ce5768960e11b81526001600160a01b03938416600482015293909216602484015260448301525f90829081838160648101614479565b803b90602c19820191821161302957602d61585761584184610738565b9361584f6040519586610708565b808552610738565b6020840190601f19013682378351923c90565b905f602091828151910182855af115612fc2575f513d6158bb57506001600160a01b0381163b155b6158995750565b604051635274afe760e01b81526001600160a01b039091166004820152602490fd5b60011415615892565b6001600160a01b0381165f9081527fb888a84a969c69ee493013b8ca47e9ecebe24196f231bedf425581252c3c62e3602052604090205f80516020615e278339815191529060ff905b541661346e575f818152602081815260408083206001600160a01b03861684529091529020805460ff1916600117905533916001600160a01b0316907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d5f80a4600190565b6001600160a01b0381165f9081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5602052604090205460ff16613493576001600160a01b03165f8181527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb560205260408120805460ff191660011790553391907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d8180a4600190565b5f818152602081815260408083206001600160a01b0386168452909152902060ff9061590d565b6040516323b872dd60e01b60208201526001600160a01b0392831660248201529290911660448301526064820192909252610736916147448260848101614736565b5f818152602081815260408083206001600160a01b038616845290915290205460ff161561346e575f818152602081815260408083206001600160a01b03861684529091529020805460ff1916905533916001600160a01b0316907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b5f80a4600190565b906001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811691906020615b4230615824565b01511691803b156106a157604051635fc5d2cb60e11b81526001600160a01b03938416600482015293909216602484015260448301525f90829081838160648101614479565b8054821015613003575f5260205f2001905f90565b9061073691615bab816146d8565b615bb481615972565b615bc8575b50615bc381614f02565b614f35565b5f80526001602052615c03906001600160a01b03167fa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49615cbc565b505f615bb9565b5f80516020615e27833981519152805f525f602052600160405f20018181549155817fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff5f80a4565b7fe0b9915a7819e810f29b50730662441fec3443eb363b7e7c90c77fada416f276805f525f602052600160405f2001908154905f80516020615e278339815191528093557fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff5f80a4565b6001810190825f528160205260405f2054155f14615d24578054680100000000000000008110156106cc57615d11615cfb826001879401855584615b88565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b8054908115615d54575f1991820191615d448383615b88565b909182549160031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615dfe575f199284840190858211613029578054948501948511613029575f958583615db7976107ce9503615dbd575b505050615d2b565b55600190565b615de7615de191615dd1615df59487615b88565b90549060031b1c92839187615b88565b90614ac5565b85905f5260205260405f2090565b555f8080615daf565b505050505f9056fe6fe412e9d35aac9d96821beeae87f7dbc832b35e704e374aa2575d81ecc7787e59783a4ae82167eefad593739a5430c1d9e896a16c35f1e5285ddd0c0980885c933b7d5c112a4d05b489cea0b2ced98acb27d3d0fc9827c92cdacb2d6c5559c21b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb09017408b7b034fda7051615c19182918ecb91d753231cffd86f81a45d996d63e038a2646970667358221220f5e55cedfae21d6c89206c403271fceafc34074fe06feeb6d18ab9559a3b466064736f6c634300081900330000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba100000000000000000000000005f2927c5c2825bc0dcdc14d258a99a36116be8b00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea291980
Deployed Bytecode
0x60806040526004361015610022575b3615610018575f80fd5b610020614317565b005b5f3560e01c806301ffc9a714610631578063032d9b301461062c5780630a4a3ed2146106275780630b91990d146106225780630bff88811461061d5780630c7c92461461061857806310741552146106135780631171bda91461060e57806313e40efb14610609578063158ef93e14610604578063196b9e06146105ff578063248a9ca3146105fa57806324e7964a146105f55780632806f5e0146105f05780632959843a146105eb5780632ae87c3e146105e65780632c0772b4146105e15780632f2ff15d146105dc5780633046bf47146105d757806332cd7ebf146105d257806333885111146105cd57806335763d2b146105c857806336568abe146105c35780633a04d4f3146105be5780633a284985146105b95780633b37990e146105b45780633be75aa3146105af5780633e619a6c146105aa5780633efd8597146105a55780634156bbf4146105a057806343c710571461059b578063487e9d4e146105965780634bc368401461059157806350d99dd41461058c578063520107ad146105875780635251ebf914610582578063528c198a1461057d5780635680e145146105785780635bc7136314610573578063626c5ebc1461056e578063634bd91a14610569578063699d79bd146105645780636ab158361461055f5780636af6eeed1461055a5780636d4755661461055557806370b24b6914610550578063722adad21461054b57806373b257bc146105465780637b108d92146105415780638322fff21461053c578063845d0a3a14610537578063846f4d7f14610532578063853c637d1461052d5780638860be4114610528578063899896cb146105235780638c5d3e341461051e5780639010d07c1461051957806391d1485414610514578063956311051461050f57806399bce5e71461050a5780639c908158146105055780639c9708a914610500578063a217fddf146104fb578063a3246ad3146104f6578063a6218709146104f1578063b1003ec3146104ec578063b35869ed146104e7578063b60d4288146104e2578063b930908f146104dd578063bccad58f146104d8578063bddf300c146104d3578063be807b28146104ce578063c70cb120146104c9578063ca15c873146104c4578063ca8317af146104bf578063cb8fbf98146104ba578063cf309012146104b5578063d4c3eea0146104b0578063d547741f146104ab578063d877a3fc146104a6578063d9fb643a146104a1578063dadb623d1461049c578063dbba4b4814610497578063e00bfe5014610492578063e02023a11461048d578063e071c0ca14610488578063e55d864b14610483578063e5bca8af1461047e578063e6c772d414610479578063e9a9c85014610474578063eb990c591461046f578063ef914a091461046a578063f0e9fcd114610465578063f3fef3a314610460578063f634dcbd1461045b578063f6c76318146104565763ff108ccb0361000e57612ef2565b612ed6565b612e6c565b612d95565b612ce0565b612cc1565b612b20565b612ae6565b612a49565b612a2f565b6129f4565b6129cf565b612995565b612951565b61290d565b61280c565b6127c8565b612738565b6126fe565b6126a8565b6125eb565b6125c6565b61250c565b6124e2565b6124a8565b61246e565b6123d9565b612340565b612306565b61229b565b612261565b612227565b61218e565b61211b565b6120bd565b6120aa565b61208c565b612074565b611fdf565b611f8e565b611f49565b611f28565b611f0b565b611eec565b611dba565b611d9a565b611d5f565b611d31565b611cd5565b611cb6565b611c33565b611bbd565b611b83565b611b3b565b611b01565b611ac7565b611aa0565b611a66565b611a3f565b611a05565b61196e565b611863565b611728565b611691565b611677565b611640565b611606565b611522565b611504565b61146d565b61144a565b61141f565b6113e5565b6113be565b611373565b6112f5565b611281565b611129565b611072565b611033565b610ff9565b610fdf565b610fc5565b610fa6565b610f77565b610f4b565b610f07565b610ee5565b610dea565b610d34565b610c4c565b610b2b565b610b0e565b610ae5565b61084e565b610753565b346106a15760203660031901126106a15760043563ffffffff60e01b81168091036106a157602090635a05180f60e01b8114908115610676575b506040519015158152f35b637965db0b60e01b811491508115610690575b505f61066b565b6301ffc9a760e01b1490505f610689565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b6001600160401b0381116106cc57604052565b6106a5565b606081019081106001600160401b038211176106cc57604052565b61014081019081106001600160401b038211176106cc57604052565b90601f801991011681019081106001600160401b038211176106cc57604052565b60405190610736826106ec565b565b6001600160401b0381116106cc57601f01601f191660200190565b346106a15760403660031901126106a1576004356001600160401b0381116106a157366023820112156106a15780600401359061078f82610738565b61079c6040519182610708565b82815236602484840101116106a1575f6020846107ec9560246107db960183860137830101526107ce60243591612f6c565b905f5260205260405f2090565b546040519081529081906020820190565b0390f35b9181601f840112156106a1578235916001600160401b0383116106a1576020808501948460051b0101116106a157565b60206003198201126106a157600435906001600160401b0382116106a15761084a916004016107f0565b9091565b346106a15761085c36610820565b5f9060209061088b61087f8361087130615824565b01516001600160a01b031690565b6001600160a01b031690565b6040928351636b96736b60e01b815260048282600481875afa918215610a38575f92610aac575b505f5b88868210610a8a5750506108c761337b565b808811610a6a57506108d761438e565b6108e0876143a1565b6108e86144b1565b61091a610915886109106109046006546001600160801b031690565b6001600160801b031690565b61301c565b61453c565b855163266bcf0560e11b81528381600481885afa8015610a3857610945915f91610a3d575b5061303d565b5f5b8681106109a1578751878152602081018a90526107ec908a908a906001600160a01b038a16907f7ca089edbf098275c3dd9807ef0d5e5f7a4c4ed5a4ed4b865def78c7260f6b7f90604090a2519081529081906020820190565b6109ac81888c612fe1565b6001600160a01b03851691906109c28180613066565b916109cf89820182613066565b939095803b156106a1578d8894610a028b5f9884519b8c998a9889966304512a2360e31b885260608c01359588016130b8565b03930135905af1918215610a3857600192610a1f575b5001610947565b80610a2c610a32926106b9565b80610adb565b5f610a18565b612fc2565b610a5d9150853d8711610a63575b610a558183610708565b81019061302e565b5f61093f565b503d610a4b565b8651633cbb525b60e21b8152600481018990526024810191909152604490fd5b819888610a9d6001948a610aa595612fe1565b01359061301c565b97016108b5565b610acd919250833d8511610ad4575b610ac58183610708565b810190612faa565b905f6108b2565b503d610abb565b5f9103126106a157565b346106a1575f3660031901126106a15760205f80516020615e6783398151915254604051908152f35b346106a1575f3660031901126106a157602060405162278d008152f35b346106a1576020806003193601126106a157610b8881610b4c61087f6145b1565b610b5c61087f8361087130615824565b604051630131592760e61b81526001600160a01b03909116600482015292839190829081906024820190565b03915afa8015610a3857610be8915f91610c02575b50610bb4610baf6004356109106134c5565b614621565b610bbc613730565b918280821115610bf957610bcf91613113565b915b80821115610bf057610be291613113565b90614652565b604051908152f35b50505f90614652565b50505f91610bd1565b610c199150833d8511610a6357610a558183610708565b5f610b9d565b9181601f840112156106a1578235916001600160401b0383116106a157602083818601950101116106a157565b346106a15760203660031901126106a1576004356001600160401b0381116106a157610c7c903690600401610c1f565b335f9081527fb263cec6d0d87327feaa8f63140311e533b781670aff412d50297f0882a7530460205260409020547f32d0d6546e21c13ff633616141dc9daad87d248d1d37c56bf493d06d627ecb7b929060ff168015610d05575b15610ce6576100209250614664565b60405163e2517d3f60e01b815233600482015260248101849052604490fd5b50825f525f602052610d1e33600160405f2001546139cd565b610cd7565b6001600160a01b038116036106a157565b346106a15760603660031901126106a157600435610d5181610d23565b60243590610d5e82610d23565b335f9081527f8e22673d1bc81fb3bd6ed1d58f34ebd725bafdd9d714cf3052b13a12f254fcff60205260409020547fa38b301640bddfd3e6a9d2a11d13551d53ef81526347ff09d798738fcc5a49d4929060ff168015610dcc575b15610ce657610020925060443591613170565b50825f525f602052610de533600160405f2001546139cd565b610db9565b60603660031901126106a1576001600160401b036004358181116106a157610e16903690600401610c1f565b90916024359081116106a157610e309036906004016107f0565b9060443592610e3e84610d23565b335f9081527f894c422fd63ef7348a50b435343ac685160997c7193187427501d7dfbbf7b59760205260409020547fea19d3b23bd90fdd52445ad672f2b6fb1fef7230d49c6a827c1cd288d02994d5959060ff168015610ec7575b15610ea8576100209550614754565b60405163e2517d3f60e01b815233600482015260248101879052604490fd5b50855f525f602052610ee033600160405f2001546139cd565b610e99565b346106a1575f3660031901126106a157602060ff600254166040519015158152f35b346106a1575f3660031901126106a1576040517f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b03168152602090f35b346106a15760203660031901126106a1576004355f525f6020526020600160405f200154604051908152f35b346106a1575f3660031901126106a15760206001600160a01b0381610f9b30615824565b015116604051908152f35b346106a1575f3660031901126106a157602061ffff60a0610f9b613feb565b346106a1575f3660031901126106a1576020610be861337b565b346106a1575f3660031901126106a1576020610be86134c5565b346106a1575f3660031901126106a15760206040517fa90c7030a27f389f9fc8ed21a0556f40c88130cc14a80db936bed68261819b2c8152f35b346106a15760403660031901126106a15761002060243560043561105682610d23565b805f525f60205261106d600160405f200154614899565b61493a565b346106a15760203660031901126106a157335f9081527fe23a8bac6ee120cc0f31e97b679e63d8a06b6b0f7ec6a1eaa96912d2415af2e060205260409020547f3f82ecf462ddac43fc17ba11472c35f18b7760b4f5a5fc50b9625f9b5a22cf629060ff16801561110b575b156110ed57610020600435614970565b6044906040519063e2517d3f60e01b82523360048301526024820152fd5b50805f525f60205261112433600160405f2001546139cd565b6110dd565b346106a1576020806003193601126106a1576001600160a01b03905f8161119c7f00000000000000000000000005f2927c5c2825bc0dcdc14d258a99a36116be8b851660043561117b81303385615a43565b604051948580948193636f074d1f60e11b8352600483019190602083019252565b03925af18015610a38576111b7915f91611264575b50615019565b604051638fcb4e5b60e01b81527f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe0884166001600160a01b0316600482015260248101829052929091908190849060449082905f907f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba1165af1928315610a385761002093611246575b5050615363565b8161125c92903d10610a6357610a558183610708565b505f8061123f565b61127b9150833d8511610a6357610a558183610708565b5f6111b1565b346106a1575f3660031901126106a1576005546040516001600160a01b039091168152602090f35b9060206003198301126106a1576004356001600160401b03928382116106a157806023830112156106a15781600401359384116106a15760248460061b830101116106a1576024019190565b346106a157611303366112a9565b908115611361575f5b82811061131557005b60019061135a602080611329848888613563565b01355f611337858989613563565b359261134284610d23565b828252526113558560405f200154614899565b6149e6565b500161130c565b60405163b7852ebb60e01b8152600490fd5b346106a15760403660031901126106a15760243561139081610d23565b336001600160a01b038216036113ac57610020906004356149e6565b60405163334bd91960e11b8152600490fd5b346106a1575f3660031901126106a15760206040515f80516020615e278339815191528152f35b346106a1575f3660031901126106a15760206040517fa38b301640bddfd3e6a9d2a11d13551d53ef81526347ff09d798738fcc5a49d48152f35b346106a15760403660031901126106a1576020611440602435600435613573565b6040519015158152f35b346106a1575f3660031901126106a15760206001600160601b0381610f9b613feb565b60403660031901126106a15760043561148581610d23565b34611498575b61002090602435906135f0565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff1680156114e6575b156110ed5750610020906114df34614ddc565b905061148b565b50805f525f6020526114ff33600160405f2001546139cd565b6114cc565b346106a15760203660031901126106a1576020611440600435613708565b346106a15760403660031901126106a1576004356001600160401b0381116106a157611552903690600401610c1f565b6024359061155f82610d23565b335f9081527fb23b36340bdad7eb67d390e1f617f5a66ce55ba02098689f188a1df60c41421060205260409020547f17960a6b137243c888669d93712b617414dd6d1ab55d5eca488ccb1d0894cd5c939060ff1680156115e8575b156115c9576100209350614f8f565b60405163e2517d3f60e01b815233600482015260248101859052604490fd5b50835f525f60205261160133600160405f2001546139cd565b6115ba565b346106a1575f3660031901126106a15760206040517f59d005e32db662b94335d6bedfeb453fd2202b9f0cc7a6ed498d9098171744b08152f35b346106a1575f3660031901126106a15760406006546001600160401b038251916001600160801b038116835260801c166020820152f35b346106a1575f3660031901126106a1576020610be8613730565b60403660031901126106a1576004356116a981610d23565b346116bc575b61002090602435906137ae565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff16801561170a575b156110ed57506100209061170334614ddc565b90506116af565b50805f525f60205261172333600160405f2001546139cd565b6116f0565b346106a15761173636610820565b335f9081527f67416b44d24f10cfdaeae95ddaabfedeacd76567f63dd48b7ad4e79941f9b8586020908152604091829020547fb850402129bccae797798069a8cf3147a0cb7c3193f70558a75f7df0b8651c309060ff168015611845575b156110ed5750917f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b0316905f5b8181106117d157005b6117e161087f8661087130615824565b906117ed818489615071565b91843b156106a157855163015004fd60e61b8152925f91849182916118169190600484016150e8565b038183885af1918215610a3857600192611832575b50016117c8565b80610a2c61183f926106b9565b5f61182b565b50805f525f60205261185e33600160405f2001546139cd565b611794565b346106a15760203660031901126106a15760043561188081610d23565b6001600160a01b036118c8602061189d61087f8261087130615824565b604051600161da2560e61b031981526001600160a01b03909116600482015291829081906024820190565b0381857f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08165afa908115610a38575f9161193f575b5061192d578116301461191b57610020906119166151d0565b61521a565b604051633b07e30760e11b8152600490fd5b6040516309e1b82160e21b8152600490fd5b611961915060203d602011611967575b6119598183610708565b8101906137c0565b5f6118fd565b503d61194f565b60403660031901126106a15760043561198681610d23565b34611999575b6100209060243590614e48565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff1680156119e7575b156110ed5750610020906119e034614ddc565b905061198c565b50805f525f602052611a0033600160405f2001546139cd565b6119cd565b346106a1575f3660031901126106a15760206040517f3f82ecf462ddac43fc17ba11472c35f18b7760b4f5a5fc50b9625f9b5a22cf628152f35b346106a1575f3660031901126106a15760206040515f80516020615e478339815191528152f35b346106a1575f3660031901126106a15760206040517fb850402129bccae797798069a8cf3147a0cb7c3193f70558a75f7df0b8651c308152f35b346106a1575f3660031901126106a15760206040515f80516020615e878339815191528152f35b346106a1575f3660031901126106a15760206040517f32d0d6546e21c13ff633616141dc9daad87d248d1d37c56bf493d06d627ecb7b8152f35b346106a1575f3660031901126106a15760206040517fea19d3b23bd90fdd52445ad672f2b6fb1fef7230d49c6a827c1cd288d02994d58152f35b346106a1575f3660031901126106a1576060611b556137d4565b63ffffffff60408051926001600160701b0381511684526020810151600d0b60208501520151166040820152f35b346106a1575f3660031901126106a15760206040517f9586321ac05f110e4b4a0a42aba899709345af0ca78910e8832ddfd71fed2bf48152f35b346106a1575f806003193601126106a157611bd6614843565b6001600160a01b036020611be930615824565b015116803b156106a1575f80916004604051809481936379ba509760e01b83525af18015610a3857611c22575b50611c1f613ece565b80f35b611c2c91506106b9565b5f80611c16565b60203660031901126106a15734611c50575b6100206004356138cc565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff168015611c98575b156110ed5750611c9334614ddc565b611c45565b50805f525f602052611cb133600160405f2001546139cd565b611c84565b346106a1575f3660031901126106a157602061ffff60c0610f9b613feb565b346106a1575f3660031901126106a157611ced613987565b604051809160208083016020845282518091526020604085019301915f5b828110611d1a57505050500390f35b835185528695509381019392810192600101611d0b565b346106a1575f3660031901126106a157602060405173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee8152f35b60403660031901126106a157611d73613ece565b611d8160243560043561526e565b15611d8857005b60405163ce2a477560e01b8152600490fd5b346106a1575f3660031901126106a157602061ffff610100610f9b613feb565b346106a1576020806003193601126106a157604051636d78045960e01b8152336004808301919091526001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe088116602484015290356044830181905292919082908290815f81606481015b03927f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba1165af18015610a3857611ecf575b507f689f0a569be0c9b6cd2c11c81cb0add722272abdae6b649fdb1e05f1d9bb8a2f5f8181528083526040808220338352602052902090919060ff905416908115611eb1575b50156110ed57610020826153ea565b5f915082825252611ec933600160405f2001546139cd565b5f611ea2565b611ee590823d8411610a6357610a558183610708565b505f611e5c565b346106a1575f3660031901126106a157602061ffff6080610f9b613feb565b346106a1575f3660031901126106a1576020600354604051908152f35b346106a15760403660031901126106a157602061144060243560043561526e565b346106a15760403660031901126106a1576004355f5260016020526020611f7560243560405f20615b88565b905460405160039290921b1c6001600160a01b03168152f35b346106a15760403660031901126106a157602060ff611fd3602435611fb281610d23565b6004355f525f845260405f209060018060a01b03165f5260205260405f2090565b54166040519015158152f35b346106a1575f3660031901126106a157335f9081527f3f3dda3fcd93f9e4551f2c87aa737426b704aa48dbb418e3deea799b35c4558960205260409020547f59d005e32db662b94335d6bedfeb453fd2202b9f0cc7a6ed498d9098171744b09060ff168015612056575b156110ed57610020615460565b50805f525f60205261206f33600160405f2001546139cd565b612049565b346106a1575f3660031901126106a1576100206139f4565b346106a15760203660031901126106a1576020611440600435613bcd565b5f3660031901126106a157610020613ece565b346106a1575f3660031901126106a15760206040515f8152f35b60209060206040818301928281528551809452019301915f5b8281106120fe575050505090565b83516001600160a01b0316855293810193928101926001016120f0565b346106a1576020806003193601126106a1576004355f5260018060205260405f20916040519182602085549182815201945f5260205f20925f905b828210612179576107ec8661216d818a0382610708565b604051918291826120d7565b84548752958601959383019390830190612156565b346106a1575f3660031901126106a1576121d260206001600160a01b03610b5c816121b76145b1565b1691836121c330615824565b0151166001600160a01b031690565b03915afa908115610a38576107ec916121f8915f91612208575b50610be2610baf6134c5565b6040519081529081906020820190565b612221915060203d602011610a6357610a558183610708565b5f6121ec565b346106a1575f3660031901126106a15760206040517f655996ffb2e08cf642b4ce5cc56668761aed116cdfcb21f71ff9fe7fbcbd450e8152f35b346106a1575f3660031901126106a15760206040517fe0b9915a7819e810f29b50730662441fec3443eb363b7e7c90c77fada416f2768152f35b5f3660031901126106a157335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff1680156122e8575b156110ed5761002034614ddc565b50805f525f60205261230133600160405f2001546139cd565b6122da565b346106a1575f3660031901126106a15760206040517f689f0a569be0c9b6cd2c11c81cb0add722272abdae6b649fdb1e05f1d9bb8a2f8152f35b346106a15760203660031901126106a157335f9081527fff5c6f7275dcc6faef7a8ce369f5d9cdfe4b73e6d3ca76b92d14e1011cfad56860205260409020547fe0b9915a7819e810f29b50730662441fec3443eb363b7e7c90c77fada416f2769060ff1680156123bb575b156110ed57610020600435613fba565b50805f525f6020526123d433600160405f2001546139cd565b6123ab565b346106a1575f3660031901126106a157335f9081527f081a651349dfbf423ffb5b045a53f71668561e554402e2db71c1db19bfb815a560205260409020547fa90c7030a27f389f9fc8ed21a0556f40c88130cc14a80db936bed68261819b2c9060ff168015612450575b156110ed5761002061558c565b50805f525f60205261246933600160405f2001546139cd565b612443565b346106a1575f3660031901126106a15760206040517f17960a6b137243c888669d93712b617414dd6d1ab55d5eca488ccb1d0894cd5c8152f35b346106a1575f3660031901126106a15760206040517fea6487df651bb740150364c496e1c7403dd62063c96e44906cc98c6a919a9d888152f35b346106a15760203660031901126106a1576004355f526001602052602060405f2054604051908152f35b346106a1575f3660031901126106a157610140612527613feb565b60405181516001600160a01b03168152906125c4906020818101516001600160601b0316908401526040818101516001600160601b03169084015260608181015115159084015260808181015161ffff169084015260a08181015161ffff169084015260c08181015161ffff169084015260e08181015161ffff16908401526101008181015161ffff169084015261012090810151151590830152565bf35b346106a1575f3660031901126106a15760206040516a084595161401484a0000008152f35b346106a1575f3660031901126106a15761264160206001600160a01b03808261261330615824565b015160405163cbf9fe5f60e01b815291166001600160a01b0316600482015292839190829081906024820190565b03917f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08165afa8015610a38576107ec915f91612689575b506040519081529081906020820190565b6126a2915060203d602011610a6357610a558183610708565b5f612678565b346106a1575f3660031901126106a15761264160206001600160a01b0380826126d030615824565b01516040516330b0680b60e01b815291166001600160a01b0316600482015292839190829081906024820190565b346106a15760403660031901126106a15761002060243560043561272182610d23565b805f525f602052611355600160405f200154614899565b346106a15760203660031901126106a15760043561275581610d23565b335f9081527fb888a84a969c69ee493013b8ca47e9ecebe24196f231bedf425581252c3c62e360205260409020545f80516020615e278339815191529060ff1680156127aa575b156110ed57610020826155f9565b50805f525f6020526127c333600160405f2001546139cd565b61279c565b346106a1575f3660031901126106a1576040517f00000000000000000000000005f2927c5c2825bc0dcdc14d258a99a36116be8b6001600160a01b03168152602090f35b346106a1575f3660031901126106a15761285b606061283261087f602061087130615824565b604051635637c1d960e11b81526001600160a01b03909116600482015291829081906024820190565b03817f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b03165afa8015610a38576128d66121f8916107ec935f916128de575b506128d061090460406128c161090460208601516001600160801b031690565b9301516001600160801b031690565b9061301c565b6128d0614181565b612900915060603d606011612906575b6128f88183610708565b8101906140dd565b5f6128a1565b503d6128ee565b346106a1575f3660031901126106a1576040517f000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea2919806001600160a01b03168152602090f35b346106a1575f3660031901126106a1576040517f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba16001600160a01b03168152602090f35b346106a1575f3660031901126106a15760206040517f355caf1c2580ed8185acb5ea3573b71f85186b41bdf69e3eb8f1fcd122a562df8152f35b346106a15760203660031901126106a1576100206004356129ef81610d23565b615688565b346106a1575f3660031901126106a1576060600454604051906001600160701b03811682528060701c600d0b602083015260e01c6040820152f35b346106a1575f3660031901126106a1576020610be8614181565b346106a1575f3660031901126106a157612a616139f4565b335f9081527f6691f8833b7b4761748530debf638b71868148ba67d9725d03a36fa8c55c585960205260409020547f9586321ac05f110e4b4a0a42aba899709345af0ca78910e8832ddfd71fed2bf49060ff168015612ac8575b156110ed57610020615738565b50805f525f602052612ae133600160405f2001546139cd565b612abb565b346106a1575f3660031901126106a15760206040517fe996ac9b332538bb1fa3cd6743aa47011623cdb94bd964a494ee9d371e4a27d38152f35b346106a15760803660031901126106a157600435612b3d81610d23565b60243590612b4a82610d23565b60443591612b57816146d8565b6002549160ff8316612cb0576001600160a01b0393307f000000000000000000000000cb3bb848252f7ca05ed7753ead0eb2bdfd2ba878861614612c9e57612bb3612be5926001612bf39660ff19161760025560643590615b9d565b7f5daa87a0e9463431830481fd4b6e3403442dfb9a12b9c07597e9f61d50b633c85f80a1612be081615520565b61553c565b612bee816155f9565b6148db565b50612bfc615c0a565b612c04615c52565b60405163095ea7b360e01b81527f00000000000000000000000005f2927c5c2825bc0dcdc14d258a99a36116be8b82166001600160a01b031660048201525f19602482015290602090829060449082905f907f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba1165af18015610a3857612c8657005b6100209060203d602011611967576119598183610708565b60405163f9cc8ff960e01b8152600490fd5b60405162dc149f60e41b8152600490fd5b346106a1575f3660031901126106a157602061ffff60e0610f9b613feb565b346106a15760603660031901126106a157600435612cfd81610d23565b604435612d0981610d23565b335f9081527f8e22673d1bc81fb3bd6ed1d58f34ebd725bafdd9d714cf3052b13a12f254fcff60205260409020547fa38b301640bddfd3e6a9d2a11d13551d53ef81526347ff09d798738fcc5a49d4929060ff168015612d77575b15610ce657610020925060243590614273565b50825f525f602052612d9033600160405f2001546139cd565b612d64565b346106a15760403660031901126106a157600435612db281610d23565b602435612dbd61337b565b808211612e4d5750335f9081527fd86621a8c2273efca305981f63eac7b1eb8bb1b258c36bf730770cdcb2e9c89a60205260409020547f355caf1c2580ed8185acb5ea3573b71f85186b41bdf69e3eb8f1fcd122a562df929060ff168015612e2f575b15610ce65761002092506157a5565b50825f525f602052612e4833600160405f2001546139cd565b612e20565b604051633cbb525b60e21b815260048101929092526024820152604490fd5b346106a157612e7a366112a9565b90612e84826146fa565b5f5b828110612e8f57005b600190612ecf602080612ea3848888613563565b01355f612eb1858989613563565b3592612ebc84610d23565b8282525261106d8560405f200154614899565b5001612e86565b346106a1575f3660031901126106a1576020604051610e108152f35b346106a1576020806003193601126106a157612f0f600435615019565b604051636d78045960e01b81523360048201526001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08811660248301526044820183905291929182908290815f8160648101611e2b565b60208091604051928184925191829101835e81017f1b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb090081520301902090565b908160209103126106a15751612fbf81610d23565b90565b6040513d5f823e3d90fd5b634e487b7160e01b5f52603260045260245ffd5b91908110156130035760051b81013590607e19813603018212156106a1570190565b612fcd565b634e487b7160e01b5f52601160045260245ffd5b9190820180921161302957565b613008565b908160209103126106a1575190565b9060405191602083015260208252604082018281106001600160401b038211176106cc57604052565b903590601e19813603018212156106a157018035906001600160401b0382116106a1576020019181360383136106a157565b908060209392818452848401375f828201840152601f01601f1916010190565b96959490926130d661310e946060969460808b5260808b0191613098565b602080928a8303828c01528051918291828552018484015e5f828201840152601f01601f191601888103820160408a01520191613098565b930152565b9190820391821161302957565b6131306020610b4c61087f6145b1565b03915afa8015610a3857612fbf915f91613151575b50610bb4610baf6134c5565b61316a915060203d602011610a6357610a558183610708565b5f613145565b91909161317c816146d8565b613185836146d8565b61318e826146fa565b6001600160a01b039081169273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee840361324f575f808080868686165af13d1561324a573d6131cf81610738565b906131dd6040519283610708565b81525f60203d92013e5b1561321e57907faca8fb252cde442184e5f10e0f2e6e4029e8cd7717cae63559079610702436aa915b6040519384521691602090a3565b604051636f54afdd60e11b81526001600160a01b03919091166004820152602481019290925250604490fd5b6131e7565b908161327d847faca8fb252cde442184e5f10e0f2e6e4029e8cd7717cae63559079610702436aa9487614701565b613210565b519061073682610d23565b51906001600160601b03821682036106a157565b519081151582036106a157565b519061ffff821682036106a157565b90816101409103126106a1576132d1610729565b906132db81613282565b82526132e96020820161328d565b60208301526132fa6040820161328d565b604083015261330b606082016132a1565b606083015261331c608082016132ae565b608083015261332d60a082016132ae565b60a083015261333e60c082016132ae565b60c083015261334f60e082016132ae565b60e08301526101006133628183016132ae565b908301526133746101208092016132a1565b9082015290565b7f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b03166133b661087f602061087130615824565b60405163280be2f560e01b81526001600160a01b039190911660048201526101408082602481865afa8015610a38576133fc926060925f92613498575b50500151151590565b6134935760206134429161341661087f8361087130615824565b604051633a4952a360e01b81526001600160a01b03909116600482015292839190829081906024820190565b03915afa908115610a38575f91613474575b5061345d614181565b8082111561346e57612fbf91613113565b50505f90565b61348d915060203d602011610a6357610a558183610708565b5f613454565b505f90565b6134b79250803d106134be575b6134af8183610708565b8101906132bd565b5f806133f3565b503d6134a5565b6001600160a01b03602081816134da30615824565b015116602460405180948193632de0e07560e01b835260048301527f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08165afa908115610a38575f91613544575b50613530614181565b8082111561346e5781039081116130295790565b61355d915060203d602011610a6357610a558183610708565b5f613527565b91908110156130035760061b0190565b906001600160801b0360065416908082036135d25750506a084595161401484a00000081116135c0576135ad6135a7613987565b36614ae2565b15613493576135bb9061453c565b600190565b60405163f579fde760e01b8152600490fd5b6044925060405191637f8c068160e11b835260048301526024820152fd5b9061362d916135ff8230614e48565b6040516240cd2760e21b815260048101929092526020926001600160a01b0392908490829081906024820190565b0381867f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba1165afa908115610a38576136ab9385915f936136e9575b507f00000000000000000000000005f2927c5c2825bc0dcdc14d258a99a36116be8b169160405180958192630ea598cb60e41b8352600483019190602083019252565b03815f855af1928315610a3857610736945f946136ca575b5050614701565b6136e1929450803d10610a6357610a558183610708565b915f806136c3565b613701919350823d8411610a6357610a558183610708565b915f613668565b61371181614f02565b61372261371c613987565b36614c7c565b15613493576135bb90614f35565b6001600160a01b036020818161374530615824565b01511660246040518094819363329ce48960e21b835260048301527f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08165afa908115610a38575f91613795575090565b612fbf915060203d602011610a6357610a558183610708565b6137ba61073692615019565b90614e48565b908160209103126106a157612fbf906132a1565b60405f8180516137e3816106d1565b828152826020820152015260018060a01b03606081602061380330615824565b015116602484518094819363f9c821a760e01b835260048301527f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08165afa918215610a38575f9261385357505090565b9091506060823d6060116138c4575b8161386f60609383610708565b810103126106a157805191613883836106d1565b80516001600160701b03811681036106a157835260208101519081600d0b82036106a1578291602085015201519063ffffffff821682036106a15782015290565b3d9150613862565b6138d590615019565b335f9081527fe23a8bac6ee120cc0f31e97b679e63d8a06b6b0f7ec6a1eaa96912d2415af2e060205260409020547f3f82ecf462ddac43fc17ba11472c35f18b7760b4f5a5fc50b9625f9b5a22cf629060ff16801561393e575b156110ed575061073690614970565b50805f525f60205261395733600160405f2001546139cd565b61392f565b6001600160401b0381116106cc5760051b60200190565b80518210156130035760209160051b010190565b60405190613994826106d1565b60028252602082019160403684378092815115613003575f90528051600110156130035760405f80516020615e27833981519152910152565b5f908152602081815260408083206001600160a01b0390941683529290522060ff90541690565b6139fc614181565b8015613b4f576001600160801b03613a1c6006546001600160801b031690565b16613b42575b613a66613a2d6137d4565b6001600160701b03815116602082015160701b91604063ffffffff60e01b91015160e01b16916001600160701b0360701b161717600455565b7f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b0316613aa161087f602061087130615824565b6005546001600160a01b031690823b156106a157604051636ce5768960e11b81526001600160a01b0391821660048201529116602482015260448101839052905f908290606490829084905af18015610a3857613b2f575b5060405190815233907f66a284d7a46e07e3f2d18b3955d275877589083787a7f656543d82c49a5db9c09080602081015b0390a2565b80610a2c613b3c926106b9565b5f613af9565b613b4a6144c3565b613a22565b50565b6001600160401b038116036106a157565b908160209103126106a15751612fbf81613b52565b908160809103126106a1576040519060808201908282106001600160401b038311176106cc57606091604052613bad816132a1565b835260208101516020840152604081015160408401520151606082015290565b7f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b03166020613c0961087f8261087130615824565b60408051634a2264ef60e01b81526001600160a01b0392909216600480840191909152909290918181602481885afa908115610a38575f91613eb1575b5015613ea2576006546001600160401b038383613c6461087f6154cd565b875163c6290e3560e01b815292839182905afa8015610a385782915f91613e75575b5016908260801c161015613e65576001600160801b0316613e5657613ced6080613cb161087f6154cd565b613cc161087f8561087130615824565b86516329938c8360e21b81526001600160a01b0390911686820190815290938492918391829160200190565b03915afa8015610a3857613d09915f91613e27575b5051151590565b613e1857613d1685615520565b613d28613d2461371c613987565b1590565b613e0f57613d43613d37614181565b95612be0613a2d6137d4565b84613d53575b5050505050600190565b61087f613d639161087130615824565b6005549093906001600160a01b031690803b156106a1578351636ce5768960e11b81526001600160a01b03958616938101938452949091166020830152604082018590525f9184919082908490829060600103925af1918215610a38577f66a284d7a46e07e3f2d18b3955d275877589083787a7f656543d82c49a5db9c092613dfc575b50519182523391602090a25f80808080613d49565b80610a2c613e09926106b9565b5f613de7565b50505050505f90565b50905163a9d1c85160e01b8152fd5b613e49915060803d608011613e4f575b613e418183610708565b810190613b78565b5f613d02565b503d613e37565b509051635abae25f60e01b8152fd5b8351632a68326f60e11b81528390fd5b613e959150853d8711613e9b575b613e8d8183610708565b810190613b63565b5f613c86565b503d613e83565b5090516366ad59df60e11b8152fd5b613ec89150823d8411611967576119598183610708565b5f613c46565b34613f65575b7f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b0316613f078161521a565b613f1861087f602061087130615824565b90803b156106a15760405163135c2b5960e31b81526001600160a01b039290921660048301525f908290602490829084905af18015610a3857613f585750565b80610a2c610736926106b9565b613f7661087f602061087130615824565b803b156106a1575f600491604051928380926316c1a85160e31b825234905af18015610a3857613fa7575b50613ed4565b80610a2c613fb4926106b9565b5f613fa1565b6001600160801b0360065416908101809111613029576a084595161401484a00000081116135c0576107369061453c565b5f610120604051613ffb816106ec565b8281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e082015282610100820152015261407361404561087f602061087130615824565b60405163280be2f560e01b81526001600160a01b039091166004820152610140918290829081906024820190565b03817f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b03165afa918215610a38575f926140b357505090565b612fbf9250803d106134be576134af8183610708565b51906001600160801b03821682036106a157565b908160609103126106a15761411b60408051926140f9846106d1565b614102816140c9565b8452614110602082016140c9565b6020850152016140c9565b604082015290565b600d91820b910b03906d7fffffffffffffffffffffffffff1982126d7fffffffffffffffffffffffffff83131761302957565b81810392915f13801582851316918412161761302957565b8181029291811591840414171561302957565b61424f604051614190816106d1565b61424a6142446004546001600160701b038116845260208401908060701c600d0b825260e01c604085015261422d6141c66137d4565b9161422d61423c60206142336141f56141ef6141ea6006546001600160801b031690565b615710565b600f0b90565b9961422d61421061421f6142108b516001600160701b031690565b6001600160701b0316600d0b90565b92516001600160701b031690565b90614123565b950151600d0b90565b9151600d0b90565b600d0b90565b614156565b5f811361425b57505f90565b61426b612fbf916003549061416e565b612710900490565b61427c816146d8565b614285836146d8565b6001600160a01b0390811692833b156106a157604051632142170760e11b81523060048201526001600160a01b038216602482015260448101849052915f8360648183895af1918215610a38577f6a30e6784464f0d1f4158aa4cb65ae9239b0fa87c7f2c083ee6dde44ba97b5e693602093614308575b506040519485521692a3565b614311906106b9565b5f6142fc565b5f80516020615e878339815191525c1561432d57565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff168015614370575b156110ed575061073634614ddc565b50805f525f60205261438933600160405f2001546139cd565b614361565b60015f80516020615e878339815191525d565b335f9081527f445ddf9e128c79eca57440455d965dbaaca76bdc8b6662aee0bd18f61bc6486160205260409020547fea6487df651bb740150364c496e1c7403dd62063c96e44906cc98c6a919a9d889060ff168015614493575b156110ed57506001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe0881169190602061443930615824565b01511690823b156106a157604051636ce5768960e11b81526001600160a01b0390921660048301523060248301526044820152905f908290818381606481015b03925af18015610a385761448a5750565b610736906106b9565b50805f525f6020526144ac33600160405f2001546139cd565b6143fb565b5f5f80516020615e878339815191525d565b6006546001600160801b03811690811561452a576001600160c01b0319164260801b67ffffffffffffffff60801b1617600655604080515f815260208101929092527f6294ce24c5cb1a9fd25acf9dc37a0c6cdd144f222c80f43b5582bc86a882564091a1565b6040516318a04aa160e01b8152600490fd5b600654906001600160801b03908183169182821461452a576001600160c01b031993909316928116929092174260801b67ffffffffffffffff60801b16176006556040805192835260208301919091527f6294ce24c5cb1a9fd25acf9dc37a0c6cdd144f222c80f43b5582bc86a882564091a1565b604051631734070560e31b81526001600160a01b03906020816004817f000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea29198086165afa908115610a38575f9161460457501690565b61461d915060203d602011610ad457610ac58183610708565b1690565b61ffff608061462e613feb565b0151166127109081039181831161302957612fbf9261464c9161416e565b04615019565b908082101561465f575090565b905090565b6001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe0881169190602061469c30615824565b01511691803b156106a157614479935f80946040519687958694859363015a8f6160e61b85526004850152604060248501526044840191613098565b6001600160a01b0316156146e857565b60405163d92e233d60e01b8152600490fd5b1561136157565b60405163a9059cbb60e01b60208201526001600160a01b03909216602483015260448201929092526107369161474482606481015b03601f198101845283610708565b61586a565b359061073682613b52565b90936001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe088116949092602090848261479230615824565b01511697873b156106a1579795949392906147cf6020916040519a8b99632f00a7e160e01b8b5260048b0152608060248b015260848a0191613098565b8781036003190160448901528281520194915f5b82811061480e57505050505f949284928392166064830152039134905af18015610a3857613f585750565b9193949583975090806001926001600160401b0382963561482e81613b52565b168152019701910191889695949391926147e3565b335f9081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5602052604090205460ff161561487b57565b60405163e2517d3f60e01b81523360048201525f6024820152604490fd5b5f8181526020818152604080832033845290915290205460ff16156148bb5750565b60405163e2517d3f60e01b81523360048201526024810191909152604490fd5b6148e4816158c4565b90816148ee575090565b5f80516020615e278339815191525f526001602052614936906001600160a01b03167fa5a481183f76091139902c30e9c198fa5bc995beffa115a7cdc58a48752719ad615cbc565b5090565b6149448282615a1c565b918261494f57505090565b5f91825260016020526040909120614936916001600160a01b031690615cbc565b6001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe088116919060206149a830615824565b01511690823b156106a1576040516303da9b9d60e41b81526001600160a01b0390921660048301526024820152905f90829081838160448101614479565b6149f08282615a85565b91826149fb57505090565b5f91825260016020526040909120614936916001600160a01b031690615d68565b90614a268261395c565b614a336040519182610708565b8281528092614a44601f199161395c565b0190602036910137565b60206040518281935f833781017f1b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb090081520301902090565b5f1981146130295760010190565b91608093918352602083015260606040830152806060830152805f848401375f828201840152601f01601f1916010190565b91614ade9183549060031b91821b915f19901b19161790565b9055565b815115614c6a578151915f614af684614a1c565b5f614b0f5f80516020615e67833981519152544261301c565b925f5b878110614bb957509015614ba7578503614b5c5750505f5b838110614b3a5750505050600190565b805f614b55614b4b60019486613973565b516107ce87614a4e565b5501614b2a565b5f9391935b858110614b72575050505050505f90565b80614b88614b8260019385613973565b51151590565b614b93575b01614b61565b85614ba1614b4b8388613973565b55614b8d565b60405163dd16cb2360e01b8152600490fd5b614bc38187613973565b5192614bcf33856139cd565b15614c365750600190614be28293614a85565b93614bf6614bf08388613973565b60019052565b6040517f52a98ceea5bf73fc6482a17b325efa51b21372c60578c2fa4cac0c2fa780035a339180614c29368c4284614a93565b0390a35b01919091614b12565b9192614c4642916107ce8a614a4e565b541015614c56575b600190614c2d565b91614c62600191614a85565b929050614c4e565b60405163f814a95b60e01b8152600490fd5b815115614c6a578151915f614c9084614a1c565b5f614ca95f80516020615e67833981519152544261301c565b925f5b878110614d3157509015614ba7578503614cec5750505f5b838110614cd45750505050600190565b805f614ce5614b4b60019486613973565b5501614cc4565b5f9391935b858110614d02575050505050505f90565b80614d12614b8260019385613973565b614d1d575b01614cf1565b85614d2b614b4b8388613973565b55614d17565b614d3b8187613973565b5192614d4733856139cd565b15614da85750600190614d5a8293614a85565b93614d68614bf08388613973565b6040517f52a98ceea5bf73fc6482a17b325efa51b21372c60578c2fa4cac0c2fa780035a339180614d9b368c4284614a93565b0390a35b01919091614cac565b9192614db842916107ce8a614a4e565b541015614dc8575b600190614d9f565b91614dd4600191614a85565b929050614dc0565b6001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe088116906020614e1330615824565b01511690803b156106a15760245f926040519485938492630460488160e31b845260048401525af18015610a385761448a5750565b9190614e52613120565b808211614ee35750335f9081527fd8ab213733a751ec9ca51242fa42dd7f07dc61105cb44f3595d018b75a32f4f560205260409020547fe996ac9b332538bb1fa3cd6743aa47011623cdb94bd964a494ee9d371e4a27d3939060ff168015614ec5575b156115c957610736929350615b09565b50835f525f602052614ede33600160405f2001546139cd565b614eb5565b60405163087df69d60e41b815260048101929092526024820152604490fd5b610e108110908115614f28575b50614f1657565b60405163bbc3a3e360e01b8152600490fd5b62278d009150115f614f0f565b614f3e81614f02565b5f80516020615e6783398151915280549082905560408051918252602082019290925233917f437dc1a3e26abf0b8381463abca48d1e7d322e803c2b160d3ee75c04a492a182919081908101613b2a565b614fdb926020926001600160a01b03915f908386614fac30615824565b015116908460405198899788968795636c1d053960e11b87526004870152606060248701526064860191613098565b9116604483015203927f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08165af1908115610a38575f91613795575090565b604051631920845160e01b815260048101919091526020816024817f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba16001600160a01b03165afa908115610a38575f91613795575090565b91908110156130035760051b8101359060be19813603018212156106a1570190565b81835290916001600160fb1b0383116106a15760209260051b809284830137010190565b9035601e19823603018112156106a15701602081359101916001600160401b0382116106a15781360383136106a157565b6001600160a01b03909116815260406020820152813536839003601e19018112156106a157820190602082359201916001600160401b0381116106a1578060051b360383136106a1576151c260a0615174615155612fbf9660e09560c06040890152610100880191615093565b61516260208901896150b7565b878303603f1901606089015290613098565b956040810135608086015261519d61518e60608301614749565b6001600160401b031686840152565b6151bc6151ac60808301614749565b6001600160401b031660c0870152565b01614749565b6001600160401b0316910152565b6151d8614843565b6001600160a01b0360206151eb30615824565b015116803b156106a1575f80916004604051809481936379ba509760e01b83525af18015610a385761448a5750565b615222614843565b6001600160a01b039081602061523730615824565b01511691823b156106a15760245f9283604051958694859363f2fde38b60e01b85521660048401525af18015610a385761448a5750565b335f9081527f33325f4ccadc9d27525cbee4f590af3a5c5c236abe27b43530b1a65b898523876020908152604090912054909291907f655996ffb2e08cf642b4ce5cc56668761aed116cdfcb21f71ff9fe7fbcbd450e9060ff168015615346575b156110ed5750829060646001600160a01b035f816152eb6145b1565b1691856152f730615824565b0151169160405196879586946321d5a4bf60e11b86526004860152602485015260448401525af1918215610a38575f9261533057505090565b612fbf9250803d10611967576119598183610708565b50805f525f845261535e33600160405f2001546139cd565b6152cf565b335f9081527fa2e2d001ecdfead4b8d27592f8b4686eb9b6cd4f5dec9f06f76377b2d0a1a1bb60205260409020547f689f0a569be0c9b6cd2c11c81cb0add722272abdae6b649fdb1e05f1d9bb8a2f9060ff1680156153cc575b156110ed5750610736906153ea565b50805f525f6020526153e533600160405f2001546139cd565b6153bd565b6001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe0881169190602061542230615824565b01511690823b156106a157604051633b9e9f0160e21b81526001600160a01b0390921660048301526024820152905f90829081838160448101614479565b6001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08811690602061549730615824565b015116813b156106a1575f9160248392604051948593849263ee762f2b60e01b845260048401525af18015610a385761448a5750565b604051630b8e411760e21b81526001600160a01b03906020816004817f000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea29198086165afa908115610a38575f9161460457501690565b6127101061552a57565b60405163137b7b8960e31b8152600490fd5b61554581615520565b7fce7788c577af89829bf8edd40fe3bf5511a2a01bd1ceb67cf4b7a1eecd8cd0c7613b2a600354928060035560405191829133958360209093929193604081019481520152565b6001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe0881169060206155c330615824565b015116813b156106a1575f91602483926040519485938492633ffa490d60e01b845260048401525af18015610a385761448a5750565b615602816146d8565b6005546001600160a01b03808216929081169190828414615676576001600160a01b0319909116909117600555604080516001600160a01b03938416815292909116602083015233917f50c7826394d934be76de5ce7a66c3bc33795e8c3e613982f8cc792cbbcea3b129181908101613b2a565b60405163f012261f60e01b8152600490fd5b61569361371c613987565b15613b4f576001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe088116919060206156d030615824565b01511690823b156106a15760405163fdc10daf60e01b81526001600160a01b03928316600482015291166024820152905f90829081838160448101614479565b6001600160801b03166f7fffffffffffffffffffffffffffffff80821161465f5750600f0b90565b6001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08811690602061576f30615824565b015116813b156106a1575f91602483926040519485938492632c4422a760e11b845260048401525af18015610a385761448a5750565b906001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe088116919060206157de30615824565b01511691803b156106a157604051636ce5768960e11b81526001600160a01b03938416600482015293909216602484015260448301525f90829081838160648101614479565b803b90602c19820191821161302957602d61585761584184610738565b9361584f6040519586610708565b808552610738565b6020840190601f19013682378351923c90565b905f602091828151910182855af115612fc2575f513d6158bb57506001600160a01b0381163b155b6158995750565b604051635274afe760e01b81526001600160a01b039091166004820152602490fd5b60011415615892565b6001600160a01b0381165f9081527fb888a84a969c69ee493013b8ca47e9ecebe24196f231bedf425581252c3c62e3602052604090205f80516020615e278339815191529060ff905b541661346e575f818152602081815260408083206001600160a01b03861684529091529020805460ff1916600117905533916001600160a01b0316907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d5f80a4600190565b6001600160a01b0381165f9081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5602052604090205460ff16613493576001600160a01b03165f8181527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb560205260408120805460ff191660011790553391907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d8180a4600190565b5f818152602081815260408083206001600160a01b0386168452909152902060ff9061590d565b6040516323b872dd60e01b60208201526001600160a01b0392831660248201529290911660448301526064820192909252610736916147448260848101614736565b5f818152602081815260408083206001600160a01b038616845290915290205460ff161561346e575f818152602081815260408083206001600160a01b03861684529091529020805460ff1916905533916001600160a01b0316907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b5f80a4600190565b906001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08811691906020615b4230615824565b01511691803b156106a157604051635fc5d2cb60e11b81526001600160a01b03938416600482015293909216602484015260448301525f90829081838160648101614479565b8054821015613003575f5260205f2001905f90565b9061073691615bab816146d8565b615bb481615972565b615bc8575b50615bc381614f02565b614f35565b5f80526001602052615c03906001600160a01b03167fa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49615cbc565b505f615bb9565b5f80516020615e27833981519152805f525f602052600160405f20018181549155817fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff5f80a4565b7fe0b9915a7819e810f29b50730662441fec3443eb363b7e7c90c77fada416f276805f525f602052600160405f2001908154905f80516020615e278339815191528093557fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff5f80a4565b6001810190825f528160205260405f2054155f14615d24578054680100000000000000008110156106cc57615d11615cfb826001879401855584615b88565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b8054908115615d54575f1991820191615d448383615b88565b909182549160031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615dfe575f199284840190858211613029578054948501948511613029575f958583615db7976107ce9503615dbd575b505050615d2b565b55600190565b615de7615de191615dd1615df59487615b88565b90549060031b1c92839187615b88565b90614ac5565b85905f5260205260405f2090565b555f8080615daf565b505050505f9056fe6fe412e9d35aac9d96821beeae87f7dbc832b35e704e374aa2575d81ecc7787e59783a4ae82167eefad593739a5430c1d9e896a16c35f1e5285ddd0c0980885c933b7d5c112a4d05b489cea0b2ced98acb27d3d0fc9827c92cdacb2d6c5559c21b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb09017408b7b034fda7051615c19182918ecb91d753231cffd86f81a45d996d63e038a2646970667358221220f5e55cedfae21d6c89206c403271fceafc34074fe06feeb6d18ab9559a3b466064736f6c63430008190033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba100000000000000000000000005f2927c5c2825bc0dcdc14d258a99a36116be8b00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea291980
-----Decoded View---------------
Arg [0] : _stETH (address): 0x2C220A2a91602dd93bEAC7b3A1773cdADE369ba1
Arg [1] : _wstETH (address): 0x05F2927c5c2825BC0dCDc14d258a99A36116bE8B
Arg [2] : _vaultHub (address): 0x26b92f0fdfeBAf43E5Ea5b5974EeBee95F17Fe08
Arg [3] : _lidoLocator (address): 0xD7c1B80fA86965B48cCA3aDcCB08E1DAEa291980
-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba1
Arg [1] : 00000000000000000000000005f2927c5c2825bc0dcdc14d258a99a36116be8b
Arg [2] : 00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08
Arg [3] : 000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea291980
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ 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.