ETH Price: $1,978.57 (-5.24%)

Contract

0x93DaaEa5D290Da9a7A6A65fe4F6C1D3ebb44E0e1
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

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

Similar Match Source Code
This contract matches the deployed Bytecode of the Source Code for Contract 0x57dAb476...429fd8418
The constructor portion of the code might be different and could alter the actual behaviour of the contract

Contract Name:
VaultViewer

Compiler Version
v0.8.25+commit.b61c2a91

Optimization Enabled:
Yes with 999999 runs

Other Settings:
cancun EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

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

// See contracts/COMPILERS.md
pragma solidity 0.8.25;
import { VaultHub } from "contracts/0.8.25/vaults/VaultHub.sol";
import { IStakingVault } from "contracts/0.8.25/vaults/interfaces/IStakingVault.sol";
import { ILido } from "contracts/common/interfaces/ILido.sol";
import { ILidoLocator } from "contracts/common/interfaces/ILidoLocator.sol";
import { LazyOracle } from "contracts/0.8.25/vaults/LazyOracle.sol";

contract VaultViewer {
    struct VaultData {
        address vaultAddress;
        VaultHub.VaultConnection connection;
        VaultHub.VaultRecord record;
        uint256 totalValue;
        uint256 liabilityStETH;
        uint256 nodeOperatorFeeRate;
        uint256 accruedFee;
        bool isReportFresh;
        LazyOracle.QuarantineInfo quarantineInfo;
    }

    struct VaultMembers {
        address vault;
        address owner;
        address nodeOperator;
        address[][] members;
    }

    /**
     * @notice Strict true value for checking role membership
     */
    bytes32 constant strictTrue = keccak256(hex"0000000000000000000000000000000000000000000000000000000000000001");

    /**
     * @notice Default admin role for checking roles
     */
    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    ILidoLocator public immutable LIDO_LOCATOR;
    VaultHub public immutable VAULT_HUB;
    LazyOracle public immutable LAZY_ORACLE;

    /// @notice Constructor
    /// @param _lidoLocator Address of the lido locator
    constructor(address _lidoLocator) {
        if (_lidoLocator == address(0)) revert ZeroArgument("_lidoLocator");
        LIDO_LOCATOR = ILidoLocator(_lidoLocator);

        VAULT_HUB = VaultHub(payable(LIDO_LOCATOR.vaultHub()));
        LAZY_ORACLE = LazyOracle(LIDO_LOCATOR.lazyOracle());
    }

    /// @notice Checks if a given address is the owner of a connection vault
    /// @param vault The vault to check
    /// @param _owner The address to check
    /// @return True if the address is the owner, false otherwise
    function isVaultOwner(IStakingVault vault, address _owner) public view returns (bool) {
        // For connected vaults the `vault.owner()` is VaultHub
        VaultHub.VaultConnection memory connection = VAULT_HUB.vaultConnection(address(vault));
        if (connection.owner == _owner) {
            return true;
        }

        return _checkHasRole(connection.owner, _owner, DEFAULT_ADMIN_ROLE);
    }

    /// @notice Checks if a given address has a given role on a connection vault owner contract
    /// @param vault The vault to check
    /// @param _member The address to check
    /// @param _role The role to check
    /// @return True if the address has the role, false otherwise
    /// @dev Return roles only for connection vault owner - dashboard contract
    function hasRole(IStakingVault vault, address _member, bytes32 _role) public view returns (bool) {
        // For connected vaults the `vault.owner()` is VaultHub
        VaultHub.VaultConnection memory connection = VAULT_HUB.vaultConnection(address(vault));
        if (connection.owner == address(0)) {
            return false;
        }

        return _checkHasRole(connection.owner, _member, _role);
    }

    /// @notice Returns vaults owned by `_owner` using batch pagination over the global vault list
    /// @param _owner Address of the owner
    /// @param _offset Zero-based offset in the vaults list [0, vaultsCount)
    /// @param _limit Maximum number of vaults to SCAN (must be > 0)
    /// @return vaults Array of owner-matching vaults found within the scanned window (length <= _limit)
    function vaultsByOwnerBatch(
        address _owner,
        uint256 _offset,
        uint256 _limit
    ) public view returns (IStakingVault[] memory vaults) {
        _requireNotZero(_limit, "_limit");

        VaultHub vaultHub = VAULT_HUB;
        uint256 vaultsCount = vaultHub.vaultsCount();

        if (_offset >= vaultsCount) {
            return new IStakingVault[](0);
        }

        uint256 scanSize = _offset + _limit > vaultsCount ? vaultsCount - _offset : _limit;

        vaults = new IStakingVault[](scanSize);
        IStakingVault vault;
        uint256 matchedCount = 0;

        uint256 end = _offset + scanSize;
        uint256 i = _offset;
        for (; i < end; ) {
            // vaultByIndex is 1-based, _offset is 0-based → add +1
            vault = IStakingVault(vaultHub.vaultByIndex(i + 1));
            if (isVaultOwner(vault, _owner)) {
                vaults[matchedCount] = vault;
            unchecked { ++matchedCount; }
            }
        unchecked { ++i; }
        }

        // shrink to actual length
        assembly { mstore(vaults, matchedCount) }
    }

    /// @notice Returns vaults where `_member` has `_role`, scanning a batch of the global vault list
    /// @param _role Role to check
    /// @param _member Address to check for the role
    /// @param _offset Zero-based offset in the vaults list [0, vaultsCount)
    /// @param _limit Maximum number of vaults to SCAN (must be > 0)
    /// @return vaults Array of vaults where `_member` has `_role` found within the scanned window (length ≤ _limit)
    function vaultsByRoleBatch(
        bytes32 _role,
        address _member,
        uint256 _offset,
        uint256 _limit
    ) public view returns (IStakingVault[] memory vaults) {
        _requireNotZero(_limit, "_limit");

        VaultHub vaultHub = VAULT_HUB;
        uint256 vaultsCount = vaultHub.vaultsCount();

        if (_offset >= vaultsCount) {
            return new IStakingVault[](0);
        }

        uint256 scanSize = _offset + _limit > vaultsCount ? vaultsCount - _offset : _limit;

        vaults = new IStakingVault[](scanSize);
        IStakingVault vault;
        uint256 matchedCount = 0;

        uint256 end = _offset + scanSize;
        uint256 i = _offset;
        for (; i < end; ) {
            // vaultByIndex is 1-based, _offset is 0-based → add +1
            vault = IStakingVault(vaultHub.vaultByIndex(i + 1));
            if (hasRole(vault, _member, _role)) {
                vaults[matchedCount] = vault;
            unchecked { ++matchedCount; }
            }
        unchecked { ++i; }
        }

        // shrink to actual number of matches
        assembly { mstore(vaults, matchedCount) }
    }

    /// @notice returns the number of vaults connected to the VaultHub
    /// @return the number of vaults connected to the VaultHub
    function vaultsCount() external view returns (uint256) {
        return VAULT_HUB.vaultsCount();
    }

    /// @notice Returns aggregated data for a single vault
    /// @param vault Address of the vault
    /// @return data Aggregated vault data
    function vaultData(address vault) public view returns (VaultData memory data) {
        ILido lido = VAULT_HUB.LIDO();
        VaultHub.VaultConnection memory connection = VAULT_HUB.vaultConnection(vault);
        VaultHub.VaultRecord memory record = VAULT_HUB.vaultRecord(vault);
        uint256 nodeOperatorFeeRate = _getNodeOperatorFeeRate(connection.owner);
        uint256 accruedFee = _getAccruedFee(connection.owner);
        LazyOracle.QuarantineInfo memory quarantineInfo = LAZY_ORACLE.vaultQuarantine(vault);

        data = VaultData({
            vaultAddress: vault,
            connection: connection,
            record: record,
            totalValue: VAULT_HUB.totalValue(vault),
            liabilityStETH: lido.getPooledEthBySharesRoundUp(record.liabilityShares),
            nodeOperatorFeeRate: nodeOperatorFeeRate,
            accruedFee: accruedFee,
            isReportFresh: VAULT_HUB.isReportFresh(vault),
            quarantineInfo: quarantineInfo
        });
    }

    /// @notice Returns aggregated data for a batch of vaults
    /// @param _offset Zero-based offset in the vaults list [0, vaultsCount)
    /// @param _limit Maximum number of vaults to return (must be > 0)
    /// @return vaultsData Array of aggregated vault data (length <= _limit)
    function vaultsDataBatch(uint256 _offset, uint256 _limit) external view returns (VaultData[] memory vaultsData) {
        _requireNotZero(_limit, '_limit');

        VaultHub vaultHub = VAULT_HUB;
        uint256 vaultsCount = vaultHub.vaultsCount();

        if (_offset >= vaultsCount) {
            return new VaultData[](0);
        }

        uint256 batchSize = _offset + _limit > vaultsCount ? vaultsCount - _offset : _limit;

        vaultsData = new VaultData[](batchSize);
        for (uint256 i = 0; i < batchSize; ) {
            // vaultByIndex is 1-based, _offset is 0-based → add +1
            address vaultAddress = vaultHub.vaultByIndex(_offset + i + 1);
            vaultsData[i] = vaultData(vaultAddress);
        unchecked { ++i; }
        }
    }

    /// @notice Returns vault addresses for a range of vaults
    /// @param _offset Zero-based offset in the vaults list [0, vaultsCount)
    /// @param _limit Maximum number of vaults to return (must be > 0)
    /// @return vaults Array of vault contracts (IStakingVault)
    function vaultAddressesBatch(uint256 _offset, uint256 _limit) public view returns (IStakingVault[] memory vaults) {
        _requireNotZero(_limit, '_limit');

        VaultHub vaultHub = VAULT_HUB;
        uint256 vaultsCount = vaultHub.vaultsCount();

        if (_offset >= vaultsCount) {
            return new IStakingVault[](0);
        }

        uint256 batchSize = _offset + _limit > vaultsCount ? vaultsCount - _offset : _limit;

        vaults = new IStakingVault[](batchSize);
        for (uint256 i = 0; i < batchSize; ) {
            // vaultByIndex is 1-based, _offset is 0-based → add +1
            address vaultAddress = vaultHub.vaultByIndex(_offset + i + 1);
            vaults[i] = IStakingVault(vaultAddress);
        unchecked { ++i; }
        }
    }

    /// @notice Returns the VaultMembers for each specified role on a single vault
    /// @param vaultAddress The address of the vault
    /// @param roles An array of role identifiers (bytes32) to query on the vault’s owner contract
    /// @return roleMembers VaultMembers containing vault address, owner, nodeOperator, and corresponding role members
    function roleMembers(
        address vaultAddress,
        bytes32[] calldata roles
    ) public view returns (VaultMembers memory roleMembers) {
        VaultHub.VaultConnection memory connection = VAULT_HUB.vaultConnection(vaultAddress);
        // For connected vaults the `vaultContract.owner()` is VaultHub
        // connection.owner is the owner of the vault - dashboard contract
        roleMembers.vault = vaultAddress;
        roleMembers.owner = connection.owner;
        roleMembers.nodeOperator = _getNodeOperatorAddress(vaultAddress);
        roleMembers.members = new address[][](roles.length);

        // owner may be an EOA wallet
        if (!_isContract(roleMembers.owner)) {
            return roleMembers;
        }

        for (uint256 i = 0; i < roles.length; i++) {
            roleMembers.members[i] = _getRoleMember(roleMembers.owner, roles[i]);
        }
        return roleMembers;
    }

    /// @notice Returns VaultMembers for each role on multiple vaults
    /// @param vaultAddresses Array of vault addresses to query
    /// @param roles Array of roles to check for each vault
    /// @return result Array of VaultMembers containing vault address, owner, nodeOperator and corresponding role members
    function roleMembersBatch(
        address[] calldata vaultAddresses,
        bytes32[] calldata roles
    ) external view returns (VaultMembers[] memory result) {
        result = new VaultMembers[](vaultAddresses.length);

        for (uint256 i = 0; i < vaultAddresses.length; i++) {
            result[i] = roleMembers(vaultAddresses[i], roles);
        }
    }

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

    /// @notice Safely attempt a staticcall to `roleMembers(bytes32)` on the owner address
    /// @dev common logic for roleMembers
    /// @dev More gas-efficient to do any `_isContract(owner)` check in the caller
    /// @param owner The address to call (may be a contract or an EOA)
    /// @param role The role identifier
    /// @return members Array of addresses if the call succeeds; empty array otherwise
    function _getRoleMember(address owner, bytes32 role) internal view returns (address[] memory members) {
        (bool success, bytes memory data) = owner.staticcall(abi.encodeWithSignature("getRoleMembers(bytes32)", role));

        if (success) {
            members = abi.decode(data, (address[]));
        }
    }

    /// @notice safely returns if role member has given role
    /// @param _contract that can have ACL or not
    /// @param _member addrress to check for role
    /// @param _role ACL role bytes
    /// @return bool status of check
    function _checkHasRole(address _contract, address _member, bytes32 _role) internal view returns (bool) {
        if (!_isContract(_contract)) return false;

        bytes memory payload = abi.encodeWithSignature("hasRole(bytes32,address)", _role, _member);
        (bool success, bytes memory result) = _contract.staticcall(payload);

        if (success && keccak256(result) == strictTrue) {
            return true;
        } else {
            return false;
        }
    }

    /// @notice Tries to fetch nodeOperator - feeRate() from the vault owner if it's a dashboard contract
    /// @dev Uses low-level staticcall to avoid reverting when the method is missing or the address is an EOA
    /// @param owner The address of the vault owner (can be either a contract or an EOA)
    /// @return fee The decoded fee value if present, otherwise 0
    function _getNodeOperatorFeeRate(address owner) internal view returns (uint256 fee) {
        if (_isContract(owner)) {
            // if dashboard contract and have feeRate method
            (bool success, bytes memory result) = owner.staticcall(abi.encodeWithSignature("feeRate()"));
            // Check ensures safe decoding — avoids abi.decode revert on short return data
            if (success && result.length >= 32) {
                fee = abi.decode(result, (uint256));
            }
        }
    }

    /// @notice Tries to fetch accruedFee() from the vault owner if it's a dashboard contract
    /// @dev Uses low-level staticcall to avoid reverting when the method is missing or the address is an EOA
    /// @param owner The address of the vault owner (can be either a contract or an EOA)
    /// @return accruedFee The decoded fee value if present, otherwise 0
    function _getAccruedFee(address owner) internal view returns (uint256 accruedFee) {
        if (_isContract(owner)) {
            // if dashboard contract and have accruedFee method
            (bool success, bytes memory result) = owner.staticcall(abi.encodeWithSignature("accruedFee()"));
            // Check ensures safe decoding — avoids abi.decode revert on short return data
            if (success && result.length >= 32) {
                accruedFee = abi.decode(result, (uint256));
            }
        }
    }

    /// @notice Tries to fetch nodeOperator() from the vault contract
    /// @dev Uses low-level staticcall to avoid reverting when the method is missing or vault is not a valid contract
    /// @param vault The address of the vault (must be a contract implementing nodeOperator())
    /// @return operator The decoded nodeOperator address if present, otherwise address(0)
    /// @custom:todo Think about the need for this method
    function _getNodeOperatorAddress(address vault) internal view returns (address operator) {
        if (_isContract(vault)) {
            (bool success, bytes memory result) = vault.staticcall(abi.encodeWithSignature("nodeOperator()"));
            // Check ensures safe decoding — avoids abi.decode revert on short return data
            if (success && result.length >= 32) {
                operator = abi.decode(result, (address));
            }
        }
    }

    /// @notice Checks if a given address is a contract
    /// @param account The address to check
    /// @return True if the address is a contract, false otherwise
    function _isContract(address account) internal view returns (bool) {
        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    /// @notice Reverts if a provided numeric argument is zero
    /// @param _value The argument value to validate
    /// @param _argName The name of the argument
    /// @custom:error ZeroArgument Thrown when `_value` equals zero
    function _requireNotZero(uint256 _value, string memory _argName) internal pure {
        if (_value == 0) revert ZeroArgument(_argName);
    }

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

    /// @notice Error for zero address arguments
    /// @param argName Name of the argument that is zero
    error ZeroArgument(string argName);
}

// 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.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) (utils/cryptography/Hashes.sol)

pragma solidity ^0.8.20;

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

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

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

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/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);
}

File 8 of 34 : SafeCast.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.

pragma solidity ^0.8.20;

