Hoodi Testnet

Contract

0x7ade83C09A0Aa0FEA45695840067438a9dC96361

Overview

ETH Balance

0 ETH

More Info

Multichain Info

N/A
Transaction Hash
Method
Block
From
To

There are no matching entries

> 10 Internal Transactions found.

Latest 25 internal transactions (View All)

Advanced mode:
Parent Transaction Hash Method Block
From
To
Locked6139572025-06-16 9:17:243 hrs ago1750065444
0x7ade83C0...a9dC96361
0 ETH
Total Value6139572025-06-16 9:17:243 hrs ago1750065444
0x7ade83C0...a9dC96361
0 ETH
Is Report Fresh6139572025-06-16 9:17:243 hrs ago1750065444
0x7ade83C0...a9dC96361
0 ETH
Owner6139572025-06-16 9:17:243 hrs ago1750065444
0x7ade83C0...a9dC96361
0 ETH
Lock6139572025-06-16 9:17:243 hrs ago1750065444
0x7ade83C0...a9dC96361
0 ETH
Locked6139572025-06-16 9:17:243 hrs ago1750065444
0x7ade83C0...a9dC96361
0 ETH
Fund6139452025-06-16 9:15:003 hrs ago1750065300
0x7ade83C0...a9dC96361
1 ETH
Locked6139342025-06-16 9:12:363 hrs ago1750065156
0x7ade83C0...a9dC96361
0 ETH
Total Value6139342025-06-16 9:12:363 hrs ago1750065156
0x7ade83C0...a9dC96361
0 ETH
Is Report Fresh6139342025-06-16 9:12:363 hrs ago1750065156
0x7ade83C0...a9dC96361
0 ETH
Owner6139342025-06-16 9:12:363 hrs ago1750065156
0x7ade83C0...a9dC96361
0 ETH
Lock6139342025-06-16 9:12:363 hrs ago1750065156
0x7ade83C0...a9dC96361
0 ETH
Locked6139342025-06-16 9:12:363 hrs ago1750065156
0x7ade83C0...a9dC96361
0 ETH
Fund6139302025-06-16 9:11:483 hrs ago1750065108
0x7ade83C0...a9dC96361
1 ETH
Locked6137912025-06-16 8:41:483 hrs ago1750063308
0x7ade83C0...a9dC96361
0 ETH
Total Value6137912025-06-16 8:41:483 hrs ago1750063308
0x7ade83C0...a9dC96361
0 ETH
Is Report Fresh6137912025-06-16 8:41:483 hrs ago1750063308
0x7ade83C0...a9dC96361
0 ETH
Owner6137912025-06-16 8:41:483 hrs ago1750063308
0x7ade83C0...a9dC96361
0 ETH
Lock6137912025-06-16 8:41:483 hrs ago1750063308
0x7ade83C0...a9dC96361
0 ETH
Locked6137912025-06-16 8:41:483 hrs ago1750063308
0x7ade83C0...a9dC96361
0 ETH
Report6137872025-06-16 8:41:003 hrs ago1750063260
0x7ade83C0...a9dC96361
0 ETH
Latest Report6137792025-06-16 8:39:123 hrs ago1750063152
0x7ade83C0...a9dC96361
0 ETH
Latest Report6137792025-06-16 8:39:123 hrs ago1750063152
0x7ade83C0...a9dC96361
0 ETH
Transfer Ownersh...6137792025-06-16 8:39:123 hrs ago1750063152
0x7ade83C0...a9dC96361
0 ETH
Report6137792025-06-16 8:39:123 hrs ago1750063152
0x7ade83C0...a9dC96361
0 ETH
View All Internal Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
StakingVault

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 {OwnableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/OwnableUpgradeable.sol";
import {TriggerableWithdrawals} from "contracts/common/lib/TriggerableWithdrawals.sol";

import {VaultHub} from "./VaultHub.sol";
import {PinnedBeaconUtils} from "./lib/PinnedBeaconUtils.sol";

import {IDepositContract} from "../interfaces/IDepositContract.sol";
import {IStakingVault, StakingVaultDeposit} from "./interfaces/IStakingVault.sol";

/**
 * @title StakingVault
 * @author Lido
 * @notice
 *
 * StakingVault is a contract which is designed to be used as withdrawal credentials
 * to stake ETH with a designated node operator, while being able to mint stETH.
 *
 * The StakingVault can be used as a backing for minting new stETH through integration with the VaultHub.
 * When minting stETH backed by the StakingVault, the VaultHub designates a portion of the StakingVault's
 * total value as locked, which cannot be withdrawn by the owner. This locked portion represents the
 * collateral for the minted stETH.
 *
 * Access Control:
 * - Owner:
 *   - `fund()`
 *   - `withdraw()`
 *   - `rebalance()`
 *   - `lock()`
 *   - `pauseBeaconChainDeposits()`
 *   - `resumeBeaconChainDeposits()`
 *   - `requestValidatorExit()`
 *   - `triggerValidatorWithdrawal()`
 *   - `authorizeLidoVaultHub()`
 *   - `deauthorizeLidoVaultHub()`
 *   - `ossifyStakingVault()`
 *   - `setDepositor()`
 *   - `resetLocked()`
 * - Operator:
 *   - `triggerValidatorWithdrawal()`
 * - Depositor:
 *   - `depositToBeaconChain()`
 * - VaultHub:
 *   - `report()`
 *   - `rebalance()`
 *   - `triggerValidatorWithdrawal()`
 * - Anyone:
 *   - Can send ETH directly to the vault (treated as rewards)
 *
 * PinnedBeaconProxy
 * The contract is designed as an extended beacon proxy implementation, allowing individual StakingVault instances
 * to be ossified (pinned) to prevent future upgrades. The implementation is petrified (non-initializable)
 * and contains immutable references to the beacon chain deposit contract.
 */
contract StakingVault is IStakingVault, OwnableUpgradeable {
    /**
     * @notice ERC-7201 storage namespace for the vault
     * @dev ERC-7201 namespace is used to prevent upgrade collisions
     * @custom:report Latest report containing total value and inOutDelta
     * @custom:locked Amount of ether locked on StakingVault by VaultHub and cannot be withdrawn by owner
     * @custom:inOutDelta Net difference between ether funded and withdrawn from StakingVault
     * @custom:nodeOperator Address of the node operator
     * @custom:depositor Address of the depositor
     * @custom:vaultHubAuthorized Whether the vaultHub is authorized at the vault
     * @custom:beaconChainDepositsPaused Whether beacon deposits are paused by the vault owner
     */
    struct ERC7201Storage {
        Report report;
        uint128 locked;
        int128 inOutDelta;
        address nodeOperator;
        address depositor;
        bool vaultHubAuthorized;
        bool beaconChainDepositsPaused;
    }

    /**
     * @notice Version of the contract on the implementation
     *         The implementation is petrified to this version
     */
    uint64 private constant _VERSION = 1;

    /**
     * @notice Address of `VaultHub`
     *         Set immutably in the constructor to avoid storage costs
     */
    VaultHub private immutable VAULT_HUB;

    /**
     * @notice Address of `BeaconChainDepositContract`
     *         Set immutably in the constructor to avoid storage costs
     */
    IDepositContract public immutable DEPOSIT_CONTRACT;

    /**
     * @notice The type of withdrawal credentials for the validators deposited from this `StakingVault`.
     */
    uint256 private constant WC_0X02_PREFIX = 0x02 << 248;

    /**
     * @notice The length of the public key in bytes
     */
    uint256 public constant PUBLIC_KEY_LENGTH = 48;

    /**
     * @notice Storage offset slot for ERC-7201 namespace
     *         The storage namespace is used to prevent upgrade collisions
     *         `keccak256(abi.encode(uint256(keccak256("Lido.Vaults.StakingVault")) - 1)) & ~bytes32(uint256(0xff))`
     */
    bytes32 private constant ERC7201_STORAGE_LOCATION =
        0x2ec50241a851d8d3fea472e7057288d4603f7a7f78e6d18a9c12cad84552b100;

    /**
     * @notice Constructs the implementation of `StakingVault`
     * @param _vaultHub Address of `VaultHub`
     * @param _beaconChainDepositContract Address of `BeaconChainDepositContract`
     * @dev Fixes `VaultHub` and `BeaconChainDepositContract` addresses in the bytecode of the implementation
     */
    constructor(address _vaultHub, address _beaconChainDepositContract) {
        if (_vaultHub == address(0)) revert ZeroArgument("_vaultHub");
        if (_beaconChainDepositContract == address(0)) revert ZeroArgument("_beaconChainDepositContract");

        VAULT_HUB = VaultHub(_vaultHub);
        DEPOSIT_CONTRACT = IDepositContract(_beaconChainDepositContract);

        // Prevents reinitialization of the implementation
        _disableInitializers();
    }

    /**
     * @notice Initializes `StakingVault` with an owner, node operator, and optional parameters
     * @param _owner Address that will own the vault
     * @param _nodeOperator Address of the node operator
     * @param _depositor Address of the depositor. If zero address, _nodeOperator will be used
     * @param - Additional initialization parameters
     */
    function initialize(
        address _owner,
        address _nodeOperator,
        address _depositor,
        bytes calldata /* _params */
    ) external initializer {
        if (_nodeOperator == address(0)) revert ZeroArgument("_nodeOperator");

        __Ownable_init(_owner);

        ERC7201Storage storage $ = _getStorage();
        $.nodeOperator = _nodeOperator;
        $.depositor = _depositor == address(0) ? _nodeOperator : _depositor;

        emit NodeOperatorSet(_nodeOperator);
        emit DepositorSet(_depositor);
    }

    /**
     * @notice Returns the highest version that has been initialized as uint64
     */
    function getInitializedVersion() external view returns (uint64) {
        return _getInitializedVersion();
    }

    /**
     * @notice Returns the version of the contract as uint64
     */
    function version() external pure returns (uint64) {
        return _VERSION;
    }

    /**
     * @notice returns owner of the contract
     * @dev fixes solidity interface inference
     */
    function owner() public view override(IStakingVault, OwnableUpgradeable) returns (address) {
        return OwnableUpgradeable.owner();
    }

    // * * * * * * * * * * * * * * * * * * * *  //
    // * * * STAKING VAULT BUSINESS LOGIC * * * //
    // * * * * * * * * * * * * * * * * * * * *  //

    /**
     * @notice Returns the address of `VaultHub`
     */
    function vaultHub() external view returns (address) {
        return address(VAULT_HUB);
    }

    /**
     * @notice Authorizes the `VaultHub` at the vault
     * @dev Can only be called by the owner
     * @dev Reverts if vaultHub is already authorized
     * @dev Reverts if vault is ossified
     * @dev Reverts if the depositor is not the Lido Predeposit Guarantee
     */
    function authorizeLidoVaultHub() external onlyOwner {
        ERC7201Storage storage $ = _getStorage();
        if ($.vaultHubAuthorized) revert VaultHubAuthorized();
        if (ossified()) revert VaultOssified();

        address lidoPredepositGuarantee = VaultHub(VAULT_HUB).LIDO_LOCATOR().predepositGuarantee();
        if ($.depositor != lidoPredepositGuarantee) revert InvalidDepositor($.depositor);

        $.vaultHubAuthorized = true;

        emit VaultHubAuthorizedSet(true);
    }

    /**
     * @notice Deauthorizes the `VaultHub` from the vault
     * @dev Can only be called by the owner
     * @dev Reverts if vaultHub is already deauthorized
     * @dev Reverts if vault is already connected to VaultHub
     */
    function deauthorizeLidoVaultHub() external onlyOwner {
        VaultHub.VaultSocket memory socket = VaultHub(VAULT_HUB).vaultSocket(address(this));
        if (socket.vault != address(0)) {
            revert VaultConnected();
        }

        ERC7201Storage storage $ = _getStorage();
        if (!$.vaultHubAuthorized) revert VaultHubNotAuthorized();

        $.vaultHubAuthorized = false;

        emit VaultHubAuthorizedSet(false);
    }

    /**
     * @notice Returns true if the vault is attached to VaultHub
     * @return True if the vault is attached to VaultHub, false otherwise
     */
    function vaultHubAuthorized() external view returns (bool) {
        return _getStorage().vaultHubAuthorized;
    }

    /**
     * @notice Ossifies the current implementation. WARNING: This operation is irreversible,
     *         once ossified, the vault cannot be upgraded or attached to VaultHub.
     * @dev Can only be called by the owner.
     *      Pins the current vault implementation to prevent further upgrades.
     *      Emits an event `PinnedImplementationUpdated` with the current implementation address.
     * @dev Reverts if already ossified.
     * @dev Reverts if vaultHub is authorized at the vault
     */
    function ossifyStakingVault() external onlyOwner {
        ERC7201Storage storage $ = _getStorage();
        if ($.vaultHubAuthorized) revert VaultHubAuthorized();
        PinnedBeaconUtils.ossify();
    }

    /**
     * @notice Returns true if the vault is ossified
     * @return True if the vault is ossified, false otherwise
     */
    function ossified() public view returns (bool) {
        return PinnedBeaconUtils.ossified();
    }

    /**
     * @notice Returns the current total value of `StakingVault` in ether
     * @dev totalValue = latestReport.totalValue + (current inOutDelta - latestReport.inOutDelta)
     */
    function totalValue() public view returns (uint256) {
        ERC7201Storage storage $ = _getStorage();
        return uint256(int256(int128($.report.totalValue) + $.inOutDelta - $.report.inOutDelta));
    }

    /**
     * @notice Returns the amount of ether locked in `StakingVault` in ether
     * @dev Locked amount is updated by `VaultHub` with reports
     *      and can also be increased by `VaultHub` outside of reports
     */
    function locked() external view returns (uint256) {
        return _getStorage().locked;
    }

    /**
     * @notice Returns the unlocked amount of ether, which is the total value minus the locked ether amount
     * @dev Unlocked amount is the total amount that can be withdrawn from `StakingVault`,
     *      including ether currently being staked on validators
     */
    function unlocked() public view returns (uint256) {
        uint256 totalValue_ = totalValue();
        uint256 locked_ = _getStorage().locked;

        if (locked_ > totalValue_) return 0;

        return totalValue_ - locked_;
    }

    /**
     * @notice Resets the locked amount to 0 only when the vaultHub is deauthorized
     * @dev Can only be called by the owner
     * @dev Reverts if vaultHub is authorized at the vault
     */
    function resetLocked() external onlyOwner {
        ERC7201Storage storage $ = _getStorage();
        if ($.vaultHubAuthorized) revert VaultHubAuthorized();
        _getStorage().locked = 0;

        emit LockedReset();
    }

    /**
     * @notice Returns the net difference between funded and withdrawn ether.
     * @dev This counter is only updated via:
     *      - `fund()`,
     *      - `withdraw()`,
     *      - `rebalance()` functions.
     *      NB: Direct ether transfers through `receive()` are not accounted for because
     *      those are considered as rewards.
     * @dev This delta will be negative if all funded ether with earned rewards are withdrawn,
     *      i.e. there will be more ether withdrawn than funded (assuming `StakingVault` is profitable).
     */
    function inOutDelta() external view returns (int256) {
        return _getStorage().inOutDelta;
    }

    /**
     * @notice Returns the latest report data for the vault (total value and inOutDelta)
     */
    function latestReport() external view returns (IStakingVault.Report memory) {
        return _getStorage().report;
    }

    /**
     * @notice Returns the address of the node operator
     *         Node operator is the party responsible for managing the validators.
     *         Node operator address is set in the initialization and can never be changed.
     */
    function nodeOperator() external view returns (address) {
        return _getStorage().nodeOperator;
    }

    /**
     * @notice Returns the address of the depositor
     *         Trusted party responsible for securely depositing validators to the beacon chain, e.g.
     *         securing against deposit frontrun vulnerability in ethereum deposit contract
     *         (for reference see LIP-5 - https://research.lido.fi/t/lip-5-mitigations-for-deposit-front-running-vulnerability/1269).
     *         In the context of this contract, the depositor performs deposits through `depositToBeaconChain()`.
     * @return Address of the depositor
     */
    function depositor() external view returns (address) {
        return _getStorage().depositor;
    }

    /**
     * @notice Sets the address of the depositor
     * @dev Can only be called by the owner
     * @dev Reverts if the `_depositor` is the zero address
     * @dev Reverts if the vault is attached to VaultHub
     */
    function setDepositor(address _depositor) external onlyOwner {
        if (_depositor == address(0)) revert ZeroArgument("_depositor");

        ERC7201Storage storage $ = _getStorage();
        if ($.vaultHubAuthorized) revert VaultHubAuthorized();
        $.depositor = _depositor;
        emit DepositorSet(_depositor);
    }

    /**
     * @notice Accepts direct ether transfers
     *         Ether received through direct transfers is not accounted for in `inOutDelta`
     */
    receive() external payable {
        if (msg.value == 0) revert ZeroArgument("msg.value");
    }

    /**
     * @notice Funds StakingVault with ether
     * @dev Updates inOutDelta to track the net difference between funded and withdrawn ether
     */
    function fund() external payable onlyOwner {
        if (msg.value == 0) revert ZeroArgument("msg.value");

        ERC7201Storage storage $ = _getStorage();
        $.inOutDelta += int128(int256(msg.value));

        emit Funded(msg.sender, msg.value);
    }

    /**
     * @notice Withdraws ether from StakingVault to a specified recipient.
     * @param _recipient Address to receive the withdrawn ether.
     * @param _ether Amount of ether to withdraw.
     * @dev Cannot withdraw more than the unlocked amount or the balance of the contract, whichever is less.
     * @dev Updates inOutDelta to track the net difference between funded and withdrawn ether.
     * @dev Checks that totalValue remains greater or equal than locked amount and prevents reentrancy attacks.
     */
    function withdraw(address _recipient, uint256 _ether) external onlyOwner {
        if (_recipient == address(0)) revert ZeroArgument("_recipient");
        if (_ether == 0) revert ZeroArgument("_ether");
        if (_ether > address(this).balance) revert InsufficientBalance(address(this).balance);
        uint256 unlocked_ = unlocked();
        if (_ether > unlocked_) revert InsufficientUnlocked(unlocked_);

        ERC7201Storage storage $ = _getStorage();
        $.inOutDelta -= int128(int256(_ether));

        (bool success, ) = _recipient.call{value: _ether}("");
        if (!success) revert TransferFailed(_recipient, _ether);

        if (isReportFresh()) {
            if (totalValue() < $.locked) revert TotalValueBelowLockedAmount();
        } else {
            if (address(this).balance < $.locked) revert TotalValueBelowLockedAmount();
        }

        emit Withdrawn(msg.sender, _recipient, _ether);
    }

    /**
     * @notice Locks ether in StakingVault
     * @dev Can only be called by owner; locked amount can only be increased
     * @param _locked New amount to lock
     */
    function lock(uint256 _locked) external onlyOwner {
        ERC7201Storage storage $ = _getStorage();
        if (_locked <= $.locked) revert NewLockedNotGreaterThanCurrent();
        if (isReportFresh()) {
            if (_locked > totalValue()) revert NewLockedExceedsTotalValue();
        } else {
            if (_locked > address(this).balance) revert NewLockedExceedsTotalValue();
        }

        $.locked = uint128(_locked);

        emit LockedIncreased(_locked);
    }

    /**
     * @notice Rebalances StakingVault by withdrawing ether to VaultHub
     * @dev Can only be called by VaultHub if StakingVault totalValue is less than locked amount
     * @param _ether Amount of ether to rebalance
     */
    function rebalance(uint256 _ether) external {
        if (_ether == 0) revert ZeroArgument("_ether");
        if (_ether > address(this).balance) revert InsufficientBalance(address(this).balance);

        uint256 totalValue_ = totalValue();
        if (_ether > totalValue_) revert RebalanceAmountExceedsTotalValue(totalValue_, _ether);

        ERC7201Storage storage $ = _getStorage();

        bool isAuthorized = (owner() == msg.sender
            || (totalValue_ < $.locked && msg.sender == address(VAULT_HUB) && $.vaultHubAuthorized)
        );
        if (!isAuthorized) revert NotAuthorized("rebalance", msg.sender);

        $.inOutDelta -= int128(int256(_ether));

        emit Withdrawn(msg.sender, address(VAULT_HUB), _ether);

        VAULT_HUB.rebalance{value: _ether}();
    }

    /**
     * @notice Submits a report containing totalValue, inOutDelta, and locked amount
     * @param _totalValue New total value: validator balances + StakingVault balance
     * @param _inOutDelta New net difference between funded and withdrawn ether
     * @param _locked New amount of locked ether
     */
    function report(uint64 _timestamp, uint256 _totalValue, int256 _inOutDelta, uint256 _locked) external {
        ERC7201Storage storage $ = _getStorage();
        if (msg.sender != address(VAULT_HUB) || !$.vaultHubAuthorized) revert NotAuthorized("report", msg.sender);

        uint64 currentTimestamp = $.report.timestamp;
        if (currentTimestamp >= _timestamp) revert ReportTooOld(currentTimestamp, _timestamp);

        $.report.timestamp = _timestamp;
        $.report.totalValue = uint128(_totalValue);
        $.report.inOutDelta = int128(_inOutDelta);
        $.locked = uint128(_locked);

        emit Reported(_timestamp, _totalValue, _inOutDelta, _locked);
    }

    /**
     * @notice Returns the 0x02-type withdrawal credentials for the validators deposited from this `StakingVault`
     *         All consensus layer rewards are sent to this contract. Only 0x02-type withdrawal credentials are supported
     */
    function withdrawalCredentials() public view returns (bytes32) {
        return bytes32(WC_0X02_PREFIX | uint160(address(this)));
    }

    /**
     * @notice Returns whether deposits are paused
     */
    function beaconChainDepositsPaused() external view returns (bool) {
        return _getStorage().beaconChainDepositsPaused;
    }

    /**
     * @notice Pauses deposits to beacon chain
     * @dev    Can only be called by the vault owner
     */
    function pauseBeaconChainDeposits() external onlyOwner {
        ERC7201Storage storage $ = _getStorage();
        if ($.beaconChainDepositsPaused) {
            revert BeaconChainDepositsResumeExpected();
        }

        $.beaconChainDepositsPaused = true;

        emit BeaconChainDepositsPaused();
    }

    /**
     * @notice Resumes deposits to beacon chain
     * @dev    Can only be called by the vault owner
     */
    function resumeBeaconChainDeposits() external onlyOwner {
        ERC7201Storage storage $ = _getStorage();
        if (!$.beaconChainDepositsPaused) {
            revert BeaconChainDepositsPauseExpected();
        }

        $.beaconChainDepositsPaused = false;

        emit BeaconChainDepositsResumed();
    }

    /**
     * @notice Performs a deposit to the beacon chain deposit contract
     * @param _deposits Array of deposit structs
     * @dev Can only be called by the depositor address
     * @dev Includes a check to ensure `StakingVault` total value is not less than locked before making deposits
     */
    function depositToBeaconChain(StakingVaultDeposit[] calldata _deposits) external {
        if (_deposits.length == 0) revert ZeroArgument("_deposits");

        ERC7201Storage storage $ = _getStorage();
        if ($.beaconChainDepositsPaused) revert BeaconChainDepositsArePaused();
        if (msg.sender != $.depositor) revert NotAuthorized("depositToBeaconChain", msg.sender);
        if (totalValue() < $.locked) revert TotalValueBelowLockedAmount();

        uint256 numberOfDeposits = _deposits.length;
        uint256 totalAmount = 0;
        bytes memory withdrawalCredentials_ = bytes.concat(withdrawalCredentials());

        for (uint256 i = 0; i < numberOfDeposits; i++) {
            StakingVaultDeposit calldata deposit = _deposits[i];

            DEPOSIT_CONTRACT.deposit{value: deposit.amount}(
                deposit.pubkey,
                withdrawalCredentials_,
                deposit.signature,
                deposit.depositDataRoot
            );

            totalAmount += deposit.amount;
        }

        emit DepositedToBeaconChain(msg.sender, numberOfDeposits, totalAmount);
    }

    /**
     * @notice Calculates the total withdrawal fee required for given number of validator keys
     * @param _numberOfKeys Number of validators' public keys
     * @return Total fee amount to pass as `msg.value` (wei)
     * @dev    The fee is only valid for the requests made in the same block
     */
    function calculateValidatorWithdrawalFee(uint256 _numberOfKeys) external view returns (uint256) {
        if (_numberOfKeys == 0) revert ZeroArgument("_numberOfKeys");

        return _numberOfKeys * TriggerableWithdrawals.getWithdrawalRequestFee();
    }

    /**
     * @notice Requests node operator to exit validators from the beacon chain
     *         It does not directly trigger exits - node operators must monitor for these events and handle the exits
     * @param _pubkeys Concatenated validator public keys, each 48 bytes long
     */
    function requestValidatorExit(bytes calldata _pubkeys) external onlyOwner {
        if (_pubkeys.length == 0) revert ZeroArgument("_pubkeys");
        if (_pubkeys.length % PUBLIC_KEY_LENGTH != 0) {
            revert InvalidPubkeysLength();
        }

        uint256 keysCount = _pubkeys.length / PUBLIC_KEY_LENGTH;
        for (uint256 i = 0; i < keysCount; i++) {
            bytes memory pubkey = _pubkeys[i * PUBLIC_KEY_LENGTH:(i + 1) * PUBLIC_KEY_LENGTH];
            emit ValidatorExitRequested(msg.sender, /* indexed */ pubkey, pubkey);
        }
    }

    /**
     * @notice Triggers validator withdrawals from the beacon chain using EIP-7002 triggerable exit
     * @param _pubkeys Concatenated validators public keys, each 48 bytes long
     * @param _amounts Amounts of ether to exit, must match the length of _pubkeys
     * @param _refundRecipient Address to receive the fee refund, if zero, refunds go to msg.sender
     * @dev    The caller must provide sufficient fee via msg.value to cover the withdrawal request costs
     */
    function triggerValidatorWithdrawal(
        bytes calldata _pubkeys,
        uint64[] calldata _amounts,
        address _refundRecipient
    ) external payable {
        if (msg.value == 0) revert ZeroArgument("msg.value");
        if (_pubkeys.length == 0) revert ZeroArgument("_pubkeys");
        if (_amounts.length == 0) revert ZeroArgument("_amounts");

        // If the refund recipient is not set, use the sender as the refund recipient
        if (_refundRecipient == address(0)) {
            _refundRecipient = msg.sender;
        }

        ERC7201Storage storage $ = _getStorage();

        bool isAuthorized = msg.sender == $.nodeOperator || msg.sender == owner();

        // Authorize VaultHub to initiate forced validator exits when total value is below locked amount
        bool isTotalValueBelowLocked = totalValue() < $.locked;
        if (isTotalValueBelowLocked) {
            isAuthorized = isAuthorized || (msg.sender == address(VAULT_HUB) && $.vaultHubAuthorized);
        }
        if (!isAuthorized) revert NotAuthorized("triggerValidatorWithdrawal", msg.sender);

        // Block partial withdrawals when total value is below locked amount or report is stale
        // This is to prevent forced validator exits from front-running with partial withdrawals
        if (isTotalValueBelowLocked || !isReportFresh()) {
            for (uint256 i = 0; i < _amounts.length; i++) {
                if (_amounts[i] > 0) revert PartialWithdrawalNotAllowed();
            }
        }

        uint256 feePerRequest = TriggerableWithdrawals.getWithdrawalRequestFee();
        uint256 totalFee = (_pubkeys.length / PUBLIC_KEY_LENGTH) * feePerRequest;
        if (msg.value < totalFee) revert InsufficientValidatorWithdrawalFee(msg.value, totalFee);

        TriggerableWithdrawals.addWithdrawalRequests(_pubkeys, _amounts, feePerRequest);

        uint256 excess = msg.value - totalFee;
        if (excess > 0) {
            (bool success, ) = _refundRecipient.call{value: excess}("");
            if (!success) revert WithdrawalFeeRefundFailed(_refundRecipient, excess);
        }

        emit ValidatorWithdrawalTriggered(msg.sender, _pubkeys, _amounts, _refundRecipient, excess);
    }

    function isReportFresh() public view returns (bool) {
        ERC7201Storage storage $ = _getStorage();
        if (!$.vaultHubAuthorized) return true;
        return block.timestamp - $.report.timestamp < VAULT_HUB.REPORT_FRESHNESS_DELTA();
    }

    function _checkFreshnessAndGetTotalValue() internal view returns (uint256) {
        if (!isReportFresh()) revert ReportStaled();
        return totalValue();
    }

    function _getStorage() private pure returns (ERC7201Storage storage $) {
        assembly {
            $.slot := ERC7201_STORAGE_LOCATION
        }
    }

    /**
     * @notice Emitted when `NodeOperator` is set
     * @param nodeOperator Address of the set `NodeOperator`
     */
    event NodeOperatorSet(address indexed nodeOperator);

    /**
     * @notice Emitted when `Depositor` is attached
     * @param depositor Address of the attached `Depositor`
     */
    event DepositorSet(address indexed depositor);

    /**
     * @notice Emitted when `StakingVault` is funded with ether
     * @dev Event is not emitted upon direct transfers through `receive()`
     * @param sender Address that funded the vault
     * @param amount Amount of ether funded
     */
    event Funded(address indexed sender, uint256 amount);

    /**
     * @notice Emitted when ether is withdrawn from `StakingVault`
     * @dev Also emitted upon rebalancing in favor of `VaultHub`
     * @param sender Address that initiated the withdrawal
     * @param recipient Address that received the withdrawn ether
     * @param amount Amount of ether withdrawn
     */
    event Withdrawn(address indexed sender, address indexed recipient, uint256 amount);

    /**
     * @notice Emitted when the locked amount is increased
     * @param locked New amount of locked ether
     */
    event LockedIncreased(uint256 locked);

    /**
     * @notice Emitted when the locked amount is reset to 0
     */
    event LockedReset();

    /**
     * @notice Emitted when a new report is submitted to `StakingVault`
     * @param totalValue Sum of the vault's validator balances and the balance of `StakingVault`
     * @param inOutDelta Net difference between ether funded and withdrawn from `StakingVault`
     * @param locked Amount of ether locked in `StakingVault`
     */
    event Reported(uint64 indexed timestamp, uint256 totalValue, int256 inOutDelta, uint256 locked);

    /**
     * @notice Emitted when deposits to beacon chain are paused
     */
    event BeaconChainDepositsPaused();

    /**
     * @notice Emitted when deposits to beacon chain are resumed
     */
    event BeaconChainDepositsResumed();

    /**
     * @notice Emitted when ether is deposited to `DepositContract`.
     * @param _sender Address that initiated the deposit.
     * @param _deposits Number of validator deposits made.
     * @param _totalAmount Total amount of ether deposited.
     */
    event DepositedToBeaconChain(address indexed _sender, uint256 _deposits, uint256 _totalAmount);

    /**
     * @notice Emitted when vault owner requests node operator to exit validators from the beacon chain
     * @param _sender Address that requested the exit
     * @param _pubkey Indexed public key of the validator to exit
     * @param _pubkeyRaw Raw public key of the validator to exit
     * @dev    Signals to node operators that they should exit this validator from the beacon chain
     */
    event ValidatorExitRequested(address _sender, bytes indexed _pubkey, bytes _pubkeyRaw);

    /**
     * @notice Emitted when validator withdrawals are requested via EIP-7002
     * @param _sender Address that requested the withdrawals
     * @param _pubkeys Concatenated public keys of the validators to withdraw
     * @param _amounts Amounts of ether to withdraw per validator
     * @param _refundRecipient Address to receive any excess withdrawal fee
     * @param _excess Amount of excess fee refunded to recipient
     */
    event ValidatorWithdrawalTriggered(
        address indexed _sender,
        bytes _pubkeys,
        uint64[] _amounts,
        address _refundRecipient,
        uint256 _excess
    );

    /**
     * @notice Emitted when `VaultHub` is authorized or deauthorized from `StakingVault`
     * @param authorized True if `VaultHub` is authorized, false otherwise
     */
    event VaultHubAuthorizedSet(bool authorized);

    /**
     * @notice Thrown when an invalid zero value is passed
     * @param name Name of the argument that was zero
     */
    error ZeroArgument(string name);

    /**
     * @notice Thrown when trying to withdraw more ether than the balance of `StakingVault`
     * @param balance Current balance
     */
    error InsufficientBalance(uint256 balance);

    /**
     * @notice Thrown when trying to withdraw more than the unlocked amount
     * @param unlocked Current unlocked amount
     */
    error InsufficientUnlocked(uint256 unlocked);

    /**
     * @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
     */
    error RebalanceAmountExceedsTotalValue(uint256 totalValue, uint256 rebalanceAmount);

    /**
     * @notice Thrown when the transfer of ether to a recipient fails
     * @param recipient Address that was supposed to receive the transfer
     * @param amount Amount that failed to transfer
     */
    error TransferFailed(address recipient, uint256 amount);

    /**
     * @notice Thrown when the total value of the vault falls below the locked amount
     */
    error TotalValueBelowLockedAmount();

    /**
     * @notice Thrown when an unauthorized address attempts a restricted operation
     * @param operation Name of the attempted operation
     * @param sender Address that attempted the operation
     */
    error NotAuthorized(string operation, address sender);

    /**
     * @notice Thrown when attempting to decrease the locked amount outside of a report
     */
    error NewLockedNotGreaterThanCurrent();

    /**
     * @notice Thrown when the locked amount exceeds the total value
     */
    error NewLockedExceedsTotalValue();

    /**
     * @notice Thrown when trying to pause deposits to beacon chain while deposits are already paused
     */
    error BeaconChainDepositsPauseExpected();

    /**
     * @notice Thrown when trying to resume deposits to beacon chain while deposits are already resumed
     */
    error BeaconChainDepositsResumeExpected();

    /**
     * @notice Thrown when trying to deposit to beacon chain while deposits are paused
     */
    error BeaconChainDepositsArePaused();

    /**
     * @notice Thrown when the length of the validator public keys is invalid
     */
    error InvalidPubkeysLength();

    /**
     * @notice Thrown when the validator withdrawal fee is insufficient
     * @param _passed Amount of ether passed to the function
     * @param _required Amount of ether required to cover the fee
     */
    error InsufficientValidatorWithdrawalFee(uint256 _passed, uint256 _required);

    /**
     * @notice Thrown when a validator withdrawal fee refund fails
     * @param _sender Address that initiated the refund
     * @param _amount Amount of ether to refund
     */
    error WithdrawalFeeRefundFailed(address _sender, uint256 _amount);

    /**
     * @notice Thrown when partial withdrawals are not allowed when total value is below locked
     */
    error PartialWithdrawalNotAllowed();

    /**
     * @notice Thrown when trying to deauthorize vaultHub while it is not authorized
     */
    error VaultHubNotAuthorized();

    /**
     * @notice Thrown when trying to ossify vault, or to attach vault to VaultHub while it is already attached
     */
    error VaultHubAuthorized();

    /**
     * @notice Thrown when trying to attach vault to VaultHub while it is ossified
     */
    error VaultOssified();

    /**
     * @notice Thrown when a report is staled
     */
    error ReportStaled();

    /**
     * @notice Thrown when a report is too old
     */
    error ReportTooOld(uint64 currentTimestamp, uint64 newTimestamp);

    /**
     * @notice Thrown when the depositor is not the Lido Predeposit Guarantee
     * @param depositor Address of the depositor
     */
    error InvalidDepositor(address depositor);

    /**
     * @notice Thrown when the vault is connected to VaultHub
     */
    error VaultConnected();
}

// 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;
}

