Hoodi Testnet

Contract

0xcb3Bb848252F7ca05ED7753Ead0Eb2bdfD2ba878

Overview

ETH Balance

0 ETH

More Info

Multichain Info

N/A
Transaction Hash
Method
Block
From
To
Amount
Unguaranteed Dep...9411712025-08-04 17:58:4895 days ago1754330328IN
0xcb3Bb848...dfD2ba878
0 ETH0.000241156.50386171
Unguaranteed Dep...9411572025-08-04 17:55:3695 days ago1754330136IN
0xcb3Bb848...dfD2ba878
0 ETH0.000211295.69877214
Unguaranteed Dep...9411382025-08-04 17:51:0095 days ago1754329860IN
0xcb3Bb848...dfD2ba878
0 ETH0.000341819.23369865
Unguaranteed Dep...9410822025-08-04 17:39:0095 days ago1754329140IN
0xcb3Bb848...dfD2ba878
0 ETH0.000137173.70322505
Unguaranteed Dep...9410522025-08-04 17:33:0095 days ago1754328780IN
0xcb3Bb848...dfD2ba878
0 ETH0.000103932.80308453
Unguaranteed Dep...9410452025-08-04 17:31:3695 days ago1754328696IN
0xcb3Bb848...dfD2ba878
0 ETH0.000107562.90397134
Unguaranteed Dep...9410342025-08-04 17:29:1295 days ago1754328552IN
0xcb3Bb848...dfD2ba878
0 ETH0.000074462.01031863
Unguaranteed Dep...9410202025-08-04 17:26:0095 days ago1754328360IN
0xcb3Bb848...dfD2ba878
0 ETH0.000080212.16551193
Transfer Vault O...6403802025-06-20 8:17:00141 days ago1750407420IN
0xcb3Bb848...dfD2ba878
0 ETH0.000037911.05026348

Latest 25 internal transactions (View All)

Advanced mode:
Parent Transaction Hash Method Block
From
To
Amount
Revoke Role15534202025-11-04 13:48:003 days ago1762264080
0xcb3Bb848...dfD2ba878
0 ETH
DEFAULT_ADMIN_RO...15534202025-11-04 13:48:003 days ago1762264080
0xcb3Bb848...dfD2ba878
0 ETH
Grant Role15534202025-11-04 13:48:003 days ago1762264080
0xcb3Bb848...dfD2ba878
0 ETH
DEFAULT_ADMIN_RO...15534202025-11-04 13:48:003 days ago1762264080
0xcb3Bb848...dfD2ba878
0 ETH
Connect To Vault...15534202025-11-04 13:48:003 days ago1762264080
0xcb3Bb848...dfD2ba878
1 ETH
Initialize15534202025-11-04 13:48:003 days ago1762264080
0xcb3Bb848...dfD2ba878
0 ETH
Revoke Role15263522025-10-31 14:46:127 days ago1761921972
0xcb3Bb848...dfD2ba878
0 ETH
DEFAULT_ADMIN_RO...15263522025-10-31 14:46:127 days ago1761921972
0xcb3Bb848...dfD2ba878
0 ETH
Grant Role15263522025-10-31 14:46:127 days ago1761921972
0xcb3Bb848...dfD2ba878
0 ETH
DEFAULT_ADMIN_RO...15263522025-10-31 14:46:127 days ago1761921972
0xcb3Bb848...dfD2ba878
0 ETH
Connect To Vault...15263522025-10-31 14:46:127 days ago1761921972
0xcb3Bb848...dfD2ba878
1 ETH
Initialize15263522025-10-31 14:46:127 days ago1761921972
0xcb3Bb848...dfD2ba878
0 ETH
Revoke Role15254042025-10-31 11:24:247 days ago1761909864
0xcb3Bb848...dfD2ba878
0 ETH
DEFAULT_ADMIN_RO...15254042025-10-31 11:24:247 days ago1761909864
0xcb3Bb848...dfD2ba878
0 ETH
Grant Role15254042025-10-31 11:24:247 days ago1761909864
0xcb3Bb848...dfD2ba878
0 ETH
DEFAULT_ADMIN_RO...15254042025-10-31 11:24:247 days ago1761909864
0xcb3Bb848...dfD2ba878
0 ETH
Connect To Vault...15254042025-10-31 11:24:247 days ago1761909864
0xcb3Bb848...dfD2ba878
1 ETH
Initialize15254042025-10-31 11:24:247 days ago1761909864
0xcb3Bb848...dfD2ba878
0 ETH
Trigger Validato...15202852025-10-30 17:19:248 days ago1761844764
0xcb3Bb848...dfD2ba878
200 wei
Trigger Validato...15202652025-10-30 17:15:248 days ago1761844524
0xcb3Bb848...dfD2ba878
200 wei
Trigger Validato...15202632025-10-30 17:15:008 days ago1761844500
0xcb3Bb848...dfD2ba878
200 wei
Trigger Validato...15202622025-10-30 17:14:488 days ago1761844488
0xcb3Bb848...dfD2ba878
200 wei
Trigger Validato...15202562025-10-30 17:13:248 days ago1761844404
0xcb3Bb848...dfD2ba878
100 wei
Trigger Validato...15202512025-10-30 17:12:248 days ago1761844344
0xcb3Bb848...dfD2ba878
200 wei
Trigger Validato...15202492025-10-30 17:12:008 days ago1761844320
0xcb3Bb848...dfD2ba878
100 wei
View All Internal Transactions
Loading...
Loading

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:
Dashboard

Compiler Version
v0.8.25+commit.b61c2a91

Optimization Enabled:
Yes with 200 runs

Other Settings:
cancun EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 47 : Dashboard.sol
// SPDX-FileCopyrightText: 2025 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

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

import {SafeERC20} from "@openzeppelin/contracts-v5.2/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts-v5.2/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts-v5.2/token/ERC721/IERC721.sol";

import {Math256} from "contracts/common/lib/Math256.sol";
import {ILido as IStETH} from "contracts/common/interfaces/ILido.sol";
import {IDepositContract} from "contracts/common/interfaces/IDepositContract.sol";

import {IStakingVault} from "../interfaces/IStakingVault.sol";
import {IPredepositGuarantee} from "../interfaces/IPredepositGuarantee.sol";
import {NodeOperatorFee} from "./NodeOperatorFee.sol";
import {VaultHub} from "../VaultHub.sol";

interface IWstETH is IERC20 {
    function wrap(uint256) external returns (uint256);

    function unwrap(uint256) external returns (uint256);
}

/**
 * @title Dashboard
 * @notice This contract is a UX-layer for StakingVault and meant to be used as its owner.
 * This contract improves the vault UX by bundling all functions from the StakingVault and VaultHub
 * in this single contract. It provides administrative functions for managing the StakingVault,
 * including funding, withdrawing, minting, burning, and rebalancing operations.
 */
contract Dashboard is NodeOperatorFee {
    bytes32 public constant RECOVER_ASSETS_ROLE = keccak256("vaults.Dashboard.RecoverAssets");

    /**
     * @notice The stETH token contract
     */
    IStETH public immutable STETH;

    /**
     * @notice The wstETH token contract
     */
    IWstETH public immutable WSTETH;

    /**
     * @notice ETH address convention per EIP-7528
     */
    address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    /**
     * @notice Slot for the fund-on-receive flag
     *         keccak256("vaults.Dashboard.fundOnReceive")
     */
    bytes32 public constant FUND_ON_RECEIVE_FLAG_SLOT =
        0x7408b7b034fda7051615c19182918ecb91d753231cffd86f81a45d996d63e038;

    /**
     * @notice Constructor sets the stETH, and WSTETH token addresses,
     * and passes the address of the vault hub up the inheritance chain.
     * @param _stETH Address of the stETH token contract.
     * @param _wstETH Address of the wstETH token contract.
     * @param _vaultHub Address of the vault hub contract.
     * @param _lidoLocator Address of the Lido locator contract.
     */
    constructor(
        address _stETH,
        address _wstETH,
        address _vaultHub,
        address _lidoLocator
    ) NodeOperatorFee(_vaultHub, _lidoLocator) {
        _requireNotZero(_stETH);
        _requireNotZero(_wstETH);

        // stETH and wstETH are cached as immutable to save gas for main operations
        STETH = IStETH(_stETH);
        WSTETH = IWstETH(_wstETH);
    }

    /**
     * @notice Calls the parent's initializer and approves the max allowance for WSTETH for gas savings
     * @param _defaultAdmin The address of the default admin
     * @param _nodeOperatorManager The address of the node operator manager
     * @param _nodeOperatorFeeBP The node operator fee in basis points
     * @param _confirmExpiry The confirmation expiry time in seconds
     */
    function initialize(
        address _defaultAdmin,
        address _nodeOperatorManager,
        uint256 _nodeOperatorFeeBP,
        uint256 _confirmExpiry
    ) external {
        super._initialize(_defaultAdmin, _nodeOperatorManager, _nodeOperatorFeeBP, _confirmExpiry);

        // reduces gas cost for `mintWsteth`
        // invariant: dashboard does not hold stETH on its balance
        STETH.approve(address(WSTETH), type(uint256).max);
    }

    // ==================== View Functions ====================

    /**
     * @notice Returns the vault connection data for the staking vault.
     * @return VaultConnection struct containing vault data
     */
    function vaultConnection() public view returns (VaultHub.VaultConnection memory) {
        return VAULT_HUB.vaultConnection(address(_stakingVault()));
    }

    /**
     * @notice Returns the stETH share limit of the vault
     */
    function shareLimit() external view returns (uint256) {
        return vaultConnection().shareLimit;
    }

    /**
     * @notice Returns the number of stETH shares minted
     */
    function liabilityShares() public view returns (uint256) {
        return VAULT_HUB.liabilityShares(address(_stakingVault()));
    }

    /**
     * @notice Returns the reserve ratio of the vault in basis points
     */
    function reserveRatioBP() public view returns (uint16) {
        return vaultConnection().reserveRatioBP;
    }

    /**
     * @notice Returns the rebalance threshold of the vault in basis points.
     */
    function forcedRebalanceThresholdBP() external view returns (uint16) {
        return vaultConnection().forcedRebalanceThresholdBP;
    }

    /**
     * @notice Returns the infra fee basis points.
     */
    function infraFeeBP() external view returns (uint16) {
        return vaultConnection().infraFeeBP;
    }

    /**
     * @notice Returns the liquidity fee basis points.
     */
    function liquidityFeeBP() external view returns (uint16) {
        return vaultConnection().liquidityFeeBP;
    }

    /**
     * @notice Returns the reservation fee basis points.
     */
    function reservationFeeBP() external view returns (uint16) {
        return vaultConnection().reservationFeeBP;
    }

    /**
     * @notice Returns the total value of the vault in ether.
     */
    function totalValue() public view returns (uint256) {
        return VAULT_HUB.totalValue(address(_stakingVault()));
    }

    /**
     * @notice Returns the overall unsettled obligations of the vault in ether
     * @dev includes the node operator fee
     */
    function unsettledObligations() external view returns (uint256) {
        VaultHub.VaultObligations memory obligations = VAULT_HUB.vaultObligations(address(_stakingVault()));
        return uint256(obligations.unsettledLidoFees) + uint256(obligations.redemptions) + nodeOperatorDisbursableFee();
    }

    /**
     * @notice Returns the locked amount of ether for the vault
     */
    function locked() public view returns (uint256) {
        return VAULT_HUB.locked(address(_stakingVault()));
    }

    /**
     * @notice Returns the max total lockable amount of ether for the vault (excluding the Lido and node operator fees)
     */
    function maxLockableValue() public view returns (uint256) {
        uint256 maxLockableValue_ = VAULT_HUB.maxLockableValue(address(_stakingVault()));
        uint256 nodeOperatorFee = nodeOperatorDisbursableFee();
        return maxLockableValue_ > nodeOperatorFee ? maxLockableValue_ - nodeOperatorFee : 0;
    }

    /**
     * @notice Returns the overall capacity for stETH shares that can be minted by the vault
     */
    function totalMintingCapacityShares() public view returns (uint256) {
        uint256 effectiveShareLimit = _operatorGrid().effectiveShareLimit(address(_stakingVault()));

        return Math256.min(effectiveShareLimit, _mintableShares(maxLockableValue()));
    }

    /**
     * @notice Returns the remaining capacity for stETH shares that can be minted
     *         by the vault if additional ether is funded
     * @param _etherToFund the amount of ether to be funded, can be zero
     * @return the number of shares that can be minted using additional ether
     */
    function remainingMintingCapacityShares(uint256 _etherToFund) public view returns (uint256) {
        uint256 effectiveShareLimit = _operatorGrid().effectiveShareLimit(address(_stakingVault()));
        uint256 vaultMintableSharesByRR = _mintableShares(maxLockableValue() + _etherToFund);
        uint256 vaultLiabilityShares = liabilityShares();

        return Math256.min(
            effectiveShareLimit > vaultLiabilityShares ? effectiveShareLimit - vaultLiabilityShares : 0,
            vaultMintableSharesByRR > vaultLiabilityShares ? vaultMintableSharesByRR - vaultLiabilityShares : 0
        );
    }

    /**
     * @notice Returns the amount of ether that can be instantly withdrawn from the staking vault.
     * @dev This is the amount of ether that is not locked in the StakingVault and not reserved for fees and obligations.
     */
    function withdrawableValue() public view returns (uint256) {
        // On pending disconnect, the vault does not allow any withdrawals, so need to return 0 here
        if (VAULT_HUB.vaultConnection(address(_stakingVault())).pendingDisconnect) return 0;

        uint256 withdrawable = VAULT_HUB.withdrawableValue(address(_stakingVault()));
        uint256 nodeOperatorFee = nodeOperatorDisbursableFee();

        return withdrawable > nodeOperatorFee ? withdrawable - nodeOperatorFee : 0;
    }

    // ==================== Vault Management Functions ====================

    /**
     * @dev Automatically funds the staking vault with ether
     */
    receive() external payable {
        if (_shouldFundOnReceive()) _fund(msg.value);
    }

    /**
     * @notice Transfers the ownership of the underlying StakingVault from this contract to a new owner
     *         without disconnecting it from the hub
     * @param _newOwner Address of the new owner.
     */
    function transferVaultOwnership(address _newOwner) external {
        _transferVaultOwnership(_newOwner);
    }

    /**
     * @notice Disconnects the underlying StakingVault from the hub and passing its ownership to Dashboard.
     *         After receiving the final report, one can call reconnectToVaultHub() to reconnect to the hub
     *         or abandonDashboard() to transfer the ownership to a new owner.
     */
    function voluntaryDisconnect() external {
        disburseNodeOperatorFee();

        _voluntaryDisconnect();
    }

    /**
     * @notice Accepts the ownership over the StakingVault transferred from VaultHub on disconnect
     * and immediately transfers it to a new pending owner. This new owner will have to accept the ownership
     * on the StakingVault contract.
     * @param _newOwner The address to transfer the StakingVault ownership to.
     */
    function abandonDashboard(address _newOwner) external {
        if (VAULT_HUB.isVaultConnected(address(_stakingVault()))) revert ConnectedToVaultHub();
        if (_newOwner == address(this)) revert DashboardNotAllowed();

        _acceptOwnership();
        _transferOwnership(_newOwner);
    }

    /**
     * @notice Accepts the ownership over the StakingVault and connects to VaultHub. Can be called to reconnect
     *         to the hub after voluntaryDisconnect()
     */
    function reconnectToVaultHub() external {
        _acceptOwnership();
        connectToVaultHub();
    }

    /**
     * @notice Connects to VaultHub, transferring ownership to VaultHub.
     */
    function connectToVaultHub() public payable {
        if (msg.value > 0) _stakingVault().fund{value: msg.value}();
        _transferOwnership(address(VAULT_HUB));
        VAULT_HUB.connectVault(address(_stakingVault()));
    }

    /**
     * @notice Changes the tier of the vault and connects to VaultHub
     * @param _tierId The tier to change to
     * @param _requestedShareLimit The requested share limit
     */
    function connectAndAcceptTier(uint256 _tierId, uint256 _requestedShareLimit) external payable {
        connectToVaultHub();
        if (!_changeTier(_tierId, _requestedShareLimit)) {
            revert TierChangeNotConfirmed();
        }
    }

    /**
     * @notice Funds the staking vault with ether
     */
    function fund() external payable {
        _fund(msg.value);
    }

    /**
     * @notice Withdraws ether from the staking vault to a recipient
     * @param _recipient Address of the recipient
     * @param _ether Amount of ether to withdraw
     */
    function withdraw(address _recipient, uint256 _ether) external {
        uint256 withdrawableEther = withdrawableValue();
        if (_ether > withdrawableEther) {
            revert ExceedsWithdrawable(_ether, withdrawableEther);
        }

        _withdraw(_recipient, _ether);
    }

    /**
     * @notice Mints stETH shares backed by the vault to the recipient.
     * @param _recipient Address of the recipient
     * @param _amountOfShares Amount of stETH shares to mint
     */
    function mintShares(address _recipient, uint256 _amountOfShares) external payable fundable {
        _mintSharesWithinMintingCapacity(_recipient, _amountOfShares);
    }

    /**
     * @notice Mints stETH tokens backed by the vault to the recipient.
     * !NB: this will revert with`VaultHub.ZeroArgument("_amountOfShares")` if the amount of stETH is less than 1 share
     * @param _recipient Address of the recipient
     * @param _amountOfStETH Amount of stETH to mint
     */
    function mintStETH(address _recipient, uint256 _amountOfStETH) external payable fundable {
        _mintSharesWithinMintingCapacity(_recipient, _getSharesByPooledEth(_amountOfStETH));
    }

    /**
     * @notice Mints wstETH tokens backed by the vault to a recipient.
     * @param _recipient Address of the recipient
     * @param _amountOfWstETH Amount of tokens to mint
     */
    function mintWstETH(address _recipient, uint256 _amountOfWstETH) external payable fundable {
        _mintSharesWithinMintingCapacity(address(this), _amountOfWstETH);

        uint256 mintedStETH = STETH.getPooledEthBySharesRoundUp(_amountOfWstETH);

        uint256 wrappedWstETH = WSTETH.wrap(mintedStETH);
        SafeERC20.safeTransfer(WSTETH, _recipient, wrappedWstETH);
    }

    /**
     * @notice Burns stETH shares from the sender backed by the vault.
     *         Expects corresponding amount of stETH approved to this contract.
     * @param _amountOfShares Amount of stETH shares to burn
     */
    function burnShares(uint256 _amountOfShares) external {
        STETH.transferSharesFrom(msg.sender, address(VAULT_HUB), _amountOfShares);
        _burnShares(_amountOfShares);
    }

    /**
     * @notice Burns stETH tokens from the sender backed by the vault. Expects stETH amount approved to this contract.
     * !NB: this will revert with `VaultHub.ZeroArgument("_amountOfShares")` if the amount of stETH is less than 1 share
     * @param _amountOfStETH Amount of stETH tokens to burn
     */
    function burnStETH(uint256 _amountOfStETH) external {
        _burnStETH(_amountOfStETH);
    }

    /**
     * @notice Burns wstETH tokens from the sender backed by the vault. Expects wstETH amount approved to this contract.
     * !NB: this will revert with `VaultHub.ZeroArgument("_amountOfShares")` on 1 wei of wstETH due to rounding inside wstETH unwrap method
     * @param _amountOfWstETH Amount of wstETH tokens to burn

     */
    function burnWstETH(uint256 _amountOfWstETH) external {
        _burnWstETH(_amountOfWstETH);
    }

    /**
     * @notice Rebalances StakingVault by withdrawing ether to VaultHub corresponding to shares amount provided
     * @param _shares amount of shares to rebalance
     */
    function rebalanceVaultWithShares(uint256 _shares) external {
        _rebalanceVault(_shares);
    }

    /**
     * @notice Rebalances the vault by transferring ether given the shares amount
     * @param _ether amount of ether to rebalance
     */
    function rebalanceVaultWithEther(uint256 _ether) external payable fundable {
        _rebalanceVault(_getSharesByPooledEth(_ether));
    }

    /**
     * @notice Withdraws ether from vault and deposits directly to provided validators bypassing the default PDG process,
     *          allowing validators to be proven post-factum via `proveUnknownValidatorsToPDG`
     *          clearing them for future deposits via `PDG.depositToBeaconChain`
     * @param _deposits array of IStakingVault.Deposit structs containing deposit data
     * @return totalAmount total amount of ether deposited to beacon chain
     * @dev requires the caller to have the `UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE`
     * @dev can be used as PDG shortcut if the node operator is trusted to not frontrun provided deposits
     */
    function unguaranteedDepositToBeaconChain(
        IStakingVault.Deposit[] calldata _deposits
    ) external returns (uint256 totalAmount) {
        IStakingVault stakingVault_ = _stakingVault();
        IDepositContract depositContract = stakingVault_.DEPOSIT_CONTRACT();

        for (uint256 i = 0; i < _deposits.length; i++) {
            totalAmount += _deposits[i].amount;
        }

        uint256 withdrawableEther = withdrawableValue();
        if (totalAmount > withdrawableEther) {
            revert ExceedsWithdrawable(totalAmount, withdrawableEther);
        }

        _disableFundOnReceive();
        _withdrawForUnguaranteedDepositToBeaconChain(totalAmount);
        // Instead of relying on auto-reset at the end of the transaction,
        // re-enable fund-on-receive manually to restore the default receive() behavior in the same transaction
        _enableFundOnReceive();
        _setRewardsAdjustment(rewardsAdjustment.amount + totalAmount);

        bytes memory withdrawalCredentials = bytes.concat(stakingVault_.withdrawalCredentials());

        IStakingVault.Deposit calldata deposit;
        for (uint256 i = 0; i < _deposits.length; i++) {
            deposit = _deposits[i];
            depositContract.deposit{value: deposit.amount}(
                deposit.pubkey,
                withdrawalCredentials,
                deposit.signature,
                deposit.depositDataRoot
            );
        }

        emit UnguaranteedDeposits(address(stakingVault_), _deposits.length, totalAmount);
    }

    /**
     * @notice Proves validators with correct vault WC if they are unknown to PDG
     * @param _witnesses array of IPredepositGuarantee.ValidatorWitness structs containing proof data for validators
     * @dev requires the caller to have the `PDG_PROVE_VALIDATOR_ROLE`
     */
    function proveUnknownValidatorsToPDG(IPredepositGuarantee.ValidatorWitness[] calldata _witnesses) external {
        _proveUnknownValidatorsToPDG(_witnesses);
    }

    /**
     * @notice Compensates ether of disproven validator's predeposit from PDG to the recipient.
     *         Can be called if validator which was predeposited via `PDG.predeposit` with vault funds
     *         was frontrun by NO's with non-vault WC (effectively NO's stealing the predeposit) and then
     *         proof of the validator's invalidity has been provided via `PDG.proveInvalidValidatorWC`.
     * @param _pubkey of validator that was proven invalid in PDG
     * @param _recipient address to receive the `PDG.PREDEPOSIT_AMOUNT`
     * @dev PDG will revert if _recipient is vault address, use fund() instead to return ether to vault
     * @dev requires the caller to have the `PDG_COMPENSATE_PREDEPOSIT_ROLE`
     */
    function compensateDisprovenPredepositFromPDG(bytes calldata _pubkey, address _recipient) external {
        _compensateDisprovenPredepositFromPDG(_pubkey, _recipient);
    }

    /**
     * @notice Recovers ERC20 tokens or ether from the dashboard contract to sender
     * @param _token Address of the token to recover or 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee for ether
     * @param _recipient Address of the recovery recipient
     */
    function recoverERC20(
        address _token,
        address _recipient,
        uint256 _amount
    ) external onlyRoleMemberOrAdmin(RECOVER_ASSETS_ROLE) {
        _requireNotZero(_token);
        _requireNotZero(_recipient);
        _requireNotZero(_amount);

        if (_token == ETH) {
            (bool success,) = payable(_recipient).call{value: _amount}("");
            if (!success) revert EthTransferFailed(_recipient, _amount);
        } else {
            SafeERC20.safeTransfer(IERC20(_token), _recipient, _amount);
        }

        emit ERC20Recovered(_recipient, _token, _amount);
    }

    /**
     * @notice Transfers a given token_id of an ERC721-compatible NFT (defined by the token contract address)
     * from the dashboard contract to sender
     *
     * @param _token an ERC721-compatible token
     * @param _tokenId token id to recover
     * @param _recipient Address of the recovery recipient
     */
    function recoverERC721(
        address _token,
        uint256 _tokenId,
        address _recipient
    ) external onlyRoleMemberOrAdmin(RECOVER_ASSETS_ROLE) {
        _requireNotZero(_token);
        _requireNotZero(_recipient);

        IERC721(_token).safeTransferFrom(address(this), _recipient, _tokenId);

        emit ERC721Recovered(_recipient, _token, _tokenId);
    }

    /**
     * @notice Pauses beacon chain deposits on the StakingVault.
     */
    function pauseBeaconChainDeposits() external {
        _pauseBeaconChainDeposits();
    }

    /**
     * @notice Resumes beacon chain deposits on the StakingVault.
     */
    function resumeBeaconChainDeposits() external {
        _resumeBeaconChainDeposits();
    }

    /**
     * @notice Signals to node operators that specific validators should exit from the beacon chain. It DOES NOT
     *         directly trigger the exit - node operators must monitor for request events and handle the exits.
     * @param _pubkeys Concatenated validator public keys (48 bytes each).
     * @dev    Emits `ValidatorExitRequested` event for each validator public key through the `StakingVault`.
     *         This is a voluntary exit request - node operators can choose whether to act on it or not.
     */
    function requestValidatorExit(bytes calldata _pubkeys) external {
        _requestValidatorExit(_pubkeys);
    }

    /**
     * @notice Initiates a withdrawal from validator(s) on the beacon chain using EIP-7002 triggerable withdrawals
     *         Both partial withdrawals (disabled for if vault is unhealthy) and full validator exits are supported.
     * @param _pubkeys Concatenated validator public keys (48 bytes each).
     * @param _amounts Withdrawal amounts in wei for each validator key and must match _pubkeys length.
     *         Set amount to 0 for a full validator exit.
     *         For partial withdrawals, amounts will be trimmed to keep MIN_ACTIVATION_BALANCE on the validator to avoid deactivation
     * @param _refundRecipient Address to receive any fee refunds, if zero, refunds go to msg.sender.
     * @dev    A withdrawal fee must be paid via msg.value.
     *         Use `StakingVault.calculateValidatorWithdrawalFee()` to determine the required fee for the current block.
     */
    function triggerValidatorWithdrawals(
        bytes calldata _pubkeys,
        uint64[] calldata _amounts,
        address _refundRecipient
    ) external payable {
        _triggerValidatorWithdrawals(_pubkeys, _amounts, _refundRecipient);
    }

    /**
     * @notice Requests a change of tier on the OperatorGrid.
     * @param _tierId The tier to change to.
     * @param _requestedShareLimit The requested share limit.
     * @return bool Whether the tier change was confirmed.
     */
    function changeTier(uint256 _tierId, uint256 _requestedShareLimit) external returns (bool) {
        return _changeTier(_tierId, _requestedShareLimit);
    }

    // ==================== Internal Functions ====================

    /**
     * @dev Modifier to fund the staking vault if msg.value > 0
     */
    modifier fundable() {
        if (msg.value > 0) {
            _fund(msg.value);
        }
        _;
    }

    /**
     * @notice Mints shares within the mintable capacity,
     *         and reverts if the resulting backing is greater than the mintable capacity.
     * @param _recipient The address of the recipient.
     * @param _amountOfShares The amount of shares to mint.
     */
    function _mintSharesWithinMintingCapacity(address _recipient, uint256 _amountOfShares) internal {
        uint256 remainingShares = remainingMintingCapacityShares(0);
        if (_amountOfShares > remainingShares) revert ExceedsMintingCapacity(_amountOfShares, remainingShares);

        _mintShares(_recipient, _amountOfShares);
    }

    /**
     * @dev Burns stETH tokens from the sender backed by the vault
     * @param _amountOfStETH Amount of tokens to burn
     */
    function _burnStETH(uint256 _amountOfStETH) internal {
        uint256 _amountOfShares = _getSharesByPooledEth(_amountOfStETH);
        STETH.transferSharesFrom(msg.sender, address(VAULT_HUB), _amountOfShares);
        _burnShares(_amountOfShares);
    }

    /**
     * @dev Burns wstETH tokens from the sender backed by the vault
     * @param _amountOfWstETH Amount of tokens to burn
     */
    function _burnWstETH(uint256 _amountOfWstETH) internal {
        SafeERC20.safeTransferFrom(WSTETH, msg.sender, address(this), _amountOfWstETH);
        uint256 unwrappedStETH = WSTETH.unwrap(_amountOfWstETH);
        uint256 unwrappedShares = _getSharesByPooledEth(unwrappedStETH);

        STETH.transferShares(address(VAULT_HUB), unwrappedShares);
        _burnShares(unwrappedShares);
    }

    /// @notice Calculates the total number of shares that can be minted by the vault
    /// @param _ether The amount of ether to consider for minting
    function _mintableShares(uint256 _ether) internal view returns (uint256) {
        uint256 mintableStETH = (_ether * (TOTAL_BASIS_POINTS - reserveRatioBP())) / TOTAL_BASIS_POINTS;
        return _getSharesByPooledEth(mintableStETH);
    }

    /// @notice Converts the given amount of stETH to shares
    function _getSharesByPooledEth(uint256 _amountOfStETH) internal view returns (uint256) {
        return STETH.getSharesByPooledEth(_amountOfStETH);
    }

    // @dev The logic is inverted, 0 means fund-on-receive is enabled,
    // so that fund-on-receive is enabled by default
    function _shouldFundOnReceive() internal view returns (bool shouldFund) {
        assembly {
            shouldFund := iszero(tload(FUND_ON_RECEIVE_FLAG_SLOT))
        }
    }

    function _enableFundOnReceive() internal {
        assembly {
            tstore(FUND_ON_RECEIVE_FLAG_SLOT, 0)
        }
    }

    function _disableFundOnReceive() internal {
        assembly {
            tstore(FUND_ON_RECEIVE_FLAG_SLOT, 1)
        }
    }

    // ==================== Events ====================

    /**
     * @notice Emitted when ether was withdrawn from the staking vault and deposited to validators directly bypassing PDG
     * @param stakingVault the address of owned staking vault
     * @param deposits the number of deposits
     * @param totalAmount the total amount of ether deposited to beacon chain
     */
    event UnguaranteedDeposits(address indexed stakingVault, uint256 deposits, uint256 totalAmount);

    /**
     * @notice Emitted when the ERC20 `token` or ether is recovered (i.e. transferred)
     * @param to The address of the recovery recipient
     * @param token The address of the recovered ERC20 token (0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee for ether)
     * @param amount The amount of the token recovered
     */
    event ERC20Recovered(address indexed to, address indexed token, uint256 amount);

    /**
     * @notice Emitted when the ERC721-compatible `token` (NFT) recovered (i.e. transferred)
     * @param to The address of the recovery recipient
     * @param token The address of the recovered ERC721 token
     * @param tokenId id of token recovered
     */
    event ERC721Recovered(address indexed to, address indexed token, uint256 tokenId);

    // ==================== Errors ====================

    /**
     * @notice Emitted when the withdrawable amount of ether is exceeded
     * @param amount The amount of ether that was attempted to be withdrawn
     * @param withdrawableValue The amount of withdrawable ether available
     */
    error ExceedsWithdrawable(uint256 amount, uint256 withdrawableValue);

    /**
     * @notice Error thrown when minting capacity is exceeded
     */
    error ExceedsMintingCapacity(uint256 requestedShares, uint256 remainingShares);

    /**
     * @notice Error thrown when recovery of ETH fails on transfer to recipient
     */
    error EthTransferFailed(address recipient, uint256 amount);

    /**
     * @notice Error when the StakingVault is still connected to the VaultHub.
     */
    error ConnectedToVaultHub();

    /**
     * @notice Error thrown when attempting to connect to VaultHub without confirmed tier change
     */
    error TierChangeNotConfirmed();

    /**
     * @notice Error when attempting to abandon the Dashboard contract itself.
     */
    error DashboardNotAllowed();
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)

pragma solidity ^0.8.20;

import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
 * to enforce additional security measures for this role.
 */
abstract contract AccessControl is Context, IAccessControl, ERC165 {
    struct RoleData {
        mapping(address account => bool) hasRole;
        bytes32 adminRole;
    }

    mapping(bytes32 role => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with an {AccessControlUnauthorizedAccount} error including the required role.
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual returns (bool) {
        return _roles[role].hasRole[account];
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
     * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
     * is missing `role`.
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert AccessControlUnauthorizedAccount(account, role);
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address callerConfirmation) public virtual {
        if (callerConfirmation != _msgSender()) {
            revert AccessControlBadConfirmation();
        }

        _revokeRole(role, callerConfirmation);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
        if (!hasRole(role, account)) {
            _roles[role].hasRole[account] = true;
            emit RoleGranted(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
        if (hasRole(role, account)) {
            _roles[role].hasRole[account] = false;
            emit RoleRevoked(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/AccessControlEnumerable.sol)

pragma solidity ^0.8.20;

import {IAccessControlEnumerable} from "./IAccessControlEnumerable.sol";
import {AccessControl} from "../AccessControl.sol";
import {EnumerableSet} from "../../utils/structs/EnumerableSet.sol";

/**
 * @dev Extension of {AccessControl} that allows enumerating the members of each role.
 */
abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
    using EnumerableSet for EnumerableSet.AddressSet;

    mapping(bytes32 role => EnumerableSet.AddressSet) private _roleMembers;

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns one of the accounts that have `role`. `index` must be a
     * value between 0 and {getRoleMemberCount}, non-inclusive.
     *
     * Role bearers are not sorted in any particular way, and their ordering may
     * change at any point.
     *
     * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
     * you perform all queries on the same block. See the following
     * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
     * for more information.
     */
    function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address) {
        return _roleMembers[role].at(index);
    }

    /**
     * @dev Returns the number of accounts that have `role`. Can be used
     * together with {getRoleMember} to enumerate all bearers of a role.
     */
    function getRoleMemberCount(bytes32 role) public view virtual returns (uint256) {
        return _roleMembers[role].length();
    }

    /**
     * @dev Return all accounts that have `role`
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function getRoleMembers(bytes32 role) public view virtual returns (address[] memory) {
        return _roleMembers[role].values();
    }

    /**
     * @dev Overload {AccessControl-_grantRole} to track enumerable memberships
     */
    function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
        bool granted = super._grantRole(role, account);
        if (granted) {
            _roleMembers[role].add(account);
        }
        return granted;
    }

    /**
     * @dev Overload {AccessControl-_revokeRole} to track enumerable memberships
     */
    function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
        bool revoked = super._revokeRole(role, account);
        if (revoked) {
            _roleMembers[role].remove(account);
        }
        return revoked;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/IAccessControlEnumerable.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev External interface of AccessControlEnumerable declared to support ERC-165 detection.
 */
interface IAccessControlEnumerable is IAccessControl {
    /**
     * @dev Returns one of the accounts that have `role`. `index` must be a
     * value between 0 and {getRoleMemberCount}, non-inclusive.
     *
     * Role bearers are not sorted in any particular way, and their ordering may
     * change at any point.
     *
     * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
     * you perform all queries on the same block. See the following
     * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
     * for more information.
     */
    function getRoleMember(bytes32 role, uint256 index) external view returns (address);

    /**
     * @dev Returns the number of accounts that have `role`. Can be used
     * together with {getRoleMember} to enumerate all bearers of a role.
     */
    function getRoleMemberCount(bytes32 role) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/IAccessControl.sol)

pragma solidity ^0.8.20;

/**
 * @dev External interface of AccessControl declared to support ERC-165 detection.
 */
interface IAccessControl {
    /**
     * @dev The `account` is missing a role.
     */
    error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);

    /**
     * @dev The caller of a function is not the expected one.
     *
     * NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
     */
    error AccessControlBadConfirmation();

    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call. This account bears the admin role (for the granted role).
     * Expected in cases where the role was granted using the internal {AccessControl-_grantRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     */
    function renounceRole(bytes32 role, address callerConfirmation) external;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";

/**
 * @title IERC1363
 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
 *
 * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
 * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
 */
interface IERC1363 is IERC20, IERC165 {
    /*
     * Note: the ERC-165 identifier for this interface is 0xb0202a11.
     * 0xb0202a11 ===
     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
     */

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @param data Additional data with no specified format, sent in call to `spender`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}

File 8 of 47 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../utils/introspection/IERC165.sol";

File 9 of 47 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../token/ERC20/IERC20.sol";

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

pragma solidity ^0.8.20;

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

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 */
library Clones {
    error CloneArgumentsTooLong();

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        return clone(implementation, 0);
    }

    /**
     * @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
     * to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function clone(address implementation, uint256 value) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        assembly ("memory-safe") {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create(value, 0x09, 0x37)
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple times will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        return cloneDeterministic(implementation, salt, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
     * a `value` parameter to send native currency to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneDeterministic(
        address implementation,
        bytes32 salt,
        uint256 value
    ) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        assembly ("memory-safe") {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create2(value, 0x09, 0x37, salt)
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(add(ptr, 0x38), deployer)
            mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
            mstore(add(ptr, 0x14), implementation)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
            mstore(add(ptr, 0x58), salt)
            mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
            predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddress(implementation, salt, address(this));
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
     * immutable arguments. These are provided through `args` and cannot be changed after deployment. To
     * access the arguments within the implementation, use {fetchCloneArgs}.
     *
     * This function uses the create opcode, which should never revert.
     */
    function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
        return cloneWithImmutableArgs(implementation, args, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
     * parameter to send native currency to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneWithImmutableArgs(
        address implementation,
        bytes memory args,
        uint256 value
    ) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        assembly ("memory-safe") {
            instance := create(value, add(bytecode, 0x20), mload(bytecode))
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation` with custom
     * immutable arguments. These are provided through `args` and cannot be changed after deployment. To
     * access the arguments within the implementation, use {fetchCloneArgs}.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
     * `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
     * at the same address.
     */
    function cloneDeterministicWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt
    ) internal returns (address instance) {
        return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
     * but with a `value` parameter to send native currency to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneDeterministicWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt,
        uint256 value
    ) internal returns (address instance) {
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        return Create2.deploy(value, salt, bytecode);
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
     */
    function predictDeterministicAddressWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        return Create2.computeAddress(salt, keccak256(bytecode), deployer);
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
     */
    function predictDeterministicAddressWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this));
    }

    /**
     * @dev Get the immutable args attached to a clone.
     *
     * - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this
     *   function will return an empty array.
     * - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or
     *   `cloneDeterministicWithImmutableArgs`, this function will return the args array used at
     *   creation.
     * - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This
     *   function should only be used to check addresses that are known to be clones.
     */
    function fetchCloneArgs(address instance) internal view returns (bytes memory) {
        bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
        assembly ("memory-safe") {
            extcodecopy(instance, add(result, 32), 45, mload(result))
        }
        return result;
    }

    /**
     * @dev Helper that prepares the initcode of the proxy with immutable args.
     *
     * An assembly variant of this function requires copying the `args` array, which can be efficiently done using
     * `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using
     * abi.encodePacked is more expensive but also more portable and easier to review.
     *
     * NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes.
     * With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes.
     */
    function _cloneCodeWithImmutableArgs(
        address implementation,
        bytes memory args
    ) private pure returns (bytes memory) {
        if (args.length > 24531) revert CloneArgumentsTooLong();
        return
            abi.encodePacked(
                hex"61",
                uint16(args.length + 45),
                hex"3d81600a3d39f3363d3d373d3d3d363d73",
                implementation,
                hex"5af43d82803e903d91602b57fd5bf3",
                args
            );
    }
}

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

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC-721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC-721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
 * `CREATE2` can be used to compute in advance the address where a smart
 * contract will be deployed, which allows for interesting new mechanisms known
 * as 'counterfactual interactions'.
 *
 * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
 * information.
 */
library Create2 {
    /**
     * @dev There's no code to deploy.
     */
    error Create2EmptyBytecode();

    /**
     * @dev Deploys a contract using `CREATE2`. The address where the contract
     * will be deployed can be known in advance via {computeAddress}.
     *
     * The bytecode for a contract can be obtained from Solidity with
     * `type(contractName).creationCode`.
     *
     * Requirements:
     *
     * - `bytecode` must not be empty.
     * - `salt` must have not been used for `bytecode` already.
     * - the factory must have a balance of at least `amount`.
     * - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
     */
    function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
        if (address(this).balance < amount) {
            revert Errors.InsufficientBalance(address(this).balance, amount);
        }
        if (bytecode.length == 0) {
            revert Create2EmptyBytecode();
        }
        assembly ("memory-safe") {
            addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
            // if no address was created, and returndata is not empty, bubble revert
            if and(iszero(addr), not(iszero(returndatasize()))) {
                let p := mload(0x40)
                returndatacopy(p, 0, returndatasize())
                revert(p, returndatasize())
            }
        }
        if (addr == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
     * `bytecodeHash` or `salt` will result in a new destination address.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
        return computeAddress(salt, bytecodeHash, address(this));
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
     * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
        assembly ("memory-safe") {
            let ptr := mload(0x40) // Get free memory pointer

            // |                   | ↓ ptr ...  ↓ ptr + 0x0B (start) ...  ↓ ptr + 0x20 ...  ↓ ptr + 0x40 ...   |
            // |-------------------|---------------------------------------------------------------------------|
            // | bytecodeHash      |                                                        CCCCCCCCCCCCC...CC |
            // | salt              |                                      BBBBBBBBBBBBB...BB                   |
            // | deployer          | 000000...0000AAAAAAAAAAAAAAAAAAA...AA                                     |
            // | 0xFF              |            FF                                                             |
            // |-------------------|---------------------------------------------------------------------------|
            // | memory            | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
            // | keccak(start, 85) |            ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |

            mstore(add(ptr, 0x40), bytecodeHash)
            mstore(add(ptr, 0x20), salt)
            mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
            let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
            mstore8(start, 0xff)
            addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/Hashes.sol)

pragma solidity ^0.8.20;

/**
 * @dev Library of standard hash functions.
 *
 * _Available since v5.1._
 */
library Hashes {
    /**
     * @dev Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
     *
     * NOTE: Equivalent to the `standardNodeHash` in our https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
     */
    function commutativeKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32) {
        return a < b ? _efficientKeccak256(a, b) : _efficientKeccak256(b, a);
    }

    /**
     * @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
     */
    function _efficientKeccak256(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
        assembly ("memory-safe") {
            mstore(0x00, a)
            mstore(0x20, b)
            value := keccak256(0x00, 0x40)
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol)
// This file was procedurally generated from scripts/generate/templates/MerkleProof.js.

pragma solidity ^0.8.20;

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

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * The tree and the proofs can be generated using our
 * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
 * You will find a quickstart guide in the readme.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the Merkle tree could be reinterpreted as a leaf value.
 * OpenZeppelin's JavaScript library generates Merkle trees that are safe
 * against this attack out of the box.
 *
 * IMPORTANT: Consider memory side-effects when using custom hashing functions
 * that access memory in an unsafe way.
 *
 * NOTE: This library supports proof verification for merkle trees built using
 * custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving
 * leaf inclusion in trees built using non-commutative hashing functions requires
 * additional logic that is not supported by this library.
 */
library MerkleProof {
    /**
     *@dev The multiproof provided is not valid.
     */
    error MerkleProofInvalidMultiproof();

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with the default hashing function.
     */
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with the default hashing function.
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with a custom hashing function.
     */
    function verify(
        bytes32[] memory proof,
        bytes32 root,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processProof(proof, leaf, hasher) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with a custom hashing function.
     */
    function processProof(
        bytes32[] memory proof,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = hasher(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with the default hashing function.
     */
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProofCalldata(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with the default hashing function.
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with a custom hashing function.
     */
    function verifyCalldata(
        bytes32[] calldata proof,
        bytes32 root,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processProofCalldata(proof, leaf, hasher) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with a custom hashing function.
     */
    function processProofCalldata(
        bytes32[] calldata proof,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = hasher(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in memory with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProof}.
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProof(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in memory with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < proofFlagsLen; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = Hashes.commutativeKeccak256(a, b);
        }

        if (proofFlagsLen > 0) {
            if (proofPos != proof.length) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[proofFlagsLen - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in memory with a custom hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProof}.
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processMultiProof(proof, proofFlags, leaves, hasher) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in memory with a custom hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < proofFlagsLen; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = hasher(a, b);
        }

        if (proofFlagsLen > 0) {
            if (proofPos != proof.length) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[proofFlagsLen - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in calldata with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProofCalldata}.
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in calldata with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < proofFlagsLen; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = Hashes.commutativeKeccak256(a, b);
        }

        if (proofFlagsLen > 0) {
            if (proofPos != proof.length) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[proofFlagsLen - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in calldata with a custom hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProofCalldata}.
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves, hasher) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in calldata with a custom hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < proofFlagsLen; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = hasher(a, b);
        }

        if (proofFlagsLen > 0) {
            if (proofPos != proof.length) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[proofFlagsLen - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }
}

File 18 of 47 : 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/ERC165.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position is the index of the value in the `values` array plus 1.
        // Position 0 is used to mean a value is not in the set.
        mapping(bytes32 value => uint256) _positions;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._positions[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We cache the value's position to prevent multiple reads from the same storage slot
        uint256 position = set._positions[value];

        if (position != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 valueIndex = position - 1;
            uint256 lastIndex = set._values.length - 1;

            if (valueIndex != lastIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the lastValue to the index where the value to delete is
                set._values[valueIndex] = lastValue;
                // Update the tracked position of the lastValue (that was just moved)
                set._positions[lastValue] = position;
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the tracked position for the deleted slot
            delete set._positions[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._positions[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }
}

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

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

import {AccessControlEnumerable} from "@openzeppelin/contracts-v5.2/access/extensions/AccessControlEnumerable.sol";
import {Confirmations} from "./Confirmations.sol";

/**
 * @title AccessControlConfirmable
 * @author Lido
 * @notice An extension of AccessControlEnumerable that allows executing functions by mutual confirmation.
 * @dev This contract extends Confirmations and AccessControlEnumerable and adds a confirmation mechanism.
 */
abstract contract AccessControlConfirmable is AccessControlEnumerable, Confirmations {

    constructor() {
        __Confirmations_init();
    }

    function _isValidConfirmer(bytes32 _role) internal view override returns (bool) {
        return hasRole(_role, msg.sender);
    }
}

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

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

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

/**
 * @title Confirmable2Addresses
 * @author Lido
 * @notice An extension of Confirmations that allows exectuing functions by mutual confirmation.
 * @dev In this implementation, roles are treated as addresses.
 */
abstract contract Confirmable2Addresses is Confirmations {
    
    function _collectAndCheckConfirmations(bytes calldata _calldata, address _role1, address _role2) internal returns (bool) {
        bytes32[] memory roles = new bytes32[](2);
        roles[0] = bytes32(uint256(uint160(_role1)));
        roles[1] = bytes32(uint256(uint160(_role2)));

        return _collectAndCheckConfirmations(_calldata, roles);
    }

    function _isValidConfirmer(bytes32 _roleAsAddress) internal view override returns (bool) {
        return _roleAsAddress == bytes32(uint256(uint160(msg.sender)));
    }
}

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

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

/**
 * @title Confirmations
 * @author Lido
 * @notice A contract that allows executing functions by mutual confirmation.
 */
abstract contract Confirmations {

    /**
     * @notice Tracks confirmations
     * @dev We cannot set confirmExpiry to 0 because this means that all confirmations have to be in the same block,
     *      which can never be guaranteed. And, more importantly, if the `_setConfirmExpiry` is restricted by
     *      the `onlyConfirmed` modifier, the confirmation expiry will be tricky to change.
     *      This is why confirmExpiry is private, set to a default value of 1 hour and cannot be set to 0.
     *
     * Storage layout:
     * - callData: msg.data of the call (selector + arguments)
     * - role: role that confirmed the action
     * - expiryTimestamp: timestamp of the confirmation
     *
     * - confirmExpiry: confirmation expiry period in seconds
     */
    struct ConfirmationStorage {
        mapping(bytes callData => mapping(bytes32 role => uint256 expiryTimestamp)) confirmations;
        uint256 confirmExpiry;
    }

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


    /**
     * @notice Minimal confirmation expiry in seconds.
     */
    uint256 public constant MIN_CONFIRM_EXPIRY = 1 hours;

    /**
     * @notice Maximal confirmation expiry in seconds.
     */
    uint256 public constant MAX_CONFIRM_EXPIRY = 30 days;

    function __Confirmations_init() internal {
        _setConfirmExpiry(1 days);
    }


    /**
     * @notice Returns the confirmation expiry.
     * @return The confirmation expiry in seconds.
     */
    function getConfirmExpiry() public view returns (uint256) {
        return _getConfirmationsStorage().confirmExpiry;
    }

    /**
     * @notice Returns the confirmation expiry for a given call data and confirmer.
     * @param _callData The call data of the function.
     * @param _role The role of the confirmer.
     * @return The confirmation expiration timestamp or 0 if there was no confirmation from this role to this _callData
     */
    function confirmation(bytes memory _callData, bytes32 _role) external view returns (uint256) {
        return _getConfirmationsStorage().confirmations[_callData][_role];
    }

    /**
     * @dev Processes a confirmation from the current caller and checks if all required confirmations are present.
     * Confirmation, in this context, is a call to the same function with the same arguments.
     * This is a one-off operation that either:
     * - Collects the current caller's confirmation and returns false if not enough confirmations
     * - Or clears all confirmations and returns true if all required confirmations are present
     *
     * The confirmation process works as follows:
     * 1. When a role member calls the function:
     *    - Their confirmation is counted immediately
     *    - If not enough confirmations exist, their confirmation is recorded
     *    - If they're not a member of any of the specified roles, the call reverts
     *
     * 2. Confirmation counting:
     *    - Counts the current caller's confirmations if they're a member of any of the specified roles
     *    - Counts existing confirmations that are not expired, i.e. expiry is not exceeded
     *
     * 3. Confirmation Management:
     *    - If all members of the specified roles have confirmed:
     *      a. Clears all confirmations for this call
     *      b. Returns true to indicate that the function can be executed
     *    - If not enough confirmations:
     *      a. Stores the current confirmations
     *      b. Returns false to indicate that the function cannot be executed yet
     *    - Thus, if the caller has all the roles, returns true immediately
     *
     * 4. Gas Optimization:
     *    - Confirmations are stored in a deferred manner using a memory array
     *    - Confirmation storage writes only occur if the function cannot be executed immediately
     *    - This prevents unnecessary storage writes when all confirmations are present,
     *      because the confirmations are cleared anyway after the function is executed,
     *    - i.e. this optimization is beneficial for the deciding caller and
     *      saves 1 storage write for each role the deciding caller has
     *
     * @param _calldata msg.data of the call (selector + arguments)
     * @param _roles Array of role identifiers that must confirm the call in order to execute it
     * @return bool True if all required confirmations are present and the function can be executed, false otherwise
     *
     * @notice Confirmations past their expiry are not counted and must be recast
     * @notice Only members of the specified roles can submit confirmations
     * @notice The order of confirmations does not matter
     *
     */
    function _collectAndCheckConfirmations(bytes calldata _calldata, bytes32[] memory _roles) internal returns (bool) {
        if (_roles.length == 0) revert ZeroConfirmingRoles();

        uint256 numberOfRoles = _roles.length;
        uint256 numberOfConfirms = 0;
        bool[] memory deferredConfirms = new bool[](numberOfRoles);
        bool isRoleMember = false;

        ConfirmationStorage storage $ = _getConfirmationsStorage();
        uint256 expiryTimestamp = block.timestamp + $.confirmExpiry;

        for (uint256 i = 0; i < numberOfRoles; ++i) {
            bytes32 role = _roles[i];
            if (_isValidConfirmer(role)) {
                isRoleMember = true;
                numberOfConfirms++;
                deferredConfirms[i] = true;

                emit RoleMemberConfirmed(msg.sender, role, block.timestamp, expiryTimestamp, msg.data);
            } else if ($.confirmations[_calldata][role] >= block.timestamp) {
                numberOfConfirms++;
            }
        }

        if (!isRoleMember) revert SenderNotMember();

        if (numberOfConfirms == numberOfRoles) {
            for (uint256 i = 0; i < numberOfRoles; ++i) {
                bytes32 role = _roles[i];
                delete $.confirmations[_calldata][role];
            }
            return true;
        } else {
            for (uint256 i = 0; i < numberOfRoles; ++i) {
                if (deferredConfirms[i]) {
                    bytes32 role = _roles[i];
                    $.confirmations[_calldata][role] = expiryTimestamp;
                }
            }
            return false;
        }
    }

    /**
     * @notice Checks if the caller is a valid confirmer
     * @param _role The role to check
     * @return bool True if the caller is a valid confirmer
     */
    function _isValidConfirmer(bytes32 _role) internal view virtual returns (bool);

    /**
     * @dev Sets the confirmation expiry.
     * Confirmation expiry is a period during which the confirmation is counted. Once expired,
     * the confirmation no longer counts and must be recasted for the confirmation to go through.
     * @dev Does not retroactively apply to existing confirmations.
     * @param _newConfirmExpiry The new confirmation expiry in seconds.
     */
    function _setConfirmExpiry(uint256 _newConfirmExpiry) internal {
        _validateConfirmExpiry(_newConfirmExpiry);

        ConfirmationStorage storage $ = _getConfirmationsStorage();

        uint256 oldConfirmExpiry = $.confirmExpiry;
        $.confirmExpiry = _newConfirmExpiry;

        emit ConfirmExpirySet(msg.sender, oldConfirmExpiry, _newConfirmExpiry);
    }

    function _validateConfirmExpiry(uint256 _newConfirmExpiry) internal pure {
        if (_newConfirmExpiry < MIN_CONFIRM_EXPIRY || _newConfirmExpiry > MAX_CONFIRM_EXPIRY)
            revert ConfirmExpiryOutOfBounds();
    }

    function _getConfirmationsStorage() private pure returns (ConfirmationStorage storage $) {
        assembly {
            $.slot := CONFIRMATIONS_STORAGE_LOCATION
        }
    }

    /**
     * @dev Emitted when the confirmation expiry is set.
     * @param oldConfirmExpiry The old confirmation expiry.
     * @param newConfirmExpiry The new confirmation expiry.
     */
    event ConfirmExpirySet(address indexed sender, uint256 oldConfirmExpiry, uint256 newConfirmExpiry);

    /**
     * @dev Emitted when a role member confirms.
     * @param member The address of the confirming member.
     * @param role The role of the confirming member.
     * @param confirmTimestamp The timestamp of the confirmation.
     * @param expiryTimestamp The timestamp when this confirmation expires.
     * @param data The msg.data of the confirmation (selector + arguments).
     */
    event RoleMemberConfirmed(address indexed member, bytes32 indexed role, uint256 confirmTimestamp, uint256 expiryTimestamp, bytes data);

    /**
     * @dev Thrown when attempting to set confirmation expiry out of bounds.
     */
    error ConfirmExpiryOutOfBounds();

    /**
     * @dev Thrown when a caller without a required role attempts to confirm.
     */
    error SenderNotMember();

    /**
     * @dev Thrown when the roles array is empty.
     */
    error ZeroConfirmingRoles();
}

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

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

import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";

import {PausableUntil} from "contracts/common/utils/PausableUntil.sol";

/**
 * @title PausableUntilWithRoles
 * @notice a `PausableUntil` implementation using OpenZeppelin's `AccessControlEnumerableUpgradeable`
 * @dev the inheriting contract must use `whenNotPaused` modifier from `PausableUntil` to block some functions on pause
 */
abstract contract PausableUntilWithRoles is PausableUntil, AccessControlEnumerableUpgradeable {
    /// @notice role that allows to pause the contract
    bytes32 public constant PAUSE_ROLE = keccak256("PausableUntilWithRoles.PauseRole");
    /// @notice role that allows to resume the contract
    bytes32 public constant RESUME_ROLE = keccak256("PausableUntilWithRoles.ResumeRole");

    /**
     * @notice Resume the contract
     * @dev Reverts if contracts is not paused
     * @dev Reverts if sender has no `RESUME_ROLE`
     */
    function resume() external onlyRole(RESUME_ROLE) {
        _resume();
    }

    /**
     * @notice Pause the contract for a specified period
     * @param _duration pause duration in seconds (use `PAUSE_INFINITELY` for unlimited)
     * @dev Reverts if contract is already paused
     * @dev Reverts if sender has no `PAUSE_ROLE`
     * @dev Reverts if zero duration is passed
     */
    function pauseFor(uint256 _duration) external onlyRole(PAUSE_ROLE) {
        _pauseFor(_duration);
    }

    /**
     * @notice Pause the contract until a specified timestamp
     * @param _pauseUntilInclusive the last second to pause until inclusive
     * @dev Reverts if the timestamp is in the past
     * @dev Reverts if sender has no `PAUSE_ROLE`
     * @dev Reverts if contract is already paused
     */
    function pauseUntil(uint256 _pauseUntilInclusive) external onlyRole(PAUSE_ROLE) {
        _pauseUntil(_pauseUntilInclusive);
    }
}

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

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

import {VaultHub} from "../VaultHub.sol";
import {LazyOracle} from "../LazyOracle.sol";
import {Permissions} from "./Permissions.sol";

/**
 * @title NodeOperatorFee
 * @author Lido
 * @notice An accounting contract for a vault's node operator fee:
 *   • Calculates the node operator's share of each reward period,
 *   • Ignores any vault value changes that aren't true rewards,
 *   • Permissionless on-demand fee disbursement,
 *   • Critical parameter changes require vault-owner<>node operator approval.
 */
contract NodeOperatorFee is Permissions {
    /**
     * @notice Total basis points; 1bp = 0.01%, 100_00bp = 100.00%.
     */
    uint256 internal constant TOTAL_BASIS_POINTS = 100_00;

    /**
     * @notice Maximum value that can be set via manual adjustment
     */
    uint256 public constant MANUAL_REWARDS_ADJUSTMENT_LIMIT = 10_000_000 ether;

    /**
     * @notice Node operator manager role:
     * - confirms confirm expiry;
     * - confirms node operator fee changes;
     * - confirms the transfer of the StakingVault ownership;
     * - sets the node operator fee recipient.
     */
    bytes32 public constant NODE_OPERATOR_MANAGER_ROLE = keccak256("vaults.NodeOperatorFee.NodeOperatorManagerRole");

    /**
     * @notice Adjusts rewards to allow fee correction during side deposits or consolidations
     */
    bytes32 public constant NODE_OPERATOR_REWARDS_ADJUST_ROLE = keccak256("vaults.NodeOperatorFee.RewardsAdjustRole");

    /**
     * @notice Node operator fee in basis points; cannot exceed 100.00%.
     * The node operator's disbursable fee in ether is returned by `nodeOperatorDisbursableFee()`.
     */
    uint256 public nodeOperatorFeeRate;

    /**
     * @notice The last report for which node operator fee was disbursed. Updated on each disbursement.
     */
    VaultHub.Report public feePeriodStartReport;

    /**
     * @notice The address of the node operator fee recipient.
     */
    address public nodeOperatorFeeRecipient;

    struct RewardsAdjustment {
        uint128 amount;
        uint64 latestTimestamp;
    }

    /**
     * @notice Adjustment to allow fee correction during side deposits or consolidations.
     *          - can be increased manually by `increaseRewardsAdjustment` by NODE_OPERATOR_REWARDS_ADJUST_ROLE
     *          - can be set via `setRewardsAdjustment` by `confirmingRoles()`
     *          - increased automatically with `unguaranteedDepositToBeaconChain` by total ether amount of deposits
     *          - reset to zero after `disburseNodeOperatorFee`
     *        This amount will be deducted from rewards during NO fee calculation and can be used effectively write off NO's accrued fees.
     *
     */
    RewardsAdjustment public rewardsAdjustment;

    /**
     * @notice Passes the address of the vault hub up the inheritance chain.
     * @param _vaultHub The address of the vault hub.
     * @param _lidoLocator The address of the Lido locator.
     */
    constructor(address _vaultHub, address _lidoLocator) Permissions(_vaultHub, _lidoLocator) {}

    /**
     * @dev Calls the parent's initializer, sets the node operator fee, assigns the node operator manager role,
     * and makes the node operator manager the admin for the node operator roles.
     * @param _defaultAdmin The address of the default admin
     * @param _nodeOperatorManager The address of the node operator manager
     * @param _nodeOperatorFeeRate The node operator fee rate
     * @param _confirmExpiry The confirmation expiry time in seconds
     */
    function _initialize(
        address _defaultAdmin,
        address _nodeOperatorManager,
        uint256 _nodeOperatorFeeRate,
        uint256 _confirmExpiry
    ) internal {
        _requireNotZero(_nodeOperatorManager);

        super._initialize(_defaultAdmin, _confirmExpiry);

        _validateNodeOperatorFeeRate(_nodeOperatorFeeRate);
        _setNodeOperatorFeeRate(_nodeOperatorFeeRate);
        _setNodeOperatorFeeRecipient(_nodeOperatorManager);

        _grantRole(NODE_OPERATOR_MANAGER_ROLE, _nodeOperatorManager);
        _setRoleAdmin(NODE_OPERATOR_MANAGER_ROLE, NODE_OPERATOR_MANAGER_ROLE);
        _setRoleAdmin(NODE_OPERATOR_REWARDS_ADJUST_ROLE, NODE_OPERATOR_MANAGER_ROLE);
    }

    /**
     * @notice Returns the roles that can:
     * - change the confirm expiry;
     * - set the node operator fee;
     * - set a new owner of the StakingVault.
     * @return roles is an array of roles that form the confirming roles.
     */
    function confirmingRoles() public pure override returns (bytes32[] memory roles) {
        roles = new bytes32[](2);
        roles[0] = DEFAULT_ADMIN_ROLE;
        roles[1] = NODE_OPERATOR_MANAGER_ROLE;
    }

    /**
     * @notice Returns the latest report data containing the total value and in-out delta.
     * @return report The latest report.
     */
    function latestReport() public view returns (VaultHub.Report memory) {
        return VAULT_HUB.latestReport(address(_stakingVault()));
    }

    /**
     * @notice Calculates the node operator's disbursable fee.
     *
     * The fee presently owed to the node-operator,
     * computed as a portion of staking rewards accrued between
     * `feePeriodStartReport` and `latestReport()`.
     *
     * Staking rewards for an accounting interval are derived as:
     *     rewards = Δ(totalValue) − Δ(inOutDelta) − rewardsAdjustment
     *
     * where
     *  • Δ(totalValue)     — change in totalValue (CL + EL balances) between reports;
     *  • Δ(inOutDelta)     — net funds/withdrawals in the same interval;
     *  • rewardsAdjustment — rewards offset that excludes side deposits and consolidations
     *                        (e.g. CL topups that are not subject to node operator fee).
     *
     * If the rewards are negative, for the purposes of fee calculation, they are considered to be zero.
     * The node-operator's fee is therefore:
     *     fee = max(0, rewards) × nodeOperatorFeeBP / TOTAL_BASIS_POINTS
     *
     * @return fee The node operator's disbursable fee.
     */
    function nodeOperatorDisbursableFee() public view returns (uint256) {
        VaultHub.Report memory periodStart = feePeriodStartReport;
        VaultHub.Report memory periodEnd = latestReport();
        int256 adjustment = _toSignedClamped(rewardsAdjustment.amount);

        // the total increase/decrease of the vault value during the fee period
        int256 growth = int112(periodEnd.totalValue) - int112(periodStart.totalValue) -
                        (periodEnd.inOutDelta - periodStart.inOutDelta);

        // the actual rewards that are subject to the fee
        int256 rewards = growth - adjustment;

        return rewards <= 0 ? 0 : (uint256(rewards) * nodeOperatorFeeRate) / TOTAL_BASIS_POINTS;
    }

    /**
     * @notice Transfers the node-operator's accrued fee (if any).
     * Steps:
     *  • Compute the current fee via `nodeOperatorDisbursableFee()`.
     *  • If there are no rewards, do nothing.
     *  • Otherwise, move `feePeriodStartReport` to `latestReport()`,
     *    reset `rewardsAdjustment` and transfer `fee` wei to `nodeOperatorFeeRecipient`.
     */
    function disburseNodeOperatorFee() public {
        uint256 fee = nodeOperatorDisbursableFee();
        // it's important not to revert here if there is no fee,
        // because the fee is automatically disbursed during `voluntaryDisconnect`
        if (fee == 0) return;

        if (rewardsAdjustment.amount != 0) _setRewardsAdjustment(0);
        feePeriodStartReport = latestReport();

        VAULT_HUB.withdraw(address(_stakingVault()), nodeOperatorFeeRecipient, fee);
        emit NodeOperatorFeeDisbursed(msg.sender, fee);
    }

    /**
     * @notice Updates the node-operator's fee rate (basis-points share).
     * @param _newNodeOperatorFeeRate The new node operator fee rate.
     * @return bool Whether the node operator fee rate was set.
     */
    function setNodeOperatorFeeRate(uint256 _newNodeOperatorFeeRate) external returns (bool) {
        // The report must be fresh so that the total value of the vault is up to date
        // and all the node operator fees are paid out fairly up to the moment of the latest fresh report
        if (!VAULT_HUB.isReportFresh(address(_stakingVault()))) revert ReportStale();

        // Latest adjustment must be earlier than the latest fresh report timestamp
        if (rewardsAdjustment.latestTimestamp >= _lazyOracle().latestReportTimestamp())
            revert AdjustmentNotReported();

        // Adjustment must be settled before the fee rate change
        if (rewardsAdjustment.amount != 0) revert AdjustmentNotSettled();

        // If the vault is quarantined, the total value is reduced and may not reflect the adjustment
        if (_lazyOracle().vaultQuarantine(address(_stakingVault())).isActive) revert VaultQuarantined();

        // Validate fee rate before collecting confirmations
        _validateNodeOperatorFeeRate(_newNodeOperatorFeeRate);

        // store the caller's confirmation; only proceed if the required number of confirmations is met.
        if (!_collectAndCheckConfirmations(msg.data, confirmingRoles())) return false;

        // To follow the check-effects-interaction pattern, we need to remember the fee here
        // because the fee calculation variables will be reset in the following lines
        uint256 fee = nodeOperatorDisbursableFee();

        // Start a new fee period
        feePeriodStartReport = latestReport();

        _setNodeOperatorFeeRate(_newNodeOperatorFeeRate);

        if (fee > 0) {
            VAULT_HUB.withdraw(address(_stakingVault()), nodeOperatorFeeRecipient, fee);
            emit NodeOperatorFeeDisbursed(msg.sender, fee);
        }

        return true;
    }

    /**
     * @notice Sets the confirm expiry.
     * Confirm expiry is a period during which the confirm is counted. Once the period is over,
     * the confirm is considered expired, no longer counts and must be recasted.
     * @param _newConfirmExpiry The new confirm expiry in seconds.
     * @return bool Whether the confirm expiry was set.
     */
    function setConfirmExpiry(uint256 _newConfirmExpiry) external returns (bool) {
        _validateConfirmExpiry(_newConfirmExpiry);

        if (!_collectAndCheckConfirmations(msg.data, confirmingRoles())) return false;

        _setConfirmExpiry(_newConfirmExpiry);

        return true;
    }

    /**
     * @notice Sets the node operator fee recipient.
     * @param _newNodeOperatorFeeRecipient The address of the new node operator fee recipient.
     */
    function setNodeOperatorFeeRecipient(
        address _newNodeOperatorFeeRecipient
    ) external onlyRoleMemberOrAdmin(NODE_OPERATOR_MANAGER_ROLE) {
        _setNodeOperatorFeeRecipient(_newNodeOperatorFeeRecipient);
    }

    /**
     * @notice Increases rewards adjustment to correct fee calculation due to non-rewards ether on CL
     * @param _adjustmentIncrease amount to increase adjustment by
     * @dev will revert if final adjustment is more than `MANUAL_REWARDS_ADJUSTMENT_LIMIT`
     */
    function increaseRewardsAdjustment(
        uint256 _adjustmentIncrease
    ) external onlyRoleMemberOrAdmin(NODE_OPERATOR_REWARDS_ADJUST_ROLE) {
        uint256 newAdjustment = rewardsAdjustment.amount + _adjustmentIncrease;
        // sanity check, though value will be cast safely during fee calculation
        if (newAdjustment > MANUAL_REWARDS_ADJUSTMENT_LIMIT) revert IncreasedOverLimit();
        _setRewardsAdjustment(newAdjustment);
    }

    /**
     * @notice set `rewardsAdjustment` to a new proposed value if `confirmingRoles()` agree
     * @param _proposedAdjustment new adjustment amount
     * @param _expectedAdjustment current adjustment value for invalidating old confirmations
     * @return bool Whether the rewards adjustment was set.
     * @dev will revert if new adjustment is more than `MANUAL_REWARDS_ADJUSTMENT_LIMIT`
     */
    function setRewardsAdjustment(
        uint256 _proposedAdjustment,
        uint256 _expectedAdjustment
    ) external returns (bool) {
        if (rewardsAdjustment.amount != _expectedAdjustment)
            revert InvalidatedAdjustmentVote(rewardsAdjustment.amount, _expectedAdjustment);
        if (_proposedAdjustment > MANUAL_REWARDS_ADJUSTMENT_LIMIT) revert IncreasedOverLimit();
        if (!_collectAndCheckConfirmations(msg.data, confirmingRoles())) return false;
        _setRewardsAdjustment(_proposedAdjustment);
        return true;
    }

    function _setNodeOperatorFeeRate(uint256 _newNodeOperatorFeeRate) internal {
        _validateNodeOperatorFeeRate(_newNodeOperatorFeeRate);

        uint256 oldNodeOperatorFeeRate = nodeOperatorFeeRate;
        nodeOperatorFeeRate = _newNodeOperatorFeeRate;

        emit NodeOperatorFeeRateSet(msg.sender, oldNodeOperatorFeeRate, _newNodeOperatorFeeRate);
    }

    function _setNodeOperatorFeeRecipient(address _newNodeOperatorFeeRecipient) internal {
        _requireNotZero(_newNodeOperatorFeeRecipient);
        if (_newNodeOperatorFeeRecipient == nodeOperatorFeeRecipient) revert SameRecipient();

        address oldNodeOperatorFeeRecipient = nodeOperatorFeeRecipient;
        nodeOperatorFeeRecipient = _newNodeOperatorFeeRecipient;
        emit NodeOperatorFeeRecipientSet(msg.sender, oldNodeOperatorFeeRecipient, _newNodeOperatorFeeRecipient);
    }

    /**
     * @notice sets InOut adjustment for correct fee calculation
     * @param _newAdjustment new adjustment value
     */
    function _setRewardsAdjustment(uint256 _newAdjustment) internal {
        uint256 oldAdjustment = rewardsAdjustment.amount;

        if (_newAdjustment == oldAdjustment) revert SameAdjustment();

        rewardsAdjustment.amount = uint128(_newAdjustment);
        rewardsAdjustment.latestTimestamp = uint64(block.timestamp);

        emit RewardsAdjustmentSet(_newAdjustment, oldAdjustment);
    }

    function _toSignedClamped(uint128 _adjustment) internal pure returns (int128) {
        if (_adjustment > uint128(type(int128).max)) return type(int128).max;
        return int128(_adjustment);
    }

    /**
     * @notice Validates that the node operator fee rate is within acceptable bounds
     * @param _nodeOperatorFeeRate The fee rate to validate
     */
    function _validateNodeOperatorFeeRate(uint256 _nodeOperatorFeeRate) internal pure {
        if (_nodeOperatorFeeRate > TOTAL_BASIS_POINTS) revert FeeValueExceed100Percent();
    }

    function _lazyOracle() internal view returns (LazyOracle) {
        return LazyOracle(LIDO_LOCATOR.lazyOracle());
    }

    // ==================== Events ====================

    /**
     * @dev Emitted when the node operator fee is set.
     * @param oldNodeOperatorFeeRate The old node operator fee rate.
     * @param newNodeOperatorFeeRate The new node operator fee rate.
     */
    event NodeOperatorFeeRateSet(address indexed sender, uint256 oldNodeOperatorFeeRate, uint256 newNodeOperatorFeeRate);

    /**
     * @dev Emitted when the node operator fee is disbursed.
     * @param fee the amount of disbursed fee.
     */
    event NodeOperatorFeeDisbursed(address indexed sender, uint256 fee);

    /**
     * @dev Emitted when the new rewards adjustment is set.
     * @param newAdjustment the new adjustment value
     * @param oldAdjustment previous adjustment value
     */
    event RewardsAdjustmentSet(uint256 newAdjustment, uint256 oldAdjustment);

    /**
     * @dev Emitted when the node operator fee recipient is set.
     * @param sender the address of the sender who set the recipient
     * @param oldNodeOperatorFeeRecipient the old node operator fee recipient
     * @param newNodeOperatorFeeRecipient the new node operator fee recipient
     */
    event NodeOperatorFeeRecipientSet(address indexed sender, address oldNodeOperatorFeeRecipient, address newNodeOperatorFeeRecipient);

    // ==================== Errors ====================

    /**
     * @dev Error emitted when the combined feeBPs exceed 100%.
     */
    error FeeValueExceed100Percent();

    /**
     * @dev Error emitted when the increased adjustment exceeds the `MANUAL_REWARDS_ADJUSTMENT_LIMIT`.
     */
    error IncreasedOverLimit();

    /**
     * @dev Error emitted when the adjustment setting vote is not valid due to changed state
     */
    error InvalidatedAdjustmentVote(uint256 currentAdjustment, uint256 currentAtPropositionAdjustment);

    /**
     * @dev Error emitted when trying to set same value for adjustment
     */
    error SameAdjustment();

    /**
     * @dev Error emitted when trying to set same value for recipient
     */
    error SameRecipient();

    /**
     * @dev Error emitted when the report is stale.
     */
    error ReportStale();

    /**
     * @dev Error emitted when the adjustment has not been reported yet.
     */
    error AdjustmentNotReported();

    /**
     * @dev Error emitted when the adjustment is not settled.
     */
    error AdjustmentNotSettled();

    /**
     * @dev Error emitted when the vault is quarantined.
     */
    error VaultQuarantined();
}

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

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

import {Clones} from "@openzeppelin/contracts-v5.2/proxy/Clones.sol";
import {AccessControlConfirmable} from "contracts/0.8.25/utils/AccessControlConfirmable.sol";
import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol";

import {IStakingVault} from "../interfaces/IStakingVault.sol";
import {IPredepositGuarantee} from "../interfaces/IPredepositGuarantee.sol";
import {OperatorGrid} from "../OperatorGrid.sol";
import {VaultHub} from "../VaultHub.sol";

/**
 * @title Permissions
 * @author Lido
 * @notice Provides granular permissions for StakingVault operations.
 */
abstract contract Permissions is AccessControlConfirmable {
    /**
     * @notice Struct containing an account and a role for granting/revoking roles.
     */
    struct RoleAssignment {
        address account;
        bytes32 role;
    }

    /**
     * @notice Permission for funding the StakingVault.
     */
    bytes32 public constant FUND_ROLE = keccak256("vaults.Permissions.Fund");

    /**
     * @notice Permission for withdrawing funds from the StakingVault.
     */
    bytes32 public constant WITHDRAW_ROLE = keccak256("vaults.Permissions.Withdraw");

    /**
     * @notice Permission for minting stETH shares backed by the StakingVault.
     */
    bytes32 public constant MINT_ROLE = keccak256("vaults.Permissions.Mint");

    /**
     * @notice Permission for burning stETH shares backed by the StakingVault.
     */
    bytes32 public constant BURN_ROLE = keccak256("vaults.Permissions.Burn");

    /**
     * @notice Permission for rebalancing the StakingVault.
     */
    bytes32 public constant REBALANCE_ROLE = keccak256("vaults.Permissions.Rebalance");

    /**
     * @notice Permission for pausing beacon chain deposits on the StakingVault.
     */
    bytes32 public constant PAUSE_BEACON_CHAIN_DEPOSITS_ROLE = keccak256("vaults.Permissions.PauseDeposits");

    /**
     * @notice Permission for resuming beacon chain deposits on the StakingVault.
     */
    bytes32 public constant RESUME_BEACON_CHAIN_DEPOSITS_ROLE = keccak256("vaults.Permissions.ResumeDeposits");

    /**
     * @notice Permission for requesting validator exit from the StakingVault.
     */
    bytes32 public constant REQUEST_VALIDATOR_EXIT_ROLE = keccak256("vaults.Permissions.RequestValidatorExit");

    /**
     * @notice Permission for triggering validator withdrawal from the StakingVault using EIP-7002 triggerable exit.
     */
    bytes32 public constant TRIGGER_VALIDATOR_WITHDRAWAL_ROLE =
        keccak256("vaults.Permissions.TriggerValidatorWithdrawal");

    /**
     * @notice Permission for voluntary disconnecting the StakingVault.
     */
    bytes32 public constant VOLUNTARY_DISCONNECT_ROLE = keccak256("vaults.Permissions.VoluntaryDisconnect");

    /**
     * @notice Permission for getting compensation for disproven validator predeposit from PDG
     */
    bytes32 public constant PDG_COMPENSATE_PREDEPOSIT_ROLE = keccak256("vaults.Permissions.PDGCompensatePredeposit");

    /**
     * @notice Permission for proving valid vault validators unknown to the PDG
     */
    bytes32 public constant PDG_PROVE_VALIDATOR_ROLE = keccak256("vaults.Permissions.PDGProveValidator");

    /**
     * @notice Permission for unguaranteed deposit to trusted validators
     */
    bytes32 public constant UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE =
        keccak256("vaults.Permissions.UnguaranteedBeaconChainDeposit");

    /**
     * @dev Permission for requesting change of tier on the OperatorGrid.
     */
    bytes32 public constant CHANGE_TIER_ROLE = keccak256("vaults.Permissions.ChangeTier");

    /**
     * @notice Address of the implementation contract
     * @dev Used to prevent initialization in the implementation
     */
    address private immutable _SELF;

    VaultHub public immutable VAULT_HUB;
    ILidoLocator public immutable LIDO_LOCATOR;

    /**
     * @notice Indicates whether the contract has been initialized
     */
    bool public initialized;

    constructor(address _vaultHub, address _lidoLocator) {
        _requireNotZero(_vaultHub);
        _requireNotZero(_lidoLocator);

        _SELF = address(this);
        // @dev vaultHub is cached as immutable to save gas for main operations
        VAULT_HUB = VaultHub(payable(_vaultHub));
        LIDO_LOCATOR = ILidoLocator(_lidoLocator);
    }

    /**
     * @notice Modifier to prevent reinitialization of the contract.
     * @dev Extracted to modifier to avoid Slither warning.
     */
    modifier initializer() {
        if (initialized) revert AlreadyInitialized();
        if (address(this) == _SELF) revert NonProxyCallsForbidden();

        initialized = true;
        _;

        emit Initialized();
    }

    /**
     * @dev Sets the ACL default admin and confirmation expiry time.
     * @param _defaultAdmin The address of the default admin
     * @param _confirmExpiry The confirmation expiry time in seconds
     */
    function _initialize(address _defaultAdmin, uint256 _confirmExpiry) internal initializer {
        _requireNotZero(_defaultAdmin);

        _grantRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
        _validateConfirmExpiry(_confirmExpiry);
        _setConfirmExpiry(_confirmExpiry);
    }

    /**
     * @notice Returns the address of the underlying StakingVault.
     * @return The address of the StakingVault.
     */
    function stakingVault() external view returns (IStakingVault) {
        return _stakingVault();
    }

    // ==================== Role Management Functions ====================

    /**
     * @notice Mass-grants multiple roles to multiple accounts.
     * @param _assignments An array of role assignments.
     * @dev Performs the role admin checks internally.
     * @dev If an account is already a member of a role, doesn't revert, emits no events.
     */
    function grantRoles(RoleAssignment[] calldata _assignments) external {
        _requireNotZero(_assignments.length);

        for (uint256 i = 0; i < _assignments.length; i++) {
            grantRole(_assignments[i].role, _assignments[i].account);
        }
    }

    /**
     * @notice Mass-revokes multiple roles from multiple accounts.
     * @param _assignments An array of role assignments.
     * @dev Performs the role admin checks internally.
     * @dev If an account is not a member of a role, doesn't revert, emits no events.
     */
    function revokeRoles(RoleAssignment[] calldata _assignments) external {
        if (_assignments.length == 0) revert ZeroArgument();

        for (uint256 i = 0; i < _assignments.length; i++) {
            revokeRole(_assignments[i].role, _assignments[i].account);
        }
    }

    /**
     * @dev Returns an array of roles that need to confirm the call
     *      used for the `onlyConfirmed` modifier.
     * @return The roles that need to confirm the call.
     */
    function confirmingRoles() public pure virtual returns (bytes32[] memory);

    /**
     * @dev A custom modifier that checks if the caller has a role or the admin role for a given role.
     * @param _role The role to check.
     */
    modifier onlyRoleMemberOrAdmin(bytes32 _role) {
        if (hasRole(_role, msg.sender) || hasRole(getRoleAdmin(_role), msg.sender)) {
            _;
        } else {
            revert AccessControlUnauthorizedAccount(msg.sender, _role);
        }
    }

    /**
     * @dev Checks the FUND_ROLE and funds the StakingVault.
     * @param _ether The amount of ether to fund the StakingVault with.
     */
    function _fund(uint256 _ether) internal onlyRoleMemberOrAdmin(FUND_ROLE) {
        VAULT_HUB.fund{value: _ether}(address(_stakingVault()));
    }

    /**
     * @dev Checks the WITHDRAW_ROLE and withdraws funds from the StakingVault.
     * @param _recipient The address to withdraw the funds to.
     * @param _ether The amount of ether to withdraw from the StakingVault.
     * @dev The zero checks for recipient and ether are performed in the StakingVault contract.
     */
    function _withdraw(address _recipient, uint256 _ether) internal virtual onlyRoleMemberOrAdmin(WITHDRAW_ROLE) {
        VAULT_HUB.withdraw(address(_stakingVault()), _recipient, _ether);
    }

    /**
     * @dev Checks the MINT_ROLE and mints shares backed by the StakingVault.
     * @param _recipient The address to mint the shares to.
     * @param _shares The amount of shares to mint.
     * @dev The zero checks for parameters are performed in the VaultHub contract.
     */
    function _mintShares(address _recipient, uint256 _shares) internal onlyRoleMemberOrAdmin(MINT_ROLE) {
        VAULT_HUB.mintShares(address(_stakingVault()), _recipient, _shares);
    }

    /**
     * @dev Checks the BURN_ROLE and burns shares backed by the StakingVault.
     * @param _shares The amount of shares to burn.
     * @dev The zero check for parameters is performed in the VaultHub contract.
     */
    function _burnShares(uint256 _shares) internal onlyRoleMemberOrAdmin(BURN_ROLE) {
        VAULT_HUB.burnShares(address(_stakingVault()), _shares);
    }

    /**
     * @dev Checks the REBALANCE_ROLE and rebalances the StakingVault.
     * @param _shares The amount of shares to rebalance the StakingVault with.
     * @dev The zero check for parameters is performed in the StakingVault contract.
     */
    function _rebalanceVault(uint256 _shares) internal onlyRoleMemberOrAdmin(REBALANCE_ROLE) {
        VAULT_HUB.rebalance(address(_stakingVault()), _shares);
    }

    /**
     * @dev Checks the PAUSE_BEACON_CHAIN_DEPOSITS_ROLE and pauses beacon chain deposits on the StakingVault.
     */
    function _pauseBeaconChainDeposits() internal onlyRoleMemberOrAdmin(PAUSE_BEACON_CHAIN_DEPOSITS_ROLE) {
        VAULT_HUB.pauseBeaconChainDeposits(address(_stakingVault()));
    }

    /**
     * @dev Checks the RESUME_BEACON_CHAIN_DEPOSITS_ROLE and resumes beacon chain deposits on the StakingVault.
     */
    function _resumeBeaconChainDeposits() internal onlyRoleMemberOrAdmin(RESUME_BEACON_CHAIN_DEPOSITS_ROLE) {
        VAULT_HUB.resumeBeaconChainDeposits(address(_stakingVault()));
    }

    /**
     * @dev Checks the REQUEST_VALIDATOR_EXIT_ROLE and requests validator exit on the StakingVault.
     * @dev The zero check for _pubkeys is performed in the StakingVault contract.
     */
    function _requestValidatorExit(bytes calldata _pubkeys) internal onlyRoleMemberOrAdmin(REQUEST_VALIDATOR_EXIT_ROLE) {
        VAULT_HUB.requestValidatorExit(address(_stakingVault()), _pubkeys);
    }

    /**
     * @dev Checks the TRIGGER_VALIDATOR_WITHDRAWAL_ROLE and triggers validator withdrawal on the StakingVault using EIP-7002 triggerable exit.
     * @dev The zero checks for parameters are performed in the StakingVault contract.
     */
    function _triggerValidatorWithdrawals(
        bytes calldata _pubkeys,
        uint64[] calldata _amounts,
        address _refundRecipient
    ) internal onlyRoleMemberOrAdmin(TRIGGER_VALIDATOR_WITHDRAWAL_ROLE) {
        VAULT_HUB.triggerValidatorWithdrawals{value: msg.value}(address(_stakingVault()), _pubkeys, _amounts, _refundRecipient);
    }

    /**
     * @dev Checks the VOLUNTARY_DISCONNECT_ROLE and voluntarily disconnects the StakingVault.
     */
    function _voluntaryDisconnect() internal onlyRoleMemberOrAdmin(VOLUNTARY_DISCONNECT_ROLE) {
        VAULT_HUB.voluntaryDisconnect(address(_stakingVault()));
    }

    /**
     * @dev Checks the DEFAULT_ADMIN_ROLE and transfers the StakingVault ownership.
     * @param _newOwner The address to transfer the ownership to.
     */
    function _transferOwnership(address _newOwner) internal onlyRole(DEFAULT_ADMIN_ROLE) {
        _stakingVault().transferOwnership(_newOwner);
    }

    /**
     * @dev Checks the DEFAULT_ADMIN_ROLE and accepts the StakingVault ownership.
     */
    function _acceptOwnership() internal onlyRole(DEFAULT_ADMIN_ROLE) {
        _stakingVault().acceptOwnership();
    }

    /**
     * @dev Checks the PDG_COMPENSATE_PREDEPOSIT_ROLE and claims disproven predeposit from PDG.
     * @param _pubkey The pubkey of the validator.
     * @param _recipient The address to compensate the disproven validator predeposit to.
     * @return The amount of ether compensated.
     */
    function _compensateDisprovenPredepositFromPDG(
        bytes calldata _pubkey,
        address _recipient
    ) internal onlyRoleMemberOrAdmin(PDG_COMPENSATE_PREDEPOSIT_ROLE) returns (uint256) {
        return VAULT_HUB.compensateDisprovenPredepositFromPDG(address(_stakingVault()), _pubkey, _recipient);
    }

    /**
     * @dev Proves validators unknown to PDG that have correct vault WC
     */
    function _proveUnknownValidatorsToPDG(
        IPredepositGuarantee.ValidatorWitness[] calldata _witnesses
    ) internal onlyRoleMemberOrAdmin(PDG_PROVE_VALIDATOR_ROLE) {
        for (uint256 i = 0; i < _witnesses.length; i++) {
            VAULT_HUB.proveUnknownValidatorToPDG(address(_stakingVault()), _witnesses[i]);
        }
    }

    /**
     * @dev Withdraws ether from vault to this contract for unguaranteed deposit to validators
     */
    function _withdrawForUnguaranteedDepositToBeaconChain(
        uint256 _ether
    ) internal onlyRoleMemberOrAdmin(UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE) {
        VAULT_HUB.withdraw(address(_stakingVault()), address(this), _ether);
    }

    /**
     * @dev Checks the confirming roles and sets the owner on the StakingVault.
     * @param _newOwner The address to set the owner to.
     */
    function _transferVaultOwnership(address _newOwner) internal {
        if (!_collectAndCheckConfirmations(msg.data, confirmingRoles())) return;
        VAULT_HUB.transferVaultOwnership(address(_stakingVault()), _newOwner);
    }

    /**
     * @dev Checks the CHANGE_TIER_ROLE and requests a change of the tier on the OperatorGrid.
     * @param _tierId The tier to change to.
     * @param _requestedShareLimit The requested share limit.
     * @return bool Whether the tier change was confirmed.
     */
    function _changeTier(uint256 _tierId, uint256 _requestedShareLimit) internal onlyRoleMemberOrAdmin(CHANGE_TIER_ROLE) returns (bool) {
        return _operatorGrid().changeTier(address(_stakingVault()), _tierId, _requestedShareLimit);
    }

    /**
     * @dev Loads the address of the underlying StakingVault.
     * @return addr The address of the StakingVault.
     */
    function _stakingVault() internal view returns (IStakingVault) {
        bytes memory args = Clones.fetchCloneArgs(address(this));
        address stakingVaultAddress;
        assembly {
            stakingVaultAddress := mload(add(args, 32))
        }
        return IStakingVault(stakingVaultAddress);
    }

    function _operatorGrid() internal view returns (OperatorGrid) {
        return OperatorGrid(LIDO_LOCATOR.operatorGrid());
    }

    function _requireNotZero(uint256 _value) internal pure {
        if (_value == 0) revert ZeroArgument();
    }

    function _requireNotZero(address _address) internal pure {
        if (_address == address(0)) revert ZeroAddress();
    }

    /**
     * @notice Emitted when the contract is initialized
     */
    event Initialized();

    /**
     * @notice Error when direct calls to the implementation are forbidden
     */
    error NonProxyCallsForbidden();

    /**
     * @notice Error when the contract is already initialized.
     */
    error AlreadyInitialized();

    /**
     * @notice Error thrown for when a given value cannot be zero
     */
    error ZeroArgument();

    /**
     * @notice Error thrown for when a given address cannot be zero
     */
    error ZeroAddress();
}

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

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

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

/**
 * @title IPredepositGuarantee
 * @author Lido
 * @notice Interface for the `PredepositGuarantee` contract
 */
interface IPredepositGuarantee {
    /**
     * @notice user input for validator proof verification
     * @custom:proof array of merkle proofs from parent(pubkey,wc) node to Beacon block root
     * @custom:pubkey of validator to prove
     * @custom:validatorIndex of validator in CL state tree
     * @custom:childBlockTimestamp of EL block that has parent block beacon root in BEACON_ROOTS contract
     * @custom:slot of the beacon block for which the proof is generated
     * @custom:proposerIndex of the beacon block for which the proof is generated
     */
    struct ValidatorWitness {
        bytes32[] proof;
        bytes pubkey;
        uint256 validatorIndex;
        uint64 childBlockTimestamp;
        uint64 slot;
        uint64 proposerIndex;
    }

    function compensateDisprovenPredeposit(
        bytes calldata _validatorPubkey,
        address _recipient
    ) external returns (uint256 compensatedEther);

    function proveUnknownValidator(ValidatorWitness calldata _witness, IStakingVault _stakingVault) external;
}

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

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

import {IDepositContract} from "contracts/common/interfaces/IDepositContract.sol";

/**
 * @title IStakingVault
 * @author Lido
 * @notice Interface for the `StakingVault` contract
 */
interface IStakingVault {
    /**
     * @notice validator deposit from the `StakingVault` to the beacon chain
     * @dev withdrawal credentials are provided by the vault
     * @custom:pubkey The validator's BLS public key (48 bytes)
     * @custom:signature BLS signature of the deposit data (96 bytes)
     * @custom:amount Amount of ETH to deposit in wei (must be a multiple of 1 ETH)
     * @custom:depositDataRoot The root hash of the deposit data per ETH beacon spec
     */
    struct Deposit {
        bytes pubkey;
        bytes signature;
        uint256 amount;
        bytes32 depositDataRoot;
    }

    function DEPOSIT_CONTRACT() external view returns (IDepositContract);
    function initialize(address _owner, address _nodeOperator, address _depositor) external;
    function version() external pure returns (uint64);
    function getInitializedVersion() external view returns (uint64);
    function withdrawalCredentials() external view returns (bytes32);

    function owner() external view returns (address);
    function pendingOwner() external view returns (address);
    function acceptOwnership() external;
    function transferOwnership(address _newOwner) external;

    function nodeOperator() external view returns (address);
    function depositor() external view returns (address);
    function isOssified() external view returns (bool);
    function calculateValidatorWithdrawalFee(uint256 _keysCount) external view returns (uint256);
    function fund() external payable;
    function withdraw(address _recipient, uint256 _ether) external;

    function beaconChainDepositsPaused() external view returns (bool);
    function pauseBeaconChainDeposits() external;
    function resumeBeaconChainDeposits() external;
    function depositToBeaconChain(Deposit[] calldata _deposits) external;

    function requestValidatorExit(bytes calldata _pubkeys) external;
    function triggerValidatorWithdrawals(bytes calldata _pubkeys, uint64[] calldata _amounts, address _refundRecipient) external payable;
    function ejectValidators(bytes calldata _pubkeys, address _refundRecipient) external payable;
    function setDepositor(address _depositor) external;
    function ossify() external;
}

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

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

import {MerkleProof} from "@openzeppelin/contracts-v5.2/utils/cryptography/MerkleProof.sol";

import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";

import {Math256} from "contracts/common/lib/Math256.sol";
import {ILazyOracle} from "contracts/common/interfaces/ILazyOracle.sol";
import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol";
import {ILido} from "contracts/common/interfaces/ILido.sol";
import {IHashConsensus} from "contracts/common/interfaces/IHashConsensus.sol";

import {VaultHub} from "./VaultHub.sol";
import {OperatorGrid} from "./OperatorGrid.sol";

import {IStakingVault} from "./interfaces/IStakingVault.sol";

contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
    /// @custom:storage-location erc7201:LazyOracle
    struct Storage {
        /// @notice root of the vaults data tree
        bytes32 vaultsDataTreeRoot;
        /// @notice CID of the vaults data tree
        string vaultsDataReportCid;
        /// @notice timestamp of the vaults data
        uint64 vaultsDataTimestamp;
        /// @notice total value increase quarantine period
        uint64 quarantinePeriod;
        /// @notice max reward ratio for refSlot-observed total value, basis points
        uint16 maxRewardRatioBP;
        /// @notice deposit quarantines for each vault
        mapping(address vault => Quarantine) vaultQuarantines;
    }

    /*
        A quarantine is a timelock applied to any sudden jump in a vault's reported total value
        that cannot be immediately confirmed on-chain (via the inOutDelta difference). If the
        reported total value exceeds the expected routine EL/CL rewards, the excess is pushed
        into a quarantine buffer for a predefined cooldown period. Only after this delay is the
        quarantined value released into VaultHub's total value.

        Normal top-ups — where the vault owner funds the contract directly using the `fund()`
        function — do not go through quarantine, as they can be verified on-chain via the
        inOutDelta value. These direct fundings are reflected immediately. In contrast,
        consolidations or deposits that bypass the vault's balance must sit in quarantine.

        Example flow:

        Time 0: Total Value = 100 ETH
        ┌────────────────────────────────────┐
        │            100 ETH Active          │
        └────────────────────────────────────┘

        Time 1: Sudden jump of +50 ETH → start quarantine for 50 ETH
        ┌────────────────────────────────────┐
        │            100 ETH Active          │
        │            50 ETH Quarantined      │
        └────────────────────────────────────┘

        Time 2: Another jump of +70 ETH → wait for current quarantine to expire
        ┌────────────────────────────────────┐
        │            100 ETH Active          │
        │            50 ETH Quarantined      │
        │            70 ETH Quarantine Queue │
        └────────────────────────────────────┘

        Time 3: First quarantine expires → add 50 ETH to active value, start new quarantine for 70 ETH
        ┌────────────────────────────────────┐
        │            150 ETH Active          │
        │            70 ETH Quarantined      │
        └────────────────────────────────────┘

        Time 4: Second quarantine expires → add 70 ETH to active value
        ┌────────────────────────────────────┐
        │            220 ETH Active          │
        └────────────────────────────────────┘
    */
    struct Quarantine {
        uint128 pendingTotalValueIncrease;
        uint64 startTimestamp;
    }

    struct QuarantineInfo {
        bool isActive;
        uint256 pendingTotalValueIncrease;
        uint256 startTimestamp;
        uint256 endTimestamp;
    }

    struct VaultInfo {
        address vault;
        uint96 vaultIndex;
        uint256 balance;
        bytes32 withdrawalCredentials;
        uint256 liabilityShares;
        uint256 mintableStETH;
        uint96 shareLimit;
        uint16 reserveRatioBP;
        uint16 forcedRebalanceThresholdBP;
        uint16 infraFeeBP;
        uint16 liquidityFeeBP;
        uint16 reservationFeeBP;
        bool pendingDisconnect;
    }

    // keccak256(abi.encode(uint256(keccak256("LazyOracle")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant LAZY_ORACLE_STORAGE_LOCATION =
        0xe5459f2b48ec5df2407caac4ec464a5cb0f7f31a1f22f649728a9579b25c1d00;

    bytes32 public constant UPDATE_SANITY_PARAMS_ROLE = keccak256("UPDATE_SANITY_PARAMS_ROLE");

    ILidoLocator public immutable LIDO_LOCATOR;

    /// @dev basis points base
    uint256 private constant TOTAL_BASIS_POINTS = 100_00;
    uint256 private constant MAX_SANE_TOTAL_VALUE = type(uint96).max;

    constructor(address _lidoLocator) {
        LIDO_LOCATOR = ILidoLocator(payable(_lidoLocator));

        _disableInitializers();
    }

    /// @notice Initializes the contract
    /// @param _admin Address of the admin
    /// @param _quarantinePeriod the quarantine period, seconds
    /// @param _maxRewardRatioBP the max reward ratio, basis points
    function initialize(address _admin, uint64 _quarantinePeriod, uint16 _maxRewardRatioBP) external initializer {
        if (_admin == address(0)) revert AdminCannotBeZero();
        _grantRole(DEFAULT_ADMIN_ROLE, _admin);

        _updateSanityParams(_quarantinePeriod, _maxRewardRatioBP);
    }

    /// @notice returns the latest report data
    /// @return timestamp of the report
    /// @return treeRoot merkle root of the report
    /// @return reportCid IPFS CID for the report JSON file
    function latestReportData() external view returns (uint64 timestamp, bytes32 treeRoot, string memory reportCid) {
        Storage storage $ = _storage();
        return ($.vaultsDataTimestamp, $.vaultsDataTreeRoot, $.vaultsDataReportCid);
    }

    /// @notice returns the latest report timestamp
    function latestReportTimestamp() external view returns (uint64) {
        return _storage().vaultsDataTimestamp;
    }

    /// @notice returns the quarantine period
    function quarantinePeriod() external view returns (uint64) {
        return _storage().quarantinePeriod;
    }

    /// @notice returns the max reward ratio for refSlot total value, basis points
    function maxRewardRatioBP() external view returns (uint16) {
        return _storage().maxRewardRatioBP;
    }

    /// @notice returns the quarantine info for the vault
    /// @param _vault the address of the vault
    // @dev returns zeroed structure if there is no active quarantine
    function vaultQuarantine(address _vault) external view returns (QuarantineInfo memory) {
        Quarantine storage q = _storage().vaultQuarantines[_vault];
        if (q.pendingTotalValueIncrease == 0) {
            return QuarantineInfo(false, 0, 0, 0);
        }

        return QuarantineInfo({
            isActive: true,
            pendingTotalValueIncrease: q.pendingTotalValueIncrease,
            startTimestamp: q.startTimestamp,
            endTimestamp: q.startTimestamp + _storage().quarantinePeriod
        });
    }

    /// @notice returns batch of vaults info
    /// @param _offset in the vaults list [0, vaultsCount)
    /// @param _limit maximum number of vaults to return
    /// @return batch of vaults info
    function batchVaultsInfo(uint256 _offset, uint256 _limit) external view returns (VaultInfo[] memory) {
        VaultHub vaultHub = _vaultHub();
        uint256 vaultCount = vaultHub.vaultsCount();
        uint256 batchSize;
        if (_offset > vaultCount) {
            batchSize = 0;
        } else {
            batchSize = _offset + _limit > vaultCount ? vaultCount - _offset : _limit;
        }

        VaultInfo[] memory batch = new VaultInfo[](batchSize);
        for (uint256 i = 0; i < batchSize; i++) {
            address vaultAddress = vaultHub.vaultByIndex(_offset + i + 1);
            IStakingVault vault = IStakingVault(vaultAddress);
            VaultHub.VaultConnection memory connection = vaultHub.vaultConnection(vaultAddress);
            VaultHub.VaultRecord memory record = vaultHub.vaultRecord(vaultAddress);
            batch[i] = VaultInfo(
                vaultAddress,
                connection.vaultIndex,
                address(vault).balance,
                vault.withdrawalCredentials(),
                record.liabilityShares,
                _mintableStETH(vaultAddress),
                connection.shareLimit,
                connection.reserveRatioBP,
                connection.forcedRebalanceThresholdBP,
                connection.infraFeeBP,
                connection.liquidityFeeBP,
                connection.reservationFeeBP,
                connection.pendingDisconnect
            );
        }
        return batch;
    }

    /// @notice update the sanity parameters
    /// @param _quarantinePeriod the quarantine period
    /// @param _maxRewardRatioBP the max EL CL rewards
    function updateSanityParams(
        uint64 _quarantinePeriod,
        uint16 _maxRewardRatioBP
    ) external onlyRole(UPDATE_SANITY_PARAMS_ROLE) {
        _updateSanityParams(_quarantinePeriod, _maxRewardRatioBP);
    }

    /// @notice Store the report root and its meta information
    /// @param _vaultsDataTimestamp the timestamp of the report
    /// @param _vaultsDataTreeRoot the root of the report
    /// @param _vaultsDataReportCid the CID of the report
    function updateReportData(
        uint256 _vaultsDataTimestamp,
        bytes32 _vaultsDataTreeRoot,
        string memory _vaultsDataReportCid
    ) external override(ILazyOracle) {
        if (msg.sender != LIDO_LOCATOR.accountingOracle()) revert NotAuthorized();

        Storage storage $ = _storage();
        $.vaultsDataTimestamp = uint64(_vaultsDataTimestamp);
        $.vaultsDataTreeRoot = _vaultsDataTreeRoot;
        $.vaultsDataReportCid = _vaultsDataReportCid;

        emit VaultsReportDataUpdated(_vaultsDataTimestamp, _vaultsDataTreeRoot, _vaultsDataReportCid);
    }

    /// @notice Permissionless update of the vault data
    /// @param _vault the address of the vault
    /// @param _totalValue the total value of the vault
    /// @param _cumulativeLidoFees the cumulative Lido fees accrued on the vault (nominated in ether)
    /// @param _liabilityShares the liabilityShares of the vault
    /// @param _proof the proof of the reported data
    function updateVaultData(
        address _vault,
        uint256 _totalValue,
        uint256 _cumulativeLidoFees,
        uint256 _liabilityShares,
        uint256 _slashingReserve,
        bytes32[] calldata _proof
    ) external {
        bytes32 leaf = keccak256(
            bytes.concat(
                keccak256(
                    abi.encode(
                        _vault,
                        _totalValue,
                        _cumulativeLidoFees,
                        _liabilityShares,
                        _slashingReserve
                    )
                )
            )
        );
        if (!MerkleProof.verify(_proof, _storage().vaultsDataTreeRoot, leaf)) revert InvalidProof();

        int256 inOutDelta;
        (_totalValue, inOutDelta) = _handleSanityChecks(_vault, _totalValue);

        _vaultHub().applyVaultReport(
            _vault,
            _storage().vaultsDataTimestamp,
            _totalValue,
            inOutDelta,
            _cumulativeLidoFees,
            _liabilityShares,
            _slashingReserve
        );
    }

    /// @notice handle sanity checks for the vault lazy report data
    /// @param _vault the address of the vault
    /// @param _totalValue the total value of the vault in refSlot
    /// @return totalValueWithoutQuarantine the smoothed total value of the vault after sanity checks
    /// @return inOutDeltaOnRefSlot the inOutDelta in the refSlot
    function _handleSanityChecks(
        address _vault,
        uint256 _totalValue
    ) public returns (uint256 totalValueWithoutQuarantine, int256 inOutDeltaOnRefSlot) {
        VaultHub vaultHub = _vaultHub();
        VaultHub.VaultRecord memory record = vaultHub.vaultRecord(_vault);

        // 1. Calculate inOutDelta in the refSlot
        int256 currentInOutDelta = record.inOutDelta.value;
        inOutDeltaOnRefSlot = vaultHub.inOutDeltaAsOfLastRefSlot(_vault);

        // 2. Sanity check for total value increase
        totalValueWithoutQuarantine = _processTotalValue(_vault, _totalValue, inOutDeltaOnRefSlot, record);

        // 3. Sanity check for dynamic total value underflow
        if (int256(totalValueWithoutQuarantine) + currentInOutDelta - inOutDeltaOnRefSlot < 0) {
            revert UnderflowInTotalValueCalculation();
        }
    }

    function _processTotalValue(
        address _vault,
        uint256 _reportedTotalValue,
        int256 _inOutDeltaOnRefSlot,
        VaultHub.VaultRecord memory record
    ) internal returns (uint256 totalValueWithoutQuarantine) {
        if (_reportedTotalValue > MAX_SANE_TOTAL_VALUE) {
            revert TotalValueTooLarge();
        }

        Storage storage $ = _storage();

        // total value from the previous report with inOutDelta correction till the current refSlot
        // it does not include CL difference and EL rewards for the period
        uint256 onchainTotalValueOnRefSlot =
            uint256(int256(uint256(record.report.totalValue)) + _inOutDeltaOnRefSlot - record.report.inOutDelta);
        // some percentage of funds hasn't passed through the vault's balance is allowed for the EL and CL rewards handling
        uint256 maxSaneTotalValue = onchainTotalValueOnRefSlot *
            (TOTAL_BASIS_POINTS + $.maxRewardRatioBP) / TOTAL_BASIS_POINTS;

        if (_reportedTotalValue > maxSaneTotalValue) {
            Quarantine storage q = $.vaultQuarantines[_vault];
            uint64 reportTs = $.vaultsDataTimestamp;
            uint128 quarDelta = q.pendingTotalValueIncrease;
            uint128 delta = uint128(_reportedTotalValue - onchainTotalValueOnRefSlot);

            if (quarDelta == 0) { // first overlimit report
                _reportedTotalValue = onchainTotalValueOnRefSlot;
                q.pendingTotalValueIncrease = delta;
                q.startTimestamp = reportTs;
                emit QuarantinedDeposit(_vault, delta);
            } else if (reportTs - q.startTimestamp < $.quarantinePeriod) { // quarantine not expired
                _reportedTotalValue = onchainTotalValueOnRefSlot;
            } else if (delta <= quarDelta + onchainTotalValueOnRefSlot * $.maxRewardRatioBP / TOTAL_BASIS_POINTS) { // quarantine expired
                q.pendingTotalValueIncrease = 0;
                emit QuarantineExpired(_vault, delta);
            } else { // start new quarantine
                _reportedTotalValue = onchainTotalValueOnRefSlot + quarDelta;
                q.pendingTotalValueIncrease = delta - quarDelta;
                q.startTimestamp = reportTs;
                emit QuarantinedDeposit(_vault, delta - quarDelta);
            }
        }

        return _reportedTotalValue;
    }

    function _updateSanityParams(uint64 _quarantinePeriod, uint16 _maxRewardRatioBP) internal {
        Storage storage $ = _storage();
        $.quarantinePeriod = _quarantinePeriod;
        $.maxRewardRatioBP = _maxRewardRatioBP;
        emit SanityParamsUpdated(_quarantinePeriod, _maxRewardRatioBP);
    }

    function _mintableStETH(address _vault) internal view returns (uint256) {
        VaultHub vaultHub = _vaultHub();
        uint256 maxLockableValue = vaultHub.maxLockableValue(_vault);
        uint256 reserveRatioBP = vaultHub.vaultConnection(_vault).reserveRatioBP;
        uint256 mintableStETHByRR = maxLockableValue * (TOTAL_BASIS_POINTS - reserveRatioBP) / TOTAL_BASIS_POINTS;

        uint256 effectiveShareLimit = _operatorGrid().effectiveShareLimit(_vault);
        uint256 mintableStEthByShareLimit = ILido(LIDO_LOCATOR.lido()).getPooledEthBySharesRoundUp(effectiveShareLimit);

        return Math256.min(mintableStETHByRR, mintableStEthByShareLimit);
    }

    function _storage() internal pure returns (Storage storage $) {
        assembly {
            $.slot := LAZY_ORACLE_STORAGE_LOCATION
        }
    }

    function _vaultHub() internal view returns (VaultHub) {
        return VaultHub(payable(LIDO_LOCATOR.vaultHub()));
    }

    function _operatorGrid() internal view returns (OperatorGrid) {
        return OperatorGrid(LIDO_LOCATOR.operatorGrid());
    }

    event VaultsReportDataUpdated(uint256 indexed timestamp, bytes32 indexed root, string cid);
    event QuarantinedDeposit(address indexed vault, uint128 delta);
    event SanityParamsUpdated(uint64 quarantinePeriod, uint16 maxRewardRatioBP);
    event QuarantineExpired(address indexed vault, uint128 delta);
    error AdminCannotBeZero();
    error NotAuthorized();
    error InvalidProof();
    error UnderflowInTotalValueCalculation();
    error TotalValueTooLarge();
}

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

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

import {IHashConsensus} from "contracts/common/interfaces/IHashConsensus.sol";

library RefSlotCache {
    struct Int112WithRefSlotCache {
        int112 value;
        int112 valueOnRefSlot;
        uint32 refSlot;
    }
    struct Uint112WithRefSlotCache {
        uint112 value;
        uint112 valueOnRefSlot;
        uint32 refSlot;
    }

    /// @notice Increases the value and caches the previous value for the current refSlot
    /// @param _storage The storage slot to update
    /// @param _consensus The consensus contract to get the current refSlot
    /// @param _increment increment the value by this amount
    /// @return the updated struct to be saved in storage
    function withValueIncrease(
        Uint112WithRefSlotCache storage _storage,
        IHashConsensus _consensus,
        uint112 _increment
    ) internal view returns (Uint112WithRefSlotCache memory) {
        (uint256 refSlot, ) = _consensus.getCurrentFrame();

        Uint112WithRefSlotCache memory newStorage = _storage;

        if (newStorage.refSlot != uint32(refSlot)) { // 32 bits is enough precision for this kind of comparison
            newStorage.valueOnRefSlot = _storage.value;
            newStorage.refSlot = uint32(refSlot);
        }

        newStorage.value += _increment;

        return newStorage;
    }

    /// @notice Increases the value and caches the previous value for the current refSlot
    /// @param _storage The storage slot to update
    /// @param _consensus The consensus contract to get the current refSlot
    /// @param _increment increment the value by this amount
    /// @return the updated struct to be saved in storage
    function withValueIncrease(
        Int112WithRefSlotCache storage _storage,
        IHashConsensus _consensus,
        int112 _increment
    ) internal view returns (Int112WithRefSlotCache memory) {
        (uint256 refSlot, ) = _consensus.getCurrentFrame();

        Int112WithRefSlotCache memory newStorage = _storage;

        if (newStorage.refSlot != uint32(refSlot)) { // 32 bits is enough precision for this kind of comparison
            newStorage.valueOnRefSlot = _storage.value;
            newStorage.refSlot = uint32(refSlot);
        }

        newStorage.value += _increment;

        return newStorage;
    }

    /// @notice Returns the value for the current refSlot
    /// @param _storage the storage pointer for the cached value
    /// @param _consensus the consensus contract to get the current refSlot
    /// @return the cached value if it's changed since the last refSlot, the current value otherwise
    function getValueForLastRefSlot(
        Uint112WithRefSlotCache storage _storage,
        IHashConsensus _consensus
    ) internal view returns (uint112) {
        (uint256 refSlot, ) = _consensus.getCurrentFrame();
        if (uint32(refSlot) > _storage.refSlot) {
            return _storage.value;
        } else {
            return _storage.valueOnRefSlot;
        }
    }

    /// @notice Returns the value for the current refSlot
    /// @param _storage the storage pointer for the cached value
    /// @param _consensus the consensus contract to get the current refSlot
    /// @return the cached value if it's changed since the last refSlot, the current value otherwise
    function getValueForLastRefSlot(
        Int112WithRefSlotCache storage _storage,
        IHashConsensus _consensus
    ) internal view returns (int112) {
        (uint256 refSlot, ) = _consensus.getCurrentFrame();
        if (uint32(refSlot) > _storage.refSlot) {
            return _storage.value;
        } else {
            return _storage.valueOnRefSlot;
        }
    }
}

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

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

import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";

import {Math256} from "contracts/common/lib/Math256.sol";
import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol";

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

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

struct TierParams {
    uint256 shareLimit;
    uint256 reserveRatioBP;
    uint256 forcedRebalanceThresholdBP;
    uint256 infraFeeBP;
    uint256 liquidityFeeBP;
    uint256 reservationFeeBP;
}

/**
 * @title OperatorGrid
 * @author loga4
 * @notice
 * OperatorGrid is a contract that manages mint parameters for vaults when they are connected to the VaultHub.
 * These parameters include:
 * - shareLimit: maximum amount of shares that can be minted
 * - reserveRatioBP: reserve ratio in basis points
 * - forcedRebalanceThresholdBP: forced rebalance threshold in basis points
 * - infraFeeBP: infra fee in basis points
 * - liquidityFeeBP: liquidity fee in basis points
 * - reservationFeeBP: reservation fee in basis points
 *
 * These parameters are determined by the Tier in which the Vault is registered.
 *
 */
contract OperatorGrid is AccessControlEnumerableUpgradeable, Confirmable2Addresses {
    /*
      Key concepts:
      1. Default Registration:
         - All Vaults initially have default tier (DEFAULT_TIER_ID = 0)
         - The default tier has no group

         DEFAULT_TIER_ID = 0
        ┌──────────────────────┐
        │        Tier 1        │
        │  tierShareLimit = z  │
        │  Vault_1 ... Vault_m │
        └──────────────────────┘

       2. Tier Change Process:
         - To predefine vaults tier or modify the existing vault's connection parameters to VaultHub, a tier change must be requested
         - Both vault owner and node operator must confirm the change (doesn't matter who confirms first)
         - The confirmation has an expiry time (default 1 hour)

       3. Tier Reset:
         - When a vault is disconnected from VaultHub, its tier is automatically reset to the default tier (DEFAULT_TIER_ID)

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

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

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

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

    uint256 public constant DEFAULT_TIER_ID = 0;

    // Special address to denote that default tier is not linked to any real operator
    address public constant DEFAULT_TIER_OPERATOR = address(uint160(type(uint160).max));

    /// @dev basis points base
    uint256 internal constant TOTAL_BASIS_POINTS = 100_00;
    /// @dev max value for fees in basis points - it's about 650%
    uint256 internal constant MAX_FEE_BP = type(uint16).max;

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

    struct Tier {
        address operator;
        uint96 shareLimit;
        uint96 liabilityShares;
        uint16 reserveRatioBP;
        uint16 forcedRebalanceThresholdBP;
        uint16 infraFeeBP;
        uint16 liquidityFeeBP;
        uint16 reservationFeeBP;
    }

    /**
     * @notice ERC-7201 storage namespace for the OperatorGrid
     * @dev ERC-7201 namespace is used to prevent upgrade collisions
     * @custom:storage-location erc7201:Lido.Vaults.OperatorGrid
     * @custom:tiers Tiers
     * @custom:vaultTier Vault tier
     * @custom:groups Groups
     * @custom:nodeOperators Node operators
     */
    struct ERC7201Storage {
        Tier[] tiers;
        mapping(address vault => uint256 tierId) vaultTier;
        mapping(address nodeOperator => Group) groups;
        address[] nodeOperators;
    }

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


    /// @notice Initializes the contract with a LidoLocator
    /// @param _locator LidoLocator contract
    constructor(ILidoLocator _locator) {
        LIDO_LOCATOR = _locator;

        _disableInitializers();
    }

    /// @notice Initializes the contract with an admin
    /// @param _admin Address of the admin
    /// @param _defaultTierParams Default tier params for the default tier
    function initialize(address _admin, TierParams calldata _defaultTierParams) external initializer {
        if (_admin == address(0)) revert ZeroArgument("_admin");

        __AccessControlEnumerable_init();
        __Confirmations_init();
        _grantRole(DEFAULT_ADMIN_ROLE, _admin);

        ERC7201Storage storage $ = _getStorage();

        //create default tier with default share limit
        $.tiers.push(
            Tier({
                operator: DEFAULT_TIER_OPERATOR,
                shareLimit: uint96(_defaultTierParams.shareLimit),
                reserveRatioBP: uint16(_defaultTierParams.reserveRatioBP),
                forcedRebalanceThresholdBP: uint16(_defaultTierParams.forcedRebalanceThresholdBP),
                infraFeeBP: uint16(_defaultTierParams.infraFeeBP),
                liquidityFeeBP: uint16(_defaultTierParams.liquidityFeeBP),
                reservationFeeBP: uint16(_defaultTierParams.reservationFeeBP),
                liabilityShares: 0
            })
        );
    }

    /// @notice Registers a new group
    /// @param _nodeOperator address of the node operator
    /// @param _shareLimit Maximum share limit for the group
    function registerGroup(address _nodeOperator, uint256 _shareLimit) external onlyRole(REGISTRY_ROLE) {
        if (_nodeOperator == address(0)) revert ZeroArgument("_nodeOperator");

        ERC7201Storage storage $ = _getStorage();
        if ($.groups[_nodeOperator].operator != address(0)) revert GroupExists();

        $.groups[_nodeOperator] = Group({
            operator: _nodeOperator,
            shareLimit: uint96(_shareLimit),
            liabilityShares: 0,
            tierIds: new uint256[](0)
        });
        $.nodeOperators.push(_nodeOperator);

        emit GroupAdded(_nodeOperator, uint96(_shareLimit));
    }

    /// @notice Updates the share limit of a group
    /// @param _nodeOperator address of the node operator
    /// @param _shareLimit New share limit value
    function updateGroupShareLimit(address _nodeOperator, uint256 _shareLimit) external onlyRole(REGISTRY_ROLE) {
        if (_nodeOperator == address(0)) revert ZeroArgument("_nodeOperator");

        ERC7201Storage storage $ = _getStorage();
        Group storage group_ = $.groups[_nodeOperator];
        if (group_.operator == address(0)) revert GroupNotExists();

        group_.shareLimit = uint96(_shareLimit);

        emit GroupShareLimitUpdated(_nodeOperator, uint96(_shareLimit));
    }

    /// @notice Returns a group by node operator address
    /// @param _nodeOperator address of the node operator
    /// @return Group
    function group(address _nodeOperator) external view returns (Group memory) {
        return _getStorage().groups[_nodeOperator];
    }

    /// @notice Returns a node operator address by index
    /// @param _index index of the node operator
    /// @return Node operator address
    function nodeOperatorAddress(uint256 _index) external view returns (address) {
        ERC7201Storage storage $ = _getStorage();
        if (_index >= $.nodeOperators.length) revert NodeOperatorNotExists();
        return $.nodeOperators[_index];
    }

    /// @notice Returns a node operator count
    /// @return Node operator count
    function nodeOperatorCount() external view returns (uint256) {
        return _getStorage().nodeOperators.length;
    }

    /// @notice Registers a new tier
    /// @param _nodeOperator address of the node operator
    /// @param _tiers array of tiers to register
    function registerTiers(
        address _nodeOperator,
        TierParams[] calldata _tiers
    ) external onlyRole(REGISTRY_ROLE) {
        if (_nodeOperator == address(0)) revert ZeroArgument("_nodeOperator");

        ERC7201Storage storage $ = _getStorage();
        Group storage group_ = $.groups[_nodeOperator];
        if (group_.operator == address(0)) revert GroupNotExists();

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

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

            emit TierAdded(
                _nodeOperator,
                tierId,
                uint96(tier_.shareLimit),
                uint16(tier_.reserveRatioBP),
                uint16(tier_.forcedRebalanceThresholdBP),
                uint16(tier_.infraFeeBP),
                uint16(tier_.liquidityFeeBP),
                uint16(tier_.reservationFeeBP)
            );

            tierId++;
        }
    }

    /// @notice Returns a tier by ID
    /// @param _tierId id of the tier
    /// @return Tier
    function tier(uint256 _tierId) external view returns (Tier memory) {
        ERC7201Storage storage $ = _getStorage();
        if (_tierId >= $.tiers.length) revert TierNotExists();
        return $.tiers[_tierId];
    }

    /// @notice Returns a tiers count
    /// @return Tiers count
    function tiersCount() external view returns (uint256) {
        return _getStorage().tiers.length;
    }

    /// @notice Alters multiple tiers
    /// @dev We do not enforce to update old vaults with the new tier params, only new ones.
    /// @param _tierIds array of tier ids to alter
    /// @param _tierParams array of new tier params
    function alterTiers(
        uint256[] calldata _tierIds,
        TierParams[] calldata _tierParams
    ) external onlyRole(REGISTRY_ROLE) {
        if (_tierIds.length != _tierParams.length) revert ArrayLengthMismatch();

        ERC7201Storage storage $ = _getStorage();
        uint256 length = _tierIds.length;
        uint256 tiersLength = $.tiers.length;

        for (uint256 i = 0; i < length; i++) {
            if (_tierIds[i] >= tiersLength) revert TierNotExists();

            _validateParams(
                _tierIds[i],
                _tierParams[i].reserveRatioBP,
                _tierParams[i].forcedRebalanceThresholdBP,
                _tierParams[i].infraFeeBP,
                _tierParams[i].liquidityFeeBP,
                _tierParams[i].reservationFeeBP
            );

            Tier storage tier_ = $.tiers[_tierIds[i]];

            tier_.shareLimit = uint96(_tierParams[i].shareLimit);
            tier_.reserveRatioBP = uint16(_tierParams[i].reserveRatioBP);
            tier_.forcedRebalanceThresholdBP = uint16(_tierParams[i].forcedRebalanceThresholdBP);
            tier_.infraFeeBP = uint16(_tierParams[i].infraFeeBP);
            tier_.liquidityFeeBP = uint16(_tierParams[i].liquidityFeeBP);
            tier_.reservationFeeBP = uint16(_tierParams[i].reservationFeeBP);

            emit TierUpdated(
                _tierIds[i],
                tier_.shareLimit,
                tier_.reserveRatioBP,
                tier_.forcedRebalanceThresholdBP,
                tier_.infraFeeBP,
                tier_.liquidityFeeBP,
                tier_.reservationFeeBP
            );
        }
    }

    /// @notice Vault tier change with multi-role confirmation
    /// @param _vault address of the vault
    /// @param _requestedTierId id of the tier
    /// @param _requestedShareLimit share limit to set
    /// @return bool Whether the tier change was confirmed.
    /*

    Legend:
    V = Vault1.liabilityShares
    LS = liabilityShares

    Scheme1 - transfer Vault from default tier to Tier2

                                         ┌──────────────────────────────┐
                                         │           Group 1            │
                                         │                              │
    ┌────────────────────┐               │  ┌─────────┐  ┌───────────┐  │
    │  Tier 1 (default)  │   confirm     │  │ Tier 2  │  │ Tier 3    │  │
    │  LS: -V            │    ─────>     │  │ LS:+V   │  │           │  │
    └────────────────────┘               │  └─────────┘  └───────────┘  │
                                         │                              │
                                         │   Group1.liabilityShares: +V │
                                         └──────────────────────────────┘

    After confirmation:
    - Tier 1.liabilityShares   = -V
    - Tier 2.liabilityShares   = +V
    - Group1.liabilityShares   = +V

    --------------------------------------------------------------------------
    Scheme2 - transfer Vault from Tier2 to Tier3, no need to change group minted shares

    ┌────────────────────────────────┐     ┌────────────────────────────────┐
    │           Group 1              │     │           Group 2              │
    │                                │     │                                │
    │  ┌───────────┐  ┌───────────┐  │     │  ┌───────────┐                 │
    │  │ Tier 2    │  │ Tier 3    │  │     │  │ Tier 4    │                 │
    │  │ LS:-V     │  │ LS:+V     │  │     │  │           │                 │
    │  └───────────┘  └───────────┘  │     │  └───────────┘                 │
    │  operator1                     │     │  operator2                     │
    └────────────────────────────────┘     └────────────────────────────────┘

    After confirmation:
    - Tier 2.liabilityShares   = -V
    - Tier 3.liabilityShares   = +V

    NB: Cannot change from Tier2 to Tier1, because Tier1 has no group
    NB: Cannot change from Tier2 to Tier4, because Tier4 has different operator.

    */
    function changeTier(address _vault, uint256 _requestedTierId, uint256 _requestedShareLimit) external returns (bool) {
        if (_vault == address(0)) revert ZeroArgument("_vault");

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

        VaultHub vaultHub = _vaultHub();
        bool isVaultConnected = vaultHub.isVaultConnected(_vault);

        address vaultOwner = isVaultConnected
            ? vaultHub.vaultConnection(_vault).owner
            : IStakingVault(_vault).owner();

        address nodeOperator = IStakingVault(_vault).nodeOperator();

        uint256 vaultTierId = $.vaultTier[_vault];
        if (vaultTierId == _requestedTierId) revert TierAlreadySet();

        Tier storage requestedTier = $.tiers[_requestedTierId];
        if (nodeOperator != requestedTier.operator) revert TierNotInOperatorGroup();
        if (_requestedShareLimit > requestedTier.shareLimit) revert RequestedShareLimitTooHigh(_requestedShareLimit, requestedTier.shareLimit);

        // store the caller's confirmation; only proceed if the required number of confirmations is met.
        if (!_collectAndCheckConfirmations(msg.data, vaultOwner, nodeOperator)) return false;
        uint256 vaultLiabilityShares = vaultHub.liabilityShares(_vault);

        //check if tier limit is exceeded
        if (requestedTier.liabilityShares + vaultLiabilityShares > requestedTier.shareLimit) revert TierLimitExceeded();

        // if the vault was in the default tier:
        // - that mean that the vault has no group, so we decrease only the minted shares of the default tier
        // - but need to check requested group limit exceeded
        if (vaultTierId == DEFAULT_TIER_ID) {
            Group storage requestedGroup = $.groups[nodeOperator];
            if (requestedGroup.liabilityShares + vaultLiabilityShares > requestedGroup.shareLimit) {
                revert GroupLimitExceeded();
            }
            requestedGroup.liabilityShares += uint96(vaultLiabilityShares);
        }

        Tier storage currentTier = $.tiers[vaultTierId];

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

        $.vaultTier[_vault] = _requestedTierId;

        // Vault may not be connected to VaultHub yet.
        // There are two possible flows:
        // 1. Vault is created and connected to VaultHub immediately with the default tier.
        //    In this case, `VaultConnection` is non-zero and updateConnection must be called.
        // 2. Vault is created, its tier is changed before connecting to VaultHub.
        //    In this case, `VaultConnection` is still zero, and updateConnection must be skipped.
        // Hence, we update the VaultHub connection only if the vault is already connected.
        vaultHub.updateConnection(
            _vault,
            _requestedShareLimit,
            requestedTier.reserveRatioBP,
            requestedTier.forcedRebalanceThresholdBP,
            requestedTier.infraFeeBP,
            requestedTier.liquidityFeeBP,
            requestedTier.reservationFeeBP
        );

        emit TierChanged(_vault, _requestedTierId, _requestedShareLimit);

        return true;
    }

    /// @notice Reset vault's tier to default
    /// @param _vault address of the vault
    /// @dev Requires vault's liabilityShares to be zero before resetting the tier
    function resetVaultTier(address _vault) external {
        if (msg.sender != LIDO_LOCATOR.vaultHub()) revert NotAuthorized("resetVaultTier", msg.sender);

        ERC7201Storage storage $ = _getStorage();

        if ($.vaultTier[_vault] != DEFAULT_TIER_ID) {
            $.vaultTier[_vault] = DEFAULT_TIER_ID;

            emit TierChanged(_vault, DEFAULT_TIER_ID, $.tiers[DEFAULT_TIER_ID].shareLimit);
        }
    }

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

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

        ERC7201Storage storage $ = _getStorage();

        uint256 tierId = $.vaultTier[_vault];
        Tier storage tier_ = $.tiers[tierId];

        uint96 tierLiabilityShares = tier_.liabilityShares;
        if (tierLiabilityShares + _amount > tier_.shareLimit) revert TierLimitExceeded();

        tier_.liabilityShares = tierLiabilityShares + uint96(_amount);

        if (tierId != DEFAULT_TIER_ID) {
            Group storage group_ = $.groups[tier_.operator];
            uint96 groupMintedShares = group_.liabilityShares;
            if (groupMintedShares + _amount > group_.shareLimit) revert GroupLimitExceeded();

            group_.liabilityShares = groupMintedShares + uint96(_amount);
        }
    }

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

        ERC7201Storage storage $ = _getStorage();

        uint256 tierId = $.vaultTier[_vault];

        Tier storage tier_ = $.tiers[tierId];

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

        tier_.liabilityShares -= uint96(_amount);

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

    /// @notice Get vault limits
    /// @param _vault address of the vault
    /// @return nodeOperator node operator of the vault
    /// @return tierId tier id of the vault
    /// @return shareLimit share limit of the vault
    /// @return reserveRatioBP reserve ratio of the vault
    /// @return forcedRebalanceThresholdBP forced rebalance threshold of the vault
    /// @return infraFeeBP infra fee of the vault
    /// @return liquidityFeeBP liquidity fee of the vault
    /// @return reservationFeeBP reservation fee of the vault
    function vaultInfo(address _vault)
        external
        view
        returns (
            address nodeOperator,
            uint256 tierId,
            uint256 shareLimit,
            uint256 reserveRatioBP,
            uint256 forcedRebalanceThresholdBP,
            uint256 infraFeeBP,
            uint256 liquidityFeeBP,
            uint256 reservationFeeBP
        )
    {
        ERC7201Storage storage $ = _getStorage();

        tierId = $.vaultTier[_vault];

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

        shareLimit = t.shareLimit;
        reserveRatioBP = t.reserveRatioBP;
        forcedRebalanceThresholdBP = t.forcedRebalanceThresholdBP;
        infraFeeBP = t.infraFeeBP;
        liquidityFeeBP = t.liquidityFeeBP;
        reservationFeeBP = t.reservationFeeBP;
    }

    /// @notice Returns the effective share limit of a vault according to the OperatorGrid and vault share limits
    /// @param _vault address of the vault
    /// @return shareLimit effective share limit of the vault
    function effectiveShareLimit(address _vault) public view returns (uint256) {
        VaultHub vaultHub = _vaultHub();
        uint256 shareLimit = vaultHub.vaultConnection(_vault).shareLimit;
        uint256 liabilityShares = vaultHub.liabilityShares(_vault);

        uint256 gridShareLimit = _gridRemainingShareLimit(_vault) + liabilityShares;
        return Math256.min(gridShareLimit, shareLimit);
    }

    /// @notice Returns the remaining share limit in a given tier and group
    /// @param _vault address of the vault
    /// @return remaining share limit
    /// @dev remaining share limit inherits the limits of the vault tier and group,
    ///      and accounts liabilities of other vaults belonging to the same tier and group
    function _gridRemainingShareLimit(address _vault) internal view returns (uint256) {
        ERC7201Storage storage $ = _getStorage();
        uint256 tierId = $.vaultTier[_vault];
        Tier storage t = $.tiers[tierId];

        uint256 tierLimit = t.shareLimit;
        uint256 tierRemaining = tierLimit > t.liabilityShares ? tierLimit - t.liabilityShares : 0;

        if (tierId == DEFAULT_TIER_ID) return tierRemaining;

        Group storage g = $.groups[t.operator];
        uint256 groupLimit = g.shareLimit;
        uint256 groupRemaining = groupLimit > g.liabilityShares ? groupLimit - g.liabilityShares : 0;
        return Math256.min(tierRemaining, groupRemaining);
    }

    /// @notice Validates tier parameters
    /// @param _reserveRatioBP Reserve ratio
    /// @param _forcedRebalanceThresholdBP Forced rebalance threshold
    /// @param _infraFeeBP Infra fee
    /// @param _liquidityFeeBP Liquidity fee
    /// @param _reservationFeeBP Reservation fee
    function _validateParams(
      uint256 _tierId,
      uint256 _reserveRatioBP,
      uint256 _forcedRebalanceThresholdBP,
      uint256 _infraFeeBP,
      uint256 _liquidityFeeBP,
      uint256 _reservationFeeBP
    ) internal pure {
        if (_reserveRatioBP == 0) revert ZeroArgument("_reserveRatioBP");
        if (_reserveRatioBP > TOTAL_BASIS_POINTS)
            revert ReserveRatioTooHigh(_tierId, _reserveRatioBP, TOTAL_BASIS_POINTS);

        if (_forcedRebalanceThresholdBP == 0) revert ZeroArgument("_forcedRebalanceThresholdBP");
        if (_forcedRebalanceThresholdBP > _reserveRatioBP)
            revert ForcedRebalanceThresholdTooHigh(_tierId, _forcedRebalanceThresholdBP, _reserveRatioBP);

        if (_infraFeeBP > MAX_FEE_BP)
            revert InfraFeeTooHigh(_tierId, _infraFeeBP, MAX_FEE_BP);

        if (_liquidityFeeBP > MAX_FEE_BP)
            revert LiquidityFeeTooHigh(_tierId, _liquidityFeeBP, MAX_FEE_BP);

        if (_reservationFeeBP > MAX_FEE_BP)
            revert ReservationFeeTooHigh(_tierId, _reservationFeeBP, MAX_FEE_BP);
    }

    function _vaultHub() internal view returns (VaultHub) {
        return VaultHub(payable(LIDO_LOCATOR.vaultHub()));
    }

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

    // -----------------------------
    //            EVENTS
    // -----------------------------
    event GroupAdded(address indexed nodeOperator, uint256 shareLimit);
    event GroupShareLimitUpdated(address indexed nodeOperator, uint256 shareLimit);
    event TierAdded(
        address indexed nodeOperator,
        uint256 indexed tierId,
        uint256 shareLimit,
        uint256 reserveRatioBP,
        uint256 forcedRebalanceThresholdBP,
        uint256 infraFeeBP,
        uint256 liquidityFeeBP,
        uint256 reservationFeeBP
    );
    event TierChanged(address indexed vault, uint256 indexed tierId, uint256 shareLimit);
    event TierUpdated(
      uint256 indexed tierId,
      uint256 shareLimit,
      uint256 reserveRatioBP,
      uint256 forcedRebalanceThresholdBP,
      uint256 infraFeeBP,
      uint256 liquidityFeeBP,
      uint256 reservationFeeBP
    );

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

    error TierNotExists();
    error TierAlreadySet();
    error TierNotInOperatorGroup();
    error CannotChangeToDefaultTier();

    error ReserveRatioTooHigh(uint256 tierId, uint256 reserveRatioBP, uint256 maxReserveRatioBP);
    error ForcedRebalanceThresholdTooHigh(uint256 tierId, uint256 forcedRebalanceThresholdBP, uint256 reserveRatioBP);
    error InfraFeeTooHigh(uint256 tierId, uint256 infraFeeBP, uint256 maxInfraFeeBP);
    error LiquidityFeeTooHigh(uint256 tierId, uint256 liquidityFeeBP, uint256 maxLiquidityFeeBP);
    error ReservationFeeTooHigh(uint256 tierId, uint256 reservationFeeBP, uint256 maxReservationFeeBP);
    error ArrayLengthMismatch();
    error RequestedShareLimitTooHigh(uint256 requestedShareLimit, uint256 tierShareLimit);
}

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

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

import {Math256} from "contracts/common/lib/Math256.sol";
import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol";
import {ILido} from "contracts/common/interfaces/ILido.sol";
import {IHashConsensus} from "contracts/common/interfaces/IHashConsensus.sol";

import {IStakingVault} from "./interfaces/IStakingVault.sol";
import {IPredepositGuarantee} from "./interfaces/IPredepositGuarantee.sol";

import {OperatorGrid} from "./OperatorGrid.sol";
import {LazyOracle} from "./LazyOracle.sol";

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

/// @notice VaultHub is a contract that manages StakingVaults connected to the Lido protocol
/// It allows to connect and disconnect vaults, mint and burn stETH using vaults as collateral
/// Also, it facilitates the individual per-vault reports from the lazy oracle to the vaults and charges Lido fees
/// @author folkyatina
contract VaultHub is PausableUntilWithRoles {
    using RefSlotCache for RefSlotCache.Uint112WithRefSlotCache;
    using RefSlotCache for RefSlotCache.Int112WithRefSlotCache;

    // -----------------------------
    //           STORAGE STRUCTS
    // -----------------------------
    /// @custom:storage-location erc7201:VaultHub
    struct Storage {
        /// @notice vault proxy contract codehashes allowed for connecting
        mapping(bytes32 codehash => bool allowed) codehashes;
        /// @notice accounting records for each vault
        mapping(address vault => VaultRecord) records;
        /// @notice connection parameters for each vault
        mapping(address vault => VaultConnection) connections;
        /// @notice obligation values for each vault
        mapping(address vault => VaultObligations) obligations;
        /// @notice 1-based array of vaults connected to the hub. index 0 is reserved for not connected vaults
        address[] vaults;
        /// @notice amount of bad debt that was internalized from the vault to become the protocol loss
        RefSlotCache.Uint112WithRefSlotCache badDebtToInternalize;
    }

    struct VaultConnection {
        // ### 1st slot
        /// @notice address of the vault owner
        address owner;
        /// @notice maximum number of stETH shares that can be minted by vault owner
        uint96 shareLimit;
        // ### 2nd slot
        /// @notice index of the vault in the list of vaults. Indexes is guaranteed to be stable only if there was no deletions.
        /// @dev vaultIndex is always greater than 0
        uint96 vaultIndex;
        /// @notice if true, vault is disconnected and fee is not accrued
        bool pendingDisconnect;
        /// @notice share of ether that is locked on the vault as an additional reserve
        /// e.g RR=30% means that for 1stETH minted 1/(1-0.3)=1.428571428571428571 ETH is locked on the vault
        uint16 reserveRatioBP;
        /// @notice if vault's reserve decreases to this threshold, it should be force rebalanced
        uint16 forcedRebalanceThresholdBP;
        /// @notice infra fee in basis points
        uint16 infraFeeBP;
        /// @notice liquidity fee in basis points
        uint16 liquidityFeeBP;
        /// @notice reservation fee in basis points
        uint16 reservationFeeBP;
        /// @notice if true, vault owner manually paused the beacon chain deposits
        bool isBeaconDepositsManuallyPaused;
        /// 64 bits gap
    }

    struct VaultRecord {
        // ### 1st slot
        /// @notice latest report for the vault
        Report report;
        // ### 2nd slot
        /// @notice amount of ether that is locked from withdrawal on the vault
        uint128 locked;
        /// @notice liability shares of the vault
        uint96 liabilityShares;
        // ### 3rd slot
        /// @notice inOutDelta of the vault (all deposits - all withdrawals)
        RefSlotCache.Int112WithRefSlotCache inOutDelta;
    }

    struct Report {
        /// @notice total value of the vault
        uint112 totalValue;
        /// @notice inOutDelta of the report
        int112 inOutDelta;
        /// @notice last 32 bits of the timestamp (in seconds)
        uint32 timestamp;
    }

    /**
     *  Obligations of the vaults towards the Lido protocol.
     *  While any part of those obligations remains unsettled, VaultHub may want to limit what the vault can do.
     *
     *  Obligations have two types:
     *  1. Redemptions. Under extreme conditions Lido protocol may rebalance the part of the vault's liability to serve
     *     the Lido Core withdrawal queue requests to guarantee that every stETH is redeemable. Calculated in ether.
     *  2. Lido fees. Record of infra, liquidity and reservation fees charged to the vault. Charged in ether on every
     *     oracle report.
     *
     *  Obligations settlement:
     *  - Lido fees are settled by transferring ether to the Lido protocol treasury
     *  - Redemptions are settled by rebalancing the vault or by burning stETH on the vault
     *  - Obligations may be settled manually using the `settleVaultObligations` function
     *  - Obligations try to automatically settle:
     *    - every time oracle report is applied to the vault
     *    - on resume of the beacon chain deposits
     *    - on disconnect initiation
     *  - Lido fees are automatically settled on the final report that completes the disconnection process
     *
     *  Constraints until obligations settled:
     *  - Beacon chain deposits are paused while unsettled obligations ≥ OBLIGATIONS_THRESHOLD (1 ETH)
     *  - Unsettled obligations can't be withdrawn
     *  - Minting new stETH is limited by unsettled Lido fees (NB: redemptions do not affect minting capacity)
     *  - Vault disconnect is refused until both unsettled redemptions and Lido fees obligations hit zero
     *
     * @dev NB: Under extreme conditions, Lido protocol may trigger validator exits to withdraw ether to the vault and
     *          rebalance it to settle redemptions.
     */
    struct VaultObligations {
        /// @notice cumulative value for Lido fees that were settled on the vault
        uint128 settledLidoFees;
        /// @notice current unsettled Lido fees amount
        uint128 unsettledLidoFees;
        /// @notice current unsettled redemptions amount
        uint128 redemptions;
    }

    // -----------------------------
    //           CONSTANTS
    // -----------------------------

    // keccak256(abi.encode(uint256(keccak256("VaultHub")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant STORAGE_LOCATION = 0xb158a1a9015c52036ff69e7937a7bb424e82a8c4cbec5c5309994af06d825300;

    /// @notice role that allows to connect vaults to the hub
    bytes32 public constant VAULT_MASTER_ROLE = keccak256("vaults.VaultHub.VaultMasterRole");
    /// @notice role that allows to set allowed codehashes
    bytes32 public constant VAULT_CODEHASH_SET_ROLE = keccak256("vaults.VaultHub.VaultCodehashSetRole");
    /// @notice role that allows to accrue Lido Core redemptions on the vault
    bytes32 public constant REDEMPTION_MASTER_ROLE = keccak256("vaults.VaultHub.RedemptionMasterRole");
    /// @notice role that allows to trigger validator exits under extreme conditions
    bytes32 public constant VALIDATOR_EXIT_ROLE = keccak256("vaults.VaultHub.ValidatorExitRole");
    /// @notice role that allows to bail out vaults with bad debt
    bytes32 public constant BAD_DEBT_MASTER_ROLE = keccak256("vaults.VaultHub.BadDebtMasterRole");
    /// @notice amount of ETH that is locked on the vault on connect and can be withdrawn on disconnect only
    uint256 public constant CONNECT_DEPOSIT = 1 ether;

    /// @notice The time delta for report freshness check
    uint256 public constant REPORT_FRESHNESS_DELTA = 2 days;

    /// @dev basis points base
    uint256 internal constant TOTAL_BASIS_POINTS = 100_00;
    /// @notice length of the validator pubkey in bytes
    uint256 internal constant PUBLIC_KEY_LENGTH = 48;
    /// @dev max value for fees in basis points - it's about 650%
    uint256 internal constant MAX_FEE_BP = type(uint16).max;

    /// @notice codehash of the account with no code
    bytes32 private constant EMPTY_CODEHASH = keccak256("");

    /// @notice no limit for the unsettled obligations on settlement
    uint256 internal constant MAX_UNSETTLED_ALLOWED = type(uint256).max;
    /// @notice threshold for the unsettled obligations that will activate the beacon chain deposits pause
    uint256 internal constant UNSETTLED_THRESHOLD = 1 ether;
    /// @notice no unsettled obligations allowed on settlement
    uint256 internal constant NO_UNSETTLED_ALLOWED = 0;

    // -----------------------------
    //           IMMUTABLES
    // -----------------------------

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

    ILido public immutable LIDO;
    ILidoLocator public immutable LIDO_LOCATOR;
    IHashConsensus public immutable CONSENSUS_CONTRACT;

    /// @param _locator Lido Locator contract
    /// @param _lido Lido stETH contract
    /// @param _consensusContract Hash consensus contract
    /// @param _maxRelativeShareLimitBP Maximum share limit relative to TVL in basis points
    constructor(ILidoLocator _locator, ILido _lido, IHashConsensus _consensusContract, uint256 _maxRelativeShareLimitBP) {
        _requireNotZero(_maxRelativeShareLimitBP);
        _requireLessThanBP(_maxRelativeShareLimitBP, TOTAL_BASIS_POINTS);

        MAX_RELATIVE_SHARE_LIMIT_BP = _maxRelativeShareLimitBP;

        LIDO_LOCATOR = _locator;
        LIDO = _lido;
        CONSENSUS_CONTRACT = _consensusContract;

        _disableInitializers();
    }

    /// @dev used to perform rebalance operations
    receive() external payable {}

    /// @notice initialize the vault hub
    /// @param _admin default admin address
    function initialize(address _admin) external initializer {
        _requireNotZero(_admin);

        __AccessControlEnumerable_init();

        // the stone in the elevator. index 0 is reserved for not connected vaults
        _storage().vaults.push(address(0));

        _grantRole(DEFAULT_ADMIN_ROLE, _admin);
    }

    /// @notice returns the number of vaults connected to the hub
    /// @dev since index 0 is reserved for not connected vaults, it's always 1 less than the vaults array length
    function vaultsCount() external view returns (uint256) {
        return _storage().vaults.length - 1;
    }

    /// @notice returns the vault address by its index
    /// @param _index index of the vault in the 1-based list of vaults. possible range [1, vaultsCount()]
    /// @dev Indexes is guaranteed to be stable only in one transaction.
    function vaultByIndex(uint256 _index) external view returns (address) {
        _requireNotZero(_index);
        return _storage().vaults[_index];
    }

    /// @return connection parameters struct for the given vault
    /// @dev it returns empty struct if the vault is not connected to the hub
    /// @dev it may return connection even if it's pending to be disconnected
    function vaultConnection(address _vault) external view returns (VaultConnection memory) {
        return _vaultConnection(_vault);
    }

    /// @return the accounting record struct for the given vault
    /// @dev it returns empty struct if the vault is not connected to the hub
    function vaultRecord(address _vault) external view returns (VaultRecord memory) {
        return _vaultRecord(_vault);
    }

    /// @return the obligations struct for the given vault
    /// @dev returns empty struct if the vault is not connected to the hub
    function vaultObligations(address _vault) external view returns (VaultObligations memory) {
        return _vaultObligations(_vault);
    }

    /// @return true if the vault is connected to the hub
    function isVaultConnected(address _vault) external view returns (bool) {
        return _vaultConnection(_vault).vaultIndex != 0;
    }

    /// @return total value of the vault
    /// @dev returns 0 if the vault is not connected
    function totalValue(address _vault) external view returns (uint256) {
        return _totalValue(_vaultRecord(_vault));
    }

    /// @return liability shares of the vault
    /// @dev returns 0 if the vault is not connected
    function liabilityShares(address _vault) external view returns (uint256) {
        return _vaultRecord(_vault).liabilityShares;
    }

    /// @return locked amount of ether for the vault
    /// @dev returns 0 if the vault is not connected
    function locked(address _vault) external view returns (uint256) {
        return _vaultRecord(_vault).locked;
    }

    /// @return the amount of ether that can be locked in the vault given the current total value
    /// @dev returns 0 if the vault is not connected
    function maxLockableValue(address _vault) external view returns (uint256) {
        return _maxLockableValue(_vaultRecord(_vault), _vaultObligations(_vault));
    }

    /// @return the amount of ether that can be instantly withdrawn from the staking vault
    /// @dev returns 0 if the vault is not connected
    /// @dev check for `pendingDisconnect = false` before using this function to avoid reverts
    function withdrawableValue(address _vault) external view returns (uint256) {
        return _withdrawableValue(_vault, _vaultRecord(_vault));
    }

    /// @return latest report for the vault
    /// @dev returns empty struct if the vault is not connected
    function latestReport(address _vault) external view returns (Report memory) {
        return _vaultRecord(_vault).report;
    }

    /// @return true if the report for the vault is fresh, false otherwise
    /// @dev returns false if the vault is not connected
    function isReportFresh(address _vault) external view returns (bool) {
        return _isReportFresh(_vaultRecord(_vault));
    }

    /// @notice checks if the vault is healthy by comparing its total value after applying forced rebalance threshold
    ///         against current liability shares
    /// @param _vault vault address
    /// @return true if vault is healthy, false otherwise
    /// @dev returns true if the vault is not connected
    function isVaultHealthy(address _vault) external view returns (bool) {
        return _isVaultHealthy(_vaultConnection(_vault), _vaultRecord(_vault));
    }

    /// @notice calculate shares amount to make the vault healthy using rebalance
    /// @param _vault vault address
    /// @return amount of shares to rebalance or UINT256_MAX if it's impossible to make the vault healthy using rebalance
    /// @dev returns 0 if the vault is not connected
    function rebalanceShortfall(address _vault) external view returns (uint256) {
        return _rebalanceShortfall(_vaultConnection(_vault), _vaultRecord(_vault));
    }

    /// @notice amount of bad debt to be internalized to become the protocol loss
    function badDebtToInternalizeAsOfLastRefSlot() external view returns (uint256) {
        return _storage().badDebtToInternalize.getValueForLastRefSlot(CONSENSUS_CONTRACT);
    }

    /// @notice inOutDelta of the vault as of the last refSlot
    /// @param _vault vault address
    /// @return inOutDelta of the vault as of the last refSlot
    /// @dev returns 0 if the vault is not connected
    function inOutDeltaAsOfLastRefSlot(address _vault) external view returns (int256) {
        return _vaultRecord(_vault).inOutDelta.getValueForLastRefSlot(CONSENSUS_CONTRACT);
    }

    /// @notice Set if a vault proxy codehash is allowed to be connected to the hub
    /// @param _codehash vault proxy codehash
    /// @param _allowed true to add, false to remove
    /// @dev msg.sender must have VAULT_CODEHASH_SET_ROLE
    function setAllowedCodehash(bytes32 _codehash, bool _allowed) external onlyRole(VAULT_CODEHASH_SET_ROLE) {
        _requireNotZero(uint256(_codehash));
        if (_codehash == EMPTY_CODEHASH) revert ZeroCodehash();

        _storage().codehashes[_codehash] = _allowed;

        emit AllowedCodehashUpdated(_codehash, _allowed);
    }

    /// @notice connects a vault to the hub in permissionless way, get limits from the Operator Grid
    /// @param _vault vault address
    /// @dev vault should have transferred ownership to the VaultHub contract
    function connectVault(address _vault) external whenResumed {
        _requireNotZero(_vault);

        IStakingVault vault_ = IStakingVault(_vault);
        if (vault_.pendingOwner() != address(this)) revert VaultHubNotPendingOwner(_vault);
        if (vault_.isOssified()) revert VaultOssified(_vault);
        if (vault_.depositor() != address(_predepositGuarantee())) revert PDGNotDepositor(_vault);

        (
            , // nodeOperatorInTier
            , // tierId
            uint256 shareLimit,
            uint256 reserveRatioBP,
            uint256 forcedRebalanceThresholdBP,
            uint256 infraFeeBP,
            uint256 liquidityFeeBP,
            uint256 reservationFeeBP
        ) = _operatorGrid().vaultInfo(_vault);

        _connectVault(_vault,
            shareLimit,
            reserveRatioBP,
            forcedRebalanceThresholdBP,
            infraFeeBP,
            liquidityFeeBP,
            reservationFeeBP
        );

        IStakingVault(_vault).acceptOwnership();

        emit VaultConnected({
            vault: _vault,
            shareLimit: shareLimit,
            reserveRatioBP: reserveRatioBP,
            forcedRebalanceThresholdBP: forcedRebalanceThresholdBP,
            infraFeeBP: infraFeeBP,
            liquidityFeeBP: liquidityFeeBP,
            reservationFeeBP: reservationFeeBP
        });
    }

    /// @notice updates share limit for the vault
    /// Setting share limit to zero actually pause the vault's ability to mint
    /// @param _vault vault address
    /// @param _shareLimit new share limit
    /// @dev msg.sender must have VAULT_MASTER_ROLE
    function updateShareLimit(address _vault, uint256 _shareLimit) external onlyRole(VAULT_MASTER_ROLE) {
        _requireNotZero(_vault);
        _requireSaneShareLimit(_shareLimit);

        VaultConnection storage connection = _checkConnection(_vault);
        connection.shareLimit = uint96(_shareLimit);

        emit VaultShareLimitUpdated(_vault, _shareLimit);
    }

    /// @notice updates fees for the vault
    /// @param _vault vault address
    /// @param _infraFeeBP new infra fee in basis points
    /// @param _liquidityFeeBP new liquidity fee in basis points
    /// @param _reservationFeeBP new reservation fee in basis points
    /// @dev msg.sender must have VAULT_MASTER_ROLE
    function updateVaultFees(
        address _vault,
        uint256 _infraFeeBP,
        uint256 _liquidityFeeBP,
        uint256 _reservationFeeBP
    ) external onlyRole(VAULT_MASTER_ROLE) {
        _requireNotZero(_vault);
        _requireLessThanBP(_infraFeeBP, MAX_FEE_BP);
        _requireLessThanBP(_liquidityFeeBP, MAX_FEE_BP);
        _requireLessThanBP(_reservationFeeBP, MAX_FEE_BP);

        VaultConnection storage connection = _checkConnection(_vault);
        uint16 preInfraFeeBP = connection.infraFeeBP;
        uint16 preLiquidityFeeBP = connection.liquidityFeeBP;
        uint16 preReservationFeeBP = connection.reservationFeeBP;

        connection.infraFeeBP = uint16(_infraFeeBP);
        connection.liquidityFeeBP = uint16(_liquidityFeeBP);
        connection.reservationFeeBP = uint16(_reservationFeeBP);

        emit VaultFeesUpdated({
            vault: _vault,
            preInfraFeeBP: preInfraFeeBP,
            preLiquidityFeeBP: preLiquidityFeeBP,
            preReservationFeeBP: preReservationFeeBP,
            infraFeeBP: _infraFeeBP,
            liquidityFeeBP: _liquidityFeeBP,
            reservationFeeBP: _reservationFeeBP
        });
    }

    /// @notice updates the vault's connection parameters
    /// @dev Reverts if the vault is not healthy as of latest report
    /// @param _vault vault address
    /// @param _shareLimit new share limit
    /// @param _reserveRatioBP new reserve ratio
    /// @param _forcedRebalanceThresholdBP new forced rebalance threshold
    /// @param _infraFeeBP new infra fee
    /// @param _liquidityFeeBP new liquidity fee
    /// @param _reservationFeeBP new reservation fee
    function updateConnection(
        address _vault,
        uint256 _shareLimit,
        uint256 _reserveRatioBP,
        uint256 _forcedRebalanceThresholdBP,
        uint256 _infraFeeBP,
        uint256 _liquidityFeeBP,
        uint256 _reservationFeeBP
    ) external {
        _requireSender(address(_operatorGrid()));
        _requireSaneShareLimit(_shareLimit);

        VaultConnection storage connection = _checkConnection(_vault);
        VaultRecord storage record = _vaultRecord(_vault);

        uint256 totalValue_ = _totalValue(record);
        uint256 liabilityShares_ = record.liabilityShares;

        if (_isThresholdBreached(totalValue_, liabilityShares_, _reserveRatioBP)) {
            revert VaultMintingCapacityExceeded(_vault, totalValue_, liabilityShares_, _reserveRatioBP);
        }

        connection.shareLimit = uint96(_shareLimit);
        connection.reserveRatioBP = uint16(_reserveRatioBP);
        connection.forcedRebalanceThresholdBP = uint16(_forcedRebalanceThresholdBP);
        connection.infraFeeBP = uint16(_infraFeeBP);
        connection.liquidityFeeBP = uint16(_liquidityFeeBP);
        connection.reservationFeeBP = uint16(_reservationFeeBP);

        emit VaultConnectionUpdated({
            vault: _vault,
            shareLimit: _shareLimit,
            reserveRatioBP: _reserveRatioBP,
            forcedRebalanceThresholdBP: _forcedRebalanceThresholdBP,
            infraFeeBP: _infraFeeBP,
            liquidityFeeBP: _liquidityFeeBP,
            reservationFeeBP: _reservationFeeBP
        });
    }

    /// @notice disconnect a vault from the hub
    /// @param _vault vault address
    /// @dev msg.sender must have VAULT_MASTER_ROLE
    /// @dev vault's `liabilityShares` should be zero
    function disconnect(address _vault) external onlyRole(VAULT_MASTER_ROLE) {
        _initiateDisconnection(_vault, _checkConnection(_vault), _vaultRecord(_vault));

        emit VaultDisconnectInitiated(_vault);
    }

    /// @notice update of the vault data by the lazy oracle report
    /// @param _vault the address of the vault
    /// @param _reportTimestamp the timestamp of the report (last 32 bits of it)
    /// @param _reportTotalValue the total value of the vault
    /// @param _reportInOutDelta the inOutDelta of the vault
    /// @param _reportCumulativeLidoFees the cumulative Lido fees of the vault
    /// @param _reportLiabilityShares the liabilityShares of the vault
    function applyVaultReport(
        address _vault,
        uint256 _reportTimestamp,
        uint256 _reportTotalValue,
        int256 _reportInOutDelta,
        uint256 _reportCumulativeLidoFees,
        uint256 _reportLiabilityShares,
        uint256 _reportSlashingReserve
    ) external whenResumed {
        _requireSender(address(_lazyOracle()));

        VaultConnection storage connection = _vaultConnection(_vault);
        VaultRecord storage record = _vaultRecord(_vault);
        VaultObligations storage obligations = _vaultObligations(_vault);

        _checkAndUpdateLidoFeesObligations(_vault, obligations, _reportCumulativeLidoFees);

        if (connection.pendingDisconnect) {
            if (_reportSlashingReserve == 0 && record.liabilityShares == 0) {
                _settleObligations(_vault, record, obligations, NO_UNSETTLED_ALLOWED);

                IStakingVault(_vault).transferOwnership(connection.owner);
                _deleteVault(_vault, connection);

                emit VaultDisconnectCompleted(_vault);
                return;
            } else {
                // we abort the disconnect process as there is a slashing conflict yet to be resolved
                connection.pendingDisconnect = false;
                emit VaultDisconnectAborted(_vault, _reportSlashingReserve);
            }
        }

        _applyVaultReport(
            record,
            connection,
            _reportTimestamp,
            _reportTotalValue,
            _reportLiabilityShares,
            _reportInOutDelta
        );

        emit VaultReportApplied({
            vault: _vault,
            reportTimestamp: _reportTimestamp,
            reportTotalValue: _reportTotalValue,
            reportInOutDelta: _reportInOutDelta,
            reportCumulativeLidoFees: _reportCumulativeLidoFees,
            reportLiabilityShares: _reportLiabilityShares,
            reportSlashingReserve: _reportSlashingReserve
        });

        _settleObligations(_vault, record, obligations, MAX_UNSETTLED_ALLOWED);
        _checkAndUpdateBeaconChainDepositsPause(_vault, connection, record);
    }

    /// @notice Transfer the bad debt from the donor vault to the acceptor vault
    /// @param _badDebtVault address of the vault that has the bad debt
    /// @param _vaultAcceptor address of the vault that will accept the bad debt
    /// @param _maxSharesToSocialize maximum amount of shares to socialize
    /// @dev msg.sender must have BAD_DEBT_MASTER_ROLE
    function socializeBadDebt(
        address _badDebtVault,
        address _vaultAcceptor,
        uint256 _maxSharesToSocialize
    ) external onlyRole(BAD_DEBT_MASTER_ROLE) {
        _requireNotZero(_badDebtVault);
        _requireNotZero(_vaultAcceptor);
        _requireNotZero(_maxSharesToSocialize);
        if (_nodeOperator(_vaultAcceptor) != _nodeOperator(_badDebtVault)) revert BadDebtSocializationNotAllowed();

        VaultConnection storage badDebtConnection = _vaultConnection(_badDebtVault);
        _requireConnected(badDebtConnection, _badDebtVault); // require connected but may be pending disconnect

        uint256 badDebtToSocialize = _writeOffBadDebt({
            _vault: _badDebtVault,
            _record: _vaultRecord(_badDebtVault),
            _maxSharesToWriteOff: _maxSharesToSocialize
        });

        VaultConnection storage connectionAcceptor = _vaultConnection(_vaultAcceptor);
        _requireConnected(connectionAcceptor, _vaultAcceptor);

        VaultRecord storage recordAcceptor = _vaultRecord(_vaultAcceptor);
        _increaseLiability({
            _vault: _vaultAcceptor,
            _record: recordAcceptor,
            _amountOfShares: badDebtToSocialize,
            _reserveRatioBP: connectionAcceptor.reserveRatioBP,
            _maxMintableRatioBP: TOTAL_BASIS_POINTS, // maxMintableRatio up to 100% of total value
            _shareLimit: _getSharesByPooledEth(recordAcceptor.locked) // we can occupy all the locked amount
        });

        emit BadDebtSocialized(_badDebtVault, _vaultAcceptor, badDebtToSocialize);
    }

    /// @notice Internalize the bad debt to the protocol
    /// @param _badDebtVault address of the vault that has the bad debt
    /// @param _maxSharesToInternalize maximum amount of shares to internalize
    /// @dev msg.sender must have BAD_DEBT_MASTER_ROLE
    function internalizeBadDebt(
        address _badDebtVault,
        uint256 _maxSharesToInternalize
    ) external onlyRole(BAD_DEBT_MASTER_ROLE) {
        _requireNotZero(_badDebtVault);
        _requireNotZero(_maxSharesToInternalize);

        VaultConnection storage badDebtConnection = _vaultConnection(_badDebtVault);
        _requireConnected(badDebtConnection, _badDebtVault);

        uint256 badDebtToInternalize = _writeOffBadDebt({
            _vault: _badDebtVault,
            _record: _vaultRecord(_badDebtVault),
            _maxSharesToWriteOff: _maxSharesToInternalize
        });

        // internalize the bad debt to the protocol
        _storage().badDebtToInternalize = _storage().badDebtToInternalize.withValueIncrease({
            _consensus: CONSENSUS_CONTRACT,
            _increment: uint112(badDebtToInternalize)
        });

        emit BadDebtWrittenOffToBeInternalized(_badDebtVault, badDebtToInternalize);
    }

    /// @notice Reset the internalized bad debt to zero
    /// @dev msg.sender must be the accounting contract
    function decreaseInternalizedBadDebt(uint256 _amountOfShares) external {
        _requireSender(LIDO_LOCATOR.accounting());

        // don't cache previous value, we don't need it for sure
        _storage().badDebtToInternalize.value -= uint112(_amountOfShares);
    }

    /// @notice transfer the ownership of the vault to a new owner without disconnecting it from the hub
    /// @param _vault vault address
    /// @param _newOwner new owner address
    /// @dev msg.sender should be vault's owner
    function transferVaultOwnership(address _vault, address _newOwner) external {
        _requireNotZero(_newOwner);
        VaultConnection storage connection = _checkConnection(_vault);
        address oldOwner = connection.owner;

        _requireSender(oldOwner);

        connection.owner = _newOwner;

        emit VaultOwnershipTransferred({
            vault: _vault,
            newOwner: _newOwner,
            oldOwner: oldOwner
        });
    }

    /// @notice disconnects a vault from the hub
    /// @param _vault vault address
    /// @dev msg.sender should be vault's owner
    /// @dev vault's `liabilityShares` should be zero
    function voluntaryDisconnect(address _vault) external whenResumed {
        VaultConnection storage connection = _checkConnectionAndOwner(_vault);

        _initiateDisconnection(_vault, connection, _vaultRecord(_vault));

        emit VaultDisconnectInitiated(_vault);
    }

    /// @notice funds the vault passing ether as msg.value
    /// @param _vault vault address
    /// @dev msg.sender should be vault's owner
    function fund(address _vault) external payable whenResumed {
        _requireNotZero(_vault);
        VaultConnection storage connection = _vaultConnection(_vault);
        if (connection.vaultIndex == 0) revert NotConnectedToHub(_vault);
        if (msg.sender != connection.owner) revert NotAuthorized();

        _updateInOutDelta(_vault, _vaultRecord(_vault), int112(int256(msg.value)));

        IStakingVault(_vault).fund{value: msg.value}();
    }

    /// @notice withdraws ether from the vault to the recipient address
    /// @param _vault vault address
    /// @param _recipient recipient address
    /// @param _ether amount of ether to withdraw
    /// @dev msg.sender should be vault's owner
    function withdraw(address _vault, address _recipient, uint256 _ether) external whenResumed {
        _checkConnectionAndOwner(_vault);

        VaultRecord storage record = _vaultRecord(_vault);
        _requireFreshReport(_vault, record);

        uint256 withdrawable = _withdrawableValue(_vault, record);
        if (_ether > withdrawable) revert AmountExceedsWithdrawableValue(_vault, withdrawable, _ether);

        _withdraw(_vault, record, _recipient, _ether);
    }

    /// @notice Rebalances StakingVault by withdrawing ether to VaultHub
    /// @param _vault vault address
    /// @param _shares amount of shares to rebalance
    /// @dev msg.sender should be vault's owner
    function rebalance(address _vault, uint256 _shares) external whenResumed {
        _requireNotZero(_shares);
        _checkConnectionAndOwner(_vault);

        _rebalance(_vault, _vaultRecord(_vault), _shares);
    }

    /// @notice mint StETH shares backed by vault external balance to the receiver address
    /// @param _vault vault address
    /// @param _recipient address of the receiver
    /// @param _amountOfShares amount of stETH shares to mint
    function mintShares(address _vault, address _recipient, uint256 _amountOfShares) external whenResumed {
        _requireNotZero(_recipient);
        _requireNotZero(_amountOfShares);

        VaultConnection storage connection = _checkConnectionAndOwner(_vault);
        VaultRecord storage record = _vaultRecord(_vault);

        _requireFreshReport(_vault, record);

        uint256 reserveRatioBP = connection.reserveRatioBP;
        _increaseLiability({
            _vault: _vault,
            _record: record,
            _amountOfShares: _amountOfShares,
            _reserveRatioBP: reserveRatioBP,
            _maxMintableRatioBP: TOTAL_BASIS_POINTS - reserveRatioBP,
            _shareLimit: connection.shareLimit
        });

        LIDO.mintExternalShares(_recipient, _amountOfShares);

        emit MintedSharesOnVault(_vault, _amountOfShares, record.locked);
    }

    /// @notice burn steth shares from the balance of the VaultHub contract
    /// @param _vault vault address
    /// @param _amountOfShares amount of shares to burn
    /// @dev msg.sender should be vault's owner
    /// @dev this function is designed to be used by the smart contract, for EOA see `transferAndBurnShares`
    function burnShares(address _vault, uint256 _amountOfShares) public whenResumed {
        _requireNotZero(_amountOfShares);
        _checkConnectionAndOwner(_vault);

        VaultRecord storage record = _vaultRecord(_vault);

        _decreaseLiability(_vault, record, _amountOfShares);

        LIDO.burnExternalShares(_amountOfShares);

        emit BurnedSharesOnVault(_vault, _amountOfShares);
    }

    /// @notice separate burn function for EOA vault owners; requires vaultHub to be approved to transfer stETH
    /// @param _vault vault address
    /// @param _amountOfShares amount of shares to transfer and burn
    /// @dev msg.sender should be vault's owner
    function transferAndBurnShares(address _vault, uint256 _amountOfShares) external {
        LIDO.transferSharesFrom(msg.sender, address(this), _amountOfShares);

        burnShares(_vault, _amountOfShares);
    }

    /// @notice pauses beacon chain deposits for the vault
    /// @param _vault vault address
    /// @dev msg.sender should be vault's owner
    function pauseBeaconChainDeposits(address _vault) external {
        VaultConnection storage connection = _checkConnectionAndOwner(_vault);

        connection.isBeaconDepositsManuallyPaused = true;
        IStakingVault(_vault).pauseBeaconChainDeposits();
    }

    /// @notice resumes beacon chain deposits for the vault
    /// @param _vault vault address
    /// @dev msg.sender should be vault's owner
    function resumeBeaconChainDeposits(address _vault) external {
        VaultConnection storage connection = _checkConnectionAndOwner(_vault);
        VaultRecord storage record = _vaultRecord(_vault);
        if (!_isVaultHealthy(connection, record)) revert UnhealthyVaultCannotDeposit(_vault);

        _settleObligations(_vault, record, _vaultObligations(_vault), UNSETTLED_THRESHOLD);

        connection.isBeaconDepositsManuallyPaused = false;
        IStakingVault(_vault).resumeBeaconChainDeposits();
    }

    /// @notice Emits a request event for the node operator to perform validator exit
    /// @param _vault vault address
    /// @param _pubkeys array of public keys of the validators to exit
    /// @dev msg.sender should be vault's owner
    function requestValidatorExit(address _vault, bytes calldata _pubkeys) external {
        _checkConnectionAndOwner(_vault);

        IStakingVault(_vault).requestValidatorExit(_pubkeys);
    }

    /// @notice Triggers validator withdrawals for the vault using EIP-7002
    /// @param _vault vault address
    /// @param _pubkeys array of public keys of the validators to withdraw from
    /// @param _amounts array of amounts to withdraw from each validator (0 for full withdrawal)
    /// @param _refundRecipient address that will receive the refund for transaction costs
    /// @dev msg.sender should be vault's owner
    function triggerValidatorWithdrawals(
        address _vault,
        bytes calldata _pubkeys,
        uint64[] calldata _amounts,
        address _refundRecipient
    ) external payable {
        VaultConnection storage connection = _checkConnectionAndOwner(_vault);
        VaultRecord storage record = _vaultRecord(_vault);
        VaultObligations storage obligations = _vaultObligations(_vault);

        /// @dev NB: Disallow partial withdrawals when the vault is unhealthy or has redemptions over the threshold
        ///          in order to prevent the vault owner from clogging the consensus layer withdrawal queue
        ///          front-running and delaying the forceful validator exits required for rebalancing the vault,
        ///          unless the requested amount of withdrawals is enough to recover the vault to healthy state and
        ///          settle the unsettled obligations
        if (!_isVaultHealthy(connection, record) || obligations.redemptions >= UNSETTLED_THRESHOLD) {
            uint256 minPartialAmount = type(uint256).max;
            for (uint256 i = 0; i < _amounts.length; i++) {
                if (_amounts[i] > 0 && _amounts[i] < minPartialAmount) minPartialAmount = _amounts[i];
            }

            if (minPartialAmount < type(uint256).max) {
                uint256 currentVaultBalance = _vault.balance;
                uint256 required = _totalUnsettledObligations(obligations) + _rebalanceShortfall(connection, record);
                uint256 amountToCover = required > currentVaultBalance ? required - currentVaultBalance : 0;

                if (minPartialAmount < amountToCover) revert PartialValidatorWithdrawalNotAllowed();
            }
        }

        IStakingVault(_vault).triggerValidatorWithdrawals{value: msg.value}(_pubkeys, _amounts, _refundRecipient);
    }

    /// @notice Triggers validator full withdrawals for the vault using EIP-7002 permissionlessly if the vault is
    ///         unhealthy or has redemptions obligation over the threshold
    /// @param _vault address of the vault to exit validators from
    /// @param _pubkeys array of public keys of the validators to exit
    /// @param _refundRecipient address that will receive the refund for transaction costs
    /// @dev    When the vault becomes unhealthy, trusted actor with the role can force its validators to exit the beacon chain
    ///         This returns the vault's deposited ETH back to vault's balance and allows to rebalance the vault
    function forceValidatorExit(
        address _vault,
        bytes calldata _pubkeys,
        address _refundRecipient
    ) external payable onlyRole(VALIDATOR_EXIT_ROLE) {
        VaultConnection storage connection = _checkConnection(_vault);
        VaultRecord storage record = _vaultRecord(_vault);

        if (
            _isVaultHealthy(connection, record) &&
            // Check if the vault has redemptions under the threshold, or enough balance to cover the redemptions fully
            _vaultObligations(_vault).redemptions < Math256.max(UNSETTLED_THRESHOLD, _vault.balance)
        ) {
            revert ForcedValidatorExitNotAllowed();
        }

        uint64[] memory amounts = new uint64[](0);
        IStakingVault(_vault).triggerValidatorWithdrawals{value: msg.value}(_pubkeys, amounts, _refundRecipient);

        emit ForcedValidatorExitTriggered(_vault, _pubkeys, _refundRecipient);
    }

    /// @notice Permissionless rebalance for unhealthy vaults
    /// @param _vault vault address
    /// @dev rebalance all available amount of ether until the vault is healthy
    function forceRebalance(address _vault) external {
        VaultConnection storage connection = _checkConnection(_vault);
        VaultRecord storage record = _vaultRecord(_vault);

        uint256 sharesToRebalance = Math256.min(
            _rebalanceShortfall(connection, record),
            _getSharesByPooledEth(_vault.balance)
        );
        if (sharesToRebalance == 0) revert AlreadyHealthy(_vault);

        _rebalance(_vault, record, sharesToRebalance);
    }

    /// @notice Accrues a redemption obligation on the vault under extreme conditions
    /// @param _vault The address of the vault
    /// @param _redemptionsValue The value of the redemptions obligation
    function setVaultRedemptions(address _vault, uint256 _redemptionsValue) external onlyRole(REDEMPTION_MASTER_ROLE) {
        VaultRecord storage record = _vaultRecord(_vault);

        uint256 liabilityShares_ = record.liabilityShares;

        // This function may intentionally perform no action in some cases, as these are EasyTrack motions
        if (liabilityShares_ > 0) {
            uint256 newRedemptions = Math256.min(_redemptionsValue, _getPooledEthBySharesRoundUp(liabilityShares_));
            _vaultObligations(_vault).redemptions = uint128(newRedemptions);
            emit RedemptionsUpdated(_vault, newRedemptions);

            _checkAndUpdateBeaconChainDepositsPause(_vault, _vaultConnection(_vault), record);
        } else {
            emit RedemptionsNotSet(_vault, _redemptionsValue);
        }
    }

    /// @notice Allows permissionless full or partial settlement of unsettled obligations on the vault
    /// @param _vault The address of the vault
    function settleVaultObligations(address _vault) external whenResumed {
        if (_vault.balance == 0) revert ZeroBalance();

        VaultRecord storage record = _vaultRecord(_vault);
        _settleObligations(_vault, record, _vaultObligations(_vault), MAX_UNSETTLED_ALLOWED);

        _checkAndUpdateBeaconChainDepositsPause(_vault, _vaultConnection(_vault), record);
    }

    /// @notice Proves that validators unknown to PDG have correct WC to participate in the vault
    /// @param _vault vault address
    /// @param _witness ValidatorWitness struct proving validator WC belonging to staking vault
    function proveUnknownValidatorToPDG(
        address _vault,
        IPredepositGuarantee.ValidatorWitness calldata _witness
    ) external {
        _checkConnectionAndOwner(_vault);

        _predepositGuarantee().proveUnknownValidator(_witness, IStakingVault(_vault));
    }

    /// @notice Compensates disproven predeposit from PDG to the recipient
    /// @param _vault vault address
    /// @param _pubkey pubkey of the validator
    /// @param _recipient address to compensate the disproven validator predeposit to
    /// @return amount of compensated ether
    function compensateDisprovenPredepositFromPDG(
        address _vault,
        bytes calldata _pubkey,
        address _recipient
    ) external returns (uint256) {
        _checkConnectionAndOwner(_vault);

        return _predepositGuarantee().compensateDisprovenPredeposit(_pubkey, _recipient);
    }

    function _connectVault(
        address _vault,
        uint256 _shareLimit,
        uint256 _reserveRatioBP,
        uint256 _forcedRebalanceThresholdBP,
        uint256 _infraFeeBP,
        uint256 _liquidityFeeBP,
        uint256 _reservationFeeBP
    ) internal {
        _requireSaneShareLimit(_shareLimit);
        _requireNotZero(_reserveRatioBP);
        _requireLessThanBP(_reserveRatioBP, TOTAL_BASIS_POINTS);
        _requireNotZero(_forcedRebalanceThresholdBP);
        _requireLessThanBP(_forcedRebalanceThresholdBP, _reserveRatioBP);

        _requireLessThanBP(_infraFeeBP, MAX_FEE_BP);
        _requireLessThanBP(_liquidityFeeBP, MAX_FEE_BP);
        _requireLessThanBP(_reservationFeeBP, MAX_FEE_BP);

        VaultConnection memory connection = _vaultConnection(_vault);
        if (connection.pendingDisconnect) revert VaultIsDisconnecting(_vault);
        if (connection.vaultIndex != 0) revert AlreadyConnected(_vault, connection.vaultIndex);

        bytes32 codehash = address(_vault).codehash;
        if (!_storage().codehashes[codehash]) revert CodehashNotAllowed(_vault, codehash);

        uint256 vaultBalance = _vault.balance;
        if (vaultBalance < CONNECT_DEPOSIT) revert VaultInsufficientBalance(_vault, vaultBalance, CONNECT_DEPOSIT);

        // Connecting a new vault with totalValue == balance
        VaultRecord memory record = VaultRecord({
            report: Report({
                totalValue: uint112(vaultBalance),
                inOutDelta: int112(int256(vaultBalance)),
                timestamp: uint32(_lazyOracle().latestReportTimestamp())
            }),
            locked: uint128(CONNECT_DEPOSIT),
            liabilityShares: 0,
            inOutDelta: RefSlotCache.Int112WithRefSlotCache({
                value: int112(int256(vaultBalance)),
                valueOnRefSlot: 0,
                refSlot: 0
            })
        });

        connection = VaultConnection({
            owner: IStakingVault(_vault).owner(),
            shareLimit: uint96(_shareLimit),
            vaultIndex: uint96(_storage().vaults.length),
            pendingDisconnect: false,
            reserveRatioBP: uint16(_reserveRatioBP),
            forcedRebalanceThresholdBP: uint16(_forcedRebalanceThresholdBP),
            infraFeeBP: uint16(_infraFeeBP),
            liquidityFeeBP: uint16(_liquidityFeeBP),
            reservationFeeBP: uint16(_reservationFeeBP),
            isBeaconDepositsManuallyPaused: false
        });

        _addVault(_vault, connection, record);
    }

    function _initiateDisconnection(
        address _vault,
        VaultConnection storage _connection,
        VaultRecord storage _record
    ) internal {
        uint256 liabilityShares_ = _record.liabilityShares;
        if (liabilityShares_ > 0) {
            revert NoLiabilitySharesShouldBeLeft(_vault, liabilityShares_);
        }

        _record.locked = 0; // unlock the connection deposit to allow fees settlement
        _settleObligations(_vault, _record, _vaultObligations(_vault), NO_UNSETTLED_ALLOWED);

        _connection.pendingDisconnect = true;

        _operatorGrid().resetVaultTier(_vault);
    }

    function _applyVaultReport(
        VaultRecord storage _record,
        VaultConnection storage _connection,
        uint256 _reportTimestamp,
        uint256 _reportTotalValue,
        uint256 _reportLiabilityShares,
        int256 _reportInOutDelta
    ) internal {
        uint256 liabilityShares_ = Math256.max(_record.liabilityShares, _reportLiabilityShares);
        uint256 liability = _getPooledEthBySharesRoundUp(liabilityShares_);

        uint256 lockedEther = Math256.max(
            liability * TOTAL_BASIS_POINTS / (TOTAL_BASIS_POINTS - _connection.reserveRatioBP),
            CONNECT_DEPOSIT
        );

        _record.locked = uint128(lockedEther);
        _record.report = Report({
            totalValue: uint112(_reportTotalValue),
            inOutDelta: int112(_reportInOutDelta),
            timestamp: uint32(_reportTimestamp)
        });
    }

    function _rebalance(address _vault, VaultRecord storage _record, uint256 _shares) internal {
        uint256 valueToRebalance = _getPooledEthBySharesRoundUp(_shares);

        uint256 totalValue_ = _totalValue(_record);
        if (valueToRebalance > totalValue_) revert RebalanceAmountExceedsTotalValue(totalValue_, valueToRebalance);

        _decreaseLiability(_vault, _record, _shares);
        _withdraw(_vault, _record, address(this), valueToRebalance);
        _rebalanceExternalEtherToInternal(valueToRebalance);

        emit VaultRebalanced(_vault, _shares, valueToRebalance);
    }

    function _withdraw(
        address _vault,
        VaultRecord storage _record,
        address _recipient,
        uint256 _amount
    ) internal {
        _updateInOutDelta(_vault, _record, -int112(int256(_amount)));

        IStakingVault(_vault).withdraw(_recipient, _amount);
    }

    function _increaseLiability(
        address _vault,
        VaultRecord storage _record,
        uint256 _amountOfShares,
        uint256 _reserveRatioBP,
        uint256 _maxMintableRatioBP,
        uint256 _shareLimit
    ) internal {
        uint256 sharesAfterMint = _record.liabilityShares + _amountOfShares;
        if (sharesAfterMint > _shareLimit) revert ShareLimitExceeded(_vault, sharesAfterMint, _shareLimit);

        uint256 stETHAfterMint = _getPooledEthBySharesRoundUp(sharesAfterMint);
        uint256 maxLockableValue_ = _maxLockableValue(_record, _vaultObligations(_vault));
        uint256 maxMintableEther = (maxLockableValue_ * _maxMintableRatioBP) / TOTAL_BASIS_POINTS;
        if (stETHAfterMint > maxMintableEther) {
            revert InsufficientValueToMint(_vault, maxLockableValue_);
        }

        // Calculate the minimum ETH that needs to be locked in the vault to maintain the reserve ratio
        uint256 etherToLock = (stETHAfterMint * TOTAL_BASIS_POINTS) / (TOTAL_BASIS_POINTS - _reserveRatioBP);
        if (etherToLock > _record.locked) {
            _record.locked = uint128(etherToLock);
        }

        _record.liabilityShares = uint96(sharesAfterMint);

        _operatorGrid().onMintedShares(_vault, _amountOfShares);
    }

    function _decreaseLiability(address _vault, VaultRecord storage _record, uint256 _amountOfShares) internal {
        uint256 liabilityShares_ = _record.liabilityShares;
        if (liabilityShares_ < _amountOfShares) revert InsufficientSharesToBurn(_vault, liabilityShares_);

        _record.liabilityShares = uint96(liabilityShares_ - _amountOfShares);

        _decreaseRedemptions(_vault, _amountOfShares);
        _operatorGrid().onBurnedShares(_vault, _amountOfShares);
    }

    function _writeOffBadDebt(
        address _vault,
        VaultRecord storage _record,
        uint256 _maxSharesToWriteOff
    ) internal returns (uint256 badDebtWrittenOff) {
        uint256 liabilityShares_ = _record.liabilityShares;
        uint256 totalValueShares = _getSharesByPooledEth(_totalValue(_record));
        if (totalValueShares > liabilityShares_) {
            revert NoBadDebtToWriteOff(_vault, totalValueShares, liabilityShares_);
        }

        badDebtWrittenOff = Math256.min(liabilityShares_ - totalValueShares, _maxSharesToWriteOff);

        _decreaseLiability(_vault, _record, badDebtWrittenOff);
    }

    function _rebalanceShortfall(
        VaultConnection storage _connection,
        VaultRecord storage _record
    ) internal view returns (uint256) {
        uint256 totalValue_ = _totalValue(_record);
        uint256 liabilityShares_ = _record.liabilityShares;

        bool isHealthy = !_isThresholdBreached(
            totalValue_,
            liabilityShares_,
            _connection.forcedRebalanceThresholdBP
        );

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

        uint256 reserveRatioBP = _connection.reserveRatioBP;
        uint256 maxMintableRatio = (TOTAL_BASIS_POINTS - reserveRatioBP);
        uint256 sharesByTotalValue = _getSharesByPooledEth(totalValue_);

        // Impossible to rebalance a vault with bad debt
        if (liabilityShares_ >= sharesByTotalValue) {
            // return MAX_UINT_256
            return type(uint256).max;
        }

        // Solve the equation for X:
        // LS - liabilityShares, TV - sharesByTotalValue
        // MR - maxMintableRatio, 100 - TOTAL_BASIS_POINTS, RR - reserveRatio
        // X - amount of shares that should be withdrawn (TV - X) and used to repay the debt (LS - X)
        // to reduce the LS/TVS ratio back to MR

        // (LS - X) / (TV - X) = MR / 100
        // (LS - X) * 100 = (TV - X) * MR
        // LS * 100 - X * 100 = TV * MR - X * MR
        // X * MR - X * 100 = TV * MR - LS * 100
        // X * (MR - 100) = TV * MR - LS * 100
        // X = (TV * MR - LS * 100) / (MR - 100)
        // X = (LS * 100 - TV * MR) / (100 - MR)
        // RR = 100 - MR
        // X = (LS * 100 - TV * MR) / RR

        return (liabilityShares_ * TOTAL_BASIS_POINTS - sharesByTotalValue * maxMintableRatio) / reserveRatioBP;
    }

    function _totalValue(VaultRecord storage _record) internal view returns (uint256) {
        Report memory report = _record.report;
        return uint256(int256(uint256(report.totalValue)) + _record.inOutDelta.value - report.inOutDelta);
    }

    function _maxLockableValue(VaultRecord storage _record, VaultObligations storage _obligations) internal view returns (uint256) {
        return _totalValue(_record) - _obligations.unsettledLidoFees;
    }

    function _isReportFresh(VaultRecord storage _record) internal view returns (bool) {
        uint256 latestReportTimestamp = _lazyOracle().latestReportTimestamp();
        return
            // check if AccountingOracle brought fresh report
            uint32(latestReportTimestamp) == _record.report.timestamp &&
            // if Accounting Oracle stop bringing the report, last report is fresh for 2 days
            block.timestamp - latestReportTimestamp < REPORT_FRESHNESS_DELTA;
    }

    function _isVaultHealthy(
        VaultConnection storage _connection,
        VaultRecord storage _record
    ) internal view returns (bool) {
        return !_isThresholdBreached(
            _totalValue(_record),
            _record.liabilityShares,
            _connection.forcedRebalanceThresholdBP
        );
    }

    /// @dev Returns true if the vault liability breached the given threshold (inverted)
    function _isThresholdBreached(
        uint256 _vaultTotalValue,
        uint256 _vaultLiabilityShares,
        uint256 _thresholdBP
    ) internal view returns (bool) {
        uint256 liability = _getPooledEthBySharesRoundUp(_vaultLiabilityShares);
        return liability > _vaultTotalValue * (TOTAL_BASIS_POINTS - _thresholdBP) / TOTAL_BASIS_POINTS;
    }

    function _addVault(address _vault, VaultConnection memory _connection, VaultRecord memory _record) internal {
        Storage storage $ = _storage();
        $.vaults.push(_vault);

        $.connections[_vault] = _connection;
        $.records[_vault] = _record;
    }

    function _deleteVault(address _vault, VaultConnection storage _connection) internal {
        Storage storage $ = _storage();
        uint96 vaultIndex = _connection.vaultIndex;

        address lastVault = $.vaults[$.vaults.length - 1];
        $.connections[lastVault].vaultIndex = vaultIndex;
        $.vaults[vaultIndex] = lastVault;
        $.vaults.pop();

        delete $.connections[_vault];
        delete $.records[_vault];
        delete $.obligations[_vault];
    }

    function _checkConnectionAndOwner(address _vault) internal view returns (VaultConnection storage connection) {
        connection = _checkConnection(_vault);
        _requireSender(connection.owner);
    }

    function _checkConnection(address _vault) internal view returns (VaultConnection storage) {
        _requireNotZero(_vault);

        VaultConnection storage connection = _vaultConnection(_vault);
        _requireConnected(connection, _vault);
        if (connection.pendingDisconnect) revert VaultIsDisconnecting(_vault);

        return connection;
    }

    /// @dev Caches the inOutDelta of the latest refSlot and updates the value
    function _updateInOutDelta(address _vault, VaultRecord storage _record, int112 _increment) internal {
        _record.inOutDelta = _record.inOutDelta.withValueIncrease({
            _consensus: CONSENSUS_CONTRACT,
            _increment: _increment
        });

        emit VaultInOutDeltaUpdated(_vault, _record.inOutDelta.value);
    }

    /**
     * @notice Updates the unsettled Lido fees obligations based on the report cumulative Lido fees
     * @param _vault The address of the vault
     * @param _reportCumulativeLidoFees The cumulative Lido fees reported in the report
     */
    function _checkAndUpdateLidoFeesObligations(
        address _vault,
        VaultObligations storage _obligations,
        uint256 _reportCumulativeLidoFees
    ) internal {
        uint256 cumulativeSettledLidoFees = _obligations.settledLidoFees;
        uint256 cumulativeLidoFees = cumulativeSettledLidoFees + _obligations.unsettledLidoFees;
        if (_reportCumulativeLidoFees < cumulativeLidoFees) {
            revert InvalidFees(_vault, _reportCumulativeLidoFees, cumulativeLidoFees);
        }

        // update unsettled lido fees
        uint256 unsettledLidoFees = _reportCumulativeLidoFees - cumulativeSettledLidoFees;
        if (unsettledLidoFees != _obligations.unsettledLidoFees) {
            _obligations.unsettledLidoFees = uint128(unsettledLidoFees);
            emit LidoFeesUpdated(_vault, unsettledLidoFees, cumulativeSettledLidoFees);
        }
    }

    /**
     * @notice Calculates a settlement plan based on vault balance and obligations
     * @param _vault The address of the vault
     * @param _record The record of the vault
     * @param _obligations The obligations of the vault to be settled
     * @return valueToRebalance The ETH amount to be rebalanced for redemptions
     * @return sharesToRebalance The shares to be rebalanced for redemptions
     * @return valueToTransferToLido The ETH amount to be sent to the Lido
     * @return unsettledRedemptions The remaining redemptions after the planned settlement
     * @return unsettledLidoFees The remaining Lido fees after the planned settlement
     * @return totalUnsettled The total ETH value of obligations remaining after the planned settlement
     */
    function _planSettlement(
        address _vault,
        VaultRecord storage _record,
        VaultObligations storage _obligations
    ) internal view returns (
        uint256 valueToRebalance,
        uint256 sharesToRebalance,
        uint256 valueToTransferToLido,
        uint256 unsettledRedemptions,
        uint256 unsettledLidoFees,
        uint256 totalUnsettled
    ) {
        (valueToRebalance, sharesToRebalance, unsettledRedemptions) = _planRebalance(_vault, _record, _obligations);
        (valueToTransferToLido, unsettledLidoFees) = _planLidoTransfer(_vault, _record, _obligations, valueToRebalance);
        totalUnsettled = unsettledRedemptions + unsettledLidoFees;
    }

    /**
     * @notice Plans the amounts and shares to rebalance for redemptions
     * @param _vault The address of the vault
     * @param _record The record of the vault
     * @param _obligations The obligations of the vault
     * @return valueToRebalance The ETH amount to be rebalanced for redemptions
     * @return sharesToRebalance The shares to be rebalanced for redemptions
     * @return unsettledRedemptions The remaining redemptions after the planned settlement
     */
    function _planRebalance(
        address _vault,
        VaultRecord storage _record,
        VaultObligations storage _obligations
    ) internal view returns (uint256 valueToRebalance, uint256 sharesToRebalance, uint256 unsettledRedemptions) {
        uint256 redemptionShares = _getSharesByPooledEth(_obligations.redemptions);
        uint256 maxRedemptionsValue = _getPooledEthBySharesRoundUp(redemptionShares);
        // if the max redemptions value is less than the redemptions, we need to round up the redemptions shares
        if (maxRedemptionsValue < _obligations.redemptions) redemptionShares += 1;

        uint256 cappedRedemptionsShares = Math256.min(_record.liabilityShares, redemptionShares);
        sharesToRebalance = Math256.min(cappedRedemptionsShares, _getSharesByPooledEth(_vault.balance));
        valueToRebalance = _getPooledEthBySharesRoundUp(sharesToRebalance);
        unsettledRedemptions = _getPooledEthBySharesRoundUp(redemptionShares - sharesToRebalance);
    }

    /**
     * @notice Plans the amount to transfer to Lido for fees
     * @param _vault The address of the vault
     * @param _record The record of the vault
     * @param _obligations The obligations of the vault
     * @param _valueToRebalance The ETH amount already allocated for rebalancing
     * @return valueToTransferToLido The ETH amount to be sent to the Lido
     * @return unsettledLidoFees The remaining Lido fees after the planned settlement
     */
    function _planLidoTransfer(
        address _vault,
        VaultRecord storage _record,
        VaultObligations storage _obligations,
        uint256 _valueToRebalance
    ) internal view returns (uint256 valueToTransferToLido, uint256 unsettledLidoFees) {
        uint256 vaultBalance = _vault.balance;
        uint256 remainingBalance = vaultBalance - _valueToRebalance;

        if (_vaultConnection(_vault).pendingDisconnect) {
            /// @dev connection deposit is unlocked, so it's available for fees
            valueToTransferToLido = Math256.min(_obligations.unsettledLidoFees, remainingBalance);
        } else {
            /// @dev connection deposit is permanently locked, so it's not available for fees
            /// @dev NB: Fees are deducted from the vault's current balance, which reduces the total value, so the
            ///          current locked value must be considered to prevent the vault from entering an unhealthy state
            uint256 lockedValue = _record.locked;
            uint256 totalValue_ = _totalValue(_record);
            uint256 unlockedValue = totalValue_ > lockedValue ? totalValue_ - lockedValue : 0;
            uint256 availableForFees = Math256.min(
                unlockedValue > _valueToRebalance ? unlockedValue - _valueToRebalance : 0,
                remainingBalance
            );
            valueToTransferToLido = Math256.min(_obligations.unsettledLidoFees, availableForFees);
        }

        unsettledLidoFees = _obligations.unsettledLidoFees - valueToTransferToLido;
    }

    /**
     * @notice Settles redemptions and Lido fee obligations for a vault
     * @param _vault The address of the vault to settle obligations for
     * @param _record The record of the vault to settle obligations for
     * @param _obligations The obligations of the vault to be settled
     * @param _allowedUnsettled The maximum allowable unsettled obligations post-settlement (triggers reverts)
     */
    function _settleObligations(
        address _vault,
        VaultRecord storage _record,
        VaultObligations storage _obligations,
        uint256 _allowedUnsettled
    ) internal {
        (
            uint256 valueToRebalance,
            uint256 sharesToRebalance,
            uint256 valueToTransferToLido,
            uint256 unsettledRedemptions,
            uint256 unsettledLidoFees,
            uint256 totalUnsettled
        ) = _planSettlement(_vault, _record, _obligations);

        // Enforce requirement for settlement completeness
        if (totalUnsettled > _allowedUnsettled) {
            revert VaultHasUnsettledObligations(_vault, totalUnsettled, _allowedUnsettled);
        }

        // Skip if no changes to obligations
        if (valueToTransferToLido == 0 && valueToRebalance == 0) {
            return;
        }

        if (valueToRebalance > 0) {
            _decreaseLiability(_vault, _record, sharesToRebalance);
            _withdraw(_vault, _record, address(this), valueToRebalance);
            _rebalanceExternalEtherToInternal(valueToRebalance);
        }

        if (valueToTransferToLido > 0) {
            _withdraw(_vault, _record, LIDO_LOCATOR.treasury(), valueToTransferToLido);
            _obligations.settledLidoFees += uint128(valueToTransferToLido);
        }

        _obligations.redemptions = uint128(unsettledRedemptions);
        _obligations.unsettledLidoFees = uint128(unsettledLidoFees);

        emit VaultObligationsSettled({
            vault: _vault,
            rebalanced: valueToRebalance,
            transferredToLido: valueToTransferToLido,
            unsettledRedemptions: unsettledRedemptions,
            unsettledLidoFees: unsettledLidoFees,
            settledLidoFees: _obligations.settledLidoFees
        });
    }

    function _decreaseRedemptions(address _vault, uint256 _shares) internal {
        VaultObligations storage obligations = _vaultObligations(_vault);

        if (obligations.redemptions > 0) {
            uint256 redemptionsValue = _getPooledEthBySharesRoundUp(_shares);
            uint256 decrease = Math256.min(obligations.redemptions, redemptionsValue);
            if (decrease > 0) {
                obligations.redemptions -= uint128(decrease);
                emit RedemptionsUpdated(_vault, obligations.redemptions);
            }
        }
    }

    function _totalUnsettledObligations(VaultObligations storage _obligations) internal view returns (uint256) {
        return _obligations.unsettledLidoFees + _obligations.redemptions;
    }

    function _checkAndUpdateBeaconChainDepositsPause(
        address _vault,
        VaultConnection storage _connection,
        VaultRecord storage _record
    ) internal {
        IStakingVault vault_ = IStakingVault(_vault);
        bool isHealthy = _isVaultHealthy(_connection, _record);
        bool isBeaconDepositsPaused = vault_.beaconChainDepositsPaused();

        if (_totalUnsettledObligations(_vaultObligations(_vault)) >= UNSETTLED_THRESHOLD || !isHealthy) {
            if (!isBeaconDepositsPaused) vault_.pauseBeaconChainDeposits();
        } else if (!_connection.isBeaconDepositsManuallyPaused) {
            if (isBeaconDepositsPaused) vault_.resumeBeaconChainDeposits();
        }
    }

    /// @return the amount of ether that can be instantly withdrawn from the staking vault
    /// @dev this amount already accounts locked value and unsettled obligations
    function _withdrawableValue(
        address _vault,
        VaultRecord storage _record
    ) internal view returns (uint256) {
        uint256 totalValue_ = _totalValue(_record);
        uint256 lockedPlusUnsettled = _record.locked + _totalUnsettledObligations(_vaultObligations(_vault));

        return Math256.min(
            _vault.balance,
            totalValue_ > lockedPlusUnsettled ? totalValue_ - lockedPlusUnsettled : 0
        );
    }

    function _storage() internal pure returns (Storage storage $) {
        assembly {
            $.slot := STORAGE_LOCATION
        }
    }

    function _vaultConnection(address _vault) internal view returns (VaultConnection storage) {
        return _storage().connections[_vault];
    }

    function _vaultRecord(address _vault) internal view returns (VaultRecord storage) {
        return _storage().records[_vault];
    }

    function _vaultObligations(address _vault) internal view returns (VaultObligations storage) {
        return _storage().obligations[_vault];
    }

    function _operatorGrid() internal view returns (OperatorGrid) {
        return OperatorGrid(LIDO_LOCATOR.operatorGrid());
    }

    function _lazyOracle() internal view returns (LazyOracle) {
        return LazyOracle(LIDO_LOCATOR.lazyOracle());
    }

    function _predepositGuarantee() internal view returns (IPredepositGuarantee) {
        return IPredepositGuarantee(LIDO_LOCATOR.predepositGuarantee());
    }

    function _getSharesByPooledEth(uint256 _ether) internal view returns (uint256) {
        return LIDO.getSharesByPooledEth(_ether);
    }

    function _getPooledEthByShares(uint256 _ether) internal view returns (uint256) {
        return LIDO.getPooledEthByShares(_ether);
    }

    function _getPooledEthBySharesRoundUp(uint256 _shares) internal view returns (uint256) {
        return LIDO.getPooledEthBySharesRoundUp(_shares);
    }

    function _rebalanceExternalEtherToInternal(uint256 _ether) internal {
        LIDO.rebalanceExternalEtherToInternal{value: _ether}();
    }

    function _nodeOperator(address _vault) internal view returns (address) {
        return IStakingVault(_vault).nodeOperator();
    }

    function _requireNotZero(uint256 _value) internal pure {
        if (_value == 0) revert ZeroArgument();
    }

    function _requireNotZero(address _address) internal pure {
        if (_address == address(0)) revert ZeroAddress();
    }

    function _requireSender(address _sender) internal view {
        if (msg.sender != _sender) revert NotAuthorized();
    }

    function _requireLessThanBP(uint256 _valueBP, uint256 _maxValueBP) internal pure {
        if (_valueBP > _maxValueBP) revert InvalidBasisPoints(_valueBP, _maxValueBP);
    }

    function _requireSaneShareLimit(uint256 _shareLimit) internal view {
        uint256 maxSaneShareLimit = (LIDO.getTotalShares() * MAX_RELATIVE_SHARE_LIMIT_BP) / TOTAL_BASIS_POINTS;
        if (_shareLimit > maxSaneShareLimit) revert ShareLimitTooHigh(_shareLimit, maxSaneShareLimit);
    }

    function _requireConnected(VaultConnection storage _connection, address _vault) internal view {
        if (_connection.vaultIndex == 0) revert NotConnectedToHub(_vault);
    }

    function _requireFreshReport(address _vault, VaultRecord storage _record) internal view {
        if (!_isReportFresh(_record)) revert VaultReportStale(_vault);
    }

    // -----------------------------
    //           EVENTS
    // -----------------------------

    event AllowedCodehashUpdated(bytes32 indexed codehash, bool allowed);

    event VaultConnected(
        address indexed vault,
        uint256 shareLimit,
        uint256 reserveRatioBP,
        uint256 forcedRebalanceThresholdBP,
        uint256 infraFeeBP,
        uint256 liquidityFeeBP,
        uint256 reservationFeeBP
    );

    event VaultConnectionUpdated(
        address indexed vault,
        uint256 shareLimit,
        uint256 reserveRatioBP,
        uint256 forcedRebalanceThresholdBP,
        uint256 infraFeeBP,
        uint256 liquidityFeeBP,
        uint256 reservationFeeBP
    );
    event VaultShareLimitUpdated(address indexed vault, uint256 newShareLimit);
    event VaultFeesUpdated(
        address indexed vault,
        uint256 preInfraFeeBP,
        uint256 preLiquidityFeeBP,
        uint256 preReservationFeeBP,
        uint256 infraFeeBP,
        uint256 liquidityFeeBP,
        uint256 reservationFeeBP
    );
    event VaultDisconnectInitiated(address indexed vault);
    event VaultDisconnectCompleted(address indexed vault);
    event VaultDisconnectAborted(address indexed vault, uint256 slashingReserve);
    event VaultReportApplied(
        address indexed vault,
        uint256 reportTimestamp,
        uint256 reportTotalValue,
        int256 reportInOutDelta,
        uint256 reportCumulativeLidoFees,
        uint256 reportLiabilityShares,
        uint256 reportSlashingReserve
    );

    event MintedSharesOnVault(address indexed vault, uint256 amountOfShares, uint256 lockedAmount);
    event BurnedSharesOnVault(address indexed vault, uint256 amountOfShares);
    event VaultRebalanced(address indexed vault, uint256 sharesBurned, uint256 etherWithdrawn);
    event VaultInOutDeltaUpdated(address indexed vault, int112 inOutDelta);
    event ForcedValidatorExitTriggered(address indexed vault, bytes pubkeys, address refundRecipient);

    /**
     * @notice Emitted when the manager is set
     * @param vault The address of the vault
     * @param newOwner The address of the new owner
     * @param oldOwner The address of the old owner
     */
    event VaultOwnershipTransferred(address indexed vault, address indexed newOwner, address indexed oldOwner);

    event LidoFeesUpdated(address indexed vault, uint256 unsettledLidoFees, uint256 settledLidoFees);
    event RedemptionsUpdated(address indexed vault, uint256 unsettledRedemptions);
    event RedemptionsNotSet(address indexed vault, uint256 redemptionsValue);
    event VaultObligationsSettled(
        address indexed vault,
        uint256 rebalanced,
        uint256 transferredToLido,
        uint256 unsettledRedemptions,
        uint256 unsettledLidoFees,
        uint256 settledLidoFees
    );

    // -----------------------------
    //           ERRORS
    // -----------------------------

    event BadDebtSocialized(address indexed vaultDonor, address indexed vaultAcceptor, uint256 badDebtShares);
    event BadDebtWrittenOffToBeInternalized(address indexed vault, uint256 badDebtShares);

    error ZeroBalance();

    /**
     * @notice Thrown when attempting to rebalance more ether than the current total value of the vault
     * @param totalValue Current total value of the vault
     * @param rebalanceAmount Amount attempting to rebalance (in ether)
     */
    error RebalanceAmountExceedsTotalValue(uint256 totalValue, uint256 rebalanceAmount);

    /**
     * @notice Thrown when attempting to withdraw more ether than the available value of the vault
     * @param vault The address of the vault
     * @param withdrawable The available value of the vault
     * @param requested The amount attempting to withdraw
     */
    error AmountExceedsWithdrawableValue(address vault, uint256 withdrawable, uint256 requested);

    error AlreadyHealthy(address vault);
    error VaultMintingCapacityExceeded(
        address vault,
        uint256 totalValue,
        uint256 liabilityShares,
        uint256 newRebalanceThresholdBP
    );
    error InsufficientSharesToBurn(address vault, uint256 amount);
    error ShareLimitExceeded(address vault, uint256 expectedSharesAfterMint, uint256 shareLimit);
    error AlreadyConnected(address vault, uint256 index);
    error NotConnectedToHub(address vault);
    error NotAuthorized();
    error ZeroAddress();
    error ZeroArgument();
    error InvalidBasisPoints(uint256 valueBP, uint256 maxValueBP);
    error ShareLimitTooHigh(uint256 shareLimit, uint256 maxShareLimit);
    error InsufficientValueToMint(address vault, uint256 maxLockableValue);
    error NoLiabilitySharesShouldBeLeft(address vault, uint256 liabilityShares);
    error CodehashNotAllowed(address vault, bytes32 codehash);
    error InvalidFees(address vault, uint256 newFees, uint256 oldFees);
    error VaultOssified(address vault);
    error VaultInsufficientBalance(address vault, uint256 currentBalance, uint256 expectedBalance);
    error VaultReportStale(address vault);
    error PDGNotDepositor(address vault);
    error ZeroCodehash();
    error VaultHubNotPendingOwner(address vault);
    error UnhealthyVaultCannotDeposit(address vault);
    error VaultIsDisconnecting(address vault);
    error VaultHasUnsettledObligations(address vault, uint256 unsettledObligations, uint256 allowedUnsettled);
    error PartialValidatorWithdrawalNotAllowed();
    error ForcedValidatorExitNotAllowed();
    error NoBadDebtToWriteOff(address vault, uint256 totalValueShares, uint256 liabilityShares);
    error BadDebtSocializationNotAllowed();
}

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

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

interface IDepositContract {
    function get_deposit_root() external view returns (bytes32 rootHash);

    function deposit(
        bytes calldata pubkey, // 48 bytes
        bytes calldata withdrawal_credentials, // 32 bytes
        bytes calldata signature, // 96 bytes
        bytes32 deposit_data_root
    ) external payable;
}

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

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


interface IHashConsensus {
    function getIsMember(address addr) external view returns (bool);

    function getCurrentFrame() external view returns (
        uint256 refSlot,
        uint256 reportProcessingDeadlineSlot
    );

    function getChainConfig() external view returns (
        uint256 slotsPerEpoch,
        uint256 secondsPerSlot,
        uint256 genesisTime
    );

    function getFrameConfig() external view returns (uint256 initialEpoch, uint256 epochsPerFrame);

    function getInitialRefSlot() external view returns (uint256);
}

File 36 of 47 : ILazyOracle.sol
// SPDX-FileCopyrightText: 2025 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

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

/**
 * Interface to connect AccountingOracle with LazyOracle and force type consistency
 */
interface ILazyOracle {
    function updateReportData(
        uint256 _timestamp,
        bytes32 _vaultsDataTreeRoot,
        string memory _vaultsDataReportCid
    ) external;
}

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

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

import {IERC20} from "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol";

import {IVersioned} from "contracts/common/interfaces/IVersioned.sol";

interface ILido is IERC20, IVersioned {
    function sharesOf(address) external view returns (uint256);

    function getSharesByPooledEth(uint256) external view returns (uint256);

    function getPooledEthByShares(uint256) external view returns (uint256);

    function getPooledEthBySharesRoundUp(uint256) external view returns (uint256);

    function transferSharesFrom(address, address, uint256) external returns (uint256);

    function transferShares(address, uint256) external returns (uint256);

    function rebalanceExternalEtherToInternal() external payable;

    function getTotalPooledEther() external view returns (uint256);

    function getExternalEther() external view returns (uint256);

    function getExternalShares() external view returns (uint256);

    function mintExternalShares(address, uint256) external;

    function burnExternalShares(uint256) external;

    function getTotalShares() external view returns (uint256);

    function getBeaconStat()
        external
        view
        returns (uint256 depositedValidators, uint256 beaconValidators, uint256 beaconBalance);

    function processClStateUpdate(
        uint256 _reportTimestamp,
        uint256 _preClValidators,
        uint256 _reportClValidators,
        uint256 _reportClBalance
    ) external;

    function collectRewardsAndProcessWithdrawals(
        uint256 _reportTimestamp,
        uint256 _reportClBalance,
        uint256 _adjustedPreCLBalance,
        uint256 _withdrawalsToWithdraw,
        uint256 _elRewardsToWithdraw,
        uint256 _lastWithdrawalRequestToFinalize,
        uint256 _simulatedShareRate,
        uint256 _etherToLockOnWithdrawalQueue
    ) external;

    function emitTokenRebase(
        uint256 _reportTimestamp,
        uint256 _timeElapsed,
        uint256 _preTotalShares,
        uint256 _preTotalEther,
        uint256 _postTotalShares,
        uint256 _postTotalEther,
        uint256 _postInternalShares,
        uint256 _postInternalEther,
        uint256 _sharesMintedAsFees
    ) external;

    function mintShares(address _recipient, uint256 _sharesAmount) external;

    function internalizeExternalBadDebt(uint256 _amountOfShares) external;
}

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

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

interface ILidoLocator {
    function accountingOracle() external view returns(address);
    function depositSecurityModule() external view returns(address);
    function elRewardsVault() external view returns(address);
    function lido() external view returns(address);
    function oracleReportSanityChecker() external view returns(address);
    function burner() external view returns(address);
    function stakingRouter() external view returns(address);
    function treasury() external view returns(address);
    function validatorsExitBusOracle() external view returns(address);
    function withdrawalQueue() external view returns(address);
    function withdrawalVault() external view returns(address);
    function postTokenRebaseReceiver() external view returns(address);
    function oracleDaemonConfig() external view returns(address);
    function validatorExitDelayVerifier() external view returns (address);
    function triggerableWithdrawalsGateway() external view returns (address);
    function accounting() external view returns (address);
    function predepositGuarantee() external view returns (address);
    function wstETH() external view returns (address);
    function vaultHub() external view returns (address);
    function vaultFactory() external view returns (address);
    function lazyOracle() external view returns (address);
    function operatorGrid() external view returns (address);

    /// @notice Returns core Lido protocol component addresses in a single call
    /// @dev This function provides a gas-efficient way to fetch multiple component addresses in a single call
    function coreComponents() external view returns(
        address elRewardsVault,
        address oracleReportSanityChecker,
        address stakingRouter,
        address treasury,
        address withdrawalQueue,
        address withdrawalVault
    );

    /// @notice Returns addresses of components involved in processing oracle reports in the Lido contract
    /// @dev This function provides a gas-efficient way to fetch multiple component addresses in a single call
    function oracleReportComponents() external view returns(
        address accountingOracle,
        address oracleReportSanityChecker,
        address burner,
        address withdrawalQueue,
        address postTokenRebaseReceiver,
        address stakingRouter,
        address vaultHub
    );
}

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

// See contracts/COMPILERS.md
// solhint-disable-next-line
pragma solidity >=0.4.24;

interface IVersioned {
    /// @notice Returns the current contract version.
    function getContractVersion() external view returns (uint256);
}

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

// Copied from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/0457042d93d9dfd760dbaa06a4d2f1216fdbe297/contracts/utils/math/Math.sol

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

library Math256 {
    /// @dev Returns the largest of two numbers.
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /// @dev Returns the smallest of two numbers.
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /// @dev Returns the largest of two numbers.
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /// @dev Returns the smallest of two numbers.
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /// @dev Returns the ceiling of the division of two numbers.
    ///
    /// This differs from standard division with `/` in that it rounds up instead
    /// of rounding down.
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /// @dev Returns absolute difference of two numbers.
    function absDiff(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a - b : b - a;
    }
}

// SPDX-FileCopyrightText: 2023 Lido <[email protected]>, Aragon
// SPDX-License-Identifier: MIT

// solhint-disable-next-line lido/fixed-compiler-version
pragma solidity ^0.8.9;

library UnstructuredStorage {
    function getStorageBool(bytes32 position) internal view returns (bool data) {
        assembly { data := sload(position) }
    }

    function getStorageAddress(bytes32 position) internal view returns (address data) {
        assembly { data := sload(position) }
    }

    function getStorageBytes32(bytes32 position) internal view returns (bytes32 data) {
        assembly { data := sload(position) }
    }

    function getStorageUint256(bytes32 position) internal view returns (uint256 data) {
        assembly { data := sload(position) }
    }

    function setStorageBool(bytes32 position, bool data) internal {
        assembly { sstore(position, data) }
    }

    function setStorageAddress(bytes32 position, address data) internal {
        assembly { sstore(position, data) }
    }

    function setStorageBytes32(bytes32 position, bytes32 data) internal {
        assembly { sstore(position, data) }
    }

    function setStorageUint256(bytes32 position, uint256 data) internal {
        assembly { sstore(position, data) }
    }
}

// SPDX-FileCopyrightText: 2025 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0
// solhint-disable-next-line lido/fixed-compiler-version
pragma solidity ^0.8.9;

import {UnstructuredStorage} from "contracts/common/lib/UnstructuredStorage.sol";

/**
 * @title PausableUntil
 * @notice allows to pause the contract for a specific duration or indefinitely
 */
abstract contract PausableUntil {
    using UnstructuredStorage for bytes32;

    /// Contract resume/pause control storage slot
    bytes32 internal constant RESUME_SINCE_TIMESTAMP_POSITION = keccak256("lido.PausableUntil.resumeSinceTimestamp");
    /// Special value for the infinite pause
    uint256 public constant PAUSE_INFINITELY = type(uint256).max;

    /// @notice Emitted when paused by the `pauseFor` or `pauseUntil` call
    event Paused(uint256 duration);
    /// @notice Emitted when resumed by the `resume` call
    event Resumed();

    error ZeroPauseDuration();
    error PausedExpected();
    error ResumedExpected();
    error PauseUntilMustBeInFuture();

    /// @notice Reverts if paused
    modifier whenResumed() {
        _checkResumed();
        _;
    }

    /// @notice Returns whether the contract is paused
    function isPaused() public view returns (bool) {
        return block.timestamp < RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256();
    }

    /// @notice Returns one of:
    ///  - PAUSE_INFINITELY if paused infinitely returns
    ///  - the timestamp when the contract get resumed if paused for specific duration
    ///  - some timestamp in past if not paused
    function getResumeSinceTimestamp() external view returns (uint256) {
        return RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256();
    }

    function _checkPaused() internal view {
        if (!isPaused()) {
            revert PausedExpected();
        }
    }

    function _checkResumed() internal view {
        if (isPaused()) {
            revert ResumedExpected();
        }
    }

    function _resume() internal {
        _checkPaused();
        RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(block.timestamp);
        emit Resumed();
    }

    function _pauseFor(uint256 _duration) internal {
        _checkResumed();
        if (_duration == 0) revert ZeroPauseDuration();

        uint256 resumeSince;
        if (_duration == PAUSE_INFINITELY) {
            resumeSince = PAUSE_INFINITELY;
        } else {
            resumeSince = block.timestamp + _duration;
        }
        _setPausedState(resumeSince);
    }

    function _pauseUntil(uint256 _pauseUntilInclusive) internal {
        _checkResumed();
        if (_pauseUntilInclusive < block.timestamp) revert PauseUntilMustBeInFuture();

        uint256 resumeSince;
        if (_pauseUntilInclusive != PAUSE_INFINITELY) {
            resumeSince = _pauseUntilInclusive + 1;
        } else {
            resumeSince = PAUSE_INFINITELY;
        }
        _setPausedState(resumeSince);
    }

    function _setPausedState(uint256 _resumeSince) internal {
        RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(_resumeSince);
        if (_resumeSince == PAUSE_INFINITELY) {
            emit Paused(PAUSE_INFINITELY);
        } else {
            emit Paused(_resumeSince - block.timestamp);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)

pragma solidity ^0.8.20;

import {IAccessControl} from "@openzeppelin/contracts-v5.2/access/IAccessControl.sol";
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {ERC165Upgradeable} from "../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
 * to enforce additional security measures for this role.
 */
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControl, ERC165Upgradeable {
    struct RoleData {
        mapping(address account => bool) hasRole;
        bytes32 adminRole;
    }

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;


    /// @custom:storage-location erc7201:openzeppelin.storage.AccessControl
    struct AccessControlStorage {
        mapping(bytes32 role => RoleData) _roles;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControl")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant AccessControlStorageLocation = 0x02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800;

    function _getAccessControlStorage() private pure returns (AccessControlStorage storage $) {
        assembly {
            $.slot := AccessControlStorageLocation
        }
    }

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with an {AccessControlUnauthorizedAccount} error including the required role.
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    function __AccessControl_init() internal onlyInitializing {
    }

    function __AccessControl_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual returns (bool) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        return $._roles[role].hasRole[account];
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
     * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
     * is missing `role`.
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert AccessControlUnauthorizedAccount(account, role);
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        return $._roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address callerConfirmation) public virtual {
        if (callerConfirmation != _msgSender()) {
            revert AccessControlBadConfirmation();
        }

        _revokeRole(role, callerConfirmation);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        AccessControlStorage storage $ = _getAccessControlStorage();
        bytes32 previousAdminRole = getRoleAdmin(role);
        $._roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        if (!hasRole(role, account)) {
            $._roles[role].hasRole[account] = true;
            emit RoleGranted(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        if (hasRole(role, account)) {
            $._roles[role].hasRole[account] = false;
            emit RoleRevoked(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/AccessControlEnumerable.sol)

pragma solidity ^0.8.20;

import {IAccessControlEnumerable} from "@openzeppelin/contracts-v5.2/access/extensions/IAccessControlEnumerable.sol";
import {AccessControlUpgradeable} from "../AccessControlUpgradeable.sol";
import {EnumerableSet} from "@openzeppelin/contracts-v5.2/utils/structs/EnumerableSet.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";

/**
 * @dev Extension of {AccessControl} that allows enumerating the members of each role.
 */
abstract contract AccessControlEnumerableUpgradeable is Initializable, IAccessControlEnumerable, AccessControlUpgradeable {
    using EnumerableSet for EnumerableSet.AddressSet;

    /// @custom:storage-location erc7201:openzeppelin.storage.AccessControlEnumerable
    struct AccessControlEnumerableStorage {
        mapping(bytes32 role => EnumerableSet.AddressSet) _roleMembers;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControlEnumerable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant AccessControlEnumerableStorageLocation = 0xc1f6fe24621ce81ec5827caf0253cadb74709b061630e6b55e82371705932000;

    function _getAccessControlEnumerableStorage() private pure returns (AccessControlEnumerableStorage storage $) {
        assembly {
            $.slot := AccessControlEnumerableStorageLocation
        }
    }

    function __AccessControlEnumerable_init() internal onlyInitializing {
    }

    function __AccessControlEnumerable_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns one of the accounts that have `role`. `index` must be a
     * value between 0 and {getRoleMemberCount}, non-inclusive.
     *
     * Role bearers are not sorted in any particular way, and their ordering may
     * change at any point.
     *
     * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
     * you perform all queries on the same block. See the following
     * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
     * for more information.
     */
    function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address) {
        AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
        return $._roleMembers[role].at(index);
    }

    /**
     * @dev Returns the number of accounts that have `role`. Can be used
     * together with {getRoleMember} to enumerate all bearers of a role.
     */
    function getRoleMemberCount(bytes32 role) public view virtual returns (uint256) {
        AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
        return $._roleMembers[role].length();
    }

    /**
     * @dev Return all accounts that have `role`
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function getRoleMembers(bytes32 role) public view virtual returns (address[] memory) {
        AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
        return $._roleMembers[role].values();
    }

    /**
     * @dev Overload {AccessControl-_grantRole} to track enumerable memberships
     */
    function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
        AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
        bool granted = super._grantRole(role, account);
        if (granted) {
            $._roleMembers[role].add(account);
        }
        return granted;
    }

    /**
     * @dev Overload {AccessControl-_revokeRole} to track enumerable memberships
     */
    function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
        AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
        bool revoked = super._revokeRole(role, account);
        if (revoked) {
            $._roleMembers[role].remove(account);
        }
        return revoked;
    }
}

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

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
    struct InitializableStorage {
        /**
         * @dev Indicates that the contract has been initialized.
         */
        uint64 _initialized;
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool _initializing;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

    /**
     * @dev The contract is already initialized.
     */
    error InvalidInitialization();

    /**
     * @dev The contract is not initializing.
     */
    error NotInitializing();

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint64 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        // Cache values to avoid duplicated sloads
        bool isTopLevelCall = !$._initializing;
        uint64 initialized = $._initialized;

        // Allowed calls:
        // - initialSetup: the contract is not in the initializing state and no previous version was
        //                 initialized
        // - construction: the contract is initialized at version 1 (no reininitialization) and the
        //                 current contract is just being deployed
        bool initialSetup = initialized == 0 && isTopLevelCall;
        bool construction = initialized == 1 && address(this).code.length == 0;

        if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }
        $._initialized = 1;
        if (isTopLevelCall) {
            $._initializing = true;
        }
        _;
        if (isTopLevelCall) {
            $._initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint64 version) {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing || $._initialized >= version) {
            revert InvalidInitialization();
        }
        $._initialized = version;
        $._initializing = true;
        _;
        $._initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        _checkInitializing();
        _;
    }

    /**
     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
    function _checkInitializing() internal view virtual {
        if (!_isInitializing()) {
            revert NotInitializing();
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing) {
            revert InvalidInitialization();
        }
        if ($._initialized != type(uint64).max) {
            $._initialized = type(uint64).max;
            emit Initialized(type(uint64).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _getInitializableStorage()._initializing;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        assembly {
            $.slot := INITIALIZABLE_STORAGE
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract ContextUpgradeable is Initializable {
    function __Context_init() internal onlyInitializing {
    }

    function __Context_init_unchained() internal onlyInitializing {
    }
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "@openzeppelin/contracts-v5.2/utils/introspection/IERC165.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 */
abstract contract ERC165Upgradeable is Initializable, IERC165 {
    function __ERC165_init() internal onlyInitializing {
    }

    function __ERC165_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

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

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_stETH","type":"address"},{"internalType":"address","name":"_wstETH","type":"address"},{"internalType":"address","name":"_vaultHub","type":"address"},{"internalType":"address","name":"_lidoLocator","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccessControlBadConfirmation","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"neededRole","type":"bytes32"}],"name":"AccessControlUnauthorizedAccount","type":"error"},{"inputs":[],"name":"AdjustmentNotReported","type":"error"},{"inputs":[],"name":"AdjustmentNotSettled","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"ConfirmExpiryOutOfBounds","type":"error"},{"inputs":[],"name":"ConnectedToVaultHub","type":"error"},{"inputs":[],"name":"DashboardNotAllowed","type":"error"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EthTransferFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"requestedShares","type":"uint256"},{"internalType":"uint256","name":"remainingShares","type":"uint256"}],"name":"ExceedsMintingCapacity","type":"error"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"withdrawableValue","type":"uint256"}],"name":"ExceedsWithdrawable","type":"error"},{"inputs":[],"name":"FeeValueExceed100Percent","type":"error"},{"inputs":[],"name":"IncreasedOverLimit","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentAdjustment","type":"uint256"},{"internalType":"uint256","name":"currentAtPropositionAdjustment","type":"uint256"}],"name":"InvalidatedAdjustmentVote","type":"error"},{"inputs":[],"name":"NonProxyCallsForbidden","type":"error"},{"inputs":[],"name":"ReportStale","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"SameAdjustment","type":"error"},{"inputs":[],"name":"SameRecipient","type":"error"},{"inputs":[],"name":"SenderNotMember","type":"error"},{"inputs":[],"name":"TierChangeNotConfirmed","type":"error"},{"inputs":[],"name":"VaultQuarantined","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"ZeroArgument","type":"error"},{"inputs":[],"name":"ZeroConfirmingRoles","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"oldConfirmExpiry","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newConfirmExpiry","type":"uint256"}],"name":"ConfirmExpirySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721Recovered","type":"event"},{"anonymous":false,"inputs":[],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"NodeOperatorFeeDisbursed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"oldNodeOperatorFeeRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newNodeOperatorFeeRate","type":"uint256"}],"name":"NodeOperatorFeeRateSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"oldNodeOperatorFeeRecipient","type":"address"},{"indexed":false,"internalType":"address","name":"newNodeOperatorFeeRecipient","type":"address"}],"name":"NodeOperatorFeeRecipientSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newAdjustment","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"oldAdjustment","type":"uint256"}],"name":"RewardsAdjustmentSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"},{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"confirmTimestamp","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"expiryTimestamp","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"RoleMemberConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"stakingVault","type":"address"},{"indexed":false,"internalType":"uint256","name":"deposits","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalAmount","type":"uint256"}],"name":"UnguaranteedDeposits","type":"event"},{"inputs":[],"name":"BURN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANGE_TIER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FUND_ON_RECEIVE_FLAG_SLOT","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FUND_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIDO_LOCATOR","outputs":[{"internalType":"contract ILidoLocator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANUAL_REWARDS_ADJUSTMENT_LIMIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_CONFIRM_EXPIRY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_CONFIRM_EXPIRY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NODE_OPERATOR_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NODE_OPERATOR_REWARDS_ADJUST_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_BEACON_CHAIN_DEPOSITS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PDG_COMPENSATE_PREDEPOSIT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PDG_PROVE_VALIDATOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REBALANCE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RECOVER_ASSETS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REQUEST_VALIDATOR_EXIT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESUME_BEACON_CHAIN_DEPOSITS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STETH","outputs":[{"internalType":"contract ILido","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TRIGGER_VALIDATOR_WITHDRAWAL_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNGUARANTEED_BEACON_CHAIN_DEPOSIT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VAULT_HUB","outputs":[{"internalType":"contract VaultHub","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VOLUNTARY_DISCONNECT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WSTETH","outputs":[{"internalType":"contract IWstETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"abandonDashboard","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountOfShares","type":"uint256"}],"name":"burnShares","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"burnStETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountOfWstETH","type":"uint256"}],"name":"burnWstETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tierId","type":"uint256"},{"internalType":"uint256","name":"_requestedShareLimit","type":"uint256"}],"name":"changeTier","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_pubkey","type":"bytes"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"compensateDisprovenPredepositFromPDG","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_callData","type":"bytes"},{"internalType":"bytes32","name":"_role","type":"bytes32"}],"name":"confirmation","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"confirmingRoles","outputs":[{"internalType":"bytes32[]","name":"roles","type":"bytes32[]"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tierId","type":"uint256"},{"internalType":"uint256","name":"_requestedShareLimit","type":"uint256"}],"name":"connectAndAcceptTier","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"connectToVaultHub","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"disburseNodeOperatorFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feePeriodStartReport","outputs":[{"internalType":"uint112","name":"totalValue","type":"uint112"},{"internalType":"int112","name":"inOutDelta","type":"int112"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"forcedRebalanceThresholdBP","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fund","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"getConfirmExpiry","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMembers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"role","type":"bytes32"}],"internalType":"struct Permissions.RoleAssignment[]","name":"_assignments","type":"tuple[]"}],"name":"grantRoles","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_adjustmentIncrease","type":"uint256"}],"name":"increaseRewardsAdjustment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"infraFeeBP","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_defaultAdmin","type":"address"},{"internalType":"address","name":"_nodeOperatorManager","type":"address"},{"internalType":"uint256","name":"_nodeOperatorFeeBP","type":"uint256"},{"internalType":"uint256","name":"_confirmExpiry","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestReport","outputs":[{"components":[{"internalType":"uint112","name":"totalValue","type":"uint112"},{"internalType":"int112","name":"inOutDelta","type":"int112"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"internalType":"struct VaultHub.Report","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"liabilityShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"liquidityFeeBP","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"locked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxLockableValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_amountOfShares","type":"uint256"}],"name":"mintShares","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"mintStETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_amountOfWstETH","type":"uint256"}],"name":"mintWstETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"nodeOperatorDisbursableFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nodeOperatorFeeRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nodeOperatorFeeRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pauseBeaconChainDeposits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"},{"internalType":"bytes","name":"pubkey","type":"bytes"},{"internalType":"uint256","name":"validatorIndex","type":"uint256"},{"internalType":"uint64","name":"childBlockTimestamp","type":"uint64"},{"internalType":"uint64","name":"slot","type":"uint64"},{"internalType":"uint64","name":"proposerIndex","type":"uint64"}],"internalType":"struct IPredepositGuarantee.ValidatorWitness[]","name":"_witnesses","type":"tuple[]"}],"name":"proveUnknownValidatorsToPDG","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ether","type":"uint256"}],"name":"rebalanceVaultWithEther","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_shares","type":"uint256"}],"name":"rebalanceVaultWithShares","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reconnectToVaultHub","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"recoverERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_etherToFund","type":"uint256"}],"name":"remainingMintingCapacityShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"callerConfirmation","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_pubkeys","type":"bytes"}],"name":"requestValidatorExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reservationFeeBP","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"reserveRatioBP","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"resumeBeaconChainDeposits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"role","type":"bytes32"}],"internalType":"struct Permissions.RoleAssignment[]","name":"_assignments","type":"tuple[]"}],"name":"revokeRoles","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardsAdjustment","outputs":[{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"uint64","name":"latestTimestamp","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newConfirmExpiry","type":"uint256"}],"name":"setConfirmExpiry","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newNodeOperatorFeeRate","type":"uint256"}],"name":"setNodeOperatorFeeRate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newNodeOperatorFeeRecipient","type":"address"}],"name":"setNodeOperatorFeeRecipient","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_proposedAdjustment","type":"uint256"},{"internalType":"uint256","name":"_expectedAdjustment","type":"uint256"}],"name":"setRewardsAdjustment","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"shareLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingVault","outputs":[{"internalType":"contract IStakingVault","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalMintingCapacityShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"transferVaultOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_pubkeys","type":"bytes"},{"internalType":"uint64[]","name":"_amounts","type":"uint64[]"},{"internalType":"address","name":"_refundRecipient","type":"address"}],"name":"triggerValidatorWithdrawals","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"pubkey","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes32","name":"depositDataRoot","type":"bytes32"}],"internalType":"struct IStakingVault.Deposit[]","name":"_deposits","type":"tuple[]"}],"name":"unguaranteedDepositToBeaconChain","outputs":[{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unsettledObligations","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vaultConnection","outputs":[{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint96","name":"shareLimit","type":"uint96"},{"internalType":"uint96","name":"vaultIndex","type":"uint96"},{"internalType":"bool","name":"pendingDisconnect","type":"bool"},{"internalType":"uint16","name":"reserveRatioBP","type":"uint16"},{"internalType":"uint16","name":"forcedRebalanceThresholdBP","type":"uint16"},{"internalType":"uint16","name":"infraFeeBP","type":"uint16"},{"internalType":"uint16","name":"liquidityFeeBP","type":"uint16"},{"internalType":"uint16","name":"reservationFeeBP","type":"uint16"},{"internalType":"bool","name":"isBeaconDepositsManuallyPaused","type":"bool"}],"internalType":"struct VaultHub.VaultConnection","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"voluntaryDisconnect","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_ether","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawableValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]

6101203461025857601f61618338819003918201601f19168301916001600160401b0383118484101761025c578084926080946040528339810103126102585761004881610270565b61005460208301610270565b9161006d606061006660408401610270565b9201610270565b917f1b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb09018054906201518080915560405191825260208201527f437dc1a3e26abf0b8381463abca48d1e7d322e803c2b160d3ee75c04a492a18260403392a26100d382610284565b6100dc83610284565b306080526001600160a01b0391821660a05291811660c05290819061010081610284565b61010984610284565b1660e05261010091168152604051615edc91826102a7833960805182612b6f015260a051828181610f1c015281816111c60152818161179d015281816118cd01528181611ded015281816126450152818161285f01528181612f2c0152818161337d015281816134f7015281816137620152818161381f01528181613a6801528181613bcf01528181613ed6015281816140770152818161440b0152818161466e015281816147600152818161497a01528181614de601528181614fe6015281816153f40152818161546a01528181615596015281816156a201528181615742015281816157b00152615b14015260c051828181612922015281816145cf01526154eb015260e05182818161120d01528181611e2f0152818161296601528181612c5801528181613632015261503601525181818161114b015281816127dd01528181612c13015261366b0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b038216820361025857565b6001600160a01b03161561029457565b60405163d92e233d60e01b8152600490fdfe60806040526004361015610022575b3615610018575f80fd5b610020614317565b005b5f3560e01c806301ffc9a714610631578063032d9b301461062c5780630a4a3ed2146106275780630b91990d146106225780630bff88811461061d5780630c7c92461461061857806310741552146106135780631171bda91461060e57806313e40efb14610609578063158ef93e14610604578063196b9e06146105ff578063248a9ca3146105fa57806324e7964a146105f55780632806f5e0146105f05780632959843a146105eb5780632ae87c3e146105e65780632c0772b4146105e15780632f2ff15d146105dc5780633046bf47146105d757806332cd7ebf146105d257806333885111146105cd57806335763d2b146105c857806336568abe146105c35780633a04d4f3146105be5780633a284985146105b95780633b37990e146105b45780633be75aa3146105af5780633e619a6c146105aa5780633efd8597146105a55780634156bbf4146105a057806343c710571461059b578063487e9d4e146105965780634bc368401461059157806350d99dd41461058c578063520107ad146105875780635251ebf914610582578063528c198a1461057d5780635680e145146105785780635bc7136314610573578063626c5ebc1461056e578063634bd91a14610569578063699d79bd146105645780636ab158361461055f5780636af6eeed1461055a5780636d4755661461055557806370b24b6914610550578063722adad21461054b57806373b257bc146105465780637b108d92146105415780638322fff21461053c578063845d0a3a14610537578063846f4d7f14610532578063853c637d1461052d5780638860be4114610528578063899896cb146105235780638c5d3e341461051e5780639010d07c1461051957806391d1485414610514578063956311051461050f57806399bce5e71461050a5780639c908158146105055780639c9708a914610500578063a217fddf146104fb578063a3246ad3146104f6578063a6218709146104f1578063b1003ec3146104ec578063b35869ed146104e7578063b60d4288146104e2578063b930908f146104dd578063bccad58f146104d8578063bddf300c146104d3578063be807b28146104ce578063c70cb120146104c9578063ca15c873146104c4578063ca8317af146104bf578063cb8fbf98146104ba578063cf309012146104b5578063d4c3eea0146104b0578063d547741f146104ab578063d877a3fc146104a6578063d9fb643a146104a1578063dadb623d1461049c578063dbba4b4814610497578063e00bfe5014610492578063e02023a11461048d578063e071c0ca14610488578063e55d864b14610483578063e5bca8af1461047e578063e6c772d414610479578063e9a9c85014610474578063eb990c591461046f578063ef914a091461046a578063f0e9fcd114610465578063f3fef3a314610460578063f634dcbd1461045b578063f6c76318146104565763ff108ccb0361000e57612ef2565b612ed6565b612e6c565b612d95565b612ce0565b612cc1565b612b20565b612ae6565b612a49565b612a2f565b6129f4565b6129cf565b612995565b612951565b61290d565b61280c565b6127c8565b612738565b6126fe565b6126a8565b6125eb565b6125c6565b61250c565b6124e2565b6124a8565b61246e565b6123d9565b612340565b612306565b61229b565b612261565b612227565b61218e565b61211b565b6120bd565b6120aa565b61208c565b612074565b611fdf565b611f8e565b611f49565b611f28565b611f0b565b611eec565b611dba565b611d9a565b611d5f565b611d31565b611cd5565b611cb6565b611c33565b611bbd565b611b83565b611b3b565b611b01565b611ac7565b611aa0565b611a66565b611a3f565b611a05565b61196e565b611863565b611728565b611691565b611677565b611640565b611606565b611522565b611504565b61146d565b61144a565b61141f565b6113e5565b6113be565b611373565b6112f5565b611281565b611129565b611072565b611033565b610ff9565b610fdf565b610fc5565b610fa6565b610f77565b610f4b565b610f07565b610ee5565b610dea565b610d34565b610c4c565b610b2b565b610b0e565b610ae5565b61084e565b610753565b346106a15760203660031901126106a15760043563ffffffff60e01b81168091036106a157602090635a05180f60e01b8114908115610676575b506040519015158152f35b637965db0b60e01b811491508115610690575b505f61066b565b6301ffc9a760e01b1490505f610689565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b6001600160401b0381116106cc57604052565b6106a5565b606081019081106001600160401b038211176106cc57604052565b61014081019081106001600160401b038211176106cc57604052565b90601f801991011681019081106001600160401b038211176106cc57604052565b60405190610736826106ec565b565b6001600160401b0381116106cc57601f01601f191660200190565b346106a15760403660031901126106a1576004356001600160401b0381116106a157366023820112156106a15780600401359061078f82610738565b61079c6040519182610708565b82815236602484840101116106a1575f6020846107ec9560246107db960183860137830101526107ce60243591612f6c565b905f5260205260405f2090565b546040519081529081906020820190565b0390f35b9181601f840112156106a1578235916001600160401b0383116106a1576020808501948460051b0101116106a157565b60206003198201126106a157600435906001600160401b0382116106a15761084a916004016107f0565b9091565b346106a15761085c36610820565b5f9060209061088b61087f8361087130615824565b01516001600160a01b031690565b6001600160a01b031690565b6040928351636b96736b60e01b815260048282600481875afa918215610a38575f92610aac575b505f5b88868210610a8a5750506108c761337b565b808811610a6a57506108d761438e565b6108e0876143a1565b6108e86144b1565b61091a610915886109106109046006546001600160801b031690565b6001600160801b031690565b61301c565b61453c565b855163266bcf0560e11b81528381600481885afa8015610a3857610945915f91610a3d575b5061303d565b5f5b8681106109a1578751878152602081018a90526107ec908a908a906001600160a01b038a16907f7ca089edbf098275c3dd9807ef0d5e5f7a4c4ed5a4ed4b865def78c7260f6b7f90604090a2519081529081906020820190565b6109ac81888c612fe1565b6001600160a01b03851691906109c28180613066565b916109cf89820182613066565b939095803b156106a1578d8894610a028b5f9884519b8c998a9889966304512a2360e31b885260608c01359588016130b8565b03930135905af1918215610a3857600192610a1f575b5001610947565b80610a2c610a32926106b9565b80610adb565b5f610a18565b612fc2565b610a5d9150853d8711610a63575b610a558183610708565b81019061302e565b5f61093f565b503d610a4b565b8651633cbb525b60e21b8152600481018990526024810191909152604490fd5b819888610a9d6001948a610aa595612fe1565b01359061301c565b97016108b5565b610acd919250833d8511610ad4575b610ac58183610708565b810190612faa565b905f6108b2565b503d610abb565b5f9103126106a157565b346106a1575f3660031901126106a15760205f80516020615e6783398151915254604051908152f35b346106a1575f3660031901126106a157602060405162278d008152f35b346106a1576020806003193601126106a157610b8881610b4c61087f6145b1565b610b5c61087f8361087130615824565b604051630131592760e61b81526001600160a01b03909116600482015292839190829081906024820190565b03915afa8015610a3857610be8915f91610c02575b50610bb4610baf6004356109106134c5565b614621565b610bbc613730565b918280821115610bf957610bcf91613113565b915b80821115610bf057610be291613113565b90614652565b604051908152f35b50505f90614652565b50505f91610bd1565b610c199150833d8511610a6357610a558183610708565b5f610b9d565b9181601f840112156106a1578235916001600160401b0383116106a157602083818601950101116106a157565b346106a15760203660031901126106a1576004356001600160401b0381116106a157610c7c903690600401610c1f565b335f9081527fb263cec6d0d87327feaa8f63140311e533b781670aff412d50297f0882a7530460205260409020547f32d0d6546e21c13ff633616141dc9daad87d248d1d37c56bf493d06d627ecb7b929060ff168015610d05575b15610ce6576100209250614664565b60405163e2517d3f60e01b815233600482015260248101849052604490fd5b50825f525f602052610d1e33600160405f2001546139cd565b610cd7565b6001600160a01b038116036106a157565b346106a15760603660031901126106a157600435610d5181610d23565b60243590610d5e82610d23565b335f9081527f8e22673d1bc81fb3bd6ed1d58f34ebd725bafdd9d714cf3052b13a12f254fcff60205260409020547fa38b301640bddfd3e6a9d2a11d13551d53ef81526347ff09d798738fcc5a49d4929060ff168015610dcc575b15610ce657610020925060443591613170565b50825f525f602052610de533600160405f2001546139cd565b610db9565b60603660031901126106a1576001600160401b036004358181116106a157610e16903690600401610c1f565b90916024359081116106a157610e309036906004016107f0565b9060443592610e3e84610d23565b335f9081527f894c422fd63ef7348a50b435343ac685160997c7193187427501d7dfbbf7b59760205260409020547fea19d3b23bd90fdd52445ad672f2b6fb1fef7230d49c6a827c1cd288d02994d5959060ff168015610ec7575b15610ea8576100209550614754565b60405163e2517d3f60e01b815233600482015260248101879052604490fd5b50855f525f602052610ee033600160405f2001546139cd565b610e99565b346106a1575f3660031901126106a157602060ff600254166040519015158152f35b346106a1575f3660031901126106a1576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346106a15760203660031901126106a1576004355f525f6020526020600160405f200154604051908152f35b346106a1575f3660031901126106a15760206001600160a01b0381610f9b30615824565b015116604051908152f35b346106a1575f3660031901126106a157602061ffff60a0610f9b613feb565b346106a1575f3660031901126106a1576020610be861337b565b346106a1575f3660031901126106a1576020610be86134c5565b346106a1575f3660031901126106a15760206040517fa90c7030a27f389f9fc8ed21a0556f40c88130cc14a80db936bed68261819b2c8152f35b346106a15760403660031901126106a15761002060243560043561105682610d23565b805f525f60205261106d600160405f200154614899565b61493a565b346106a15760203660031901126106a157335f9081527fe23a8bac6ee120cc0f31e97b679e63d8a06b6b0f7ec6a1eaa96912d2415af2e060205260409020547f3f82ecf462ddac43fc17ba11472c35f18b7760b4f5a5fc50b9625f9b5a22cf629060ff16801561110b575b156110ed57610020600435614970565b6044906040519063e2517d3f60e01b82523360048301526024820152fd5b50805f525f60205261112433600160405f2001546139cd565b6110dd565b346106a1576020806003193601126106a1576001600160a01b03905f8161119c7f0000000000000000000000000000000000000000000000000000000000000000851660043561117b81303385615a43565b604051948580948193636f074d1f60e11b8352600483019190602083019252565b03925af18015610a38576111b7915f91611264575b50615019565b604051638fcb4e5b60e01b81527f000000000000000000000000000000000000000000000000000000000000000084166001600160a01b0316600482015260248101829052929091908190849060449082905f907f0000000000000000000000000000000000000000000000000000000000000000165af1928315610a385761002093611246575b5050615363565b8161125c92903d10610a6357610a558183610708565b505f8061123f565b61127b9150833d8511610a6357610a558183610708565b5f6111b1565b346106a1575f3660031901126106a1576005546040516001600160a01b039091168152602090f35b9060206003198301126106a1576004356001600160401b03928382116106a157806023830112156106a15781600401359384116106a15760248460061b830101116106a1576024019190565b346106a157611303366112a9565b908115611361575f5b82811061131557005b60019061135a602080611329848888613563565b01355f611337858989613563565b359261134284610d23565b828252526113558560405f200154614899565b6149e6565b500161130c565b60405163b7852ebb60e01b8152600490fd5b346106a15760403660031901126106a15760243561139081610d23565b336001600160a01b038216036113ac57610020906004356149e6565b60405163334bd91960e11b8152600490fd5b346106a1575f3660031901126106a15760206040515f80516020615e278339815191528152f35b346106a1575f3660031901126106a15760206040517fa38b301640bddfd3e6a9d2a11d13551d53ef81526347ff09d798738fcc5a49d48152f35b346106a15760403660031901126106a1576020611440602435600435613573565b6040519015158152f35b346106a1575f3660031901126106a15760206001600160601b0381610f9b613feb565b60403660031901126106a15760043561148581610d23565b34611498575b61002090602435906135f0565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff1680156114e6575b156110ed5750610020906114df34614ddc565b905061148b565b50805f525f6020526114ff33600160405f2001546139cd565b6114cc565b346106a15760203660031901126106a1576020611440600435613708565b346106a15760403660031901126106a1576004356001600160401b0381116106a157611552903690600401610c1f565b6024359061155f82610d23565b335f9081527fb23b36340bdad7eb67d390e1f617f5a66ce55ba02098689f188a1df60c41421060205260409020547f17960a6b137243c888669d93712b617414dd6d1ab55d5eca488ccb1d0894cd5c939060ff1680156115e8575b156115c9576100209350614f8f565b60405163e2517d3f60e01b815233600482015260248101859052604490fd5b50835f525f60205261160133600160405f2001546139cd565b6115ba565b346106a1575f3660031901126106a15760206040517f59d005e32db662b94335d6bedfeb453fd2202b9f0cc7a6ed498d9098171744b08152f35b346106a1575f3660031901126106a15760406006546001600160401b038251916001600160801b038116835260801c166020820152f35b346106a1575f3660031901126106a1576020610be8613730565b60403660031901126106a1576004356116a981610d23565b346116bc575b61002090602435906137ae565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff16801561170a575b156110ed57506100209061170334614ddc565b90506116af565b50805f525f60205261172333600160405f2001546139cd565b6116f0565b346106a15761173636610820565b335f9081527f67416b44d24f10cfdaeae95ddaabfedeacd76567f63dd48b7ad4e79941f9b8586020908152604091829020547fb850402129bccae797798069a8cf3147a0cb7c3193f70558a75f7df0b8651c309060ff168015611845575b156110ed5750917f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316905f5b8181106117d157005b6117e161087f8661087130615824565b906117ed818489615071565b91843b156106a157855163015004fd60e61b8152925f91849182916118169190600484016150e8565b038183885af1918215610a3857600192611832575b50016117c8565b80610a2c61183f926106b9565b5f61182b565b50805f525f60205261185e33600160405f2001546139cd565b611794565b346106a15760203660031901126106a15760043561188081610d23565b6001600160a01b036118c8602061189d61087f8261087130615824565b604051600161da2560e61b031981526001600160a01b03909116600482015291829081906024820190565b0381857f0000000000000000000000000000000000000000000000000000000000000000165afa908115610a38575f9161193f575b5061192d578116301461191b57610020906119166151d0565b61521a565b604051633b07e30760e11b8152600490fd5b6040516309e1b82160e21b8152600490fd5b611961915060203d602011611967575b6119598183610708565b8101906137c0565b5f6118fd565b503d61194f565b60403660031901126106a15760043561198681610d23565b34611999575b6100209060243590614e48565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff1680156119e7575b156110ed5750610020906119e034614ddc565b905061198c565b50805f525f602052611a0033600160405f2001546139cd565b6119cd565b346106a1575f3660031901126106a15760206040517f3f82ecf462ddac43fc17ba11472c35f18b7760b4f5a5fc50b9625f9b5a22cf628152f35b346106a1575f3660031901126106a15760206040515f80516020615e478339815191528152f35b346106a1575f3660031901126106a15760206040517fb850402129bccae797798069a8cf3147a0cb7c3193f70558a75f7df0b8651c308152f35b346106a1575f3660031901126106a15760206040515f80516020615e878339815191528152f35b346106a1575f3660031901126106a15760206040517f32d0d6546e21c13ff633616141dc9daad87d248d1d37c56bf493d06d627ecb7b8152f35b346106a1575f3660031901126106a15760206040517fea19d3b23bd90fdd52445ad672f2b6fb1fef7230d49c6a827c1cd288d02994d58152f35b346106a1575f3660031901126106a1576060611b556137d4565b63ffffffff60408051926001600160701b0381511684526020810151600d0b60208501520151166040820152f35b346106a1575f3660031901126106a15760206040517f9586321ac05f110e4b4a0a42aba899709345af0ca78910e8832ddfd71fed2bf48152f35b346106a1575f806003193601126106a157611bd6614843565b6001600160a01b036020611be930615824565b015116803b156106a1575f80916004604051809481936379ba509760e01b83525af18015610a3857611c22575b50611c1f613ece565b80f35b611c2c91506106b9565b5f80611c16565b60203660031901126106a15734611c50575b6100206004356138cc565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff168015611c98575b156110ed5750611c9334614ddc565b611c45565b50805f525f602052611cb133600160405f2001546139cd565b611c84565b346106a1575f3660031901126106a157602061ffff60c0610f9b613feb565b346106a1575f3660031901126106a157611ced613987565b604051809160208083016020845282518091526020604085019301915f5b828110611d1a57505050500390f35b835185528695509381019392810192600101611d0b565b346106a1575f3660031901126106a157602060405173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee8152f35b60403660031901126106a157611d73613ece565b611d8160243560043561526e565b15611d8857005b60405163ce2a477560e01b8152600490fd5b346106a1575f3660031901126106a157602061ffff610100610f9b613feb565b346106a1576020806003193601126106a157604051636d78045960e01b8152336004808301919091526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116602484015290356044830181905292919082908290815f81606481015b03927f0000000000000000000000000000000000000000000000000000000000000000165af18015610a3857611ecf575b507f689f0a569be0c9b6cd2c11c81cb0add722272abdae6b649fdb1e05f1d9bb8a2f5f8181528083526040808220338352602052902090919060ff905416908115611eb1575b50156110ed57610020826153ea565b5f915082825252611ec933600160405f2001546139cd565b5f611ea2565b611ee590823d8411610a6357610a558183610708565b505f611e5c565b346106a1575f3660031901126106a157602061ffff6080610f9b613feb565b346106a1575f3660031901126106a1576020600354604051908152f35b346106a15760403660031901126106a157602061144060243560043561526e565b346106a15760403660031901126106a1576004355f5260016020526020611f7560243560405f20615b88565b905460405160039290921b1c6001600160a01b03168152f35b346106a15760403660031901126106a157602060ff611fd3602435611fb281610d23565b6004355f525f845260405f209060018060a01b03165f5260205260405f2090565b54166040519015158152f35b346106a1575f3660031901126106a157335f9081527f3f3dda3fcd93f9e4551f2c87aa737426b704aa48dbb418e3deea799b35c4558960205260409020547f59d005e32db662b94335d6bedfeb453fd2202b9f0cc7a6ed498d9098171744b09060ff168015612056575b156110ed57610020615460565b50805f525f60205261206f33600160405f2001546139cd565b612049565b346106a1575f3660031901126106a1576100206139f4565b346106a15760203660031901126106a1576020611440600435613bcd565b5f3660031901126106a157610020613ece565b346106a1575f3660031901126106a15760206040515f8152f35b60209060206040818301928281528551809452019301915f5b8281106120fe575050505090565b83516001600160a01b0316855293810193928101926001016120f0565b346106a1576020806003193601126106a1576004355f5260018060205260405f20916040519182602085549182815201945f5260205f20925f905b828210612179576107ec8661216d818a0382610708565b604051918291826120d7565b84548752958601959383019390830190612156565b346106a1575f3660031901126106a1576121d260206001600160a01b03610b5c816121b76145b1565b1691836121c330615824565b0151166001600160a01b031690565b03915afa908115610a38576107ec916121f8915f91612208575b50610be2610baf6134c5565b6040519081529081906020820190565b612221915060203d602011610a6357610a558183610708565b5f6121ec565b346106a1575f3660031901126106a15760206040517f655996ffb2e08cf642b4ce5cc56668761aed116cdfcb21f71ff9fe7fbcbd450e8152f35b346106a1575f3660031901126106a15760206040517fe0b9915a7819e810f29b50730662441fec3443eb363b7e7c90c77fada416f2768152f35b5f3660031901126106a157335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff1680156122e8575b156110ed5761002034614ddc565b50805f525f60205261230133600160405f2001546139cd565b6122da565b346106a1575f3660031901126106a15760206040517f689f0a569be0c9b6cd2c11c81cb0add722272abdae6b649fdb1e05f1d9bb8a2f8152f35b346106a15760203660031901126106a157335f9081527fff5c6f7275dcc6faef7a8ce369f5d9cdfe4b73e6d3ca76b92d14e1011cfad56860205260409020547fe0b9915a7819e810f29b50730662441fec3443eb363b7e7c90c77fada416f2769060ff1680156123bb575b156110ed57610020600435613fba565b50805f525f6020526123d433600160405f2001546139cd565b6123ab565b346106a1575f3660031901126106a157335f9081527f081a651349dfbf423ffb5b045a53f71668561e554402e2db71c1db19bfb815a560205260409020547fa90c7030a27f389f9fc8ed21a0556f40c88130cc14a80db936bed68261819b2c9060ff168015612450575b156110ed5761002061558c565b50805f525f60205261246933600160405f2001546139cd565b612443565b346106a1575f3660031901126106a15760206040517f17960a6b137243c888669d93712b617414dd6d1ab55d5eca488ccb1d0894cd5c8152f35b346106a1575f3660031901126106a15760206040517fea6487df651bb740150364c496e1c7403dd62063c96e44906cc98c6a919a9d888152f35b346106a15760203660031901126106a1576004355f526001602052602060405f2054604051908152f35b346106a1575f3660031901126106a157610140612527613feb565b60405181516001600160a01b03168152906125c4906020818101516001600160601b0316908401526040818101516001600160601b03169084015260608181015115159084015260808181015161ffff169084015260a08181015161ffff169084015260c08181015161ffff169084015260e08181015161ffff16908401526101008181015161ffff169084015261012090810151151590830152565bf35b346106a1575f3660031901126106a15760206040516a084595161401484a0000008152f35b346106a1575f3660031901126106a15761264160206001600160a01b03808261261330615824565b015160405163cbf9fe5f60e01b815291166001600160a01b0316600482015292839190829081906024820190565b03917f0000000000000000000000000000000000000000000000000000000000000000165afa8015610a38576107ec915f91612689575b506040519081529081906020820190565b6126a2915060203d602011610a6357610a558183610708565b5f612678565b346106a1575f3660031901126106a15761264160206001600160a01b0380826126d030615824565b01516040516330b0680b60e01b815291166001600160a01b0316600482015292839190829081906024820190565b346106a15760403660031901126106a15761002060243560043561272182610d23565b805f525f602052611355600160405f200154614899565b346106a15760203660031901126106a15760043561275581610d23565b335f9081527fb888a84a969c69ee493013b8ca47e9ecebe24196f231bedf425581252c3c62e360205260409020545f80516020615e278339815191529060ff1680156127aa575b156110ed57610020826155f9565b50805f525f6020526127c333600160405f2001546139cd565b61279c565b346106a1575f3660031901126106a1576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346106a1575f3660031901126106a15761285b606061283261087f602061087130615824565b604051635637c1d960e11b81526001600160a01b03909116600482015291829081906024820190565b03817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa8015610a38576128d66121f8916107ec935f916128de575b506128d061090460406128c161090460208601516001600160801b031690565b9301516001600160801b031690565b9061301c565b6128d0614181565b612900915060603d606011612906575b6128f88183610708565b8101906140dd565b5f6128a1565b503d6128ee565b346106a1575f3660031901126106a1576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346106a1575f3660031901126106a1576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346106a1575f3660031901126106a15760206040517f355caf1c2580ed8185acb5ea3573b71f85186b41bdf69e3eb8f1fcd122a562df8152f35b346106a15760203660031901126106a1576100206004356129ef81610d23565b615688565b346106a1575f3660031901126106a1576060600454604051906001600160701b03811682528060701c600d0b602083015260e01c6040820152f35b346106a1575f3660031901126106a1576020610be8614181565b346106a1575f3660031901126106a157612a616139f4565b335f9081527f6691f8833b7b4761748530debf638b71868148ba67d9725d03a36fa8c55c585960205260409020547f9586321ac05f110e4b4a0a42aba899709345af0ca78910e8832ddfd71fed2bf49060ff168015612ac8575b156110ed57610020615738565b50805f525f602052612ae133600160405f2001546139cd565b612abb565b346106a1575f3660031901126106a15760206040517fe996ac9b332538bb1fa3cd6743aa47011623cdb94bd964a494ee9d371e4a27d38152f35b346106a15760803660031901126106a157600435612b3d81610d23565b60243590612b4a82610d23565b60443591612b57816146d8565b6002549160ff8316612cb0576001600160a01b0393307f0000000000000000000000000000000000000000000000000000000000000000861614612c9e57612bb3612be5926001612bf39660ff19161760025560643590615b9d565b7f5daa87a0e9463431830481fd4b6e3403442dfb9a12b9c07597e9f61d50b633c85f80a1612be081615520565b61553c565b612bee816155f9565b6148db565b50612bfc615c0a565b612c04615c52565b60405163095ea7b360e01b81527f000000000000000000000000000000000000000000000000000000000000000082166001600160a01b031660048201525f19602482015290602090829060449082905f907f0000000000000000000000000000000000000000000000000000000000000000165af18015610a3857612c8657005b6100209060203d602011611967576119598183610708565b60405163f9cc8ff960e01b8152600490fd5b60405162dc149f60e41b8152600490fd5b346106a1575f3660031901126106a157602061ffff60e0610f9b613feb565b346106a15760603660031901126106a157600435612cfd81610d23565b604435612d0981610d23565b335f9081527f8e22673d1bc81fb3bd6ed1d58f34ebd725bafdd9d714cf3052b13a12f254fcff60205260409020547fa38b301640bddfd3e6a9d2a11d13551d53ef81526347ff09d798738fcc5a49d4929060ff168015612d77575b15610ce657610020925060243590614273565b50825f525f602052612d9033600160405f2001546139cd565b612d64565b346106a15760403660031901126106a157600435612db281610d23565b602435612dbd61337b565b808211612e4d5750335f9081527fd86621a8c2273efca305981f63eac7b1eb8bb1b258c36bf730770cdcb2e9c89a60205260409020547f355caf1c2580ed8185acb5ea3573b71f85186b41bdf69e3eb8f1fcd122a562df929060ff168015612e2f575b15610ce65761002092506157a5565b50825f525f602052612e4833600160405f2001546139cd565b612e20565b604051633cbb525b60e21b815260048101929092526024820152604490fd5b346106a157612e7a366112a9565b90612e84826146fa565b5f5b828110612e8f57005b600190612ecf602080612ea3848888613563565b01355f612eb1858989613563565b3592612ebc84610d23565b8282525261106d8560405f200154614899565b5001612e86565b346106a1575f3660031901126106a1576020604051610e108152f35b346106a1576020806003193601126106a157612f0f600435615019565b604051636d78045960e01b81523360048201526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660248301526044820183905291929182908290815f8160648101611e2b565b60208091604051928184925191829101835e81017f1b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb090081520301902090565b908160209103126106a15751612fbf81610d23565b90565b6040513d5f823e3d90fd5b634e487b7160e01b5f52603260045260245ffd5b91908110156130035760051b81013590607e19813603018212156106a1570190565b612fcd565b634e487b7160e01b5f52601160045260245ffd5b9190820180921161302957565b613008565b908160209103126106a1575190565b9060405191602083015260208252604082018281106001600160401b038211176106cc57604052565b903590601e19813603018212156106a157018035906001600160401b0382116106a1576020019181360383136106a157565b908060209392818452848401375f828201840152601f01601f1916010190565b96959490926130d661310e946060969460808b5260808b0191613098565b602080928a8303828c01528051918291828552018484015e5f828201840152601f01601f191601888103820160408a01520191613098565b930152565b9190820391821161302957565b6131306020610b4c61087f6145b1565b03915afa8015610a3857612fbf915f91613151575b50610bb4610baf6134c5565b61316a915060203d602011610a6357610a558183610708565b5f613145565b91909161317c816146d8565b613185836146d8565b61318e826146fa565b6001600160a01b039081169273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee840361324f575f808080868686165af13d1561324a573d6131cf81610738565b906131dd6040519283610708565b81525f60203d92013e5b1561321e57907faca8fb252cde442184e5f10e0f2e6e4029e8cd7717cae63559079610702436aa915b6040519384521691602090a3565b604051636f54afdd60e11b81526001600160a01b03919091166004820152602481019290925250604490fd5b6131e7565b908161327d847faca8fb252cde442184e5f10e0f2e6e4029e8cd7717cae63559079610702436aa9487614701565b613210565b519061073682610d23565b51906001600160601b03821682036106a157565b519081151582036106a157565b519061ffff821682036106a157565b90816101409103126106a1576132d1610729565b906132db81613282565b82526132e96020820161328d565b60208301526132fa6040820161328d565b604083015261330b606082016132a1565b606083015261331c608082016132ae565b608083015261332d60a082016132ae565b60a083015261333e60c082016132ae565b60c083015261334f60e082016132ae565b60e08301526101006133628183016132ae565b908301526133746101208092016132a1565b9082015290565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166133b661087f602061087130615824565b60405163280be2f560e01b81526001600160a01b039190911660048201526101408082602481865afa8015610a38576133fc926060925f92613498575b50500151151590565b6134935760206134429161341661087f8361087130615824565b604051633a4952a360e01b81526001600160a01b03909116600482015292839190829081906024820190565b03915afa908115610a38575f91613474575b5061345d614181565b8082111561346e57612fbf91613113565b50505f90565b61348d915060203d602011610a6357610a558183610708565b5f613454565b505f90565b6134b79250803d106134be575b6134af8183610708565b8101906132bd565b5f806133f3565b503d6134a5565b6001600160a01b03602081816134da30615824565b015116602460405180948193632de0e07560e01b835260048301527f0000000000000000000000000000000000000000000000000000000000000000165afa908115610a38575f91613544575b50613530614181565b8082111561346e5781039081116130295790565b61355d915060203d602011610a6357610a558183610708565b5f613527565b91908110156130035760061b0190565b906001600160801b0360065416908082036135d25750506a084595161401484a00000081116135c0576135ad6135a7613987565b36614ae2565b15613493576135bb9061453c565b600190565b60405163f579fde760e01b8152600490fd5b6044925060405191637f8c068160e11b835260048301526024820152fd5b9061362d916135ff8230614e48565b6040516240cd2760e21b815260048101929092526020926001600160a01b0392908490829081906024820190565b0381867f0000000000000000000000000000000000000000000000000000000000000000165afa908115610a38576136ab9385915f936136e9575b507f0000000000000000000000000000000000000000000000000000000000000000169160405180958192630ea598cb60e41b8352600483019190602083019252565b03815f855af1928315610a3857610736945f946136ca575b5050614701565b6136e1929450803d10610a6357610a558183610708565b915f806136c3565b613701919350823d8411610a6357610a558183610708565b915f613668565b61371181614f02565b61372261371c613987565b36614c7c565b15613493576135bb90614f35565b6001600160a01b036020818161374530615824565b01511660246040518094819363329ce48960e21b835260048301527f0000000000000000000000000000000000000000000000000000000000000000165afa908115610a38575f91613795575090565b612fbf915060203d602011610a6357610a558183610708565b6137ba61073692615019565b90614e48565b908160209103126106a157612fbf906132a1565b60405f8180516137e3816106d1565b828152826020820152015260018060a01b03606081602061380330615824565b015116602484518094819363f9c821a760e01b835260048301527f0000000000000000000000000000000000000000000000000000000000000000165afa918215610a38575f9261385357505090565b9091506060823d6060116138c4575b8161386f60609383610708565b810103126106a157805191613883836106d1565b80516001600160701b03811681036106a157835260208101519081600d0b82036106a1578291602085015201519063ffffffff821682036106a15782015290565b3d9150613862565b6138d590615019565b335f9081527fe23a8bac6ee120cc0f31e97b679e63d8a06b6b0f7ec6a1eaa96912d2415af2e060205260409020547f3f82ecf462ddac43fc17ba11472c35f18b7760b4f5a5fc50b9625f9b5a22cf629060ff16801561393e575b156110ed575061073690614970565b50805f525f60205261395733600160405f2001546139cd565b61392f565b6001600160401b0381116106cc5760051b60200190565b80518210156130035760209160051b010190565b60405190613994826106d1565b60028252602082019160403684378092815115613003575f90528051600110156130035760405f80516020615e27833981519152910152565b5f908152602081815260408083206001600160a01b0390941683529290522060ff90541690565b6139fc614181565b8015613b4f576001600160801b03613a1c6006546001600160801b031690565b16613b42575b613a66613a2d6137d4565b6001600160701b03815116602082015160701b91604063ffffffff60e01b91015160e01b16916001600160701b0360701b161717600455565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316613aa161087f602061087130615824565b6005546001600160a01b031690823b156106a157604051636ce5768960e11b81526001600160a01b0391821660048201529116602482015260448101839052905f908290606490829084905af18015610a3857613b2f575b5060405190815233907f66a284d7a46e07e3f2d18b3955d275877589083787a7f656543d82c49a5db9c09080602081015b0390a2565b80610a2c613b3c926106b9565b5f613af9565b613b4a6144c3565b613a22565b50565b6001600160401b038116036106a157565b908160209103126106a15751612fbf81613b52565b908160809103126106a1576040519060808201908282106001600160401b038311176106cc57606091604052613bad816132a1565b835260208101516020840152604081015160408401520151606082015290565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166020613c0961087f8261087130615824565b60408051634a2264ef60e01b81526001600160a01b0392909216600480840191909152909290918181602481885afa908115610a38575f91613eb1575b5015613ea2576006546001600160401b038383613c6461087f6154cd565b875163c6290e3560e01b815292839182905afa8015610a385782915f91613e75575b5016908260801c161015613e65576001600160801b0316613e5657613ced6080613cb161087f6154cd565b613cc161087f8561087130615824565b86516329938c8360e21b81526001600160a01b0390911686820190815290938492918391829160200190565b03915afa8015610a3857613d09915f91613e27575b5051151590565b613e1857613d1685615520565b613d28613d2461371c613987565b1590565b613e0f57613d43613d37614181565b95612be0613a2d6137d4565b84613d53575b5050505050600190565b61087f613d639161087130615824565b6005549093906001600160a01b031690803b156106a1578351636ce5768960e11b81526001600160a01b03958616938101938452949091166020830152604082018590525f9184919082908490829060600103925af1918215610a38577f66a284d7a46e07e3f2d18b3955d275877589083787a7f656543d82c49a5db9c092613dfc575b50519182523391602090a25f80808080613d49565b80610a2c613e09926106b9565b5f613de7565b50505050505f90565b50905163a9d1c85160e01b8152fd5b613e49915060803d608011613e4f575b613e418183610708565b810190613b78565b5f613d02565b503d613e37565b509051635abae25f60e01b8152fd5b8351632a68326f60e11b81528390fd5b613e959150853d8711613e9b575b613e8d8183610708565b810190613b63565b5f613c86565b503d613e83565b5090516366ad59df60e11b8152fd5b613ec89150823d8411611967576119598183610708565b5f613c46565b34613f65575b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316613f078161521a565b613f1861087f602061087130615824565b90803b156106a15760405163135c2b5960e31b81526001600160a01b039290921660048301525f908290602490829084905af18015610a3857613f585750565b80610a2c610736926106b9565b613f7661087f602061087130615824565b803b156106a1575f600491604051928380926316c1a85160e31b825234905af18015610a3857613fa7575b50613ed4565b80610a2c613fb4926106b9565b5f613fa1565b6001600160801b0360065416908101809111613029576a084595161401484a00000081116135c0576107369061453c565b5f610120604051613ffb816106ec565b8281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e082015282610100820152015261407361404561087f602061087130615824565b60405163280be2f560e01b81526001600160a01b039091166004820152610140918290829081906024820190565b03817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa918215610a38575f926140b357505090565b612fbf9250803d106134be576134af8183610708565b51906001600160801b03821682036106a157565b908160609103126106a15761411b60408051926140f9846106d1565b614102816140c9565b8452614110602082016140c9565b6020850152016140c9565b604082015290565b600d91820b910b03906d7fffffffffffffffffffffffffff1982126d7fffffffffffffffffffffffffff83131761302957565b81810392915f13801582851316918412161761302957565b8181029291811591840414171561302957565b61424f604051614190816106d1565b61424a6142446004546001600160701b038116845260208401908060701c600d0b825260e01c604085015261422d6141c66137d4565b9161422d61423c60206142336141f56141ef6141ea6006546001600160801b031690565b615710565b600f0b90565b9961422d61421061421f6142108b516001600160701b031690565b6001600160701b0316600d0b90565b92516001600160701b031690565b90614123565b950151600d0b90565b9151600d0b90565b600d0b90565b614156565b5f811361425b57505f90565b61426b612fbf916003549061416e565b612710900490565b61427c816146d8565b614285836146d8565b6001600160a01b0390811692833b156106a157604051632142170760e11b81523060048201526001600160a01b038216602482015260448101849052915f8360648183895af1918215610a38577f6a30e6784464f0d1f4158aa4cb65ae9239b0fa87c7f2c083ee6dde44ba97b5e693602093614308575b506040519485521692a3565b614311906106b9565b5f6142fc565b5f80516020615e878339815191525c1561432d57565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff168015614370575b156110ed575061073634614ddc565b50805f525f60205261438933600160405f2001546139cd565b614361565b60015f80516020615e878339815191525d565b335f9081527f445ddf9e128c79eca57440455d965dbaaca76bdc8b6662aee0bd18f61bc6486160205260409020547fea6487df651bb740150364c496e1c7403dd62063c96e44906cc98c6a919a9d889060ff168015614493575b156110ed57506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169190602061443930615824565b01511690823b156106a157604051636ce5768960e11b81526001600160a01b0390921660048301523060248301526044820152905f908290818381606481015b03925af18015610a385761448a5750565b610736906106b9565b50805f525f6020526144ac33600160405f2001546139cd565b6143fb565b5f5f80516020615e878339815191525d565b6006546001600160801b03811690811561452a576001600160c01b0319164260801b67ffffffffffffffff60801b1617600655604080515f815260208101929092527f6294ce24c5cb1a9fd25acf9dc37a0c6cdd144f222c80f43b5582bc86a882564091a1565b6040516318a04aa160e01b8152600490fd5b600654906001600160801b03908183169182821461452a576001600160c01b031993909316928116929092174260801b67ffffffffffffffff60801b16176006556040805192835260208301919091527f6294ce24c5cb1a9fd25acf9dc37a0c6cdd144f222c80f43b5582bc86a882564091a1565b604051631734070560e31b81526001600160a01b03906020816004817f000000000000000000000000000000000000000000000000000000000000000086165afa908115610a38575f9161460457501690565b61461d915060203d602011610ad457610ac58183610708565b1690565b61ffff608061462e613feb565b0151166127109081039181831161302957612fbf9261464c9161416e565b04615019565b908082101561465f575090565b905090565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169190602061469c30615824565b01511691803b156106a157614479935f80946040519687958694859363015a8f6160e61b85526004850152604060248501526044840191613098565b6001600160a01b0316156146e857565b60405163d92e233d60e01b8152600490fd5b1561136157565b60405163a9059cbb60e01b60208201526001600160a01b03909216602483015260448201929092526107369161474482606481015b03601f198101845283610708565b61586a565b359061073682613b52565b90936001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116949092602090848261479230615824565b01511697873b156106a1579795949392906147cf6020916040519a8b99632f00a7e160e01b8b5260048b0152608060248b015260848a0191613098565b8781036003190160448901528281520194915f5b82811061480e57505050505f949284928392166064830152039134905af18015610a3857613f585750565b9193949583975090806001926001600160401b0382963561482e81613b52565b168152019701910191889695949391926147e3565b335f9081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5602052604090205460ff161561487b57565b60405163e2517d3f60e01b81523360048201525f6024820152604490fd5b5f8181526020818152604080832033845290915290205460ff16156148bb5750565b60405163e2517d3f60e01b81523360048201526024810191909152604490fd5b6148e4816158c4565b90816148ee575090565b5f80516020615e278339815191525f526001602052614936906001600160a01b03167fa5a481183f76091139902c30e9c198fa5bc995beffa115a7cdc58a48752719ad615cbc565b5090565b6149448282615a1c565b918261494f57505090565b5f91825260016020526040909120614936916001600160a01b031690615cbc565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116919060206149a830615824565b01511690823b156106a1576040516303da9b9d60e41b81526001600160a01b0390921660048301526024820152905f90829081838160448101614479565b6149f08282615a85565b91826149fb57505090565b5f91825260016020526040909120614936916001600160a01b031690615d68565b90614a268261395c565b614a336040519182610708565b8281528092614a44601f199161395c565b0190602036910137565b60206040518281935f833781017f1b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb090081520301902090565b5f1981146130295760010190565b91608093918352602083015260606040830152806060830152805f848401375f828201840152601f01601f1916010190565b91614ade9183549060031b91821b915f19901b19161790565b9055565b815115614c6a578151915f614af684614a1c565b5f614b0f5f80516020615e67833981519152544261301c565b925f5b878110614bb957509015614ba7578503614b5c5750505f5b838110614b3a5750505050600190565b805f614b55614b4b60019486613973565b516107ce87614a4e565b5501614b2a565b5f9391935b858110614b72575050505050505f90565b80614b88614b8260019385613973565b51151590565b614b93575b01614b61565b85614ba1614b4b8388613973565b55614b8d565b60405163dd16cb2360e01b8152600490fd5b614bc38187613973565b5192614bcf33856139cd565b15614c365750600190614be28293614a85565b93614bf6614bf08388613973565b60019052565b6040517f52a98ceea5bf73fc6482a17b325efa51b21372c60578c2fa4cac0c2fa780035a339180614c29368c4284614a93565b0390a35b01919091614b12565b9192614c4642916107ce8a614a4e565b541015614c56575b600190614c2d565b91614c62600191614a85565b929050614c4e565b60405163f814a95b60e01b8152600490fd5b815115614c6a578151915f614c9084614a1c565b5f614ca95f80516020615e67833981519152544261301c565b925f5b878110614d3157509015614ba7578503614cec5750505f5b838110614cd45750505050600190565b805f614ce5614b4b60019486613973565b5501614cc4565b5f9391935b858110614d02575050505050505f90565b80614d12614b8260019385613973565b614d1d575b01614cf1565b85614d2b614b4b8388613973565b55614d17565b614d3b8187613973565b5192614d4733856139cd565b15614da85750600190614d5a8293614a85565b93614d68614bf08388613973565b6040517f52a98ceea5bf73fc6482a17b325efa51b21372c60578c2fa4cac0c2fa780035a339180614d9b368c4284614a93565b0390a35b01919091614cac565b9192614db842916107ce8a614a4e565b541015614dc8575b600190614d9f565b91614dd4600191614a85565b929050614dc0565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116906020614e1330615824565b01511690803b156106a15760245f926040519485938492630460488160e31b845260048401525af18015610a385761448a5750565b9190614e52613120565b808211614ee35750335f9081527fd8ab213733a751ec9ca51242fa42dd7f07dc61105cb44f3595d018b75a32f4f560205260409020547fe996ac9b332538bb1fa3cd6743aa47011623cdb94bd964a494ee9d371e4a27d3939060ff168015614ec5575b156115c957610736929350615b09565b50835f525f602052614ede33600160405f2001546139cd565b614eb5565b60405163087df69d60e41b815260048101929092526024820152604490fd5b610e108110908115614f28575b50614f1657565b60405163bbc3a3e360e01b8152600490fd5b62278d009150115f614f0f565b614f3e81614f02565b5f80516020615e6783398151915280549082905560408051918252602082019290925233917f437dc1a3e26abf0b8381463abca48d1e7d322e803c2b160d3ee75c04a492a182919081908101613b2a565b614fdb926020926001600160a01b03915f908386614fac30615824565b015116908460405198899788968795636c1d053960e11b87526004870152606060248701526064860191613098565b9116604483015203927f0000000000000000000000000000000000000000000000000000000000000000165af1908115610a38575f91613795575090565b604051631920845160e01b815260048101919091526020816024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610a38575f91613795575090565b91908110156130035760051b8101359060be19813603018212156106a1570190565b81835290916001600160fb1b0383116106a15760209260051b809284830137010190565b9035601e19823603018112156106a15701602081359101916001600160401b0382116106a15781360383136106a157565b6001600160a01b03909116815260406020820152813536839003601e19018112156106a157820190602082359201916001600160401b0381116106a1578060051b360383136106a1576151c260a0615174615155612fbf9660e09560c06040890152610100880191615093565b61516260208901896150b7565b878303603f1901606089015290613098565b956040810135608086015261519d61518e60608301614749565b6001600160401b031686840152565b6151bc6151ac60808301614749565b6001600160401b031660c0870152565b01614749565b6001600160401b0316910152565b6151d8614843565b6001600160a01b0360206151eb30615824565b015116803b156106a1575f80916004604051809481936379ba509760e01b83525af18015610a385761448a5750565b615222614843565b6001600160a01b039081602061523730615824565b01511691823b156106a15760245f9283604051958694859363f2fde38b60e01b85521660048401525af18015610a385761448a5750565b335f9081527f33325f4ccadc9d27525cbee4f590af3a5c5c236abe27b43530b1a65b898523876020908152604090912054909291907f655996ffb2e08cf642b4ce5cc56668761aed116cdfcb21f71ff9fe7fbcbd450e9060ff168015615346575b156110ed5750829060646001600160a01b035f816152eb6145b1565b1691856152f730615824565b0151169160405196879586946321d5a4bf60e11b86526004860152602485015260448401525af1918215610a38575f9261533057505090565b612fbf9250803d10611967576119598183610708565b50805f525f845261535e33600160405f2001546139cd565b6152cf565b335f9081527fa2e2d001ecdfead4b8d27592f8b4686eb9b6cd4f5dec9f06f76377b2d0a1a1bb60205260409020547f689f0a569be0c9b6cd2c11c81cb0add722272abdae6b649fdb1e05f1d9bb8a2f9060ff1680156153cc575b156110ed5750610736906153ea565b50805f525f6020526153e533600160405f2001546139cd565b6153bd565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169190602061542230615824565b01511690823b156106a157604051633b9e9f0160e21b81526001600160a01b0390921660048301526024820152905f90829081838160448101614479565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811690602061549730615824565b015116813b156106a1575f9160248392604051948593849263ee762f2b60e01b845260048401525af18015610a385761448a5750565b604051630b8e411760e21b81526001600160a01b03906020816004817f000000000000000000000000000000000000000000000000000000000000000086165afa908115610a38575f9161460457501690565b6127101061552a57565b60405163137b7b8960e31b8152600490fd5b61554581615520565b7fce7788c577af89829bf8edd40fe3bf5511a2a01bd1ceb67cf4b7a1eecd8cd0c7613b2a600354928060035560405191829133958360209093929193604081019481520152565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169060206155c330615824565b015116813b156106a1575f91602483926040519485938492633ffa490d60e01b845260048401525af18015610a385761448a5750565b615602816146d8565b6005546001600160a01b03808216929081169190828414615676576001600160a01b0319909116909117600555604080516001600160a01b03938416815292909116602083015233917f50c7826394d934be76de5ce7a66c3bc33795e8c3e613982f8cc792cbbcea3b129181908101613b2a565b60405163f012261f60e01b8152600490fd5b61569361371c613987565b15613b4f576001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116919060206156d030615824565b01511690823b156106a15760405163fdc10daf60e01b81526001600160a01b03928316600482015291166024820152905f90829081838160448101614479565b6001600160801b03166f7fffffffffffffffffffffffffffffff80821161465f5750600f0b90565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811690602061576f30615824565b015116813b156106a1575f91602483926040519485938492632c4422a760e11b845260048401525af18015610a385761448a5750565b906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116919060206157de30615824565b01511691803b156106a157604051636ce5768960e11b81526001600160a01b03938416600482015293909216602484015260448301525f90829081838160648101614479565b803b90602c19820191821161302957602d61585761584184610738565b9361584f6040519586610708565b808552610738565b6020840190601f19013682378351923c90565b905f602091828151910182855af115612fc2575f513d6158bb57506001600160a01b0381163b155b6158995750565b604051635274afe760e01b81526001600160a01b039091166004820152602490fd5b60011415615892565b6001600160a01b0381165f9081527fb888a84a969c69ee493013b8ca47e9ecebe24196f231bedf425581252c3c62e3602052604090205f80516020615e278339815191529060ff905b541661346e575f818152602081815260408083206001600160a01b03861684529091529020805460ff1916600117905533916001600160a01b0316907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d5f80a4600190565b6001600160a01b0381165f9081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5602052604090205460ff16613493576001600160a01b03165f8181527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb560205260408120805460ff191660011790553391907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d8180a4600190565b5f818152602081815260408083206001600160a01b0386168452909152902060ff9061590d565b6040516323b872dd60e01b60208201526001600160a01b0392831660248201529290911660448301526064820192909252610736916147448260848101614736565b5f818152602081815260408083206001600160a01b038616845290915290205460ff161561346e575f818152602081815260408083206001600160a01b03861684529091529020805460ff1916905533916001600160a01b0316907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b5f80a4600190565b906001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811691906020615b4230615824565b01511691803b156106a157604051635fc5d2cb60e11b81526001600160a01b03938416600482015293909216602484015260448301525f90829081838160648101614479565b8054821015613003575f5260205f2001905f90565b9061073691615bab816146d8565b615bb481615972565b615bc8575b50615bc381614f02565b614f35565b5f80526001602052615c03906001600160a01b03167fa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49615cbc565b505f615bb9565b5f80516020615e27833981519152805f525f602052600160405f20018181549155817fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff5f80a4565b7fe0b9915a7819e810f29b50730662441fec3443eb363b7e7c90c77fada416f276805f525f602052600160405f2001908154905f80516020615e278339815191528093557fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff5f80a4565b6001810190825f528160205260405f2054155f14615d24578054680100000000000000008110156106cc57615d11615cfb826001879401855584615b88565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b8054908115615d54575f1991820191615d448383615b88565b909182549160031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615dfe575f199284840190858211613029578054948501948511613029575f958583615db7976107ce9503615dbd575b505050615d2b565b55600190565b615de7615de191615dd1615df59487615b88565b90549060031b1c92839187615b88565b90614ac5565b85905f5260205260405f2090565b555f8080615daf565b505050505f9056fe6fe412e9d35aac9d96821beeae87f7dbc832b35e704e374aa2575d81ecc7787e59783a4ae82167eefad593739a5430c1d9e896a16c35f1e5285ddd0c0980885c933b7d5c112a4d05b489cea0b2ced98acb27d3d0fc9827c92cdacb2d6c5559c21b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb09017408b7b034fda7051615c19182918ecb91d753231cffd86f81a45d996d63e038a2646970667358221220f5e55cedfae21d6c89206c403271fceafc34074fe06feeb6d18ab9559a3b466064736f6c634300081900330000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba100000000000000000000000005f2927c5c2825bc0dcdc14d258a99a36116be8b00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea291980

Deployed Bytecode

0x60806040526004361015610022575b3615610018575f80fd5b610020614317565b005b5f3560e01c806301ffc9a714610631578063032d9b301461062c5780630a4a3ed2146106275780630b91990d146106225780630bff88811461061d5780630c7c92461461061857806310741552146106135780631171bda91461060e57806313e40efb14610609578063158ef93e14610604578063196b9e06146105ff578063248a9ca3146105fa57806324e7964a146105f55780632806f5e0146105f05780632959843a146105eb5780632ae87c3e146105e65780632c0772b4146105e15780632f2ff15d146105dc5780633046bf47146105d757806332cd7ebf146105d257806333885111146105cd57806335763d2b146105c857806336568abe146105c35780633a04d4f3146105be5780633a284985146105b95780633b37990e146105b45780633be75aa3146105af5780633e619a6c146105aa5780633efd8597146105a55780634156bbf4146105a057806343c710571461059b578063487e9d4e146105965780634bc368401461059157806350d99dd41461058c578063520107ad146105875780635251ebf914610582578063528c198a1461057d5780635680e145146105785780635bc7136314610573578063626c5ebc1461056e578063634bd91a14610569578063699d79bd146105645780636ab158361461055f5780636af6eeed1461055a5780636d4755661461055557806370b24b6914610550578063722adad21461054b57806373b257bc146105465780637b108d92146105415780638322fff21461053c578063845d0a3a14610537578063846f4d7f14610532578063853c637d1461052d5780638860be4114610528578063899896cb146105235780638c5d3e341461051e5780639010d07c1461051957806391d1485414610514578063956311051461050f57806399bce5e71461050a5780639c908158146105055780639c9708a914610500578063a217fddf146104fb578063a3246ad3146104f6578063a6218709146104f1578063b1003ec3146104ec578063b35869ed146104e7578063b60d4288146104e2578063b930908f146104dd578063bccad58f146104d8578063bddf300c146104d3578063be807b28146104ce578063c70cb120146104c9578063ca15c873146104c4578063ca8317af146104bf578063cb8fbf98146104ba578063cf309012146104b5578063d4c3eea0146104b0578063d547741f146104ab578063d877a3fc146104a6578063d9fb643a146104a1578063dadb623d1461049c578063dbba4b4814610497578063e00bfe5014610492578063e02023a11461048d578063e071c0ca14610488578063e55d864b14610483578063e5bca8af1461047e578063e6c772d414610479578063e9a9c85014610474578063eb990c591461046f578063ef914a091461046a578063f0e9fcd114610465578063f3fef3a314610460578063f634dcbd1461045b578063f6c76318146104565763ff108ccb0361000e57612ef2565b612ed6565b612e6c565b612d95565b612ce0565b612cc1565b612b20565b612ae6565b612a49565b612a2f565b6129f4565b6129cf565b612995565b612951565b61290d565b61280c565b6127c8565b612738565b6126fe565b6126a8565b6125eb565b6125c6565b61250c565b6124e2565b6124a8565b61246e565b6123d9565b612340565b612306565b61229b565b612261565b612227565b61218e565b61211b565b6120bd565b6120aa565b61208c565b612074565b611fdf565b611f8e565b611f49565b611f28565b611f0b565b611eec565b611dba565b611d9a565b611d5f565b611d31565b611cd5565b611cb6565b611c33565b611bbd565b611b83565b611b3b565b611b01565b611ac7565b611aa0565b611a66565b611a3f565b611a05565b61196e565b611863565b611728565b611691565b611677565b611640565b611606565b611522565b611504565b61146d565b61144a565b61141f565b6113e5565b6113be565b611373565b6112f5565b611281565b611129565b611072565b611033565b610ff9565b610fdf565b610fc5565b610fa6565b610f77565b610f4b565b610f07565b610ee5565b610dea565b610d34565b610c4c565b610b2b565b610b0e565b610ae5565b61084e565b610753565b346106a15760203660031901126106a15760043563ffffffff60e01b81168091036106a157602090635a05180f60e01b8114908115610676575b506040519015158152f35b637965db0b60e01b811491508115610690575b505f61066b565b6301ffc9a760e01b1490505f610689565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b6001600160401b0381116106cc57604052565b6106a5565b606081019081106001600160401b038211176106cc57604052565b61014081019081106001600160401b038211176106cc57604052565b90601f801991011681019081106001600160401b038211176106cc57604052565b60405190610736826106ec565b565b6001600160401b0381116106cc57601f01601f191660200190565b346106a15760403660031901126106a1576004356001600160401b0381116106a157366023820112156106a15780600401359061078f82610738565b61079c6040519182610708565b82815236602484840101116106a1575f6020846107ec9560246107db960183860137830101526107ce60243591612f6c565b905f5260205260405f2090565b546040519081529081906020820190565b0390f35b9181601f840112156106a1578235916001600160401b0383116106a1576020808501948460051b0101116106a157565b60206003198201126106a157600435906001600160401b0382116106a15761084a916004016107f0565b9091565b346106a15761085c36610820565b5f9060209061088b61087f8361087130615824565b01516001600160a01b031690565b6001600160a01b031690565b6040928351636b96736b60e01b815260048282600481875afa918215610a38575f92610aac575b505f5b88868210610a8a5750506108c761337b565b808811610a6a57506108d761438e565b6108e0876143a1565b6108e86144b1565b61091a610915886109106109046006546001600160801b031690565b6001600160801b031690565b61301c565b61453c565b855163266bcf0560e11b81528381600481885afa8015610a3857610945915f91610a3d575b5061303d565b5f5b8681106109a1578751878152602081018a90526107ec908a908a906001600160a01b038a16907f7ca089edbf098275c3dd9807ef0d5e5f7a4c4ed5a4ed4b865def78c7260f6b7f90604090a2519081529081906020820190565b6109ac81888c612fe1565b6001600160a01b03851691906109c28180613066565b916109cf89820182613066565b939095803b156106a1578d8894610a028b5f9884519b8c998a9889966304512a2360e31b885260608c01359588016130b8565b03930135905af1918215610a3857600192610a1f575b5001610947565b80610a2c610a32926106b9565b80610adb565b5f610a18565b612fc2565b610a5d9150853d8711610a63575b610a558183610708565b81019061302e565b5f61093f565b503d610a4b565b8651633cbb525b60e21b8152600481018990526024810191909152604490fd5b819888610a9d6001948a610aa595612fe1565b01359061301c565b97016108b5565b610acd919250833d8511610ad4575b610ac58183610708565b810190612faa565b905f6108b2565b503d610abb565b5f9103126106a157565b346106a1575f3660031901126106a15760205f80516020615e6783398151915254604051908152f35b346106a1575f3660031901126106a157602060405162278d008152f35b346106a1576020806003193601126106a157610b8881610b4c61087f6145b1565b610b5c61087f8361087130615824565b604051630131592760e61b81526001600160a01b03909116600482015292839190829081906024820190565b03915afa8015610a3857610be8915f91610c02575b50610bb4610baf6004356109106134c5565b614621565b610bbc613730565b918280821115610bf957610bcf91613113565b915b80821115610bf057610be291613113565b90614652565b604051908152f35b50505f90614652565b50505f91610bd1565b610c199150833d8511610a6357610a558183610708565b5f610b9d565b9181601f840112156106a1578235916001600160401b0383116106a157602083818601950101116106a157565b346106a15760203660031901126106a1576004356001600160401b0381116106a157610c7c903690600401610c1f565b335f9081527fb263cec6d0d87327feaa8f63140311e533b781670aff412d50297f0882a7530460205260409020547f32d0d6546e21c13ff633616141dc9daad87d248d1d37c56bf493d06d627ecb7b929060ff168015610d05575b15610ce6576100209250614664565b60405163e2517d3f60e01b815233600482015260248101849052604490fd5b50825f525f602052610d1e33600160405f2001546139cd565b610cd7565b6001600160a01b038116036106a157565b346106a15760603660031901126106a157600435610d5181610d23565b60243590610d5e82610d23565b335f9081527f8e22673d1bc81fb3bd6ed1d58f34ebd725bafdd9d714cf3052b13a12f254fcff60205260409020547fa38b301640bddfd3e6a9d2a11d13551d53ef81526347ff09d798738fcc5a49d4929060ff168015610dcc575b15610ce657610020925060443591613170565b50825f525f602052610de533600160405f2001546139cd565b610db9565b60603660031901126106a1576001600160401b036004358181116106a157610e16903690600401610c1f565b90916024359081116106a157610e309036906004016107f0565b9060443592610e3e84610d23565b335f9081527f894c422fd63ef7348a50b435343ac685160997c7193187427501d7dfbbf7b59760205260409020547fea19d3b23bd90fdd52445ad672f2b6fb1fef7230d49c6a827c1cd288d02994d5959060ff168015610ec7575b15610ea8576100209550614754565b60405163e2517d3f60e01b815233600482015260248101879052604490fd5b50855f525f602052610ee033600160405f2001546139cd565b610e99565b346106a1575f3660031901126106a157602060ff600254166040519015158152f35b346106a1575f3660031901126106a1576040517f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b03168152602090f35b346106a15760203660031901126106a1576004355f525f6020526020600160405f200154604051908152f35b346106a1575f3660031901126106a15760206001600160a01b0381610f9b30615824565b015116604051908152f35b346106a1575f3660031901126106a157602061ffff60a0610f9b613feb565b346106a1575f3660031901126106a1576020610be861337b565b346106a1575f3660031901126106a1576020610be86134c5565b346106a1575f3660031901126106a15760206040517fa90c7030a27f389f9fc8ed21a0556f40c88130cc14a80db936bed68261819b2c8152f35b346106a15760403660031901126106a15761002060243560043561105682610d23565b805f525f60205261106d600160405f200154614899565b61493a565b346106a15760203660031901126106a157335f9081527fe23a8bac6ee120cc0f31e97b679e63d8a06b6b0f7ec6a1eaa96912d2415af2e060205260409020547f3f82ecf462ddac43fc17ba11472c35f18b7760b4f5a5fc50b9625f9b5a22cf629060ff16801561110b575b156110ed57610020600435614970565b6044906040519063e2517d3f60e01b82523360048301526024820152fd5b50805f525f60205261112433600160405f2001546139cd565b6110dd565b346106a1576020806003193601126106a1576001600160a01b03905f8161119c7f00000000000000000000000005f2927c5c2825bc0dcdc14d258a99a36116be8b851660043561117b81303385615a43565b604051948580948193636f074d1f60e11b8352600483019190602083019252565b03925af18015610a38576111b7915f91611264575b50615019565b604051638fcb4e5b60e01b81527f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe0884166001600160a01b0316600482015260248101829052929091908190849060449082905f907f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba1165af1928315610a385761002093611246575b5050615363565b8161125c92903d10610a6357610a558183610708565b505f8061123f565b61127b9150833d8511610a6357610a558183610708565b5f6111b1565b346106a1575f3660031901126106a1576005546040516001600160a01b039091168152602090f35b9060206003198301126106a1576004356001600160401b03928382116106a157806023830112156106a15781600401359384116106a15760248460061b830101116106a1576024019190565b346106a157611303366112a9565b908115611361575f5b82811061131557005b60019061135a602080611329848888613563565b01355f611337858989613563565b359261134284610d23565b828252526113558560405f200154614899565b6149e6565b500161130c565b60405163b7852ebb60e01b8152600490fd5b346106a15760403660031901126106a15760243561139081610d23565b336001600160a01b038216036113ac57610020906004356149e6565b60405163334bd91960e11b8152600490fd5b346106a1575f3660031901126106a15760206040515f80516020615e278339815191528152f35b346106a1575f3660031901126106a15760206040517fa38b301640bddfd3e6a9d2a11d13551d53ef81526347ff09d798738fcc5a49d48152f35b346106a15760403660031901126106a1576020611440602435600435613573565b6040519015158152f35b346106a1575f3660031901126106a15760206001600160601b0381610f9b613feb565b60403660031901126106a15760043561148581610d23565b34611498575b61002090602435906135f0565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff1680156114e6575b156110ed5750610020906114df34614ddc565b905061148b565b50805f525f6020526114ff33600160405f2001546139cd565b6114cc565b346106a15760203660031901126106a1576020611440600435613708565b346106a15760403660031901126106a1576004356001600160401b0381116106a157611552903690600401610c1f565b6024359061155f82610d23565b335f9081527fb23b36340bdad7eb67d390e1f617f5a66ce55ba02098689f188a1df60c41421060205260409020547f17960a6b137243c888669d93712b617414dd6d1ab55d5eca488ccb1d0894cd5c939060ff1680156115e8575b156115c9576100209350614f8f565b60405163e2517d3f60e01b815233600482015260248101859052604490fd5b50835f525f60205261160133600160405f2001546139cd565b6115ba565b346106a1575f3660031901126106a15760206040517f59d005e32db662b94335d6bedfeb453fd2202b9f0cc7a6ed498d9098171744b08152f35b346106a1575f3660031901126106a15760406006546001600160401b038251916001600160801b038116835260801c166020820152f35b346106a1575f3660031901126106a1576020610be8613730565b60403660031901126106a1576004356116a981610d23565b346116bc575b61002090602435906137ae565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff16801561170a575b156110ed57506100209061170334614ddc565b90506116af565b50805f525f60205261172333600160405f2001546139cd565b6116f0565b346106a15761173636610820565b335f9081527f67416b44d24f10cfdaeae95ddaabfedeacd76567f63dd48b7ad4e79941f9b8586020908152604091829020547fb850402129bccae797798069a8cf3147a0cb7c3193f70558a75f7df0b8651c309060ff168015611845575b156110ed5750917f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b0316905f5b8181106117d157005b6117e161087f8661087130615824565b906117ed818489615071565b91843b156106a157855163015004fd60e61b8152925f91849182916118169190600484016150e8565b038183885af1918215610a3857600192611832575b50016117c8565b80610a2c61183f926106b9565b5f61182b565b50805f525f60205261185e33600160405f2001546139cd565b611794565b346106a15760203660031901126106a15760043561188081610d23565b6001600160a01b036118c8602061189d61087f8261087130615824565b604051600161da2560e61b031981526001600160a01b03909116600482015291829081906024820190565b0381857f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08165afa908115610a38575f9161193f575b5061192d578116301461191b57610020906119166151d0565b61521a565b604051633b07e30760e11b8152600490fd5b6040516309e1b82160e21b8152600490fd5b611961915060203d602011611967575b6119598183610708565b8101906137c0565b5f6118fd565b503d61194f565b60403660031901126106a15760043561198681610d23565b34611999575b6100209060243590614e48565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff1680156119e7575b156110ed5750610020906119e034614ddc565b905061198c565b50805f525f602052611a0033600160405f2001546139cd565b6119cd565b346106a1575f3660031901126106a15760206040517f3f82ecf462ddac43fc17ba11472c35f18b7760b4f5a5fc50b9625f9b5a22cf628152f35b346106a1575f3660031901126106a15760206040515f80516020615e478339815191528152f35b346106a1575f3660031901126106a15760206040517fb850402129bccae797798069a8cf3147a0cb7c3193f70558a75f7df0b8651c308152f35b346106a1575f3660031901126106a15760206040515f80516020615e878339815191528152f35b346106a1575f3660031901126106a15760206040517f32d0d6546e21c13ff633616141dc9daad87d248d1d37c56bf493d06d627ecb7b8152f35b346106a1575f3660031901126106a15760206040517fea19d3b23bd90fdd52445ad672f2b6fb1fef7230d49c6a827c1cd288d02994d58152f35b346106a1575f3660031901126106a1576060611b556137d4565b63ffffffff60408051926001600160701b0381511684526020810151600d0b60208501520151166040820152f35b346106a1575f3660031901126106a15760206040517f9586321ac05f110e4b4a0a42aba899709345af0ca78910e8832ddfd71fed2bf48152f35b346106a1575f806003193601126106a157611bd6614843565b6001600160a01b036020611be930615824565b015116803b156106a1575f80916004604051809481936379ba509760e01b83525af18015610a3857611c22575b50611c1f613ece565b80f35b611c2c91506106b9565b5f80611c16565b60203660031901126106a15734611c50575b6100206004356138cc565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff168015611c98575b156110ed5750611c9334614ddc565b611c45565b50805f525f602052611cb133600160405f2001546139cd565b611c84565b346106a1575f3660031901126106a157602061ffff60c0610f9b613feb565b346106a1575f3660031901126106a157611ced613987565b604051809160208083016020845282518091526020604085019301915f5b828110611d1a57505050500390f35b835185528695509381019392810192600101611d0b565b346106a1575f3660031901126106a157602060405173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee8152f35b60403660031901126106a157611d73613ece565b611d8160243560043561526e565b15611d8857005b60405163ce2a477560e01b8152600490fd5b346106a1575f3660031901126106a157602061ffff610100610f9b613feb565b346106a1576020806003193601126106a157604051636d78045960e01b8152336004808301919091526001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe088116602484015290356044830181905292919082908290815f81606481015b03927f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba1165af18015610a3857611ecf575b507f689f0a569be0c9b6cd2c11c81cb0add722272abdae6b649fdb1e05f1d9bb8a2f5f8181528083526040808220338352602052902090919060ff905416908115611eb1575b50156110ed57610020826153ea565b5f915082825252611ec933600160405f2001546139cd565b5f611ea2565b611ee590823d8411610a6357610a558183610708565b505f611e5c565b346106a1575f3660031901126106a157602061ffff6080610f9b613feb565b346106a1575f3660031901126106a1576020600354604051908152f35b346106a15760403660031901126106a157602061144060243560043561526e565b346106a15760403660031901126106a1576004355f5260016020526020611f7560243560405f20615b88565b905460405160039290921b1c6001600160a01b03168152f35b346106a15760403660031901126106a157602060ff611fd3602435611fb281610d23565b6004355f525f845260405f209060018060a01b03165f5260205260405f2090565b54166040519015158152f35b346106a1575f3660031901126106a157335f9081527f3f3dda3fcd93f9e4551f2c87aa737426b704aa48dbb418e3deea799b35c4558960205260409020547f59d005e32db662b94335d6bedfeb453fd2202b9f0cc7a6ed498d9098171744b09060ff168015612056575b156110ed57610020615460565b50805f525f60205261206f33600160405f2001546139cd565b612049565b346106a1575f3660031901126106a1576100206139f4565b346106a15760203660031901126106a1576020611440600435613bcd565b5f3660031901126106a157610020613ece565b346106a1575f3660031901126106a15760206040515f8152f35b60209060206040818301928281528551809452019301915f5b8281106120fe575050505090565b83516001600160a01b0316855293810193928101926001016120f0565b346106a1576020806003193601126106a1576004355f5260018060205260405f20916040519182602085549182815201945f5260205f20925f905b828210612179576107ec8661216d818a0382610708565b604051918291826120d7565b84548752958601959383019390830190612156565b346106a1575f3660031901126106a1576121d260206001600160a01b03610b5c816121b76145b1565b1691836121c330615824565b0151166001600160a01b031690565b03915afa908115610a38576107ec916121f8915f91612208575b50610be2610baf6134c5565b6040519081529081906020820190565b612221915060203d602011610a6357610a558183610708565b5f6121ec565b346106a1575f3660031901126106a15760206040517f655996ffb2e08cf642b4ce5cc56668761aed116cdfcb21f71ff9fe7fbcbd450e8152f35b346106a1575f3660031901126106a15760206040517fe0b9915a7819e810f29b50730662441fec3443eb363b7e7c90c77fada416f2768152f35b5f3660031901126106a157335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff1680156122e8575b156110ed5761002034614ddc565b50805f525f60205261230133600160405f2001546139cd565b6122da565b346106a1575f3660031901126106a15760206040517f689f0a569be0c9b6cd2c11c81cb0add722272abdae6b649fdb1e05f1d9bb8a2f8152f35b346106a15760203660031901126106a157335f9081527fff5c6f7275dcc6faef7a8ce369f5d9cdfe4b73e6d3ca76b92d14e1011cfad56860205260409020547fe0b9915a7819e810f29b50730662441fec3443eb363b7e7c90c77fada416f2769060ff1680156123bb575b156110ed57610020600435613fba565b50805f525f6020526123d433600160405f2001546139cd565b6123ab565b346106a1575f3660031901126106a157335f9081527f081a651349dfbf423ffb5b045a53f71668561e554402e2db71c1db19bfb815a560205260409020547fa90c7030a27f389f9fc8ed21a0556f40c88130cc14a80db936bed68261819b2c9060ff168015612450575b156110ed5761002061558c565b50805f525f60205261246933600160405f2001546139cd565b612443565b346106a1575f3660031901126106a15760206040517f17960a6b137243c888669d93712b617414dd6d1ab55d5eca488ccb1d0894cd5c8152f35b346106a1575f3660031901126106a15760206040517fea6487df651bb740150364c496e1c7403dd62063c96e44906cc98c6a919a9d888152f35b346106a15760203660031901126106a1576004355f526001602052602060405f2054604051908152f35b346106a1575f3660031901126106a157610140612527613feb565b60405181516001600160a01b03168152906125c4906020818101516001600160601b0316908401526040818101516001600160601b03169084015260608181015115159084015260808181015161ffff169084015260a08181015161ffff169084015260c08181015161ffff169084015260e08181015161ffff16908401526101008181015161ffff169084015261012090810151151590830152565bf35b346106a1575f3660031901126106a15760206040516a084595161401484a0000008152f35b346106a1575f3660031901126106a15761264160206001600160a01b03808261261330615824565b015160405163cbf9fe5f60e01b815291166001600160a01b0316600482015292839190829081906024820190565b03917f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08165afa8015610a38576107ec915f91612689575b506040519081529081906020820190565b6126a2915060203d602011610a6357610a558183610708565b5f612678565b346106a1575f3660031901126106a15761264160206001600160a01b0380826126d030615824565b01516040516330b0680b60e01b815291166001600160a01b0316600482015292839190829081906024820190565b346106a15760403660031901126106a15761002060243560043561272182610d23565b805f525f602052611355600160405f200154614899565b346106a15760203660031901126106a15760043561275581610d23565b335f9081527fb888a84a969c69ee493013b8ca47e9ecebe24196f231bedf425581252c3c62e360205260409020545f80516020615e278339815191529060ff1680156127aa575b156110ed57610020826155f9565b50805f525f6020526127c333600160405f2001546139cd565b61279c565b346106a1575f3660031901126106a1576040517f00000000000000000000000005f2927c5c2825bc0dcdc14d258a99a36116be8b6001600160a01b03168152602090f35b346106a1575f3660031901126106a15761285b606061283261087f602061087130615824565b604051635637c1d960e11b81526001600160a01b03909116600482015291829081906024820190565b03817f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b03165afa8015610a38576128d66121f8916107ec935f916128de575b506128d061090460406128c161090460208601516001600160801b031690565b9301516001600160801b031690565b9061301c565b6128d0614181565b612900915060603d606011612906575b6128f88183610708565b8101906140dd565b5f6128a1565b503d6128ee565b346106a1575f3660031901126106a1576040517f000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea2919806001600160a01b03168152602090f35b346106a1575f3660031901126106a1576040517f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba16001600160a01b03168152602090f35b346106a1575f3660031901126106a15760206040517f355caf1c2580ed8185acb5ea3573b71f85186b41bdf69e3eb8f1fcd122a562df8152f35b346106a15760203660031901126106a1576100206004356129ef81610d23565b615688565b346106a1575f3660031901126106a1576060600454604051906001600160701b03811682528060701c600d0b602083015260e01c6040820152f35b346106a1575f3660031901126106a1576020610be8614181565b346106a1575f3660031901126106a157612a616139f4565b335f9081527f6691f8833b7b4761748530debf638b71868148ba67d9725d03a36fa8c55c585960205260409020547f9586321ac05f110e4b4a0a42aba899709345af0ca78910e8832ddfd71fed2bf49060ff168015612ac8575b156110ed57610020615738565b50805f525f602052612ae133600160405f2001546139cd565b612abb565b346106a1575f3660031901126106a15760206040517fe996ac9b332538bb1fa3cd6743aa47011623cdb94bd964a494ee9d371e4a27d38152f35b346106a15760803660031901126106a157600435612b3d81610d23565b60243590612b4a82610d23565b60443591612b57816146d8565b6002549160ff8316612cb0576001600160a01b0393307f000000000000000000000000cb3bb848252f7ca05ed7753ead0eb2bdfd2ba878861614612c9e57612bb3612be5926001612bf39660ff19161760025560643590615b9d565b7f5daa87a0e9463431830481fd4b6e3403442dfb9a12b9c07597e9f61d50b633c85f80a1612be081615520565b61553c565b612bee816155f9565b6148db565b50612bfc615c0a565b612c04615c52565b60405163095ea7b360e01b81527f00000000000000000000000005f2927c5c2825bc0dcdc14d258a99a36116be8b82166001600160a01b031660048201525f19602482015290602090829060449082905f907f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba1165af18015610a3857612c8657005b6100209060203d602011611967576119598183610708565b60405163f9cc8ff960e01b8152600490fd5b60405162dc149f60e41b8152600490fd5b346106a1575f3660031901126106a157602061ffff60e0610f9b613feb565b346106a15760603660031901126106a157600435612cfd81610d23565b604435612d0981610d23565b335f9081527f8e22673d1bc81fb3bd6ed1d58f34ebd725bafdd9d714cf3052b13a12f254fcff60205260409020547fa38b301640bddfd3e6a9d2a11d13551d53ef81526347ff09d798738fcc5a49d4929060ff168015612d77575b15610ce657610020925060243590614273565b50825f525f602052612d9033600160405f2001546139cd565b612d64565b346106a15760403660031901126106a157600435612db281610d23565b602435612dbd61337b565b808211612e4d5750335f9081527fd86621a8c2273efca305981f63eac7b1eb8bb1b258c36bf730770cdcb2e9c89a60205260409020547f355caf1c2580ed8185acb5ea3573b71f85186b41bdf69e3eb8f1fcd122a562df929060ff168015612e2f575b15610ce65761002092506157a5565b50825f525f602052612e4833600160405f2001546139cd565b612e20565b604051633cbb525b60e21b815260048101929092526024820152604490fd5b346106a157612e7a366112a9565b90612e84826146fa565b5f5b828110612e8f57005b600190612ecf602080612ea3848888613563565b01355f612eb1858989613563565b3592612ebc84610d23565b8282525261106d8560405f200154614899565b5001612e86565b346106a1575f3660031901126106a1576020604051610e108152f35b346106a1576020806003193601126106a157612f0f600435615019565b604051636d78045960e01b81523360048201526001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08811660248301526044820183905291929182908290815f8160648101611e2b565b60208091604051928184925191829101835e81017f1b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb090081520301902090565b908160209103126106a15751612fbf81610d23565b90565b6040513d5f823e3d90fd5b634e487b7160e01b5f52603260045260245ffd5b91908110156130035760051b81013590607e19813603018212156106a1570190565b612fcd565b634e487b7160e01b5f52601160045260245ffd5b9190820180921161302957565b613008565b908160209103126106a1575190565b9060405191602083015260208252604082018281106001600160401b038211176106cc57604052565b903590601e19813603018212156106a157018035906001600160401b0382116106a1576020019181360383136106a157565b908060209392818452848401375f828201840152601f01601f1916010190565b96959490926130d661310e946060969460808b5260808b0191613098565b602080928a8303828c01528051918291828552018484015e5f828201840152601f01601f191601888103820160408a01520191613098565b930152565b9190820391821161302957565b6131306020610b4c61087f6145b1565b03915afa8015610a3857612fbf915f91613151575b50610bb4610baf6134c5565b61316a915060203d602011610a6357610a558183610708565b5f613145565b91909161317c816146d8565b613185836146d8565b61318e826146fa565b6001600160a01b039081169273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee840361324f575f808080868686165af13d1561324a573d6131cf81610738565b906131dd6040519283610708565b81525f60203d92013e5b1561321e57907faca8fb252cde442184e5f10e0f2e6e4029e8cd7717cae63559079610702436aa915b6040519384521691602090a3565b604051636f54afdd60e11b81526001600160a01b03919091166004820152602481019290925250604490fd5b6131e7565b908161327d847faca8fb252cde442184e5f10e0f2e6e4029e8cd7717cae63559079610702436aa9487614701565b613210565b519061073682610d23565b51906001600160601b03821682036106a157565b519081151582036106a157565b519061ffff821682036106a157565b90816101409103126106a1576132d1610729565b906132db81613282565b82526132e96020820161328d565b60208301526132fa6040820161328d565b604083015261330b606082016132a1565b606083015261331c608082016132ae565b608083015261332d60a082016132ae565b60a083015261333e60c082016132ae565b60c083015261334f60e082016132ae565b60e08301526101006133628183016132ae565b908301526133746101208092016132a1565b9082015290565b7f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b03166133b661087f602061087130615824565b60405163280be2f560e01b81526001600160a01b039190911660048201526101408082602481865afa8015610a38576133fc926060925f92613498575b50500151151590565b6134935760206134429161341661087f8361087130615824565b604051633a4952a360e01b81526001600160a01b03909116600482015292839190829081906024820190565b03915afa908115610a38575f91613474575b5061345d614181565b8082111561346e57612fbf91613113565b50505f90565b61348d915060203d602011610a6357610a558183610708565b5f613454565b505f90565b6134b79250803d106134be575b6134af8183610708565b8101906132bd565b5f806133f3565b503d6134a5565b6001600160a01b03602081816134da30615824565b015116602460405180948193632de0e07560e01b835260048301527f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08165afa908115610a38575f91613544575b50613530614181565b8082111561346e5781039081116130295790565b61355d915060203d602011610a6357610a558183610708565b5f613527565b91908110156130035760061b0190565b906001600160801b0360065416908082036135d25750506a084595161401484a00000081116135c0576135ad6135a7613987565b36614ae2565b15613493576135bb9061453c565b600190565b60405163f579fde760e01b8152600490fd5b6044925060405191637f8c068160e11b835260048301526024820152fd5b9061362d916135ff8230614e48565b6040516240cd2760e21b815260048101929092526020926001600160a01b0392908490829081906024820190565b0381867f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba1165afa908115610a38576136ab9385915f936136e9575b507f00000000000000000000000005f2927c5c2825bc0dcdc14d258a99a36116be8b169160405180958192630ea598cb60e41b8352600483019190602083019252565b03815f855af1928315610a3857610736945f946136ca575b5050614701565b6136e1929450803d10610a6357610a558183610708565b915f806136c3565b613701919350823d8411610a6357610a558183610708565b915f613668565b61371181614f02565b61372261371c613987565b36614c7c565b15613493576135bb90614f35565b6001600160a01b036020818161374530615824565b01511660246040518094819363329ce48960e21b835260048301527f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08165afa908115610a38575f91613795575090565b612fbf915060203d602011610a6357610a558183610708565b6137ba61073692615019565b90614e48565b908160209103126106a157612fbf906132a1565b60405f8180516137e3816106d1565b828152826020820152015260018060a01b03606081602061380330615824565b015116602484518094819363f9c821a760e01b835260048301527f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08165afa918215610a38575f9261385357505090565b9091506060823d6060116138c4575b8161386f60609383610708565b810103126106a157805191613883836106d1565b80516001600160701b03811681036106a157835260208101519081600d0b82036106a1578291602085015201519063ffffffff821682036106a15782015290565b3d9150613862565b6138d590615019565b335f9081527fe23a8bac6ee120cc0f31e97b679e63d8a06b6b0f7ec6a1eaa96912d2415af2e060205260409020547f3f82ecf462ddac43fc17ba11472c35f18b7760b4f5a5fc50b9625f9b5a22cf629060ff16801561393e575b156110ed575061073690614970565b50805f525f60205261395733600160405f2001546139cd565b61392f565b6001600160401b0381116106cc5760051b60200190565b80518210156130035760209160051b010190565b60405190613994826106d1565b60028252602082019160403684378092815115613003575f90528051600110156130035760405f80516020615e27833981519152910152565b5f908152602081815260408083206001600160a01b0390941683529290522060ff90541690565b6139fc614181565b8015613b4f576001600160801b03613a1c6006546001600160801b031690565b16613b42575b613a66613a2d6137d4565b6001600160701b03815116602082015160701b91604063ffffffff60e01b91015160e01b16916001600160701b0360701b161717600455565b7f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b0316613aa161087f602061087130615824565b6005546001600160a01b031690823b156106a157604051636ce5768960e11b81526001600160a01b0391821660048201529116602482015260448101839052905f908290606490829084905af18015610a3857613b2f575b5060405190815233907f66a284d7a46e07e3f2d18b3955d275877589083787a7f656543d82c49a5db9c09080602081015b0390a2565b80610a2c613b3c926106b9565b5f613af9565b613b4a6144c3565b613a22565b50565b6001600160401b038116036106a157565b908160209103126106a15751612fbf81613b52565b908160809103126106a1576040519060808201908282106001600160401b038311176106cc57606091604052613bad816132a1565b835260208101516020840152604081015160408401520151606082015290565b7f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b03166020613c0961087f8261087130615824565b60408051634a2264ef60e01b81526001600160a01b0392909216600480840191909152909290918181602481885afa908115610a38575f91613eb1575b5015613ea2576006546001600160401b038383613c6461087f6154cd565b875163c6290e3560e01b815292839182905afa8015610a385782915f91613e75575b5016908260801c161015613e65576001600160801b0316613e5657613ced6080613cb161087f6154cd565b613cc161087f8561087130615824565b86516329938c8360e21b81526001600160a01b0390911686820190815290938492918391829160200190565b03915afa8015610a3857613d09915f91613e27575b5051151590565b613e1857613d1685615520565b613d28613d2461371c613987565b1590565b613e0f57613d43613d37614181565b95612be0613a2d6137d4565b84613d53575b5050505050600190565b61087f613d639161087130615824565b6005549093906001600160a01b031690803b156106a1578351636ce5768960e11b81526001600160a01b03958616938101938452949091166020830152604082018590525f9184919082908490829060600103925af1918215610a38577f66a284d7a46e07e3f2d18b3955d275877589083787a7f656543d82c49a5db9c092613dfc575b50519182523391602090a25f80808080613d49565b80610a2c613e09926106b9565b5f613de7565b50505050505f90565b50905163a9d1c85160e01b8152fd5b613e49915060803d608011613e4f575b613e418183610708565b810190613b78565b5f613d02565b503d613e37565b509051635abae25f60e01b8152fd5b8351632a68326f60e11b81528390fd5b613e959150853d8711613e9b575b613e8d8183610708565b810190613b63565b5f613c86565b503d613e83565b5090516366ad59df60e11b8152fd5b613ec89150823d8411611967576119598183610708565b5f613c46565b34613f65575b7f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b0316613f078161521a565b613f1861087f602061087130615824565b90803b156106a15760405163135c2b5960e31b81526001600160a01b039290921660048301525f908290602490829084905af18015610a3857613f585750565b80610a2c610736926106b9565b613f7661087f602061087130615824565b803b156106a1575f600491604051928380926316c1a85160e31b825234905af18015610a3857613fa7575b50613ed4565b80610a2c613fb4926106b9565b5f613fa1565b6001600160801b0360065416908101809111613029576a084595161401484a00000081116135c0576107369061453c565b5f610120604051613ffb816106ec565b8281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e082015282610100820152015261407361404561087f602061087130615824565b60405163280be2f560e01b81526001600160a01b039091166004820152610140918290829081906024820190565b03817f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe086001600160a01b03165afa918215610a38575f926140b357505090565b612fbf9250803d106134be576134af8183610708565b51906001600160801b03821682036106a157565b908160609103126106a15761411b60408051926140f9846106d1565b614102816140c9565b8452614110602082016140c9565b6020850152016140c9565b604082015290565b600d91820b910b03906d7fffffffffffffffffffffffffff1982126d7fffffffffffffffffffffffffff83131761302957565b81810392915f13801582851316918412161761302957565b8181029291811591840414171561302957565b61424f604051614190816106d1565b61424a6142446004546001600160701b038116845260208401908060701c600d0b825260e01c604085015261422d6141c66137d4565b9161422d61423c60206142336141f56141ef6141ea6006546001600160801b031690565b615710565b600f0b90565b9961422d61421061421f6142108b516001600160701b031690565b6001600160701b0316600d0b90565b92516001600160701b031690565b90614123565b950151600d0b90565b9151600d0b90565b600d0b90565b614156565b5f811361425b57505f90565b61426b612fbf916003549061416e565b612710900490565b61427c816146d8565b614285836146d8565b6001600160a01b0390811692833b156106a157604051632142170760e11b81523060048201526001600160a01b038216602482015260448101849052915f8360648183895af1918215610a38577f6a30e6784464f0d1f4158aa4cb65ae9239b0fa87c7f2c083ee6dde44ba97b5e693602093614308575b506040519485521692a3565b614311906106b9565b5f6142fc565b5f80516020615e878339815191525c1561432d57565b335f9081525f80516020615e0783398151915260205260409020545f80516020615e478339815191529060ff168015614370575b156110ed575061073634614ddc565b50805f525f60205261438933600160405f2001546139cd565b614361565b60015f80516020615e878339815191525d565b335f9081527f445ddf9e128c79eca57440455d965dbaaca76bdc8b6662aee0bd18f61bc6486160205260409020547fea6487df651bb740150364c496e1c7403dd62063c96e44906cc98c6a919a9d889060ff168015614493575b156110ed57506001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe0881169190602061443930615824565b01511690823b156106a157604051636ce5768960e11b81526001600160a01b0390921660048301523060248301526044820152905f908290818381606481015b03925af18015610a385761448a5750565b610736906106b9565b50805f525f6020526144ac33600160405f2001546139cd565b6143fb565b5f5f80516020615e878339815191525d565b6006546001600160801b03811690811561452a576001600160c01b0319164260801b67ffffffffffffffff60801b1617600655604080515f815260208101929092527f6294ce24c5cb1a9fd25acf9dc37a0c6cdd144f222c80f43b5582bc86a882564091a1565b6040516318a04aa160e01b8152600490fd5b600654906001600160801b03908183169182821461452a576001600160c01b031993909316928116929092174260801b67ffffffffffffffff60801b16176006556040805192835260208301919091527f6294ce24c5cb1a9fd25acf9dc37a0c6cdd144f222c80f43b5582bc86a882564091a1565b604051631734070560e31b81526001600160a01b03906020816004817f000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea29198086165afa908115610a38575f9161460457501690565b61461d915060203d602011610ad457610ac58183610708565b1690565b61ffff608061462e613feb565b0151166127109081039181831161302957612fbf9261464c9161416e565b04615019565b908082101561465f575090565b905090565b6001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe0881169190602061469c30615824565b01511691803b156106a157614479935f80946040519687958694859363015a8f6160e61b85526004850152604060248501526044840191613098565b6001600160a01b0316156146e857565b60405163d92e233d60e01b8152600490fd5b1561136157565b60405163a9059cbb60e01b60208201526001600160a01b03909216602483015260448201929092526107369161474482606481015b03601f198101845283610708565b61586a565b359061073682613b52565b90936001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe088116949092602090848261479230615824565b01511697873b156106a1579795949392906147cf6020916040519a8b99632f00a7e160e01b8b5260048b0152608060248b015260848a0191613098565b8781036003190160448901528281520194915f5b82811061480e57505050505f949284928392166064830152039134905af18015610a3857613f585750565b9193949583975090806001926001600160401b0382963561482e81613b52565b168152019701910191889695949391926147e3565b335f9081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5602052604090205460ff161561487b57565b60405163e2517d3f60e01b81523360048201525f6024820152604490fd5b5f8181526020818152604080832033845290915290205460ff16156148bb5750565b60405163e2517d3f60e01b81523360048201526024810191909152604490fd5b6148e4816158c4565b90816148ee575090565b5f80516020615e278339815191525f526001602052614936906001600160a01b03167fa5a481183f76091139902c30e9c198fa5bc995beffa115a7cdc58a48752719ad615cbc565b5090565b6149448282615a1c565b918261494f57505090565b5f91825260016020526040909120614936916001600160a01b031690615cbc565b6001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe088116919060206149a830615824565b01511690823b156106a1576040516303da9b9d60e41b81526001600160a01b0390921660048301526024820152905f90829081838160448101614479565b6149f08282615a85565b91826149fb57505090565b5f91825260016020526040909120614936916001600160a01b031690615d68565b90614a268261395c565b614a336040519182610708565b8281528092614a44601f199161395c565b0190602036910137565b60206040518281935f833781017f1b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb090081520301902090565b5f1981146130295760010190565b91608093918352602083015260606040830152806060830152805f848401375f828201840152601f01601f1916010190565b91614ade9183549060031b91821b915f19901b19161790565b9055565b815115614c6a578151915f614af684614a1c565b5f614b0f5f80516020615e67833981519152544261301c565b925f5b878110614bb957509015614ba7578503614b5c5750505f5b838110614b3a5750505050600190565b805f614b55614b4b60019486613973565b516107ce87614a4e565b5501614b2a565b5f9391935b858110614b72575050505050505f90565b80614b88614b8260019385613973565b51151590565b614b93575b01614b61565b85614ba1614b4b8388613973565b55614b8d565b60405163dd16cb2360e01b8152600490fd5b614bc38187613973565b5192614bcf33856139cd565b15614c365750600190614be28293614a85565b93614bf6614bf08388613973565b60019052565b6040517f52a98ceea5bf73fc6482a17b325efa51b21372c60578c2fa4cac0c2fa780035a339180614c29368c4284614a93565b0390a35b01919091614b12565b9192614c4642916107ce8a614a4e565b541015614c56575b600190614c2d565b91614c62600191614a85565b929050614c4e565b60405163f814a95b60e01b8152600490fd5b815115614c6a578151915f614c9084614a1c565b5f614ca95f80516020615e67833981519152544261301c565b925f5b878110614d3157509015614ba7578503614cec5750505f5b838110614cd45750505050600190565b805f614ce5614b4b60019486613973565b5501614cc4565b5f9391935b858110614d02575050505050505f90565b80614d12614b8260019385613973565b614d1d575b01614cf1565b85614d2b614b4b8388613973565b55614d17565b614d3b8187613973565b5192614d4733856139cd565b15614da85750600190614d5a8293614a85565b93614d68614bf08388613973565b6040517f52a98ceea5bf73fc6482a17b325efa51b21372c60578c2fa4cac0c2fa780035a339180614d9b368c4284614a93565b0390a35b01919091614cac565b9192614db842916107ce8a614a4e565b541015614dc8575b600190614d9f565b91614dd4600191614a85565b929050614dc0565b6001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe088116906020614e1330615824565b01511690803b156106a15760245f926040519485938492630460488160e31b845260048401525af18015610a385761448a5750565b9190614e52613120565b808211614ee35750335f9081527fd8ab213733a751ec9ca51242fa42dd7f07dc61105cb44f3595d018b75a32f4f560205260409020547fe996ac9b332538bb1fa3cd6743aa47011623cdb94bd964a494ee9d371e4a27d3939060ff168015614ec5575b156115c957610736929350615b09565b50835f525f602052614ede33600160405f2001546139cd565b614eb5565b60405163087df69d60e41b815260048101929092526024820152604490fd5b610e108110908115614f28575b50614f1657565b60405163bbc3a3e360e01b8152600490fd5b62278d009150115f614f0f565b614f3e81614f02565b5f80516020615e6783398151915280549082905560408051918252602082019290925233917f437dc1a3e26abf0b8381463abca48d1e7d322e803c2b160d3ee75c04a492a182919081908101613b2a565b614fdb926020926001600160a01b03915f908386614fac30615824565b015116908460405198899788968795636c1d053960e11b87526004870152606060248701526064860191613098565b9116604483015203927f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08165af1908115610a38575f91613795575090565b604051631920845160e01b815260048101919091526020816024817f0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba16001600160a01b03165afa908115610a38575f91613795575090565b91908110156130035760051b8101359060be19813603018212156106a1570190565b81835290916001600160fb1b0383116106a15760209260051b809284830137010190565b9035601e19823603018112156106a15701602081359101916001600160401b0382116106a15781360383136106a157565b6001600160a01b03909116815260406020820152813536839003601e19018112156106a157820190602082359201916001600160401b0381116106a1578060051b360383136106a1576151c260a0615174615155612fbf9660e09560c06040890152610100880191615093565b61516260208901896150b7565b878303603f1901606089015290613098565b956040810135608086015261519d61518e60608301614749565b6001600160401b031686840152565b6151bc6151ac60808301614749565b6001600160401b031660c0870152565b01614749565b6001600160401b0316910152565b6151d8614843565b6001600160a01b0360206151eb30615824565b015116803b156106a1575f80916004604051809481936379ba509760e01b83525af18015610a385761448a5750565b615222614843565b6001600160a01b039081602061523730615824565b01511691823b156106a15760245f9283604051958694859363f2fde38b60e01b85521660048401525af18015610a385761448a5750565b335f9081527f33325f4ccadc9d27525cbee4f590af3a5c5c236abe27b43530b1a65b898523876020908152604090912054909291907f655996ffb2e08cf642b4ce5cc56668761aed116cdfcb21f71ff9fe7fbcbd450e9060ff168015615346575b156110ed5750829060646001600160a01b035f816152eb6145b1565b1691856152f730615824565b0151169160405196879586946321d5a4bf60e11b86526004860152602485015260448401525af1918215610a38575f9261533057505090565b612fbf9250803d10611967576119598183610708565b50805f525f845261535e33600160405f2001546139cd565b6152cf565b335f9081527fa2e2d001ecdfead4b8d27592f8b4686eb9b6cd4f5dec9f06f76377b2d0a1a1bb60205260409020547f689f0a569be0c9b6cd2c11c81cb0add722272abdae6b649fdb1e05f1d9bb8a2f9060ff1680156153cc575b156110ed5750610736906153ea565b50805f525f6020526153e533600160405f2001546139cd565b6153bd565b6001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe0881169190602061542230615824565b01511690823b156106a157604051633b9e9f0160e21b81526001600160a01b0390921660048301526024820152905f90829081838160448101614479565b6001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08811690602061549730615824565b015116813b156106a1575f9160248392604051948593849263ee762f2b60e01b845260048401525af18015610a385761448a5750565b604051630b8e411760e21b81526001600160a01b03906020816004817f000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea29198086165afa908115610a38575f9161460457501690565b6127101061552a57565b60405163137b7b8960e31b8152600490fd5b61554581615520565b7fce7788c577af89829bf8edd40fe3bf5511a2a01bd1ceb67cf4b7a1eecd8cd0c7613b2a600354928060035560405191829133958360209093929193604081019481520152565b6001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe0881169060206155c330615824565b015116813b156106a1575f91602483926040519485938492633ffa490d60e01b845260048401525af18015610a385761448a5750565b615602816146d8565b6005546001600160a01b03808216929081169190828414615676576001600160a01b0319909116909117600555604080516001600160a01b03938416815292909116602083015233917f50c7826394d934be76de5ce7a66c3bc33795e8c3e613982f8cc792cbbcea3b129181908101613b2a565b60405163f012261f60e01b8152600490fd5b61569361371c613987565b15613b4f576001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe088116919060206156d030615824565b01511690823b156106a15760405163fdc10daf60e01b81526001600160a01b03928316600482015291166024820152905f90829081838160448101614479565b6001600160801b03166f7fffffffffffffffffffffffffffffff80821161465f5750600f0b90565b6001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08811690602061576f30615824565b015116813b156106a1575f91602483926040519485938492632c4422a760e11b845260048401525af18015610a385761448a5750565b906001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe088116919060206157de30615824565b01511691803b156106a157604051636ce5768960e11b81526001600160a01b03938416600482015293909216602484015260448301525f90829081838160648101614479565b803b90602c19820191821161302957602d61585761584184610738565b9361584f6040519586610708565b808552610738565b6020840190601f19013682378351923c90565b905f602091828151910182855af115612fc2575f513d6158bb57506001600160a01b0381163b155b6158995750565b604051635274afe760e01b81526001600160a01b039091166004820152602490fd5b60011415615892565b6001600160a01b0381165f9081527fb888a84a969c69ee493013b8ca47e9ecebe24196f231bedf425581252c3c62e3602052604090205f80516020615e278339815191529060ff905b541661346e575f818152602081815260408083206001600160a01b03861684529091529020805460ff1916600117905533916001600160a01b0316907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d5f80a4600190565b6001600160a01b0381165f9081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5602052604090205460ff16613493576001600160a01b03165f8181527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb560205260408120805460ff191660011790553391907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d8180a4600190565b5f818152602081815260408083206001600160a01b0386168452909152902060ff9061590d565b6040516323b872dd60e01b60208201526001600160a01b0392831660248201529290911660448301526064820192909252610736916147448260848101614736565b5f818152602081815260408083206001600160a01b038616845290915290205460ff161561346e575f818152602081815260408083206001600160a01b03861684529091529020805460ff1916905533916001600160a01b0316907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b5f80a4600190565b906001600160a01b037f00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08811691906020615b4230615824565b01511691803b156106a157604051635fc5d2cb60e11b81526001600160a01b03938416600482015293909216602484015260448301525f90829081838160648101614479565b8054821015613003575f5260205f2001905f90565b9061073691615bab816146d8565b615bb481615972565b615bc8575b50615bc381614f02565b614f35565b5f80526001602052615c03906001600160a01b03167fa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49615cbc565b505f615bb9565b5f80516020615e27833981519152805f525f602052600160405f20018181549155817fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff5f80a4565b7fe0b9915a7819e810f29b50730662441fec3443eb363b7e7c90c77fada416f276805f525f602052600160405f2001908154905f80516020615e278339815191528093557fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff5f80a4565b6001810190825f528160205260405f2054155f14615d24578054680100000000000000008110156106cc57615d11615cfb826001879401855584615b88565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b8054908115615d54575f1991820191615d448383615b88565b909182549160031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615dfe575f199284840190858211613029578054948501948511613029575f958583615db7976107ce9503615dbd575b505050615d2b565b55600190565b615de7615de191615dd1615df59487615b88565b90549060031b1c92839187615b88565b90614ac5565b85905f5260205260405f2090565b555f8080615daf565b505050505f9056fe6fe412e9d35aac9d96821beeae87f7dbc832b35e704e374aa2575d81ecc7787e59783a4ae82167eefad593739a5430c1d9e896a16c35f1e5285ddd0c0980885c933b7d5c112a4d05b489cea0b2ced98acb27d3d0fc9827c92cdacb2d6c5559c21b8b5828bd311c11f60881dedc705c95b2fbc3408c25f5c1964af0a81ceb09017408b7b034fda7051615c19182918ecb91d753231cffd86f81a45d996d63e038a2646970667358221220f5e55cedfae21d6c89206c403271fceafc34074fe06feeb6d18ab9559a3b466064736f6c63430008190033

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

0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba100000000000000000000000005f2927c5c2825bc0dcdc14d258a99a36116be8b00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea291980

-----Decoded View---------------
Arg [0] : _stETH (address): 0x2C220A2a91602dd93bEAC7b3A1773cdADE369ba1
Arg [1] : _wstETH (address): 0x05F2927c5c2825BC0dCDc14d258a99A36116bE8B
Arg [2] : _vaultHub (address): 0x26b92f0fdfeBAf43E5Ea5b5974EeBee95F17Fe08
Arg [3] : _lidoLocator (address): 0xD7c1B80fA86965B48cCA3aDcCB08E1DAEa291980

-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 0000000000000000000000002c220a2a91602dd93beac7b3a1773cdade369ba1
Arg [1] : 00000000000000000000000005f2927c5c2825bc0dcdc14d258a99a36116be8b
Arg [2] : 00000000000000000000000026b92f0fdfebaf43e5ea5b5974eebee95f17fe08
Arg [3] : 000000000000000000000000d7c1b80fa86965b48cca3adccb08e1daea291980


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

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