/**
 * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeCast {
    /**
     * @dev Value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);

    /**
     * @dev An int value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedIntToUint(int256 value);

    /**
     * @dev Value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);

    /**
     * @dev An uint value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedUintToInt(uint256 value);

    /**
     * @dev Returns the downcasted uint248 from uint256, reverting on
     * overflow (when the input is greater than largest uint248).
     *
     * Counterpart to Solidity's `uint248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toUint248(uint256 value) internal pure returns (uint248) {
        if (value > type(uint248).max) {
            revert SafeCastOverflowedUintDowncast(248, value);
        }
        return uint248(value);
    }

    /**
     * @dev Returns the downcasted uint240 from uint256, reverting on
     * overflow (when the input is greater than largest uint240).
     *
     * Counterpart to Solidity's `uint240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toUint240(uint256 value) internal pure returns (uint240) {
        if (value > type(uint240).max) {
            revert SafeCastOverflowedUintDowncast(240, value);
        }
        return uint240(value);
    }

    /**
     * @dev Returns the downcasted uint232 from uint256, reverting on
     * overflow (when the input is greater than largest uint232).
     *
     * Counterpart to Solidity's `uint232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toUint232(uint256 value) internal pure returns (uint232) {
        if (value > type(uint232).max) {
            revert SafeCastOverflowedUintDowncast(232, value);
        }
        return uint232(value);
    }

    /**
     * @dev Returns the downcasted uint224 from uint256, reverting on
     * overflow (when the input is greater than largest uint224).
     *
     * Counterpart to Solidity's `uint224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        if (value > type(uint224).max) {
            revert SafeCastOverflowedUintDowncast(224, value);
        }
        return uint224(value);
    }

    /**
     * @dev Returns the downcasted uint216 from uint256, reverting on
     * overflow (when the input is greater than largest uint216).
     *
     * Counterpart to Solidity's `uint216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toUint216(uint256 value) internal pure returns (uint216) {
        if (value > type(uint216).max) {
            revert SafeCastOverflowedUintDowncast(216, value);
        }
        return uint216(value);
    }

    /**
     * @dev Returns the downcasted uint208 from uint256, reverting on
     * overflow (when the input is greater than largest uint208).
     *
     * Counterpart to Solidity's `uint208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toUint208(uint256 value) internal pure returns (uint208) {
        if (value > type(uint208).max) {
            revert SafeCastOverflowedUintDowncast(208, value);
        }
        return uint208(value);
    }

    /**
     * @dev Returns the downcasted uint200 from uint256, reverting on
     * overflow (when the input is greater than largest uint200).
     *
     * Counterpart to Solidity's `uint200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toUint200(uint256 value) internal pure returns (uint200) {
        if (value > type(uint200).max) {
            revert SafeCastOverflowedUintDowncast(200, value);
        }
        return uint200(value);
    }

    /**
     * @dev Returns the downcasted uint192 from uint256, reverting on
     * overflow (when the input is greater than largest uint192).
     *
     * Counterpart to Solidity's `uint192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toUint192(uint256 value) internal pure returns (uint192) {
        if (value > type(uint192).max) {
            revert SafeCastOverflowedUintDowncast(192, value);
        }
        return uint192(value);
    }

    /**
     * @dev Returns the downcasted uint184 from uint256, reverting on
     * overflow (when the input is greater than largest uint184).
     *
     * Counterpart to Solidity's `uint184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toUint184(uint256 value) internal pure returns (uint184) {
        if (value > type(uint184).max) {
            revert SafeCastOverflowedUintDowncast(184, value);
        }
        return uint184(value);
    }

    /**
     * @dev Returns the downcasted uint176 from uint256, reverting on
     * overflow (when the input is greater than largest uint176).
     *
     * Counterpart to Solidity's `uint176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toUint176(uint256 value) internal pure returns (uint176) {
        if (value > type(uint176).max) {
            revert SafeCastOverflowedUintDowncast(176, value);
        }
        return uint176(value);
    }

    /**
     * @dev Returns the downcasted uint168 from uint256, reverting on
     * overflow (when the input is greater than largest uint168).
     *
     * Counterpart to Solidity's `uint168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toUint168(uint256 value) internal pure returns (uint168) {
        if (value > type(uint168).max) {
            revert SafeCastOverflowedUintDowncast(168, value);
        }
        return uint168(value);
    }

    /**
     * @dev Returns the downcasted uint160 from uint256, reverting on
     * overflow (when the input is greater than largest uint160).
     *
     * Counterpart to Solidity's `uint160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toUint160(uint256 value) internal pure returns (uint160) {
        if (value > type(uint160).max) {
            revert SafeCastOverflowedUintDowncast(160, value);
        }
        return uint160(value);
    }

    /**
     * @dev Returns the downcasted uint152 from uint256, reverting on
     * overflow (when the input is greater than largest uint152).
     *
     * Counterpart to Solidity's `uint152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toUint152(uint256 value) internal pure returns (uint152) {
        if (value > type(uint152).max) {
            revert SafeCastOverflowedUintDowncast(152, value);
        }
        return uint152(value);
    }

    /**
     * @dev Returns the downcasted uint144 from uint256, reverting on
     * overflow (when the input is greater than largest uint144).
     *
     * Counterpart to Solidity's `uint144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toUint144(uint256 value) internal pure returns (uint144) {
        if (value > type(uint144).max) {
            revert SafeCastOverflowedUintDowncast(144, value);
        }
        return uint144(value);
    }

    /**
     * @dev Returns the downcasted uint136 from uint256, reverting on
     * overflow (when the input is greater than largest uint136).
     *
     * Counterpart to Solidity's `uint136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toUint136(uint256 value) internal pure returns (uint136) {
        if (value > type(uint136).max) {
            revert SafeCastOverflowedUintDowncast(136, value);
        }
        return uint136(value);
    }

    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        if (value > type(uint128).max) {
            revert SafeCastOverflowedUintDowncast(128, value);
        }
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint120 from uint256, reverting on
     * overflow (when the input is greater than largest uint120).
     *
     * Counterpart to Solidity's `uint120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toUint120(uint256 value) internal pure returns (uint120) {
        if (value > type(uint120).max) {
            revert SafeCastOverflowedUintDowncast(120, value);
        }
        return uint120(value);
    }

    /**
     * @dev Returns the downcasted uint112 from uint256, reverting on
     * overflow (when the input is greater than largest uint112).
     *
     * Counterpart to Solidity's `uint112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toUint112(uint256 value) internal pure returns (uint112) {
        if (value > type(uint112).max) {
            revert SafeCastOverflowedUintDowncast(112, value);
        }
        return uint112(value);
    }

    /**
     * @dev Returns the downcasted uint104 from uint256, reverting on
     * overflow (when the input is greater than largest uint104).
     *
     * Counterpart to Solidity's `uint104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toUint104(uint256 value) internal pure returns (uint104) {
        if (value > type(uint104).max) {
            revert SafeCastOverflowedUintDowncast(104, value);
        }
        return uint104(value);
    }

    /**
     * @dev Returns the downcasted uint96 from uint256, reverting on
     * overflow (when the input is greater than largest uint96).
     *
     * Counterpart to Solidity's `uint96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toUint96(uint256 value) internal pure returns (uint96) {
        if (value > type(uint96).max) {
            revert SafeCastOverflowedUintDowncast(96, value);
        }
        return uint96(value);
    }

    /**
     * @dev Returns the downcasted uint88 from uint256, reverting on
     * overflow (when the input is greater than largest uint88).
     *
     * Counterpart to Solidity's `uint88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toUint88(uint256 value) internal pure returns (uint88) {
        if (value > type(uint88).max) {
            revert SafeCastOverflowedUintDowncast(88, value);
        }
        return uint88(value);
    }

    /**
     * @dev Returns the downcasted uint80 from uint256, reverting on
     * overflow (when the input is greater than largest uint80).
     *
     * Counterpart to Solidity's `uint80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toUint80(uint256 value) internal pure returns (uint80) {
        if (value > type(uint80).max) {
            revert SafeCastOverflowedUintDowncast(80, value);
        }
        return uint80(value);
    }

    /**
     * @dev Returns the downcasted uint72 from uint256, reverting on
     * overflow (when the input is greater than largest uint72).
     *
     * Counterpart to Solidity's `uint72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toUint72(uint256 value) internal pure returns (uint72) {
        if (value > type(uint72).max) {
            revert SafeCastOverflowedUintDowncast(72, value);
        }
        return uint72(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        if (value > type(uint64).max) {
            revert SafeCastOverflowedUintDowncast(64, value);
        }
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint56 from uint256, reverting on
     * overflow (when the input is greater than largest uint56).
     *
     * Counterpart to Solidity's `uint56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toUint56(uint256 value) internal pure returns (uint56) {
        if (value > type(uint56).max) {
            revert SafeCastOverflowedUintDowncast(56, value);
        }
        return uint56(value);
    }

    /**
     * @dev Returns the downcasted uint48 from uint256, reverting on
     * overflow (when the input is greater than largest uint48).
     *
     * Counterpart to Solidity's `uint48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toUint48(uint256 value) internal pure returns (uint48) {
        if (value > type(uint48).max) {
            revert SafeCastOverflowedUintDowncast(48, value);
        }
        return uint48(value);
    }

    /**
     * @dev Returns the downcasted uint40 from uint256, reverting on
     * overflow (when the input is greater than largest uint40).
     *
     * Counterpart to Solidity's `uint40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toUint40(uint256 value) internal pure returns (uint40) {
        if (value > type(uint40).max) {
            revert SafeCastOverflowedUintDowncast(40, value);
        }
        return uint40(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        if (value > type(uint32).max) {
            revert SafeCastOverflowedUintDowncast(32, value);
        }
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint24 from uint256, reverting on
     * overflow (when the input is greater than largest uint24).
     *
     * Counterpart to Solidity's `uint24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toUint24(uint256 value) internal pure returns (uint24) {
        if (value > type(uint24).max) {
            revert SafeCastOverflowedUintDowncast(24, value);
        }
        return uint24(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        if (value > type(uint16).max) {
            revert SafeCastOverflowedUintDowncast(16, value);
        }
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        if (value > type(uint8).max) {
            revert SafeCastOverflowedUintDowncast(8, value);
        }
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        if (value < 0) {
            revert SafeCastOverflowedIntToUint(value);
        }
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int248 from int256, reverting on
     * overflow (when the input is less than smallest int248 or
     * greater than largest int248).
     *
     * Counterpart to Solidity's `int248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toInt248(int256 value) internal pure returns (int248 downcasted) {
        downcasted = int248(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(248, value);
        }
    }

    /**
     * @dev Returns the downcasted int240 from int256, reverting on
     * overflow (when the input is less than smallest int240 or
     * greater than largest int240).
     *
     * Counterpart to Solidity's `int240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toInt240(int256 value) internal pure returns (int240 downcasted) {
        downcasted = int240(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(240, value);
        }
    }

    /**
     * @dev Returns the downcasted int232 from int256, reverting on
     * overflow (when the input is less than smallest int232 or
     * greater than largest int232).
     *
     * Counterpart to Solidity's `int232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toInt232(int256 value) internal pure returns (int232 downcasted) {
        downcasted = int232(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(232, value);
        }
    }

    /**
     * @dev Returns the downcasted int224 from int256, reverting on
     * overflow (when the input is less than smallest int224 or
     * greater than largest int224).
     *
     * Counterpart to Solidity's `int224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toInt224(int256 value) internal pure returns (int224 downcasted) {
        downcasted = int224(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(224, value);
        }
    }

    /**
     * @dev Returns the downcasted int216 from int256, reverting on
     * overflow (when the input is less than smallest int216 or
     * greater than largest int216).
     *
     * Counterpart to Solidity's `int216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toInt216(int256 value) internal pure returns (int216 downcasted) {
        downcasted = int216(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(216, value);
        }
    }

    /**
     * @dev Returns the downcasted int208 from int256, reverting on
     * overflow (when the input is less than smallest int208 or
     * greater than largest int208).
     *
     * Counterpart to Solidity's `int208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toInt208(int256 value) internal pure returns (int208 downcasted) {
        downcasted = int208(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(208, value);
        }
    }

    /**
     * @dev Returns the downcasted int200 from int256, reverting on
     * overflow (when the input is less than smallest int200 or
     * greater than largest int200).
     *
     * Counterpart to Solidity's `int200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toInt200(int256 value) internal pure returns (int200 downcasted) {
        downcasted = int200(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(200, value);
        }
    }

    /**
     * @dev Returns the downcasted int192 from int256, reverting on
     * overflow (when the input is less than smallest int192 or
     * greater than largest int192).
     *
     * Counterpart to Solidity's `int192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toInt192(int256 value) internal pure returns (int192 downcasted) {
        downcasted = int192(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(192, value);
        }
    }

    /**
     * @dev Returns the downcasted int184 from int256, reverting on
     * overflow (when the input is less than smallest int184 or
     * greater than largest int184).
     *
     * Counterpart to Solidity's `int184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toInt184(int256 value) internal pure returns (int184 downcasted) {
        downcasted = int184(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(184, value);
        }
    }

    /**
     * @dev Returns the downcasted int176 from int256, reverting on
     * overflow (when the input is less than smallest int176 or
     * greater than largest int176).
     *
     * Counterpart to Solidity's `int176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toInt176(int256 value) internal pure returns (int176 downcasted) {
        downcasted = int176(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(176, value);
        }
    }

    /**
     * @dev Returns the downcasted int168 from int256, reverting on
     * overflow (when the input is less than smallest int168 or
     * greater than largest int168).
     *
     * Counterpart to Solidity's `int168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toInt168(int256 value) internal pure returns (int168 downcasted) {
        downcasted = int168(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(168, value);
        }
    }

    /**
     * @dev Returns the downcasted int160 from int256, reverting on
     * overflow (when the input is less than smallest int160 or
     * greater than largest int160).
     *
     * Counterpart to Solidity's `int160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toInt160(int256 value) internal pure returns (int160 downcasted) {
        downcasted = int160(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(160, value);
        }
    }

    /**
     * @dev Returns the downcasted int152 from int256, reverting on
     * overflow (when the input is less than smallest int152 or
     * greater than largest int152).
     *
     * Counterpart to Solidity's `int152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toInt152(int256 value) internal pure returns (int152 downcasted) {
        downcasted = int152(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(152, value);
        }
    }

    /**
     * @dev Returns the downcasted int144 from int256, reverting on
     * overflow (when the input is less than smallest int144 or
     * greater than largest int144).
     *
     * Counterpart to Solidity's `int144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toInt144(int256 value) internal pure returns (int144 downcasted) {
        downcasted = int144(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(144, value);
        }
    }

    /**
     * @dev Returns the downcasted int136 from int256, reverting on
     * overflow (when the input is less than smallest int136 or
     * greater than largest int136).
     *
     * Counterpart to Solidity's `int136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toInt136(int256 value) internal pure returns (int136 downcasted) {
        downcasted = int136(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(136, value);
        }
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toInt128(int256 value) internal pure returns (int128 downcasted) {
        downcasted = int128(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(128, value);
        }
    }

    /**
     * @dev Returns the downcasted int120 from int256, reverting on
     * overflow (when the input is less than smallest int120 or
     * greater than largest int120).
     *
     * Counterpart to Solidity's `int120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toInt120(int256 value) internal pure returns (int120 downcasted) {
        downcasted = int120(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(120, value);
        }
    }

    /**
     * @dev Returns the downcasted int112 from int256, reverting on
     * overflow (when the input is less than smallest int112 or
     * greater than largest int112).
     *
     * Counterpart to Solidity's `int112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toInt112(int256 value) internal pure returns (int112 downcasted) {
        downcasted = int112(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(112, value);
        }
    }

    /**
     * @dev Returns the downcasted int104 from int256, reverting on
     * overflow (when the input is less than smallest int104 or
     * greater than largest int104).
     *
     * Counterpart to Solidity's `int104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toInt104(int256 value) internal pure returns (int104 downcasted) {
        downcasted = int104(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(104, value);
        }
    }

    /**
     * @dev Returns the downcasted int96 from int256, reverting on
     * overflow (when the input is less than smallest int96 or
     * greater than largest int96).
     *
     * Counterpart to Solidity's `int96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toInt96(int256 value) internal pure returns (int96 downcasted) {
        downcasted = int96(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(96, value);
        }
    }

    /**
     * @dev Returns the downcasted int88 from int256, reverting on
     * overflow (when the input is less than smallest int88 or
     * greater than largest int88).
     *
     * Counterpart to Solidity's `int88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toInt88(int256 value) internal pure returns (int88 downcasted) {
        downcasted = int88(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(88, value);
        }
    }

    /**
     * @dev Returns the downcasted int80 from int256, reverting on
     * overflow (when the input is less than smallest int80 or
     * greater than largest int80).
     *
     * Counterpart to Solidity's `int80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toInt80(int256 value) internal pure returns (int80 downcasted) {
        downcasted = int80(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(80, value);
        }
    }

    /**
     * @dev Returns the downcasted int72 from int256, reverting on
     * overflow (when the input is less than smallest int72 or
     * greater than largest int72).
     *
     * Counterpart to Solidity's `int72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toInt72(int256 value) internal pure returns (int72 downcasted) {
        downcasted = int72(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(72, value);
        }
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toInt64(int256 value) internal pure returns (int64 downcasted) {
        downcasted = int64(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(64, value);
        }
    }

    /**
     * @dev Returns the downcasted int56 from int256, reverting on
     * overflow (when the input is less than smallest int56 or
     * greater than largest int56).
     *
     * Counterpart to Solidity's `int56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toInt56(int256 value) internal pure returns (int56 downcasted) {
        downcasted = int56(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(56, value);
        }
    }

    /**
     * @dev Returns the downcasted int48 from int256, reverting on
     * overflow (when the input is less than smallest int48 or
     * greater than largest int48).
     *
     * Counterpart to Solidity's `int48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toInt48(int256 value) internal pure returns (int48 downcasted) {
        downcasted = int48(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(48, value);
        }
    }

    /**
     * @dev Returns the downcasted int40 from int256, reverting on
     * overflow (when the input is less than smallest int40 or
     * greater than largest int40).
     *
     * Counterpart to Solidity's `int40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toInt40(int256 value) internal pure returns (int40 downcasted) {
        downcasted = int40(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(40, value);
        }
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toInt32(int256 value) internal pure returns (int32 downcasted) {
        downcasted = int32(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(32, value);
        }
    }

    /**
     * @dev Returns the downcasted int24 from int256, reverting on
     * overflow (when the input is less than smallest int24 or
     * greater than largest int24).
     *
     * Counterpart to Solidity's `int24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toInt24(int256 value) internal pure returns (int24 downcasted) {
        downcasted = int24(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(24, value);
        }
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toInt16(int256 value) internal pure returns (int16 downcasted) {
        downcasted = int16(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(16, value);
        }
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toInt8(int256 value) internal pure returns (int8 downcasted) {
        downcasted = int8(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(8, value);
        }
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        if (value > uint256(type(int256).max)) {
            revert SafeCastOverflowedUintToInt(value);
        }
        return int256(value);
    }

    /**
     * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
     */
    function toUint(bool b) internal pure returns (uint256 u) {
        assembly ("memory-safe") {
            u := iszero(iszero(b))
        }
    }
}

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

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

            return true;
        } else {
            return false;
        }
    }

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

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

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

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

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

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

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

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

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

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

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

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

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

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

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

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

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

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

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

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

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

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

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

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

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

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

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

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

        return result;
    }
}

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

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

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

/**
 * @title Confirmable2Addresses
 * @author Lido
 * @notice An extension of Confirmations that allows executing 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
     * @custom:storage-location erc7201:Lido.Utils.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` requires confirmation,
     *      the confirmation expiry will be tricky to change.
     *      This is why confirmExpiry is private, set to a default value of 1 days 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.Utils.Confirmations")) - 1)) & ~bytes32(uint256(0xff))
     */
    bytes32 private constant CONFIRMATIONS_STORAGE_LOCATION =
        0xe4ca011a1344eb515c922209bf867930fc05bf79f4b0e3bb4ec9938eedd47700;


    /**
     * @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 sender msg.sender of the call
     * @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 roleOrAddress The role or address 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 roleOrAddress, 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
    /// @dev 0x8d0e4ae4847b49935b55c99f9c3ce025c87e7c4604c35b7ae56929bd32fa5a78
    bytes32 public constant PAUSE_ROLE = keccak256("PausableUntilWithRoles.PauseRole");

    /// @notice role that allows to resume the contract
    /// @dev 0xa79a6aede309e0d48bf2ef0f71355c06ad317956d4c0da2deb0dc47cc34f826c
    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);
    }
}

File 13 of 34 : IPinnedBeaconProxy.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.8.0;

/**
 * @title IPinnedBeaconProxy
 * @author Lido
 * @notice Interface for the `PinnedBeaconProxy` contract
 */
interface IPinnedBeaconProxy {
    function isOssified() external view returns (bool);
}