File 4 of 33 : IERC1967.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1967.sol)

pragma solidity ^0.8.20;

/**
 * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
 */
interface IERC1967 {
    /**
     * @dev Emitted when the implementation is upgraded.
     */
    event Upgraded(address indexed implementation);

    /**
     * @dev Emitted when the admin account has changed.
     */
    event AdminChanged(address previousAdmin, address newAdmin);

    /**
     * @dev Emitted when the beacon is changed.
     */
    event BeaconUpgraded(address indexed beacon);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is the interface that {BeaconProxy} expects of its beacon.
 */
interface IBeacon {
    /**
     * @dev Must return an address that can be used as a delegate call target.
     *
     * {UpgradeableBeacon} will check that this address is a contract.
     */
    function implementation() external view returns (address);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/ERC1967/ERC1967Utils.sol)

pragma solidity ^0.8.22;

import {IBeacon} from "../beacon/IBeacon.sol";
import {IERC1967} from "../../interfaces/IERC1967.sol";
import {Address} from "../../utils/Address.sol";
import {StorageSlot} from "../../utils/StorageSlot.sol";

/**
 * @dev This library provides getters and event emitting update functions for
 * https://eips.ethereum.org/EIPS/eip-1967[ERC-1967] slots.
 */
library ERC1967Utils {
    /**
     * @dev Storage slot with the address of the current implementation.
     * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
     */
    // solhint-disable-next-line private-vars-leading-underscore
    bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    /**
     * @dev The `implementation` of the proxy is invalid.
     */
    error ERC1967InvalidImplementation(address implementation);

    /**
     * @dev The `admin` of the proxy is invalid.
     */
    error ERC1967InvalidAdmin(address admin);

    /**
     * @dev The `beacon` of the proxy is invalid.
     */
    error ERC1967InvalidBeacon(address beacon);

    /**
     * @dev An upgrade function sees `msg.value > 0` that may be lost.
     */
    error ERC1967NonPayable();

    /**
     * @dev Returns the current implementation address.
     */
    function getImplementation() internal view returns (address) {
        return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
    }

    /**
     * @dev Stores a new address in the ERC-1967 implementation slot.
     */
    function _setImplementation(address newImplementation) private {
        if (newImplementation.code.length == 0) {
            revert ERC1967InvalidImplementation(newImplementation);
        }
        StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
    }

    /**
     * @dev Performs implementation upgrade with additional setup call if data is nonempty.
     * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
     * to avoid stuck value in the contract.
     *
     * Emits an {IERC1967-Upgraded} event.
     */
    function upgradeToAndCall(address newImplementation, bytes memory data) internal {
        _setImplementation(newImplementation);
        emit IERC1967.Upgraded(newImplementation);

        if (data.length > 0) {
            Address.functionDelegateCall(newImplementation, data);
        } else {
            _checkNonPayable();
        }
    }

    /**
     * @dev Storage slot with the admin of the contract.
     * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
     */
    // solhint-disable-next-line private-vars-leading-underscore
    bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

    /**
     * @dev Returns the current admin.
     *
     * TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using
     * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
     * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
     */
    function getAdmin() internal view returns (address) {
        return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
    }

    /**
     * @dev Stores a new address in the ERC-1967 admin slot.
     */
    function _setAdmin(address newAdmin) private {
        if (newAdmin == address(0)) {
            revert ERC1967InvalidAdmin(address(0));
        }
        StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
    }

    /**
     * @dev Changes the admin of the proxy.
     *
     * Emits an {IERC1967-AdminChanged} event.
     */
    function changeAdmin(address newAdmin) internal {
        emit IERC1967.AdminChanged(getAdmin(), newAdmin);
        _setAdmin(newAdmin);
    }

    /**
     * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
     * This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
     */
    // solhint-disable-next-line private-vars-leading-underscore
    bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;

    /**
     * @dev Returns the current beacon.
     */
    function getBeacon() internal view returns (address) {
        return StorageSlot.getAddressSlot(BEACON_SLOT).value;
    }

    /**
     * @dev Stores a new beacon in the ERC-1967 beacon slot.
     */
    function _setBeacon(address newBeacon) private {
        if (newBeacon.code.length == 0) {
            revert ERC1967InvalidBeacon(newBeacon);
        }

        StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;

        address beaconImplementation = IBeacon(newBeacon).implementation();
        if (beaconImplementation.code.length == 0) {
            revert ERC1967InvalidImplementation(beaconImplementation);
        }
    }

    /**
     * @dev Change the beacon and trigger a setup call if data is nonempty.
     * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
     * to avoid stuck value in the contract.
     *
     * Emits an {IERC1967-BeaconUpgraded} event.
     *
     * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
     * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
     * efficiency.
     */
    function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
        _setBeacon(newBeacon);
        emit IERC1967.BeaconUpgraded(newBeacon);

        if (data.length > 0) {
            Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
        } else {
            _checkNonPayable();
        }
    }

    /**
     * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
     * if an upgrade doesn't perform an initialization call.
     */
    function _checkNonPayable() private {
        if (msg.value > 0) {
            revert ERC1967NonPayable();
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[ERC-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC-20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (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) (utils/Address.sol)

pragma solidity ^0.8.20;

import {Errors} from "./Errors.sol";

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert Errors.InsufficientBalance(address(this).balance, amount);
        }

        (bool success, bytes memory returndata) = recipient.call{value: amount}("");
        if (!success) {
            _revert(returndata);
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {Errors.FailedCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
     * of an unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {Errors.FailedCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            assembly ("memory-safe") {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert Errors.FailedCall();
        }
    }
}

// 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];
        }
    }
}

File 12 of 33 : Errors.sol
// 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/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/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC-1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(newImplementation.code.length > 0);
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * TIP: Consider using this library along with {SlotDerivation}.
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct Int256Slot {
        int256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Int256Slot` with member `value` located at `slot`.
     */
    function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns a `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }
}

// 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;

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
pragma solidity 0.8.25;

import {IERC20} from "@openzeppelin/contracts-v5.2/token/ERC20/IERC20.sol";
import {IERC20Permit} from "@openzeppelin/contracts-v5.2/token/ERC20/extensions/IERC20Permit.sol";

interface ILido is IERC20, IERC20Permit {
    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;
}

// SPDX-FileCopyrightText: 2025 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

// See contracts/COMPILERS.md
pragma solidity 0.8.25;

import {PausableUntil} from "contracts/common/utils/PausableUntil.sol";
import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.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 {IDepositContract} from "contracts/0.8.25/interfaces/IDepositContract.sol";

/**
 * @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 StakingVaultDeposit {
    bytes pubkey;
    bytes signature;
    uint256 amount;
    bytes32 depositDataRoot;
}

/**
 * @title IStakingVault
 * @author Lido
 * @notice Interface for the `StakingVault` contract
 */
interface IStakingVault {
    /**
     * @notice Latest reported totalValue and inOutDelta
     * @custom:totalValue Aggregated validator balances plus the balance of `StakingVault`
     * @custom:inOutDelta Net difference between ether funded and withdrawn from `StakingVault`
     */
    struct Report {
        uint128 totalValue;
        int128 inOutDelta;
        uint64 timestamp;
    }

    function DEPOSIT_CONTRACT() external view returns (IDepositContract);

    function initialize(address _owner, address _operator, address _depositor, bytes calldata _params) external;
    function version() external pure returns (uint64);
    function owner() external view returns (address);
    function getInitializedVersion() external view returns (uint64);
    function vaultHub() external view returns (address);
    function nodeOperator() external view returns (address);
    function depositor() external view returns (address);
    function locked() external view returns (uint256);
    function totalValue() external view returns (uint256);
    function unlocked() external view returns (uint256);
    function inOutDelta() external view returns (int256);
    function fund() external payable;
    function withdraw(address _recipient, uint256 _ether) external;
    function lock(uint256 _locked) external;
    function rebalance(uint256 _ether) external;
    function latestReport() external view returns (Report memory);
    function report(uint64 _timestamp, uint256 _totalValue, int256 _inOutDelta, uint256 _locked) external;
    function withdrawalCredentials() external view returns (bytes32);
    function beaconChainDepositsPaused() external view returns (bool);
    function pauseBeaconChainDeposits() external;
    function resumeBeaconChainDeposits() external;
    function depositToBeaconChain(StakingVaultDeposit[] calldata _deposits) external;
    function requestValidatorExit(bytes calldata _pubkeys) external;
    function calculateValidatorWithdrawalFee(uint256 _keysCount) external view returns (uint256);
    function triggerValidatorWithdrawal(
        bytes calldata _pubkeys,
        uint64[] calldata _amounts,
        address _refundRecipient
    ) external payable;
    function authorizeLidoVaultHub() external;
    function deauthorizeLidoVaultHub() external;
    function vaultHubAuthorized() external view returns (bool);
    function ossifyStakingVault() external;
    function ossified() external view returns (bool);
    function setDepositor(address _depositor) external;
    function resetLocked() external;
    function isReportFresh() external view returns (bool);
}

// SPDX-FileCopyrightText: 2025 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

// See contracts/COMPILERS.md
pragma solidity 0.8.25;

import {StorageSlot} from "@openzeppelin/contracts-v5.2/utils/StorageSlot.sol";
import {IBeacon} from "@openzeppelin/contracts-v5.2/proxy/beacon/IBeacon.sol";
import {ERC1967Utils} from "@openzeppelin/contracts-v5.2/proxy/ERC1967/ERC1967Utils.sol";

library PinnedBeaconUtils {
    /**
     * @dev Storage slot with the address of the last implementation.
     * PINNED_BEACON_STORAGE_SLOT = bytes32(uint256(keccak256("stakingVault.proxy.pinnedBeacon")) - 1)
     */
    bytes32 internal constant PINNED_BEACON_STORAGE_SLOT = 0x8d75cfa6c9a3cd2fb8b6d445eafb32adc5497a45b333009f9000379f7024f9f5;

    function getPinnedImplementation() internal view returns (address) {
        return StorageSlot.getAddressSlot(PINNED_BEACON_STORAGE_SLOT).value;
    }

    /**
     * @notice Ossifies the beacon by pinning the current implementation
     */
    function ossify() internal {
        if (ossified()) revert AlreadyOssified();
        address currentImplementation = IBeacon(ERC1967Utils.getBeacon()).implementation();
        StorageSlot.getAddressSlot(PINNED_BEACON_STORAGE_SLOT).value = currentImplementation;
        emit PinnedImplementationUpdated(currentImplementation);
    }

    /**
     * @notice Returns true if the proxy is ossified
     * @return True if the proxy is ossified, false otherwise
     */
    function ossified() internal view returns(bool) {
        return getPinnedImplementation() != address(0);
    }

    /**
     * @notice Emitted when the pinned implementation is updated
     * @param implementation The address of the new pinned implementation
     */
    event PinnedImplementationUpdated(address indexed implementation);

    /**
     * @notice Thrown when trying to ossify the proxy while it is already ossified
     */
    error AlreadyOssified();
}

// 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 {EnumerableSet} from "@openzeppelin/contracts-v5.2/utils/structs/EnumerableSet.sol";

import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol";
import {IStakingVault} from "./interfaces/IStakingVault.sol";
import {VaultHub} from "./VaultHub.sol";

struct TierParams {
    uint256 shareLimit;
    uint256 reserveRatioBP;
    uint256 forcedRebalanceThresholdBP;
    uint256 treasuryFeeBP;
}

/**
 * @title OperatorGrid
 * @author Lido
 * @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
 * - treasuryFeeBP: treasury fee in basis points
 *
 * These parameters are determined by the Tier in which the Vault is registered.
 *
 */
contract OperatorGrid is AccessControlEnumerableUpgradeable {
    /*
      Key concepts:
      1. Default Registration:
         - All Vaults are initially has 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 modify a vault's connection parameters to VaultHub, a tier change must be requested
         - Change requests must be approved by the target tier's Node Operator
         - All pending requests are tracked in the pendingRequests mapping

         Operator1.pendingRequests = [Vault_1, Vault_2, ...]

       3. Confirmation Process:
         - Node Operator can confirm the tier change if:
           a) The target tier has sufficient capacity (shareLimit)
           b) Vault's node operator corresponds to the target tier group
         For detailed tier change scenarios and share accounting, see the ASCII diagrams in the `confirmTierChange` function.

       4. Tier Capacity:
         - Tiers are not limited by the number of vaults
         - Tiers are limited by the sum of vaults' minted shares

        ┌──────────────────────────────────────────────────────┐
        │                 Group 1 = operator 1                 │
        │  ┌────────────────────────────────────────────────┐  │
        │  │  groupShareLimit = 1kk                         │  │
        │  └────────────────────────────────────────────────┘  │
        │  ┌──────────────────────┐  ┌──────────────────────┐  │
        │  │       Tier 1         │  │       Tier 2         │  │
        │  │  tierShareLimit = x  │  │  tierShareLimit = y  │  │
        │  │  Vault_2 ... Vault_k │  │                      │  │
        │  └──────────────────────┘  └──────────────────────┘  │
        └──────────────────────────────────────────────────────┘
     */

    using EnumerableSet for EnumerableSet.AddressSet;

    bytes32 public constant REGISTRY_ROLE = keccak256("vaults.OperatorsGrid.Registry");

    /// @notice Lido Locator contract
    ILidoLocator public immutable LIDO_LOCATOR;

    /// @notice Default group address
    uint256 public constant DEFAULT_TIER_ID = 0;
    address public constant DEFAULT_TIER_OPERATOR = address(uint160(type(uint160).max));

    /// @dev basis points base
    uint256 internal constant TOTAL_BASIS_POINTS = 100_00;

    // -----------------------------
    //            STRUCTS
    // -----------------------------
    struct Group {
        address operator;
        uint96 shareLimit;
        uint96 liabilityShares;
        uint128[] tierIds;
    }

    struct Tier {
        address operator;
        uint96 shareLimit;
        uint96 liabilityShares;
        uint16 reserveRatioBP;
        uint16 forcedRebalanceThresholdBP;
        uint16 treasuryFeeBP;
    }

    struct VaultTier {
        uint128 currentTierId;
        uint128 requestedTierId;
    }

    /**
     * @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 tiers
     * @custom:groups Groups
     * @custom:pendingRequests Pending requests
     * @custom:nodeOperators Node operators
     */
    struct ERC7201Storage {
        Tier[] tiers;
        mapping(address vault => VaultTier) vaultTier;
        mapping(address nodeOperator => Group) groups;
        mapping(address nodeOperator => EnumerableSet.AddressSet) pendingRequests;
        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 ERC7201_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();
        _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),
                treasuryFeeBP: uint16(_defaultTierParams.treasuryFeeBP),
                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 uint128[](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();

        uint128 tierId = uint128($.tiers.length);
        uint256 length = _tiers.length;
        for (uint256 i = 0; i < length; i++) {
            _validateParams(tierId, _tiers[i].reserveRatioBP, _tiers[i].forcedRebalanceThresholdBP, _tiers[i].treasuryFeeBP);

            Tier memory tier_ = Tier({
                operator: _nodeOperator,
                shareLimit: uint96(_tiers[i].shareLimit),
                reserveRatioBP: uint16(_tiers[i].reserveRatioBP),
                forcedRebalanceThresholdBP: uint16(_tiers[i].forcedRebalanceThresholdBP),
                treasuryFeeBP: uint16(_tiers[i].treasuryFeeBP),
                liabilityShares: 0
            });
            $.tiers.push(tier_);
            group_.tierIds.push(tierId);

            emit TierAdded(
                _nodeOperator,
                tierId,
                uint96(_tiers[i].shareLimit),
                uint16(_tiers[i].reserveRatioBP),
                uint16(_tiers[i].forcedRebalanceThresholdBP),
                uint16(_tiers[i].treasuryFeeBP)
            );

            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 Alters a tier
    /// @dev We do not enforce to update old vaults with the new tier params, only new ones.
    /// @param _tierId id of the tier
    /// @param _tierParams new tier params
    function alterTier(uint256 _tierId, TierParams calldata _tierParams) external onlyRole(REGISTRY_ROLE) {
        ERC7201Storage storage $ = _getStorage();
        if (_tierId >= $.tiers.length) revert TierNotExists();

        _validateParams(_tierId, _tierParams.reserveRatioBP, _tierParams.forcedRebalanceThresholdBP, _tierParams.treasuryFeeBP);

        Tier storage tier_ = $.tiers[_tierId];

        tier_.shareLimit = uint96(_tierParams.shareLimit);
        tier_.reserveRatioBP = uint16(_tierParams.reserveRatioBP);
        tier_.forcedRebalanceThresholdBP = uint16(_tierParams.forcedRebalanceThresholdBP);
        tier_.treasuryFeeBP = uint16(_tierParams.treasuryFeeBP);

        emit TierUpdated(_tierId, tier_.shareLimit, tier_.reserveRatioBP, tier_.forcedRebalanceThresholdBP, tier_.treasuryFeeBP);
    }

    /// @notice Request to change tier
    /// @param _vault address of the vault
    /// @param _tierId id of the tier
    function requestTierChange(address _vault, uint256 _tierId) external {
        if (_vault == address(0)) revert ZeroArgument("_vault");
        if (msg.sender != IStakingVault(_vault).owner()) revert NotAuthorized("requestTierChange", msg.sender);

        ERC7201Storage storage $ = _getStorage();
        if (_tierId >= $.tiers.length) revert TierNotExists();
        if (_tierId == DEFAULT_TIER_ID) revert CannotChangeToDefaultTier();

        Tier memory requestedTier = $.tiers[_tierId];
        address requestedTierOperator = requestedTier.operator;
        address nodeOperator = IStakingVault(_vault).nodeOperator();
        if (nodeOperator != requestedTierOperator) revert TierNotInOperatorGroup();

        uint128 tierId = uint128(_tierId);

        VaultTier storage vaultTier = $.vaultTier[_vault];
        if (vaultTier.currentTierId == tierId) revert TierAlreadySet();
        if (vaultTier.requestedTierId == tierId) revert TierAlreadyRequested();

        vaultTier.requestedTierId = tierId;

        $.pendingRequests[nodeOperator].add(_vault); //returns true if the vault was not in the set

        emit TierChangeRequested(_vault, vaultTier.currentTierId, _tierId);
    }

    /// @notice Confirm tier change request
    /// @param _vault address of the vault
    /// @param _tierIdToConfirm id of the tier to confirm
    ///
    /*

    Legend:
    V = Vault1.liabilityShares

    Scheme1 - transfer Vault from default tier to Tier2

                                         ┌────────────────────────────────┐
                                         │           Group 1              │
                                         │                                │
    ┌────────────────────┐               │  ┌───────────┐  ┌───────────┐  │
    │  Tier 1 (default)  │   confirm     │  │ Tier 2    │  │ Tier 3    │  │
    │  minted: -V        │    ─────▶     │  │ minted:+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    │                 │
    │  │ minted:-V │  │ minted:+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. Reverts on `requestTierChange`
    NB: Cannot change from Tier2 to Tier4, because Tier4 has different operator.

    */
    function confirmTierChange(address _vault, uint256 _tierIdToConfirm) external {
        if (_vault == address(0)) revert ZeroArgument("_vault");

        address nodeOperator = IStakingVault(_vault).nodeOperator();
        if (msg.sender != nodeOperator) revert NotAuthorized("confirmTierChange", msg.sender);
        if (_tierIdToConfirm == DEFAULT_TIER_ID) revert CannotChangeToDefaultTier();

        ERC7201Storage storage $ = _getStorage();
        VaultTier storage vaultTier = $.vaultTier[_vault];
        uint128 requestedTierId = vaultTier.requestedTierId;
        if (requestedTierId != _tierIdToConfirm) revert InvalidTierId(requestedTierId, _tierIdToConfirm);

        Tier storage requestedTier = $.tiers[requestedTierId];

        VaultHub vaultHub = VaultHub(LIDO_LOCATOR.vaultHub());
        VaultHub.VaultSocket memory vaultSocket = vaultHub.vaultSocket(_vault);
        uint256 vaultLiabilityShares = vaultSocket.liabilityShares;

        //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 (vaultTier.currentTierId == DEFAULT_TIER_ID) {
            Group storage requestedGroup = $.groups[nodeOperator];
            if (requestedGroup.liabilityShares + vaultLiabilityShares > requestedGroup.shareLimit) revert GroupLimitExceeded();
            requestedGroup.liabilityShares += uint96(vaultLiabilityShares);
        }

        Tier storage currentTier = $.tiers[vaultTier.currentTierId];

        currentTier.liabilityShares -= uint96(vaultLiabilityShares);
        requestedTier.liabilityShares += uint96(vaultLiabilityShares);

        vaultTier.currentTierId = requestedTierId;
        vaultTier.requestedTierId = 0;

        $.pendingRequests[nodeOperator].remove(_vault);

        VaultHub(LIDO_LOCATOR.vaultHub()).updateConnection(
            _vault,
            requestedTier.shareLimit,
            requestedTier.reserveRatioBP,
            requestedTier.forcedRebalanceThresholdBP,
            requestedTier.treasuryFeeBP
        );

        emit TierChanged(_vault, requestedTierId);
    }

    /// @notice Returns pending requests for a node operator
    /// @param _nodeOperator address of the node operator
    /// @return vault addresses
    function pendingRequests(address _nodeOperator) external view returns (address[] memory) {
        return _getStorage().pendingRequests[_nodeOperator].values();
    }

    /// @notice Returns a pending request for a node operator
    /// @param _nodeOperator address of the node operator
    /// @param _index index of the pending request
    /// @return vault address
    function pendingRequest(address _nodeOperator, uint256 _index) external view returns (address) {
        return _getStorage().pendingRequests[_nodeOperator].at(_index);
    }

    /// @notice Returns a pending requests count for a node operator
    /// @param _nodeOperator address of the node operator
    /// @return pending requests count
    function pendingRequestsCount(address _nodeOperator) external view returns (uint256) {
        return _getStorage().pendingRequests[_nodeOperator].length();
    }

   // -----------------------------
   //     MINT / BURN
   // -----------------------------

    /// @notice Mint shares limit check
    /// @param vaultAddr address of the vault
    /// @param amount amount of shares will be minted
    function onMintedShares(
        address vaultAddr,
        uint256 amount
    ) external {
        if (msg.sender != LIDO_LOCATOR.vaultHub()) revert NotAuthorized("onMintedShares", msg.sender);

        ERC7201Storage storage $ = _getStorage();

        VaultTier memory vaultTier = $.vaultTier[vaultAddr];
        uint128 tierId = vaultTier.currentTierId;

        uint96 amount_ = uint96(amount);

        Tier storage tier_ = $.tiers[tierId];

        uint96 tierLiabilityShares = tier_.liabilityShares; //cache
        if (tierLiabilityShares + amount_ > tier_.shareLimit) revert TierLimitExceeded();

        tier_.liabilityShares = tierLiabilityShares + 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 + amount_;
        }
    }

    /// @notice Burn shares limit check
    /// @param vaultAddr address of the vault
    /// @param amount amount of shares to burn
    function onBurnedShares(
        address vaultAddr,
        uint256 amount
    ) external {
        if (msg.sender != LIDO_LOCATOR.vaultHub()) revert NotAuthorized("burnShares", msg.sender);

        ERC7201Storage storage $ = _getStorage();

        VaultTier memory vaultTier = $.vaultTier[vaultAddr];
        uint128 tierId = vaultTier.currentTierId;

        uint96 amount_ = uint96(amount);

        Tier storage tier_ = $.tiers[tierId];

        // we skip the check for minted shared underflow, because it's done in the VaultHub.burnShares()

        tier_.liabilityShares -= amount_;

        if (tierId != DEFAULT_TIER_ID) {
            Group storage group_ = $.groups[tier_.operator];
            group_.liabilityShares -= amount_;
        }
    }

    /// @notice Get vault limits
    /// @param vaultAddr 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 treasuryFeeBP treasury fee of the vault
    function vaultInfo(address vaultAddr)
        external
        view
        returns (
            address nodeOperator,
            uint256 tierId,
            uint256 shareLimit,
            uint256 reserveRatioBP,
            uint256 forcedRebalanceThresholdBP,
            uint256 treasuryFeeBP
        )
    {
        ERC7201Storage storage $ = _getStorage();

        VaultTier memory vaultTier = $.vaultTier[vaultAddr];
        tierId = vaultTier.currentTierId;

        Tier memory t = $.tiers[tierId];
        nodeOperator = t.operator;

        shareLimit = t.shareLimit;
        reserveRatioBP = t.reserveRatioBP;
        forcedRebalanceThresholdBP = t.forcedRebalanceThresholdBP;
        treasuryFeeBP = t.treasuryFeeBP;
    }

    /// @notice Validates tier parameters
    /// @param _reserveRatioBP Reserve ratio
    /// @param _forcedRebalanceThresholdBP Forced rebalance threshold
    /// @param _treasuryFeeBP Treasury fee
    function _validateParams(
      uint256 _tierId,
      uint256 _reserveRatioBP,
      uint256 _forcedRebalanceThresholdBP,
      uint256 _treasuryFeeBP
    ) 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 (_treasuryFeeBP > TOTAL_BASIS_POINTS)
            revert TreasuryFeeTooHigh(_tierId, _treasuryFeeBP, TOTAL_BASIS_POINTS);
    }

    function _getStorage() private pure returns (ERC7201Storage storage $) {
        assembly {
            $.slot := ERC7201_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 treasuryFee);
    event VaultAdded(address indexed vault);
    event TierChanged(address indexed vault, uint256 indexed tierId);
    event TierChangeRequested(address indexed vault, uint256 indexed currentTierId, uint256 indexed requestedTierId);
    event TierUpdated(uint256 indexed tierId, uint256 shareLimit, uint256 reserveRatioBP, uint256 forcedRebalanceThresholdBP, uint256 treasuryFee);

    // -----------------------------
    //            ERRORS
    // -----------------------------
    error NotAuthorized(string operation, address sender);
    error ZeroArgument(string argument);
    error GroupExists();
    error GroupNotExists();
    error GroupLimitExceeded();
    error GroupMintedSharesUnderflow();
    error NodeOperatorNotExists();
    error TierExists();
    error TiersNotAvailable();
    error TierLimitExceeded();
    error TierMintedSharesUnderflow();

    error TierNotExists();
    error TierAlreadySet();
    error TierAlreadyRequested();
    error TierNotInOperatorGroup();
    error InvalidTierId(uint256 requestedTierId, uint256 confirmedTierId);
    error CannotChangeToDefaultTier();

    error ReserveRatioTooHigh(uint256 tierId, uint256 reserveRatioBP, uint256 maxReserveRatioBP);
    error ForcedRebalanceThresholdTooHigh(uint256 tierId, uint256 forcedRebalanceThresholdBP, uint256 reserveRatioBP);
    error TreasuryFeeTooHigh(uint256 tierId, uint256 treasuryFeeBP, uint256 maxTreasuryFeeBP);
}