// 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 represents validator stages in PDG flow
     * @param NONE - initial stage
     * @param PREDEPOSITED - PREDEPOSIT_AMOUNT is deposited to this validator by the vault
     * @param PROVEN - validator is proven to be valid and can be used to deposit to beacon chain
     * @param ACTIVATED - validator is proven and the ACTIVATION_DEPOSIT_AMOUNT is deposited to this validator
     * @param COMPENSATED - disproven validator has its PREDEPOSIT_AMOUNT ether compensated to staking vault owner and validator cannot be used in PDG anymore
     */
    enum ValidatorStage {
        NONE,
        PREDEPOSITED,
        PROVEN,
        ACTIVATED,
        COMPENSATED
    }
    /**
     * @notice represents status of the validator in PDG
     * @param stage represents validator stage in PDG flow
     * @param stakingVault pins validator to specific StakingVault
     * @param nodeOperator pins validator to specific NO
     */
    struct ValidatorStatus {
        ValidatorStage stage;
        IStakingVault stakingVault;
        address nodeOperator;
    }

    /**
     * @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 pendingActivations(IStakingVault _vault) external view returns (uint256);
    function validatorStatus(bytes calldata _pubkey) external view returns (ValidatorStatus memory);
    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 Gwei and minimum 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 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 _amountsInGwei, address _refundRecipient) external payable;
    function ejectValidators(bytes calldata _pubkeys, address _refundRecipient) external payable;
    function setDepositor(address _depositor) external;
    function ossify() external;
    function collectERC20(address _token, address _recipient, uint256 _amount) external;

    function availableBalance() external view returns (uint256);
    function stagedBalance() external view returns (uint256);
    function stage(uint256 _ether) external;
    function unstage(uint256 _ether) external;
    function depositFromStaged(Deposit calldata _deposit, uint256 _additionalAmount) external;
}

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

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

interface IVaultFactory {
    function deployedVaults(address _vault) external view returns (bool);
}

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

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

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

import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.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 {VaultHub} from "./VaultHub.sol";
import {OperatorGrid} from "./OperatorGrid.sol";

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

import {DoubleRefSlotCache, DOUBLE_CACHE_LENGTH} from "./lib/RefSlotCache.sol";

contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
    using DoubleRefSlotCache for DoubleRefSlotCache.Int104WithCache[DOUBLE_CACHE_LENGTH];

    enum QuarantineState {
        NO_QUARANTINE,      // No active quarantine
        QUARANTINE_ACTIVE,  // Quarantine active, not expired
        QUARANTINE_EXPIRED  // Quarantine period has passed
    }

    /// @custom:storage-location erc7201:Lido.Vaults.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 refSlot of the vaults data
        uint48 vaultsDataRefSlot;
        /// @notice total value increase quarantine period
        uint64 quarantinePeriod;
        /// @notice max reward ratio for refSlot-observed total value, basis points
        uint16 maxRewardRatioBP;
        /// @notice max Lido fee rate per second, in wei
        uint64 maxLidoFeeRatePerSecond;  // 64 bit is enough for up to 18 ETH/s
        /// @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;
        uint128 totalValueRemainder;
    }

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

    struct VaultInfo {
        address vault;
        uint256 aggregatedBalance; // includes availableBalance and stagedBalance
        int256 inOutDelta;
        bytes32 withdrawalCredentials;
        uint256 liabilityShares;
        uint256 maxLiabilityShares;
        uint256 mintableStETH;
        uint96 shareLimit;
        uint16 reserveRatioBP;
        uint16 forcedRebalanceThresholdBP;
        uint16 infraFeeBP;
        uint16 liquidityFeeBP;
        uint16 reservationFeeBP;
        bool pendingDisconnect;
    }

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

    /// @dev 0x7baf7f4a9784fa74c97162de631a3eb567edeb85878cb6965945310f2c512c63
    bytes32 public constant UPDATE_SANITY_PARAMS_ROLE = keccak256("vaults.LazyOracle.UpdateSanityParams");

    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;
    uint256 public constant MAX_QUARANTINE_PERIOD = 30 days;
    /// @dev max value for reward ratio - it's about 650%
    uint256 public constant MAX_REWARD_RATIO = type(uint16).max;
    uint256 public constant MAX_LIDO_FEE_RATE_PER_SECOND = 10 ether;

    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
    /// @param _maxLidoFeeRatePerSecond the max Lido fee rate per second
    function initialize(
        address _admin,
        uint256 _quarantinePeriod,
        uint256 _maxRewardRatioBP,
        uint256 _maxLidoFeeRatePerSecond
    ) external initializer {
        if (_admin == address(0)) revert AdminCannotBeZero();
        _grantRole(DEFAULT_ADMIN_ROLE, _admin);

        _updateSanityParams(_quarantinePeriod, _maxRewardRatioBP, _maxLidoFeeRatePerSecond);
    }

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

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

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

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

    /// @notice returns the max Lido fee rate per second, in ether
    function maxLidoFeeRatePerSecond() external view returns (uint256) {
        return _storage().maxLidoFeeRatePerSecond;
    }

    /// @notice returns the amount of total value that is pending in the quarantine for the given vault
    function quarantineValue(address _vault) external view returns (uint256) {
        Quarantine memory q = _storage().vaultQuarantines[_vault];
        uint256 pendingValue = q.pendingTotalValueIncrease;
        if (pendingValue > 0) {
            // saving one SLOAD if pendingValue is zero
            pendingValue += q.totalValueRemainder;
        }
        return pendingValue;
    }

    /// @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 memory q = _storage().vaultQuarantines[_vault];

        bool isQuarantineInactive = q.pendingTotalValueIncrease == 0;

        if (isQuarantineInactive) {
            return QuarantineInfo(false, 0, 0, 0, 0);
        }

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

    /// @notice returns the number of vaults connected to the VaultHub
    /// @return the number of vaults connected to the VaultHub
    function vaultsCount() external view returns (uint256) {
        return _vaultHub().vaultsCount();
    }

    /// @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);
            batch[i] = _vaultInfo(vaultAddress, vaultHub);
        }
        return batch;
    }

    /// @notice returns the vault data info
    /// @param _vault the address of the vault
    /// @return the vault data info
    function vaultInfo(address _vault) external view returns (VaultInfo memory) {
        return _vaultInfo(_vault, _vaultHub());
    }

    /**
     * @notice batch method to mass check the validator statuses in PredepositGuarantee contract
     * @param _pubkeys the array of validator's pubkeys to check
     * @return batch array of IPredepositGuarantee.ValidatorStatus structs
     */
    function batchValidatorStatuses(
        bytes[] calldata _pubkeys
    ) external view returns (IPredepositGuarantee.ValidatorStatus[] memory batch) {
        batch = new IPredepositGuarantee.ValidatorStatus[](_pubkeys.length);

        for (uint256 i = 0; i < _pubkeys.length; i++) {
            batch[i] = predepositGuarantee().validatorStatus(_pubkeys[i]);
        }
    }

    /// @notice update the sanity parameters
    /// @param _quarantinePeriod the quarantine period
    /// @param _maxRewardRatioBP the max EL CL rewards
    /// @param _maxLidoFeeRatePerSecond the max Lido fee rate per second
    function updateSanityParams(
        uint256 _quarantinePeriod,
        uint256 _maxRewardRatioBP,
        uint256 _maxLidoFeeRatePerSecond
    ) external onlyRole(UPDATE_SANITY_PARAMS_ROLE) {
        _updateSanityParams(_quarantinePeriod, _maxRewardRatioBP, _maxLidoFeeRatePerSecond);
    }

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

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

        emit VaultsReportDataUpdated(
            _vaultsDataTimestamp,
            _vaultsDataRefSlot,
            _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 value of the vault (on the vaultsDataRefSlot)
    /// @param _maxLiabilityShares the maxLiabilityShares value of the vault (on the vaultsDataRefSlot)
    /// @param _proof the proof of the reported data
    function updateVaultData(
        address _vault,
        uint256 _totalValue,
        uint256 _cumulativeLidoFees,
        uint256 _liabilityShares,
        uint256 _maxLiabilityShares,
        uint256 _slashingReserve,
        bytes32[] calldata _proof
    ) external {
        bytes32 leaf = keccak256(
            bytes.concat(
                keccak256(
                    abi.encode(
                        _vault,
                        _totalValue,
                        _cumulativeLidoFees,
                        _liabilityShares,
                        _maxLiabilityShares,
                        _slashingReserve
                    )
                )
            )
        );
        if (!MerkleProof.verify(_proof, _storage().vaultsDataTreeRoot, leaf)) revert InvalidProof();

        uint256 vaultsDataTimestamp = _storage().vaultsDataTimestamp;
        (uint256 checkedTotalValue, int256 inOutDelta) = _handleSanityChecks(
            _vault,
            _totalValue,
            _storage().vaultsDataRefSlot,
            vaultsDataTimestamp,
            _cumulativeLidoFees,
            _liabilityShares,
            _maxLiabilityShares
        );

        _vaultHub().applyVaultReport(
            _vault,
            vaultsDataTimestamp,
            checkedTotalValue,
            inOutDelta,
            _cumulativeLidoFees,
            _liabilityShares,
            _maxLiabilityShares,
            _slashingReserve
        );
    }

    /// @notice removes the quarantine for the vault
    /// @param _vault the address of the vault
    function removeVaultQuarantine(address _vault) external {
        if (msg.sender != LIDO_LOCATOR.vaultHub()) revert NotAuthorized();

        mapping(address => Quarantine) storage quarantines = _storage().vaultQuarantines;
        if (quarantines[_vault].pendingTotalValueIncrease > 0) {
            emit QuarantineRemoved(_vault);
        }
        delete quarantines[_vault];
    }

    function _vaultInfo(address _vault, VaultHub _vh) internal view returns (VaultInfo memory) {
        IStakingVault vault = IStakingVault(_vault);
        VaultHub.VaultConnection memory connection = _vh.vaultConnection(_vault);
        VaultHub.VaultRecord memory record = _vh.vaultRecord(_vault);
        return VaultInfo(
            _vault,
            vault.availableBalance() + vault.stagedBalance(),
            record.inOutDelta.currentValue(),
            vault.withdrawalCredentials(),
            record.liabilityShares,
            record.maxLiabilityShares,
            _mintableStETH(_vault, _vh),
            connection.shareLimit,
            connection.reserveRatioBP,
            connection.forcedRebalanceThresholdBP,
            connection.infraFeeBP,
            connection.liquidityFeeBP,
            connection.reservationFeeBP,
            _vh.isPendingDisconnect(_vault)
        );
    }

    /// @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
    /// @param _reportRefSlot the refSlot of the report
    /// @param _reportTimestamp the timestamp of the report
    /// @param _cumulativeLidoFees the cumulative Lido fees accrued on the vault (nominated in ether)
    /// @param _liabilityShares the liabilityShares value of the vault (on the _reportRefSlot)
    /// @param _maxLiabilityShares the maxLiabilityShares value of the vault (on the _reportRefSlot)
    /// @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,
        uint256 _reportRefSlot,
        uint256 _reportTimestamp,
        uint256 _cumulativeLidoFees,
        uint256 _liabilityShares,
        uint256 _maxLiabilityShares
    ) internal returns (uint256 totalValueWithoutQuarantine, int256 inOutDeltaOnRefSlot) {
        VaultHub vaultHub = _vaultHub();
        VaultHub.VaultRecord memory record = vaultHub.vaultRecord(_vault);
        uint48 previousReportTs = record.report.timestamp;

        // 0. Check if the report is already fresh enough
        if (uint48(_reportTimestamp) <= previousReportTs) {
            revert VaultReportIsFreshEnough();
        }

        // 1. Calculate inOutDelta in the refSlot
        int256 currentInOutDelta = record.inOutDelta.currentValue();
        inOutDeltaOnRefSlot = record.inOutDelta.getValueForRefSlot(uint48(_reportRefSlot));

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

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

        // 4. Sanity check for cumulative Lido fees
        uint256 previousCumulativeLidoFees = record.cumulativeLidoFees;
        if (previousCumulativeLidoFees > _cumulativeLidoFees) {
            revert CumulativeLidoFeesTooLow(_cumulativeLidoFees, previousCumulativeLidoFees);
        }

        uint256 maxLidoFees = (_reportTimestamp - previousReportTs) * uint256(_storage().maxLidoFeeRatePerSecond);
        if (_cumulativeLidoFees - previousCumulativeLidoFees > maxLidoFees) {
            revert CumulativeLidoFeesTooLarge(_cumulativeLidoFees - previousCumulativeLidoFees, maxLidoFees);
        }

        // 5. _maxLiabilityShares must be greater or equal than _liabilityShares
        // _maxLiabilityShares must be less or equal than the currently tracked on-chain record.maxLiabilityShares
        // (the latter can increase after the ref slot reported)
        if (_maxLiabilityShares < _liabilityShares || _maxLiabilityShares > record.maxLiabilityShares) {
            revert InvalidMaxLiabilityShares();
        }
    }

    /*
        Quarantine State Diagram

        States:
        • NO_QUARANTINE: No active quarantine, all value is immediately available
        • QUARANTINE_ACTIVE: Total value increase is quarantined, waiting for expiration
        • QUARANTINE_EXPIRED: Quarantine period passed, quarantined value can be released

        ┌─────────────────┐                              ┌──────────────────┐
        │  NO_QUARANTINE  │ reported > threshold         │QUARANTINE_ACTIVE │
        │                 ├─────────────────────────────►│                  │
        │  quarantined=0  │                              │  quarantined>0   │
        │  startTime=0    │◄─────────────────────────────┤  startTime>0     │
        │                 |                              │  time<expiration |
        └─────────────────┘ reported ≤ threshold         └───┬──────────────┘
                ▲         (early release)                    │       ▲
                │                                            │       │  increase > quarantined + rewards
                │                          time ≥            │       │  (release old, start new)
                │                          quarantine period │       │
                │                                            ▼       │
                │                                      ┌─────────────┴────────┐
                │ reported ≤ threshold OR              │  QUARANTINE_EXPIRED  │
                │ increase ≤ quarantined + rewards     │                      │
                │                                      │  quarantined>0       │
                │                                      │  startTime>0         │
                └──────────────────────────────────────┤  time>=expiration    │
                                                       └──────────────────────┘

        Legend:
        • threshold = onchainTotalValue * (100% + maxRewardRatio)
        • increase = reportedTotalValue - onchainTotalValue
        • quarantined - total value increase that is currently quarantined
        • rewards - expected EL/CL rewards based on maxRewardRatio
        • time = block.timestamp
        • expiration = quarantine.startTimestamp + quarantinePeriod
    */
    function _processTotalValue(
        address _vault,
        uint256 _reportedTotalValue,
        int256 _inOutDeltaOnRefSlot,
        VaultHub.VaultRecord memory record,
        uint256 _reportTimestamp
    ) internal returns (uint256 totalValueWithoutQuarantine) {
        if (_reportedTotalValue > MAX_SANE_TOTAL_VALUE) {
            revert TotalValueTooLarge();
        }

        // Calculate base values for quarantine logic -------------------------
        // --------------------------------------------------------------------

        // 0. Read storage values
        Storage storage $ = _storage();
        Quarantine storage quarantine = $.vaultQuarantines[_vault];
        uint256 quarantinedValue = quarantine.pendingTotalValueIncrease;
        // 1. Onchain total value on 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);
        // 2. Some percentage of funds that haven’t passed through the vault’s balance is allowed for handling EL and CL rewards.
        // NB: allowed amount of rewards is not scaled by time here, because:
        // - if we set a small per-day percentage, honest vaults receiving unexpectedly high MEV would get quarantined;
        // - if we set a large per-day percentage, a vault that hasn’t reported for a long time could bypass quarantine;
        // As a result, we would need to impose very tiny limits for non-quarantine percentage — which would complicate the logic
        // without bringing meaningful improvements.
        uint256 quarantineThreshold =
            onchainTotalValueOnRefSlot * (TOTAL_BASIS_POINTS + $.maxRewardRatioBP) / TOTAL_BASIS_POINTS;
        // 3. Determine current quarantine state
        QuarantineState currentState = _determineQuarantineState(quarantine, quarantinedValue, _reportTimestamp);


        // Execute logic based on current state and conditions ----------------
        // --------------------------------------------------------------------

        if (currentState == QuarantineState.NO_QUARANTINE) {
            if (_reportedTotalValue <= quarantineThreshold) {
                // Transition: NO_QUARANTINE → NO_QUARANTINE (no change needed)
                return _reportedTotalValue;
            } else {
                // Transition: NO_QUARANTINE → QUARANTINE_ACTIVE (start new quarantine)
                _startNewQuarantine(quarantine, _reportedTotalValue - onchainTotalValueOnRefSlot, _reportTimestamp);
                emit QuarantineActivated(_vault, _reportedTotalValue - onchainTotalValueOnRefSlot);
                return onchainTotalValueOnRefSlot;
            }
        } else if (currentState == QuarantineState.QUARANTINE_ACTIVE) {
            if (_reportedTotalValue <= quarantineThreshold) {
                // Transition: QUARANTINE_ACTIVE → NO_QUARANTINE (release quarantine early)
                delete $.vaultQuarantines[_vault];
                emit QuarantineReleased(_vault, 0);
                return _reportedTotalValue;
            } else {
                // Transition: QUARANTINE_ACTIVE → QUARANTINE_ACTIVE (maintain quarantine)
                uint256 reminder = _reportedTotalValue > (onchainTotalValueOnRefSlot + quarantinedValue)
                    ? _reportedTotalValue - (onchainTotalValueOnRefSlot + quarantinedValue)
                    : 0;
                quarantine.totalValueRemainder = uint128(reminder);
                emit QuarantineUpdated(reminder);
                return onchainTotalValueOnRefSlot;
            }
        } else { // QuarantineState.QUARANTINE_EXPIRED
            uint256 totalValueIncrease = _reportedTotalValue > onchainTotalValueOnRefSlot
                ? _reportedTotalValue - onchainTotalValueOnRefSlot
                : 0;
            uint256 quarantineThresholdWithRewards = quarantineThreshold + quarantinedValue
                * (TOTAL_BASIS_POINTS + $.maxRewardRatioBP) / TOTAL_BASIS_POINTS;

            if (_reportedTotalValue <= quarantineThresholdWithRewards) {
                // Transition: QUARANTINE_EXPIRED → NO_QUARANTINE (release and accept all)
                delete $.vaultQuarantines[_vault];
                emit QuarantineReleased(_vault, _reportedTotalValue <= quarantineThreshold ? 0 : totalValueIncrease);
                return _reportedTotalValue;
            } else {
                // Transition: QUARANTINE_EXPIRED → QUARANTINE_ACTIVE (release old, start new)
                emit QuarantineReleased(_vault, quarantinedValue);

                _startNewQuarantine(quarantine, totalValueIncrease - quarantinedValue, _reportTimestamp);
                emit QuarantineActivated(_vault, totalValueIncrease - quarantinedValue);

                return onchainTotalValueOnRefSlot + quarantinedValue;
            }
        }
    }

    function _determineQuarantineState(
        Quarantine storage _quarantine,
        uint256 _quarantinedValue,
        uint256 _vaultsDataTimestamp
    ) internal view returns (QuarantineState) {
        if (_quarantinedValue == 0) {
            return QuarantineState.NO_QUARANTINE;
        }

        bool isQuarantineExpired = (_vaultsDataTimestamp - _quarantine.startTimestamp) >= _storage().quarantinePeriod;
        return isQuarantineExpired ? QuarantineState.QUARANTINE_EXPIRED : QuarantineState.QUARANTINE_ACTIVE;
    }

    function _startNewQuarantine(
        Quarantine storage _quarantine,
        uint256 _amountToQuarantine,
        uint256 _currentTimestamp
    ) internal {
        _quarantine.pendingTotalValueIncrease = uint128(_amountToQuarantine);
        _quarantine.startTimestamp = uint64(_currentTimestamp);
        _quarantine.totalValueRemainder = 0;
    }

    function _updateSanityParams(
        uint256 _quarantinePeriod,
        uint256 _maxRewardRatioBP,
        uint256 _maxLidoFeeRatePerSecond
    ) internal {
        if (_quarantinePeriod > MAX_QUARANTINE_PERIOD) {
            revert QuarantinePeriodTooLarge(_quarantinePeriod, MAX_QUARANTINE_PERIOD);
        }
        if (_maxRewardRatioBP > MAX_REWARD_RATIO) {
            revert MaxRewardRatioTooLarge(_maxRewardRatioBP, MAX_REWARD_RATIO);
        }
        if (_maxLidoFeeRatePerSecond > MAX_LIDO_FEE_RATE_PER_SECOND) {
            revert MaxLidoFeeRatePerSecondTooLarge(_maxLidoFeeRatePerSecond, MAX_LIDO_FEE_RATE_PER_SECOND);
        }

        Storage storage $ = _storage();
        $.quarantinePeriod = uint64(_quarantinePeriod);
        $.maxRewardRatioBP = uint16(_maxRewardRatioBP);
        $.maxLidoFeeRatePerSecond = uint64(_maxLidoFeeRatePerSecond);

        emit SanityParamsUpdated(_quarantinePeriod, _maxRewardRatioBP, _maxLidoFeeRatePerSecond);
    }

    function _mintableStETH(address _vault, VaultHub _vh) internal view returns (uint256) {
        uint256 mintableShares = _vh.totalMintingCapacityShares(_vault, 0 /* zero eth delta */);
        return _getPooledEthBySharesRoundUp(mintableShares);
    }

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

    function predepositGuarantee() internal view returns (IPredepositGuarantee) {
        return IPredepositGuarantee(LIDO_LOCATOR.predepositGuarantee());
    }

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

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

    function _getPooledEthBySharesRoundUp(uint256 _shares) internal view returns (uint256) {
        return ILido(LIDO_LOCATOR.lido()).getPooledEthBySharesRoundUp(_shares);
    }

    event VaultsReportDataUpdated(uint256 indexed timestamp, uint256 indexed refSlot, bytes32 indexed root, string cid);
    event QuarantineActivated(address indexed vault, uint256 delta);
    event QuarantineReleased(address indexed vault, uint256 delta);
    event QuarantineRemoved(address indexed vault);
    event QuarantineUpdated(uint256 totalValueReminder);

    event SanityParamsUpdated(uint256 quarantinePeriod, uint256 maxRewardRatioBP, uint256 maxLidoFeeRatePerSecond);

    error AdminCannotBeZero();
    error NotAuthorized();
    error InvalidProof();
    error UnderflowInTotalValueCalculation();
    error TotalValueTooLarge();
    error VaultReportIsFreshEnough();
    error CumulativeLidoFeesTooLow(uint256 reportingFees, uint256 previousFees);
    error CumulativeLidoFeesTooLarge(uint256 feeIncrease, uint256 maxFeeIncrease);
    error QuarantinePeriodTooLarge(uint256 quarantinePeriod, uint256 maxQuarantinePeriod);
    error MaxRewardRatioTooLarge(uint256 rewardRatio, uint256 maxRewardRatio);
    error MaxLidoFeeRatePerSecondTooLarge(uint256 feeRate, uint256 maxFeeRate);
    error InvalidMaxLiabilityShares();
}

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

// See contracts/COMPILERS.md
// solhint-disable one-contract-per-file
pragma solidity 0.8.25;

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

uint256 constant DOUBLE_CACHE_LENGTH = 2;

// wrap external call in function to save bytecode
function _getCurrentRefSlot(IHashConsensus _consensus) view returns (uint256) {
    (uint256 refSlot, ) = _consensus.getCurrentFrame();
    return refSlot;
}

library RefSlotCache {
    struct Uint104WithCache {
        uint104 value;
        uint104 valueOnRefSlot;
        uint48 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(
        Uint104WithCache storage _storage,
        IHashConsensus _consensus,
        uint104 _increment
    ) internal view returns (Uint104WithCache memory) {
        uint256 refSlot = _getCurrentRefSlot(_consensus);

        Uint104WithCache memory newCache = _storage;

        if (newCache.refSlot != uint48(refSlot)) {
            newCache.valueOnRefSlot = _storage.value;
            newCache.refSlot = uint48(refSlot);
        }

        newCache.value += _increment;

        return newCache;
    }

    /// @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(
        Uint104WithCache storage _storage,
        IHashConsensus _consensus
    ) internal view returns (uint104) {
        uint256 refSlot = _getCurrentRefSlot(_consensus);
        if (uint48(refSlot) != _storage.refSlot) {
            return _storage.value;
        } else {
            return _storage.valueOnRefSlot;
        }
    }
}

library DoubleRefSlotCache {
    struct Int104WithCache {
        int104 value;
        int104 valueOnRefSlot;
        uint48 refSlot;
    }

    /// @notice Initializes the cache with the given value
    /// @param _value the value to initialize the cache with
    /// @return the initialized cache
    function initializeInt104DoubleCache(
        int104 _value
    ) internal pure returns (Int104WithCache[DOUBLE_CACHE_LENGTH] memory) {
        return [
            Int104WithCache({
                value: _value,
                valueOnRefSlot: 0,
                refSlot: 0 // first cache slot is active by default (as >= used in _activeCacheIndex)
            }),
            Int104WithCache(0, 0, 0)
        ];
    }

    /// @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(
        Int104WithCache[DOUBLE_CACHE_LENGTH] storage _storage,
        IHashConsensus _consensus,
        int104 _increment
    ) internal view returns (Int104WithCache[DOUBLE_CACHE_LENGTH] memory) {
        uint256 refSlot = _getCurrentRefSlot(_consensus);

        Int104WithCache[DOUBLE_CACHE_LENGTH] memory newCache = _storage;
        uint256 activeCacheIndex = _activeCacheIndex(newCache);

        if (newCache[activeCacheIndex].refSlot != uint48(refSlot)) {
            uint256 previousCacheIndex = activeCacheIndex;
            activeCacheIndex = 1 - activeCacheIndex;
            newCache[activeCacheIndex].value = newCache[previousCacheIndex].value;
            newCache[activeCacheIndex].valueOnRefSlot = newCache[previousCacheIndex].value;
            newCache[activeCacheIndex].refSlot = uint48(refSlot);
        }

        newCache[activeCacheIndex].value += _increment;

        return newCache;
    }

    /// @notice Returns the current value of the cache
    /// @param _cache the storage pointer for the array of cached values
    /// @return the current value of the cache
    function currentValue(Int104WithCache[DOUBLE_CACHE_LENGTH] memory _cache) internal pure returns (int104) {
        return _cache[_activeCacheIndex(_cache)].value;
    }

    /// @notice Returns the value for the refSlot
    /// @param _cache the storage pointer for the cached value
    /// @param _refSlot the refSlot to get the value for
    /// @return the cached value if it's changed since the last refSlot, the current value otherwise
    /// @dev reverts if the cache was overwritten after target refSlot
    function getValueForRefSlot(
        Int104WithCache[DOUBLE_CACHE_LENGTH] memory _cache,
        uint48 _refSlot
    ) internal pure returns (int104) {
        uint256 activeCacheIndex = _activeCacheIndex(_cache);

        // 1. refSlot is more than activeRefSlot
        if (_refSlot > _cache[activeCacheIndex].refSlot) {
            return _cache[activeCacheIndex].value;
        }

        uint256 previousCacheIndex = 1 - activeCacheIndex;
        // 2. refSlot is in (prevRefSlot, activeRefSlot]
        if (_refSlot > _cache[previousCacheIndex].refSlot) {
            return _cache[activeCacheIndex].valueOnRefSlot;
        }

        // 3. refSlot is equal to prevRefSlot
        if (_refSlot == _cache[previousCacheIndex].refSlot) {
            return _cache[previousCacheIndex].valueOnRefSlot;
        }

        // 4. refSlot is less than prevRefSlot
        revert InOutDeltaCacheIsOverwritten();
    }

    /// @dev There is a limitation on the refSlot value: it must be less than 2^48.
    /// If it exceeds this limit, the refSlot will be truncated to 48 bits.
    /// _activeCacheIndex may work incorrectly if one refSlot value is truncated and the other is not,
    /// because the non-truncated value will always be greater than the truncated one,
    /// causing incorrect activeIndex determination. However, 2^48 is a very large number,
    /// so if block time will be 1 second, it will take 8_925_512 years to reach this limit.
    function _activeCacheIndex(Int104WithCache[DOUBLE_CACHE_LENGTH] memory _cache) private pure returns (uint256) {
        return _cache[0].refSlot >= _cache[1].refSlot ? 0 : 1;
    }

    error InOutDeltaCacheIsOverwritten();
}