File 22 of 33 : VaultHub.sol
// SPDX-FileCopyrightText: 2025 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

// See contracts/COMPILERS.md
pragma solidity 0.8.25;

import {OwnableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/OwnableUpgradeable.sol";
import {MerkleProof} from "@openzeppelin/contracts-v5.2/utils/cryptography/MerkleProof.sol";
import {OperatorGrid} from "./OperatorGrid.sol";

import {IStakingVault} from "./interfaces/IStakingVault.sol";
import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol";
import {ILido} from "../interfaces/ILido.sol";

import {PausableUntilWithRoles} from "../utils/PausableUntilWithRoles.sol";

import {Math256} from "contracts/common/lib/Math256.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 passes the report from the accounting oracle to the vaults and charges fees
/// @author folkyatina
contract VaultHub is PausableUntilWithRoles {
    /// @custom:storage-location erc7201:VaultHub
    struct VaultHubStorage {
        /// @notice vault sockets with vaults connected to the hub
        /// @dev    first socket is always zero. stone in the elevator
        VaultSocket[] sockets;
        /// @notice mapping from vault address to its socket
        /// @dev    if vault is not connected to the hub, its index is zero
        mapping(address => uint256) vaultIndex;
        /// @notice allowed beacon addresses
        mapping(bytes32 => bool) vaultProxyCodehash;
        /// @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;
    }

    struct VaultSocket {
        // ### 1st slot
        /// @notice vault address
        address vault;
        /// @notice total number of stETH shares that the vault owes to Lido
        uint96 liabilityShares;
        // ### 2nd slot
        /// @notice maximum number of stETH shares that can be minted by vault owner
        uint96 shareLimit;
        /// @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 treasury fee in basis points
        uint16 treasuryFeeBP;
        /// @notice if true, vault is disconnected and fee is not accrued
        bool pendingDisconnect;
        /// @notice cumulative amount of shares charged as fees for the vault
        uint96 feeSharesCharged;
        /// @notice unused gap in the slot 2
        /// uint8 _unused_gap_;
    }

    struct VaultInfo {
        address vault;
        uint256 balance;
        int256 inOutDelta;
        bytes32 withdrawalCredentials;
        uint256 liabilityShares;
    }

    // keccak256(abi.encode(uint256(keccak256("VaultHub")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant VAULT_HUB_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 add factories and vault implementations to hub
    bytes32 public constant VAULT_REGISTRY_ROLE = keccak256("Vaults.VaultHub.VaultRegistryRole");
    /// @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;
    /// @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 = 1 days;

    /// @notice limit for a single vault share limit relative to Lido TVL in basis points
    uint256 private immutable RELATIVE_SHARE_LIMIT_BP;

    /// @notice Lido stETH contract
    ILido public immutable LIDO;
    /// @notice Lido Locator contract
    ILidoLocator public immutable LIDO_LOCATOR;

    /// @param _locator Lido Locator contract
    /// @param _lido Lido stETH contract
    /// @param _relativeShareLimitBP Maximum share limit relative to TVL in basis points
    constructor(
        ILidoLocator _locator,
        ILido _lido,
        uint256 _relativeShareLimitBP
    ) {
        if (_relativeShareLimitBP == 0) revert ZeroArgument("_relativeShareLimitBP");
        if (_relativeShareLimitBP > TOTAL_BASIS_POINTS)
            revert RelativeShareLimitBPTooHigh(_relativeShareLimitBP, TOTAL_BASIS_POINTS);

        LIDO_LOCATOR = _locator;
        LIDO = _lido;
        RELATIVE_SHARE_LIMIT_BP = _relativeShareLimitBP;

        _disableInitializers();
    }

    function initialize(address _admin) external initializer {
        if (_admin == address(0)) revert ZeroArgument("_admin");

        __VaultHub_init(_admin);
    }

    /// @param _admin admin address to manage the roles
    function __VaultHub_init(address _admin) internal onlyInitializing {
        __AccessControlEnumerable_init();

        // the stone in the elevator
        _getVaultHubStorage().sockets.push(VaultSocket(address(0), 0, 0, 0, 0, 0, false, 0));

        _grantRole(DEFAULT_ADMIN_ROLE, _admin);
    }

    function operatorGrid() external view returns (address) {
        return LIDO_LOCATOR.operatorGrid();
    }

    /// @notice added vault proxy codehash to allowed list
    /// @param codehash vault proxy codehash
    function addVaultProxyCodehash(bytes32 codehash) public onlyRole(VAULT_REGISTRY_ROLE) {
        if (codehash == bytes32(0)) revert ZeroArgument("codehash");

        VaultHubStorage storage $ = _getVaultHubStorage();
        if ($.vaultProxyCodehash[codehash]) revert AlreadyExists(codehash);
        $.vaultProxyCodehash[codehash] = true;
        emit VaultProxyCodehashAdded(codehash);
    }

    /// @notice returns the number of vaults connected to the hub
    function vaultsCount() public view returns (uint256) {
        return _getVaultHubStorage().sockets.length - 1;
    }

    /// @param _index index of the vault
    /// @return vault address
    function vault(uint256 _index) public view returns (address) {
        return _getVaultHubStorage().sockets[_index + 1].vault;
    }

    /// @param _index index of the vault
    /// @return vault socket
    function vaultSocket(uint256 _index) external view returns (VaultSocket memory) {
        return _getVaultHubStorage().sockets[_index + 1];
    }

    /// @param _vault vault address
    /// @return vault socket
    function vaultSocket(address _vault) external view returns (VaultSocket memory) {
        VaultHubStorage storage $ = _getVaultHubStorage();
        return $.sockets[$.vaultIndex[_vault]];
    }

    /// @notice returns batch of vaults info
    /// @param _offset offset of the vault in the batch (indexes start from 0)
    /// @param _limit limit of the batch
    /// @return batch of vaults info
    function batchVaultsInfo(uint256 _offset, uint256 _limit) external view returns (VaultInfo[] memory) {
        VaultHubStorage storage $ = _getVaultHubStorage();
        uint256 limit = _offset + _limit > $.sockets.length - 1 ? $.sockets.length - 1 - _offset : _limit;
        VaultInfo[] memory batch = new VaultInfo[](limit);
        for (uint256 i = 0; i < limit; i++) {
            VaultSocket storage socket = $.sockets[i + 1 + _offset];
            IStakingVault currentVault = IStakingVault(socket.vault);
            batch[i] = VaultInfo(
                address(currentVault),
                address(currentVault).balance,
                currentVault.inOutDelta(),
                currentVault.withdrawalCredentials(),
                socket.liabilityShares
            );
        }
        return batch;
    }

    /// @notice checks if the vault is healthy by comparing its total value after applying rebalance threshold
    ///         against current liability shares
    /// @param _vault vault address
    /// @return true if vault is healthy, false otherwise
    function isVaultHealthyAsOfLatestReport(address _vault) public view returns (bool) {
        VaultSocket storage socket = _connectedSocket(_vault);
        return _isVaultHealthyByThreshold(
            IStakingVault(_vault).totalValue(),
            socket.liabilityShares,
            socket.forcedRebalanceThresholdBP
        );
    }

    function _isVaultHealthyByThreshold(
        uint256 _totalValue,
        uint256 _liabilityShares,
        uint256 _checkThreshold
    ) internal view returns (bool) {
        if (_liabilityShares == 0) return true;

        return
            ((_totalValue * (TOTAL_BASIS_POINTS - _checkThreshold)) /
                TOTAL_BASIS_POINTS) >= LIDO.getPooledEthBySharesRoundUp(_liabilityShares);
    }

    /// @notice estimate ether amount to make the vault healthy using rebalance
    /// @param _vault vault address
    /// @return amount to rebalance  or UINT256_MAX if it's impossible to make the vault healthy using rebalance
    function rebalanceShortfall(address _vault) public view returns (uint256) {
        if (_vault == address(0)) revert ZeroArgument("_vault");
        bool isHealthy = isVaultHealthyAsOfLatestReport(_vault);

        // Health vault do not need to rebalance
        if (isHealthy) {
            return 0;
        }

        VaultSocket storage socket = _connectedSocket(_vault);

        uint256 liabilityStETH = LIDO.getPooledEthBySharesRoundUp(socket.liabilityShares);
        uint256 reserveRatioBP = socket.reserveRatioBP;
        uint256 maxMintableRatio = (TOTAL_BASIS_POINTS - reserveRatioBP);
        uint256 totalValue = IStakingVault(_vault).totalValue();

        // Impossible to rebalance a vault with deficit
        if (liabilityStETH >= totalValue) {
            // return MAX_UINT_256
            return type(uint256).max;
        }

        // (liabilityStETH - X) / (vault.totalValue() - X) = maxMintableRatio / TOTAL_BASIS_POINTS
        // (liabilityStETH - X) * TOTAL_BASIS_POINTS = (vault.totalValue() - X) * maxMintableRatio
        // liabilityStETH * TOTAL_BASIS_POINTS - X * TOTAL_BASIS_POINTS = totalValue * maxMintableRatio - X * maxMintableRatio
        // X * maxMintableRatio - X * TOTAL_BASIS_POINTS = totalValue * maxMintableRatio - liabilityStETH * TOTAL_BASIS_POINTS
        // X * (maxMintableRatio - TOTAL_BASIS_POINTS) = vault.totalValue() * maxMintableRatio - liabilityStETH * TOTAL_BASIS_POINTS
        // X = (vault.totalValue() * maxMintableRatio - liabilityStETH * TOTAL_BASIS_POINTS) / (maxMintableRatio - TOTAL_BASIS_POINTS)
        // X = (liabilityStETH * TOTAL_BASIS_POINTS - vault.totalValue() * maxMintableRatio) / (TOTAL_BASIS_POINTS - maxMintableRatio)
        // reserveRatio = TOTAL_BASIS_POINTS - maxMintableRatio
        // X = (liabilityStETH * TOTAL_BASIS_POINTS - totalValue * maxMintableRatio) / reserveRatio

        return (liabilityStETH * TOTAL_BASIS_POINTS - totalValue * maxMintableRatio) / reserveRatioBP;
    }

    /// @notice connects a vault to the hub in permissionless way, get limits from the Operator Grid
    /// @param _vault vault address
    function connectVault(address _vault) external {
        (
        /* address nodeOperator */,
        /* uint256 tierId */,
            uint256 shareLimit,
            uint256 reserveRatioBP,
            uint256 forcedRebalanceThresholdBP,
            uint256 treasuryFeeBP
        ) = OperatorGrid(LIDO_LOCATOR.operatorGrid()).vaultInfo(_vault);
        _connectVault(_vault, shareLimit, reserveRatioBP, forcedRebalanceThresholdBP, treasuryFeeBP);
    }

    /// @notice returns the latest report data
    /// @return timestamp of the report
    /// @return treeRoot of the report
    /// @return reportCid of the report
    function latestReportData() external view returns (
        uint64 timestamp,
        bytes32 treeRoot,
        string memory reportCid
    ) {
        VaultHubStorage storage $ = _getVaultHubStorage();
        return (
            $.vaultsDataTimestamp,
            $.vaultsDataTreeRoot,
            $.vaultsDataReportCid
        );
    }

    /// @notice connects a vault to the hub
    /// @param _vault vault address
    /// @param _shareLimit maximum number of stETH shares that can be minted by the vault
    /// @param _reserveRatioBP minimum reserve ratio in basis points
    /// @param _forcedRebalanceThresholdBP threshold to force rebalance on the vault in basis points
    /// @param _treasuryFeeBP treasury fee in basis points
    function _connectVault(
        address _vault,
        uint256 _shareLimit,
        uint256 _reserveRatioBP,
        uint256 _forcedRebalanceThresholdBP,
        uint256 _treasuryFeeBP
    ) internal {
        if (_reserveRatioBP == 0) revert ZeroArgument("_reserveRatioBP");
        if (_reserveRatioBP > TOTAL_BASIS_POINTS)
            revert ReserveRatioTooHigh(_vault, _reserveRatioBP, TOTAL_BASIS_POINTS);
        if (_forcedRebalanceThresholdBP == 0) revert ZeroArgument("_forcedRebalanceThresholdBP");
        if (_forcedRebalanceThresholdBP > _reserveRatioBP)
            revert ForcedRebalanceThresholdTooHigh(_vault, _forcedRebalanceThresholdBP, _reserveRatioBP);
        if (_treasuryFeeBP > TOTAL_BASIS_POINTS) revert TreasuryFeeTooHigh(_vault, _treasuryFeeBP, TOTAL_BASIS_POINTS);

        IStakingVault vault_ = IStakingVault(_vault);
        if (vault_.ossified()) revert VaultOssified(_vault);
        if (!vault_.vaultHubAuthorized()) revert VaultDeauthorized(_vault);
        _checkShareLimitUpperBound(_vault, _shareLimit);

        VaultHubStorage storage $ = _getVaultHubStorage();
        if ($.vaultIndex[_vault] != 0) revert AlreadyConnected(_vault, $.vaultIndex[_vault]);

        bytes32 vaultProxyCodehash = address(_vault).codehash;
        if (!$.vaultProxyCodehash[vaultProxyCodehash]) revert VaultProxyNotAllowed(_vault);

        if (vault_.depositor() != LIDO_LOCATOR.predepositGuarantee())
            revert VaultDepositorNotAllowed(vault_.depositor());

        if (vault_.locked() < CONNECT_DEPOSIT)
            revert VaultInsufficientLocked(_vault, vault_.locked(), CONNECT_DEPOSIT);
        if (_vault.balance < CONNECT_DEPOSIT)
            revert VaultInsufficientBalance(_vault, _vault.balance, CONNECT_DEPOSIT);

        VaultSocket memory vsocket = VaultSocket(
            _vault,
            0, // liabilityShares
            uint96(_shareLimit),
            uint16(_reserveRatioBP),
            uint16(_forcedRebalanceThresholdBP),
            uint16(_treasuryFeeBP),
            false, // pendingDisconnect
            0
        );
        $.vaultIndex[_vault] = $.sockets.length;
        $.sockets.push(vsocket);

        // here we intentionally prohibit all reports having referenceSlot earlier than the current block;
        vault_.report(uint64(block.timestamp), _vault.balance, vault_.inOutDelta(), vault_.locked());

        emit VaultConnectionSet(_vault, _shareLimit, _reserveRatioBP, _forcedRebalanceThresholdBP, _treasuryFeeBP);
    }

    /// @notice updates share limit for the vault
    /// Setting share limit to zero actually pause the vault's ability to mint
    /// and stops charging fees from the vault
    /// @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) {
        if (_vault == address(0)) revert ZeroArgument("_vault");
        _checkShareLimitUpperBound(_vault, _shareLimit);

        VaultSocket storage socket = _connectedSocket(_vault);

        socket.shareLimit = uint96(_shareLimit);

        emit ShareLimitUpdated(_vault, _shareLimit);
    }

    /// @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 _treasuryFeeBP new treasury fee
    function updateConnection(
        address _vault,
        uint256 _shareLimit,
        uint256 _reserveRatioBP,
        uint256 _forcedRebalanceThresholdBP,
        uint256 _treasuryFeeBP
    ) external {
        if (_vault == address(0)) revert ZeroArgument("_vault");
        _checkShareLimitUpperBound(_vault, _shareLimit);
        if (msg.sender != LIDO_LOCATOR.operatorGrid()) revert NotAuthorized("updateConnection", msg.sender);

        VaultSocket storage socket = _connectedSocket(_vault);

        uint256 totalValue = IStakingVault(_vault).totalValue();
        uint256 liabilityShares = socket.liabilityShares;

        // check healthy with new rebalance threshold
        if (!_isVaultHealthyByThreshold(totalValue, liabilityShares, _reserveRatioBP))
            revert VaultMintingCapacityExceeded(_vault, totalValue, liabilityShares, _reserveRatioBP);

        socket.shareLimit = uint96(_shareLimit);
        socket.reserveRatioBP = uint16(_reserveRatioBP);
        socket.forcedRebalanceThresholdBP = uint16(_forcedRebalanceThresholdBP);
        socket.treasuryFeeBP = uint16(_treasuryFeeBP);

        emit VaultConnectionSet(_vault, _shareLimit, _reserveRatioBP, _forcedRebalanceThresholdBP, _treasuryFeeBP);
    }

    function updateReportData(
        uint64 _vaultsDataTimestamp,
        bytes32 _vaultsDataTreeRoot,
        string memory _vaultsDataReportCid
    ) external {
        if (msg.sender != LIDO_LOCATOR.accounting()) revert NotAuthorized("updateReportData", msg.sender);

        VaultHubStorage storage $ = _getVaultHubStorage();
        $.vaultsDataTimestamp = _vaultsDataTimestamp;
        $.vaultsDataTreeRoot = _vaultsDataTreeRoot;
        $.vaultsDataReportCid = _vaultsDataReportCid;
        emit VaultsReportDataUpdated(_vaultsDataTimestamp, _vaultsDataTreeRoot, _vaultsDataReportCid);
    }

    /// @notice force disconnects 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) {
        if (_vault == address(0)) revert ZeroArgument("_vault");

        _disconnect(_vault);
    }

    /// @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 {
        if (_vault == address(0)) revert ZeroArgument("_vault");
        _vaultAuth(_vault, "disconnect");

        _disconnect(_vault);
    }

    /// @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
    /// @dev msg.sender should be vault's owner
    function mintShares(address _vault, address _recipient, uint256 _amountOfShares) external whenResumed {
        if (_vault == address(0)) revert ZeroArgument("_vault");
        if (_recipient == address(0)) revert ZeroArgument("_recipient");
        if (_amountOfShares == 0) revert ZeroArgument("_amountOfShares");

        _vaultAuth(_vault, "mint");

        VaultSocket storage socket = _connectedSocket(_vault);

        uint256 vaultSharesAfterMint = socket.liabilityShares + _amountOfShares;
        uint256 shareLimit = socket.shareLimit;
        if (vaultSharesAfterMint > shareLimit) revert ShareLimitExceeded(_vault, shareLimit);

        IStakingVault vault_ = IStakingVault(_vault);
        if (!vault_.isReportFresh()) revert VaultReportStaled(_vault);

        uint256 maxMintableRatioBP = TOTAL_BASIS_POINTS - socket.reserveRatioBP;
        uint256 maxMintableEther = (vault_.totalValue() * maxMintableRatioBP) / TOTAL_BASIS_POINTS;
        uint256 stETHAfterMint = LIDO.getPooledEthBySharesRoundUp(vaultSharesAfterMint);
        if (stETHAfterMint > maxMintableEther) {
            revert InsufficientTotalValueToMint(_vault, vault_.totalValue());
        }

        // Calculate the minimum ETH that needs to be locked in the vault to maintain the reserve ratio
        uint256 minLocked = (stETHAfterMint * TOTAL_BASIS_POINTS) / maxMintableRatioBP;

        if (minLocked > vault_.locked()) {
            revert VaultInsufficientLocked(_vault, vault_.locked(), minLocked);
        }

        socket.liabilityShares = uint96(vaultSharesAfterMint);
        LIDO.mintExternalShares(_recipient, _amountOfShares);
        OperatorGrid(LIDO_LOCATOR.operatorGrid()).onMintedShares(_vault, _amountOfShares);

        emit MintedSharesOnVault(_vault, _amountOfShares);
    }

    /// @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 VaultHub must have all the stETH on its balance
    function burnShares(address _vault, uint256 _amountOfShares) public whenResumed {
        if (_vault == address(0)) revert ZeroArgument("_vault");
        if (_amountOfShares == 0) revert ZeroArgument("_amountOfShares");
        _vaultAuth(_vault, "burn");

        VaultSocket storage socket = _connectedSocket(_vault);

        uint256 liabilityShares = socket.liabilityShares;
        if (liabilityShares < _amountOfShares) revert InsufficientSharesToBurn(_vault, liabilityShares);

        socket.liabilityShares = uint96(liabilityShares - _amountOfShares);

        LIDO.burnExternalShares(_amountOfShares);
        OperatorGrid(LIDO_LOCATOR.operatorGrid()).onBurnedShares(_vault, _amountOfShares);

        emit BurnedSharesOnVault(_vault, _amountOfShares);
    }

    /// @notice separate burn function for EOA vault owners; requires vaultHub to be approved to transfer stETH
    /// @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 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 {
        if (_vault == address(0)) revert ZeroArgument("_vault");

        uint256 maxAmountToRebalance = rebalanceShortfall(_vault);
        if (maxAmountToRebalance == 0) revert AlreadyHealthy(_vault);
        uint256 amountToRebalance = Math256.min(maxAmountToRebalance, _vault.balance);

        // TODO: add some gas compensation here
        IStakingVault(_vault).rebalance(amountToRebalance);
    }

    /// @notice rebalances the vault by writing off the amount of ether equal
    ///     to `msg.value` from the vault's liability stETH
    /// @dev msg.sender should be vault's contract
    function rebalance() external payable whenResumed {
        if (msg.value == 0) revert ZeroArgument("msg.value");

        VaultSocket storage socket = _connectedSocket(msg.sender);

        uint256 sharesToBurn = LIDO.getSharesByPooledEth(msg.value);
        uint256 liabilityShares = socket.liabilityShares;
        if (liabilityShares < sharesToBurn) revert InsufficientSharesToBurn(msg.sender, liabilityShares);

        socket.liabilityShares = uint96(liabilityShares - sharesToBurn);

        LIDO.rebalanceExternalEtherToInternal{value: msg.value}();

        emit VaultRebalanced(msg.sender, sharesToBurn);
    }

    /// @notice Forces validator exit from the beacon chain when vault is unhealthy
    /// @param _vault The address of the vault to exit validators from
    /// @param _pubkeys The public keys of the validators to exit
    /// @param _refundRecipient The address that will receive the refund for transaction costs
    /// @dev    When the vault becomes unhealthy, anyone 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 {
        if (msg.value == 0) revert ZeroArgument("msg.value");
        if (_vault == address(0)) revert ZeroArgument("_vault");
        if (_pubkeys.length == 0) revert ZeroArgument("_pubkeys");
        if (_refundRecipient == address(0)) revert ZeroArgument("_refundRecipient");
        if (_pubkeys.length % PUBLIC_KEY_LENGTH != 0) revert InvalidPubkeysLength();
        if (isVaultHealthyAsOfLatestReport(_vault)) revert AlreadyHealthy(_vault);

        uint256 numValidators = _pubkeys.length / PUBLIC_KEY_LENGTH;
        uint64[] memory amounts = new uint64[](numValidators);

        IStakingVault(_vault).triggerValidatorWithdrawal{value: msg.value}(_pubkeys, amounts, _refundRecipient);

        emit ForcedValidatorExitTriggered(_vault, _pubkeys, _refundRecipient);
    }

    function _disconnect(address _vault) internal {
        VaultSocket storage socket = _connectedSocket(_vault);

        uint256 liabilityShares = socket.liabilityShares;
        if (liabilityShares > 0) {
            revert NoLiabilitySharesShouldBeLeft(_vault, liabilityShares);
        }

        socket.pendingDisconnect = true;

        emit VaultDisconnected(_vault);
    }

    /// @notice Permissionless update of the vault data
    /// @param _vault the address of the vault
    /// @param _totalValue the total value of the vault
    /// @param _inOutDelta the inOutDelta of the vault
    /// @param _feeSharesCharged the feeSharesCharged of the vault
    /// @param _liabilityShares the liabilityShares of the vault
    /// @param _proof the proof of the reported data
    function updateVaultData(
        address _vault,
        uint256 _totalValue,
        int256 _inOutDelta,
        uint256 _feeSharesCharged,
        uint256 _liabilityShares,
        bytes32[] calldata _proof
    ) external {
        VaultHubStorage storage $ = _getVaultHubStorage();
        uint256 vaultIndex = $.vaultIndex[_vault];
        if (vaultIndex == 0) revert NotConnectedToHub(_vault);

        bytes32 root = $.vaultsDataTreeRoot;
        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(_vault, _totalValue, _inOutDelta, _feeSharesCharged, _liabilityShares))));
        if (!MerkleProof.verify(_proof, root, leaf)) revert InvalidProof();

        VaultSocket storage socket = $.sockets[vaultIndex];
        // NB: charged fees can only cumulatively increase with time
        if (_feeSharesCharged  < socket.feeSharesCharged) {
            revert InvalidFees(_vault, _feeSharesCharged, socket.feeSharesCharged);
        }
        socket.liabilityShares += uint96(_feeSharesCharged - socket.feeSharesCharged);
        socket.feeSharesCharged = uint96(_feeSharesCharged);

        uint256 newLiabilityShares = Math256.max(socket.liabilityShares, _liabilityShares);
        // locked ether can only be increased asynchronously once the oracle settled the new floor value
        // as of reference slot to prevent slashing upsides in between the report gathering and delivering
        uint256 lockedEther = Math256.max(
            LIDO.getPooledEthBySharesRoundUp(newLiabilityShares) * TOTAL_BASIS_POINTS / (TOTAL_BASIS_POINTS - socket.reserveRatioBP),
            socket.pendingDisconnect ? 0 : CONNECT_DEPOSIT
        );

        IStakingVault(socket.vault).report($.vaultsDataTimestamp, _totalValue, _inOutDelta, lockedEther);

        uint256 length = $.sockets.length;
        if (socket.pendingDisconnect) {
            // remove disconnected vault from the list
            address vaultAddress = socket.vault;
            VaultSocket memory lastSocket = $.sockets[length - 1];
            $.sockets[vaultIndex] = lastSocket;
            $.vaultIndex[lastSocket.vault] = vaultIndex;
            $.sockets.pop();
            delete $.vaultIndex[vaultAddress];
        }
    }

    function mintVaultsTreasuryFeeShares(uint256 _amountOfShares) external {
        if (msg.sender != LIDO_LOCATOR.accounting()) revert NotAuthorized("mintVaultsTreasuryFeeShares", msg.sender);
        LIDO.mintExternalShares(LIDO_LOCATOR.treasury(), _amountOfShares);
    }

    function _vaultAuth(address _vault, string memory _operation) internal view {
        if (msg.sender != OwnableUpgradeable(_vault).owner()) revert NotAuthorized(_operation, msg.sender);
    }

    function _connectedSocket(address _vault) internal view returns (VaultSocket storage) {
        VaultHubStorage storage $ = _getVaultHubStorage();
        uint256 index = $.vaultIndex[_vault];
        if (index == 0 || $.sockets[index].pendingDisconnect) revert NotConnectedToHub(_vault);
        return $.sockets[index];
    }

    function _getVaultHubStorage() private pure returns (VaultHubStorage storage $) {
        assembly {
            $.slot := VAULT_HUB_STORAGE_LOCATION
        }
    }

    /// @dev check if the share limit is within the upper bound set by RELATIVE_SHARE_LIMIT_BP
    function _checkShareLimitUpperBound(address _vault, uint256 _shareLimit) internal view {
        uint256 relativeMaxShareLimitPerVault = (LIDO.getTotalShares() * RELATIVE_SHARE_LIMIT_BP) / TOTAL_BASIS_POINTS;
        if (_shareLimit > relativeMaxShareLimitPerVault) {
            revert ShareLimitTooHigh(_vault, _shareLimit, relativeMaxShareLimitPerVault);
        }
    }

    event VaultConnectionSet(
        address indexed vault,
        uint256 shareLimit,
        uint256 reserveRatioBP,
        uint256 forcedRebalanceThresholdBP,
        uint256 treasuryFeeBP
    );

    event VaultsReportDataUpdated(uint64 indexed timestamp, bytes32 root, string cid);
    event ShareLimitUpdated(address indexed vault, uint256 newShareLimit);
    event VaultDisconnected(address indexed vault);
    event MintedSharesOnVault(address indexed vault, uint256 amountOfShares);
    event BurnedSharesOnVault(address indexed vault, uint256 amountOfShares);
    event VaultRebalanced(address indexed vault, uint256 sharesBurned);
    event VaultProxyCodehashAdded(bytes32 indexed codehash);
    event ForcedValidatorExitTriggered(address indexed vault, bytes pubkeys, address refundRecipient);

    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 shareLimit);
    error AlreadyConnected(address vault, uint256 index);
    error NotConnectedToHub(address vault);
    error NotAuthorized(string operation, address addr);
    error ZeroArgument(string argument);
    error ShareLimitTooHigh(address vault, uint256 shareLimit, uint256 maxShareLimit);
    error ReserveRatioTooHigh(address vault, uint256 reserveRatioBP, uint256 maxReserveRatioBP);
    error ForcedRebalanceThresholdTooHigh(address vault, uint256 forcedRebalanceThresholdBP, uint256 maxForcedRebalanceThresholdBP);
    error TreasuryFeeTooHigh(address vault, uint256 treasuryFeeBP, uint256 maxTreasuryFeeBP);
    error InsufficientTotalValueToMint(address vault, uint256 totalValue);
    error AlreadyExists(bytes32 codehash);
    error NoLiabilitySharesShouldBeLeft(address vault, uint256 liabilityShares);
    error VaultProxyNotAllowed(address beacon);
    error InvalidPubkeysLength();
    error RelativeShareLimitBPTooHigh(uint256 relativeShareLimitBP, uint256 totalBasisPoints);
    error VaultDepositorNotAllowed(address depositor);
    error InvalidProof();
    error InvalidFees(address vault, uint256 newFees, uint256 oldFees);
    error VaultInsufficientLocked(address vault, uint256 currentLocked, uint256 expectedLocked);
    error VaultOssified(address vault);
    error VaultInsufficientBalance(address vault, uint256 currentBalance, uint256 expectedBalance);
    error VaultReportStaled(address vault);
    error VaultDeauthorized(address vault);
}

// SPDX-FileCopyrightText: 2023 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

// See contracts/COMPILERS.md
// solhint-disable-next-line lido/fixed-compiler-version
pragma solidity >=0.4.24 <0.9.0;

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 accounting() external view returns (address);
    function predepositGuarantee() external view returns (address);
    function wstETH() external view returns (address);
    function vaultHub() 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: 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: 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.9 <0.9.0;

/**
 * @title A lib for EIP-7002: Execution layer triggerable withdrawals.
 * Allow validators to trigger withdrawals and exits from their execution layer (0x01) withdrawal credentials.
 */
library TriggerableWithdrawals {
    address constant WITHDRAWAL_REQUEST = 0x00000961Ef480Eb55e80D19ad83579A64c007002;

    uint256 internal constant PUBLIC_KEY_LENGTH = 48;
    uint256 internal constant WITHDRAWAL_AMOUNT_LENGTH = 8;
    uint256 internal constant WITHDRAWAL_REQUEST_CALLDATA_LENGTH = 56;

    error WithdrawalFeeReadFailed();
    error WithdrawalFeeInvalidData();
    error WithdrawalRequestAdditionFailed(bytes callData);

    error NoWithdrawalRequests();
    error MalformedPubkeysArray();
    error PartialWithdrawalRequired(uint256 index);
    error MismatchedArrayLengths(uint256 keysCount, uint256 amountsCount);

    /**
     * @dev Send EIP-7002 full withdrawal requests for the specified public keys.
     *      Each request instructs a validator to fully withdraw its stake and exit its duties as a validator.
     *
     * @param pubkeys A tightly packed array of 48-byte public keys corresponding to validators requesting full withdrawals.
     *      | ----- public key (48 bytes) ----- || ----- public key (48 bytes) ----- | ...
     *
     * @param feePerRequest The withdrawal fee for each withdrawal request.
     *        - Must be greater than or equal to the current minimal withdrawal fee.
     *
     * @notice Reverts if:
     *         - Validation of the public keys fails.
     *         - The provided fee per request is insufficient.
     *         - The contract has an insufficient balance to cover the total fees.
     */
    function addFullWithdrawalRequests(bytes calldata pubkeys, uint256 feePerRequest) internal {
        uint256 keysCount = _validateAndCountPubkeys(pubkeys);

        bytes memory callData = new bytes(56);

        for (uint256 i = 0; i < keysCount; i++) {
            _copyPubkeyToMemory(pubkeys, callData, i);

            (bool success, ) = WITHDRAWAL_REQUEST.call{value: feePerRequest}(callData);

            if (!success) {
                revert WithdrawalRequestAdditionFailed(callData);
            }
        }
    }

    /**
     * @dev Send EIP-7002 partial withdrawal requests for the specified public keys with corresponding amounts.
     *      Each request instructs a validator to partially withdraw its stake.
     *      A partial withdrawal is any withdrawal where the amount is greater than zero,
     *      allows withdrawal of any balance exceeding 32 ETH (e.g., if a validator has 35 ETH, up to 3 ETH can be withdrawn),
     *      the protocol enforces a minimum balance of 32 ETH per validator, even if a higher amount is requested.
     *
     * @param pubkeys A tightly packed array of 48-byte public keys corresponding to validators requesting full withdrawals.
     *      | ----- public key (48 bytes) ----- || ----- public key (48 bytes) ----- | ...
     *
     * @param amounts An array of corresponding partial withdrawal amounts for each public key.
     *
     * @param feePerRequest The withdrawal fee for each withdrawal request.
     *        - Must be greater than or equal to the current minimal withdrawal fee.
     *
     * @notice Reverts if:
     *         - Validation of the public keys fails.
     *         - The pubkeys and amounts length mismatch.
     *         - Full withdrawal requested for any pubkeys (withdrawal amount = 0).
     *         - The provided fee per request is insufficient.
     *         - The contract has an insufficient balance to cover the total fees.
     */
    function addPartialWithdrawalRequests(
        bytes calldata pubkeys,
        uint64[] calldata amounts,
        uint256 feePerRequest
    ) internal {
        for (uint256 i = 0; i < amounts.length; i++) {
            if (amounts[i] == 0) {
                revert PartialWithdrawalRequired(i);
            }
        }

        addWithdrawalRequests(pubkeys, amounts, feePerRequest);
    }

    /**
     * @dev Send EIP-7002 partial or full withdrawal requests for the specified public keys with corresponding amounts.
     *      Each request instructs a validator to partially or fully withdraw its stake.

     *      1. A partial withdrawal is any withdrawal where the amount is greater than zero,
     *      allows withdrawal of any balance exceeding 32 ETH (e.g., if a validator has 35 ETH, up to 3 ETH can be withdrawn),
     *      the protocol enforces a minimum balance of 32 ETH per validator, even if a higher amount is requested.
     *
     *      2. A full withdrawal is a withdrawal where the amount is equal to zero,
     *      allows to fully withdraw validator stake and exit its duties as a validator.
     *
     * @param pubkeys A tightly packed array of 48-byte public keys corresponding to validators requesting full withdrawals.
     *      | ----- public key (48 bytes) ----- || ----- public key (48 bytes) ----- | ...
     *
     * @param amounts An array of corresponding partial withdrawal amounts for each public key.
     *
     * @param feePerRequest The withdrawal fee for each withdrawal request.
     *        - Must be greater than or equal to the current minimal withdrawal fee.
     *
     * @notice Reverts if:
     *         - Validation of the public keys fails.
     *         - The pubkeys and amounts length mismatch.
     *         - The provided fee per request is insufficient.
     *         - The contract has an insufficient balance to cover the total fees.
     */
    function addWithdrawalRequests(bytes calldata pubkeys, uint64[] calldata amounts, uint256 feePerRequest) internal {
        uint256 keysCount = _validateAndCountPubkeys(pubkeys);

        if (keysCount != amounts.length) {
            revert MismatchedArrayLengths(keysCount, amounts.length);
        }

        bytes memory callData = new bytes(56);
        for (uint256 i = 0; i < keysCount; i++) {
            _copyPubkeyToMemory(pubkeys, callData, i);
            _copyAmountToMemory(callData, amounts[i]);

            (bool success, ) = WITHDRAWAL_REQUEST.call{value: feePerRequest}(callData);

            if (!success) {
                revert WithdrawalRequestAdditionFailed(callData);
            }
        }
    }

    /**
     * @dev Retrieves the current EIP-7002 withdrawal fee.
     * @return The minimum fee required per withdrawal request.
     */
    function getWithdrawalRequestFee() internal view returns (uint256) {
        (bool success, bytes memory feeData) = WITHDRAWAL_REQUEST.staticcall("");

        if (!success) {
            revert WithdrawalFeeReadFailed();
        }

        if (feeData.length != 32) {
            revert WithdrawalFeeInvalidData();
        }

        return abi.decode(feeData, (uint256));
    }

    function _copyPubkeyToMemory(bytes calldata pubkeys, bytes memory target, uint256 keyIndex) private pure {
        assembly {
            calldatacopy(add(target, 32), add(pubkeys.offset, mul(keyIndex, PUBLIC_KEY_LENGTH)), PUBLIC_KEY_LENGTH)
        }
    }

    function _copyAmountToMemory(bytes memory target, uint64 amount) private pure {
        assembly {
            mstore(add(target, 80), shl(192, amount))
        }
    }

    function _validateAndCountPubkeys(bytes calldata pubkeys) private pure returns (uint256) {
        if (pubkeys.length % PUBLIC_KEY_LENGTH != 0) {
            revert MalformedPubkeysArray();
        }

        uint256 keysCount = pubkeys.length / PUBLIC_KEY_LENGTH;
        if (keysCount == 0) {
            revert NoWithdrawalRequests();
        }

        return keysCount;
    }
}