// 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 {SafeCast} from "@openzeppelin/contracts-v5.2/utils/math/SafeCast.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
         - Administrative operations (like bad debt socialization) can bypass tier/group limits

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

        5. Jail Mechanism:
         - A vault can be "jailed" as a penalty mechanism for misbehavior or violations
         - When a vault is in jail, it cannot mint new stETH shares (normal minting operations are blocked)
         - Vaults can be jailed/unjailed by addresses with appropriate governance roles
         - Administrative operations (like bad debt socialization) can bypass jail restrictions
     */

    /// @dev 0xa495a3428837724c7f7648cda02eb83c9c4c778c8688d6f254c7f3f80c154d55
    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;
    /// @dev max value for reserve ratio in basis points - 9999
    uint256 internal constant MAX_RESERVE_RATIO_BP = 99_99;

    // -----------------------------
    //            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
     * @custom:isVaultInJail if true, vault is in jail and can't mint stETH
     */
    struct ERC7201Storage {
        Tier[] tiers;
        mapping(address vault => uint256 tierId) vaultTier;
        mapping(address nodeOperator => Group) groups;
        address[] nodeOperators;
        mapping(address vault => bool isInJail) isVaultInJail;
    }

    /**
     * @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);

        _validateParams(
            DEFAULT_TIER_ID,
            _defaultTierParams.reserveRatioBP,
            _defaultTierParams.forcedRebalanceThresholdBP,
            _defaultTierParams.infraFeeBP,
            _defaultTierParams.liquidityFeeBP,
            _defaultTierParams.reservationFeeBP
        );

        ERC7201Storage storage $ = _getStorage();

        //create default tier with default share limit
        $.tiers.push(
            Tier({
                operator: DEFAULT_TIER_OPERATOR,
                shareLimit: SafeCast.toUint96(_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 Sets the confirmation expiry period
    /// @param _newConfirmExpiry The new confirmation expiry period in seconds
    function setConfirmExpiry(uint256 _newConfirmExpiry) external onlyRole(REGISTRY_ROLE) {
        _setConfirmExpiry(_newConfirmExpiry);
    }

    /// @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: SafeCast.toUint96(_shareLimit),
            liabilityShares: 0,
            tierIds: new uint256[](0)
        });
        $.nodeOperators.push(_nodeOperator);

        emit GroupAdded(_nodeOperator, _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 = SafeCast.toUint96(_shareLimit);

        emit GroupShareLimitUpdated(_nodeOperator, _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: SafeCast.toUint96(_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,
                tier_.shareLimit,
                tier_.reserveRatioBP,
                tier_.forcedRebalanceThresholdBP,
                tier_.infraFeeBP,
                tier_.liquidityFeeBP,
                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 = SafeCast.toUint96(_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
            );
        }
    }

    /*

    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.

    */
    /// @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 executed.
    /// @dev Node operator confirmation can be collected even if the vault is disconnected
    /// @dev Requires vault to be connected to VaultHub to finalize tier change from the vault owner side.
    /// @dev Both vault owner (via Dashboard) and node operator confirmations are required.
    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();

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

        address nodeOperator = IStakingVault(_vault).nodeOperator();
        // we allow node operator to pre-approve not connected vaults
        if (msg.sender != nodeOperator && !vaultHub.isVaultConnected(_vault)) revert VaultNotConnected();

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

        uint256 vaultLiabilityShares = vaultHub.liabilityShares(_vault);

        if (_requestedShareLimit < vaultLiabilityShares) {
            revert RequestedShareLimitTooLow(_requestedShareLimit, vaultLiabilityShares);
        }

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

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

        vaultHub.updateConnection(
            _vault,
            _requestedShareLimit,
            requestedTier.reserveRatioBP,
            requestedTier.forcedRebalanceThresholdBP,
            requestedTier.infraFeeBP,
            requestedTier.liquidityFeeBP,
            requestedTier.reservationFeeBP
        );

        emit TierChanged(_vault, _requestedTierId, _requestedShareLimit);

        return true;
    }

    /// @notice Syncs vault tier with current tier params
    /// @param _vault address of the vault
    /// @return bool Whether the sync was executed.
    /// @dev Requires vault to be connected to VaultHub.
    /// @dev Both vault owner (via Dashboard) and node operator confirmations are required.
    function syncTier(address _vault) external returns (bool) {
        (VaultHub vaultHub, VaultHub.VaultConnection memory vaultConnection,
        address vaultOwner, address nodeOperator, uint256 vaultTierId) = _getVaultContextForConnectedVault(_vault);

        Tier storage tier_ = _getStorage().tiers[vaultTierId];

        if (
            vaultConnection.reserveRatioBP == tier_.reserveRatioBP &&
            vaultConnection.forcedRebalanceThresholdBP == tier_.forcedRebalanceThresholdBP &&
            vaultConnection.infraFeeBP == tier_.infraFeeBP &&
            vaultConnection.liquidityFeeBP == tier_.liquidityFeeBP &&
            vaultConnection.reservationFeeBP == tier_.reservationFeeBP
        ) {
            revert VaultAlreadySyncedWithTier();
        }

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

        vaultHub.updateConnection(
            _vault,
            vaultConnection.shareLimit,
            tier_.reserveRatioBP,
            tier_.forcedRebalanceThresholdBP,
            tier_.infraFeeBP,
            tier_.liquidityFeeBP,
            tier_.reservationFeeBP
        );

        return true;
    }

    /// @notice Update vault share limit
    /// @param _vault address of the vault
    /// @param _requestedShareLimit share limit to set
    /// @return bool Whether the update was executed.
    /// @dev Requires vault to be connected to VaultHub.
    /// @dev Both vault owner (via Dashboard) and node operator confirmations are required.
    function updateVaultShareLimit(address _vault, uint256 _requestedShareLimit) external returns (bool) {
        (VaultHub vaultHub, VaultHub.VaultConnection memory vaultConnection,
        address vaultOwner, address nodeOperator, uint256 vaultTierId) = _getVaultContextForConnectedVault(_vault);

        uint256 tierShareLimit = _getStorage().tiers[vaultTierId].shareLimit;

        if (_requestedShareLimit > tierShareLimit) revert RequestedShareLimitTooHigh(_requestedShareLimit, tierShareLimit);
        if (_requestedShareLimit == vaultConnection.shareLimit) revert ShareLimitAlreadySet();

        uint256 vaultLiabilityShares = vaultHub.liabilityShares(_vault);

        if (_requestedShareLimit < vaultLiabilityShares) {
            revert RequestedShareLimitTooLow(_requestedShareLimit, vaultLiabilityShares);
        }

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

        vaultHub.updateConnection(
            _vault,
            _requestedShareLimit,
            vaultConnection.reserveRatioBP,
            vaultConnection.forcedRebalanceThresholdBP,
            vaultConnection.infraFeeBP,
            vaultConnection.liquidityFeeBP,
            vaultConnection.reservationFeeBP
        );

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

    /// @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
    function updateVaultFees(
        address _vault,
        uint256 _infraFeeBP,
        uint256 _liquidityFeeBP,
        uint256 _reservationFeeBP
    ) external onlyRole(REGISTRY_ROLE) {
        if (_vault == address(0)) revert ZeroArgument("_vault");

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

        VaultHub vaultHub = _vaultHub();
        if (!vaultHub.isVaultConnected(_vault)) revert VaultNotConnected();

        VaultHub.VaultConnection memory vaultConnection = vaultHub.vaultConnection(_vault);
        vaultHub.updateConnection(
            _vault,
            vaultConnection.shareLimit,
            vaultConnection.reserveRatioBP,
            vaultConnection.forcedRebalanceThresholdBP,
            _infraFeeBP,
            _liquidityFeeBP,
            _reservationFeeBP
        );
    }

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

    /// @notice Mint shares limit check
    /// @param _vault address of the vault
    /// @param _amount amount of shares will be minted
    /// @param _overrideLimits true if group and tier limits should not be checked
    function onMintedShares(
        address _vault,
        uint256 _amount,
        bool _overrideLimits
    ) external {
        if (msg.sender != LIDO_LOCATOR.vaultHub()) revert NotAuthorized("onMintedShares", msg.sender);

        ERC7201Storage storage $ = _getStorage();

        if (!_overrideLimits && $.isVaultInJail[_vault]) revert VaultInJail();

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

        uint96 tierLiabilityShares = tier_.liabilityShares;
        if (!_overrideLimits && 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 (!_overrideLimits && 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 Updates if the vault is in jail
    /// @param _vault vault address
    /// @param _isInJail true if the vault is in jail, false otherwise
    function setVaultJailStatus(address _vault, bool _isInJail) external onlyRole(REGISTRY_ROLE) {
        if (_vault == address(0)) revert ZeroArgument("_vault");

        ERC7201Storage storage $ = _getStorage();
        if ($.isVaultInJail[_vault] == _isInJail) revert VaultInJailAlreadySet();
        $.isVaultInJail[_vault] = _isInJail;

        emit VaultJailStatusUpdated(_vault, _isInJail);
    }

    /// @notice Get vault's tier 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 vaultTierInfo(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 true if the vault is in jail
    /// @param _vault address of the vault
    /// @return true if the vault is in jail
    function isVaultInJail(address _vault) external view returns (bool) {
        return _getStorage().isVaultInJail[_vault];
    }

    /// @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 > MAX_RESERVE_RATIO_BP)
            revert ReserveRatioTooHigh(_tierId, _reserveRatioBP, MAX_RESERVE_RATIO_BP);

        if (_forcedRebalanceThresholdBP == 0) revert ZeroArgument("_forcedRebalanceThresholdBP");
        if (_forcedRebalanceThresholdBP + 10 >= _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
        }
    }

    function _getVaultContextForConnectedVault(address _vault) internal view returns (
        VaultHub vaultHub,
        VaultHub.VaultConnection memory vaultConnection,
        address vaultOwner,
        address nodeOperator,
        uint256 vaultTierId
    ) {
        if (_vault == address(0)) revert ZeroArgument("_vault");

        vaultHub = _vaultHub();
        if (!vaultHub.isVaultConnected(_vault)) revert VaultNotConnected();

        vaultConnection = vaultHub.vaultConnection(_vault);
        vaultOwner = vaultConnection.owner;
        nodeOperator = IStakingVault(_vault).nodeOperator();

        vaultTierId = _getStorage().vaultTier[_vault];
    }

    function _requireLessOrEqToBP(uint256 _valueBP, uint256 _maxValueBP) internal pure {
        if (_valueBP > _maxValueBP) revert InvalidBasisPoints(_valueBP, _maxValueBP);
    }

    // -----------------------------
    //            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
    );
    event VaultJailStatusUpdated(address indexed vault, bool isInJail);

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

    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);
    error RequestedShareLimitTooLow(uint256 requestedSHareLimit, uint256 vaultShares);
    error VaultNotConnected();
    error VaultAlreadySyncedWithTier();
    error ShareLimitAlreadySet();
    error InvalidBasisPoints(uint256 valueBP, uint256 maxValueBP);
}

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

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

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

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 {IPinnedBeaconProxy} from "./interfaces/IPinnedBeaconProxy.sol";
import {IVaultFactory} from "./interfaces/IVaultFactory.sol";

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