// 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) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Ownable
    struct OwnableStorage {
        address _owner;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant OwnableStorageLocation =
        0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;

    function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
        assembly {
            $.slot := OwnableStorageLocation
        }
    }

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    function __Ownable_init(address initialOwner) internal onlyInitializing {
        __Ownable_init_unchained(initialOwner);
    }

    function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        OwnableStorage storage $ = _getOwnableStorage();
        return $._owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        OwnableStorage storage $ = _getOwnableStorage();
        address oldOwner = $._owner;
        $._owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// 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;
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "evmVersion": "cancun",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_vaultHub","type":"address"},{"internalType":"address","name":"_beaconChainDepositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyOssified","type":"error"},{"inputs":[],"name":"BeaconChainDepositsArePaused","type":"error"},{"inputs":[],"name":"BeaconChainDepositsPauseExpected","type":"error"},{"inputs":[],"name":"BeaconChainDepositsResumeExpected","type":"error"},{"inputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"name":"InsufficientBalance","type":"error"},{"inputs":[{"internalType":"uint256","name":"unlocked","type":"uint256"}],"name":"InsufficientUnlocked","type":"error"},{"inputs":[{"internalType":"uint256","name":"_passed","type":"uint256"},{"internalType":"uint256","name":"_required","type":"uint256"}],"name":"InsufficientValidatorWithdrawalFee","type":"error"},{"inputs":[{"internalType":"address","name":"depositor","type":"address"}],"name":"InvalidDepositor","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"InvalidPubkeysLength","type":"error"},{"inputs":[],"name":"MalformedPubkeysArray","type":"error"},{"inputs":[{"internalType":"uint256","name":"keysCount","type":"uint256"},{"internalType":"uint256","name":"amountsCount","type":"uint256"}],"name":"MismatchedArrayLengths","type":"error"},{"inputs":[],"name":"NewLockedExceedsTotalValue","type":"error"},{"inputs":[],"name":"NewLockedNotGreaterThanCurrent","type":"error"},{"inputs":[],"name":"NoWithdrawalRequests","type":"error"},{"inputs":[{"internalType":"string","name":"operation","type":"string"},{"internalType":"address","name":"sender","type":"address"}],"name":"NotAuthorized","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"PartialWithdrawalNotAllowed","type":"error"},{"inputs":[{"internalType":"uint256","name":"totalValue","type":"uint256"},{"internalType":"uint256","name":"rebalanceAmount","type":"uint256"}],"name":"RebalanceAmountExceedsTotalValue","type":"error"},{"inputs":[],"name":"ReportStaled","type":"error"},{"inputs":[{"internalType":"uint64","name":"currentTimestamp","type":"uint64"},{"internalType":"uint64","name":"newTimestamp","type":"uint64"}],"name":"ReportTooOld","type":"error"},{"inputs":[],"name":"TotalValueBelowLockedAmount","type":"error"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TransferFailed","type":"error"},{"inputs":[],"name":"VaultConnected","type":"error"},{"inputs":[],"name":"VaultHubAuthorized","type":"error"},{"inputs":[],"name":"VaultHubNotAuthorized","type":"error"},{"inputs":[],"name":"VaultOssified","type":"error"},{"inputs":[],"name":"WithdrawalFeeInvalidData","type":"error"},{"inputs":[],"name":"WithdrawalFeeReadFailed","type":"error"},{"inputs":[{"internalType":"address","name":"_sender","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"WithdrawalFeeRefundFailed","type":"error"},{"inputs":[{"internalType":"bytes","name":"callData","type":"bytes"}],"name":"WithdrawalRequestAdditionFailed","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"ZeroArgument","type":"error"},{"anonymous":false,"inputs":[],"name":"BeaconChainDepositsPaused","type":"event"},{"anonymous":false,"inputs":[],"name":"BeaconChainDepositsResumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"_deposits","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_totalAmount","type":"uint256"}],"name":"DepositedToBeaconChain","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"}],"name":"DepositorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Funded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"locked","type":"uint256"}],"name":"LockedIncreased","type":"event"},{"anonymous":false,"inputs":[],"name":"LockedReset","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nodeOperator","type":"address"}],"name":"NodeOperatorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"PinnedImplementationUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint64","name":"timestamp","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"totalValue","type":"uint256"},{"indexed":false,"internalType":"int256","name":"inOutDelta","type":"int256"},{"indexed":false,"internalType":"uint256","name":"locked","type":"uint256"}],"name":"Reported","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_sender","type":"address"},{"indexed":true,"internalType":"bytes","name":"_pubkey","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"_pubkeyRaw","type":"bytes"}],"name":"ValidatorExitRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_sender","type":"address"},{"indexed":false,"internalType":"bytes","name":"_pubkeys","type":"bytes"},{"indexed":false,"internalType":"uint64[]","name":"_amounts","type":"uint64[]"},{"indexed":false,"internalType":"address","name":"_refundRecipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"_excess","type":"uint256"}],"name":"ValidatorWithdrawalTriggered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"authorized","type":"bool"}],"name":"VaultHubAuthorizedSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawn","type":"event"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PUBLIC_KEY_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"authorizeLidoVaultHub","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"beaconChainDepositsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_numberOfKeys","type":"uint256"}],"name":"calculateValidatorWithdrawalFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deauthorizeLidoVaultHub","outputs":[],"stateMutability":"nonpayable","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 StakingVaultDeposit[]","name":"_deposits","type":"tuple[]"}],"name":"depositToBeaconChain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fund","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"getInitializedVersion","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"inOutDelta","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_nodeOperator","type":"address"},{"internalType":"address","name":"_depositor","type":"address"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isReportFresh","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestReport","outputs":[{"components":[{"internalType":"uint128","name":"totalValue","type":"uint128"},{"internalType":"int128","name":"inOutDelta","type":"int128"},{"internalType":"uint64","name":"timestamp","type":"uint64"}],"internalType":"struct IStakingVault.Report","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_locked","type":"uint256"}],"name":"lock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"locked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nodeOperator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ossified","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ossifyStakingVault","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pauseBeaconChainDeposits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ether","type":"uint256"}],"name":"rebalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"_timestamp","type":"uint64"},{"internalType":"uint256","name":"_totalValue","type":"uint256"},{"internalType":"int256","name":"_inOutDelta","type":"int256"},{"internalType":"uint256","name":"_locked","type":"uint256"}],"name":"report","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_pubkeys","type":"bytes"}],"name":"requestValidatorExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resetLocked","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resumeBeaconChainDeposits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_depositor","type":"address"}],"name":"setDepositor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","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":"triggerValidatorWithdrawal","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"unlocked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vaultHub","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vaultHubAuthorized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_ether","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawalCredentials","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]

60c060405234801561000f575f80fd5b50604051612f4e380380612f4e83398101604081905261002e916101bc565b6001600160a01b038216610076576040516356e4289360e01b81526020600482015260096024820152682fbb30bab63a243ab160b91b60448201526064015b60405180910390fd5b6001600160a01b0381166100cd576040516356e4289360e01b815260206004820152601b60248201527f5f626561636f6e436861696e4465706f736974436f6e74726163740000000000604482015260640161006d565b6001600160a01b03808316608052811660a0526100e86100ef565b50506101ed565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161561013f5760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b039081161461019e5780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b80516001600160a01b03811681146101b7575f80fd5b919050565b5f80604083850312156101cd575f80fd5b6101d6836101a1565b91506101e4602084016101a1565b90509250929050565b60805160a051612d0161024d5f395f818161043101526111a801525f818161046101528181610b9301528181610e1a01528181610f9c015281816113590152818161152a01528181611f0701528181611fce015261203b0152612d015ff3fe608060405260043610610215575f3560e01c80638da5cb5b1161011e578063bf15af56116100a8578063f2c098b71161006d578063f2c098b7146105c4578063f2fde38b146105e3578063f3fef3a314610602578063f499301814610621578063f88cc1d314610640575f80fd5b8063bf15af5614610555578063c7c4ff4614610569578063cf3090121461057d578063d4c3eea014610591578063dd467064146105a5575f80fd5b80639f223e6f116100ee5780639f223e6f146104fd578063aa9f2d6a14610511578063b3c6501514610525578063b60d428814610539578063bddf300c14610541575f80fd5b80638da5cb5b146104ad57806392e03036146104c157806395631105146104d55780639a23d851146104e9575f80fd5b80634cd79e0a1161019f5780636af6eeed1161016f5780636af6eeed146103d55780636b96736b146104205780636dd6e80b1461045357806370ba285714610485578063715018a614610499575f80fd5b80634cd79e0a1461036357806354fd4d501461037b57806362b2f4b0146103a25780636a5e2650146103c1575f80fd5b80632cbf918f116101e55780632cbf918f146102cc57806333ac88b4146102f95780634123bc611461030c57806343467d6e14610330578063479bacfc1461034f575f80fd5b8063023c5b0814610249578063107415521461027a5780631e73cad314610299578063246581f7146102ad575f80fd5b3661024557345f03610243576040516356e4289360e01b815260040161023a906125b5565b60405180910390fd5b005b5f80fd5b348015610254575f80fd5b5061025d610654565b6040516001600160a01b0390911681526020015b60405180910390f35b348015610285575f80fd5b5061024361029436600461261c565b61066f565b3480156102a4575f80fd5b506102436107bc565b3480156102b8575f80fd5b506102436102c736600461266e565b610808565b3480156102d7575f80fd5b506102eb6102e63660046126e4565b610a1a565b604051908152602001610271565b61024361030736600461273b565b610a73565b348015610317575f80fd5b50610320610de8565b6040519015158152602001610271565b34801561033b575f80fd5b5061024361034a3660046127d3565b610e04565b34801561035a575f80fd5b50610320610f72565b34801561036e575f80fd5b5030600160f91b176102eb565b348015610386575f80fd5b5060015b6040516001600160401b039091168152602001610271565b3480156103ad575f80fd5b506102436103bc366004612809565b611039565b3480156103cc575f80fd5b506102eb611292565b3480156103e0575f80fd5b506103e96112d7565b6040805182516001600160801b03168152602080840151600f0b90820152918101516001600160401b031690820152606001610271565b34801561042b575f80fd5b5061025d7f000000000000000000000000000000000000000000000000000000000000000081565b34801561045e575f80fd5b507f000000000000000000000000000000000000000000000000000000000000000061025d565b348015610490575f80fd5b5061024361133a565b3480156104a4575f80fd5b50610243611476565b3480156104b8575f80fd5b5061025d611489565b3480156104cc575f80fd5b506102436114c0565b3480156104e0575f80fd5b50610243611694565b3480156104f4575f80fd5b5061032061170e565b348015610508575f80fd5b506102eb61172a565b34801561051c575f80fd5b50610243611746565b348015610530575f80fd5b5061038a6117dc565b61024361180e565b34801561054c575f80fd5b506102436118cf565b348015610560575f80fd5b506102eb603081565b348015610574575f80fd5b5061025d61194f565b348015610588575f80fd5b506102eb61196a565b34801561059c575f80fd5b506102eb611985565b3480156105b0575f80fd5b506102436105bf3660046126e4565b6119d2565b3480156105cf575f80fd5b506102436105de36600461283b565b611abb565b3480156105ee575f80fd5b506102436105fd36600461283b565b611b8c565b34801561060d575f80fd5b5061024361061c366004612856565b611bc6565b34801561062c575f80fd5b5061024361063b3660046126e4565b611e2e565b34801561064b575f80fd5b506103206120af565b5f61065d6120b8565b600301546001600160a01b0316919050565b6106776120dc565b5f8190036106b3576040516356e4289360e01b81526020600482015260086024820152675f7075626b65797360c01b604482015260640161023a565b6106be603082612894565b156106dc576040516349b7ed0560e11b815260040160405180910390fd5b5f6106e86030836128bb565b90505f5b818110156107b6575f84846107026030856128ce565b9060306107108660016128e5565b61071a91906128ce565b92610727939291906128f8565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505060405192935061076c92849250905061291f565b60405180910390207f67d6925387aada877c003f8a473d2e08d2bcc0bf8b8322d9c17f8a47213d38d533836040516107a5929190612963565b60405180910390a2506001016106ec565b50505050565b6107c46120dc565b5f6107cd6120b8565b6004810154909150600160a01b900460ff16156107fd5760405163c25e38af60e01b815260040160405180910390fd5b61080561210e565b50565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff1615906001600160401b03165f8115801561084c5750825b90505f826001600160401b031660011480156108675750303b155b905081158015610875575080155b156108935760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156108bd57845460ff60401b1916600160401b1785555b6001600160a01b038916610904576040516356e4289360e01b815260206004820152600d60248201526c2fb737b232a7b832b930ba37b960991b604482015260640161023a565b61090d8a612209565b5f6109166120b8565b6003810180546001600160a01b0319166001600160a01b038d811691909117909155909150891615610948578861094a565b895b6004820180546001600160a01b0319166001600160a01b03928316179055604051908b16907f57d209fa057c282ad4b54dc6ad1a002b01d20bf16299f62ff8040943a692b8d3905f90a26040516001600160a01b038a16907f206352915439eaf303e567806f90699d60d2447047d948300ae179651ff42c05905f90a2508315610a0e57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050505050565b5f815f03610a5b576040516356e4289360e01b815260206004820152600d60248201526c5f6e756d6265724f664b65797360981b604482015260640161023a565b610a6361221a565b610a6d90836128ce565b92915050565b345f03610a93576040516356e4289360e01b815260040161023a906125b5565b5f849003610acf576040516356e4289360e01b81526020600482015260086024820152675f7075626b65797360c01b604482015260640161023a565b5f829003610b0b576040516356e4289360e01b81526020600482015260086024820152675f616d6f756e747360c01b604482015260640161023a565b6001600160a01b038116610b1c5750335b5f610b256120b8565b60038101549091505f906001600160a01b0316331480610b5d5750610b48611489565b6001600160a01b0316336001600160a01b0316145b60028301549091505f906001600160801b0316610b78611985565b1090508015610bce578180610bcb5750336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148015610bcb57506004830154600160a01b900460ff165b91505b81610c2457604080516373e3652560e01b81526004810191909152601a60448201527f7472696767657256616c696461746f725769746864726177616c000000000000606482015233602482015260840161023a565b8080610c355750610c33610f72565b155b15610c9e575f5b85811015610c9c575f878783818110610c5757610c5761298e565b9050602002016020810190610c6c91906129a2565b6001600160401b03161115610c945760405163dd5b277960e01b815260040160405180910390fd5b600101610c3c565b505b5f610ca761221a565b90505f81610cb660308b6128bb565b610cc091906128ce565b905080341015610cec5760405163e546602560e01b81523460048201526024810182905260440161023a565b610cf98a8a8a8a866122c9565b5f610d0482346129bb565b90508015610d90575f876001600160a01b0316826040515f6040518083038185875af1925050503d805f8114610d55576040519150601f19603f3d011682016040523d82523d5f602084013e610d5a565b606091505b5050905080610d8e5760405163112bec8b60e01b81526001600160a01b03891660048201526024810183905260440161023a565b505b336001600160a01b03167f085bc7631f3dac6c30fec9bf152fd7c53ea7fbeadc4288bf963291d843a159248c8c8c8c8c87604051610dd3969594939291906129f6565b60405180910390a25050505050505050505050565b5f610df16120b8565b60040154600160a01b900460ff16919050565b5f610e0d6120b8565b9050336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016141580610e5357506004810154600160a01b900460ff16155b15610e9257604080516373e3652560e01b8152600481019190915260066044820152651c995c1bdc9d60d21b606482015233602482015260840161023a565b60018101546001600160401b039081169086168110610ed757604051633fb579ad60e01b81526001600160401b0380831660048301528716602482015260440161023a565b60018201805467ffffffffffffffff19166001600160401b0388169081179091556001600160801b03868116600160801b878316021784556002840180546001600160801b03191691861691909117905560408051878152602081018790529081018590527f70b5f9716f6485b2e0a05a3c96591041bea2f66184fb5e48c930985be06a9c8c906060015b60405180910390a2505050505050565b5f80610f7c6120b8565b6004810154909150600160a01b900460ff16610f9a57600191505090565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663625c14f26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ff6573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061101a9190612a6b565b6001820154611032906001600160401b0316426129bb565b1091505090565b5f819003611076576040516356e4289360e01b81526020600482015260096024820152685f6465706f7369747360b81b604482015260640161023a565b5f61107f6120b8565b6004810154909150600160a81b900460ff16156110af5760405163fac4552f60e01b815260040160405180910390fd5b60048101546001600160a01b0316331461110b57604080516373e3652560e01b8152600481019190915260146044820152733232b837b9b4ba2a37a132b0b1b7b721b430b4b760611b606482015233602482015260840161023a565b60028101546001600160801b0316611121611985565b1015611140576040516339231d1560e21b815260040160405180910390fd5b815f8030600160f91b1760405160200161115c91815260200190565b60405160208183030381529060405290505f5b83811015611259573687878381811061118a5761118a61298e565b905060200281019061119c9190612a82565b90506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016632289511860408301356111dc8480612aa0565b876111ea6020880188612aa0565b88606001356040518863ffffffff1660e01b815260040161121096959493929190612ae2565b5f604051808303818588803b158015611227575f80fd5b505af1158015611239573d5f803e3d5ffd5b505050505080604001358461124e91906128e5565b93505060010161116f565b50604080518481526020810184905233917f9e9fe020bd32482efd0b7caef23ddacf42c27b7c513943b1b8650983c910968a9101610f62565b5f8061129c611985565b90505f6112a76120b8565b600201546001600160801b03169050818111156112c6575f9250505090565b6112d081836129bb565b9250505090565b604080516060810182525f80825260208201819052918101919091526112fb6120b8565b6040805160608101825282546001600160801b0381168252600160801b9004600f0b60208201526001909201546001600160401b031690820152919050565b6113426120dc565b60405163efe98d8560e01b81523060048201525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063efe98d859060240161010060405180830381865afa1580156113a7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113cb9190612b76565b80519091506001600160a01b0316156113f757604051635debf27360e11b815260040160405180910390fd5b5f6114006120b8565b6004810154909150600160a01b900460ff1661142f57604051630d6c579d60e31b815260040160405180910390fd5b60048101805460ff60a01b191690556040515f81527fe05171ce5d2bb89bcb4969164f770f6c2079d15ccc411d2c982476a6050decd8906020015b60405180910390a15050565b61147e6120dc565b6114875f612410565b565b5f6114bb7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b905090565b6114c86120dc565b5f6114d16120b8565b6004810154909150600160a01b900460ff16156115015760405163c25e38af60e01b815260040160405180910390fd5b6115096120af565b156115275760405163603245d760e11b815260040160405180910390fd5b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663dbba4b486040518163ffffffff1660e01b8152600401602060405180830381865afa158015611584573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115a89190612c44565b6001600160a01b0316638d4e61536040518163ffffffff1660e01b8152600401602060405180830381865afa1580156115e3573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116079190612c44565b60048301549091506001600160a01b0380831691161461164e5760048281015460405163028a603f60e01b81526001600160a01b039091169181019190915260240161023a565b60048201805460ff60a01b1916600160a01b179055604051600181527fe05171ce5d2bb89bcb4969164f770f6c2079d15ccc411d2c982476a6050decd89060200161146a565b61169c6120dc565b5f6116a56120b8565b6004810154909150600160a81b900460ff166116d457604051631351925560e21b815260040160405180910390fd5b60048101805460ff60a81b191690556040517f27a55085732907412de31b5518e3722e1d43962cead04fee6c02a358732f65ed905f90a150565b5f6117176120b8565b60040154600160a81b900460ff16919050565b5f6117336120b8565b60020154600160801b9004600f0b919050565b61174e6120dc565b5f6117576120b8565b6004810154909150600160a01b900460ff16156117875760405163c25e38af60e01b815260040160405180910390fd5b5f6117906120b8565b60020180546001600160801b0319166001600160801b03929092169190911790556040517f9dbced3c821dc986096422a59acf031b4baf957d46d9608282986e701edc4a07905f90a150565b5f6114bb7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00546001600160401b031690565b6118166120dc565b345f03611836576040516356e4289360e01b815260040161023a906125b5565b5f61183f6120b8565b6002810180549192503491601090611862908490600160801b9004600f0b612c5f565b92506101000a8154816001600160801b030219169083600f0b6001600160801b03160217905550336001600160a01b03167f5af8184bef8e4b45eb9f6ed7734d04da38ced226495548f46e0c8ff8d7d9a524346040516118c491815260200190565b60405180910390a250565b6118d76120dc565b5f6118e06120b8565b6004810154909150600160a81b900460ff161561190f576040516240df6560e21b815260040160405180910390fd5b60048101805460ff60a81b1916600160a81b1790556040517f1b0c9f2dbf20ce090cc310b3480b4dc96cbc2dd2806563f379cb4750e47d1aeb905f90a150565b5f6119586120b8565b600401546001600160a01b0316919050565b5f6119736120b8565b600201546001600160801b0316919050565b5f8061198f6120b8565b80546002820154919250600160801b808204600f90810b936119bf93929004900b906001600160801b0316612c5f565b6119c99190612c8c565b600f0b91505090565b6119da6120dc565b5f6119e36120b8565b60028101549091506001600160801b03168211611a135760405163198efd9360e31b815260040160405180910390fd5b611a1b610f72565b15611a4d57611a28611985565b821115611a485760405163ea16634960e01b815260040160405180910390fd5b611a6e565b47821115611a6e5760405163ea16634960e01b815260040160405180910390fd5b6002810180546001600160801b0319166001600160801b0384161790556040518281527f9b5421fec3df58296c3298b07f39385e3c22e4b76d20a8727a05506a3ea3c7639060200161146a565b611ac36120dc565b6001600160a01b038116611b07576040516356e4289360e01b815260206004820152600a6024820152692fb232b837b9b4ba37b960b11b604482015260640161023a565b5f611b106120b8565b6004810154909150600160a01b900460ff1615611b405760405163c25e38af60e01b815260040160405180910390fd5b6004810180546001600160a01b0319166001600160a01b0384169081179091556040517f206352915439eaf303e567806f90699d60d2447047d948300ae179651ff42c05905f90a25050565b611b946120dc565b6001600160a01b038116611bbd57604051631e4fbdf760e01b81525f600482015260240161023a565b61080581612410565b611bce6120dc565b6001600160a01b038216611c12576040516356e4289360e01b815260206004820152600a60248201526917dc9958da5c1a595b9d60b21b604482015260640161023a565b805f03611c4b576040516356e4289360e01b81526020600482015260066024820152652fb2ba3432b960d11b604482015260640161023a565b47811115611c6e57604051639266535160e01b815247600482015260240161023a565b5f611c77611292565b905080821115611c9d57604051637e3232fd60e01b81526004810182905260240161023a565b5f611ca66120b8565b6002810180549192508491601090611cc9908490600160801b9004600f0b612c8c565b92506101000a8154816001600160801b030219169083600f0b6001600160801b031602179055505f846001600160a01b0316846040515f6040518083038185875af1925050503d805f8114611d39576040519150601f19603f3d011682016040523d82523d5f602084013e611d3e565b606091505b5050905080611d7257604051630e21dcbb60e11b81526001600160a01b03861660048201526024810185905260440161023a565b611d7a610f72565b15611db95760028201546001600160801b0316611d95611985565b1015611db4576040516339231d1560e21b815260040160405180910390fd5b611de7565b60028201546001600160801b0316471015611de7576040516339231d1560e21b815260040160405180910390fd5b6040518481526001600160a01b0386169033907fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb9060200160405180910390a35050505050565b805f03611e67576040516356e4289360e01b81526020600482015260066024820152652fb2ba3432b960d11b604482015260640161023a565b47811115611e8a57604051639266535160e01b815247600482015260240161023a565b5f611e93611985565b905080821115611ec057604051633e99200360e11b8152600481018290526024810183905260440161023a565b5f611ec96120b8565b90505f33611ed5611489565b6001600160a01b03161480611f40575060028201546001600160801b031683108015611f295750336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016145b8015611f4057506004820154600160a01b900460ff165b905080611f8457604080516373e3652560e01b815260048101919091526009604482015268726562616c616e636560b81b606482015233602482015260840161023a565b600282018054859190601090611fa5908490600160801b9004600f0b612c8c565b92506101000a8154816001600160801b030219169083600f0b6001600160801b031602179055507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316336001600160a01b03167fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb8660405161203191815260200190565b60405180910390a37f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316637d7c2a1c856040518263ffffffff1660e01b81526004015f604051808303818588803b158015612092575f80fd5b505af11580156120a4573d5f803e3d5ffd5b505050505050505050565b5f6114bb612480565b7f2ec50241a851d8d3fea472e7057288d4603f7a7f78e6d18a9c12cad84552b10090565b336120e5611489565b6001600160a01b0316146114875760405163118cdaa760e01b815233600482015260240161023a565b612116612480565b15612134576040516328c9deb560e21b815260040160405180910390fd5b5f61213d61249a565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612178573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061219c9190612c44565b9050807f8d75cfa6c9a3cd2fb8b6d445eafb32adc5497a45b333009f9000379f7024f9f580546001600160a01b0319166001600160a01b03928316179055604051908216907f948a0b52be7a2928784f372bdcaa858365110f82b72b8acc5341cb70ad6c516b905f90a250565b6122116124cc565b61080581612515565b6040515f9081908190710961ef480eb55e80d19ad83579a64c0070029082818181855afa9150503d805f811461226b576040519150601f19603f3d011682016040523d82523d5f602084013e612270565b606091505b509150915081612293576040516322daa69760e21b815260040160405180910390fd5b80516020146122b557604051631dc5371160e31b815260040160405180910390fd5b808060200190518101906112d09190612a6b565b5f6122d4868661251d565b905082811461230057604051633adf150f60e21b8152600481018290526024810184905260440161023a565b604080516038808252606082019092525f916020820181803683370190505090505f5b82811015612406576123378888848461257c565b61236e8287878481811061234d5761234d61298e565b905060200201602081019061236291906129a2565b60c01b60509190910152565b5f710961ef480eb55e80d19ad83579a64c0070026001600160a01b0316858460405161239a919061291f565b5f6040518083038185875af1925050503d805f81146123d4576040519150601f19603f3d011682016040523d82523d5f602084013e6123d9565b606091505b50509050806123fd5782604051638751976f60e01b815260040161023a9190612cb9565b50600101612323565b5050505050505050565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b5f8061248a61258e565b6001600160a01b03161415905090565b5f7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d505b546001600160a01b0316919050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661148757604051631afcd79f60e31b815260040160405180910390fd5b611b946124cc565b5f612529603083612894565b1561254757604051630726fa7960e31b815260040160405180910390fd5b5f6125536030846128bb565b9050805f0361257557604051639106113760e01b815260040160405180910390fd5b9392505050565b60308082028501602084013750505050565b5f7f8d75cfa6c9a3cd2fb8b6d445eafb32adc5497a45b333009f9000379f7024f9f56124bd565b6020808252600990820152686d73672e76616c756560b81b604082015260600190565b5f8083601f8401126125e8575f80fd5b5081356001600160401b038111156125fe575f80fd5b602083019150836020828501011115612615575f80fd5b9250929050565b5f806020838503121561262d575f80fd5b82356001600160401b03811115612642575f80fd5b61264e858286016125d8565b90969095509350505050565b6001600160a01b0381168114610805575f80fd5b5f805f805f60808688031215612682575f80fd5b853561268d8161265a565b9450602086013561269d8161265a565b935060408601356126ad8161265a565b925060608601356001600160401b038111156126c7575f80fd5b6126d3888289016125d8565b969995985093965092949392505050565b5f602082840312156126f4575f80fd5b5035919050565b5f8083601f84011261270b575f80fd5b5081356001600160401b03811115612721575f80fd5b6020830191508360208260051b8501011115612615575f80fd5b5f805f805f6060868803121561274f575f80fd5b85356001600160401b0380821115612765575f80fd5b61277189838a016125d8565b90975095506020880135915080821115612789575f80fd5b50612796888289016126fb565b90945092505060408601356127aa8161265a565b809150509295509295909350565b80356001600160401b03811681146127ce575f80fd5b919050565b5f805f80608085870312156127e6575f80fd5b6127ef856127b8565b966020860135965060408601359560600135945092505050565b5f806020838503121561281a575f80fd5b82356001600160401b0381111561282f575f80fd5b61264e858286016126fb565b5f6020828403121561284b575f80fd5b81356125758161265a565b5f8060408385031215612867575f80fd5b82356128728161265a565b946020939093013593505050565b634e487b7160e01b5f52601260045260245ffd5b5f826128a2576128a2612880565b500690565b634e487b7160e01b5f52601160045260245ffd5b5f826128c9576128c9612880565b500490565b8082028115828204841417610a6d57610a6d6128a7565b80820180821115610a6d57610a6d6128a7565b5f8085851115612906575f80fd5b83861115612912575f80fd5b5050820193919092039150565b5f82518060208501845e5f920191825250919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b6001600160a01b03831681526040602082018190525f9061298690830184612935565b949350505050565b634e487b7160e01b5f52603260045260245ffd5b5f602082840312156129b2575f80fd5b612575826127b8565b81810381811115610a6d57610a6d6128a7565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b608081525f612a0960808301888a6129ce565b828103602084810191909152868252879181015f5b88811015612a4a576001600160401b03612a37856127b8565b1682529282019290820190600101612a1e565b506001600160a01b0396909616604085015250505060600152949350505050565b5f60208284031215612a7b575f80fd5b5051919050565b5f8235607e19833603018112612a96575f80fd5b9190910192915050565b5f808335601e19843603018112612ab5575f80fd5b8301803591506001600160401b03821115612ace575f80fd5b602001915036819003821315612615575f80fd5b608081525f612af560808301888a6129ce565b8281036020840152612b078188612935565b90508281036040840152612b1c8186886129ce565b915050826060830152979650505050505050565b80516127ce8161265a565b80516bffffffffffffffffffffffff811681146127ce575f80fd5b805161ffff811681146127ce575f80fd5b805180151581146127ce575f80fd5b5f610100808385031215612b88575f80fd5b604051908101906001600160401b0382118183101715612bb657634e487b7160e01b5f52604160045260245ffd5b81604052612bc384612b30565b8152612bd160208501612b3b565b6020820152612be260408501612b3b565b6040820152612bf360608501612b56565b6060820152612c0460808501612b56565b6080820152612c1560a08501612b56565b60a0820152612c2660c08501612b67565b60c0820152612c3760e08501612b3b565b60e0820152949350505050565b5f60208284031215612c54575f80fd5b81516125758161265a565b600f81810b9083900b0160016001607f1b03811360016001607f1b031982121715610a6d57610a6d6128a7565b600f82810b9082900b0360016001607f1b0319811260016001607f1b0382131715610a6d57610a6d6128a7565b602081525f612575602083018461293556fea26469706673582212209de78492b993998e7b63ee3b4619637f543911781a16f64a5952d0c8af98541f64736f6c63430008190033000000000000000000000000dfa0b34f28b1b6735d2df150a99048139302a80e00000000000000000000000000000000219ab540356cbb839cbe05303d7705fa

Deployed Bytecode