import {PausableUntilWithRoles} from "../utils/PausableUntilWithRoles.sol";
import {RefSlotCache, DoubleRefSlotCache, DOUBLE_CACHE_LENGTH} 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.Uint104WithCache;
    using DoubleRefSlotCache for DoubleRefSlotCache.Int104WithCache[DOUBLE_CACHE_LENGTH];

    // -----------------------------
    //           STORAGE STRUCTS
    // -----------------------------
    /// @custom:storage-location erc7201:Lido.Vaults.VaultHub
    struct Storage {
        /// @notice accounting records for each vault
        mapping(address vault => VaultRecord) records;
        /// @notice connection parameters for each vault
        mapping(address vault => VaultConnection) connections;
        /// @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.Uint104WithCache 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 are not guaranteed to be stable.
        /// @dev vaultIndex is always greater than 0
        uint96 vaultIndex;
        /// @notice timestamp of the block when disconnection was initiated
        /// equal 0 if vault is disconnected and max(uint48) - for connected ,
        uint48 disconnectInitiatedTs;
        /// @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 intends to pause the beacon chain deposits
        bool beaconChainDepositsPauseIntent;
        /// 24 bits gap
    }

    struct VaultRecord {
        // ### 1st slot
        /// @notice latest report for the vault
        Report report;
        // ### 2nd slot
        /// @notice max number of shares that was minted by the vault in current Oracle period
        /// (used to calculate the locked value on the vault)
        uint96 maxLiabilityShares;
        /// @notice liability shares of the vault
        uint96 liabilityShares;
        // ### 3rd and 4th slots
        /// @notice inOutDelta of the vault (all deposits - all withdrawals)
        DoubleRefSlotCache.Int104WithCache[DOUBLE_CACHE_LENGTH] inOutDelta;
        // ### 5th slot
        /// @notice the minimal value that the reserve part of the locked can be
        uint128 minimalReserve;
        /// @notice part of liability shares reserved to be burnt as Lido core redemptions
        uint128 redemptionShares;
        // ### 6th slot
        /// @notice cumulative value for Lido fees that accrued on the vault
        uint128 cumulativeLidoFees;
        /// @notice cumulative value for Lido fees that were settled on the vault
        uint128 settledLidoFees;
    }

    struct Report {
        /// @notice total value of the vault
        uint104 totalValue;
        /// @notice inOutDelta of the report
        int104 inOutDelta;
        /// @notice timestamp (in seconds)
        uint48 timestamp;
    }

    // -----------------------------
    //           CONSTANTS
    // -----------------------------
    // some constants are immutables to save bytecode

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

    /// @notice role that allows to disconnect vaults from the hub
    /// @dev 0x479bc4a51d27fbdc8e51b5b1ebd3dcd58bd229090980bff226f8930587e69ce3
    bytes32 public immutable VAULT_MASTER_ROLE = keccak256("vaults.VaultHub.VaultMasterRole");
    /// @notice role that allows to accrue Lido Core redemptions on the vault
    /// @dev 0x44f007e8cc2a08047a03d8d9c295057454942eb49ee3ced9c87e9b9406f21174
    bytes32 public immutable REDEMPTION_MASTER_ROLE = keccak256("vaults.VaultHub.RedemptionMasterRole");
    /// @notice role that allows to trigger validator exits under extreme conditions
    /// @dev 0x2159c5943234d9f3a7225b9a743ea06e4a0d0ba5ed82889e867759a8a9eb7883
    bytes32 public immutable VALIDATOR_EXIT_ROLE = keccak256("vaults.VaultHub.ValidatorExitRole");
    /// @notice role that allows to bail out vaults with bad debt
    /// @dev 0xa85bab4b576ca359fa6ae02ab8744b5c85c7e7ed4d7e0bca7b5b64580ac5d17d
    bytes32 public immutable 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;
    /// @dev special value for `disconnectTimestamp` storage means the vault is not marked for disconnect
    uint48 internal constant DISCONNECT_NOT_INITIATED = type(uint48).max;
    /// @notice minimum amount of ether that is required for the beacon chain deposit
    /// @dev used as a threshold for the beacon chain deposits pause
    uint256 internal constant MIN_BEACON_DEPOSIT = 1 ether;
    /// @dev amount of ether required to activate a validator after PDG
    uint256 internal constant PDG_ACTIVATION_DEPOSIT = 31 ether;

    // -----------------------------
    //           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;
    /// @dev it's cached as immutable to save the gas, but it's add some rigidity to the contract structure
    /// and will require update of the VaultHub if HashConsensus changes
    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(address(_locator));
        _requireNotZero(address(_lido));
        _requireNotZero(address(_consensusContract));

        _requireNotZero(_maxRelativeShareLimitBP);
        if (_maxRelativeShareLimitBP > TOTAL_BASIS_POINTS) revert InvalidBasisPoints(_maxRelativeShareLimitBP, TOTAL_BASIS_POINTS);

        MAX_RELATIVE_SHARE_LIMIT_BP = _maxRelativeShareLimitBP;

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

        _disableInitializers();
        _pauseUntil(PAUSE_INFINITELY);
    }

    /// @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 are 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 true if the vault is connected to the hub or pending to be disconnected
    function isVaultConnected(address _vault) external view returns (bool) {
        return _vaultConnection(_vault).vaultIndex != 0;
    }

    /// @return true if vault is pending for disconnect, false if vault is connected or disconnected
    /// @dev disconnect can be performed by applying the report for the period when it was initiated
    function isPendingDisconnect(address _vault) external view returns (bool) {
        return _isPendingDisconnect(_vaultConnection(_vault));
    }

    /// @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 _locked(_vaultConnection(_vault), _vaultRecord(_vault));
    }

    /// @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), 0);
    }

    /// @notice Calculates the total number of shares that is possible to mint on the vault
    /// @param _vault The address of the vault
    /// @param _deltaValue The delta value to apply to the total value of the vault (may be negative)
    /// @return the number of shares that can be minted
    /// @dev returns 0 if the vault is not connected
    function totalMintingCapacityShares(address _vault, int256 _deltaValue) external view returns (uint256) {
        return _totalMintingCapacityShares(_vault, _deltaValue);
    }

    /// @return the amount of ether that can be instantly withdrawn from the staking vault
    /// @dev returns 0 if the vault is not connected or disconnect pending
    function withdrawableValue(address _vault) external view returns (uint256) {
        VaultConnection storage connection = _vaultConnection(_vault);
        if (_isPendingDisconnect(connection)) return 0;

        return _withdrawableValue(_vault, connection, _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 shares amount or UINT256_MAX if it's impossible to make the vault healthy using rebalance
    /// @dev returns 0 if the vault is not connected
    function healthShortfallShares(address _vault) external view returns (uint256) {
        return _healthShortfallShares(_vaultConnection(_vault), _vaultRecord(_vault));
    }

    /// @notice calculate ether amount required to cover obligations shortfall of the vault
    /// @param _vault vault address
    /// @return ether amount or UINT256_MAX if it's impossible to cover obligations shortfall
    /// @dev returns 0 if the vault is not connected
    function obligationsShortfallValue(address _vault) external view returns (uint256) {
        VaultConnection storage connection = _vaultConnection(_vault);
        if (connection.vaultIndex == 0) return 0;

        return _obligationsShortfallValue(_vault, connection, _vaultRecord(_vault));
    }

    /// @notice returns the vault's current obligations toward the protocol
    ///
    /// Obligations are amounts the vault must cover, in the following priority:
    /// 1) Maintain healthiness - burn/rebalance liability shares until the health ratio is restored
    /// 2) Cover redemptions - burn/rebalance part of the liability shares marked as `redemptionShares`
    /// 3) Pay Lido fees - settle accrued but unsettled fees
    ///
    /// Effects:
    /// - Withdrawals from the vault are limited by the amount required to cover the obligations
    /// - Beacon chain deposits are auto-paused while the vault is unhealthy, has redemptions to cover, or has
    ///   unsettled fees ≥ `MIN_BEACON_DEPOSIT` (1 ETH)
    ///
    /// How to settle:
    /// - Anyone can:
    ///   - Rebalance shares permissionlessly when there are funds via `forceRebalance` (restores health / covers redemptions)
    ///   - Settle fees permissionlessly when there are funds via `settleLidoFees`
    /// - The owner (or a trusted role) can trigger validator exits / withdrawals to source ETH when needed
    ///
    /// @param _vault vault address
    /// @return sharesToBurn amount of shares to burn / rebalance
    /// @return feesToSettle amount of Lido fees to settle
    /// @dev if the vault has bad debt (i.e. not fixable by rebalance), returns `type(uint256).max` for `sharesToBurn`
    /// @dev returns (0, 0) if the vault is not connected
    function obligations(address _vault) external view returns (uint256 sharesToBurn, uint256 feesToSettle) {
        VaultConnection storage connection = _vaultConnection(_vault);
        VaultRecord storage record = _vaultRecord(_vault);

        return (
            _obligationsShares(connection, record),
            _unsettledLidoFeesValue(record)
        );
    }

    /// @return the amount of Lido fees that currently can be settled. Even if vault's balance is sufficient to cover
    ///         the fees, some amount may be blocked for redemptions, or locked ether
    /// @dev returns 0 if the vault is not connected
    function settleableLidoFeesValue(address _vault) external view returns (uint256) {
        VaultRecord storage record = _vaultRecord(_vault);
        return _settleableLidoFeesValue(_vault, _vaultConnection(_vault), record, _unsettledLidoFeesValue(record));
    }

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

    /// @notice amount of bad debt to be internalized to become the protocol loss (that was actual on the last refSlot)
    function badDebtToInternalizeForLastRefSlot() external view returns (uint256) {
        return _storage().badDebtToInternalize.getValueForLastRefSlot(CONSENSUS_CONTRACT);
    }

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

        if (!IVaultFactory(LIDO_LOCATOR.vaultFactory()).deployedVaults(_vault)) revert VaultNotFactoryDeployed(_vault);
        IStakingVault vault_ = IStakingVault(_vault);
        _requireSender(vault_.owner());
        if (vault_.pendingOwner() != address(this)) revert VaultHubNotPendingOwner(_vault);
        if (IPinnedBeaconProxy(address(vault_)).isOssified()) revert VaultOssified(_vault);
        if (vault_.depositor() != address(_predepositGuarantee())) revert PDGNotDepositor(_vault);
        // we need vault to match staged balance with pendingActivations
        if (vault_.stagedBalance() != _predepositGuarantee().pendingActivations(vault_) * PDG_ACTIVATION_DEPOSIT) {
            revert InsufficientStagedBalance(_vault);
        }

        (
            , // nodeOperatorInTier
            , // tierId
            uint256 shareLimit,
            uint256 reserveRatioBP,
            uint256 forcedRebalanceThresholdBP,
            uint256 infraFeeBP,
            uint256 liquidityFeeBP,
            uint256 reservationFeeBP
        ) = _operatorGrid().vaultTierInfo(_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 a redemption shares on the vault
    /// @param _vault The address of the vault
    /// @param _liabilitySharesTarget maximum amount of liabilityShares that will be preserved, the rest will be
    ///         marked as redemptionShares. If value is greater than liabilityShares, redemptionShares are set to 0
    /// @dev NB: Mechanism to be triggered when Lido Core TVL <= stVaults TVL
    function setLiabilitySharesTarget(address _vault, uint256 _liabilitySharesTarget) external onlyRole(REDEMPTION_MASTER_ROLE) {
        VaultConnection storage connection = _checkConnection(_vault);
        VaultRecord storage record = _vaultRecord(_vault);

        uint256 liabilityShares_ = record.liabilityShares;
        uint256 redemptionShares = liabilityShares_ > _liabilitySharesTarget ? liabilityShares_ - _liabilitySharesTarget : 0;
        record.redemptionShares = uint128(redemptionShares);

        _updateBeaconChainDepositsPause(_vault, record, connection);

        emit VaultRedemptionSharesUpdated(_vault, record.redemptionShares);
    }

    /// @notice updates the vault's connection parameters
    /// @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
    /// @dev reverts if the vault's minting capacity will be exceeded with new reserve parameters
    /// @dev requires the fresh report
    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 = _vaultConnection(_vault);
        _requireConnected(connection, _vault);

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

        if (
            _reserveRatioBP != connection.reserveRatioBP ||
            _forcedRebalanceThresholdBP != connection.forcedRebalanceThresholdBP
        ) {
            uint256 totalValue_ = _totalValue(record);
            uint256 liabilityShares_ = record.liabilityShares;

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

        // special event for the Oracle to track fee calculation
        emit VaultFeesUpdated({
            vault: _vault,
            preInfraFeeBP: connection.infraFeeBP,
            preLiquidityFeeBP: connection.liquidityFeeBP,
            preReservationFeeBP: connection.reservationFeeBP,
            infraFeeBP: _infraFeeBP,
            liquidityFeeBP: _liquidityFeeBP,
            reservationFeeBP: _reservationFeeBP
        });

        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,
            nodeOperator: _nodeOperator(_vault),
            shareLimit: _shareLimit,
            reserveRatioBP: _reserveRatioBP,
            forcedRebalanceThresholdBP: _forcedRebalanceThresholdBP
        });
    }

    /// @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
    /// @dev requires the fresh report (see _initiateDisconnection)
    function disconnect(address _vault) external onlyRole(VAULT_MASTER_ROLE) {
        _initiateDisconnection(_vault, _checkConnection(_vault), _vaultRecord(_vault), false);

        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 on refSlot
    /// @param _reportMaxLiabilityShares the maxLiabilityShares of the vault on refSlot
    /// @param _reportSlashingReserve the slashingReserve of the vault
    /// @dev NB: LazyOracle sanity checks already verify that the fee can only increase
    function applyVaultReport(
        address _vault,
        uint256 _reportTimestamp,
        uint256 _reportTotalValue,
        int256 _reportInOutDelta,
        uint256 _reportCumulativeLidoFees,
        uint256 _reportLiabilityShares,
        uint256 _reportMaxLiabilityShares,
        uint256 _reportSlashingReserve
    ) external whenResumed {
        _requireSender(address(_lazyOracle()));

        VaultConnection storage connection = _vaultConnection(_vault);
        _requireConnected(connection, _vault);

        VaultRecord storage record = _vaultRecord(_vault);

        if (connection.disconnectInitiatedTs <= _reportTimestamp) {
            if (_reportSlashingReserve == 0 && record.liabilityShares == 0) {
                // liabilityShares can increase if badDebt was socialized to this vault
                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.disconnectInitiatedTs = DISCONNECT_NOT_INITIATED;
                emit VaultDisconnectAborted(_vault, _reportSlashingReserve);
            }
        }

        _applyVaultReport(
            record,
            _reportTimestamp,
            _reportTotalValue,
            _reportInOutDelta,
            _reportCumulativeLidoFees,
            _reportLiabilityShares,
            _reportMaxLiabilityShares,
            _reportSlashingReserve
        );

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

        _updateBeaconChainDepositsPause(_vault, record, connection);
    }

    /// @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
    /// @return number of shares that was socialized
    ///         (it's limited by acceptor vault capacity and bad debt actual size)
    /// @dev msg.sender must have BAD_DEBT_MASTER_ROLE
    /// @dev requires the fresh report for both bad debt and acceptor vaults
    function socializeBadDebt(
        address _badDebtVault,
        address _vaultAcceptor,
        uint256 _maxSharesToSocialize
    ) external onlyRole(BAD_DEBT_MASTER_ROLE) returns (uint256) {
        _requireNotZero(_badDebtVault);
        _requireNotZero(_vaultAcceptor);
        _requireNotZero(_maxSharesToSocialize);
        if (_nodeOperator(_vaultAcceptor) != _nodeOperator(_badDebtVault)) {
            revert BadDebtSocializationNotAllowed();
        }

        VaultConnection storage badDebtConnection = _vaultConnection(_badDebtVault);
        VaultRecord storage badDebtRecord = _vaultRecord(_badDebtVault);
        VaultConnection storage acceptorConnection = _vaultConnection(_vaultAcceptor);
        VaultRecord storage acceptorRecord = _vaultRecord(_vaultAcceptor);

        _requireConnected(badDebtConnection, _badDebtVault);
        _requireConnected(acceptorConnection, _vaultAcceptor);
        _requireFreshReport(_badDebtVault, badDebtRecord);
        _requireFreshReport(_vaultAcceptor, acceptorRecord);

        uint256 badDebtShares = _badDebtShares(badDebtRecord);
        uint256 badDebtToSocialize = Math256.min(badDebtShares, _maxSharesToSocialize);

        uint256 acceptorTotalValueShares = _getSharesByPooledEth(_totalValue(acceptorRecord));
        uint256 acceptorLiabilityShares = acceptorRecord.liabilityShares;

        // it's possible to socialize up to bad debt:
        uint256 acceptorCapacity = acceptorTotalValueShares < acceptorLiabilityShares ? 0
            : acceptorTotalValueShares - acceptorLiabilityShares;

        uint256 badDebtSharesToAccept = Math256.min(badDebtToSocialize, acceptorCapacity);

        if (badDebtSharesToAccept > 0) {
            _decreaseLiability(_badDebtVault, badDebtRecord, badDebtSharesToAccept);
            _increaseLiability({
                _vault: _vaultAcceptor,
                _record: acceptorRecord,
                _amountOfShares: badDebtSharesToAccept,
                _reserveRatioBP: acceptorConnection.reserveRatioBP,
                // don't check any limits
                _lockableValueLimit: type(uint256).max,
                _shareLimit: type(uint256).max,
                _overrideOperatorLimits: true
            });

            _updateBeaconChainDepositsPause(_vaultAcceptor, acceptorRecord, acceptorConnection);

            emit BadDebtSocialized(_badDebtVault, _vaultAcceptor, badDebtSharesToAccept);
        }

        return badDebtSharesToAccept;
    }

    /// @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
    /// @return number of shares that was internalized (limited by actual size of the bad debt)
    /// @dev msg.sender must have BAD_DEBT_MASTER_ROLE
    /// @dev requires the fresh report
    function internalizeBadDebt(
        address _badDebtVault,
        uint256 _maxSharesToInternalize
    ) external onlyRole(BAD_DEBT_MASTER_ROLE) returns (uint256) {
        _requireNotZero(_badDebtVault);
        _requireNotZero(_maxSharesToInternalize);

        VaultConnection storage badDebtConnection = _vaultConnection(_badDebtVault);
        VaultRecord storage badDebtRecord = _vaultRecord(_badDebtVault);
        _requireConnected(badDebtConnection, _badDebtVault);
        _requireFreshReport(_badDebtVault, badDebtRecord);

        uint256 badDebtShares = _badDebtShares(badDebtRecord);
        uint256 badDebtToInternalize_ = Math256.min(badDebtShares, _maxSharesToInternalize);

        if (badDebtToInternalize_ > 0) {
            _decreaseLiability(_badDebtVault, badDebtRecord, badDebtToInternalize_);

            // store internalization in a separate counter that will be settled
            // by the Accounting Oracle during the report
            _storage().badDebtToInternalize = _storage().badDebtToInternalize.withValueIncrease({
                _consensus: CONSENSUS_CONTRACT,
                _increment: SafeCast.toUint104(badDebtToInternalize_)
            });

            emit BadDebtWrittenOffToBeInternalized(_badDebtVault, badDebtToInternalize_);
        }

        return 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 -= uint104(_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
    /// @dev requires the fresh report (see _initiateDisconnection)
    function voluntaryDisconnect(address _vault) external whenResumed {
        VaultConnection storage connection = _checkConnectionAndOwner(_vault);

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

        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);
        _requireConnected(connection, _vault);
        _requireSender(connection.owner);

        _updateInOutDelta(_vault, _vaultRecord(_vault), int104(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
    /// @dev requires the fresh report
    function withdraw(address _vault, address _recipient, uint256 _ether) external whenResumed {
        VaultConnection storage connection = _checkConnectionAndOwner(_vault);
        VaultRecord storage record = _vaultRecord(_vault);
        _requireFreshReport(_vault, record);

        uint256 withdrawable = _withdrawableValue(_vault, connection, 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
    /// @dev requires the fresh report
    function rebalance(address _vault, uint256 _shares) external whenResumed {
        _requireNotZero(_shares);
        _checkConnectionAndOwner(_vault);

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

        _rebalance(_vault, record, _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
    /// @dev requires the fresh report
    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);

        _increaseLiability({
            _vault: _vault,
            _record: record,
            _amountOfShares: _amountOfShares,
            _reserveRatioBP: connection.reserveRatioBP,
            _lockableValueLimit: _maxLockableValue(record, 0),
            _shareLimit: connection.shareLimit,
            _overrideOperatorLimits: false
        });

        LIDO.mintExternalShares(_recipient, _amountOfShares);

        emit MintedSharesOnVault(_vault, _amountOfShares, _locked(connection, record));
    }

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

        _updateBeaconChainDepositsPause(_vault, record, _vaultConnection(_vault));

        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);
        if (connection.beaconChainDepositsPauseIntent) revert PauseIntentAlreadySet();

        connection.beaconChainDepositsPauseIntent = true;
        emit BeaconChainDepositsPauseIntentSet(_vault, true);

        _pauseBeaconChainDepositsIfNotAlready(IStakingVault(_vault));
    }

    /// @notice resumes beacon chain deposits for the vault
    /// @param _vault vault address
    /// @dev msg.sender should be vault's owner
    /// @dev requires the fresh report
    /// @dev NB: if the vault has outstanding obligations, this call will clear the manual pause flag but deposits will
    ///         remain paused until the obligations are covered. Once covered, deposits will resume automatically
    function resumeBeaconChainDeposits(address _vault) external {
        VaultConnection storage connection = _checkConnectionAndOwner(_vault);
        if (!connection.beaconChainDepositsPauseIntent) revert PauseIntentAlreadyUnset();

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

        connection.beaconChainDepositsPauseIntent = false;
        emit BeaconChainDepositsPauseIntentSet(_vault, false);

        _updateBeaconChainDepositsPause(_vault, record, connection);
    }

    /// @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 _amountsInGwei 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
    /// @dev requires the fresh report (in case of partial withdrawals)
    /// @dev A withdrawal fee must be paid via msg.value.
    ///      `StakingVault.calculateValidatorWithdrawalFee()` can be used to calculate the approximate fee amount but
    ///      it's accurate only for the current block. The fee may change when the tx is included, so it's recommended
    ///      to send some surplus. The exact amount required will be paid and the excess will be refunded to the
    ///      `_refundRecipient` address. The fee required can grow exponentially, so limit msg.value wisely to avoid
    ///      overspending.
    function triggerValidatorWithdrawals(
        address _vault,
        bytes calldata _pubkeys,
        uint64[] calldata _amountsInGwei,
        address _refundRecipient
    ) external payable {
        VaultConnection storage connection = _checkConnectionAndOwner(_vault);
        VaultRecord storage record = _vaultRecord(_vault);

        uint256 minPartialAmountInGwei = type(uint256).max;
        for (uint256 i = 0; i < _amountsInGwei.length; i++) {
            if (_amountsInGwei[i] > 0 && _amountsInGwei[i] < minPartialAmountInGwei) {
                minPartialAmountInGwei = _amountsInGwei[i];
            }
        }

        if (minPartialAmountInGwei < type(uint256).max) {
            _requireFreshReport(_vault, record);

            /// @dev NB: Disallow partial withdrawals when the vault has obligations shortfall in order to prevent the
            ///      vault owner from clogging the consensus layer withdrawal queue by front-running and delaying the
            ///      forceful validator exits required for rebalancing the vault. Partial withdrawals only allowed if
            ///      the requested amount of withdrawals is enough to cover the uncovered obligations.
            uint256 obligationsShortfallAmount = _obligationsShortfallValue(_vault, connection, record);
            if (obligationsShortfallAmount > 0 && minPartialAmountInGwei * 1e9 < obligationsShortfallAmount) {
                revert PartialValidatorWithdrawalNotAllowed();
            }
        }

        _triggerVaultValidatorWithdrawals(_vault, msg.value, _pubkeys, _amountsInGwei, _refundRecipient);
    }

    /// @notice Triggers validator full withdrawals for the vault using EIP-7002 if the vault has obligations shortfall
    /// @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 In case the vault has obligations shortfall, 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
    /// @dev requires the fresh report
    /// @dev A withdrawal fee must be paid via msg.value.
    ///      `StakingVault.calculateValidatorWithdrawalFee()` can be used to calculate the approximate fee amount but
    ///      it's accurate only for the current block. The fee may change when the tx is included, so it's recommended
    ///      to send some surplus. The exact amount required will be paid and the excess will be refunded to the
    ///      `_refundRecipient` address. The fee required can grow exponentially, so limit msg.value wisely to avoid
    ///      overspending.
    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);
        _requireFreshReport(_vault, record);

        uint256 obligationsShortfallAmount = _obligationsShortfallValue(_vault, connection, record);
        if (obligationsShortfallAmount == 0) revert ForcedValidatorExitNotAllowed();

        uint64[] memory amountsInGwei = new uint64[](0);
        _triggerVaultValidatorWithdrawals(_vault, msg.value, _pubkeys, amountsInGwei, _refundRecipient);

        emit ForcedValidatorExitTriggered(_vault, _pubkeys, _refundRecipient);
    }

    /// @notice allows anyone to rebalance a vault with an obligations shortfall
    /// @param _vault vault address
    /// @dev uses all available ether in the vault to cover outstanding obligations and restore vault health; this
    ///      operation does not settle Lido fees
    /// @dev requires the fresh report
    function forceRebalance(address _vault) external {
        VaultConnection storage connection = _checkConnection(_vault);
        VaultRecord storage record = _vaultRecord(_vault);
        _requireFreshReport(_vault, record);

        uint256 availableBalance = Math256.min(_availableBalance(_vault), _totalValue(record));
        if (availableBalance == 0) revert NoFundsForForceRebalance(_vault);

        uint256 sharesToForceRebalance = Math256.min(
            _obligationsShares(connection, record),
            _getSharesByPooledEth(availableBalance)
        );

        if (sharesToForceRebalance == 0) revert NoReasonForForceRebalance(_vault);

        _rebalance(_vault, record, sharesToForceRebalance);
    }

    /// @notice allows anyone to settle any outstanding Lido fees for a vault, sending them to the treasury
    /// @param _vault vault address
    /// @dev requires the fresh report
    function settleLidoFees(address _vault) external {
        VaultConnection storage connection = _checkConnection(_vault);
        VaultRecord storage record = _vaultRecord(_vault);
        _requireFreshReport(_vault, record);

        uint256 unsettledLidoFees = _unsettledLidoFeesValue(record);
        if (unsettledLidoFees == 0) revert NoUnsettledLidoFeesToSettle(_vault);

        uint256 valueToSettle = _settleableLidoFeesValue(_vault, connection, record, unsettledLidoFees);
        if (valueToSettle == 0) revert NoFundsToSettleLidoFees(_vault, unsettledLidoFees);

        _settleLidoFees(_vault, record, connection, valueToSettle);
    }

    /// @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 collects ERC20 tokens from vault
    /// @param _vault vault address
    /// @param _token address of the ERC20 token to collect
    /// @param _recipient address to send collected tokens to
    /// @param _amount amount of tokens to collect
    /// @dev will revert with ZeroArgument() if _token, _recipient or _amount is zero
    /// @dev will revert with EthCollectionNotAllowed() if _token is ETH (via EIP-7528 address)
    function collectERC20FromVault(
        address _vault,
        address _token,
        address _recipient,
        uint256 _amount
    ) external {
         _checkConnectionAndOwner(_vault);
         IStakingVault(_vault).collectERC20(_token, _recipient, _amount);
    }

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

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

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

        IStakingVault vault = IStakingVault(_vault);

        // Connecting a new vault with totalValue == balance
        VaultRecord memory record = VaultRecord({
            report: Report({
                totalValue: uint104(vaultBalance),
                inOutDelta: int104(int256(vaultBalance)),
                timestamp: uint48(block.timestamp)
            }),
            maxLiabilityShares: 0,
            liabilityShares: 0,
            inOutDelta: DoubleRefSlotCache.initializeInt104DoubleCache(int104(int256(vaultBalance))),
            minimalReserve: uint128(CONNECT_DEPOSIT),
            redemptionShares: 0,
            cumulativeLidoFees: 0,
            settledLidoFees: 0
        });

        connection = VaultConnection({
            owner: vault.owner(),
            shareLimit: uint96(_shareLimit),
            vaultIndex: uint96(_storage().vaults.length),
            disconnectInitiatedTs: DISCONNECT_NOT_INITIATED,
            reserveRatioBP: uint16(_reserveRatioBP),
            forcedRebalanceThresholdBP: uint16(_forcedRebalanceThresholdBP),
            infraFeeBP: uint16(_infraFeeBP),
            liquidityFeeBP: uint16(_liquidityFeeBP),
            reservationFeeBP: uint16(_reservationFeeBP),
            beaconChainDepositsPauseIntent: vault.beaconChainDepositsPaused()
        });

        _addVault(_vault, connection, record);
    }

    function _initiateDisconnection(
        address _vault,
        VaultConnection storage _connection,
        VaultRecord storage _record,
        bool _forceFullFeesSettlement
    ) internal {
        _requireFreshReport(_vault, _record);

        uint256 liabilityShares_ = _record.liabilityShares;
        if (liabilityShares_ > 0) revert NoLiabilitySharesShouldBeLeft(_vault, liabilityShares_);

        uint256 unsettledLidoFees = _unsettledLidoFeesValue(_record);
        if (unsettledLidoFees > 0) {
            uint256 balance = Math256.min(_availableBalance(_vault), _totalValue(_record));
            if (_forceFullFeesSettlement) {
                if (balance < unsettledLidoFees) revert NoUnsettledLidoFeesShouldBeLeft(_vault, unsettledLidoFees);

                _settleLidoFees(_vault, _record, _connection, unsettledLidoFees);
            } else {
                uint256 withdrawable = Math256.min(balance, unsettledLidoFees);
                if (withdrawable > 0) {
                    _settleLidoFees(_vault, _record, _connection, withdrawable);
                }
            }
        }

        _connection.disconnectInitiatedTs = uint48(block.timestamp);
    }

    function _applyVaultReport(
        VaultRecord storage _record,
        uint256 _reportTimestamp,
        uint256 _reportTotalValue,
        int256 _reportInOutDelta,
        uint256 _reportCumulativeLidoFees,
        uint256 _reportLiabilityShares,
        uint256 _reportMaxLiabilityShares,
        uint256 _reportSlashingReserve
    ) internal {
        _record.cumulativeLidoFees = uint128(_reportCumulativeLidoFees);
        _record.minimalReserve = uint128(Math256.max(CONNECT_DEPOSIT, _reportSlashingReserve));

        // We want to prevent 1 tx looping here:
        // 1. bring ETH (TV+)
        // 2. mint stETH (locked+)
        // 3. burn stETH
        // 4. bring the last report (locked-)
        // 5. withdraw ETH(TV-)

        // current maxLiabilityShares will be greater than the report one
        // if any stETH is minted on funds added after the refslot
        // in that case we don't update it (preventing unlock)
        if (_record.maxLiabilityShares == _reportMaxLiabilityShares) {
            _record.maxLiabilityShares = uint96(Math256.max(_record.liabilityShares, _reportLiabilityShares));
        }
        _record.report = Report({
            totalValue: uint104(_reportTotalValue),
            inOutDelta: int104(_reportInOutDelta),
            timestamp: uint48(_reportTimestamp)
        });
    }

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

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

        _updateBeaconChainDepositsPause(_vault, _record, _vaultConnection(_vault));

        emit VaultRebalanced(_vault, _shares, valueToRebalance);
    }

    function _withdraw(address _vault, VaultRecord storage _record, address _recipient, uint256 _amount) internal {
        uint256 totalValue_ = _totalValue(_record);
        if (_amount > totalValue_) {
            revert AmountExceedsTotalValue(_vault, totalValue_, _amount);
        }

        _updateInOutDelta(_vault, _record, -int104(int256(_amount)));
        _withdrawFromVault(_vault, _recipient, _amount);
    }

    /// @dev Increases liabilityShares of the vault and updates the locked amount
    function _increaseLiability(
        address _vault,
        VaultRecord storage _record,
        uint256 _amountOfShares,
        uint256 _reserveRatioBP,
        uint256 _lockableValueLimit,
        uint256 _shareLimit,
        bool _overrideOperatorLimits
    ) internal {
        uint256 sharesAfterMint = _record.liabilityShares + _amountOfShares;
        if (sharesAfterMint > _shareLimit) {
            revert ShareLimitExceeded(_vault, sharesAfterMint, _shareLimit);
        }

        // Calculate the minimum ETH that needs to be locked in the vault to maintain the reserve ratio
        uint256 etherToLock = _locked(sharesAfterMint, _record.minimalReserve, _reserveRatioBP);
        if (etherToLock > _lockableValueLimit) {
            revert InsufficientValue(_vault, etherToLock, _lockableValueLimit);
        }

        if (sharesAfterMint > _record.maxLiabilityShares) {
            _record.maxLiabilityShares = uint96(sharesAfterMint);
        }

        _record.liabilityShares = uint96(sharesAfterMint);

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

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

        uint256 redemptionShares = _record.redemptionShares;
        if (_amountOfShares > 0 && redemptionShares > 0) {
            uint256 decreasedRedemptionShares = redemptionShares - Math256.min(redemptionShares, _amountOfShares);
            _record.redemptionShares = uint128(decreasedRedemptionShares);

            emit VaultRedemptionSharesUpdated(_vault, decreasedRedemptionShares);
        }

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

    function _badDebtShares(VaultRecord storage _record) internal view returns (uint256) {
        uint256 liabilityShares_ = _record.liabilityShares;
        uint256 totalValueShares = _getSharesByPooledEth(_totalValue(_record));

        if (totalValueShares > liabilityShares_) {
            return 0;
        }

        return liabilityShares_ - totalValueShares;
    }

    function _healthShortfallShares(
        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 liability = _getPooledEthBySharesRoundUp(liabilityShares_);

        // Impossible to rebalance a vault with bad debt
        if (liability > totalValue_) {
            return type(uint256).max;
        }

        // if not healthy and low in debt, please rebalance the whole amount
        if (liabilityShares_ <= 100) return liabilityShares_;

        // Solve the equation for X:
        // L - liability, TV - totalValue
        // MR - maxMintableRatio, 100 - TOTAL_BASIS_POINTS, RR - reserveRatio
        // X - amount of ether that should be withdrawn (TV - X) and used to repay the debt (L - X) to reduce the
        // L/TV ratio back to MR

        // (L - X) / (TV - X) = MR / 100
        // (L - X) * 100 = (TV - X) * MR
        // L * 100 - X * 100 = TV * MR - X * MR
        // X * MR - X * 100 = TV * MR - L * 100
        // X * (MR - 100) = TV * MR - L * 100
        // X = (TV * MR - L * 100) / (MR - 100)
        // X = (L * 100 - TV * MR) / (100 - MR)
        // RR = 100 - MR
        // X = (L * 100 - TV * MR) / RR
        uint256 shortfallEth = Math256.ceilDiv(liability * TOTAL_BASIS_POINTS - totalValue_ * maxMintableRatio,
            reserveRatioBP);

        // Add 100 extra shares to avoid dealing with rounding/precision issues
        uint256 shortfallShares = _getSharesByPooledEth(shortfallEth) + 100;

        return Math256.min(shortfallShares, liabilityShares_);
    }

    function _totalValue(VaultRecord storage _record) internal view returns (uint256) {
        Report memory report = _record.report;
        DoubleRefSlotCache.Int104WithCache[DOUBLE_CACHE_LENGTH] memory inOutDelta = _record.inOutDelta;
        return SafeCast.toUint256(int256(uint256(report.totalValue)) + inOutDelta.currentValue() - report.inOutDelta);
    }

    function _locked(
        VaultConnection storage _connection,
        VaultRecord storage _record
    ) internal view returns (uint256) {
        return _locked(_record.maxLiabilityShares, _record.minimalReserve, _connection.reserveRatioBP);
    }

    /// @param _liabilityShares amount of shares that the vault is minted
    /// @param _minimalReserve minimal amount of additional reserve to be locked
    /// @param _reserveRatioBP the reserve ratio of the vault
    /// @return the amount of collateral to be locked on the vault
    function _locked(
        uint256 _liabilityShares,
        uint256 _minimalReserve,
        uint256 _reserveRatioBP
    ) internal view returns (uint256) {
        uint256 liability = _getPooledEthBySharesRoundUp(_liabilityShares);

        // uint256 reserve = liability * TOTAL_BASIS_POINTS / (TOTAL_BASIS_POINTS - _reserveRatioBP) - liability;
        // simplified to:
        uint256 reserve = Math256.ceilDiv(liability * _reserveRatioBP, TOTAL_BASIS_POINTS - _reserveRatioBP);

        return liability + Math256.max(reserve, _minimalReserve);
    }

    function _isReportFresh(VaultRecord storage _record) internal view returns (bool) {
        uint256 latestReportTimestamp = _lazyOracle().latestReportTimestamp();
        return
            // check if AccountingOracle brought fresh report
            uint48(latestReportTimestamp) <= _record.report.timestamp &&
            // if Accounting Oracle stop bringing the report, last report is fresh during this time
            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;
    }

    /// @return the total amount of ether needed to fully cover all outstanding obligations of the vault, including:
    ///         - shares to burn required to restore vault healthiness or cover redemptions
    ///         - unsettled Lido fees (if above the minimum beacon deposit)
    function _obligationsAmount(
        VaultConnection storage _connection,
        VaultRecord storage _record
    ) internal view returns (uint256) {
        uint256 sharesToBurn = _obligationsShares(_connection, _record);
        if (sharesToBurn == type(uint256).max) return type(uint256).max;

        // no need to cover fees if they are less than the minimum beacon deposit
        uint256 unsettledLidoFees = _unsettledLidoFeesValue(_record);
        uint256 feesToSettle = unsettledLidoFees < MIN_BEACON_DEPOSIT ? 0 : unsettledLidoFees;

        return _getPooledEthBySharesRoundUp(sharesToBurn) + feesToSettle;
    }

    /// @return the ether shortfall required to fully cover all outstanding obligations amount of the vault
    function _obligationsShortfallValue(
        address _vault,
        VaultConnection storage _connection,
        VaultRecord storage _record
    ) internal view returns (uint256) {
        uint256 obligationsAmount_ = _obligationsAmount(_connection, _record);
        if (obligationsAmount_ == type(uint256).max) return type(uint256).max;

        uint256 balance = _availableBalance(_vault);

        return obligationsAmount_ > balance ? obligationsAmount_ - balance : 0;
    }

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

        _lazyOracle().removeVaultQuarantine(_vault);
        _operatorGrid().resetVaultTier(_vault);
    }

    function _checkConnectionAndOwner(address _vault) internal view returns (VaultConnection storage connection) {
        connection = _checkConnection(_vault);
        _requireSender(connection.owner);
    }

    function _isPendingDisconnect(VaultConnection storage _connection) internal view returns (bool) {
        uint256 disconnectionTs = _connection.disconnectInitiatedTs;
        return disconnectionTs != 0 // vault is disconnected
            && disconnectionTs != DISCONNECT_NOT_INITIATED; // vault in connected but not pending for disconnect
    }

    function _checkConnection(address _vault) internal view returns (VaultConnection storage) {
        _requireNotZero(_vault);

        VaultConnection storage connection = _vaultConnection(_vault);
        _requireConnected(connection, _vault);
        if (_isPendingDisconnect(connection)) revert VaultIsDisconnecting(_vault);

        return connection;
    }

    /// @dev Caches the inOutDelta of the latest refSlot and updates the value
    function _updateInOutDelta(address _vault, VaultRecord storage _record, int104 _increment) internal {
        DoubleRefSlotCache.Int104WithCache[DOUBLE_CACHE_LENGTH] memory inOutDelta = _record.inOutDelta.withValueIncrease({
            _consensus: CONSENSUS_CONTRACT,
            _increment: _increment
        });
        _record.inOutDelta = inOutDelta;
        emit VaultInOutDeltaUpdated(_vault, inOutDelta.currentValue());
    }

    function _updateBeaconChainDepositsPause(
        address _vault,
        VaultRecord storage _record,
        VaultConnection storage _connection
    ) internal {
        IStakingVault vault_ = IStakingVault(_vault);
        uint256 obligationsAmount_ = _obligationsAmount(_connection, _record);
        if (obligationsAmount_ > 0) {
            _pauseBeaconChainDepositsIfNotAlready(vault_);
        } else if (!_connection.beaconChainDepositsPauseIntent) {
            _resumeBeaconChainDepositsIfNotAlready(vault_);
        }
    }

    function _settleLidoFees(
        address _vault,
        VaultRecord storage _record,
        VaultConnection storage _connection,
        uint256 _valueToSettle
    ) internal {
        uint256 settledLidoFees = _record.settledLidoFees + _valueToSettle;
        _record.settledLidoFees = uint128(settledLidoFees);

        _withdraw(_vault, _record, LIDO_LOCATOR.treasury(), _valueToSettle);
        _updateBeaconChainDepositsPause(_vault, _record, _connection);

        emit LidoFeesSettled({
            vault: _vault,
            transferred: _valueToSettle,
            cumulativeLidoFees: _record.cumulativeLidoFees,
            settledLidoFees: settledLidoFees
        });
    }

    /// @notice the amount of ether that can be withdrawn from the vault based on the available balance,
    ///         locked value, vault redemption shares (does not include Lido fees)
    function _withdrawableValueFeesIncluded(
        address _vault,
        VaultConnection storage _connection,
        VaultRecord storage _record
    ) internal view returns (uint256) {
        uint256 availableBalance = Math256.min(_availableBalance(_vault), _totalValue(_record));

        // We can't withdraw funds that can be used to cover redemptions
        uint256 redemptionValue = _getPooledEthBySharesRoundUp(_record.redemptionShares);
        if (redemptionValue > availableBalance) return 0;
        availableBalance -= redemptionValue;

        // We must account vaults locked value when calculating the withdrawable amount
        return Math256.min(availableBalance, _unlocked(_connection, _record));
    }

    /// @notice the amount of lido fees that can be settled on the vault based on the withdrawable value
    function _settleableLidoFeesValue(
        address _vault,
        VaultConnection storage _connection,
        VaultRecord storage _record,
        uint256 _feesToSettle
    ) internal view returns (uint256) {
        return Math256.min(_withdrawableValueFeesIncluded(_vault, _connection, _record), _feesToSettle);
    }

    /// @notice the amount of ether that can be instantly withdrawn from the vault based on the available balance,
    ///         locked value, vault redemption shares and unsettled Lido fees accrued on the vault
    function _withdrawableValue(
        address _vault,
        VaultConnection storage _connection,
        VaultRecord storage _record
    ) internal view returns (uint256) {
        uint256 withdrawable = _withdrawableValueFeesIncluded(_vault, _connection, _record);
        uint256 feesValue = _unsettledLidoFeesValue(_record);
        return withdrawable > feesValue ? withdrawable - feesValue : 0;
    }

    /// @notice Calculates the max lockable value of the vault
    /// @param _record The record of the vault
    /// @param _deltaValue The delta value to apply to the total value of the vault (may be negative)
    /// @return the max lockable value of the vault
    function _maxLockableValue(VaultRecord storage _record, int256 _deltaValue) internal view returns (uint256) {
        uint256 totalValue_ = _totalValue(_record);
        uint256 unsettledLidoFees_ = _unsettledLidoFeesValue(_record);
        if (_deltaValue < 0) {
            uint256 absDeltaValue = uint256(-_deltaValue);
            totalValue_ = totalValue_ > absDeltaValue ? totalValue_ - absDeltaValue : 0;
        } else {
            totalValue_ += uint256(_deltaValue);
        }

        return totalValue_ > unsettledLidoFees_ ? totalValue_ - unsettledLidoFees_ : 0;
    }

    /// @notice Calculates the total number of shares that is possible to mint on the vault taking into account
    ///         minimal reserve, reserve ratio and the operator grid share limit
    /// @param _vault The address of the vault
    /// @param _deltaValue The delta value to apply to the total value of the vault (may be negative)
    /// @return the number of shares that can be minted
    /// @dev returns 0 if the vault is not connected
    function _totalMintingCapacityShares(address _vault, int256 _deltaValue) internal view returns (uint256) {
        VaultRecord storage record = _vaultRecord(_vault);
        VaultConnection storage connection = _vaultConnection(_vault);

        uint256 maxLockableValue_ = _maxLockableValue(record, _deltaValue);
        uint256 minimalReserve_ = record.minimalReserve;
        if (maxLockableValue_ <= minimalReserve_) return 0;

        uint256 reserve = Math256.ceilDiv(maxLockableValue_ * connection.reserveRatioBP, TOTAL_BASIS_POINTS);

        uint256 capacityShares = _getSharesByPooledEth(maxLockableValue_ - Math256.max(reserve, minimalReserve_));
        return Math256.min(capacityShares, _operatorGrid().effectiveShareLimit(_vault));
    }

    function _unlocked(
        VaultConnection storage _connection,
        VaultRecord storage _record
    ) internal view returns (uint256) {
        uint256 totalValue_ = _totalValue(_record);
        uint256 locked_ = _locked(_connection, _record);
        return totalValue_ > locked_ ? totalValue_ - locked_ : 0;
    }

    function _unsettledLidoFeesValue(VaultRecord storage _record) internal view returns (uint256) {
        return _record.cumulativeLidoFees - _record.settledLidoFees;
    }

    function _obligationsShares(
        VaultConnection storage _connection,
        VaultRecord storage _record
    ) internal view returns (uint256) {
        return Math256.max(_healthShortfallShares(_connection, _record), _record.redemptionShares);
    }

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

    // -----------------------------
    //          EXTERNAL CALLS
    // -----------------------------
    // All external calls that is used more than once is wrapped in internal function to save bytecode

    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 _getPooledEthBySharesRoundUp(uint256 _shares) internal view returns (uint256) {
        return LIDO.getPooledEthBySharesRoundUp(_shares);
    }

    function _rebalanceExternalEtherToInternal(uint256 _ether, uint256 _amountOfShares) internal {
        LIDO.rebalanceExternalEtherToInternal{value: _ether}(_amountOfShares);
    }

    function _triggerVaultValidatorWithdrawals(
        address _vault,
        uint256 _value,
        bytes calldata _pubkeys,
        uint64[] memory _amountsInGwei,
        address _refundRecipient
    ) internal {
        IStakingVault(_vault).triggerValidatorWithdrawals{value: _value}(_pubkeys, _amountsInGwei, _refundRecipient);
    }

    function _withdrawFromVault(address _vault, address _recipient, uint256 _amount) internal {
        IStakingVault(_vault).withdraw(_recipient, _amount);
    }

    function _nodeOperator(address _vault) internal view returns (address) {
        return IStakingVault(_vault).nodeOperator();
    }

    function _availableBalance(address _vault) internal view returns (uint256) {
        return IStakingVault(_vault).availableBalance();
    }

    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 _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);
    }

    function _isBeaconChainDepositsPaused(IStakingVault _vault) internal view returns (bool) {
        return _vault.beaconChainDepositsPaused();
    }

    function _pauseBeaconChainDepositsIfNotAlready(IStakingVault _vault) internal {
        if (!_isBeaconChainDepositsPaused(_vault)) {
            _vault.pauseBeaconChainDeposits();
        }
    }

    function _resumeBeaconChainDepositsIfNotAlready(IStakingVault _vault) internal {
        if (_isBeaconChainDepositsPaused(_vault)) {
            _vault.resumeBeaconChainDeposits();
        }
    }

    // -----------------------------
    //           EVENTS
    // -----------------------------

    /// @dev Warning! used by Accounting Oracle to calculate fees
    event VaultConnected(
        address indexed vault,
        uint256 shareLimit,
        uint256 reserveRatioBP,
        uint256 forcedRebalanceThresholdBP,
        uint256 infraFeeBP,
        uint256 liquidityFeeBP,
        uint256 reservationFeeBP
    );

    event VaultConnectionUpdated(
        address indexed vault,
        address indexed nodeOperator,
        uint256 shareLimit,
        uint256 reserveRatioBP,
        uint256 forcedRebalanceThresholdBP
    );

    /// @dev Warning! used by Accounting Oracle to calculate fees
    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 reportMaxLiabilityShares,
        uint256 reportSlashingReserve
    );

    /// @dev Warning! used by Accounting Oracle to calculate fees
    event MintedSharesOnVault(address indexed vault, uint256 amountOfShares, uint256 lockedAmount);
    /// @dev Warning! used by Accounting Oracle to calculate fees
    event BurnedSharesOnVault(address indexed vault, uint256 amountOfShares);
    /// @dev Warning! used by Accounting Oracle to calculate fees
    event VaultRebalanced(address indexed vault, uint256 sharesBurned, uint256 etherWithdrawn);
    event VaultInOutDeltaUpdated(address indexed vault, int256 inOutDelta);
    event ForcedValidatorExitTriggered(address indexed vault, bytes pubkeys, address refundRecipient);

    /**
     * @notice Emitted when the vault ownership is changed
     * @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 LidoFeesSettled(address indexed vault, uint256 transferred, uint256 cumulativeLidoFees, uint256 settledLidoFees);
    event VaultRedemptionSharesUpdated(address indexed vault, uint256 redemptionShares);

    event BeaconChainDepositsPauseIntentSet(address indexed vault, bool pauseIntent);

    /// @dev Warning! used by Accounting Oracle to calculate fees
    event BadDebtSocialized(address indexed vaultDonor, address indexed vaultAcceptor, uint256 badDebtShares);
    /// @dev Warning! used by Accounting Oracle to calculate fees
    event BadDebtWrittenOffToBeInternalized(address indexed vault, uint256 badDebtShares);

    // -----------------------------
    //           ERRORS
    // -----------------------------

    error PauseIntentAlreadySet();
    error PauseIntentAlreadyUnset();

    error AmountExceedsTotalValue(address vault, uint256 totalValue, uint256 withdrawAmount);
    error AmountExceedsWithdrawableValue(address vault, uint256 withdrawable, uint256 requested);

    error NoFundsForForceRebalance(address vault);
    error NoReasonForForceRebalance(address vault);

    error NoUnsettledLidoFeesToSettle(address vault);
    error NoFundsToSettleLidoFees(address vault, uint256 unsettledLidoFees);

    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 InsufficientStagedBalance(address vault);
    error NotConnectedToHub(address vault);
    error NotAuthorized();
    error ZeroAddress();
    error ZeroArgument();
    error InvalidBasisPoints(uint256 valueBP, uint256 maxValueBP);
    error ShareLimitTooHigh(uint256 shareLimit, uint256 maxShareLimit);
    error InsufficientValue(address vault, uint256 etherToLock, uint256 maxLockableValue);
    error NoLiabilitySharesShouldBeLeft(address vault, uint256 liabilityShares);
    error NoUnsettledLidoFeesShouldBeLeft(address vault, uint256 unsettledLidoFees);
    error VaultOssified(address vault);
    error VaultInsufficientBalance(address vault, uint256 currentBalance, uint256 expectedBalance);
    error VaultReportStale(address vault);
    error PDGNotDepositor(address vault);
    error VaultHubNotPendingOwner(address vault);
    error VaultIsDisconnecting(address vault);
    error PartialValidatorWithdrawalNotAllowed();
    error ForcedValidatorExitNotAllowed();
    error BadDebtSocializationNotAllowed();
    error VaultNotFactoryDeployed(address vault);
}

// 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 23 of 34 : 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 _vaultsDataTimestamp,
        uint256 _vaultsDataRefSlot,
        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(uint256 _amountOfShares) 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 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
{
  "viaIR": true,
  "optimizer": {
    "enabled": true,
    "runs": 999999
  },
  "evmVersion": "cancun",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "remappings": [
    "contracts/=submodules/lidofinance-core/contracts/",
    "@openzeppelin/contracts-v4.4/=npm/@openzeppelin/[email protected]/",
    "@openzeppelin/contracts-v5.2/=npm/@openzeppelin/[email protected]/"
  ]
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_lidoLocator","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"string","name":"argName","type":"string"}],"name":"ZeroArgument","type":"error"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LAZY_ORACLE","outputs":[{"internalType":"contract LazyOracle","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIDO_LOCATOR","outputs":[{"internalType":"contract ILidoLocator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VAULT_HUB","outputs":[{"internalType":"contract VaultHub","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IStakingVault","name":"vault","type":"address"},{"internalType":"address","name":"_member","type":"address"},{"internalType":"bytes32","name":"_role","type":"bytes32"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IStakingVault","name":"vault","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"name":"isVaultOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vaultAddress","type":"address"},{"internalType":"bytes32[]","name":"roles","type":"bytes32[]"}],"name":"roleMembers","outputs":[{"components":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"nodeOperator","type":"address"},{"internalType":"address[][]","name":"members","type":"address[][]"}],"internalType":"struct VaultViewer.VaultMembers","name":"roleMembers","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"vaultAddresses","type":"address[]"},{"internalType":"bytes32[]","name":"roles","type":"bytes32[]"}],"name":"roleMembersBatch","outputs":[{"components":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"nodeOperator","type":"address"},{"internalType":"address[][]","name":"members","type":"address[][]"}],"internalType":"struct VaultViewer.VaultMembers[]","name":"result","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"vaultAddressesBatch","outputs":[{"internalType":"contract IStakingVault[]","name":"vaults","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"}],"name":"vaultData","outputs":[{"components":[{"internalType":"address","name":"vaultAddress","type":"address"},{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint96","name":"shareLimit","type":"uint96"},{"internalType":"uint96","name":"vaultIndex","type":"uint96"},{"internalType":"uint48","name":"disconnectInitiatedTs","type":"uint48"},{"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":"beaconChainDepositsPauseIntent","type":"bool"}],"internalType":"struct VaultHub.VaultConnection","name":"connection","type":"tuple"},{"components":[{"components":[{"internalType":"uint104","name":"totalValue","type":"uint104"},{"internalType":"int104","name":"inOutDelta","type":"int104"},{"internalType":"uint48","name":"timestamp","type":"uint48"}],"internalType":"struct VaultHub.Report","name":"report","type":"tuple"},{"internalType":"uint96","name":"maxLiabilityShares","type":"uint96"},{"internalType":"uint96","name":"liabilityShares","type":"uint96"},{"components":[{"internalType":"int104","name":"value","type":"int104"},{"internalType":"int104","name":"valueOnRefSlot","type":"int104"},{"internalType":"uint48","name":"refSlot","type":"uint48"}],"internalType":"struct DoubleRefSlotCache.Int104WithCache[2]","name":"inOutDelta","type":"tuple[2]"},{"internalType":"uint128","name":"minimalReserve","type":"uint128"},{"internalType":"uint128","name":"redemptionShares","type":"uint128"},{"internalType":"uint128","name":"cumulativeLidoFees","type":"uint128"},{"internalType":"uint128","name":"settledLidoFees","type":"uint128"}],"internalType":"struct VaultHub.VaultRecord","name":"record","type":"tuple"},{"internalType":"uint256","name":"totalValue","type":"uint256"},{"internalType":"uint256","name":"liabilityStETH","type":"uint256"},{"internalType":"uint256","name":"nodeOperatorFeeRate","type":"uint256"},{"internalType":"uint256","name":"accruedFee","type":"uint256"},{"internalType":"bool","name":"isReportFresh","type":"bool"},{"components":[{"internalType":"bool","name":"isActive","type":"bool"},{"internalType":"uint256","name":"pendingTotalValueIncrease","type":"uint256"},{"internalType":"uint256","name":"startTimestamp","type":"uint256"},{"internalType":"uint256","name":"endTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalValueRemainder","type":"uint256"}],"internalType":"struct LazyOracle.QuarantineInfo","name":"quarantineInfo","type":"tuple"}],"internalType":"struct VaultViewer.VaultData","name":"data","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"vaultsByOwnerBatch","outputs":[{"internalType":"contract IStakingVault[]","name":"vaults","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_role","type":"bytes32"},{"internalType":"address","name":"_member","type":"address"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"vaultsByRoleBatch","outputs":[{"internalType":"contract IStakingVault[]","name":"vaults","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vaultsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"vaultsDataBatch","outputs":[{"components":[{"internalType":"address","name":"vaultAddress","type":"address"},{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint96","name":"shareLimit","type":"uint96"},{"internalType":"uint96","name":"vaultIndex","type":"uint96"},{"internalType":"uint48","name":"disconnectInitiatedTs","type":"uint48"},{"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":"beaconChainDepositsPauseIntent","type":"bool"}],"internalType":"struct VaultHub.VaultConnection","name":"connection","type":"tuple"},{"components":[{"components":[{"internalType":"uint104","name":"totalValue","type":"uint104"},{"internalType":"int104","name":"inOutDelta","type":"int104"},{"internalType":"uint48","name":"timestamp","type":"uint48"}],"internalType":"struct VaultHub.Report","name":"report","type":"tuple"},{"internalType":"uint96","name":"maxLiabilityShares","type":"uint96"},{"internalType":"uint96","name":"liabilityShares","type":"uint96"},{"components":[{"internalType":"int104","name":"value","type":"int104"},{"internalType":"int104","name":"valueOnRefSlot","type":"int104"},{"internalType":"uint48","name":"refSlot","type":"uint48"}],"internalType":"struct DoubleRefSlotCache.Int104WithCache[2]","name":"inOutDelta","type":"tuple[2]"},{"internalType":"uint128","name":"minimalReserve","type":"uint128"},{"internalType":"uint128","name":"redemptionShares","type":"uint128"},{"internalType":"uint128","name":"cumulativeLidoFees","type":"uint128"},{"internalType":"uint128","name":"settledLidoFees","type":"uint128"}],"internalType":"struct VaultHub.VaultRecord","name":"record","type":"tuple"},{"internalType":"uint256","name":"totalValue","type":"uint256"},{"internalType":"uint256","name":"liabilityStETH","type":"uint256"},{"internalType":"uint256","name":"nodeOperatorFeeRate","type":"uint256"},{"internalType":"uint256","name":"accruedFee","type":"uint256"},{"internalType":"bool","name":"isReportFresh","type":"bool"},{"components":[{"internalType":"bool","name":"isActive","type":"bool"},{"internalType":"uint256","name":"pendingTotalValueIncrease","type":"uint256"},{"internalType":"uint256","name":"startTimestamp","type":"uint256"},{"internalType":"uint256","name":"endTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalValueRemainder","type":"uint256"}],"internalType":"struct LazyOracle.QuarantineInfo","name":"quarantineInfo","type":"tuple"}],"internalType":"struct VaultViewer.VaultData[]","name":"vaultsData","type":"tuple[]"}],"stateMutability":"view","type":"function"}]

0x60e06040908082523461019357612ab3803803809161001e8285610197565b83396001600160a01b0391829161003891908101906101ce565b16801561015f578060805282518091636dd6e80b60e01b825281600460209485935afa80156101385783915f91610142575b501660a0526004818360805116855192838092630b8e411760e21b82525afa918215610138575f9261010b575b50501660c052516128c590816101ee823960805181610b82015260a05181818161013e015281816106fa015281816110350152818161121f015281816113d4015281816115c90152818161184101528181611be8015281816120d3015261229a015260c0518181816103700152611dca0152f35b61012a9250803d10610131575b6101228183610197565b8101906101ce565b5f80610097565b503d610118565b84513d5f823e3d90fd5b6101599150833d8511610131576101228183610197565b5f61006a565b82516356e4289360e01b815260206004820152600c60248201526b2fb634b237a637b1b0ba37b960a11b6044820152606490fd5b5f80fd5b601f909101601f19168101906001600160401b038211908210176101ba57604052565b634e487b7160e01b5f52604160045260245ffd5b9081602091031261019357516001600160a01b0381168103610193579056fe60806040526004361015610011575f80fd5b5f3560e01c8063196b9e06146100f45780632af23703146100ef578063535f6dbe146100ea57806357b14864146100e55780635ee77854146100e05780636eea4183146100db57806382a503fa146100d657806388977738146100d157806399abe483146100cc578063a217fddf146100c7578063d72ff79a146100c2578063dbba4b48146100bd578063df572f0d146100b85763f041b7d3146100b3575f80fd5b610bf7565b610ba6565b610b38565b610ae3565b610aab565b610a1f565b610689565b610634565b6105e8565b610471565b610394565b610326565b6102a6565b34610162575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016257602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b5f80fd5b73ffffffffffffffffffffffffffffffffffffffff81160361016257565b9181601f840112156101625782359167ffffffffffffffff8311610162576020808501948460051b01011161016257565b906080810173ffffffffffffffffffffffffffffffffffffffff9081845116835260606020948386820151168686015283604082015116604086015201519360806060850152845180925260a08401928160a08460051b8701019601935f915b848310610226575050505050505090565b909192877fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6088839798999b9a030184528751908280835192838152019201905f905b80821061028b575050509080600192980193019301919493929097959697610215565b91938060019294878751168152019401920187939291610268565b346101625760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610162576004356102e181610166565b60243567ffffffffffffffff8111610162576103229161030861030e923690600401610184565b91610fc2565b6040519182916020835260208301906101b5565b0390f35b34610162575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016257602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346101625760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101625760206103e66004356103d481610166565b602435906103e182610166565b6111ce565b6040519015158152f35b6020808201906020835283518092526040830192602060408460051b8301019501935f915b8483106104255750505050505090565b9091929394958480610461837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc086600196030187528a516101b5565b9801930193019194939290610415565b346101625760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101625767ffffffffffffffff600435818111610162576104c1903690600401610184565b91602435908111610162576104da903690600401610184565b906104e484610eee565b936104f26040519586610d21565b8085527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061051f82610eee565b015f5b8181106105805750505f5b818110610542576040518061032288826103f0565b806105648585610555600195878b610f99565b3561055f81610166565b610fc2565b61056e8289610fae565b526105798188610fae565b500161052d565b60209061058b610d9d565b82828a01015201610522565b60209060206040818301928281528551809452019301915f5b8281106105be575050505090565b835173ffffffffffffffffffffffffffffffffffffffff16855293810193928101926001016105b0565b346101625760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610162576103226106286024356004356113aa565b60405191829182610597565b346101625760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101625760206103e660043561067481610166565b60243561068081610166565b60443591611578565b34610162575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610162576040517f8897773800000000000000000000000000000000000000000000000000000000815260208160048173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa8015610762576020915f91610735575b50604051908152f35b6107559150823d841161075b575b61074d8183610d21565b8101906112c7565b5f61072c565b503d610743565b610ee3565b805173ffffffffffffffffffffffffffffffffffffffff16825261081d91906020818101516bffffffffffffffffffffffff16908301526040818101516bffffffffffffffffffffffff169083015260608181015165ffffffffffff169083015260808181015161ffff169083015260a08181015161ffff169083015260c08181015161ffff169083015260e08181015161ffff16908301526101008181015161ffff1690830152610120908101511515910152565b565b5f915b6002831061082f57505050565b6001906060835191825192600c93840b825260209384820151900b8483015265ffffffffffff604080920151169082015201920192019190610822565b906101c060e061081d9365ffffffffffff604082516cffffffffffffffffffffffffff81511687526020810151600c0b602088015201511660408501526108c8602082015160608601906bffffffffffffffffffffffff169052565b60408101516bffffffffffffffffffffffff1660808501526108f2606082015160a086019061081f565b60808101516fffffffffffffffffffffffffffffffff1661016085015260a08101516fffffffffffffffffffffffffffffffff1661018085015260c08101516fffffffffffffffffffffffffffffffff166101a085015201516fffffffffffffffffffffffffffffffff16910152565b906103e061010061081d9361098d84825173ffffffffffffffffffffffffffffffffffffffff169052565b61099f60208201516020860190610767565b6109b2604082015161016086019061086c565b6060810151610340850152608081015161036085015260a081015161038085015260c08101516103a085015260e081015115156103c08501520151910190608080918051151584526020810151602085015260408101516040850152606081015160608501520151910152565b346101625760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016257610a5c602435600435611817565b604051602091602082016020835281518091526020604084019201935f5b828110610a875784840385f35b9091928261048082610a9c6001948a51610962565b01960191019492919094610a7a565b34610162575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101625760206040515f8152f35b346101625760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016257610480610b29600435610b2481610166565b611bc7565b610b366040518092610962565bf35b34610162575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016257602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346101625760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016257610322610628602435610be781610166565b60643590604435906004356120a4565b346101625760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016257610322610628600435610c3881610166565b6044359060243590612273565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6060810190811067ffffffffffffffff821117610c8e57604052565b610c45565b60a0810190811067ffffffffffffffff821117610c8e57604052565b610120810190811067ffffffffffffffff821117610c8e57604052565b610140810190811067ffffffffffffffff821117610c8e57604052565b6040810190811067ffffffffffffffff821117610c8e57604052565b6020810190811067ffffffffffffffff821117610c8e57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610c8e57604052565b6040519061081d82610ccc565b60405190610100820182811067ffffffffffffffff821117610c8e57604052565b6040519061081d82610caf565b604051906080820182811067ffffffffffffffff821117610c8e57604052606080835f81525f60208201525f60408201520152565b519061081d82610166565b51906bffffffffffffffffffffffff8216820361016257565b519065ffffffffffff8216820361016257565b519061ffff8216820361016257565b5190811515820361016257565b908161014091031261016257610e39610d62565b90610e4381610dd2565b8252610e5160208201610ddd565b6020830152610e6260408201610ddd565b6040830152610e7360608201610df6565b6060830152610e8460808201610e09565b6080830152610e9560a08201610e09565b60a0830152610ea660c08201610e09565b60c0830152610eb760e08201610e09565b60e0830152610100610eca818301610e09565b90830152610edc610120809201610e18565b9082015290565b6040513d5f823e3d90fd5b67ffffffffffffffff8111610c8e5760051b60200190565b90610f1082610eee565b610f1d6040519182610d21565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0610f4b8294610eee565b01905f5b828110610f5b57505050565b806060602080938501015201610f4f565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b9190811015610fa95760051b0190565b610f6c565b8051821015610fa95760209160051b010190565b9091610fcc610d9d565b6040517f280be2f500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff841660048201529093906101409081818060248101038173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa9081156107625761109e925f926111a1575b505073ffffffffffffffffffffffffffffffffffffffff851686525173ffffffffffffffffffffffffffffffffffffffff1690565b916110ee6110d16020956110cc6020890196879073ffffffffffffffffffffffffffffffffffffffff169052565b61245e565b73ffffffffffffffffffffffffffffffffffffffff166040870152565b61112d61112961112361110084610f06565b9560609660608a01525173ffffffffffffffffffffffffffffffffffffffff1690565b3b151590565b1590565b61119a575f5b81811061114257505050505090565b8061119360019261117e61116c898b015173ffffffffffffffffffffffffffffffffffffffff1690565b61117783888a610f99565b3590612502565b878a01519061118d8383610fae565b52610fae565b5001611133565b5050505090565b6111c09250803d106111c7575b6111b88183610d21565b810190610e25565b5f80611069565b503d6111ae565b9073ffffffffffffffffffffffffffffffffffffffff9182604051917f280be2f5000000000000000000000000000000000000000000000000000000008352166004820152610140908181602481877f0000000000000000000000000000000000000000000000000000000000000000165afa9081156107625784925f92611271575b50505116918116821461126a57611267916125f2565b90565b5050600190565b6112879250803d106111c7576111b88183610d21565b5f80611251565b6040519061129b82610ce9565b600682527f5f6c696d697400000000000000000000000000000000000000000000000000006020830152565b90816020910312610162575190565b6040516112e281610d05565b5f8152905f368137565b906112f682610eee565b6113036040519182610d21565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06113318294610eee565b0190602036910137565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b906001820180921161137657565b61133b565b9190820180921161137657565b9190820391821161137657565b90816020910312610162575161126781610166565b916113bc6113b661128e565b83612747565b73ffffffffffffffffffffffffffffffffffffffff807f000000000000000000000000000000000000000000000000000000000000000016604093604051947f889777380000000000000000000000000000000000000000000000000000000086526020916004968381600481885afa908115610762575f9161155b575b508089101561154a57808991611450848461137b565b1115611542576114609250611388565b925b61146b846112ec565b965f5b8581106114815750505050505050909150565b80856114986114936114ce948e61137b565b611368565b8651809481927f6bdce1b60000000000000000000000000000000000000000000000000000000083528783019190602083019252565b0381875afa8015610762578861150f916001945f91611515575b50166114f4838d610fae565b9073ffffffffffffffffffffffffffffffffffffffff169052565b0161146e565b6115359150893d8b1161153b575b61152d8183610d21565b810190611395565b5f6114e8565b503d611523565b505092611462565b5050505050509150506112676112d6565b6115729150843d861161075b5761074d8183610d21565b5f61143a565b6040517f280be2f500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529192916101409081816024817f000000000000000000000000000000000000000000000000000000000000000087165afa918215610762575f92611615575b5050511691821561160e57611267926126c2565b5050505f90565b61162b9250803d106111c7576111b88183610d21565b5f806115fa565b6040519061163f82610c72565b5f6040838281528260208201520152565b604090604051610100810181811067ffffffffffffffff821117610c8e57604052809261167b611632565b825260205f60208401525f60408401526040519161169883610ce9565b5f5b8181106116c4575050508160e09160605f9401528260808201528260a08201528260c08201520152565b82906116ce611632565b818601520161169a565b604051906116e582610c93565b5f6080838281528260208201528260408201528260608201520152565b6040519061170f82610caf565b815f815260405161171f81610ccc565b5f81525f60208201525f60408201525f60608201525f60808201525f60a08201525f60c08201525f60e0820152610100905f828201525f6101208201526020830152611769611650565b60408301525f60608301525f60808301525f60a08301525f60c08301525f60e08301526117946116d8565b910152565b6040516117a581610d05565b5f815290565b906117b582610eee565b6117c26040519182610d21565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06117f08294610eee565b01905f5b82811061180057505050565b60209061180b611702565b828285010152016117f4565b919061182a61182461128e565b82612747565b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660408051917f889777380000000000000000000000000000000000000000000000000000000083526020936004938581600481865afa908115610762575f916119a2575b5080881015611992578088916118bb848461137b565b111561198a576118cb9250611388565b925b6118d6846117ab565b965f5b8581106118e95750505050505050565b80876118fb611493611931948661137b565b8751809481927f6bdce1b60000000000000000000000000000000000000000000000000000000083528883019190602083019252565b0381885afa91821561076257600192611951915f9161196d575b50611bc7565b61195b828c610fae565b52611966818b610fae565b50016118d9565b61198491508a3d8c1161153b5761152d8183610d21565b5f61194b565b5050926118cd565b5050505050509050611267611799565b6119b99150863d881161075b5761074d8183610d21565b5f6118a5565b519081600c0b820361016257565b9190826060910312610162576040516119e581610c72565b80928051906cffffffffffffffffffffffffff821682036101625760406117949181938552611a16602082016119bf565b602086015201610df6565b9080601f830112156101625760409160405192611a3d84610ce9565b839260c083019281841161016257935b838510611a5c57505050505090565b606085830312610162578251606091611a7482610c72565b611a7d876119bf565b8252602091611a8d8389016119bf565b83820152611a9c868901610df6565b86820152815201940193611a4d565b51906fffffffffffffffffffffffffffffffff8216820361016257565b6101e081830312610162576101c0611b5e91611b1d611ae5610d6f565b94611af081846119cd565b8652611afe60608401610ddd565b6020870152611b0f60808401610ddd565b604087015260a08301611a21565b6060850152611b2f6101608201611aab565b6080850152611b416101808201611aab565b60a0850152611b536101a08201611aab565b60c085015201611aab565b60e082015290565b908160a091031261016257608060405191611b8083610c93565b611b8981610e18565b83526020810151602084015260408101516040840152606081015160608401520151608082015290565b908160209103126101625761126790610e18565b611bcf611702565b5073ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000000000000000000000000000000000000000000000166040918251907f8b21f17000000000000000000000000000000000000000000000000000000000825260209160049183828481885afa918215610762575f92612085575b508551917f280be2f500000000000000000000000000000000000000000000000000000000835261014092838180611ca38c89830191909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b03818a5afa938415610762575f94612066575b505086517fe8ddced00000000000000000000000000000000000000000000000000000000081526101e097888280611d0d8d8a830191909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b03818b5afa91821561076257611dc5995f93612037575b5050611d4c611d47865173ffffffffffffffffffffffffffffffffffffffff1690565b6127c6565b94611d73611d6e825173ffffffffffffffffffffffffffffffffffffffff1690565b612848565b9660a08c8451809d81927fa64e320c00000000000000000000000000000000000000000000000000000000835285830191909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b0381897f0000000000000000000000000000000000000000000000000000000000000000165afa9a8b15610762575f9b612006575b5082517f30b0680b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8d168282019081528a908290819003602001818e5afa958615610762578a915f97611fe3575b50611ec090611e78868801516bffffffffffffffffffffffff1690565b8651998a809481937f0103349c0000000000000000000000000000000000000000000000000000000083528883019190916bffffffffffffffffffffffff6020820193169052565b0392165afa95861561076257611f2b9a8a928e925f99611fc4575b508551809d819482937f4a2264ef000000000000000000000000000000000000000000000000000000008452830191909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b03915afa97881561076257611f8c995f99611f95575b50611f69611f4d610d90565b73ffffffffffffffffffffffffffffffffffffffff909d168d52565b8b01528901526060880152608087015260a086015260c0850152151560e0840152565b61010082015290565b81611fb6929a503d8b11611fbd575b611fae8183610d21565b810190611bb3565b975f611f41565b503d611fa4565b611fdc919950843d861161075b5761074d8183610d21565b975f611edb565b611ec0919750611fff90833d851161075b5761074d8183610d21565b9690611e5b565b612029919b5060a03d60a011612030575b6120218183610d21565b810190611b66565b995f611dfa565b503d612017565b612057929350803d1061205f575b61204f8183610d21565b810190611ac8565b905f80611d24565b503d612045565b61207d929450803d106111c7576111b88183610d21565b915f80611cb6565b61209d919250843d861161153b5761152d8183610d21565b905f611c4c565b91909392936120ba6120b461128e565b86612747565b73ffffffffffffffffffffffffffffffffffffffff94857f00000000000000000000000000000000000000000000000000000000000000001693604096604051977f8897773800000000000000000000000000000000000000000000000000000000895260209360049985816004818c5afa908115610762575f91612256575b508088101561224357808891612150848461137b565b111561223b576121609250611388565b965b61216b886112ec565b996121775f998961137b565b975b88811061218d575050505050505050508252565b6121d08761219a83611368565b8651809381927f6bdce1b60000000000000000000000000000000000000000000000000000000083528783019190602083019252565b0381875afa80156107625786915f9161221e575b50168c6121f2888b84611578565b612201575b5050600101612179565b9a6122156001939c926114f4838695610fae565b0199908c6121f7565b6122359150893d8b1161153b5761152d8183610d21565b5f6121e4565b505096612162565b50505050505050505090506112676112d6565b61226d9150863d881161075b5761074d8183610d21565b5f61213a565b9291906122816113b661128e565b73ffffffffffffffffffffffffffffffffffffffff93847f00000000000000000000000000000000000000000000000000000000000000001692604095604051967f8897773800000000000000000000000000000000000000000000000000000000885260209260049884816004818b5afa908115610762575f916123e4575b50808710156123d257808791612317848461137b565b11156123ca576123279250611388565b955b612332876112ec565b9861233e5f988861137b565b965b8781106123535750505050505050508252565b6123608661219a83611368565b0381875afa80156107625786915f916123ad575b50168b61238189836111ce565b612390575b5050600101612340565b996123a46001939b926114f4838695610fae565b0198908b612386565b6123c49150883d8a1161153b5761152d8183610d21565b5f612374565b505095612329565b505050505050505090506112676112d6565b6123fb9150853d871161075b5761074d8183610d21565b5f612301565b3d15612459573d9067ffffffffffffffff8211610c8e576040519161244e60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160184610d21565b82523d5f602084013e565b606090565b905f91803b61246a5750565b5f809160405160208101907f023c5b08000000000000000000000000000000000000000000000000000000008252600481526124a581610ce9565b51915afa6124b1612401565b90806124f6575b6124bf5750565b90915060208180518101031261016257602073ffffffffffffffffffffffffffffffffffffffff9101516124f281610166565b1690565b506020815110156124b8565b91905f8060609460405193602094858101917fa3246ad300000000000000000000000000000000000000000000000000000000835260248201526024815261254981610c72565b51915afa90612556612401565b9161255f575050565b908092935051820191818181850194031261016257818101519067ffffffffffffffff821161016257019180603f8401121561016257818301516125a281610eee565b936125b06040519586610d21565b81855260408486019260051b82010192831161016257604001905b8282106125d9575050505090565b83809183516125e781610166565b8152019101906125cb565b90813b156126bc576040519173ffffffffffffffffffffffffffffffffffffffff60208401927f91d148540000000000000000000000000000000000000000000000000000000084525f602486015216604484015260448352608083019183831067ffffffffffffffff841117610c8e575f93849360405251915afa612676612401565b8161268b575b501561268757600190565b5f90565b7fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6915060208151910120145f61267c565b50505f90565b919091803b1561160e5773ffffffffffffffffffffffffffffffffffffffff6040519360208501937f91d14854000000000000000000000000000000000000000000000000000000008552602486015216604484015260448352608083019183831067ffffffffffffffff841117610c8e575f93849360405251915afa612676612401565b1561274f5750565b60446020917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f6040519485937f56e428930000000000000000000000000000000000000000000000000000000085528160048601528051918291826024880152018686015e5f85828601015201168101030190fd5b905f91803b6127d25750565b5f809160405160208101907f978bbdb90000000000000000000000000000000000000000000000000000000082526004815261280d81610ce9565b51915afa612819612401565b908061283c575b6128275750565b611267919250602080825183010191016112c7565b50602081511015612820565b905f91803b6128545750565b5f809160405160208101907f0e5f5dbd0000000000000000000000000000000000000000000000000000000082526004815261280d81610ce956fea26469706673582212208d78d869b573dbf80c2b186f7bfcc74dee4fd33f683a51152bdb08302f28ae1564736f6c63430008190033000000000000000000000000c1d0b3de6792bf6b4b37eccdcc24e45978cfd2eb

Deployed Bytecode

0x60806040526004361015610011575f80fd5b5f3560e01c8063196b9e06146100f45780632af23703146100ef578063535f6dbe146100ea57806357b14864146100e55780635ee77854146100e05780636eea4183146100db57806382a503fa146100d657806388977738146100d157806399abe483146100cc578063a217fddf146100c7578063d72ff79a146100c2578063dbba4b48146100bd578063df572f0d146100b85763f041b7d3146100b3575f80fd5b610bf7565b610ba6565b610b38565b610ae3565b610aab565b610a1f565b610689565b610634565b6105e8565b610471565b610394565b610326565b6102a6565b34610162575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016257602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001d201be093d847f6446530efb0e8fb426d176709168152f35b5f80fd5b73ffffffffffffffffffffffffffffffffffffffff81160361016257565b9181601f840112156101625782359167ffffffffffffffff8311610162576020808501948460051b01011161016257565b906080810173ffffffffffffffffffffffffffffffffffffffff9081845116835260606020948386820151168686015283604082015116604086015201519360806060850152845180925260a08401928160a08460051b8701019601935f915b848310610226575050505050505090565b909192877fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6088839798999b9a030184528751908280835192838152019201905f905b80821061028b575050509080600192980193019301919493929097959697610215565b91938060019294878751168152019401920187939291610268565b346101625760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610162576004356102e181610166565b60243567ffffffffffffffff8111610162576103229161030861030e923690600401610184565b91610fc2565b6040519182916020835260208301906101b5565b0390f35b34610162575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016257602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000005db427080200c235f2ae8cd17a7be87921f7ad6c168152f35b346101625760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101625760206103e66004356103d481610166565b602435906103e182610166565b6111ce565b6040519015158152f35b6020808201906020835283518092526040830192602060408460051b8301019501935f915b8483106104255750505050505090565b9091929394958480610461837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc086600196030187528a516101b5565b9801930193019194939290610415565b346101625760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101625767ffffffffffffffff600435818111610162576104c1903690600401610184565b91602435908111610162576104da903690600401610184565b906104e484610eee565b936104f26040519586610d21565b8085527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061051f82610eee565b015f5b8181106105805750505f5b818110610542576040518061032288826103f0565b806105648585610555600195878b610f99565b3561055f81610166565b610fc2565b61056e8289610fae565b526105798188610fae565b500161052d565b60209061058b610d9d565b82828a01015201610522565b60209060206040818301928281528551809452019301915f5b8281106105be575050505090565b835173ffffffffffffffffffffffffffffffffffffffff16855293810193928101926001016105b0565b346101625760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610162576103226106286024356004356113aa565b60405191829182610597565b346101625760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101625760206103e660043561067481610166565b60243561068081610166565b60443591611578565b34610162575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610162576040517f8897773800000000000000000000000000000000000000000000000000000000815260208160048173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001d201be093d847f6446530efb0e8fb426d176709165afa8015610762576020915f91610735575b50604051908152f35b6107559150823d841161075b575b61074d8183610d21565b8101906112c7565b5f61072c565b503d610743565b610ee3565b805173ffffffffffffffffffffffffffffffffffffffff16825261081d91906020818101516bffffffffffffffffffffffff16908301526040818101516bffffffffffffffffffffffff169083015260608181015165ffffffffffff169083015260808181015161ffff169083015260a08181015161ffff169083015260c08181015161ffff169083015260e08181015161ffff16908301526101008181015161ffff1690830152610120908101511515910152565b565b5f915b6002831061082f57505050565b6001906060835191825192600c93840b825260209384820151900b8483015265ffffffffffff604080920151169082015201920192019190610822565b906101c060e061081d9365ffffffffffff604082516cffffffffffffffffffffffffff81511687526020810151600c0b602088015201511660408501526108c8602082015160608601906bffffffffffffffffffffffff169052565b60408101516bffffffffffffffffffffffff1660808501526108f2606082015160a086019061081f565b60808101516fffffffffffffffffffffffffffffffff1661016085015260a08101516fffffffffffffffffffffffffffffffff1661018085015260c08101516fffffffffffffffffffffffffffffffff166101a085015201516fffffffffffffffffffffffffffffffff16910152565b906103e061010061081d9361098d84825173ffffffffffffffffffffffffffffffffffffffff169052565b61099f60208201516020860190610767565b6109b2604082015161016086019061086c565b6060810151610340850152608081015161036085015260a081015161038085015260c08101516103a085015260e081015115156103c08501520151910190608080918051151584526020810151602085015260408101516040850152606081015160608501520151910152565b346101625760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016257610a5c602435600435611817565b604051602091602082016020835281518091526020604084019201935f5b828110610a875784840385f35b9091928261048082610a9c6001948a51610962565b01960191019492919094610a7a565b34610162575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101625760206040515f8152f35b346101625760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016257610480610b29600435610b2481610166565b611bc7565b610b366040518092610962565bf35b34610162575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016257602060405173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c1d0b3de6792bf6b4b37eccdcc24e45978cfd2eb168152f35b346101625760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016257610322610628602435610be781610166565b60643590604435906004356120a4565b346101625760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016257610322610628600435610c3881610166565b6044359060243590612273565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6060810190811067ffffffffffffffff821117610c8e57604052565b610c45565b60a0810190811067ffffffffffffffff821117610c8e57604052565b610120810190811067ffffffffffffffff821117610c8e57604052565b610140810190811067ffffffffffffffff821117610c8e57604052565b6040810190811067ffffffffffffffff821117610c8e57604052565b6020810190811067ffffffffffffffff821117610c8e57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610c8e57604052565b6040519061081d82610ccc565b60405190610100820182811067ffffffffffffffff821117610c8e57604052565b6040519061081d82610caf565b604051906080820182811067ffffffffffffffff821117610c8e57604052606080835f81525f60208201525f60408201520152565b519061081d82610166565b51906bffffffffffffffffffffffff8216820361016257565b519065ffffffffffff8216820361016257565b519061ffff8216820361016257565b5190811515820361016257565b908161014091031261016257610e39610d62565b90610e4381610dd2565b8252610e5160208201610ddd565b6020830152610e6260408201610ddd565b6040830152610e7360608201610df6565b6060830152610e8460808201610e09565b6080830152610e9560a08201610e09565b60a0830152610ea660c08201610e09565b60c0830152610eb760e08201610e09565b60e0830152610100610eca818301610e09565b90830152610edc610120809201610e18565b9082015290565b6040513d5f823e3d90fd5b67ffffffffffffffff8111610c8e5760051b60200190565b90610f1082610eee565b610f1d6040519182610d21565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0610f4b8294610eee565b01905f5b828110610f5b57505050565b806060602080938501015201610f4f565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b9190811015610fa95760051b0190565b610f6c565b8051821015610fa95760209160051b010190565b9091610fcc610d9d565b6040517f280be2f500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff841660048201529093906101409081818060248101038173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001d201be093d847f6446530efb0e8fb426d176709165afa9081156107625761109e925f926111a1575b505073ffffffffffffffffffffffffffffffffffffffff851686525173ffffffffffffffffffffffffffffffffffffffff1690565b916110ee6110d16020956110cc6020890196879073ffffffffffffffffffffffffffffffffffffffff169052565b61245e565b73ffffffffffffffffffffffffffffffffffffffff166040870152565b61112d61112961112361110084610f06565b9560609660608a01525173ffffffffffffffffffffffffffffffffffffffff1690565b3b151590565b1590565b61119a575f5b81811061114257505050505090565b8061119360019261117e61116c898b015173ffffffffffffffffffffffffffffffffffffffff1690565b61117783888a610f99565b3590612502565b878a01519061118d8383610fae565b52610fae565b5001611133565b5050505090565b6111c09250803d106111c7575b6111b88183610d21565b810190610e25565b5f80611069565b503d6111ae565b9073ffffffffffffffffffffffffffffffffffffffff9182604051917f280be2f5000000000000000000000000000000000000000000000000000000008352166004820152610140908181602481877f0000000000000000000000001d201be093d847f6446530efb0e8fb426d176709165afa9081156107625784925f92611271575b50505116918116821461126a57611267916125f2565b90565b5050600190565b6112879250803d106111c7576111b88183610d21565b5f80611251565b6040519061129b82610ce9565b600682527f5f6c696d697400000000000000000000000000000000000000000000000000006020830152565b90816020910312610162575190565b6040516112e281610d05565b5f8152905f368137565b906112f682610eee565b6113036040519182610d21565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06113318294610eee565b0190602036910137565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b906001820180921161137657565b61133b565b9190820180921161137657565b9190820391821161137657565b90816020910312610162575161126781610166565b916113bc6113b661128e565b83612747565b73ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000001d201be093d847f6446530efb0e8fb426d17670916604093604051947f889777380000000000000000000000000000000000000000000000000000000086526020916004968381600481885afa908115610762575f9161155b575b508089101561154a57808991611450848461137b565b1115611542576114609250611388565b925b61146b846112ec565b965f5b8581106114815750505050505050909150565b80856114986114936114ce948e61137b565b611368565b8651809481927f6bdce1b60000000000000000000000000000000000000000000000000000000083528783019190602083019252565b0381875afa8015610762578861150f916001945f91611515575b50166114f4838d610fae565b9073ffffffffffffffffffffffffffffffffffffffff169052565b0161146e565b6115359150893d8b1161153b575b61152d8183610d21565b810190611395565b5f6114e8565b503d611523565b505092611462565b5050505050509150506112676112d6565b6115729150843d861161075b5761074d8183610d21565b5f61143a565b6040517f280be2f500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529192916101409081816024817f0000000000000000000000001d201be093d847f6446530efb0e8fb426d17670987165afa918215610762575f92611615575b5050511691821561160e57611267926126c2565b5050505f90565b61162b9250803d106111c7576111b88183610d21565b5f806115fa565b6040519061163f82610c72565b5f6040838281528260208201520152565b604090604051610100810181811067ffffffffffffffff821117610c8e57604052809261167b611632565b825260205f60208401525f60408401526040519161169883610ce9565b5f5b8181106116c4575050508160e09160605f9401528260808201528260a08201528260c08201520152565b82906116ce611632565b818601520161169a565b604051906116e582610c93565b5f6080838281528260208201528260408201528260608201520152565b6040519061170f82610caf565b815f815260405161171f81610ccc565b5f81525f60208201525f60408201525f60608201525f60808201525f60a08201525f60c08201525f60e0820152610100905f828201525f6101208201526020830152611769611650565b60408301525f60608301525f60808301525f60a08301525f60c08301525f60e08301526117946116d8565b910152565b6040516117a581610d05565b5f815290565b906117b582610eee565b6117c26040519182610d21565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06117f08294610eee565b01905f5b82811061180057505050565b60209061180b611702565b828285010152016117f4565b919061182a61182461128e565b82612747565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001d201be093d847f6446530efb0e8fb426d1767091660408051917f889777380000000000000000000000000000000000000000000000000000000083526020936004938581600481865afa908115610762575f916119a2575b5080881015611992578088916118bb848461137b565b111561198a576118cb9250611388565b925b6118d6846117ab565b965f5b8581106118e95750505050505050565b80876118fb611493611931948661137b565b8751809481927f6bdce1b60000000000000000000000000000000000000000000000000000000083528883019190602083019252565b0381885afa91821561076257600192611951915f9161196d575b50611bc7565b61195b828c610fae565b52611966818b610fae565b50016118d9565b61198491508a3d8c1161153b5761152d8183610d21565b5f61194b565b5050926118cd565b5050505050509050611267611799565b6119b99150863d881161075b5761074d8183610d21565b5f6118a5565b519081600c0b820361016257565b9190826060910312610162576040516119e581610c72565b80928051906cffffffffffffffffffffffffff821682036101625760406117949181938552611a16602082016119bf565b602086015201610df6565b9080601f830112156101625760409160405192611a3d84610ce9565b839260c083019281841161016257935b838510611a5c57505050505090565b606085830312610162578251606091611a7482610c72565b611a7d876119bf565b8252602091611a8d8389016119bf565b83820152611a9c868901610df6565b86820152815201940193611a4d565b51906fffffffffffffffffffffffffffffffff8216820361016257565b6101e081830312610162576101c0611b5e91611b1d611ae5610d6f565b94611af081846119cd565b8652611afe60608401610ddd565b6020870152611b0f60808401610ddd565b604087015260a08301611a21565b6060850152611b2f6101608201611aab565b6080850152611b416101808201611aab565b60a0850152611b536101a08201611aab565b60c085015201611aab565b60e082015290565b908160a091031261016257608060405191611b8083610c93565b611b8981610e18565b83526020810151602084015260408101516040840152606081015160608401520151608082015290565b908160209103126101625761126790610e18565b611bcf611702565b5073ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000001d201be093d847f6446530efb0e8fb426d176709166040918251907f8b21f17000000000000000000000000000000000000000000000000000000000825260209160049183828481885afa918215610762575f92612085575b508551917f280be2f500000000000000000000000000000000000000000000000000000000835261014092838180611ca38c89830191909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b03818a5afa938415610762575f94612066575b505086517fe8ddced00000000000000000000000000000000000000000000000000000000081526101e097888280611d0d8d8a830191909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b03818b5afa91821561076257611dc5995f93612037575b5050611d4c611d47865173ffffffffffffffffffffffffffffffffffffffff1690565b6127c6565b94611d73611d6e825173ffffffffffffffffffffffffffffffffffffffff1690565b612848565b9660a08c8451809d81927fa64e320c00000000000000000000000000000000000000000000000000000000835285830191909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b0381897f0000000000000000000000005db427080200c235f2ae8cd17a7be87921f7ad6c165afa9a8b15610762575f9b612006575b5082517f30b0680b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8d168282019081528a908290819003602001818e5afa958615610762578a915f97611fe3575b50611ec090611e78868801516bffffffffffffffffffffffff1690565b8651998a809481937f0103349c0000000000000000000000000000000000000000000000000000000083528883019190916bffffffffffffffffffffffff6020820193169052565b0392165afa95861561076257611f2b9a8a928e925f99611fc4575b508551809d819482937f4a2264ef000000000000000000000000000000000000000000000000000000008452830191909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b03915afa97881561076257611f8c995f99611f95575b50611f69611f4d610d90565b73ffffffffffffffffffffffffffffffffffffffff909d168d52565b8b01528901526060880152608087015260a086015260c0850152151560e0840152565b61010082015290565b81611fb6929a503d8b11611fbd575b611fae8183610d21565b810190611bb3565b975f611f41565b503d611fa4565b611fdc919950843d861161075b5761074d8183610d21565b975f611edb565b611ec0919750611fff90833d851161075b5761074d8183610d21565b9690611e5b565b612029919b5060a03d60a011612030575b6120218183610d21565b810190611b66565b995f611dfa565b503d612017565b612057929350803d1061205f575b61204f8183610d21565b810190611ac8565b905f80611d24565b503d612045565b61207d929450803d106111c7576111b88183610d21565b915f80611cb6565b61209d919250843d861161153b5761152d8183610d21565b905f611c4c565b91909392936120ba6120b461128e565b86612747565b73ffffffffffffffffffffffffffffffffffffffff94857f0000000000000000000000001d201be093d847f6446530efb0e8fb426d1767091693604096604051977f8897773800000000000000000000000000000000000000000000000000000000895260209360049985816004818c5afa908115610762575f91612256575b508088101561224357808891612150848461137b565b111561223b576121609250611388565b965b61216b886112ec565b996121775f998961137b565b975b88811061218d575050505050505050508252565b6121d08761219a83611368565b8651809381927f6bdce1b60000000000000000000000000000000000000000000000000000000083528783019190602083019252565b0381875afa80156107625786915f9161221e575b50168c6121f2888b84611578565b612201575b5050600101612179565b9a6122156001939c926114f4838695610fae565b0199908c6121f7565b6122359150893d8b1161153b5761152d8183610d21565b5f6121e4565b505096612162565b50505050505050505090506112676112d6565b61226d9150863d881161075b5761074d8183610d21565b5f61213a565b9291906122816113b661128e565b73ffffffffffffffffffffffffffffffffffffffff93847f0000000000000000000000001d201be093d847f6446530efb0e8fb426d1767091692604095604051967f8897773800000000000000000000000000000000000000000000000000000000885260209260049884816004818b5afa908115610762575f916123e4575b50808710156123d257808791612317848461137b565b11156123ca576123279250611388565b955b612332876112ec565b9861233e5f988861137b565b965b8781106123535750505050505050508252565b6123608661219a83611368565b0381875afa80156107625786915f916123ad575b50168b61238189836111ce565b612390575b5050600101612340565b996123a46001939b926114f4838695610fae565b0198908b612386565b6123c49150883d8a1161153b5761152d8183610d21565b5f612374565b505095612329565b505050505050505090506112676112d6565b6123fb9150853d871161075b5761074d8183610d21565b5f612301565b3d15612459573d9067ffffffffffffffff8211610c8e576040519161244e60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160184610d21565b82523d5f602084013e565b606090565b905f91803b61246a5750565b5f809160405160208101907f023c5b08000000000000000000000000000000000000000000000000000000008252600481526124a581610ce9565b51915afa6124b1612401565b90806124f6575b6124bf5750565b90915060208180518101031261016257602073ffffffffffffffffffffffffffffffffffffffff9101516124f281610166565b1690565b506020815110156124b8565b91905f8060609460405193602094858101917fa3246ad300000000000000000000000000000000000000000000000000000000835260248201526024815261254981610c72565b51915afa90612556612401565b9161255f575050565b908092935051820191818181850194031261016257818101519067ffffffffffffffff821161016257019180603f8401121561016257818301516125a281610eee565b936125b06040519586610d21565b81855260408486019260051b82010192831161016257604001905b8282106125d9575050505090565b83809183516125e781610166565b8152019101906125cb565b90813b156126bc576040519173ffffffffffffffffffffffffffffffffffffffff60208401927f91d148540000000000000000000000000000000000000000000000000000000084525f602486015216604484015260448352608083019183831067ffffffffffffffff841117610c8e575f93849360405251915afa612676612401565b8161268b575b501561268757600190565b5f90565b7fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6915060208151910120145f61267c565b50505f90565b919091803b1561160e5773ffffffffffffffffffffffffffffffffffffffff6040519360208501937f91d14854000000000000000000000000000000000000000000000000000000008552602486015216604484015260448352608083019183831067ffffffffffffffff841117610c8e575f93849360405251915afa612676612401565b1561274f5750565b60446020917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f6040519485937f56e428930000000000000000000000000000000000000000000000000000000085528160048601528051918291826024880152018686015e5f85828601015201168101030190fd5b905f91803b6127d25750565b5f809160405160208101907f978bbdb90000000000000000000000000000000000000000000000000000000082526004815261280d81610ce9565b51915afa612819612401565b908061283c575b6128275750565b611267919250602080825183010191016112c7565b50602081511015612820565b905f91803b6128545750565b5f809160405160208101907f0e5f5dbd0000000000000000000000000000000000000000000000000000000082526004815261280d81610ce956fea26469706673582212208d78d869b573dbf80c2b186f7bfcc74dee4fd33f683a51152bdb08302f28ae1564736f6c63430008190033

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

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.