0x608060405260043610610215575f3560e01c80638da5cb5b1161011e578063bf15af56116100a8578063f2c098b71161006d578063f2c098b7146105c4578063f2fde38b146105e3578063f3fef3a314610602578063f499301814610621578063f88cc1d314610640575f80fd5b8063bf15af5614610555578063c7c4ff4614610569578063cf3090121461057d578063d4c3eea014610591578063dd467064146105a5575f80fd5b80639f223e6f116100ee5780639f223e6f146104fd578063aa9f2d6a14610511578063b3c6501514610525578063b60d428814610539578063bddf300c14610541575f80fd5b80638da5cb5b146104ad57806392e03036146104c157806395631105146104d55780639a23d851146104e9575f80fd5b80634cd79e0a1161019f5780636af6eeed1161016f5780636af6eeed146103d55780636b96736b146104205780636dd6e80b1461045357806370ba285714610485578063715018a614610499575f80fd5b80634cd79e0a1461036357806354fd4d501461037b57806362b2f4b0146103a25780636a5e2650146103c1575f80fd5b80632cbf918f116101e55780632cbf918f146102cc57806333ac88b4146102f95780634123bc611461030c57806343467d6e14610330578063479bacfc1461034f575f80fd5b8063023c5b0814610249578063107415521461027a5780631e73cad314610299578063246581f7146102ad575f80fd5b3661024557345f03610243576040516356e4289360e01b815260040161023a906125b5565b60405180910390fd5b005b5f80fd5b348015610254575f80fd5b5061025d610654565b6040516001600160a01b0390911681526020015b60405180910390f35b348015610285575f80fd5b5061024361029436600461261c565b61066f565b3480156102a4575f80fd5b506102436107bc565b3480156102b8575f80fd5b506102436102c736600461266e565b610808565b3480156102d7575f80fd5b506102eb6102e63660046126e4565b610a1a565b604051908152602001610271565b61024361030736600461273b565b610a73565b348015610317575f80fd5b50610320610de8565b6040519015158152602001610271565b34801561033b575f80fd5b5061024361034a3660046127d3565b610e04565b34801561035a575f80fd5b50610320610f72565b34801561036e575f80fd5b5030600160f91b176102eb565b348015610386575f80fd5b5060015b6040516001600160401b039091168152602001610271565b3480156103ad575f80fd5b506102436103bc366004612809565b611039565b3480156103cc575f80fd5b506102eb611292565b3480156103e0575f80fd5b506103e96112d7565b6040805182516001600160801b03168152602080840151600f0b90820152918101516001600160401b031690820152606001610271565b34801561042b575f80fd5b5061025d7f00000000000000000000000000000000219ab540356cbb839cbe05303d7705fa81565b34801561045e575f80fd5b507f000000000000000000000000dfa0b34f28b1b6735d2df150a99048139302a80e61025d565b348015610490575f80fd5b5061024361133a565b3480156104a4575f80fd5b50610243611476565b3480156104b8575f80fd5b5061025d611489565b3480156104cc575f80fd5b506102436114c0565b3480156104e0575f80fd5b50610243611694565b3480156104f4575f80fd5b5061032061170e565b348015610508575f80fd5b506102eb61172a565b34801561051c575f80fd5b50610243611746565b348015610530575f80fd5b5061038a6117dc565b61024361180e565b34801561054c575f80fd5b506102436118cf565b348015610560575f80fd5b506102eb603081565b348015610574575f80fd5b5061025d61194f565b348015610588575f80fd5b506102eb61196a565b34801561059c575f80fd5b506102eb611985565b3480156105b0575f80fd5b506102436105bf3660046126e4565b6119d2565b3480156105cf575f80fd5b506102436105de36600461283b565b611abb565b3480156105ee575f80fd5b506102436105fd36600461283b565b611b8c565b34801561060d575f80fd5b5061024361061c366004612856565b611bc6565b34801561062c575f80fd5b5061024361063b3660046126e4565b611e2e565b34801561064b575f80fd5b506103206120af565b5f61065d6120b8565b600301546001600160a01b0316919050565b6106776120dc565b5f8190036106b3576040516356e4289360e01b81526020600482015260086024820152675f7075626b65797360c01b604482015260640161023a565b6106be603082612894565b156106dc576040516349b7ed0560e11b815260040160405180910390fd5b5f6106e86030836128bb565b90505f5b818110156107b6575f84846107026030856128ce565b9060306107108660016128e5565b61071a91906128ce565b92610727939291906128f8565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505060405192935061076c92849250905061291f565b60405180910390207f67d6925387aada877c003f8a473d2e08d2bcc0bf8b8322d9c17f8a47213d38d533836040516107a5929190612963565b60405180910390a2506001016106ec565b50505050565b6107c46120dc565b5f6107cd6120b8565b6004810154909150600160a01b900460ff16156107fd5760405163c25e38af60e01b815260040160405180910390fd5b61080561210e565b50565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff1615906001600160401b03165f8115801561084c5750825b90505f826001600160401b031660011480156108675750303b155b905081158015610875575080155b156108935760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156108bd57845460ff60401b1916600160401b1785555b6001600160a01b038916610904576040516356e4289360e01b815260206004820152600d60248201526c2fb737b232a7b832b930ba37b960991b604482015260640161023a565b61090d8a612209565b5f6109166120b8565b6003810180546001600160a01b0319166001600160a01b038d811691909117909155909150891615610948578861094a565b895b6004820180546001600160a01b0319166001600160a01b03928316179055604051908b16907f57d209fa057c282ad4b54dc6ad1a002b01d20bf16299f62ff8040943a692b8d3905f90a26040516001600160a01b038a16907f206352915439eaf303e567806f90699d60d2447047d948300ae179651ff42c05905f90a2508315610a0e57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050505050565b5f815f03610a5b576040516356e4289360e01b815260206004820152600d60248201526c5f6e756d6265724f664b65797360981b604482015260640161023a565b610a6361221a565b610a6d90836128ce565b92915050565b345f03610a93576040516356e4289360e01b815260040161023a906125b5565b5f849003610acf576040516356e4289360e01b81526020600482015260086024820152675f7075626b65797360c01b604482015260640161023a565b5f829003610b0b576040516356e4289360e01b81526020600482015260086024820152675f616d6f756e747360c01b604482015260640161023a565b6001600160a01b038116610b1c5750335b5f610b256120b8565b60038101549091505f906001600160a01b0316331480610b5d5750610b48611489565b6001600160a01b0316336001600160a01b0316145b60028301549091505f906001600160801b0316610b78611985565b1090508015610bce578180610bcb5750336001600160a01b037f000000000000000000000000dfa0b34f28b1b6735d2df150a99048139302a80e16148015610bcb57506004830154600160a01b900460ff165b91505b81610c2457604080516373e3652560e01b81526004810191909152601a60448201527f7472696767657256616c696461746f725769746864726177616c000000000000606482015233602482015260840161023a565b8080610c355750610c33610f72565b155b15610c9e575f5b85811015610c9c575f878783818110610c5757610c5761298e565b9050602002016020810190610c6c91906129a2565b6001600160401b03161115610c945760405163dd5b277960e01b815260040160405180910390fd5b600101610c3c565b505b5f610ca761221a565b90505f81610cb660308b6128bb565b610cc091906128ce565b905080341015610cec5760405163e546602560e01b81523460048201526024810182905260440161023a565b610cf98a8a8a8a866122c9565b5f610d0482346129bb565b90508015610d90575f876001600160a01b0316826040515f6040518083038185875af1925050503d805f8114610d55576040519150601f19603f3d011682016040523d82523d5f602084013e610d5a565b606091505b5050905080610d8e5760405163112bec8b60e01b81526001600160a01b03891660048201526024810183905260440161023a565b505b336001600160a01b03167f085bc7631f3dac6c30fec9bf152fd7c53ea7fbeadc4288bf963291d843a159248c8c8c8c8c87604051610dd3969594939291906129f6565b60405180910390a25050505050505050505050565b5f610df16120b8565b60040154600160a01b900460ff16919050565b5f610e0d6120b8565b9050336001600160a01b037f000000000000000000000000dfa0b34f28b1b6735d2df150a99048139302a80e16141580610e5357506004810154600160a01b900460ff16155b15610e9257604080516373e3652560e01b8152600481019190915260066044820152651c995c1bdc9d60d21b606482015233602482015260840161023a565b60018101546001600160401b039081169086168110610ed757604051633fb579ad60e01b81526001600160401b0380831660048301528716602482015260440161023a565b60018201805467ffffffffffffffff19166001600160401b0388169081179091556001600160801b03868116600160801b878316021784556002840180546001600160801b03191691861691909117905560408051878152602081018790529081018590527f70b5f9716f6485b2e0a05a3c96591041bea2f66184fb5e48c930985be06a9c8c906060015b60405180910390a2505050505050565b5f80610f7c6120b8565b6004810154909150600160a01b900460ff16610f9a57600191505090565b7f000000000000000000000000dfa0b34f28b1b6735d2df150a99048139302a80e6001600160a01b031663625c14f26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ff6573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061101a9190612a6b565b6001820154611032906001600160401b0316426129bb565b1091505090565b5f819003611076576040516356e4289360e01b81526020600482015260096024820152685f6465706f7369747360b81b604482015260640161023a565b5f61107f6120b8565b6004810154909150600160a81b900460ff16156110af5760405163fac4552f60e01b815260040160405180910390fd5b60048101546001600160a01b0316331461110b57604080516373e3652560e01b8152600481019190915260146044820152733232b837b9b4ba2a37a132b0b1b7b721b430b4b760611b606482015233602482015260840161023a565b60028101546001600160801b0316611121611985565b1015611140576040516339231d1560e21b815260040160405180910390fd5b815f8030600160f91b1760405160200161115c91815260200190565b60405160208183030381529060405290505f5b83811015611259573687878381811061118a5761118a61298e565b905060200281019061119c9190612a82565b90506001600160a01b037f00000000000000000000000000000000219ab540356cbb839cbe05303d7705fa16632289511860408301356111dc8480612aa0565b876111ea6020880188612aa0565b88606001356040518863ffffffff1660e01b815260040161121096959493929190612ae2565b5f604051808303818588803b158015611227575f80fd5b505af1158015611239573d5f803e3d5ffd5b505050505080604001358461124e91906128e5565b93505060010161116f565b50604080518481526020810184905233917f9e9fe020bd32482efd0b7caef23ddacf42c27b7c513943b1b8650983c910968a9101610f62565b5f8061129c611985565b90505f6112a76120b8565b600201546001600160801b03169050818111156112c6575f9250505090565b6112d081836129bb565b9250505090565b604080516060810182525f80825260208201819052918101919091526112fb6120b8565b6040805160608101825282546001600160801b0381168252600160801b9004600f0b60208201526001909201546001600160401b031690820152919050565b6113426120dc565b60405163efe98d8560e01b81523060048201525f907f000000000000000000000000dfa0b34f28b1b6735d2df150a99048139302a80e6001600160a01b03169063efe98d859060240161010060405180830381865afa1580156113a7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113cb9190612b76565b80519091506001600160a01b0316156113f757604051635debf27360e11b815260040160405180910390fd5b5f6114006120b8565b6004810154909150600160a01b900460ff1661142f57604051630d6c579d60e31b815260040160405180910390fd5b60048101805460ff60a01b191690556040515f81527fe05171ce5d2bb89bcb4969164f770f6c2079d15ccc411d2c982476a6050decd8906020015b60405180910390a15050565b61147e6120dc565b6114875f612410565b565b5f6114bb7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b905090565b6114c86120dc565b5f6114d16120b8565b6004810154909150600160a01b900460ff16156115015760405163c25e38af60e01b815260040160405180910390fd5b6115096120af565b156115275760405163603245d760e11b815260040160405180910390fd5b5f7f000000000000000000000000dfa0b34f28b1b6735d2df150a99048139302a80e6001600160a01b031663dbba4b486040518163ffffffff1660e01b8152600401602060405180830381865afa158015611584573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115a89190612c44565b6001600160a01b0316638d4e61536040518163ffffffff1660e01b8152600401602060405180830381865afa1580156115e3573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116079190612c44565b60048301549091506001600160a01b0380831691161461164e5760048281015460405163028a603f60e01b81526001600160a01b039091169181019190915260240161023a565b60048201805460ff60a01b1916600160a01b179055604051600181527fe05171ce5d2bb89bcb4969164f770f6c2079d15ccc411d2c982476a6050decd89060200161146a565b61169c6120dc565b5f6116a56120b8565b6004810154909150600160a81b900460ff166116d457604051631351925560e21b815260040160405180910390fd5b60048101805460ff60a81b191690556040517f27a55085732907412de31b5518e3722e1d43962cead04fee6c02a358732f65ed905f90a150565b5f6117176120b8565b60040154600160a81b900460ff16919050565b5f6117336120b8565b60020154600160801b9004600f0b919050565b61174e6120dc565b5f6117576120b8565b6004810154909150600160a01b900460ff16156117875760405163c25e38af60e01b815260040160405180910390fd5b5f6117906120b8565b60020180546001600160801b0319166001600160801b03929092169190911790556040517f9dbced3c821dc986096422a59acf031b4baf957d46d9608282986e701edc4a07905f90a150565b5f6114bb7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00546001600160401b031690565b6118166120dc565b345f03611836576040516356e4289360e01b815260040161023a906125b5565b5f61183f6120b8565b6002810180549192503491601090611862908490600160801b9004600f0b612c5f565b92506101000a8154816001600160801b030219169083600f0b6001600160801b03160217905550336001600160a01b03167f5af8184bef8e4b45eb9f6ed7734d04da38ced226495548f46e0c8ff8d7d9a524346040516118c491815260200190565b60405180910390a250565b6118d76120dc565b5f6118e06120b8565b6004810154909150600160a81b900460ff161561190f576040516240df6560e21b815260040160405180910390fd5b60048101805460ff60a81b1916600160a81b1790556040517f1b0c9f2dbf20ce090cc310b3480b4dc96cbc2dd2806563f379cb4750e47d1aeb905f90a150565b5f6119586120b8565b600401546001600160a01b0316919050565b5f6119736120b8565b600201546001600160801b0316919050565b5f8061198f6120b8565b80546002820154919250600160801b808204600f90810b936119bf93929004900b906001600160801b0316612c5f565b6119c99190612c8c565b600f0b91505090565b6119da6120dc565b5f6119e36120b8565b60028101549091506001600160801b03168211611a135760405163198efd9360e31b815260040160405180910390fd5b611a1b610f72565b15611a4d57611a28611985565b821115611a485760405163ea16634960e01b815260040160405180910390fd5b611a6e565b47821115611a6e5760405163ea16634960e01b815260040160405180910390fd5b6002810180546001600160801b0319166001600160801b0384161790556040518281527f9b5421fec3df58296c3298b07f39385e3c22e4b76d20a8727a05506a3ea3c7639060200161146a565b611ac36120dc565b6001600160a01b038116611b07576040516356e4289360e01b815260206004820152600a6024820152692fb232b837b9b4ba37b960b11b604482015260640161023a565b5f611b106120b8565b6004810154909150600160a01b900460ff1615611b405760405163c25e38af60e01b815260040160405180910390fd5b6004810180546001600160a01b0319166001600160a01b0384169081179091556040517f206352915439eaf303e567806f90699d60d2447047d948300ae179651ff42c05905f90a25050565b611b946120dc565b6001600160a01b038116611bbd57604051631e4fbdf760e01b81525f600482015260240161023a565b61080581612410565b611bce6120dc565b6001600160a01b038216611c12576040516356e4289360e01b815260206004820152600a60248201526917dc9958da5c1a595b9d60b21b604482015260640161023a565b805f03611c4b576040516356e4289360e01b81526020600482015260066024820152652fb2ba3432b960d11b604482015260640161023a565b47811115611c6e57604051639266535160e01b815247600482015260240161023a565b5f611c77611292565b905080821115611c9d57604051637e3232fd60e01b81526004810182905260240161023a565b5f611ca66120b8565b6002810180549192508491601090611cc9908490600160801b9004600f0b612c8c565b92506101000a8154816001600160801b030219169083600f0b6001600160801b031602179055505f846001600160a01b0316846040515f6040518083038185875af1925050503d805f8114611d39576040519150601f19603f3d011682016040523d82523d5f602084013e611d3e565b606091505b5050905080611d7257604051630e21dcbb60e11b81526001600160a01b03861660048201526024810185905260440161023a565b611d7a610f72565b15611db95760028201546001600160801b0316611d95611985565b1015611db4576040516339231d1560e21b815260040160405180910390fd5b611de7565b60028201546001600160801b0316471015611de7576040516339231d1560e21b815260040160405180910390fd5b6040518481526001600160a01b0386169033907fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb9060200160405180910390a35050505050565b805f03611e67576040516356e4289360e01b81526020600482015260066024820152652fb2ba3432b960d11b604482015260640161023a565b47811115611e8a57604051639266535160e01b815247600482015260240161023a565b5f611e93611985565b905080821115611ec057604051633e99200360e11b8152600481018290526024810183905260440161023a565b5f611ec96120b8565b90505f33611ed5611489565b6001600160a01b03161480611f40575060028201546001600160801b031683108015611f295750336001600160a01b037f000000000000000000000000dfa0b34f28b1b6735d2df150a99048139302a80e16145b8015611f4057506004820154600160a01b900460ff165b905080611f8457604080516373e3652560e01b815260048101919091526009604482015268726562616c616e636560b81b606482015233602482015260840161023a565b600282018054859190601090611fa5908490600160801b9004600f0b612c8c565b92506101000a8154816001600160801b030219169083600f0b6001600160801b031602179055507f000000000000000000000000dfa0b34f28b1b6735d2df150a99048139302a80e6001600160a01b0316336001600160a01b03167fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb8660405161203191815260200190565b60405180910390a37f000000000000000000000000dfa0b34f28b1b6735d2df150a99048139302a80e6001600160a01b0316637d7c2a1c856040518263ffffffff1660e01b81526004015f604051808303818588803b158015612092575f80fd5b505af11580156120a4573d5f803e3d5ffd5b505050505050505050565b5f6114bb612480565b7f2ec50241a851d8d3fea472e7057288d4603f7a7f78e6d18a9c12cad84552b10090565b336120e5611489565b6001600160a01b0316146114875760405163118cdaa760e01b815233600482015260240161023a565b612116612480565b15612134576040516328c9deb560e21b815260040160405180910390fd5b5f61213d61249a565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612178573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061219c9190612c44565b9050807f8d75cfa6c9a3cd2fb8b6d445eafb32adc5497a45b333009f9000379f7024f9f580546001600160a01b0319166001600160a01b03928316179055604051908216907f948a0b52be7a2928784f372bdcaa858365110f82b72b8acc5341cb70ad6c516b905f90a250565b6122116124cc565b61080581612515565b6040515f9081908190710961ef480eb55e80d19ad83579a64c0070029082818181855afa9150503d805f811461226b576040519150601f19603f3d011682016040523d82523d5f602084013e612270565b606091505b509150915081612293576040516322daa69760e21b815260040160405180910390fd5b80516020146122b557604051631dc5371160e31b815260040160405180910390fd5b808060200190518101906112d09190612a6b565b5f6122d4868661251d565b905082811461230057604051633adf150f60e21b8152600481018290526024810184905260440161023a565b604080516038808252606082019092525f916020820181803683370190505090505f5b82811015612406576123378888848461257c565b61236e8287878481811061234d5761234d61298e565b905060200201602081019061236291906129a2565b60c01b60509190910152565b5f710961ef480eb55e80d19ad83579a64c0070026001600160a01b0316858460405161239a919061291f565b5f6040518083038185875af1925050503d805f81146123d4576040519150601f19603f3d011682016040523d82523d5f602084013e6123d9565b606091505b50509050806123fd5782604051638751976f60e01b815260040161023a9190612cb9565b50600101612323565b5050505050505050565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b5f8061248a61258e565b6001600160a01b03161415905090565b5f7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d505b546001600160a01b0316919050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661148757604051631afcd79f60e31b815260040160405180910390fd5b611b946124cc565b5f612529603083612894565b1561254757604051630726fa7960e31b815260040160405180910390fd5b5f6125536030846128bb565b9050805f0361257557604051639106113760e01b815260040160405180910390fd5b9392505050565b60308082028501602084013750505050565b5f7f8d75cfa6c9a3cd2fb8b6d445eafb32adc5497a45b333009f9000379f7024f9f56124bd565b6020808252600990820152686d73672e76616c756560b81b604082015260600190565b5f8083601f8401126125e8575f80fd5b5081356001600160401b038111156125fe575f80fd5b602083019150836020828501011115612615575f80fd5b9250929050565b5f806020838503121561262d575f80fd5b82356001600160401b03811115612642575f80fd5b61264e858286016125d8565b90969095509350505050565b6001600160a01b0381168114610805575f80fd5b5f805f805f60808688031215612682575f80fd5b853561268d8161265a565b9450602086013561269d8161265a565b935060408601356126ad8161265a565b925060608601356001600160401b038111156126c7575f80fd5b6126d3888289016125d8565b969995985093965092949392505050565b5f602082840312156126f4575f80fd5b5035919050565b5f8083601f84011261270b575f80fd5b5081356001600160401b03811115612721575f80fd5b6020830191508360208260051b8501011115612615575f80fd5b5f805f805f6060868803121561274f575f80fd5b85356001600160401b0380821115612765575f80fd5b61277189838a016125d8565b90975095506020880135915080821115612789575f80fd5b50612796888289016126fb565b90945092505060408601356127aa8161265a565b809150509295509295909350565b80356001600160401b03811681146127ce575f80fd5b919050565b5f805f80608085870312156127e6575f80fd5b6127ef856127b8565b966020860135965060408601359560600135945092505050565b5f806020838503121561281a575f80fd5b82356001600160401b0381111561282f575f80fd5b61264e858286016126fb565b5f6020828403121561284b575f80fd5b81356125758161265a565b5f8060408385031215612867575f80fd5b82356128728161265a565b946020939093013593505050565b634e487b7160e01b5f52601260045260245ffd5b5f826128a2576128a2612880565b500690565b634e487b7160e01b5f52601160045260245ffd5b5f826128c9576128c9612880565b500490565b8082028115828204841417610a6d57610a6d6128a7565b80820180821115610a6d57610a6d6128a7565b5f8085851115612906575f80fd5b83861115612912575f80fd5b5050820193919092039150565b5f82518060208501845e5f920191825250919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b6001600160a01b03831681526040602082018190525f9061298690830184612935565b949350505050565b634e487b7160e01b5f52603260045260245ffd5b5f602082840312156129b2575f80fd5b612575826127b8565b81810381811115610a6d57610a6d6128a7565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b608081525f612a0960808301888a6129ce565b828103602084810191909152868252879181015f5b88811015612a4a576001600160401b03612a37856127b8565b1682529282019290820190600101612a1e565b506001600160a01b0396909616604085015250505060600152949350505050565b5f60208284031215612a7b575f80fd5b5051919050565b5f8235607e19833603018112612a96575f80fd5b9190910192915050565b5f808335601e19843603018112612ab5575f80fd5b8301803591506001600160401b03821115612ace575f80fd5b602001915036819003821315612615575f80fd5b608081525f612af560808301888a6129ce565b8281036020840152612b078188612935565b90508281036040840152612b1c8186886129ce565b915050826060830152979650505050505050565b80516127ce8161265a565b80516bffffffffffffffffffffffff811681146127ce575f80fd5b805161ffff811681146127ce575f80fd5b805180151581146127ce575f80fd5b5f610100808385031215612b88575f80fd5b604051908101906001600160401b0382118183101715612bb657634e487b7160e01b5f52604160045260245ffd5b81604052612bc384612b30565b8152612bd160208501612b3b565b6020820152612be260408501612b3b565b6040820152612bf360608501612b56565b6060820152612c0460808501612b56565b6080820152612c1560a08501612b56565b60a0820152612c2660c08501612b67565b60c0820152612c3760e08501612b3b565b60e0820152949350505050565b5f60208284031215612c54575f80fd5b81516125758161265a565b600f81810b9083900b0160016001607f1b03811360016001607f1b031982121715610a6d57610a6d6128a7565b600f82810b9082900b0360016001607f1b0319811260016001607f1b0382131715610a6d57610a6d6128a7565b602081525f612575602083018461293556fea26469706673582212209de78492b993998e7b63ee3b4619637f543911781a16f64a5952d0c8af98541f64736f6c63430008190033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000dfa0b34f28b1b6735d2df150a99048139302a80e00000000000000000000000000000000219ab540356cbb839cbe05303d7705fa

-----Decoded View---------------
Arg [0] : _vaultHub (address): 0xDfA0B34F28b1b6735d2df150a99048139302a80E
Arg [1] : _beaconChainDepositContract (address): 0x00000000219ab540356cBB839Cbe05303d7705Fa

-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 000000000000000000000000dfa0b34f28b1b6735d2df150a99048139302a80e
Arg [1] : 00000000000000000000000000000000219ab540356cbb839cbe05303d7705fa


Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.