ETH Price: $3,169.81 (-1.37%)

Transaction Decoder

Block:
20217304 at Jul-02-2024 07:21:11 AM +UTC
Transaction Fee:
0.00025000185461328 ETH $0.79
Gas Used:
41,514 Gas / 6.02210952 Gwei

Emitted Events:

155 TransparentUpgradeableProxy.0x3ee6fe8d54610244c3e9d3c066ae4aee997884aa28f10616ae821925401318ac( 0x3ee6fe8d54610244c3e9d3c066ae4aee997884aa28f10616ae821925401318ac, 0x0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000050, 0000000000000000000000000000000000000000000000000000000000002af8, 0000000000000000000000000000000000000000000000000000000000000064 )

Account State Difference:

  Address   Before After State Difference Code
0x0f5661B5...aff3FCe41
0.175905096045175491 Eth
Nonce: 105
0.175655094190562211 Eth
Nonce: 106
0.00025000185461328
2.392782488396460389 Eth2.392897837525714319 Eth0.00011534912925393
0x414696E4...666D63Bd4

Execution Trace

TransparentUpgradeableProxy.5b0b829f( )
  • RegistryCoordinator.setOperatorSetParams( quorumNumber=0, operatorSetParams=[{name:maxOperatorCount, type:uint32, order:1, indexed:false, value:80, valueString:80}, {name:kickBIPsOfOperatorStake, type:uint16, order:2, indexed:false, value:11000, valueString:11000}, {name:kickBIPsOfTotalStake, type:uint16, order:3, indexed:false, value:100, valueString:100}] )
    File 1 of 2: TransparentUpgradeableProxy
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (proxy/transparent/TransparentUpgradeableProxy.sol)
    
    pragma solidity ^0.8.0;
    
    // OpenZeppelin Contracts (last updated v4.7.0) (proxy/ERC1967/ERC1967Proxy.sol)
    
    // OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)
    
    /**
     * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
     * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
     * be specified by overriding the virtual {_implementation} function.
     *
     * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
     * different contract through the {_delegate} function.
     *
     * The success and return data of the delegated call will be returned back to the caller of the proxy.
     */
    abstract contract Proxy {
        /**
         * @dev Delegates the current call to `implementation`.
         *
         * This function does not return to its internal call site, it will return directly to the external caller.
         */
        function _delegate(address implementation) internal virtual {
            assembly {
                // Copy msg.data. We take full control of memory in this inline assembly
                // block because it will not return to Solidity code. We overwrite the
                // Solidity scratch pad at memory position 0.
                calldatacopy(0, 0, calldatasize())
    
                // Call the implementation.
                // out and outsize are 0 because we don't know the size yet.
                let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
    
                // Copy the returned data.
                returndatacopy(0, 0, returndatasize())
    
                switch result
                // delegatecall returns 0 on error.
                case 0 {
                    revert(0, returndatasize())
                }
                default {
                    return(0, returndatasize())
                }
            }
        }
    
        /**
         * @dev This is a virtual function that should be overridden so it returns the address to which the fallback function
         * and {_fallback} should delegate.
         */
        function _implementation() internal view virtual returns (address);
    
        /**
         * @dev Delegates the current call to the address returned by `_implementation()`.
         *
         * This function does not return to its internal call site, it will return directly to the external caller.
         */
        function _fallback() internal virtual {
            _beforeFallback();
            _delegate(_implementation());
        }
    
        /**
         * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
         * function in the contract matches the call data.
         */
        fallback() external payable virtual {
            _fallback();
        }
    
        /**
         * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
         * is empty.
         */
        receive() external payable virtual {
            _fallback();
        }
    
        /**
         * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
         * call, or as part of the Solidity `fallback` or `receive` functions.
         *
         * If overridden should call `super._beforeFallback()`.
         */
        function _beforeFallback() internal virtual {}
    }
    
    // OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol)
    
    // OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)
    
    /**
     * @dev This is the interface that {BeaconProxy} expects of its beacon.
     */
    interface IBeacon {
        /**
         * @dev Must return an address that can be used as a delegate call target.
         *
         * {BeaconProxy} will check that this address is a contract.
         */
        function implementation() external view returns (address);
    }
    
    // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol)
    
    /**
     * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
     *
     * _Available since v4.8.3._
     */
    interface IERC1967 {
        /**
         * @dev Emitted when the implementation is upgraded.
         */
        event Upgraded(address indexed implementation);
    
        /**
         * @dev Emitted when the admin account has changed.
         */
        event AdminChanged(address previousAdmin, address newAdmin);
    
        /**
         * @dev Emitted when the beacon is changed.
         */
        event BeaconUpgraded(address indexed beacon);
    }
    
    // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)
    
    /**
     * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
     * proxy whose upgrades are fully controlled by the current implementation.
     */
    interface IERC1822Proxiable {
        /**
         * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
         * address.
         *
         * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
         * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
         * function revert if invoked through a proxy.
         */
        function proxiableUUID() external view returns (bytes32);
    }
    
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
    
    /**
     * @dev Collection of functions related to the address type
     */
    library Address {
        /**
         * @dev Returns true if `account` is a contract.
         *
         * [IMPORTANT]
         * ====
         * It is unsafe to assume that an address for which this function returns
         * false is an externally-owned account (EOA) and not a contract.
         *
         * Among others, `isContract` will return false for the following
         * types of addresses:
         *
         *  - an externally-owned account
         *  - a contract in construction
         *  - an address where a contract will be created
         *  - an address where a contract lived, but was destroyed
         *
         * Furthermore, `isContract` will also return true if the target contract within
         * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
         * which only has an effect at the end of a transaction.
         * ====
         *
         * [IMPORTANT]
         * ====
         * You shouldn't rely on `isContract` to protect against flash loan attacks!
         *
         * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
         * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
         * constructor.
         * ====
         */
        function isContract(address account) internal view returns (bool) {
            // This method relies on extcodesize/address.code.length, which returns 0
            // for contracts in construction, since the code is only stored at the end
            // of the constructor execution.
    
            return account.code.length > 0;
        }
    
        /**
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
         * `recipient`, forwarding all available gas and reverting on errors.
         *
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
         * imposed by `transfer`, making them unable to receive funds via
         * `transfer`. {sendValue} removes this limitation.
         *
         * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
         *
         * IMPORTANT: because control is transferred to `recipient`, care must be
         * taken to not create reentrancy vulnerabilities. Consider using
         * {ReentrancyGuard} or the
         * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            require(address(this).balance >= amount, "Address: insufficient balance");
    
            (bool success, ) = recipient.call{value: amount}("");
            require(success, "Address: unable to send value, recipient may have reverted");
        }
    
        /**
         * @dev Performs a Solidity function call using a low level `call`. A
         * plain `call` is an unsafe replacement for a function call: use this
         * function instead.
         *
         * If `target` reverts with a revert reason, it is bubbled up by this
         * function (like regular Solidity function calls).
         *
         * Returns the raw returned data. To convert to the expected return value,
         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
         *
         * Requirements:
         *
         * - `target` must be a contract.
         * - calling `target` with `data` must not revert.
         *
         * _Available since v3.1._
         */
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, "Address: low-level call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
         * `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but also transferring `value` wei to `target`.
         *
         * Requirements:
         *
         * - the calling contract must have an ETH balance of at least `value`.
         * - the called Solidity function must be `payable`.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
         * with `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(
            address target,
            bytes memory data,
            uint256 value,
            string memory errorMessage
        ) internal returns (bytes memory) {
            require(address(this).balance >= value, "Address: insufficient balance for call");
            (bool success, bytes memory returndata) = target.call{value: value}(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            return functionStaticCall(target, data, "Address: low-level static call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            (bool success, bytes memory returndata) = target.staticcall(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
         * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
         *
         * _Available since v4.8._
         */
        function verifyCallResultFromTarget(
            address target,
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            if (success) {
                if (returndata.length == 0) {
                    // only check isContract if the call was successful and the return data is empty
                    // otherwise we already know that it was a contract
                    require(isContract(target), "Address: call to non-contract");
                }
                return returndata;
            } else {
                _revert(returndata, errorMessage);
            }
        }
    
        /**
         * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
         * revert reason or using the provided one.
         *
         * _Available since v4.3._
         */
        function verifyCallResult(
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal pure returns (bytes memory) {
            if (success) {
                return returndata;
            } else {
                _revert(returndata, errorMessage);
            }
        }
    
        function _revert(bytes memory returndata, string memory errorMessage) private pure {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly
                /// @solidity memory-safe-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
    
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
    // This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
    
    /**
     * @dev Library for reading and writing primitive types to specific storage slots.
     *
     * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
     * This library helps with reading and writing to such slots without the need for inline assembly.
     *
     * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
     *
     * Example usage to set ERC1967 implementation slot:
     * ```solidity
     * contract ERC1967 {
     *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
     *
     *     function _getImplementation() internal view returns (address) {
     *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
     *     }
     *
     *     function _setImplementation(address newImplementation) internal {
     *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
     *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
     *     }
     * }
     * ```
     *
     * _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
     * _Available since v4.9 for `string`, `bytes`._
     */
    library StorageSlot {
        struct AddressSlot {
            address value;
        }
    
        struct BooleanSlot {
            bool value;
        }
    
        struct Bytes32Slot {
            bytes32 value;
        }
    
        struct Uint256Slot {
            uint256 value;
        }
    
        struct StringSlot {
            string value;
        }
    
        struct BytesSlot {
            bytes value;
        }
    
        /**
         * @dev Returns an `AddressSlot` with member `value` located at `slot`.
         */
        function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
         */
        function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
         */
        function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
         */
        function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `StringSlot` with member `value` located at `slot`.
         */
        function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
         */
        function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := store.slot
            }
        }
    
        /**
         * @dev Returns an `BytesSlot` with member `value` located at `slot`.
         */
        function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
         */
        function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := store.slot
            }
        }
    }
    
    /**
     * @dev This abstract contract provides getters and event emitting update functions for
     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
     *
     * _Available since v4.1._
     */
    abstract contract ERC1967Upgrade is IERC1967 {
        // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
        bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
    
        /**
         * @dev Storage slot with the address of the current implementation.
         * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
         * validated in the constructor.
         */
        bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    
        /**
         * @dev Returns the current implementation address.
         */
        function _getImplementation() internal view returns (address) {
            return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
        }
    
        /**
         * @dev Stores a new address in the EIP1967 implementation slot.
         */
        function _setImplementation(address newImplementation) private {
            require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
            StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
        }
    
        /**
         * @dev Perform implementation upgrade
         *
         * Emits an {Upgraded} event.
         */
        function _upgradeTo(address newImplementation) internal {
            _setImplementation(newImplementation);
            emit Upgraded(newImplementation);
        }
    
        /**
         * @dev Perform implementation upgrade with additional setup call.
         *
         * Emits an {Upgraded} event.
         */
        function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
            _upgradeTo(newImplementation);
            if (data.length > 0 || forceCall) {
                Address.functionDelegateCall(newImplementation, data);
            }
        }
    
        /**
         * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
         *
         * Emits an {Upgraded} event.
         */
        function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {
            // Upgrades from old implementations will perform a rollback test. This test requires the new
            // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
            // this special case will break upgrade paths from old UUPS implementation to new ones.
            if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
                _setImplementation(newImplementation);
            } else {
                try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
                    require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
                } catch {
                    revert("ERC1967Upgrade: new implementation is not UUPS");
                }
                _upgradeToAndCall(newImplementation, data, forceCall);
            }
        }
    
        /**
         * @dev Storage slot with the admin of the contract.
         * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
         * validated in the constructor.
         */
        bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
    
        /**
         * @dev Returns the current admin.
         */
        function _getAdmin() internal view returns (address) {
            return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
        }
    
        /**
         * @dev Stores a new address in the EIP1967 admin slot.
         */
        function _setAdmin(address newAdmin) private {
            require(newAdmin != address(0), "ERC1967: new admin is the zero address");
            StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
        }
    
        /**
         * @dev Changes the admin of the proxy.
         *
         * Emits an {AdminChanged} event.
         */
        function _changeAdmin(address newAdmin) internal {
            emit AdminChanged(_getAdmin(), newAdmin);
            _setAdmin(newAdmin);
        }
    
        /**
         * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
         * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
         */
        bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
    
        /**
         * @dev Returns the current beacon.
         */
        function _getBeacon() internal view returns (address) {
            return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
        }
    
        /**
         * @dev Stores a new beacon in the EIP1967 beacon slot.
         */
        function _setBeacon(address newBeacon) private {
            require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract");
            require(
                Address.isContract(IBeacon(newBeacon).implementation()),
                "ERC1967: beacon implementation is not a contract"
            );
            StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
        }
    
        /**
         * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
         * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
         *
         * Emits a {BeaconUpgraded} event.
         */
        function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
            _setBeacon(newBeacon);
            emit BeaconUpgraded(newBeacon);
            if (data.length > 0 || forceCall) {
                Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
            }
        }
    }
    
    /**
     * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
     * implementation address that can be changed. This address is stored in storage in the location specified by
     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
     * implementation behind the proxy.
     */
    contract ERC1967Proxy is Proxy, ERC1967Upgrade {
        /**
         * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
         *
         * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
         * function call, and allows initializing the storage of the proxy like a Solidity constructor.
         */
        constructor(address _logic, bytes memory _data) payable {
            _upgradeToAndCall(_logic, _data, false);
        }
    
        /**
         * @dev Returns the current implementation address.
         */
        function _implementation() internal view virtual override returns (address impl) {
            return ERC1967Upgrade._getImplementation();
        }
    }
    
    /**
     * @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy}
     * does not implement this interface directly, and some of its functions are implemented by an internal dispatch
     * mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not
     * include them in the ABI so this interface must be used to interact with it.
     */
    interface ITransparentUpgradeableProxy is IERC1967 {
        function admin() external view returns (address);
    
        function implementation() external view returns (address);
    
        function changeAdmin(address) external;
    
        function upgradeTo(address) external;
    
        function upgradeToAndCall(address, bytes memory) external payable;
    }
    
    /**
     * @dev This contract implements a proxy that is upgradeable by an admin.
     *
     * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
     * clashing], which can potentially be used in an attack, this contract uses the
     * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
     * things that go hand in hand:
     *
     * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
     * that call matches one of the admin functions exposed by the proxy itself.
     * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
     * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
     * "admin cannot fallback to proxy target".
     *
     * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
     * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
     * to sudden errors when trying to call a function from the proxy implementation.
     *
     * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
     * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
     *
     * NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not
     * inherit from that interface, and instead the admin functions are implicitly implemented using a custom dispatch
     * mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to
     * fully implement transparency without decoding reverts caused by selector clashes between the proxy and the
     * implementation.
     *
     * WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the compiler
     * will not check that there are no selector conflicts, due to the note above. A selector clash between any new function
     * and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This could
     * render the admin operations inaccessible, which could prevent upgradeability. Transparency may also be compromised.
     */
    contract TransparentUpgradeableProxy is ERC1967Proxy {
        /**
         * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
         * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
         */
        constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
            _changeAdmin(admin_);
        }
    
        /**
         * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
         *
         * CAUTION: This modifier is deprecated, as it could cause issues if the modified function has arguments, and the
         * implementation provides a function with the same selector.
         */
        modifier ifAdmin() {
            if (msg.sender == _getAdmin()) {
                _;
            } else {
                _fallback();
            }
        }
    
        /**
         * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior
         */
        function _fallback() internal virtual override {
            if (msg.sender == _getAdmin()) {
                bytes memory ret;
                bytes4 selector = msg.sig;
                if (selector == ITransparentUpgradeableProxy.upgradeTo.selector) {
                    ret = _dispatchUpgradeTo();
                } else if (selector == ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
                    ret = _dispatchUpgradeToAndCall();
                } else if (selector == ITransparentUpgradeableProxy.changeAdmin.selector) {
                    ret = _dispatchChangeAdmin();
                } else if (selector == ITransparentUpgradeableProxy.admin.selector) {
                    ret = _dispatchAdmin();
                } else if (selector == ITransparentUpgradeableProxy.implementation.selector) {
                    ret = _dispatchImplementation();
                } else {
                    revert("TransparentUpgradeableProxy: admin cannot fallback to proxy target");
                }
                assembly {
                    return(add(ret, 0x20), mload(ret))
                }
            } else {
                super._fallback();
            }
        }
    
        /**
         * @dev Returns the current admin.
         *
         * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
         * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
         * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
         */
        function _dispatchAdmin() private returns (bytes memory) {
            _requireZeroValue();
    
            address admin = _getAdmin();
            return abi.encode(admin);
        }
    
        /**
         * @dev Returns the current implementation.
         *
         * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
         * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
         * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
         */
        function _dispatchImplementation() private returns (bytes memory) {
            _requireZeroValue();
    
            address implementation = _implementation();
            return abi.encode(implementation);
        }
    
        /**
         * @dev Changes the admin of the proxy.
         *
         * Emits an {AdminChanged} event.
         */
        function _dispatchChangeAdmin() private returns (bytes memory) {
            _requireZeroValue();
    
            address newAdmin = abi.decode(msg.data[4:], (address));
            _changeAdmin(newAdmin);
    
            return "";
        }
    
        /**
         * @dev Upgrade the implementation of the proxy.
         */
        function _dispatchUpgradeTo() private returns (bytes memory) {
            _requireZeroValue();
    
            address newImplementation = abi.decode(msg.data[4:], (address));
            _upgradeToAndCall(newImplementation, bytes(""), false);
    
            return "";
        }
    
        /**
         * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
         * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
         * proxied contract.
         */
        function _dispatchUpgradeToAndCall() private returns (bytes memory) {
            (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
            _upgradeToAndCall(newImplementation, data, true);
    
            return "";
        }
    
        /**
         * @dev Returns the current admin.
         *
         * CAUTION: This function is deprecated. Use {ERC1967Upgrade-_getAdmin} instead.
         */
        function _admin() internal view virtual returns (address) {
            return _getAdmin();
        }
    
        /**
         * @dev To keep this contract fully transparent, all `ifAdmin` functions must be payable. This helper is here to
         * emulate some proxy functions being non-payable while still allowing value to pass through.
         */
        function _requireZeroValue() private {
            require(msg.value == 0);
        }
    }

    File 2 of 2: RegistryCoordinator
    // SPDX-License-Identifier: BUSL-1.1
    pragma solidity =0.8.12;
    
    /**
     * @title Interface for the `PauserRegistry` contract.
     * @author Layr Labs, Inc.
     * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
     */
    interface IPauserRegistry {
        event PauserStatusChanged(address pauser, bool canPause);
    
        event UnpauserChanged(address previousUnpauser, address newUnpauser);
        
        /// @notice Mapping of addresses to whether they hold the pauser role.
        function isPauser(address pauser) external view returns (bool);
    
        /// @notice Unique address that holds the unpauser role. Capable of changing *both* the pauser and unpauser addresses.
        function unpauser() external view returns (address);
    }
    
    /**
     * @title The interface for common signature utilities.
     * @author Layr Labs, Inc.
     * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
     */
    interface ISignatureUtils {
        // @notice Struct that bundles together a signature and an expiration time for the signature. Used primarily for stack management.
        struct SignatureWithExpiry {
            // the signature itself, formatted as a single bytes object
            bytes signature;
            // the expiration timestamp (UTC) of the signature
            uint256 expiry;
        }
    
        // @notice Struct that bundles together a signature, a salt for uniqueness, and an expiration time for the signature. Used primarily for stack management.
        struct SignatureWithSaltAndExpiry {
            // the signature itself, formatted as a single bytes object
            bytes signature;
            // the salt used to generate the signature
            bytes32 salt;
            // the expiration timestamp (UTC) of the signature
            uint256 expiry;
        }
    }
    
    /**
     * @title Interface for an `ISocketUpdater` where operators can update their sockets.
     * @author Layr Labs, Inc.
     */
    interface ISocketUpdater {
        // EVENTS
    
        event OperatorSocketUpdate(bytes32 indexed operatorId, string socket);
    
        // FUNCTIONS
        
        /**
         * @notice Updates the socket of the msg.sender given they are a registered operator
         * @param socket is the new socket of the operator
         */
        function updateSocket(string memory socket) external;
    }
    
    /**
     * @title Minimal interface for a `Registry`-type contract.
     * @author Layr Labs, Inc.
     * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
     * @notice Functions related to the registration process itself have been intentionally excluded
     * because their function signatures may vary significantly.
     */
    interface IRegistry {
        function registryCoordinator() external view returns (address);
    }
    
    // several functions are taken or adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol (MIT license):
    // Copyright 2017 Christian Reitwiessner
    // Permission is hereby granted, free of charge, to any person obtaining a copy
    // of this software and associated documentation files (the "Software"), to
    // deal in the Software without restriction, including without limitation the
    // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
    // sell copies of the Software, and to permit persons to whom the Software is
    // furnished to do so, subject to the following conditions:
    // The above copyright notice and this permission notice shall be included in
    // all copies or substantial portions of the Software.
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    // IN THE SOFTWARE.
    
    // The remainder of the code in this library is written by LayrLabs Inc. and is also under an MIT license
    
    /**
     * @title Library for operations on the BN254 elliptic curve.
     * @author Layr Labs, Inc.
     * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
     * @notice Contains BN254 parameters, common operations (addition, scalar mul, pairing), and BLS signature functionality.
     */
    library BN254 {
        // modulus for the underlying field F_p of the elliptic curve
        uint256 internal constant FP_MODULUS =
            21888242871839275222246405745257275088696311157297823662689037894645226208583;
        // modulus for the underlying field F_r of the elliptic curve
        uint256 internal constant FR_MODULUS =
            21888242871839275222246405745257275088548364400416034343698204186575808495617;
    
        struct G1Point {
            uint256 X;
            uint256 Y;
        }
    
        // Encoding of field elements is: X[1] * i + X[0]
        struct G2Point {
            uint256[2] X;
            uint256[2] Y;
        }
    
        function generatorG1() internal pure returns (G1Point memory) {
            return G1Point(1, 2);
        }
    
        // generator of group G2
        /// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + iy1).
        uint256 internal constant G2x1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
        uint256 internal constant G2x0 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
        uint256 internal constant G2y1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
        uint256 internal constant G2y0 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
    
        /// @notice returns the G2 generator
        /// @dev mind the ordering of the 1s and 0s!
        ///      this is because of the (unknown to us) convention used in the bn254 pairing precompile contract
        ///      "Elements a * i + b of F_p^2 are encoded as two elements of F_p, (a, b)."
        ///      https://github.com/ethereum/EIPs/blob/master/EIPS/eip-197.md#encoding
        function generatorG2() internal pure returns (G2Point memory) {
            return G2Point([G2x1, G2x0], [G2y1, G2y0]);
        }
    
        // negation of the generator of group G2
        /// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + iy1).
        uint256 internal constant nG2x1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
        uint256 internal constant nG2x0 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
        uint256 internal constant nG2y1 = 17805874995975841540914202342111839520379459829704422454583296818431106115052;
        uint256 internal constant nG2y0 = 13392588948715843804641432497768002650278120570034223513918757245338268106653;
    
        function negGeneratorG2() internal pure returns (G2Point memory) {
            return G2Point([nG2x1, nG2x0], [nG2y1, nG2y0]);
        }
    
        bytes32 internal constant powersOfTauMerkleRoot =
            0x22c998e49752bbb1918ba87d6d59dd0e83620a311ba91dd4b2cc84990b31b56f;
    
        /**
         * @param p Some point in G1.
         * @return The negation of `p`, i.e. p.plus(p.negate()) should be zero.
         */
        function negate(G1Point memory p) internal pure returns (G1Point memory) {
            // The prime q in the base field F_q for G1
            if (p.X == 0 && p.Y == 0) {
                return G1Point(0, 0);
            } else {
                return G1Point(p.X, FP_MODULUS - (p.Y % FP_MODULUS));
            }
        }
    
        /**
         * @return r the sum of two points of G1
         */
        function plus(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) {
            uint256[4] memory input;
            input[0] = p1.X;
            input[1] = p1.Y;
            input[2] = p2.X;
            input[3] = p2.Y;
            bool success;
    
            // solium-disable-next-line security/no-inline-assembly
            assembly {
                success := staticcall(sub(gas(), 2000), 6, input, 0x80, r, 0x40)
                // Use "invalid" to make gas estimation work
                switch success
                case 0 {
                    invalid()
                }
            }
    
            require(success, "ec-add-failed");
        }
    
        /**
         * @notice an optimized ecMul implementation that takes O(log_2(s)) ecAdds
         * @param p the point to multiply
         * @param s the scalar to multiply by
         * @dev this function is only safe to use if the scalar is 9 bits or less
         */ 
        function scalar_mul_tiny(BN254.G1Point memory p, uint16 s) internal view returns (BN254.G1Point memory) {
            require(s < 2**9, "scalar-too-large");
    
            // if s is 1 return p
            if(s == 1) {
                return p;
            }
    
            // the accumulated product to return
            BN254.G1Point memory acc = BN254.G1Point(0, 0);
            // the 2^n*p to add to the accumulated product in each iteration
            BN254.G1Point memory p2n = p;
            // value of most significant bit
            uint16 m = 1;
            // index of most significant bit
            uint8 i = 0;
    
            //loop until we reach the most significant bit
            while(s >= m){
                unchecked {
                    // if the  current bit is 1, add the 2^n*p to the accumulated product
                    if ((s >> i) & 1 == 1) {
                        acc = plus(acc, p2n);
                    }
                    // double the 2^n*p for the next iteration
                    p2n = plus(p2n, p2n);
    
                    // increment the index and double the value of the most significant bit
                    m <<= 1;
                    ++i;
                }
            }
            
            // return the accumulated product
            return acc;
        }
    
        /**
         * @return r the product of a point on G1 and a scalar, i.e.
         *         p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all
         *         points p.
         */
        function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) {
            uint256[3] memory input;
            input[0] = p.X;
            input[1] = p.Y;
            input[2] = s;
            bool success;
            // solium-disable-next-line security/no-inline-assembly
            assembly {
                success := staticcall(sub(gas(), 2000), 7, input, 0x60, r, 0x40)
                // Use "invalid" to make gas estimation work
                switch success
                case 0 {
                    invalid()
                }
            }
            require(success, "ec-mul-failed");
        }
    
        /**
         *  @return The result of computing the pairing check
         *         e(p1[0], p2[0]) *  .... * e(p1[n], p2[n]) == 1
         *         For example,
         *         pairing([P1(), P1().negate()], [P2(), P2()]) should return true.
         */
        function pairing(
            G1Point memory a1,
            G2Point memory a2,
            G1Point memory b1,
            G2Point memory b2
        ) internal view returns (bool) {
            G1Point[2] memory p1 = [a1, b1];
            G2Point[2] memory p2 = [a2, b2];
    
            uint256[12] memory input;
    
            for (uint256 i = 0; i < 2; i++) {
                uint256 j = i * 6;
                input[j + 0] = p1[i].X;
                input[j + 1] = p1[i].Y;
                input[j + 2] = p2[i].X[0];
                input[j + 3] = p2[i].X[1];
                input[j + 4] = p2[i].Y[0];
                input[j + 5] = p2[i].Y[1];
            }
    
            uint256[1] memory out;
            bool success;
    
            // solium-disable-next-line security/no-inline-assembly
            assembly {
                success := staticcall(sub(gas(), 2000), 8, input, mul(12, 0x20), out, 0x20)
                // Use "invalid" to make gas estimation work
                switch success
                case 0 {
                    invalid()
                }
            }
    
            require(success, "pairing-opcode-failed");
    
            return out[0] != 0;
        }
    
        /**
         * @notice This function is functionally the same as pairing(), however it specifies a gas limit
         *         the user can set, as a precompile may use the entire gas budget if it reverts.
         */
        function safePairing(
            G1Point memory a1,
            G2Point memory a2,
            G1Point memory b1,
            G2Point memory b2,
            uint256 pairingGas
        ) internal view returns (bool, bool) {
            G1Point[2] memory p1 = [a1, b1];
            G2Point[2] memory p2 = [a2, b2];
    
            uint256[12] memory input;
    
            for (uint256 i = 0; i < 2; i++) {
                uint256 j = i * 6;
                input[j + 0] = p1[i].X;
                input[j + 1] = p1[i].Y;
                input[j + 2] = p2[i].X[0];
                input[j + 3] = p2[i].X[1];
                input[j + 4] = p2[i].Y[0];
                input[j + 5] = p2[i].Y[1];
            }
    
            uint256[1] memory out;
            bool success;
    
            // solium-disable-next-line security/no-inline-assembly
            assembly {
                success := staticcall(pairingGas, 8, input, mul(12, 0x20), out, 0x20)
            }
    
            //Out is the output of the pairing precompile, either 0 or 1 based on whether the two pairings are equal.
            //Success is true if the precompile actually goes through (aka all inputs are valid)
    
            return (success, out[0] != 0);
        }
    
        /// @return hashedG1 the keccak256 hash of the G1 Point
        /// @dev used for BLS signatures
        function hashG1Point(BN254.G1Point memory pk) internal pure returns (bytes32 hashedG1) {
            assembly {
                mstore(0, mload(pk))
                mstore(0x20, mload(add(0x20, pk)))
                hashedG1 := keccak256(0, 0x40)
            }
        }
    
        /// @return the keccak256 hash of the G2 Point
        /// @dev used for BLS signatures
        function hashG2Point(
            BN254.G2Point memory pk
        ) internal pure returns (bytes32) {
            return keccak256(abi.encodePacked(pk.X[0], pk.X[1], pk.Y[0], pk.Y[1]));
        }
    
        /**
         * @notice adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol
         */
        function hashToG1(bytes32 _x) internal view returns (G1Point memory) {
            uint256 beta = 0;
            uint256 y = 0;
    
            uint256 x = uint256(_x) % FP_MODULUS;
    
            while (true) {
                (beta, y) = findYFromX(x);
    
                // y^2 == beta
                if( beta == mulmod(y, y, FP_MODULUS) ) {
                    return G1Point(x, y);
                }
    
                x = addmod(x, 1, FP_MODULUS);
            }
            return G1Point(0, 0);
        }
    
        /**
         * Given X, find Y
         *
         *   where y = sqrt(x^3 + b)
         *
         * Returns: (x^3 + b), y
         */
        function findYFromX(uint256 x) internal view returns (uint256, uint256) {
            // beta = (x^3 + b) % p
            uint256 beta = addmod(mulmod(mulmod(x, x, FP_MODULUS), x, FP_MODULUS), 3, FP_MODULUS);
    
            // y^2 = x^3 + b
            // this acts like: y = sqrt(beta) = beta^((p+1) / 4)
            uint256 y = expMod(beta, 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52, FP_MODULUS);
    
            return (beta, y);
        }
    
        function expMod(uint256 _base, uint256 _exponent, uint256 _modulus) internal view returns (uint256 retval) {
            bool success;
            uint256[1] memory output;
            uint[6] memory input;
            input[0] = 0x20; // baseLen = new(big.Int).SetBytes(getData(input, 0, 32))
            input[1] = 0x20; // expLen  = new(big.Int).SetBytes(getData(input, 32, 32))
            input[2] = 0x20; // modLen  = new(big.Int).SetBytes(getData(input, 64, 32))
            input[3] = _base;
            input[4] = _exponent;
            input[5] = _modulus;
            assembly {
                success := staticcall(sub(gas(), 2000), 5, input, 0xc0, output, 0x20)
                // Use "invalid" to make gas estimation work
                switch success
                case 0 {
                    invalid()
                }
            }
            require(success, "BN254.expMod: call failure");
            return output[0];
        }
    }
    
    /**
     * @title Minimal interface for a registry that keeps track of aggregate operator public keys across many quorums.
     * @author Layr Labs, Inc.
     */
    interface IBLSApkRegistry is IRegistry {
        // STRUCTS
        /// @notice Data structure used to track the history of the Aggregate Public Key of all operators
        struct ApkUpdate {
            // first 24 bytes of keccak256(apk_x0, apk_x1, apk_y0, apk_y1)
            bytes24 apkHash;
            // block number at which the update occurred
            uint32 updateBlockNumber;
            // block number at which the next update occurred
            uint32 nextUpdateBlockNumber;
        }
    
        /**
         * @notice Struct used when registering a new public key
         * @param pubkeyRegistrationSignature is the registration message signed by the private key of the operator
         * @param pubkeyG1 is the corresponding G1 public key of the operator 
         * @param pubkeyG2 is the corresponding G2 public key of the operator
         */     
        struct PubkeyRegistrationParams {
            BN254.G1Point pubkeyRegistrationSignature;
            BN254.G1Point pubkeyG1;
            BN254.G2Point pubkeyG2;
        }
    
        // EVENTS
        /// @notice Emitted when `operator` registers with the public keys `pubkeyG1` and `pubkeyG2`.
        event NewPubkeyRegistration(address indexed operator, BN254.G1Point pubkeyG1, BN254.G2Point pubkeyG2);
    
        // @notice Emitted when a new operator pubkey is registered for a set of quorums
        event OperatorAddedToQuorums(
            address operator,
            bytes32 operatorId,
            bytes quorumNumbers
        );
    
        // @notice Emitted when an operator pubkey is removed from a set of quorums
        event OperatorRemovedFromQuorums(
            address operator, 
            bytes32 operatorId,
            bytes quorumNumbers
        );
    
        /**
         * @notice Registers the `operator`'s pubkey for the specified `quorumNumbers`.
         * @param operator The address of the operator to register.
         * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber.
         * @dev access restricted to the RegistryCoordinator
         * @dev Preconditions (these are assumed, not validated in this contract):
         *         1) `quorumNumbers` has no duplicates
         *         2) `quorumNumbers.length` != 0
         *         3) `quorumNumbers` is ordered in ascending order
         *         4) the operator is not already registered
         */
        function registerOperator(address operator, bytes calldata quorumNumbers) external;
    
        /**
         * @notice Deregisters the `operator`'s pubkey for the specified `quorumNumbers`.
         * @param operator The address of the operator to deregister.
         * @param quorumNumbers The quorum numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber.
         * @dev access restricted to the RegistryCoordinator
         * @dev Preconditions (these are assumed, not validated in this contract):
         *         1) `quorumNumbers` has no duplicates
         *         2) `quorumNumbers.length` != 0
         *         3) `quorumNumbers` is ordered in ascending order
         *         4) the operator is not already deregistered
         *         5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for
         */ 
        function deregisterOperator(address operator, bytes calldata quorumNumbers) external;
        
        /**
         * @notice Initializes a new quorum by pushing its first apk update
         * @param quorumNumber The number of the new quorum
         */
        function initializeQuorum(uint8 quorumNumber) external;
    
        /**
         * @notice mapping from operator address to pubkey hash.
         * Returns *zero* if the `operator` has never registered, and otherwise returns the hash of the public key of the operator.
         */
        function operatorToPubkeyHash(address operator) external view returns (bytes32);
    
        /**
         * @notice mapping from pubkey hash to operator address.
         * Returns *zero* if no operator has ever registered the public key corresponding to `pubkeyHash`,
         * and otherwise returns the (unique) registered operator who owns the BLS public key that is the preimage of `pubkeyHash`.
         */
        function pubkeyHashToOperator(bytes32 pubkeyHash) external view returns (address);
    
        /**
         * @notice Called by the RegistryCoordinator register an operator as the owner of a BLS public key.
         * @param operator is the operator for whom the key is being registered
         * @param params contains the G1 & G2 public keys of the operator, and a signature proving their ownership
         * @param pubkeyRegistrationMessageHash is a hash that the operator must sign to prove key ownership
         */
        function registerBLSPublicKey(
            address operator,
            PubkeyRegistrationParams calldata params,
            BN254.G1Point calldata pubkeyRegistrationMessageHash
        ) external returns (bytes32 operatorId);
    
        /**
         * @notice Returns the pubkey and pubkey hash of an operator
         * @dev Reverts if the operator has not registered a valid pubkey
         */
        function getRegisteredPubkey(address operator) external view returns (BN254.G1Point memory, bytes32);
    
        /// @notice Returns the current APK for the provided `quorumNumber `
        function getApk(uint8 quorumNumber) external view returns (BN254.G1Point memory);
    
        /// @notice Returns the index of the quorumApk index at `blockNumber` for the provided `quorumNumber`
        function getApkIndicesAtBlockNumber(bytes calldata quorumNumbers, uint256 blockNumber) external view returns(uint32[] memory);
    
        /// @notice Returns the `ApkUpdate` struct at `index` in the list of APK updates for the `quorumNumber`
        function getApkUpdateAtIndex(uint8 quorumNumber, uint256 index) external view returns (ApkUpdate memory);
    
        /// @notice Returns the operator address for the given `pubkeyHash`
        function getOperatorFromPubkeyHash(bytes32 pubkeyHash) external view returns (address);
    
        /**
         * @notice get 24 byte hash of the apk of `quorumNumber` at `blockNumber` using the provided `index`;
         * called by checkSignatures in BLSSignatureChecker.sol.
         * @param quorumNumber is the quorum whose ApkHash is being retrieved
         * @param blockNumber is the number of the block for which the latest ApkHash will be retrieved
         * @param index is the index of the apkUpdate being retrieved from the list of quorum apkUpdates in storage
         */
        function getApkHashAtBlockNumberAndIndex(uint8 quorumNumber, uint32 blockNumber, uint256 index) external view returns (bytes24);
    
        /// @notice returns the ID used to identify the `operator` within this AVS.
        /// @dev Returns zero in the event that the `operator` has never registered for the AVS
        function getOperatorId(address operator) external view returns (bytes32);
    }
    
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
    
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
         * another (`to`).
         *
         * Note that `value` may be zero.
         */
        event Transfer(address indexed from, address indexed to, uint256 value);
    
        /**
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
         * a call to {approve}. `value` is the new allowance.
         */
        event Approval(address indexed owner, address indexed spender, uint256 value);
    
        /**
         * @dev Returns the 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 `to`.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address to, 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 `from` to `to` 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 from, address to, uint256 amount) external returns (bool);
    }
    
    /**
     * @title Minimal interface for an `Strategy` contract.
     * @author Layr Labs, Inc.
     * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
     * @notice Custom `Strategy` implementations may expand extensively on this interface.
     */
    interface IStrategy {
        /**
         * @notice Used to deposit tokens into this Strategy
         * @param token is the ERC20 token being deposited
         * @param amount is the amount of token being deposited
         * @dev This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's
         * `depositIntoStrategy` function, and individual share balances are recorded in the strategyManager as well.
         * @return newShares is the number of new shares issued at the current exchange ratio.
         */
        function deposit(IERC20 token, uint256 amount) external returns (uint256);
    
        /**
         * @notice Used to withdraw tokens from this Strategy, to the `recipient`'s address
         * @param recipient is the address to receive the withdrawn funds
         * @param token is the ERC20 token being transferred out
         * @param amountShares is the amount of shares being withdrawn
         * @dev This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's
         * other functions, and individual share balances are recorded in the strategyManager as well.
         */
        function withdraw(address recipient, IERC20 token, uint256 amountShares) external;
    
        /**
         * @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
         * @notice In contrast to `sharesToUnderlyingView`, this function **may** make state modifications
         * @param amountShares is the amount of shares to calculate its conversion into the underlying token
         * @return The amount of underlying tokens corresponding to the input `amountShares`
         * @dev Implementation for these functions in particular may vary significantly for different strategies
         */
        function sharesToUnderlying(uint256 amountShares) external returns (uint256);
    
        /**
         * @notice Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy.
         * @notice In contrast to `underlyingToSharesView`, this function **may** make state modifications
         * @param amountUnderlying is the amount of `underlyingToken` to calculate its conversion into strategy shares
         * @return The amount of underlying tokens corresponding to the input `amountShares`
         * @dev Implementation for these functions in particular may vary significantly for different strategies
         */
        function underlyingToShares(uint256 amountUnderlying) external returns (uint256);
    
        /**
         * @notice convenience function for fetching the current underlying value of all of the `user`'s shares in
         * this strategy. In contrast to `userUnderlyingView`, this function **may** make state modifications
         */
        function userUnderlying(address user) external returns (uint256);
    
        /**
         * @notice convenience function for fetching the current total shares of `user` in this strategy, by
         * querying the `strategyManager` contract
         */
        function shares(address user) external view returns (uint256);
    
        /**
         * @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
         * @notice In contrast to `sharesToUnderlying`, this function guarantees no state modifications
         * @param amountShares is the amount of shares to calculate its conversion into the underlying token
         * @return The amount of shares corresponding to the input `amountUnderlying`
         * @dev Implementation for these functions in particular may vary significantly for different strategies
         */
        function sharesToUnderlyingView(uint256 amountShares) external view returns (uint256);
    
        /**
         * @notice Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy.
         * @notice In contrast to `underlyingToShares`, this function guarantees no state modifications
         * @param amountUnderlying is the amount of `underlyingToken` to calculate its conversion into strategy shares
         * @return The amount of shares corresponding to the input `amountUnderlying`
         * @dev Implementation for these functions in particular may vary significantly for different strategies
         */
        function underlyingToSharesView(uint256 amountUnderlying) external view returns (uint256);
    
        /**
         * @notice convenience function for fetching the current underlying value of all of the `user`'s shares in
         * this strategy. In contrast to `userUnderlying`, this function guarantees no state modifications
         */
        function userUnderlyingView(address user) external view returns (uint256);
    
        /// @notice The underlying token for shares in this Strategy
        function underlyingToken() external view returns (IERC20);
    
        /// @notice The total number of extant shares in this Strategy
        function totalShares() external view returns (uint256);
    
        /// @notice Returns either a brief string explaining the strategy's goal & purpose, or a link to metadata that explains in more detail.
        function explanation() external view returns (string memory);
    }
    
    /**
     * @title Interface for the primary 'slashing' contract for EigenLayer.
     * @author Layr Labs, Inc.
     * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
     * @notice See the `Slasher` contract itself for implementation details.
     */
    interface ISlasher {
        // struct used to store information about the current state of an operator's obligations to middlewares they are serving
        struct MiddlewareTimes {
            // The update block for the middleware whose most recent update was earliest, i.e. the 'stalest' update out of all middlewares the operator is serving
            uint32 stalestUpdateBlock;
            // The latest 'serveUntilBlock' from all of the middleware that the operator is serving
            uint32 latestServeUntilBlock;
        }
    
        // struct used to store details relevant to a single middleware that an operator has opted-in to serving
        struct MiddlewareDetails {
            // the block at which the contract begins being able to finalize the operator's registration with the service via calling `recordFirstStakeUpdate`
            uint32 registrationMayBeginAtBlock;
            // the block before which the contract is allowed to slash the user
            uint32 contractCanSlashOperatorUntilBlock;
            // the block at which the middleware's view of the operator's stake was most recently updated
            uint32 latestUpdateBlock;
        }
    
        /// @notice Emitted when a middleware times is added to `operator`'s array.
        event MiddlewareTimesAdded(
            address operator,
            uint256 index,
            uint32 stalestUpdateBlock,
            uint32 latestServeUntilBlock
        );
    
        /// @notice Emitted when `operator` begins to allow `contractAddress` to slash them.
        event OptedIntoSlashing(address indexed operator, address indexed contractAddress);
    
        /// @notice Emitted when `contractAddress` signals that it will no longer be able to slash `operator` after the `contractCanSlashOperatorUntilBlock`.
        event SlashingAbilityRevoked(
            address indexed operator,
            address indexed contractAddress,
            uint32 contractCanSlashOperatorUntilBlock
        );
    
        /**
         * @notice Emitted when `slashingContract` 'freezes' the `slashedOperator`.
         * @dev The `slashingContract` must have permission to slash the `slashedOperator`, i.e. `canSlash(slasherOperator, slashingContract)` must return 'true'.
         */
        event OperatorFrozen(address indexed slashedOperator, address indexed slashingContract);
    
        /// @notice Emitted when `previouslySlashedAddress` is 'unfrozen', allowing them to again move deposited funds within EigenLayer.
        event FrozenStatusReset(address indexed previouslySlashedAddress);
    
        /**
         * @notice Gives the `contractAddress` permission to slash the funds of the caller.
         * @dev Typically, this function must be called prior to registering for a middleware.
         */
        function optIntoSlashing(address contractAddress) external;
    
        /**
         * @notice Used for 'slashing' a certain operator.
         * @param toBeFrozen The operator to be frozen.
         * @dev Technically the operator is 'frozen' (hence the name of this function), and then subject to slashing pending a decision by a human-in-the-loop.
         * @dev The operator must have previously given the caller (which should be a contract) the ability to slash them, through a call to `optIntoSlashing`.
         */
        function freezeOperator(address toBeFrozen) external;
    
        /**
         * @notice Removes the 'frozen' status from each of the `frozenAddresses`
         * @dev Callable only by the contract owner (i.e. governance).
         */
        function resetFrozenStatus(address[] calldata frozenAddresses) external;
    
        /**
         * @notice this function is a called by middlewares during an operator's registration to make sure the operator's stake at registration
         *         is slashable until serveUntil
         * @param operator the operator whose stake update is being recorded
         * @param serveUntilBlock the block until which the operator's stake at the current block is slashable
         * @dev adds the middleware's slashing contract to the operator's linked list
         */
        function recordFirstStakeUpdate(address operator, uint32 serveUntilBlock) external;
    
        /**
         * @notice this function is a called by middlewares during a stake update for an operator (perhaps to free pending withdrawals)
         *         to make sure the operator's stake at updateBlock is slashable until serveUntil
         * @param operator the operator whose stake update is being recorded
         * @param updateBlock the block for which the stake update is being recorded
         * @param serveUntilBlock the block until which the operator's stake at updateBlock is slashable
         * @param insertAfter the element of the operators linked list that the currently updating middleware should be inserted after
         * @dev insertAfter should be calculated offchain before making the transaction that calls this. this is subject to race conditions,
         *      but it is anticipated to be rare and not detrimental.
         */
        function recordStakeUpdate(
            address operator,
            uint32 updateBlock,
            uint32 serveUntilBlock,
            uint256 insertAfter
        ) external;
    
        /**
         * @notice this function is a called by middlewares during an operator's deregistration to make sure the operator's stake at deregistration
         *         is slashable until serveUntil
         * @param operator the operator whose stake update is being recorded
         * @param serveUntilBlock the block until which the operator's stake at the current block is slashable
         * @dev removes the middleware's slashing contract to the operator's linked list and revokes the middleware's (i.e. caller's) ability to
         * slash `operator` once `serveUntil` is reached
         */
        function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntilBlock) external;
    
        /// @notice The StrategyManager contract of EigenLayer
        function strategyManager() external view returns (IStrategyManager);
    
        /// @notice The DelegationManager contract of EigenLayer
        function delegation() external view returns (IDelegationManager);
    
        /**
         * @notice Used to determine whether `staker` is actively 'frozen'. If a staker is frozen, then they are potentially subject to
         * slashing of their funds, and cannot cannot deposit or withdraw from the strategyManager until the slashing process is completed
         * and the staker's status is reset (to 'unfrozen').
         * @param staker The staker of interest.
         * @return Returns 'true' if `staker` themselves has their status set to frozen, OR if the staker is delegated
         * to an operator who has their status set to frozen. Otherwise returns 'false'.
         */
        function isFrozen(address staker) external view returns (bool);
    
        /// @notice Returns true if `slashingContract` is currently allowed to slash `toBeSlashed`.
        function canSlash(address toBeSlashed, address slashingContract) external view returns (bool);
    
        /// @notice Returns the block until which `serviceContract` is allowed to slash the `operator`.
        function contractCanSlashOperatorUntilBlock(
            address operator,
            address serviceContract
        ) external view returns (uint32);
    
        /// @notice Returns the block at which the `serviceContract` last updated its view of the `operator`'s stake
        function latestUpdateBlock(address operator, address serviceContract) external view returns (uint32);
    
        /// @notice A search routine for finding the correct input value of `insertAfter` to `recordStakeUpdate` / `_updateMiddlewareList`.
        function getCorrectValueForInsertAfter(address operator, uint32 updateBlock) external view returns (uint256);
    
        /**
         * @notice Returns 'true' if `operator` can currently complete a withdrawal started at the `withdrawalStartBlock`, with `middlewareTimesIndex` used
         * to specify the index of a `MiddlewareTimes` struct in the operator's list (i.e. an index in `operatorToMiddlewareTimes[operator]`). The specified
         * struct is consulted as proof of the `operator`'s ability (or lack thereof) to complete the withdrawal.
         * This function will return 'false' if the operator cannot currently complete a withdrawal started at the `withdrawalStartBlock`, *or* in the event
         * that an incorrect `middlewareTimesIndex` is supplied, even if one or more correct inputs exist.
         * @param operator Either the operator who queued the withdrawal themselves, or if the withdrawing party is a staker who delegated to an operator,
         * this address is the operator *who the staker was delegated to* at the time of the `withdrawalStartBlock`.
         * @param withdrawalStartBlock The block number at which the withdrawal was initiated.
         * @param middlewareTimesIndex Indicates an index in `operatorToMiddlewareTimes[operator]` to consult as proof of the `operator`'s ability to withdraw
         * @dev The correct `middlewareTimesIndex` input should be computable off-chain.
         */
        function canWithdraw(
            address operator,
            uint32 withdrawalStartBlock,
            uint256 middlewareTimesIndex
        ) external returns (bool);
    
        /**
         * operator =>
         *  [
         *      (
         *          the least recent update block of all of the middlewares it's serving/served,
         *          latest time that the stake bonded at that update needed to serve until
         *      )
         *  ]
         */
        function operatorToMiddlewareTimes(
            address operator,
            uint256 arrayIndex
        ) external view returns (MiddlewareTimes memory);
    
        /// @notice Getter function for fetching `operatorToMiddlewareTimes[operator].length`
        function middlewareTimesLength(address operator) external view returns (uint256);
    
        /// @notice Getter function for fetching `operatorToMiddlewareTimes[operator][index].stalestUpdateBlock`.
        function getMiddlewareTimesIndexStalestUpdateBlock(address operator, uint32 index) external view returns (uint32);
    
        /// @notice Getter function for fetching `operatorToMiddlewareTimes[operator][index].latestServeUntil`.
        function getMiddlewareTimesIndexServeUntilBlock(address operator, uint32 index) external view returns (uint32);
    
        /// @notice Getter function for fetching `_operatorToWhitelistedContractsByUpdate[operator].size`.
        function operatorWhitelistedContractsLinkedListSize(address operator) external view returns (uint256);
    
        /// @notice Getter function for fetching a single node in the operator's linked list (`_operatorToWhitelistedContractsByUpdate[operator]`).
        function operatorWhitelistedContractsLinkedListEntry(
            address operator,
            address node
        ) external view returns (bool, uint256, uint256);
    }
    
    // OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)
    
    /**
     * @dev This is the interface that {BeaconProxy} expects of its beacon.
     */
    interface IBeacon {
        /**
         * @dev Must return an address that can be used as a delegate call target.
         *
         * {BeaconProxy} will check that this address is a contract.
         */
        function implementation() external view returns (address);
    }
    
    // ┏━━━┓━┏┓━┏┓━━┏━━━┓━━┏━━━┓━━━━┏━━━┓━━━━━━━━━━━━━━━━━━━┏┓━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━━━━━━━┏┓━
    // ┃┏━━┛┏┛┗┓┃┃━━┃┏━┓┃━━┃┏━┓┃━━━━┗┓┏┓┃━━━━━━━━━━━━━━━━━━┏┛┗┓━━━━┃┏━┓┃━━━━━━━━┏┛┗┓━━━━━━━━━━━━┏┛┗┓
    // ┃┗━━┓┗┓┏┛┃┗━┓┗┛┏┛┃━━┃┃━┃┃━━━━━┃┃┃┃┏━━┓┏━━┓┏━━┓┏━━┓┏┓┗┓┏┛━━━━┃┃━┗┛┏━━┓┏━┓━┗┓┏┛┏━┓┏━━┓━┏━━┓┗┓┏┛
    // ┃┏━━┛━┃┃━┃┏┓┃┏━┛┏┛━━┃┃━┃┃━━━━━┃┃┃┃┃┏┓┃┃┏┓┃┃┏┓┃┃━━┫┣┫━┃┃━━━━━┃┃━┏┓┃┏┓┃┃┏┓┓━┃┃━┃┏┛┗━┓┃━┃┏━┛━┃┃━
    // ┃┗━━┓━┃┗┓┃┃┃┃┃┃┗━┓┏┓┃┗━┛┃━━━━┏┛┗┛┃┃┃━┫┃┗┛┃┃┗┛┃┣━━┃┃┃━┃┗┓━━━━┃┗━┛┃┃┗┛┃┃┃┃┃━┃┗┓┃┃━┃┗┛┗┓┃┗━┓━┃┗┓
    // ┗━━━┛━┗━┛┗┛┗┛┗━━━┛┗┛┗━━━┛━━━━┗━━━┛┗━━┛┃┏━┛┗━━┛┗━━┛┗┛━┗━┛━━━━┗━━━┛┗━━┛┗┛┗┛━┗━┛┗┛━┗━━━┛┗━━┛━┗━┛
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┃┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┗┛━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
    // This interface is designed to be compatible with the Vyper version.
    /// @notice This is the Ethereum 2.0 deposit contract interface.
    /// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
    interface IETHPOSDeposit {
        /// @notice A processed deposit event.
        event DepositEvent(bytes pubkey, bytes withdrawal_credentials, bytes amount, bytes signature, bytes index);
    
        /// @notice Submit a Phase 0 DepositData object.
        /// @param pubkey A BLS12-381 public key.
        /// @param withdrawal_credentials Commitment to a public key for withdrawals.
        /// @param signature A BLS12-381 signature.
        /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.
        /// Used as a protection against malformed input.
        function deposit(
            bytes calldata pubkey,
            bytes calldata withdrawal_credentials,
            bytes calldata signature,
            bytes32 deposit_data_root
        ) external payable;
    
        /// @notice Query the current deposit root hash.
        /// @return The deposit root hash.
        function get_deposit_root() external view returns (bytes32);
    
        /// @notice Query the current deposit count.
        /// @return The deposit count encoded as a little endian 64-bit number.
        function get_deposit_count() external view returns (bytes memory);
    }
    
    // Adapted from OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/MerkleProof.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.
     */
    library Merkle {
        /**
         * @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. The tree is built assuming `leaf` is
         * the 0 indexed `index`'th leaf from the bottom left of the tree.
         *
         * Note this is for a Merkle tree using the keccak/sha3 hash function
         */
        function verifyInclusionKeccak(
            bytes memory proof,
            bytes32 root,
            bytes32 leaf,
            uint256 index
        ) internal pure returns (bool) {
            return processInclusionProofKeccak(proof, leaf, index) == 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. The tree is built assuming `leaf` is
         * the 0 indexed `index`'th leaf from the bottom left of the tree.
         *
         * _Available since v4.4._
         *
         * Note this is for a Merkle tree using the keccak/sha3 hash function
         */
        function processInclusionProofKeccak(
            bytes memory proof,
            bytes32 leaf,
            uint256 index
        ) internal pure returns (bytes32) {
            require(
                proof.length != 0 && proof.length % 32 == 0,
                "Merkle.processInclusionProofKeccak: proof length should be a non-zero multiple of 32"
            );
            bytes32 computedHash = leaf;
            for (uint256 i = 32; i <= proof.length; i += 32) {
                if (index % 2 == 0) {
                    // if ith bit of index is 0, then computedHash is a left sibling
                    assembly {
                        mstore(0x00, computedHash)
                        mstore(0x20, mload(add(proof, i)))
                        computedHash := keccak256(0x00, 0x40)
                        index := div(index, 2)
                    }
                } else {
                    // if ith bit of index is 1, then computedHash is a right sibling
                    assembly {
                        mstore(0x00, mload(add(proof, i)))
                        mstore(0x20, computedHash)
                        computedHash := keccak256(0x00, 0x40)
                        index := div(index, 2)
                    }
                }
            }
            return computedHash;
        }
    
        /**
         * @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. The tree is built assuming `leaf` is
         * the 0 indexed `index`'th leaf from the bottom left of the tree.
         *
         * Note this is for a Merkle tree using the sha256 hash function
         */
        function verifyInclusionSha256(
            bytes memory proof,
            bytes32 root,
            bytes32 leaf,
            uint256 index
        ) internal view returns (bool) {
            return processInclusionProofSha256(proof, leaf, index) == 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. The tree is built assuming `leaf` is
         * the 0 indexed `index`'th leaf from the bottom left of the tree.
         *
         * _Available since v4.4._
         *
         * Note this is for a Merkle tree using the sha256 hash function
         */
        function processInclusionProofSha256(
            bytes memory proof,
            bytes32 leaf,
            uint256 index
        ) internal view returns (bytes32) {
            require(
                proof.length != 0 && proof.length % 32 == 0,
                "Merkle.processInclusionProofSha256: proof length should be a non-zero multiple of 32"
            );
            bytes32[1] memory computedHash = [leaf];
            for (uint256 i = 32; i <= proof.length; i += 32) {
                if (index % 2 == 0) {
                    // if ith bit of index is 0, then computedHash is a left sibling
                    assembly {
                        mstore(0x00, mload(computedHash))
                        mstore(0x20, mload(add(proof, i)))
                        if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {
                            revert(0, 0)
                        }
                        index := div(index, 2)
                    }
                } else {
                    // if ith bit of index is 1, then computedHash is a right sibling
                    assembly {
                        mstore(0x00, mload(add(proof, i)))
                        mstore(0x20, mload(computedHash))
                        if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {
                            revert(0, 0)
                        }
                        index := div(index, 2)
                    }
                }
            }
            return computedHash[0];
        }
    
        /**
         @notice this function returns the merkle root of a tree created from a set of leaves using sha256 as its hash function
         @param leaves the leaves of the merkle tree
         @return The computed Merkle root of the tree.
         @dev A pre-condition to this function is that leaves.length is a power of two.  If not, the function will merkleize the inputs incorrectly.
         */
        function merkleizeSha256(bytes32[] memory leaves) internal pure returns (bytes32) {
            //there are half as many nodes in the layer above the leaves
            uint256 numNodesInLayer = leaves.length / 2;
            //create a layer to store the internal nodes
            bytes32[] memory layer = new bytes32[](numNodesInLayer);
            //fill the layer with the pairwise hashes of the leaves
            for (uint256 i = 0; i < numNodesInLayer; i++) {
                layer[i] = sha256(abi.encodePacked(leaves[2 * i], leaves[2 * i + 1]));
            }
            //the next layer above has half as many nodes
            numNodesInLayer /= 2;
            //while we haven't computed the root
            while (numNodesInLayer != 0) {
                //overwrite the first numNodesInLayer nodes in layer with the pairwise hashes of their children
                for (uint256 i = 0; i < numNodesInLayer; i++) {
                    layer[i] = sha256(abi.encodePacked(layer[2 * i], layer[2 * i + 1]));
                }
                //the next layer above has half as many nodes
                numNodesInLayer /= 2;
            }
            //the first node in the layer is the root
            return layer[0];
        }
    }
    
    library Endian {
        /**
         * @notice Converts a little endian-formatted uint64 to a big endian-formatted uint64
         * @param lenum little endian-formatted uint64 input, provided as 'bytes32' type
         * @return n The big endian-formatted uint64
         * @dev Note that the input is formatted as a 'bytes32' type (i.e. 256 bits), but it is immediately truncated to a uint64 (i.e. 64 bits)
         * through a right-shift/shr operation.
         */
        function fromLittleEndianUint64(bytes32 lenum) internal pure returns (uint64 n) {
            // the number needs to be stored in little-endian encoding (ie in bytes 0-8)
            n = uint64(uint256(lenum >> 192));
            return
                (n >> 56) |
                ((0x00FF000000000000 & n) >> 40) |
                ((0x0000FF0000000000 & n) >> 24) |
                ((0x000000FF00000000 & n) >> 8) |
                ((0x00000000FF000000 & n) << 8) |
                ((0x0000000000FF0000 & n) << 24) |
                ((0x000000000000FF00 & n) << 40) |
                ((0x00000000000000FF & n) << 56);
        }
    }
    
    //Utility library for parsing and PHASE0 beacon chain block headers
    //SSZ Spec: https://github.com/ethereum/consensus-specs/blob/dev/ssz/simple-serialize.md#merkleization
    //BeaconBlockHeader Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader
    //BeaconState Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconstate
    library BeaconChainProofs {
        // constants are the number of fields and the heights of the different merkle trees used in merkleizing beacon chain containers
        uint256 internal constant BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT = 3;
    
        uint256 internal constant BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT = 4;
    
        uint256 internal constant BEACON_STATE_FIELD_TREE_HEIGHT = 5;
    
        uint256 internal constant VALIDATOR_FIELD_TREE_HEIGHT = 3;
    
        //Note: changed in the deneb hard fork from 4->5
        uint256 internal constant EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB = 5;
        uint256 internal constant EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA = 4;
    
        // SLOTS_PER_HISTORICAL_ROOT = 2**13, so tree height is 13
        uint256 internal constant BLOCK_ROOTS_TREE_HEIGHT = 13;
    
        //HISTORICAL_ROOTS_LIMIT = 2**24, so tree height is 24
        uint256 internal constant HISTORICAL_SUMMARIES_TREE_HEIGHT = 24;
    
        //Index of block_summary_root in historical_summary container
        uint256 internal constant BLOCK_SUMMARY_ROOT_INDEX = 0;
    
        // tree height for hash tree of an individual withdrawal container
        uint256 internal constant WITHDRAWAL_FIELD_TREE_HEIGHT = 2;
    
        uint256 internal constant VALIDATOR_TREE_HEIGHT = 40;
    
        // MAX_WITHDRAWALS_PER_PAYLOAD = 2**4, making tree height = 4
        uint256 internal constant WITHDRAWALS_TREE_HEIGHT = 4;
    
        //in beacon block body https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconblockbody
        uint256 internal constant EXECUTION_PAYLOAD_INDEX = 9;
    
        // in beacon block header https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader
        uint256 internal constant SLOT_INDEX = 0;
        uint256 internal constant STATE_ROOT_INDEX = 3;
        uint256 internal constant BODY_ROOT_INDEX = 4;
        // in beacon state https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconstate
        uint256 internal constant VALIDATOR_TREE_ROOT_INDEX = 11;
        uint256 internal constant HISTORICAL_SUMMARIES_INDEX = 27;
    
        // in validator https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
        uint256 internal constant VALIDATOR_PUBKEY_INDEX = 0;
        uint256 internal constant VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX = 1;
        uint256 internal constant VALIDATOR_BALANCE_INDEX = 2;
        uint256 internal constant VALIDATOR_WITHDRAWABLE_EPOCH_INDEX = 7;
    
        // in execution payload header
        uint256 internal constant TIMESTAMP_INDEX = 9;
    
        //in execution payload
        uint256 internal constant WITHDRAWALS_INDEX = 14;
    
        // in withdrawal
        uint256 internal constant WITHDRAWAL_VALIDATOR_INDEX_INDEX = 1;
        uint256 internal constant WITHDRAWAL_VALIDATOR_AMOUNT_INDEX = 3;
    
        //Misc Constants
    
        /// @notice The number of slots each epoch in the beacon chain
        uint64 internal constant SLOTS_PER_EPOCH = 32;
    
        /// @notice The number of seconds in a slot in the beacon chain
        uint64 internal constant SECONDS_PER_SLOT = 12;
    
        /// @notice Number of seconds per epoch: 384 == 32 slots/epoch * 12 seconds/slot 
        uint64 internal constant SECONDS_PER_EPOCH = SLOTS_PER_EPOCH * SECONDS_PER_SLOT;
    
        bytes8 internal constant UINT64_MASK = 0xffffffffffffffff;
    
        /// @notice This struct contains the merkle proofs and leaves needed to verify a partial/full withdrawal
        struct WithdrawalProof {
            bytes withdrawalProof;
            bytes slotProof;
            bytes executionPayloadProof;
            bytes timestampProof;
            bytes historicalSummaryBlockRootProof;
            uint64 blockRootIndex;
            uint64 historicalSummaryIndex;
            uint64 withdrawalIndex;
            bytes32 blockRoot;
            bytes32 slotRoot;
            bytes32 timestampRoot;
            bytes32 executionPayloadRoot;
        }
    
        /// @notice This struct contains the root and proof for verifying the state root against the oracle block root
        struct StateRootProof {
            bytes32 beaconStateRoot;
            bytes proof;
        }
    
        /**
         * @notice This function verifies merkle proofs of the fields of a certain validator against a beacon chain state root
         * @param validatorIndex the index of the proven validator
         * @param beaconStateRoot is the beacon chain state root to be proven against.
         * @param validatorFieldsProof is the data used in proving the validator's fields
         * @param validatorFields the claimed fields of the validator
         */
        function verifyValidatorFields(
            bytes32 beaconStateRoot,
            bytes32[] calldata validatorFields,
            bytes calldata validatorFieldsProof,
            uint40 validatorIndex
        ) internal view {
            require(
                validatorFields.length == 2 ** VALIDATOR_FIELD_TREE_HEIGHT,
                "BeaconChainProofs.verifyValidatorFields: Validator fields has incorrect length"
            );
    
            /**
             * Note: the length of the validator merkle proof is BeaconChainProofs.VALIDATOR_TREE_HEIGHT + 1.
             * There is an additional layer added by hashing the root with the length of the validator list
             */
            require(
                validatorFieldsProof.length == 32 * ((VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT),
                "BeaconChainProofs.verifyValidatorFields: Proof has incorrect length"
            );
            uint256 index = (VALIDATOR_TREE_ROOT_INDEX << (VALIDATOR_TREE_HEIGHT + 1)) | uint256(validatorIndex);
            // merkleize the validatorFields to get the leaf to prove
            bytes32 validatorRoot = Merkle.merkleizeSha256(validatorFields);
    
            // verify the proof of the validatorRoot against the beaconStateRoot
            require(
                Merkle.verifyInclusionSha256({
                    proof: validatorFieldsProof,
                    root: beaconStateRoot,
                    leaf: validatorRoot,
                    index: index
                }),
                "BeaconChainProofs.verifyValidatorFields: Invalid merkle proof"
            );
        }
    
        /**
         * @notice This function verifies the latestBlockHeader against the state root. the latestBlockHeader is
         * a tracked in the beacon state.
         * @param beaconStateRoot is the beacon chain state root to be proven against.
         * @param stateRootProof is the provided merkle proof
         * @param latestBlockRoot is hashtree root of the latest block header in the beacon state
         */
        function verifyStateRootAgainstLatestBlockRoot(
            bytes32 latestBlockRoot,
            bytes32 beaconStateRoot,
            bytes calldata stateRootProof
        ) internal view {
            require(
                stateRootProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT),
                "BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot: Proof has incorrect length"
            );
            //Next we verify the slot against the blockRoot
            require(
                Merkle.verifyInclusionSha256({
                    proof: stateRootProof,
                    root: latestBlockRoot,
                    leaf: beaconStateRoot,
                    index: STATE_ROOT_INDEX
                }),
                "BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot: Invalid latest block header root merkle proof"
            );
        }
    
        /**
         * @notice This function verifies the slot and the withdrawal fields for a given withdrawal
         * @param withdrawalProof is the provided set of merkle proofs
         * @param withdrawalFields is the serialized withdrawal container to be proven
         */
        function verifyWithdrawal(
            bytes32 beaconStateRoot,
            bytes32[] calldata withdrawalFields,
            WithdrawalProof calldata withdrawalProof,
            uint64 denebForkTimestamp
        ) internal view {
            require(
                withdrawalFields.length == 2 ** WITHDRAWAL_FIELD_TREE_HEIGHT,
                "BeaconChainProofs.verifyWithdrawal: withdrawalFields has incorrect length"
            );
    
            require(
                withdrawalProof.blockRootIndex < 2 ** BLOCK_ROOTS_TREE_HEIGHT,
                "BeaconChainProofs.verifyWithdrawal: blockRootIndex is too large"
            );
            require(
                withdrawalProof.withdrawalIndex < 2 ** WITHDRAWALS_TREE_HEIGHT,
                "BeaconChainProofs.verifyWithdrawal: withdrawalIndex is too large"
            );
    
            require(
                withdrawalProof.historicalSummaryIndex < 2 ** HISTORICAL_SUMMARIES_TREE_HEIGHT,
                "BeaconChainProofs.verifyWithdrawal: historicalSummaryIndex is too large"
            );
    
            //Note: post deneb hard fork, the number of exection payload header fields increased from 15->17, adding an extra level to the tree height
            uint256 executionPayloadHeaderFieldTreeHeight = (getWithdrawalTimestamp(withdrawalProof) < denebForkTimestamp) ? EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA : EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB;
            require(
                withdrawalProof.withdrawalProof.length ==
                    32 * (executionPayloadHeaderFieldTreeHeight + WITHDRAWALS_TREE_HEIGHT + 1),
                "BeaconChainProofs.verifyWithdrawal: withdrawalProof has incorrect length"
            );
            require(
                withdrawalProof.executionPayloadProof.length ==
                    32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT + BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT),
                "BeaconChainProofs.verifyWithdrawal: executionPayloadProof has incorrect length"
            );
            require(
                withdrawalProof.slotProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT),
                "BeaconChainProofs.verifyWithdrawal: slotProof has incorrect length"
            );
            require(
                withdrawalProof.timestampProof.length == 32 * (executionPayloadHeaderFieldTreeHeight),
                "BeaconChainProofs.verifyWithdrawal: timestampProof has incorrect length"
            );
    
            require(
                withdrawalProof.historicalSummaryBlockRootProof.length ==
                    32 *
                        (BEACON_STATE_FIELD_TREE_HEIGHT +
                            (HISTORICAL_SUMMARIES_TREE_HEIGHT + 1) +
                            1 +
                            (BLOCK_ROOTS_TREE_HEIGHT)),
                "BeaconChainProofs.verifyWithdrawal: historicalSummaryBlockRootProof has incorrect length"
            );
            /**
             * Note: Here, the "1" in "1 + (BLOCK_ROOTS_TREE_HEIGHT)" signifies that extra step of choosing the "block_root_summary" within the individual
             * "historical_summary". Everywhere else it signifies merkelize_with_mixin, where the length of an array is hashed with the root of the array,
             * but not here.
             */
            uint256 historicalBlockHeaderIndex = (HISTORICAL_SUMMARIES_INDEX <<
                ((HISTORICAL_SUMMARIES_TREE_HEIGHT + 1) + 1 + (BLOCK_ROOTS_TREE_HEIGHT))) |
                (uint256(withdrawalProof.historicalSummaryIndex) << (1 + (BLOCK_ROOTS_TREE_HEIGHT))) |
                (BLOCK_SUMMARY_ROOT_INDEX << (BLOCK_ROOTS_TREE_HEIGHT)) |
                uint256(withdrawalProof.blockRootIndex);
    
            require(
                Merkle.verifyInclusionSha256({
                    proof: withdrawalProof.historicalSummaryBlockRootProof,
                    root: beaconStateRoot,
                    leaf: withdrawalProof.blockRoot,
                    index: historicalBlockHeaderIndex
                }),
                "BeaconChainProofs.verifyWithdrawal: Invalid historicalsummary merkle proof"
            );
    
            //Next we verify the slot against the blockRoot
            require(
                Merkle.verifyInclusionSha256({
                    proof: withdrawalProof.slotProof,
                    root: withdrawalProof.blockRoot,
                    leaf: withdrawalProof.slotRoot,
                    index: SLOT_INDEX
                }),
                "BeaconChainProofs.verifyWithdrawal: Invalid slot merkle proof"
            );
    
            {
                // Next we verify the executionPayloadRoot against the blockRoot
                uint256 executionPayloadIndex = (BODY_ROOT_INDEX << (BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT)) |
                    EXECUTION_PAYLOAD_INDEX;
                require(
                    Merkle.verifyInclusionSha256({
                        proof: withdrawalProof.executionPayloadProof,
                        root: withdrawalProof.blockRoot,
                        leaf: withdrawalProof.executionPayloadRoot,
                        index: executionPayloadIndex
                    }),
                    "BeaconChainProofs.verifyWithdrawal: Invalid executionPayload merkle proof"
                );
            }
    
            // Next we verify the timestampRoot against the executionPayload root
            require(
                Merkle.verifyInclusionSha256({
                    proof: withdrawalProof.timestampProof,
                    root: withdrawalProof.executionPayloadRoot,
                    leaf: withdrawalProof.timestampRoot,
                    index: TIMESTAMP_INDEX
                }),
                "BeaconChainProofs.verifyWithdrawal: Invalid timestamp merkle proof"
            );
    
            {
                /**
                 * Next we verify the withdrawal fields against the executionPayloadRoot:
                 * First we compute the withdrawal_index, then we merkleize the 
                 * withdrawalFields container to calculate the withdrawalRoot.
                 *
                 * Note: Merkleization of the withdrawals root tree uses MerkleizeWithMixin, i.e., the length of the array is hashed with the root of
                 * the array.  Thus we shift the WITHDRAWALS_INDEX over by WITHDRAWALS_TREE_HEIGHT + 1 and not just WITHDRAWALS_TREE_HEIGHT.
                 */
                uint256 withdrawalIndex = (WITHDRAWALS_INDEX << (WITHDRAWALS_TREE_HEIGHT + 1)) |
                    uint256(withdrawalProof.withdrawalIndex);
                bytes32 withdrawalRoot = Merkle.merkleizeSha256(withdrawalFields);
                require(
                    Merkle.verifyInclusionSha256({
                        proof: withdrawalProof.withdrawalProof,
                        root: withdrawalProof.executionPayloadRoot,
                        leaf: withdrawalRoot,
                        index: withdrawalIndex
                    }),
                    "BeaconChainProofs.verifyWithdrawal: Invalid withdrawal merkle proof"
                );
            }
        }
    
        /**
         * @notice This function replicates the ssz hashing of a validator's pubkey, outlined below:
         *  hh := ssz.NewHasher()
         *  hh.PutBytes(validatorPubkey[:])
         *  validatorPubkeyHash := hh.Hash()
         *  hh.Reset()
         */
        function hashValidatorBLSPubkey(bytes memory validatorPubkey) internal pure returns (bytes32 pubkeyHash) {
            require(validatorPubkey.length == 48, "Input should be 48 bytes in length");
            return sha256(abi.encodePacked(validatorPubkey, bytes16(0)));
        }
    
        /**
         * @dev Retrieve the withdrawal timestamp
         */
        function getWithdrawalTimestamp(WithdrawalProof memory withdrawalProof) internal pure returns (uint64) {
            return
                Endian.fromLittleEndianUint64(withdrawalProof.timestampRoot);
        }
    
        /**
         * @dev Converts the withdrawal's slot to an epoch
         */
        function getWithdrawalEpoch(WithdrawalProof memory withdrawalProof) internal pure returns (uint64) {
            return
                Endian.fromLittleEndianUint64(withdrawalProof.slotRoot) / SLOTS_PER_EPOCH;
        }
    
        /**
         * Indices for validator fields (refer to consensus specs):
         * 0: pubkey
         * 1: withdrawal credentials
         * 2: effective balance
         * 3: slashed?
         * 4: activation elligibility epoch
         * 5: activation epoch
         * 6: exit epoch
         * 7: withdrawable epoch
         */
    
        /**
         * @dev Retrieves a validator's pubkey hash
         */
        function getPubkeyHash(bytes32[] memory validatorFields) internal pure returns (bytes32) {
            return 
                validatorFields[VALIDATOR_PUBKEY_INDEX];
        }
    
        function getWithdrawalCredentials(bytes32[] memory validatorFields) internal pure returns (bytes32) {
            return
                validatorFields[VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX];
        }
    
        /**
         * @dev Retrieves a validator's effective balance (in gwei)
         */
        function getEffectiveBalanceGwei(bytes32[] memory validatorFields) internal pure returns (uint64) {
            return 
                Endian.fromLittleEndianUint64(validatorFields[VALIDATOR_BALANCE_INDEX]);
        }
    
        /**
         * @dev Retrieves a validator's withdrawable epoch
         */
        function getWithdrawableEpoch(bytes32[] memory validatorFields) internal pure returns (uint64) {
            return 
                Endian.fromLittleEndianUint64(validatorFields[VALIDATOR_WITHDRAWABLE_EPOCH_INDEX]);
        }
    
        /**
         * Indices for withdrawal fields (refer to consensus specs):
         * 0: withdrawal index
         * 1: validator index
         * 2: execution address
         * 3: withdrawal amount
         */
    
        /**
         * @dev Retrieves a withdrawal's validator index
         */
        function getValidatorIndex(bytes32[] memory withdrawalFields) internal pure returns (uint40) {
            return 
                uint40(Endian.fromLittleEndianUint64(withdrawalFields[WITHDRAWAL_VALIDATOR_INDEX_INDEX]));
        }
    
        /**
         * @dev Retrieves a withdrawal's withdrawal amount (in gwei)
         */
        function getWithdrawalAmountGwei(bytes32[] memory withdrawalFields) internal pure returns (uint64) {
            return
                Endian.fromLittleEndianUint64(withdrawalFields[WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]);
        }
    }
    
    /**
     * @title Interface for the BeaconStateOracle contract.
     * @author Layr Labs, Inc.
     * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
     */
    interface IBeaconChainOracle {
        /// @notice The block number to state root mapping.
        function timestampToBlockRoot(uint256 timestamp) external view returns (bytes32);
    }
    
    /**
     * @title The implementation contract used for restaking beacon chain ETH on EigenLayer
     * @author Layr Labs, Inc.
     * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
     * @notice The main functionalities are:
     * - creating new ETH validators with their withdrawal credentials pointed to this contract
     * - proving from beacon chain state roots that withdrawal credentials are pointed to this contract
     * - proving from beacon chain state roots the balances of ETH validators with their withdrawal credentials
     *   pointed to this contract
     * - updating aggregate balances in the EigenPodManager
     * - withdrawing eth when withdrawals are initiated
     * @dev Note that all beacon chain balances are stored as gwei within the beacon chain datastructures. We choose
     *   to account balances in terms of gwei in the EigenPod contract and convert to wei when making calls to other contracts
     */
    interface IEigenPod {
        enum VALIDATOR_STATUS {
            INACTIVE, // doesnt exist
            ACTIVE, // staked on ethpos and withdrawal credentials are pointed to the EigenPod
            WITHDRAWN // withdrawn from the Beacon Chain
        }
    
        struct ValidatorInfo {
            // index of the validator in the beacon chain
            uint64 validatorIndex;
            // amount of beacon chain ETH restaked on EigenLayer in gwei
            uint64 restakedBalanceGwei;
            //timestamp of the validator's most recent balance update
            uint64 mostRecentBalanceUpdateTimestamp;
            // status of the validator
            VALIDATOR_STATUS status;
        }
    
        /**
         * @notice struct used to store amounts related to proven withdrawals in memory. Used to help
         * manage stack depth and optimize the number of external calls, when batching withdrawal operations.
         */
        struct VerifiedWithdrawal {
            // amount to send to a podOwner from a proven withdrawal
            uint256 amountToSendGwei;
            // difference in shares to be recorded in the eigenPodManager, as a result of the withdrawal
            int256 sharesDeltaGwei;
        }
    
        enum PARTIAL_WITHDRAWAL_CLAIM_STATUS {
            REDEEMED,
            PENDING,
            FAILED
        }
    
        /// @notice Emitted when an ETH validator stakes via this eigenPod
        event EigenPodStaked(bytes pubkey);
    
        /// @notice Emitted when an ETH validator's withdrawal credentials are successfully verified to be pointed to this eigenPod
        event ValidatorRestaked(uint40 validatorIndex);
    
        /// @notice Emitted when an ETH validator's  balance is proven to be updated.  Here newValidatorBalanceGwei
        //  is the validator's balance that is credited on EigenLayer.
        event ValidatorBalanceUpdated(uint40 validatorIndex, uint64 balanceTimestamp, uint64 newValidatorBalanceGwei);
    
        /// @notice Emitted when an ETH validator is prove to have withdrawn from the beacon chain
        event FullWithdrawalRedeemed(
            uint40 validatorIndex,
            uint64 withdrawalTimestamp,
            address indexed recipient,
            uint64 withdrawalAmountGwei
        );
    
        /// @notice Emitted when a partial withdrawal claim is successfully redeemed
        event PartialWithdrawalRedeemed(
            uint40 validatorIndex,
            uint64 withdrawalTimestamp,
            address indexed recipient,
            uint64 partialWithdrawalAmountGwei
        );
    
        /// @notice Emitted when restaked beacon chain ETH is withdrawn from the eigenPod.
        event RestakedBeaconChainETHWithdrawn(address indexed recipient, uint256 amount);
    
        /// @notice Emitted when podOwner enables restaking
        event RestakingActivated(address indexed podOwner);
    
        /// @notice Emitted when ETH is received via the `receive` fallback
        event NonBeaconChainETHReceived(uint256 amountReceived);
    
        /// @notice Emitted when ETH that was previously received via the `receive` fallback is withdrawn
        event NonBeaconChainETHWithdrawn(address indexed recipient, uint256 amountWithdrawn);
    
        /// @notice The max amount of eth, in gwei, that can be restaked per validator
        function MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() external view returns (uint64);
    
        /// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from beaconchain but not EigenLayer),
        function withdrawableRestakedExecutionLayerGwei() external view returns (uint64);
    
        /// @notice any ETH deposited into the EigenPod contract via the `receive` fallback function
        function nonBeaconChainETHBalanceWei() external view returns (uint256);
    
        /// @notice Used to initialize the pointers to contracts crucial to the pod's functionality, in beacon proxy construction from EigenPodManager
        function initialize(address owner) external;
    
        /// @notice Called by EigenPodManager when the owner wants to create another ETH validator.
        function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable;
    
        /**
         * @notice Transfers `amountWei` in ether from this contract to the specified `recipient` address
         * @notice Called by EigenPodManager to withdrawBeaconChainETH that has been added to the EigenPod's balance due to a withdrawal from the beacon chain.
         * @dev The podOwner must have already proved sufficient withdrawals, so that this pod's `withdrawableRestakedExecutionLayerGwei` exceeds the
         * `amountWei` input (when converted to GWEI).
         * @dev Reverts if `amountWei` is not a whole Gwei amount
         */
        function withdrawRestakedBeaconChainETH(address recipient, uint256 amount) external;
    
        /// @notice The single EigenPodManager for EigenLayer
        function eigenPodManager() external view returns (IEigenPodManager);
    
        /// @notice The owner of this EigenPod
        function podOwner() external view returns (address);
    
        /// @notice an indicator of whether or not the podOwner has ever "fully restaked" by successfully calling `verifyCorrectWithdrawalCredentials`.
        function hasRestaked() external view returns (bool);
    
        /**
         * @notice The latest timestamp at which the pod owner withdrew the balance of the pod, via calling `withdrawBeforeRestaking`.
         * @dev This variable is only updated when the `withdrawBeforeRestaking` function is called, which can only occur before `hasRestaked` is set to true for this pod.
         * Proofs for this pod are only valid against Beacon Chain state roots corresponding to timestamps after the stored `mostRecentWithdrawalTimestamp`.
         */
        function mostRecentWithdrawalTimestamp() external view returns (uint64);
    
        /// @notice Returns the validatorInfo struct for the provided pubkeyHash
        function validatorPubkeyHashToInfo(bytes32 validatorPubkeyHash) external view returns (ValidatorInfo memory);
    
        /// @notice Returns the validatorInfo struct for the provided pubkey
        function validatorPubkeyToInfo(bytes calldata validatorPubkey) external view returns (ValidatorInfo memory);
    
        ///@notice mapping that tracks proven withdrawals
        function provenWithdrawal(bytes32 validatorPubkeyHash, uint64 slot) external view returns (bool);
    
        /// @notice This returns the status of a given validator
        function validatorStatus(bytes32 pubkeyHash) external view returns (VALIDATOR_STATUS);
    
        /// @notice This returns the status of a given validator pubkey
        function validatorStatus(bytes calldata validatorPubkey) external view returns (VALIDATOR_STATUS);
    
        /**
         * @notice This function verifies that the withdrawal credentials of validator(s) owned by the podOwner are pointed to
         * this contract. It also verifies the effective balance  of the validator.  It verifies the provided proof of the ETH validator against the beacon chain state
         * root, marks the validator as 'active' in EigenLayer, and credits the restaked ETH in Eigenlayer.
         * @param oracleTimestamp is the Beacon Chain timestamp whose state root the `proof` will be proven against.
         * @param validatorIndices is the list of indices of the validators being proven, refer to consensus specs
         * @param withdrawalCredentialProofs is an array of proofs, where each proof proves each ETH validator's balance and withdrawal credentials
         * against a beacon chain state root
         * @param validatorFields are the fields of the "Validator Container", refer to consensus specs
         * for details: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
         */
        function verifyWithdrawalCredentials(
            uint64 oracleTimestamp,
            BeaconChainProofs.StateRootProof calldata stateRootProof,
            uint40[] calldata validatorIndices,
            bytes[] calldata withdrawalCredentialProofs,
            bytes32[][] calldata validatorFields
        )
            external;
    
        /**
         * @notice This function records an update (either increase or decrease) in the pod's balance in the StrategyManager.  
                   It also verifies a merkle proof of the validator's current beacon chain balance.  
         * @param oracleTimestamp The oracleTimestamp whose state root the `proof` will be proven against.
         *        Must be within `VERIFY_BALANCE_UPDATE_WINDOW_SECONDS` of the current block.
         * @param validatorIndices is the list of indices of the validators being proven, refer to consensus specs 
         * @param validatorFieldsProofs proofs against the `beaconStateRoot` for each validator in `validatorFields`
         * @param validatorFields are the fields of the "Validator Container", refer to consensus specs
         * @dev For more details on the Beacon Chain spec, see: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
         */
        function verifyBalanceUpdates(
            uint64 oracleTimestamp,
            uint40[] calldata validatorIndices,
            BeaconChainProofs.StateRootProof calldata stateRootProof,
            bytes[] calldata validatorFieldsProofs,
            bytes32[][] calldata validatorFields
        ) external;
    
        /**
         * @notice This function records full and partial withdrawals on behalf of one of the Ethereum validators for this EigenPod
         * @param oracleTimestamp is the timestamp of the oracle slot that the withdrawal is being proven against
         * @param withdrawalProofs is the information needed to check the veracity of the block numbers and withdrawals being proven
         * @param validatorFieldsProofs is the proof of the validator's fields' in the validator tree
         * @param withdrawalFields are the fields of the withdrawals being proven
         * @param validatorFields are the fields of the validators being proven
         */
        function verifyAndProcessWithdrawals(
            uint64 oracleTimestamp,
            BeaconChainProofs.StateRootProof calldata stateRootProof,
            BeaconChainProofs.WithdrawalProof[] calldata withdrawalProofs,
            bytes[] calldata validatorFieldsProofs,
            bytes32[][] calldata validatorFields,
            bytes32[][] calldata withdrawalFields
        ) external;
    
        /**
         * @notice Called by the pod owner to activate restaking by withdrawing
         * all existing ETH from the pod and preventing further withdrawals via
         * "withdrawBeforeRestaking()"
         */
        function activateRestaking() external;
    
        /// @notice Called by the pod owner to withdraw the balance of the pod when `hasRestaked` is set to false
        function withdrawBeforeRestaking() external;
    
        /// @notice Called by the pod owner to withdraw the nonBeaconChainETHBalanceWei
        function withdrawNonBeaconChainETHBalanceWei(address recipient, uint256 amountToWithdraw) external;
    
        /// @notice called by owner of a pod to remove any ERC20s deposited in the pod
        function recoverTokens(IERC20[] memory tokenList, uint256[] memory amountsToWithdraw, address recipient) external;
    }
    
    /**
     * @title Adds pausability to a contract, with pausing & unpausing controlled by the `pauser` and `unpauser` of a PauserRegistry contract.
     * @author Layr Labs, Inc.
     * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
     * @notice Contracts that inherit from this contract may define their own `pause` and `unpause` (and/or related) functions.
     * These functions should be permissioned as "onlyPauser" which defers to a `PauserRegistry` for determining access control.
     * @dev Pausability is implemented using a uint256, which allows up to 256 different single bit-flags; each bit can potentially pause different functionality.
     * Inspiration for this was taken from the NearBridge design here https://etherscan.io/address/0x3FEFc5A4B1c02f21cBc8D3613643ba0635b9a873#code.
     * For the `pause` and `unpause` functions we've implemented, if you pause, you can only flip (any number of) switches to on/1 (aka "paused"), and if you unpause,
     * you can only flip (any number of) switches to off/0 (aka "paused").
     * If you want a pauseXYZ function that just flips a single bit / "pausing flag", it will:
     * 1) 'bit-wise and' (aka `&`) a flag with the current paused state (as a uint256)
     * 2) update the paused state to this new value
     * @dev We note as well that we have chosen to identify flags by their *bit index* as opposed to their numerical value, so, e.g. defining `DEPOSITS_PAUSED = 3`
     * indicates specifically that if the *third bit* of `_paused` is flipped -- i.e. it is a '1' -- then deposits should be paused
     */
    
    interface IPausable {
        /// @notice Emitted when the `pauserRegistry` is set to `newPauserRegistry`.
        event PauserRegistrySet(IPauserRegistry pauserRegistry, IPauserRegistry newPauserRegistry);
    
        /// @notice Emitted when the pause is triggered by `account`, and changed to `newPausedStatus`.
        event Paused(address indexed account, uint256 newPausedStatus);
    
        /// @notice Emitted when the pause is lifted by `account`, and changed to `newPausedStatus`.
        event Unpaused(address indexed account, uint256 newPausedStatus);
        
        /// @notice Address of the `PauserRegistry` contract that this contract defers to for determining access control (for pausing).
        function pauserRegistry() external view returns (IPauserRegistry);
    
        /**
         * @notice This function is used to pause an EigenLayer contract's functionality.
         * It is permissioned to the `pauser` address, which is expected to be a low threshold multisig.
         * @param newPausedStatus represents the new value for `_paused` to take, which means it may flip several bits at once.
         * @dev This function can only pause functionality, and thus cannot 'unflip' any bit in `_paused` from 1 to 0.
         */
        function pause(uint256 newPausedStatus) external;
    
        /**
         * @notice Alias for `pause(type(uint256).max)`.
         */
        function pauseAll() external;
    
        /**
         * @notice This function is used to unpause an EigenLayer contract's functionality.
         * It is permissioned to the `unpauser` address, which is expected to be a high threshold multisig or governance contract.
         * @param newPausedStatus represents the new value for `_paused` to take, which means it may flip several bits at once.
         * @dev This function can only unpause functionality, and thus cannot 'flip' any bit in `_paused` from 0 to 1.
         */
        function unpause(uint256 newPausedStatus) external;
    
        /// @notice Returns the current paused status as a uint256.
        function paused() external view returns (uint256);
    
        /// @notice Returns 'true' if the `indexed`th bit of `_paused` is 1, and 'false' otherwise
        function paused(uint8 index) external view returns (bool);
    
        /// @notice Allows the unpauser to set a new pauser registry
        function setPauserRegistry(IPauserRegistry newPauserRegistry) external;
    }
    
    /**
     * @title Interface for factory that creates and manages solo staking pods that have their withdrawal credentials pointed to EigenLayer.
     * @author Layr Labs, Inc.
     * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
     */
    
    interface IEigenPodManager is IPausable {
        /// @notice Emitted to notify the update of the beaconChainOracle address
        event BeaconOracleUpdated(address indexed newOracleAddress);
    
        /// @notice Emitted to notify the deployment of an EigenPod
        event PodDeployed(address indexed eigenPod, address indexed podOwner);
    
        /// @notice Emitted to notify a deposit of beacon chain ETH recorded in the strategy manager
        event BeaconChainETHDeposited(address indexed podOwner, uint256 amount);
    
        /// @notice Emitted when the balance of an EigenPod is updated
        event PodSharesUpdated(address indexed podOwner, int256 sharesDelta);
    
        /// @notice Emitted when a withdrawal of beacon chain ETH is completed
        event BeaconChainETHWithdrawalCompleted(
            address indexed podOwner,
            uint256 shares,
            uint96 nonce,
            address delegatedAddress,
            address withdrawer,
            bytes32 withdrawalRoot
        );
    
        event DenebForkTimestampUpdated(uint64 newValue);
    
        /**
         * @notice Creates an EigenPod for the sender.
         * @dev Function will revert if the `msg.sender` already has an EigenPod.
         * @dev Returns EigenPod address 
         */
        function createPod() external returns (address);
    
        /**
         * @notice Stakes for a new beacon chain validator on the sender's EigenPod.
         * Also creates an EigenPod for the sender if they don't have one already.
         * @param pubkey The 48 bytes public key of the beacon chain validator.
         * @param signature The validator's signature of the deposit data.
         * @param depositDataRoot The root/hash of the deposit data for the validator's deposit.
         */
        function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable;
    
        /**
         * @notice Changes the `podOwner`'s shares by `sharesDelta` and performs a call to the DelegationManager
         * to ensure that delegated shares are also tracked correctly
         * @param podOwner is the pod owner whose balance is being updated.
         * @param sharesDelta is the change in podOwner's beaconChainETHStrategy shares
         * @dev Callable only by the podOwner's EigenPod contract.
         * @dev Reverts if `sharesDelta` is not a whole Gwei amount
         */
        function recordBeaconChainETHBalanceUpdate(address podOwner, int256 sharesDelta) external;
    
        /**
         * @notice Updates the oracle contract that provides the beacon chain state root
         * @param newBeaconChainOracle is the new oracle contract being pointed to
         * @dev Callable only by the owner of this contract (i.e. governance)
         */
        function updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) external;
    
        /// @notice Returns the address of the `podOwner`'s EigenPod if it has been deployed.
        function ownerToPod(address podOwner) external view returns (IEigenPod);
    
        /// @notice Returns the address of the `podOwner`'s EigenPod (whether it is deployed yet or not).
        function getPod(address podOwner) external view returns (IEigenPod);
    
        /// @notice The ETH2 Deposit Contract
        function ethPOS() external view returns (IETHPOSDeposit);
    
        /// @notice Beacon proxy to which the EigenPods point
        function eigenPodBeacon() external view returns (IBeacon);
    
        /// @notice Oracle contract that provides updates to the beacon chain's state
        function beaconChainOracle() external view returns (IBeaconChainOracle);
    
        /// @notice Returns the beacon block root at `timestamp`. Reverts if the Beacon block root at `timestamp` has not yet been finalized.
        function getBlockRootAtTimestamp(uint64 timestamp) external view returns (bytes32);
    
        /// @notice EigenLayer's StrategyManager contract
        function strategyManager() external view returns (IStrategyManager);
    
        /// @notice EigenLayer's Slasher contract
        function slasher() external view returns (ISlasher);
    
        /// @notice Returns 'true' if the `podOwner` has created an EigenPod, and 'false' otherwise.
        function hasPod(address podOwner) external view returns (bool);
    
        /// @notice Returns the number of EigenPods that have been created
        function numPods() external view returns (uint256);
    
        /**
         * @notice Mapping from Pod owner owner to the number of shares they have in the virtual beacon chain ETH strategy.
         * @dev The share amount can become negative. This is necessary to accommodate the fact that a pod owner's virtual beacon chain ETH shares can
         * decrease between the pod owner queuing and completing a withdrawal.
         * When the pod owner's shares would otherwise increase, this "deficit" is decreased first _instead_.
         * Likewise, when a withdrawal is completed, this "deficit" is decreased and the withdrawal amount is decreased; We can think of this
         * as the withdrawal "paying off the deficit".
         */
        function podOwnerShares(address podOwner) external view returns (int256);
    
        /// @notice returns canonical, virtual beaconChainETH strategy
        function beaconChainETHStrategy() external view returns (IStrategy);
    
        /**
         * @notice Used by the DelegationManager to remove a pod owner's shares while they're in the withdrawal queue.
         * Simply decreases the `podOwner`'s shares by `shares`, down to a minimum of zero.
         * @dev This function reverts if it would result in `podOwnerShares[podOwner]` being less than zero, i.e. it is forbidden for this function to
         * result in the `podOwner` incurring a "share deficit". This behavior prevents a Staker from queuing a withdrawal which improperly removes excessive
         * shares from the operator to whom the staker is delegated.
         * @dev Reverts if `shares` is not a whole Gwei amount
         */
        function removeShares(address podOwner, uint256 shares) external;
    
        /**
         * @notice Increases the `podOwner`'s shares by `shares`, paying off deficit if possible.
         * Used by the DelegationManager to award a pod owner shares on exiting the withdrawal queue
         * @dev Returns the number of shares added to `podOwnerShares[podOwner]` above zero, which will be less than the `shares` input
         * in the event that the podOwner has an existing shares deficit (i.e. `podOwnerShares[podOwner]` starts below zero)
         * @dev Reverts if `shares` is not a whole Gwei amount
         */
        function addShares(address podOwner, uint256 shares) external returns (uint256);
    
        /**
         * @notice Used by the DelegationManager to complete a withdrawal, sending tokens to some destination address
         * @dev Prioritizes decreasing the podOwner's share deficit, if they have one
         * @dev Reverts if `shares` is not a whole Gwei amount
         */
        function withdrawSharesAsTokens(address podOwner, address destination, uint256 shares) external;
    
        /**
         * @notice the deneb hard fork timestamp used to determine which proof path to use for proving a withdrawal
         */
        function denebForkTimestamp() external view returns (uint64);
    
         /**
         * setting the deneb hard fork timestamp by the eigenPodManager owner
         * @dev this function is designed to be called twice.  Once, it is set to type(uint64).max 
         * prior to the actual deneb fork timestamp being set, and then the second time it is set 
         * to the actual deneb fork timestamp.
         */
        function setDenebForkTimestamp(uint64 newDenebForkTimestamp) external;
    
    }
    
    /**
     * @title Interface for the primary entrypoint for funds into EigenLayer.
     * @author Layr Labs, Inc.
     * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
     * @notice See the `StrategyManager` contract itself for implementation details.
     */
    interface IStrategyManager {
        /**
         * @notice Emitted when a new deposit occurs on behalf of `staker`.
         * @param staker Is the staker who is depositing funds into EigenLayer.
         * @param strategy Is the strategy that `staker` has deposited into.
         * @param token Is the token that `staker` deposited.
         * @param shares Is the number of new shares `staker` has been granted in `strategy`.
         */
        event Deposit(address staker, IERC20 token, IStrategy strategy, uint256 shares);
    
        /// @notice Emitted when `thirdPartyTransfersForbidden` is updated for a strategy and value by the owner
        event UpdatedThirdPartyTransfersForbidden(IStrategy strategy, bool value);
    
        /// @notice Emitted when the `strategyWhitelister` is changed
        event StrategyWhitelisterChanged(address previousAddress, address newAddress);
    
        /// @notice Emitted when a strategy is added to the approved list of strategies for deposit
        event StrategyAddedToDepositWhitelist(IStrategy strategy);
    
        /// @notice Emitted when a strategy is removed from the approved list of strategies for deposit
        event StrategyRemovedFromDepositWhitelist(IStrategy strategy);
    
        /**
         * @notice Deposits `amount` of `token` into the specified `strategy`, with the resultant shares credited to `msg.sender`
         * @param strategy is the specified strategy where deposit is to be made,
         * @param token is the denomination in which the deposit is to be made,
         * @param amount is the amount of token to be deposited in the strategy by the staker
         * @return shares The amount of new shares in the `strategy` created as part of the action.
         * @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf.
         * @dev Cannot be called by an address that is 'frozen' (this function will revert if the `msg.sender` is frozen).
         *
         * WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended.  This can lead to attack vectors
         *          where the token balance and corresponding strategy shares are not in sync upon reentrancy.
         */
        function depositIntoStrategy(IStrategy strategy, IERC20 token, uint256 amount) external returns (uint256 shares);
    
        /**
         * @notice Used for depositing an asset into the specified strategy with the resultant shares credited to `staker`,
         * who must sign off on the action.
         * Note that the assets are transferred out/from the `msg.sender`, not from the `staker`; this function is explicitly designed
         * purely to help one address deposit 'for' another.
         * @param strategy is the specified strategy where deposit is to be made,
         * @param token is the denomination in which the deposit is to be made,
         * @param amount is the amount of token to be deposited in the strategy by the staker
         * @param staker the staker that the deposited assets will be credited to
         * @param expiry the timestamp at which the signature expires
         * @param signature is a valid signature from the `staker`. either an ECDSA signature if the `staker` is an EOA, or data to forward
         * following EIP-1271 if the `staker` is a contract
         * @return shares The amount of new shares in the `strategy` created as part of the action.
         * @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf.
         * @dev A signature is required for this function to eliminate the possibility of griefing attacks, specifically those
         * targeting stakers who may be attempting to undelegate.
         * @dev Cannot be called if thirdPartyTransfersForbidden is set to true for this strategy
         *
         *  WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended.  This can lead to attack vectors
         *          where the token balance and corresponding strategy shares are not in sync upon reentrancy
         */
        function depositIntoStrategyWithSignature(
            IStrategy strategy,
            IERC20 token,
            uint256 amount,
            address staker,
            uint256 expiry,
            bytes memory signature
        ) external returns (uint256 shares);
    
        /// @notice Used by the DelegationManager to remove a Staker's shares from a particular strategy when entering the withdrawal queue
        function removeShares(address staker, IStrategy strategy, uint256 shares) external;
    
        /// @notice Used by the DelegationManager to award a Staker some shares that have passed through the withdrawal queue
        function addShares(address staker, IERC20 token, IStrategy strategy, uint256 shares) external;
        
        /// @notice Used by the DelegationManager to convert withdrawn shares to tokens and send them to a recipient
        function withdrawSharesAsTokens(address recipient, IStrategy strategy, uint256 shares, IERC20 token) external;
    
        /// @notice Returns the current shares of `user` in `strategy`
        function stakerStrategyShares(address user, IStrategy strategy) external view returns (uint256 shares);
    
        /**
         * @notice Get all details on the staker's deposits and corresponding shares
         * @return (staker's strategies, shares in these strategies)
         */
        function getDeposits(address staker) external view returns (IStrategy[] memory, uint256[] memory);
    
        /// @notice Simple getter function that returns `stakerStrategyList[staker].length`.
        function stakerStrategyListLength(address staker) external view returns (uint256);
    
        /**
         * @notice Owner-only function that adds the provided Strategies to the 'whitelist' of strategies that stakers can deposit into
         * @param strategiesToWhitelist Strategies that will be added to the `strategyIsWhitelistedForDeposit` mapping (if they aren't in it already)
         * @param thirdPartyTransfersForbiddenValues bool values to set `thirdPartyTransfersForbidden` to for each strategy
         */
        function addStrategiesToDepositWhitelist(
            IStrategy[] calldata strategiesToWhitelist,
            bool[] calldata thirdPartyTransfersForbiddenValues
        ) external;
    
        /**
         * @notice Owner-only function that removes the provided Strategies from the 'whitelist' of strategies that stakers can deposit into
         * @param strategiesToRemoveFromWhitelist Strategies that will be removed to the `strategyIsWhitelistedForDeposit` mapping (if they are in it)
         */
        function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external;
    
        /// @notice Returns the single, central Delegation contract of EigenLayer
        function delegation() external view returns (IDelegationManager);
    
        /// @notice Returns the single, central Slasher contract of EigenLayer
        function slasher() external view returns (ISlasher);
    
        /// @notice Returns the EigenPodManager contract of EigenLayer
        function eigenPodManager() external view returns (IEigenPodManager);
    
        /// @notice Returns the address of the `strategyWhitelister`
        function strategyWhitelister() external view returns (address);
    
        /**
         * @notice Returns bool for whether or not `strategy` enables credit transfers. i.e enabling
         * depositIntoStrategyWithSignature calls or queueing withdrawals to a different address than the staker.
         */
        function thirdPartyTransfersForbidden(IStrategy strategy) external view returns (bool);
    
    // LIMITED BACKWARDS-COMPATIBILITY FOR DEPRECATED FUNCTIONALITY
        // packed struct for queued withdrawals; helps deal with stack-too-deep errors
        struct DeprecatedStruct_WithdrawerAndNonce {
            address withdrawer;
            uint96 nonce;
        }
    
        /**
         * Struct type used to specify an existing queued withdrawal. Rather than storing the entire struct, only a hash is stored.
         * In functions that operate on existing queued withdrawals -- e.g. `startQueuedWithdrawalWaitingPeriod` or `completeQueuedWithdrawal`,
         * the data is resubmitted and the hash of the submitted data is computed by `calculateWithdrawalRoot` and checked against the
         * stored hash in order to confirm the integrity of the submitted data.
         */
        struct DeprecatedStruct_QueuedWithdrawal {
            IStrategy[] strategies;
            uint256[] shares;
            address staker;
            DeprecatedStruct_WithdrawerAndNonce withdrawerAndNonce;
            uint32 withdrawalStartBlock;
            address delegatedAddress;
        }
    
        function migrateQueuedWithdrawal(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) external returns (bool, bytes32);
    
        function calculateWithdrawalRoot(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) external pure returns (bytes32);
    }
    
    /**
     * @title DelegationManager
     * @author Layr Labs, Inc.
     * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
     * @notice  This is the contract for delegation in EigenLayer. The main functionalities of this contract are
     * - enabling anyone to register as an operator in EigenLayer
     * - allowing operators to specify parameters related to stakers who delegate to them
     * - enabling any staker to delegate its stake to the operator of its choice (a given staker can only delegate to a single operator at a time)
     * - enabling a staker to undelegate its assets from the operator it is delegated to (performed as part of the withdrawal process, initiated through the StrategyManager)
     */
    interface IDelegationManager is ISignatureUtils {
        // @notice Struct used for storing information about a single operator who has registered with EigenLayer
        struct OperatorDetails {
            // @notice address to receive the rewards that the operator earns via serving applications built on EigenLayer.
            address earningsReceiver;
            /**
             * @notice Address to verify signatures when a staker wishes to delegate to the operator, as well as controlling "forced undelegations".
             * @dev Signature verification follows these rules:
             * 1) If this address is left as address(0), then any staker will be free to delegate to the operator, i.e. no signature verification will be performed.
             * 2) If this address is an EOA (i.e. it has no code), then we follow standard ECDSA signature verification for delegations to the operator.
             * 3) If this address is a contract (i.e. it has code) then we forward a call to the contract and verify that it returns the correct EIP-1271 "magic value".
             */
            address delegationApprover;
            /**
             * @notice A minimum delay -- measured in blocks -- enforced between:
             * 1) the operator signalling their intent to register for a service, via calling `Slasher.optIntoSlashing`
             * and
             * 2) the operator completing registration for the service, via the service ultimately calling `Slasher.recordFirstStakeUpdate`
             * @dev note that for a specific operator, this value *cannot decrease*, i.e. if the operator wishes to modify their OperatorDetails,
             * then they are only allowed to either increase this value or keep it the same.
             */
            uint32 stakerOptOutWindowBlocks;
        }
    
        /**
         * @notice Abstract struct used in calculating an EIP712 signature for a staker to approve that they (the staker themselves) delegate to a specific operator.
         * @dev Used in computing the `STAKER_DELEGATION_TYPEHASH` and as a reference in the computation of the stakerDigestHash in the `delegateToBySignature` function.
         */
        struct StakerDelegation {
            // the staker who is delegating
            address staker;
            // the operator being delegated to
            address operator;
            // the staker's nonce
            uint256 nonce;
            // the expiration timestamp (UTC) of the signature
            uint256 expiry;
        }
    
        /**
         * @notice Abstract struct used in calculating an EIP712 signature for an operator's delegationApprover to approve that a specific staker delegate to the operator.
         * @dev Used in computing the `DELEGATION_APPROVAL_TYPEHASH` and as a reference in the computation of the approverDigestHash in the `_delegate` function.
         */
        struct DelegationApproval {
            // the staker who is delegating
            address staker;
            // the operator being delegated to
            address operator;
            // the operator's provided salt
            bytes32 salt;
            // the expiration timestamp (UTC) of the signature
            uint256 expiry;
        }
    
        /**
         * Struct type used to specify an existing queued withdrawal. Rather than storing the entire struct, only a hash is stored.
         * In functions that operate on existing queued withdrawals -- e.g. completeQueuedWithdrawal`, the data is resubmitted and the hash of the submitted
         * data is computed by `calculateWithdrawalRoot` and checked against the stored hash in order to confirm the integrity of the submitted data.
         */
        struct Withdrawal {
            // The address that originated the Withdrawal
            address staker;
            // The address that the staker was delegated to at the time that the Withdrawal was created
            address delegatedTo;
            // The address that can complete the Withdrawal + will receive funds when completing the withdrawal
            address withdrawer;
            // Nonce used to guarantee that otherwise identical withdrawals have unique hashes
            uint256 nonce;
            // Block number when the Withdrawal was created
            uint32 startBlock;
            // Array of strategies that the Withdrawal contains
            IStrategy[] strategies;
            // Array containing the amount of shares in each Strategy in the `strategies` array
            uint256[] shares;
        }
    
        struct QueuedWithdrawalParams {
            // Array of strategies that the QueuedWithdrawal contains
            IStrategy[] strategies;
            // Array containing the amount of shares in each Strategy in the `strategies` array
            uint256[] shares;
            // The address of the withdrawer
            address withdrawer;
        }
    
        // @notice Emitted when a new operator registers in EigenLayer and provides their OperatorDetails.
        event OperatorRegistered(address indexed operator, OperatorDetails operatorDetails);
    
        /// @notice Emitted when an operator updates their OperatorDetails to @param newOperatorDetails
        event OperatorDetailsModified(address indexed operator, OperatorDetails newOperatorDetails);
    
        /**
         * @notice Emitted when @param operator indicates that they are updating their MetadataURI string
         * @dev Note that these strings are *never stored in storage* and are instead purely emitted in events for off-chain indexing
         */
        event OperatorMetadataURIUpdated(address indexed operator, string metadataURI);
    
        /// @notice Emitted whenever an operator's shares are increased for a given strategy. Note that shares is the delta in the operator's shares.
        event OperatorSharesIncreased(address indexed operator, address staker, IStrategy strategy, uint256 shares);
    
        /// @notice Emitted whenever an operator's shares are decreased for a given strategy. Note that shares is the delta in the operator's shares.
        event OperatorSharesDecreased(address indexed operator, address staker, IStrategy strategy, uint256 shares);
    
        /// @notice Emitted when @param staker delegates to @param operator.
        event StakerDelegated(address indexed staker, address indexed operator);
    
        /// @notice Emitted when @param staker undelegates from @param operator.
        event StakerUndelegated(address indexed staker, address indexed operator);
    
        /// @notice Emitted when @param staker is undelegated via a call not originating from the staker themself
        event StakerForceUndelegated(address indexed staker, address indexed operator);
    
        /**
         * @notice Emitted when a new withdrawal is queued.
         * @param withdrawalRoot Is the hash of the `withdrawal`.
         * @param withdrawal Is the withdrawal itself.
         */
        event WithdrawalQueued(bytes32 withdrawalRoot, Withdrawal withdrawal);
    
        /// @notice Emitted when a queued withdrawal is completed
        event WithdrawalCompleted(bytes32 withdrawalRoot);
    
        /// @notice Emitted when a queued withdrawal is *migrated* from the StrategyManager to the DelegationManager
        event WithdrawalMigrated(bytes32 oldWithdrawalRoot, bytes32 newWithdrawalRoot);
        
        /// @notice Emitted when the `minWithdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
        event MinWithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue);
    
        /// @notice Emitted when the `strategyWithdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
        event StrategyWithdrawalDelayBlocksSet(IStrategy strategy, uint256 previousValue, uint256 newValue);
    
        /**
         * @notice Registers the caller as an operator in EigenLayer.
         * @param registeringOperatorDetails is the `OperatorDetails` for the operator.
         * @param metadataURI is a URI for the operator's metadata, i.e. a link providing more details on the operator.
         *
         * @dev Once an operator is registered, they cannot 'deregister' as an operator, and they will forever be considered "delegated to themself".
         * @dev This function will revert if the caller attempts to set their `earningsReceiver` to address(0).
         * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `OperatorMetadataURIUpdated` event
         */
        function registerAsOperator(
            OperatorDetails calldata registeringOperatorDetails,
            string calldata metadataURI
        ) external;
    
        /**
         * @notice Updates an operator's stored `OperatorDetails`.
         * @param newOperatorDetails is the updated `OperatorDetails` for the operator, to replace their current OperatorDetails`.
         *
         * @dev The caller must have previously registered as an operator in EigenLayer.
         * @dev This function will revert if the caller attempts to set their `earningsReceiver` to address(0).
         */
        function modifyOperatorDetails(OperatorDetails calldata newOperatorDetails) external;
    
        /**
         * @notice Called by an operator to emit an `OperatorMetadataURIUpdated` event indicating the information has updated.
         * @param metadataURI The URI for metadata associated with an operator
         * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `OperatorMetadataURIUpdated` event
         */
        function updateOperatorMetadataURI(string calldata metadataURI) external;
    
        /**
         * @notice Caller delegates their stake to an operator.
         * @param operator The account (`msg.sender`) is delegating its assets to for use in serving applications built on EigenLayer.
         * @param approverSignatureAndExpiry Verifies the operator approves of this delegation
         * @param approverSalt A unique single use value tied to an individual signature.
         * @dev The approverSignatureAndExpiry is used in the event that:
         *          1) the operator's `delegationApprover` address is set to a non-zero value.
         *                  AND
         *          2) neither the operator nor their `delegationApprover` is the `msg.sender`, since in the event that the operator
         *             or their delegationApprover is the `msg.sender`, then approval is assumed.
         * @dev In the event that `approverSignatureAndExpiry` is not checked, its content is ignored entirely; it's recommended to use an empty input
         * in this case to save on complexity + gas costs
         */
        function delegateTo(
            address operator,
            SignatureWithExpiry memory approverSignatureAndExpiry,
            bytes32 approverSalt
        ) external;
    
        /**
         * @notice Caller delegates a staker's stake to an operator with valid signatures from both parties.
         * @param staker The account delegating stake to an `operator` account
         * @param operator The account (`staker`) is delegating its assets to for use in serving applications built on EigenLayer.
         * @param stakerSignatureAndExpiry Signed data from the staker authorizing delegating stake to an operator
         * @param approverSignatureAndExpiry is a parameter that will be used for verifying that the operator approves of this delegation action in the event that:
         * @param approverSalt Is a salt used to help guarantee signature uniqueness. Each salt can only be used once by a given approver.
         *
         * @dev If `staker` is an EOA, then `stakerSignature` is verified to be a valid ECDSA stakerSignature from `staker`, indicating their intention for this action.
         * @dev If `staker` is a contract, then `stakerSignature` will be checked according to EIP-1271.
         * @dev the operator's `delegationApprover` address is set to a non-zero value.
         * @dev neither the operator nor their `delegationApprover` is the `msg.sender`, since in the event that the operator or their delegationApprover
         * is the `msg.sender`, then approval is assumed.
         * @dev This function will revert if the current `block.timestamp` is equal to or exceeds the expiry
         * @dev In the case that `approverSignatureAndExpiry` is not checked, its content is ignored entirely; it's recommended to use an empty input
         * in this case to save on complexity + gas costs
         */
        function delegateToBySignature(
            address staker,
            address operator,
            SignatureWithExpiry memory stakerSignatureAndExpiry,
            SignatureWithExpiry memory approverSignatureAndExpiry,
            bytes32 approverSalt
        ) external;
    
        /**
         * @notice Undelegates the staker from the operator who they are delegated to. Puts the staker into the "undelegation limbo" mode of the EigenPodManager
         * and queues a withdrawal of all of the staker's shares in the StrategyManager (to the staker), if necessary.
         * @param staker The account to be undelegated.
         * @return withdrawalRoot The root of the newly queued withdrawal, if a withdrawal was queued. Otherwise just bytes32(0).
         *
         * @dev Reverts if the `staker` is also an operator, since operators are not allowed to undelegate from themselves.
         * @dev Reverts if the caller is not the staker, nor the operator who the staker is delegated to, nor the operator's specified "delegationApprover"
         * @dev Reverts if the `staker` is already undelegated.
         */
        function undelegate(address staker) external returns (bytes32[] memory withdrawalRoot);
    
        /**
         * Allows a staker to withdraw some shares. Withdrawn shares/strategies are immediately removed
         * from the staker. If the staker is delegated, withdrawn shares/strategies are also removed from
         * their operator.
         *
         * All withdrawn shares/strategies are placed in a queue and can be fully withdrawn after a delay.
         */
        function queueWithdrawals(
            QueuedWithdrawalParams[] calldata queuedWithdrawalParams
        ) external returns (bytes32[] memory);
    
        /**
         * @notice Used to complete the specified `withdrawal`. The caller must match `withdrawal.withdrawer`
         * @param withdrawal The Withdrawal to complete.
         * @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `withdrawal.strategies` array.
         * This input can be provided with zero length if `receiveAsTokens` is set to 'false' (since in that case, this input will be unused)
         * @param middlewareTimesIndex is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array
         * @param receiveAsTokens If true, the shares specified in the withdrawal will be withdrawn from the specified strategies themselves
         * and sent to the caller, through calls to `withdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies
         * will simply be transferred to the caller directly.
         * @dev middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw`
         * @dev beaconChainETHStrategy shares are non-transferrable, so if `receiveAsTokens = false` and `withdrawal.withdrawer != withdrawal.staker`, note that
         * any beaconChainETHStrategy shares in the `withdrawal` will be _returned to the staker_, rather than transferred to the withdrawer, unlike shares in
         * any other strategies, which will be transferred to the withdrawer.
         */
        function completeQueuedWithdrawal(
            Withdrawal calldata withdrawal,
            IERC20[] calldata tokens,
            uint256 middlewareTimesIndex,
            bool receiveAsTokens
        ) external;
    
        /**
         * @notice Array-ified version of `completeQueuedWithdrawal`.
         * Used to complete the specified `withdrawals`. The function caller must match `withdrawals[...].withdrawer`
         * @param withdrawals The Withdrawals to complete.
         * @param tokens Array of tokens for each Withdrawal. See `completeQueuedWithdrawal` for the usage of a single array.
         * @param middlewareTimesIndexes One index to reference per Withdrawal. See `completeQueuedWithdrawal` for the usage of a single index.
         * @param receiveAsTokens Whether or not to complete each withdrawal as tokens. See `completeQueuedWithdrawal` for the usage of a single boolean.
         * @dev See `completeQueuedWithdrawal` for relevant dev tags
         */
        function completeQueuedWithdrawals(
            Withdrawal[] calldata withdrawals,
            IERC20[][] calldata tokens,
            uint256[] calldata middlewareTimesIndexes,
            bool[] calldata receiveAsTokens
        ) external;
    
        /**
         * @notice Increases a staker's delegated share balance in a strategy.
         * @param staker The address to increase the delegated shares for their operator.
         * @param strategy The strategy in which to increase the delegated shares.
         * @param shares The number of shares to increase.
         *
         * @dev *If the staker is actively delegated*, then increases the `staker`'s delegated shares in `strategy` by `shares`. Otherwise does nothing.
         * @dev Callable only by the StrategyManager or EigenPodManager.
         */
        function increaseDelegatedShares(
            address staker,
            IStrategy strategy,
            uint256 shares
        ) external;
    
        /**
         * @notice Decreases a staker's delegated share balance in a strategy.
         * @param staker The address to increase the delegated shares for their operator.
         * @param strategy The strategy in which to decrease the delegated shares.
         * @param shares The number of shares to decrease.
         *
         * @dev *If the staker is actively delegated*, then decreases the `staker`'s delegated shares in `strategy` by `shares`. Otherwise does nothing.
         * @dev Callable only by the StrategyManager or EigenPodManager.
         */
        function decreaseDelegatedShares(
            address staker,
            IStrategy strategy,
            uint256 shares
        ) external;
    
        /**
         * @notice returns the address of the operator that `staker` is delegated to.
         * @notice Mapping: staker => operator whom the staker is currently delegated to.
         * @dev Note that returning address(0) indicates that the staker is not actively delegated to any operator.
         */
        function delegatedTo(address staker) external view returns (address);
    
        /**
         * @notice Returns the OperatorDetails struct associated with an `operator`.
         */
        function operatorDetails(address operator) external view returns (OperatorDetails memory);
    
        /*
         * @notice Returns the earnings receiver address for an operator
         */
        function earningsReceiver(address operator) external view returns (address);
    
        /**
         * @notice Returns the delegationApprover account for an operator
         */
        function delegationApprover(address operator) external view returns (address);
    
        /**
         * @notice Returns the stakerOptOutWindowBlocks for an operator
         */
        function stakerOptOutWindowBlocks(address operator) external view returns (uint256);
    
        /**
         * @notice Given array of strategies, returns array of shares for the operator
         */
        function getOperatorShares(
            address operator,
            IStrategy[] memory strategies
        ) external view returns (uint256[] memory);
    
        /**
         * @notice Given a list of strategies, return the minimum number of blocks that must pass to withdraw
         * from all the inputted strategies. Return value is >= minWithdrawalDelayBlocks as this is the global min withdrawal delay.
         * @param strategies The strategies to check withdrawal delays for
         */
        function getWithdrawalDelay(IStrategy[] calldata strategies) external view returns (uint256);
    
        /**
         * @notice returns the total number of shares in `strategy` that are delegated to `operator`.
         * @notice Mapping: operator => strategy => total number of shares in the strategy delegated to the operator.
         * @dev By design, the following invariant should hold for each Strategy:
         * (operator's shares in delegation manager) = sum (shares above zero of all stakers delegated to operator)
         * = sum (delegateable shares of all stakers delegated to the operator)
         */
        function operatorShares(address operator, IStrategy strategy) external view returns (uint256);
    
        /**
         * @notice Returns 'true' if `staker` *is* actively delegated, and 'false' otherwise.
         */
        function isDelegated(address staker) external view returns (bool);
    
        /**
         * @notice Returns true is an operator has previously registered for delegation.
         */
        function isOperator(address operator) external view returns (bool);
    
        /// @notice Mapping: staker => number of signed delegation nonces (used in `delegateToBySignature`) from the staker that the contract has already checked
        function stakerNonce(address staker) external view returns (uint256);
    
        /**
         * @notice Mapping: delegationApprover => 32-byte salt => whether or not the salt has already been used by the delegationApprover.
         * @dev Salts are used in the `delegateTo` and `delegateToBySignature` functions. Note that these functions only process the delegationApprover's
         * signature + the provided salt if the operator being delegated to has specified a nonzero address as their `delegationApprover`.
         */
        function delegationApproverSaltIsSpent(address _delegationApprover, bytes32 salt) external view returns (bool);
    
        /**
         * @notice Minimum delay enforced by this contract for completing queued withdrawals. Measured in blocks, and adjustable by this contract's owner,
         * up to a maximum of `MAX_WITHDRAWAL_DELAY_BLOCKS`. Minimum value is 0 (i.e. no delay enforced).
         * Note that strategies each have a separate withdrawal delay, which can be greater than this value. So the minimum number of blocks that must pass
         * to withdraw a strategy is MAX(minWithdrawalDelayBlocks, strategyWithdrawalDelayBlocks[strategy])
         */
        function minWithdrawalDelayBlocks() external view returns (uint256);
    
        /**
         * @notice Minimum delay enforced by this contract per Strategy for completing queued withdrawals. Measured in blocks, and adjustable by this contract's owner,
         * up to a maximum of `MAX_WITHDRAWAL_DELAY_BLOCKS`. Minimum value is 0 (i.e. no delay enforced).
         */
        function strategyWithdrawalDelayBlocks(IStrategy strategy) external view returns (uint256);
    
        /**
         * @notice Calculates the digestHash for a `staker` to sign to delegate to an `operator`
         * @param staker The signing staker
         * @param operator The operator who is being delegated to
         * @param expiry The desired expiry time of the staker's signature
         */
        function calculateCurrentStakerDelegationDigestHash(
            address staker,
            address operator,
            uint256 expiry
        ) external view returns (bytes32);
    
        /**
         * @notice Calculates the digest hash to be signed and used in the `delegateToBySignature` function
         * @param staker The signing staker
         * @param _stakerNonce The nonce of the staker. In practice we use the staker's current nonce, stored at `stakerNonce[staker]`
         * @param operator The operator who is being delegated to
         * @param expiry The desired expiry time of the staker's signature
         */
        function calculateStakerDelegationDigestHash(
            address staker,
            uint256 _stakerNonce,
            address operator,
            uint256 expiry
        ) external view returns (bytes32);
    
        /**
         * @notice Calculates the digest hash to be signed by the operator's delegationApprove and used in the `delegateTo` and `delegateToBySignature` functions.
         * @param staker The account delegating their stake
         * @param operator The account receiving delegated stake
         * @param _delegationApprover the operator's `delegationApprover` who will be signing the delegationHash (in general)
         * @param approverSalt A unique and single use value associated with the approver signature.
         * @param expiry Time after which the approver's signature becomes invalid
         */
        function calculateDelegationApprovalDigestHash(
            address staker,
            address operator,
            address _delegationApprover,
            bytes32 approverSalt,
            uint256 expiry
        ) external view returns (bytes32);
    
        /// @notice The EIP-712 typehash for the contract's domain
        function DOMAIN_TYPEHASH() external view returns (bytes32);
    
        /// @notice The EIP-712 typehash for the StakerDelegation struct used by the contract
        function STAKER_DELEGATION_TYPEHASH() external view returns (bytes32);
    
        /// @notice The EIP-712 typehash for the DelegationApproval struct used by the contract
        function DELEGATION_APPROVAL_TYPEHASH() external view returns (bytes32);
    
        /**
         * @notice Getter function for the current EIP-712 domain separator for this contract.
         *
         * @dev The domain separator will change in the event of a fork that changes the ChainID.
         * @dev By introducing a domain separator the DApp developers are guaranteed that there can be no signature collision.
         * for more detailed information please read EIP-712.
         */
        function domainSeparator() external view returns (bytes32);
        
        /// @notice Mapping: staker => cumulative number of queued withdrawals they have ever initiated.
        /// @dev This only increments (doesn't decrement), and is used to help ensure that otherwise identical withdrawals have unique hashes.
        function cumulativeWithdrawalsQueued(address staker) external view returns (uint256);
    
        /// @notice Returns the keccak256 hash of `withdrawal`.
        function calculateWithdrawalRoot(Withdrawal memory withdrawal) external pure returns (bytes32);
    
        function migrateQueuedWithdrawals(IStrategyManager.DeprecatedStruct_QueuedWithdrawal[] memory withdrawalsToQueue) external;
    }
    
    /**
     * @title Interface for a `Registry` that keeps track of stakes of operators for up to 256 quorums.
     * @author Layr Labs, Inc.
     */
    interface IStakeRegistry is IRegistry {
        
        // DATA STRUCTURES
    
        /// @notice struct used to store the stakes of an individual operator or the sum of all operators' stakes, for storage
        struct StakeUpdate {
            // the block number at which the stake amounts were updated and stored
            uint32 updateBlockNumber;
            // the block number at which the *next update* occurred.
            /// @notice This entry has the value **0** until another update takes place.
            uint32 nextUpdateBlockNumber;
            // stake weight for the quorum
            uint96 stake;
        }
    
        /**
         * @notice In weighing a particular strategy, the amount of underlying asset for that strategy is
         * multiplied by its multiplier, then divided by WEIGHTING_DIVISOR
         */
        struct StrategyParams {
            IStrategy strategy;
            uint96 multiplier;
        }
    
        // EVENTS
    
        /// @notice emitted whenever the stake of `operator` is updated
        event OperatorStakeUpdate(
            bytes32 indexed operatorId,
            uint8 quorumNumber,
            uint96 stake
        );
        /// @notice emitted when the minimum stake for a quorum is updated
        event MinimumStakeForQuorumUpdated(uint8 indexed quorumNumber, uint96 minimumStake);
        /// @notice emitted when a new quorum is created
        event QuorumCreated(uint8 indexed quorumNumber);
        /// @notice emitted when `strategy` has been added to the array at `strategyParams[quorumNumber]`
        event StrategyAddedToQuorum(uint8 indexed quorumNumber, IStrategy strategy);
        /// @notice emitted when `strategy` has removed from the array at `strategyParams[quorumNumber]`
        event StrategyRemovedFromQuorum(uint8 indexed quorumNumber, IStrategy strategy);
        /// @notice emitted when `strategy` has its `multiplier` updated in the array at `strategyParams[quorumNumber]`
        event StrategyMultiplierUpdated(uint8 indexed quorumNumber, IStrategy strategy, uint256 multiplier);
    
        /**
         * @notice Registers the `operator` with `operatorId` for the specified `quorumNumbers`.
         * @param operator The address of the operator to register.
         * @param operatorId The id of the operator to register.
         * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber.
         * @return The operator's current stake for each quorum, and the total stake for each quorum
         * @dev access restricted to the RegistryCoordinator
         * @dev Preconditions (these are assumed, not validated in this contract):
         *         1) `quorumNumbers` has no duplicates
         *         2) `quorumNumbers.length` != 0
         *         3) `quorumNumbers` is ordered in ascending order
         *         4) the operator is not already registered
         */
        function registerOperator(
            address operator, 
            bytes32 operatorId, 
            bytes memory quorumNumbers
        ) external returns (uint96[] memory, uint96[] memory);
    
        /**
         * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`.
         * @param operatorId The id of the operator to deregister.
         * @param quorumNumbers The quorum numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber.
         * @dev access restricted to the RegistryCoordinator
         * @dev Preconditions (these are assumed, not validated in this contract):
         *         1) `quorumNumbers` has no duplicates
         *         2) `quorumNumbers.length` != 0
         *         3) `quorumNumbers` is ordered in ascending order
         *         4) the operator is not already deregistered
         *         5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for
         */
        function deregisterOperator(bytes32 operatorId, bytes memory quorumNumbers) external;
    
        /**
         * @notice Initialize a new quorum created by the registry coordinator by setting strategies, weights, and minimum stake
         */
        function initializeQuorum(uint8 quorumNumber, uint96 minimumStake, StrategyParams[] memory strategyParams) external;
    
        /// @notice Adds new strategies and the associated multipliers to the @param quorumNumber.
        function addStrategies(
            uint8 quorumNumber,
            StrategyParams[] memory strategyParams
        ) external;
    
        /**
         * @notice This function is used for removing strategies and their associated weights from the
         * mapping strategyParams for a specific @param quorumNumber.
         * @dev higher indices should be *first* in the list of @param indicesToRemove, since otherwise
         * the removal of lower index entries will cause a shift in the indices of the other strategiesToRemove
         */
        function removeStrategies(uint8 quorumNumber, uint256[] calldata indicesToRemove) external;
    
        /**
         * @notice This function is used for modifying the weights of strategies that are already in the
         * mapping strategyParams for a specific
         * @param quorumNumber is the quorum number to change the strategy for
         * @param strategyIndices are the indices of the strategies to change
         * @param newMultipliers are the new multipliers for the strategies
         */
        function modifyStrategyParams(
            uint8 quorumNumber,
            uint256[] calldata strategyIndices,
            uint96[] calldata newMultipliers
        ) external;
    
        /// @notice Constant used as a divisor in calculating weights.
        function WEIGHTING_DIVISOR() external pure returns (uint256);
    
        /// @notice Returns the EigenLayer delegation manager contract.
        function delegation() external view returns (IDelegationManager);
    
        /// @notice In order to register for a quorum i, an operator must have at least `minimumStakeForQuorum[i]`
        function minimumStakeForQuorum(uint8 quorumNumber) external view returns (uint96);
    
        /// @notice Returns the length of the dynamic array stored in `strategyParams[quorumNumber]`.
        function strategyParamsLength(uint8 quorumNumber) external view returns (uint256);
    
        /// @notice Returns the strategy and weight multiplier for the `index`'th strategy in the quorum `quorumNumber`
        function strategyParamsByIndex(
            uint8 quorumNumber,
            uint256 index
        ) external view returns (StrategyParams memory);
    
        /**
         * @notice This function computes the total weight of the @param operator in the quorum @param quorumNumber.
         * @dev reverts in the case that `quorumNumber` is greater than or equal to `quorumCount`
         */
        function weightOfOperatorForQuorum(uint8 quorumNumber, address operator) external view returns (uint96);
    
        /**
         * @notice Returns the entire `operatorIdToStakeHistory[operatorId][quorumNumber]` array.
         * @param operatorId The id of the operator of interest.
         * @param quorumNumber The quorum number to get the stake for.
         */
        function getStakeHistory(bytes32 operatorId, uint8 quorumNumber) external view returns (StakeUpdate[] memory);
    
        function getTotalStakeHistoryLength(uint8 quorumNumber) external view returns (uint256);
    
        /**
         * @notice Returns the `index`-th entry in the dynamic array of total stake, `totalStakeHistory` for quorum `quorumNumber`.
         * @param quorumNumber The quorum number to get the stake for.
         * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`.
         */
        function getTotalStakeUpdateAtIndex(uint8 quorumNumber, uint256 index) external view returns (StakeUpdate memory);
    
        /// @notice Returns the indices of the operator stakes for the provided `quorumNumber` at the given `blockNumber`
        function getStakeUpdateIndexAtBlockNumber(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber)
            external
            view
            returns (uint32);
    
        /// @notice Returns the indices of the total stakes for the provided `quorumNumbers` at the given `blockNumber`
        function getTotalStakeIndicesAtBlockNumber(uint32 blockNumber, bytes calldata quorumNumbers) external view returns(uint32[] memory) ;
    
        /**
         * @notice Returns the `index`-th entry in the `operatorIdToStakeHistory[operatorId][quorumNumber]` array.
         * @param quorumNumber The quorum number to get the stake for.
         * @param operatorId The id of the operator of interest.
         * @param index Array index for lookup, within the dynamic array `operatorIdToStakeHistory[operatorId][quorumNumber]`.
         * @dev Function will revert if `index` is out-of-bounds.
         */
        function getStakeUpdateAtIndex(uint8 quorumNumber, bytes32 operatorId, uint256 index)
            external
            view
            returns (StakeUpdate memory);
    
        /**
         * @notice Returns the most recent stake weight for the `operatorId` for a certain quorum
         * @dev Function returns an StakeUpdate struct with **every entry equal to 0** in the event that the operator has no stake history
         */
        function getLatestStakeUpdate(bytes32 operatorId, uint8 quorumNumber) external view returns (StakeUpdate memory);
    
        /**
         * @notice Returns the stake weight corresponding to `operatorId` for quorum `quorumNumber`, at the
         * `index`-th entry in the `operatorIdToStakeHistory[operatorId][quorumNumber]` array if the entry 
         * corresponds to the operator's stake at `blockNumber`. Reverts otherwise.
         * @param quorumNumber The quorum number to get the stake for.
         * @param operatorId The id of the operator of interest.
         * @param index Array index for lookup, within the dynamic array `operatorIdToStakeHistory[operatorId][quorumNumber]`.
         * @param blockNumber Block number to make sure the stake is from.
         * @dev Function will revert if `index` is out-of-bounds.
         * @dev used the BLSSignatureChecker to get past stakes of signing operators
         */
        function getStakeAtBlockNumberAndIndex(uint8 quorumNumber, uint32 blockNumber, bytes32 operatorId, uint256 index)
            external
            view
            returns (uint96);
    
        /**
         * @notice Returns the total stake weight for quorum `quorumNumber`, at the `index`-th entry in the 
         * `totalStakeHistory[quorumNumber]` array if the entry corresponds to the total stake at `blockNumber`. 
         * Reverts otherwise.
         * @param quorumNumber The quorum number to get the stake for.
         * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`.
         * @param blockNumber Block number to make sure the stake is from.
         * @dev Function will revert if `index` is out-of-bounds.
         * @dev used the BLSSignatureChecker to get past stakes of signing operators
         */
        function getTotalStakeAtBlockNumberFromIndex(uint8 quorumNumber, uint32 blockNumber, uint256 index) external view returns (uint96);
    
        /**
         * @notice Returns the most recent stake weight for the `operatorId` for quorum `quorumNumber`
         * @dev Function returns weight of **0** in the event that the operator has no stake history
         */
        function getCurrentStake(bytes32 operatorId, uint8 quorumNumber) external view returns (uint96);
    
        /// @notice Returns the stake of the operator for the provided `quorumNumber` at the given `blockNumber`
        function getStakeAtBlockNumber(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber)
            external
            view
            returns (uint96);
    
        /**
         * @notice Returns the stake weight from the latest entry in `_totalStakeHistory` for quorum `quorumNumber`.
         * @dev Will revert if `_totalStakeHistory[quorumNumber]` is empty.
         */
        function getCurrentTotalStake(uint8 quorumNumber) external view returns (uint96);
    
        /**
         * @notice Called by the registry coordinator to update an operator's stake for one
         * or more quorums.
         *
         * If the operator no longer has the minimum stake required for a quorum, they are
         * added to the
         * @return A bitmap of quorums where the operator no longer meets the minimum stake
         * and should be deregistered.
         */
        function updateOperatorStake(
            address operator, 
            bytes32 operatorId, 
            bytes calldata quorumNumbers
        ) external returns (uint192);
    }
    
    /**
     * @title Interface for a `Registry`-type contract that keeps track of an ordered list of operators for up to 256 quorums.
     * @author Layr Labs, Inc.
     */
    interface IIndexRegistry is IRegistry {
        // EVENTS
        
        // emitted when an operator's index in the ordered operator list for the quorum with number `quorumNumber` is updated
        event QuorumIndexUpdate(bytes32 indexed operatorId, uint8 quorumNumber, uint32 newOperatorIndex);
    
        // DATA STRUCTURES
    
        // struct used to give definitive ordering to operators at each blockNumber. 
        struct OperatorUpdate {
            // blockNumber number from which `operatorIndex` was the operators index
            // the operator's index is the first entry such that `blockNumber >= entry.fromBlockNumber`
            uint32 fromBlockNumber;
            // the operator at this index
            bytes32 operatorId;
        }
    
        // struct used to denote the number of operators in a quorum at a given blockNumber
        struct QuorumUpdate {
            // The total number of operators at a `blockNumber` is the first entry such that `blockNumber >= entry.fromBlockNumber`
            uint32 fromBlockNumber;
            // The number of operators at `fromBlockNumber`
            uint32 numOperators;
        }
    
        /**
         * @notice Registers the operator with the specified `operatorId` for the quorums specified by `quorumNumbers`.
         * @param operatorId is the id of the operator that is being registered
         * @param quorumNumbers is the quorum numbers the operator is registered for
         * @return numOperatorsPerQuorum is a list of the number of operators (including the registering operator) in each of the quorums the operator is registered for
         * @dev access restricted to the RegistryCoordinator
         * @dev Preconditions (these are assumed, not validated in this contract):
         *         1) `quorumNumbers` has no duplicates
         *         2) `quorumNumbers.length` != 0
         *         3) `quorumNumbers` is ordered in ascending order
         *         4) the operator is not already registered
         */
        function registerOperator(bytes32 operatorId, bytes calldata quorumNumbers) external returns(uint32[] memory);
    
        /**
         * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumNumbers`.
         * @param operatorId is the id of the operator that is being deregistered
         * @param quorumNumbers is the quorum numbers the operator is deregistered for
         * @dev access restricted to the RegistryCoordinator
         * @dev Preconditions (these are assumed, not validated in this contract):
         *         1) `quorumNumbers` has no duplicates
         *         2) `quorumNumbers.length` != 0
         *         3) `quorumNumbers` is ordered in ascending order
         *         4) the operator is not already deregistered
         *         5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for
         */
        function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers) external;
    
        /**
         * @notice Initialize a quorum by pushing its first quorum update
         * @param quorumNumber The number of the new quorum
         */
        function initializeQuorum(uint8 quorumNumber) external;
    
        /// @notice Returns the OperatorUpdate entry for the specified `operatorIndex` and `quorumNumber` at the specified `arrayIndex`
        function getOperatorUpdateAtIndex(
            uint8 quorumNumber,
            uint32 operatorIndex,
            uint32 arrayIndex
        ) external view returns (OperatorUpdate memory);
    
        /// @notice Returns the QuorumUpdate entry for the specified `quorumNumber` at the specified `quorumIndex`
        function getQuorumUpdateAtIndex(uint8 quorumNumber, uint32 quorumIndex) external view returns (QuorumUpdate memory);
    
        /// @notice Returns the most recent OperatorUpdate entry for the specified quorumNumber and operatorIndex
        function getLatestOperatorUpdate(uint8 quorumNumber, uint32 operatorIndex) external view returns (OperatorUpdate memory);
    
        /// @notice Returns the most recent QuorumUpdate entry for the specified quorumNumber
        function getLatestQuorumUpdate(uint8 quorumNumber) external view returns (QuorumUpdate memory);
    
        /// @notice Returns the current number of operators of this service for `quorumNumber`.
        function totalOperatorsForQuorum(uint8 quorumNumber) external view returns (uint32);
    
        /// @notice Returns an ordered list of operators of the services for the given `quorumNumber` at the given `blockNumber`
        function getOperatorListAtBlockNumber(uint8 quorumNumber, uint32 blockNumber) external view returns (bytes32[] memory);
    }
    
    /**
     * @title Minimal interface for a ServiceManager-type contract that forms the single point for an AVS to push updates to EigenLayer
     * @author Layr Labs, Inc.
     */
    interface IServiceManager {
        /**
         * @notice Updates the metadata URI for the AVS
         * @param _metadataURI is the metadata URI for the AVS
         */
        function updateAVSMetadataURI(string memory _metadataURI) external;
    
        /**
         * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator registration with the AVS
         * @param operator The address of the operator to register.
         * @param operatorSignature The signature, salt, and expiry of the operator's signature.
         */
        function registerOperatorToAVS(
            address operator,
            ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
        ) external;
    
        /**
         * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS
         * @param operator The address of the operator to deregister.
         */
        function deregisterOperatorFromAVS(address operator) external;
    
        /**
         * @notice Returns the list of strategies that the operator has potentially restaked on the AVS
         * @param operator The address of the operator to get restaked strategies for
         * @dev This function is intended to be called off-chain
         * @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness 
         *      of each element in the returned array. The off-chain service should do that validation separately
         */
        function getOperatorRestakedStrategies(address operator) external view returns (address[] memory);
    
        /**
         * @notice Returns the list of strategies that the AVS supports for restaking
         * @dev This function is intended to be called off-chain
         * @dev No guarantee is made on uniqueness of each element in the returned array. 
         *      The off-chain service should do that validation separately
         */
        function getRestakeableStrategies() external view returns (address[] memory);
    
        /// @notice Returns the EigenLayer AVSDirectory contract.
        function avsDirectory() external view returns (address);
    }
    
    /**
     * @title Interface for a contract that coordinates between various registries for an AVS.
     * @author Layr Labs, Inc.
     */
    interface IRegistryCoordinator {
        // EVENTS
    
        /// Emits when an operator is registered
        event OperatorRegistered(address indexed operator, bytes32 indexed operatorId);
    
        /// Emits when an operator is deregistered
        event OperatorDeregistered(address indexed operator, bytes32 indexed operatorId);
    
        event OperatorSetParamsUpdated(uint8 indexed quorumNumber, OperatorSetParam operatorSetParams);
    
        event ChurnApproverUpdated(address prevChurnApprover, address newChurnApprover);
    
        event EjectorUpdated(address prevEjector, address newEjector);
    
        /// @notice emitted when all the operators for a quorum are updated at once
        event QuorumBlockNumberUpdated(uint8 indexed quorumNumber, uint256 blocknumber);
    
        // DATA STRUCTURES
        enum OperatorStatus
        {
            // default is NEVER_REGISTERED
            NEVER_REGISTERED,
            REGISTERED,
            DEREGISTERED
        }
    
        // STRUCTS
    
        /**
         * @notice Data structure for storing info on operators
         */
        struct OperatorInfo {
            // the id of the operator, which is likely the keccak256 hash of the operator's public key if using BLSRegistry
            bytes32 operatorId;
            // indicates whether the operator is actively registered for serving the middleware or not
            OperatorStatus status;
        }
    
        /**
         * @notice Data structure for storing info on quorum bitmap updates where the `quorumBitmap` is the bitmap of the 
         * quorums the operator is registered for starting at (inclusive)`updateBlockNumber` and ending at (exclusive) `nextUpdateBlockNumber`
         * @dev nextUpdateBlockNumber is initialized to 0 for the latest update
         */
        struct QuorumBitmapUpdate {
            uint32 updateBlockNumber;
            uint32 nextUpdateBlockNumber;
            uint192 quorumBitmap;
        }
    
        /**
         * @notice Data structure for storing operator set params for a given quorum. Specifically the 
         * `maxOperatorCount` is the maximum number of operators that can be registered for the quorum,
         * `kickBIPsOfOperatorStake` is the basis points of a new operator needs to have of an operator they are trying to kick from the quorum,
         * and `kickBIPsOfTotalStake` is the basis points of the total stake of the quorum that an operator needs to be below to be kicked.
         */ 
         struct OperatorSetParam {
            uint32 maxOperatorCount;
            uint16 kickBIPsOfOperatorStake;
            uint16 kickBIPsOfTotalStake;
        }
    
        /**
         * @notice Data structure for the parameters needed to kick an operator from a quorum with number `quorumNumber`, used during registration churn.
         * `operator` is the address of the operator to kick
         */
        struct OperatorKickParam {
            uint8 quorumNumber;
            address operator;
        }
    
        /// @notice Returns the operator set params for the given `quorumNumber`
        function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory);
        /// @notice the Stake registry contract that will keep track of operators' stakes
        function stakeRegistry() external view returns (IStakeRegistry);
        /// @notice the BLS Aggregate Pubkey Registry contract that will keep track of operators' BLS aggregate pubkeys per quorum
        function blsApkRegistry() external view returns (IBLSApkRegistry);
        /// @notice the index Registry contract that will keep track of operators' indexes
        function indexRegistry() external view returns (IIndexRegistry);
    
        /**
         * @notice Ejects the provided operator from the provided quorums from the AVS
         * @param operator is the operator to eject
         * @param quorumNumbers are the quorum numbers to eject the operator from
         */
        function ejectOperator(
            address operator, 
            bytes calldata quorumNumbers
        ) external;
    
        /// @notice Returns the number of quorums the registry coordinator has created
        function quorumCount() external view returns (uint8);
    
        /// @notice Returns the operator struct for the given `operator`
        function getOperator(address operator) external view returns (OperatorInfo memory);
    
        /// @notice Returns the operatorId for the given `operator`
        function getOperatorId(address operator) external view returns (bytes32);
    
        /// @notice Returns the operator address for the given `operatorId`
        function getOperatorFromId(bytes32 operatorId) external view returns (address operator);
    
        /// @notice Returns the status for the given `operator`
        function getOperatorStatus(address operator) external view returns (IRegistryCoordinator.OperatorStatus);
    
        /// @notice Returns the indices of the quorumBitmaps for the provided `operatorIds` at the given `blockNumber`
        function getQuorumBitmapIndicesAtBlockNumber(uint32 blockNumber, bytes32[] memory operatorIds) external view returns (uint32[] memory);
    
        /**
         * @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index`
         * @dev reverts if `index` is incorrect 
         */ 
        function getQuorumBitmapAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192);
    
        /// @notice Returns the `index`th entry in the operator with `operatorId`'s bitmap history
        function getQuorumBitmapUpdateByIndex(bytes32 operatorId, uint256 index) external view returns (QuorumBitmapUpdate memory);
    
        /// @notice Returns the current quorum bitmap for the given `operatorId`
        function getCurrentQuorumBitmap(bytes32 operatorId) external view returns (uint192);
    
        /// @notice Returns the length of the quorum bitmap history for the given `operatorId`
        function getQuorumBitmapHistoryLength(bytes32 operatorId) external view returns (uint256);
    
        /// @notice Returns the registry at the desired index
        function registries(uint256) external view returns (address);
    
        /// @notice Returns the number of registries
        function numRegistries() external view returns (uint256);
    
        /**
         * @notice Returns the message hash that an operator must sign to register their BLS public key.
         * @param operator is the address of the operator registering their BLS public key
         */
        function pubkeyRegistrationMessageHash(address operator) external view returns (BN254.G1Point memory);
    
        /// @notice returns the blocknumber the quorum was last updated all at once for all operators
        function quorumUpdateBlockNumber(uint8 quorumNumber) external view returns (uint256);
    
        /// @notice The owner of the registry coordinator
        function owner() external view returns (address);
    }
    
    // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol)
    
    /**
     * @dev Interface of the ERC1271 standard signature validation method for
     * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
     *
     * _Available since v4.1._
     */
    interface IERC1271 {
        /**
         * @dev Should return whether the signature provided is valid for the provided data
         * @param hash      Hash of the data to be signed
         * @param signature Signature byte array associated with _data
         */
        function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
    }
    
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
    
    /**
     * @dev Collection of functions related to the address type
     */
    library Address {
        /**
         * @dev Returns true if `account` is a contract.
         *
         * [IMPORTANT]
         * ====
         * It is unsafe to assume that an address for which this function returns
         * false is an externally-owned account (EOA) and not a contract.
         *
         * Among others, `isContract` will return false for the following
         * types of addresses:
         *
         *  - an externally-owned account
         *  - a contract in construction
         *  - an address where a contract will be created
         *  - an address where a contract lived, but was destroyed
         *
         * Furthermore, `isContract` will also return true if the target contract within
         * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
         * which only has an effect at the end of a transaction.
         * ====
         *
         * [IMPORTANT]
         * ====
         * You shouldn't rely on `isContract` to protect against flash loan attacks!
         *
         * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
         * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
         * constructor.
         * ====
         */
        function isContract(address account) internal view returns (bool) {
            // This method relies on extcodesize/address.code.length, which returns 0
            // for contracts in construction, since the code is only stored at the end
            // of the constructor execution.
    
            return account.code.length > 0;
        }
    
        /**
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
         * `recipient`, forwarding all available gas and reverting on errors.
         *
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
         * imposed by `transfer`, making them unable to receive funds via
         * `transfer`. {sendValue} removes this limitation.
         *
         * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
         *
         * IMPORTANT: because control is transferred to `recipient`, care must be
         * taken to not create reentrancy vulnerabilities. Consider using
         * {ReentrancyGuard} or the
         * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            require(address(this).balance >= amount, "Address: insufficient balance");
    
            (bool success, ) = recipient.call{value: amount}("");
            require(success, "Address: unable to send value, recipient may have reverted");
        }
    
        /**
         * @dev Performs a Solidity function call using a low level `call`. A
         * plain `call` is an unsafe replacement for a function call: use this
         * function instead.
         *
         * If `target` reverts with a revert reason, it is bubbled up by this
         * function (like regular Solidity function calls).
         *
         * Returns the raw returned data. To convert to the expected return value,
         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
         *
         * Requirements:
         *
         * - `target` must be a contract.
         * - calling `target` with `data` must not revert.
         *
         * _Available since v3.1._
         */
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, "Address: low-level call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
         * `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but also transferring `value` wei to `target`.
         *
         * Requirements:
         *
         * - the calling contract must have an ETH balance of at least `value`.
         * - the called Solidity function must be `payable`.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
         * with `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(
            address target,
            bytes memory data,
            uint256 value,
            string memory errorMessage
        ) internal returns (bytes memory) {
            require(address(this).balance >= value, "Address: insufficient balance for call");
            (bool success, bytes memory returndata) = target.call{value: value}(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            return functionStaticCall(target, data, "Address: low-level static call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            (bool success, bytes memory returndata) = target.staticcall(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
         * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
         *
         * _Available since v4.8._
         */
        function verifyCallResultFromTarget(
            address target,
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            if (success) {
                if (returndata.length == 0) {
                    // only check isContract if the call was successful and the return data is empty
                    // otherwise we already know that it was a contract
                    require(isContract(target), "Address: call to non-contract");
                }
                return returndata;
            } else {
                _revert(returndata, errorMessage);
            }
        }
    
        /**
         * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
         * revert reason or using the provided one.
         *
         * _Available since v4.3._
         */
        function verifyCallResult(
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal pure returns (bytes memory) {
            if (success) {
                return returndata;
            } else {
                _revert(returndata, errorMessage);
            }
        }
    
        function _revert(bytes memory returndata, string memory errorMessage) private pure {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly
                /// @solidity memory-safe-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
    
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)
    
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
    
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
    
    /**
     * @dev Standard math utilities missing in the Solidity language.
     */
    library Math {
        enum Rounding {
            Down, // Toward negative infinity
            Up, // Toward infinity
            Zero // Toward zero
        }
    
        /**
         * @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 average of two numbers. The result is rounded towards
         * zero.
         */
        function average(uint256 a, uint256 b) internal pure returns (uint256) {
            // (a + b) / 2 can overflow.
            return (a & b) + (a ^ b) / 2;
        }
    
        /**
         * @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;
        }
    
        /**
         * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
         * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
         * with further edits by Uniswap Labs also under MIT license.
         */
        function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
            unchecked {
                // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                // variables such that product = prod1 * 2^256 + prod0.
                uint256 prod0; // Least significant 256 bits of the product
                uint256 prod1; // Most significant 256 bits of the product
                assembly {
                    let mm := mulmod(x, y, not(0))
                    prod0 := mul(x, y)
                    prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                }
    
                // Handle non-overflow cases, 256 by 256 division.
                if (prod1 == 0) {
                    // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                    // The surrounding unchecked block does not change this fact.
                    // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                    return prod0 / denominator;
                }
    
                // Make sure the result is less than 2^256. Also prevents denominator == 0.
                require(denominator > prod1, "Math: mulDiv overflow");
    
                ///////////////////////////////////////////////
                // 512 by 256 division.
                ///////////////////////////////////////////////
    
                // Make division exact by subtracting the remainder from [prod1 prod0].
                uint256 remainder;
                assembly {
                    // Compute remainder using mulmod.
                    remainder := mulmod(x, y, denominator)
    
                    // Subtract 256 bit number from 512 bit number.
                    prod1 := sub(prod1, gt(remainder, prod0))
                    prod0 := sub(prod0, remainder)
                }
    
                // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                // See https://cs.stackexchange.com/q/138556/92363.
    
                // Does not overflow because the denominator cannot be zero at this stage in the function.
                uint256 twos = denominator & (~denominator + 1);
                assembly {
                    // Divide denominator by twos.
                    denominator := div(denominator, twos)
    
                    // Divide [prod1 prod0] by twos.
                    prod0 := div(prod0, twos)
    
                    // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                    twos := add(div(sub(0, twos), twos), 1)
                }
    
                // Shift in bits from prod1 into prod0.
                prod0 |= prod1 * twos;
    
                // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                // four bits. That is, denominator * inv = 1 mod 2^4.
                uint256 inverse = (3 * denominator) ^ 2;
    
                // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                // in modular arithmetic, doubling the correct bits in each step.
                inverse *= 2 - denominator * inverse; // inverse mod 2^8
                inverse *= 2 - denominator * inverse; // inverse mod 2^16
                inverse *= 2 - denominator * inverse; // inverse mod 2^32
                inverse *= 2 - denominator * inverse; // inverse mod 2^64
                inverse *= 2 - denominator * inverse; // inverse mod 2^128
                inverse *= 2 - denominator * inverse; // inverse mod 2^256
    
                // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                // is no longer required.
                result = prod0 * inverse;
                return result;
            }
        }
    
        /**
         * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
         */
        function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
            uint256 result = mulDiv(x, y, denominator);
            if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                result += 1;
            }
            return result;
        }
    
        /**
         * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
         *
         * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
         */
        function sqrt(uint256 a) internal pure returns (uint256) {
            if (a == 0) {
                return 0;
            }
    
            // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
            //
            // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
            // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
            //
            // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
            // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
            // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
            //
            // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
            uint256 result = 1 << (log2(a) >> 1);
    
            // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
            // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
            // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
            // into the expected uint128 result.
            unchecked {
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                return min(result, a / result);
            }
        }
    
        /**
         * @notice Calculates sqrt(a), following the selected rounding direction.
         */
        function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = sqrt(a);
                return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
            }
        }
    
        /**
         * @dev Return the log in base 2, rounded down, of a positive value.
         * Returns 0 if given 0.
         */
        function log2(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >> 128 > 0) {
                    value >>= 128;
                    result += 128;
                }
                if (value >> 64 > 0) {
                    value >>= 64;
                    result += 64;
                }
                if (value >> 32 > 0) {
                    value >>= 32;
                    result += 32;
                }
                if (value >> 16 > 0) {
                    value >>= 16;
                    result += 16;
                }
                if (value >> 8 > 0) {
                    value >>= 8;
                    result += 8;
                }
                if (value >> 4 > 0) {
                    value >>= 4;
                    result += 4;
                }
                if (value >> 2 > 0) {
                    value >>= 2;
                    result += 2;
                }
                if (value >> 1 > 0) {
                    result += 1;
                }
            }
            return result;
        }
    
        /**
         * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log2(value);
                return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
            }
        }
    
        /**
         * @dev Return the log in base 10, rounded down, of a positive value.
         * Returns 0 if given 0.
         */
        function log10(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >= 10 ** 64) {
                    value /= 10 ** 64;
                    result += 64;
                }
                if (value >= 10 ** 32) {
                    value /= 10 ** 32;
                    result += 32;
                }
                if (value >= 10 ** 16) {
                    value /= 10 ** 16;
                    result += 16;
                }
                if (value >= 10 ** 8) {
                    value /= 10 ** 8;
                    result += 8;
                }
                if (value >= 10 ** 4) {
                    value /= 10 ** 4;
                    result += 4;
                }
                if (value >= 10 ** 2) {
                    value /= 10 ** 2;
                    result += 2;
                }
                if (value >= 10 ** 1) {
                    result += 1;
                }
            }
            return result;
        }
    
        /**
         * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log10(value);
                return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
            }
        }
    
        /**
         * @dev Return the log in base 256, rounded down, of a positive value.
         * Returns 0 if given 0.
         *
         * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
         */
        function log256(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >> 128 > 0) {
                    value >>= 128;
                    result += 16;
                }
                if (value >> 64 > 0) {
                    value >>= 64;
                    result += 8;
                }
                if (value >> 32 > 0) {
                    value >>= 32;
                    result += 4;
                }
                if (value >> 16 > 0) {
                    value >>= 16;
                    result += 2;
                }
                if (value >> 8 > 0) {
                    result += 1;
                }
            }
            return result;
        }
    
        /**
         * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log256(value);
                return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
            }
        }
    }
    
    // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
    
    /**
     * @dev Standard signed math utilities missing in the Solidity language.
     */
    library SignedMath {
        /**
         * @dev Returns the largest of two signed numbers.
         */
        function max(int256 a, int256 b) internal pure returns (int256) {
            return a > b ? a : b;
        }
    
        /**
         * @dev Returns the smallest of two signed numbers.
         */
        function min(int256 a, int256 b) internal pure returns (int256) {
            return a < b ? a : b;
        }
    
        /**
         * @dev Returns the average of two signed numbers without overflow.
         * The result is rounded towards zero.
         */
        function average(int256 a, int256 b) internal pure returns (int256) {
            // Formula from the book "Hacker's Delight"
            int256 x = (a & b) + ((a ^ b) >> 1);
            return x + (int256(uint256(x) >> 255) & (a ^ b));
        }
    
        /**
         * @dev Returns the absolute unsigned value of a signed value.
         */
        function abs(int256 n) internal pure returns (uint256) {
            unchecked {
                // must be unchecked in order to support `n = type(int256).min`
                return uint256(n >= 0 ? n : -n);
            }
        }
    }
    
    /**
     * @dev String operations.
     */
    library Strings {
        bytes16 private constant _SYMBOLS = "0123456789abcdef";
        uint8 private constant _ADDRESS_LENGTH = 20;
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` decimal representation.
         */
        function toString(uint256 value) internal pure returns (string memory) {
            unchecked {
                uint256 length = Math.log10(value) + 1;
                string memory buffer = new string(length);
                uint256 ptr;
                /// @solidity memory-safe-assembly
                assembly {
                    ptr := add(buffer, add(32, length))
                }
                while (true) {
                    ptr--;
                    /// @solidity memory-safe-assembly
                    assembly {
                        mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                    }
                    value /= 10;
                    if (value == 0) break;
                }
                return buffer;
            }
        }
    
        /**
         * @dev Converts a `int256` to its ASCII `string` decimal representation.
         */
        function toString(int256 value) internal pure returns (string memory) {
            return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
        }
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
         */
        function toHexString(uint256 value) internal pure returns (string memory) {
            unchecked {
                return toHexString(value, Math.log256(value) + 1);
            }
        }
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
         */
        function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
            bytes memory buffer = new bytes(2 * length + 2);
            buffer[0] = "0";
            buffer[1] = "x";
            for (uint256 i = 2 * length + 1; i > 1; --i) {
                buffer[i] = _SYMBOLS[value & 0xf];
                value >>= 4;
            }
            require(value == 0, "Strings: hex length insufficient");
            return string(buffer);
        }
    
        /**
         * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
         */
        function toHexString(address addr) internal pure returns (string memory) {
            return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
        }
    
        /**
         * @dev Returns true if the two strings are equal.
         */
        function equal(string memory a, string memory b) internal pure returns (bool) {
            return keccak256(bytes(a)) == keccak256(bytes(b));
        }
    }
    
    /**
     * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
     *
     * These functions can be used to verify that a message was signed by the holder
     * of the private keys of a given address.
     */
    library ECDSA {
        enum RecoverError {
            NoError,
            InvalidSignature,
            InvalidSignatureLength,
            InvalidSignatureS,
            InvalidSignatureV // Deprecated in v4.8
        }
    
        function _throwError(RecoverError error) private pure {
            if (error == RecoverError.NoError) {
                return; // no error: do nothing
            } else if (error == RecoverError.InvalidSignature) {
                revert("ECDSA: invalid signature");
            } else if (error == RecoverError.InvalidSignatureLength) {
                revert("ECDSA: invalid signature length");
            } else if (error == RecoverError.InvalidSignatureS) {
                revert("ECDSA: invalid signature 's' value");
            }
        }
    
        /**
         * @dev Returns the address that signed a hashed message (`hash`) with
         * `signature` or error string. This address can then be used for verification purposes.
         *
         * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
         * this function rejects them by requiring the `s` value to be in the lower
         * half order, and the `v` value to be either 27 or 28.
         *
         * IMPORTANT: `hash` _must_ be the result of a hash operation for the
         * verification to be secure: it is possible to craft signatures that
         * recover to arbitrary addresses for non-hashed data. A safe way to ensure
         * this is by receiving a hash of the original message (which may otherwise
         * be too long), and then calling {toEthSignedMessageHash} on it.
         *
         * Documentation for signature generation:
         * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
         * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
         *
         * _Available since v4.3._
         */
        function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
            if (signature.length == 65) {
                bytes32 r;
                bytes32 s;
                uint8 v;
                // ecrecover takes the signature parameters, and the only way to get them
                // currently is to use assembly.
                /// @solidity memory-safe-assembly
                assembly {
                    r := mload(add(signature, 0x20))
                    s := mload(add(signature, 0x40))
                    v := byte(0, mload(add(signature, 0x60)))
                }
                return tryRecover(hash, v, r, s);
            } else {
                return (address(0), RecoverError.InvalidSignatureLength);
            }
        }
    
        /**
         * @dev Returns the address that signed a hashed message (`hash`) with
         * `signature`. This address can then be used for verification purposes.
         *
         * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
         * this function rejects them by requiring the `s` value to be in the lower
         * half order, and the `v` value to be either 27 or 28.
         *
         * IMPORTANT: `hash` _must_ be the result of a hash operation for the
         * verification to be secure: it is possible to craft signatures that
         * recover to arbitrary addresses for non-hashed data. A safe way to ensure
         * this is by receiving a hash of the original message (which may otherwise
         * be too long), and then calling {toEthSignedMessageHash} on it.
         */
        function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
            (address recovered, RecoverError error) = tryRecover(hash, signature);
            _throwError(error);
            return recovered;
        }
    
        /**
         * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
         *
         * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
         *
         * _Available since v4.3._
         */
        function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
            bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
            uint8 v = uint8((uint256(vs) >> 255) + 27);
            return tryRecover(hash, v, r, s);
        }
    
        /**
         * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
         *
         * _Available since v4.2._
         */
        function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
            (address recovered, RecoverError error) = tryRecover(hash, r, vs);
            _throwError(error);
            return recovered;
        }
    
        /**
         * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
         * `r` and `s` signature fields separately.
         *
         * _Available since v4.3._
         */
        function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
            // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
            // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
            // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
            // signatures from current libraries generate a unique signature with an s-value in the lower half order.
            //
            // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
            // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
            // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
            // these malleable signatures as well.
            if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                return (address(0), RecoverError.InvalidSignatureS);
            }
    
            // If the signature is valid (and not malleable), return the signer address
            address signer = ecrecover(hash, v, r, s);
            if (signer == address(0)) {
                return (address(0), RecoverError.InvalidSignature);
            }
    
            return (signer, RecoverError.NoError);
        }
    
        /**
         * @dev Overload of {ECDSA-recover} that receives the `v`,
         * `r` and `s` signature fields separately.
         */
        function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
            (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
            _throwError(error);
            return recovered;
        }
    
        /**
         * @dev Returns an Ethereum Signed Message, created from a `hash`. This
         * produces hash corresponding to the one signed with the
         * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
         * JSON-RPC method as part of EIP-191.
         *
         * See {recover}.
         */
        function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
            // 32 is the length in bytes of hash,
            // enforced by the type signature above
            /// @solidity memory-safe-assembly
            assembly {
                mstore(0x00, "\x19Ethereum Signed Message:\n32")
                mstore(0x1c, hash)
                message := keccak256(0x00, 0x3c)
            }
        }
    
        /**
         * @dev Returns an Ethereum Signed Message, created from `s`. This
         * produces hash corresponding to the one signed with the
         * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
         * JSON-RPC method as part of EIP-191.
         *
         * See {recover}.
         */
        function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
            return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
        }
    
        /**
         * @dev Returns an Ethereum Signed Typed Data, created from a
         * `domainSeparator` and a `structHash`. This produces hash corresponding
         * to the one signed with the
         * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
         * JSON-RPC method as part of EIP-712.
         *
         * See {recover}.
         */
        function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
            /// @solidity memory-safe-assembly
            assembly {
                let ptr := mload(0x40)
                mstore(ptr, "\x19\x01")
                mstore(add(ptr, 0x02), domainSeparator)
                mstore(add(ptr, 0x22), structHash)
                data := keccak256(ptr, 0x42)
            }
        }
    
        /**
         * @dev Returns an Ethereum Signed Data with intended validator, created from a
         * `validator` and `data` according to the version 0 of EIP-191.
         *
         * See {recover}.
         */
        function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
            return keccak256(abi.encodePacked("\x19\x00", validator, data));
        }
    }
    
    /**
     * @title Library of utilities for making EIP1271-compliant signature checks.
     * @author Layr Labs, Inc.
     * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
     */
    library EIP1271SignatureUtils {
        // bytes4(keccak256("isValidSignature(bytes32,bytes)")
        bytes4 internal constant EIP1271_MAGICVALUE = 0x1626ba7e;
    
        /**
         * @notice Checks @param signature is a valid signature of @param digestHash from @param signer.
         * If the `signer` contains no code -- i.e. it is not (yet, at least) a contract address, then checks using standard ECDSA logic
         * Otherwise, passes on the signature to the signer to verify the signature and checks that it returns the `EIP1271_MAGICVALUE`.
         */
        function checkSignature_EIP1271(address signer, bytes32 digestHash, bytes memory signature) internal view {
            /**
             * check validity of signature:
             * 1) if `signer` is an EOA, then `signature` must be a valid ECDSA signature from `signer`,
             * indicating their intention for this action
             * 2) if `signer` is a contract, then `signature` must will be checked according to EIP-1271
             */
            if (Address.isContract(signer)) {
                require(
                    IERC1271(signer).isValidSignature(digestHash, signature) == EIP1271_MAGICVALUE,
                    "EIP1271SignatureUtils.checkSignature_EIP1271: ERC1271 signature verification failed"
                );
            } else {
                require(
                    ECDSA.recover(digestHash, signature) == signer,
                    "EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer"
                );
            }
        }
    }
    
    /**
     * @title Library for Bitmap utilities such as converting between an array of bytes and a bitmap and finding the number of 1s in a bitmap.
     * @author Layr Labs, Inc.
     */
    library BitmapUtils {
        /**
         * @notice Byte arrays are meant to contain unique bytes.
         * If the array length exceeds 256, then it's impossible for all entries to be unique.
         * This constant captures the max allowed array length (inclusive, i.e. 256 is allowed).
         */
        uint256 internal constant MAX_BYTE_ARRAY_LENGTH = 256;
    
        /**
         * @notice Converts an ordered array of bytes into a bitmap.
         * @param orderedBytesArray The array of bytes to convert/compress into a bitmap. Must be in strictly ascending order.
         * @return The resulting bitmap.
         * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap.
         * @dev This function will eventually revert in the event that the `orderedBytesArray` is not properly ordered (in ascending order).
         * @dev This function will also revert if the `orderedBytesArray` input contains any duplicate entries (i.e. duplicate bytes).
         */
        function orderedBytesArrayToBitmap(bytes memory orderedBytesArray) internal pure returns (uint256) {
            // sanity-check on input. a too-long input would fail later on due to having duplicate entry(s)
            require(orderedBytesArray.length <= MAX_BYTE_ARRAY_LENGTH,
                "BitmapUtils.orderedBytesArrayToBitmap: orderedBytesArray is too long");
    
            // return empty bitmap early if length of array is 0
            if (orderedBytesArray.length == 0) {
                return uint256(0);
            }
    
            // initialize the empty bitmap, to be built inside the loop
            uint256 bitmap;
            // initialize an empty uint256 to be used as a bitmask inside the loop
            uint256 bitMask;
    
            // perform the 0-th loop iteration with the ordering check *omitted* (since it is unnecessary / will always pass)
            // construct a single-bit mask from the numerical value of the 0th byte of the array, and immediately add it to the bitmap
            bitmap = uint256(1 << uint8(orderedBytesArray[0]));
    
            // loop through each byte in the array to construct the bitmap
            for (uint256 i = 1; i < orderedBytesArray.length; ++i) {
                // construct a single-bit mask from the numerical value of the next byte of the array
                bitMask = uint256(1 << uint8(orderedBytesArray[i]));
                // check strictly ascending array ordering by comparing the mask to the bitmap so far (revert if mask isn't greater than bitmap)
                require(bitMask > bitmap, "BitmapUtils.orderedBytesArrayToBitmap: orderedBytesArray is not ordered");
                // add the entry to the bitmap
                bitmap = (bitmap | bitMask);
            }
            return bitmap;
        }
    
        /**
         * @notice Converts an ordered byte array to a bitmap, validating that all bits are less than `bitUpperBound`
         * @param orderedBytesArray The array to convert to a bitmap; must be in strictly ascending order
         * @param bitUpperBound The exclusive largest bit. Each bit must be strictly less than this value.
         * @dev Reverts if bitmap contains a bit greater than or equal to `bitUpperBound`
         */
        function orderedBytesArrayToBitmap(bytes memory orderedBytesArray, uint8 bitUpperBound) internal pure returns (uint256) {
            uint256 bitmap = orderedBytesArrayToBitmap(orderedBytesArray);
    
            require((1 << bitUpperBound) > bitmap, 
                "BitmapUtils.orderedBytesArrayToBitmap: bitmap exceeds max value"
            );
    
            return bitmap;
        }
    
        /**
         * @notice Utility function for checking if a bytes array is strictly ordered, in ascending order.
         * @param bytesArray the bytes array of interest
         * @return Returns 'true' if the array is ordered in strictly ascending order, and 'false' otherwise.
         * @dev This function returns 'true' for the edge case of the `bytesArray` having zero length.
         * It also returns 'false' early for arrays with length in excess of MAX_BYTE_ARRAY_LENGTH (i.e. so long that they cannot be strictly ordered)
         */
        function isArrayStrictlyAscendingOrdered(bytes calldata bytesArray) internal pure returns (bool) {
            // Return early if the array is too long, or has a length of 0
            if (bytesArray.length > MAX_BYTE_ARRAY_LENGTH) {
                return false;
            }
    
            if (bytesArray.length == 0) {
                return true;
            }
    
            // Perform the 0-th loop iteration by pulling the 0th byte out of the array
            bytes1 singleByte = bytesArray[0];
    
            // For each byte, validate that each entry is *strictly greater than* the previous
            // If it isn't, return false as the array is not ordered
            for (uint256 i = 1; i < bytesArray.length; ++i) {
                if (uint256(uint8(bytesArray[i])) <= uint256(uint8(singleByte))) {
                    return false;
                }
                
                // Pull the next byte out of the array
                singleByte = bytesArray[i];
            }
            
            return true;
        }
    
        /**
         * @notice Converts a bitmap into an array of bytes.
         * @param bitmap The bitmap to decompress/convert to an array of bytes.
         * @return bytesArray The resulting bitmap array of bytes.
         * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap
         */
        function bitmapToBytesArray(uint256 bitmap) internal pure returns (bytes memory /*bytesArray*/) {
            // initialize an empty uint256 to be used as a bitmask inside the loop
            uint256 bitMask;
            // allocate only the needed amount of memory
            bytes memory bytesArray = new bytes(countNumOnes(bitmap));
            // track the array index to assign to
            uint256 arrayIndex = 0;
            /**
             * loop through each index in the bitmap to construct the array,
             * but short-circuit the loop if we reach the number of ones and thus are done
             * assigning to memory
             */
            for (uint256 i = 0; (arrayIndex < bytesArray.length) && (i < 256); ++i) {
                // construct a single-bit mask for the i-th bit
                bitMask = uint256(1 << i);
                // check if the i-th bit is flipped in the bitmap
                if (bitmap & bitMask != 0) {
                    // if the i-th bit is flipped, then add a byte encoding the value 'i' to the `bytesArray`
                    bytesArray[arrayIndex] = bytes1(uint8(i));
                    // increment the bytesArray slot since we've assigned one more byte of memory
                    unchecked{ ++arrayIndex; }
                }
            }
            return bytesArray;
        }
    
        /// @return count number of ones in binary representation of `n`
        function countNumOnes(uint256 n) internal pure returns (uint16) {
            uint16 count = 0;
            while (n > 0) {
                n &= (n - 1); // Clear the least significant bit (turn off the rightmost set bit).
                count++; // Increment the count for each cleared bit (each one encountered).
            }
            return count; // Return the total count of ones in the binary representation of n.
        }
    
        /// @notice Returns `true` if `bit` is in `bitmap`. Returns `false` otherwise.
        function isSet(uint256 bitmap, uint8 bit) internal pure returns (bool) {
            return 1 == ((bitmap >> bit) & 1);
        }
        
        /**
         * @notice Returns a copy of `bitmap` with `bit` set. 
         * @dev IMPORTANT: we're dealing with stack values here, so this doesn't modify
         * the original bitmap. Using this correctly requires an assignment statement:
         * `bitmap = bitmap.setBit(bit);`
         */
        function setBit(uint256 bitmap, uint8 bit) internal pure returns (uint256) {
            return bitmap | (1 << bit);
        }
    
        /**
         * @notice Returns true if `bitmap` has no set bits
         */
        function isEmpty(uint256 bitmap) internal pure returns (bool) {
            return bitmap == 0;
        }
    
        /**
         * @notice Returns true if `a` and `b` have no common set bits
         */
        function noBitsInCommon(uint256 a, uint256 b) internal pure returns (bool) {
            return a & b == 0;
        }
    
        /**
         * @notice Returns true if `a` is a subset of `b`: ALL of the bits in `a` are also in `b`
         */
        function isSubsetOf(uint256 a, uint256 b) internal pure returns (bool) {
            return a & b == a;
        }
    
        /**
         * @notice Returns a new bitmap that contains all bits set in either `a` or `b`
         * @dev Result is the union of `a` and `b`
         */
        function plus(uint256 a, uint256 b) internal pure returns (uint256) {
            return a | b;
        }
    
        /**
         * @notice Returns a new bitmap that clears all set bits of `b` from `a`
         * @dev Negates `b` and returns the intersection of the result with `a`
         */
        function minus(uint256 a, uint256 b) internal pure returns (uint256) {
            return a & ~b;
        }
    }
    
    // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
    
    // OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
    
    // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
    
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
    
    /**
     * @dev Collection of functions related to the address type
     */
    library AddressUpgradeable {
        /**
         * @dev Returns true if `account` is a contract.
         *
         * [IMPORTANT]
         * ====
         * It is unsafe to assume that an address for which this function returns
         * false is an externally-owned account (EOA) and not a contract.
         *
         * Among others, `isContract` will return false for the following
         * types of addresses:
         *
         *  - an externally-owned account
         *  - a contract in construction
         *  - an address where a contract will be created
         *  - an address where a contract lived, but was destroyed
         *
         * Furthermore, `isContract` will also return true if the target contract within
         * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
         * which only has an effect at the end of a transaction.
         * ====
         *
         * [IMPORTANT]
         * ====
         * You shouldn't rely on `isContract` to protect against flash loan attacks!
         *
         * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
         * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
         * constructor.
         * ====
         */
        function isContract(address account) internal view returns (bool) {
            // This method relies on extcodesize/address.code.length, which returns 0
            // for contracts in construction, since the code is only stored at the end
            // of the constructor execution.
    
            return account.code.length > 0;
        }
    
        /**
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
         * `recipient`, forwarding all available gas and reverting on errors.
         *
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
         * imposed by `transfer`, making them unable to receive funds via
         * `transfer`. {sendValue} removes this limitation.
         *
         * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
         *
         * IMPORTANT: because control is transferred to `recipient`, care must be
         * taken to not create reentrancy vulnerabilities. Consider using
         * {ReentrancyGuard} or the
         * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            require(address(this).balance >= amount, "Address: insufficient balance");
    
            (bool success, ) = recipient.call{value: amount}("");
            require(success, "Address: unable to send value, recipient may have reverted");
        }
    
        /**
         * @dev Performs a Solidity function call using a low level `call`. A
         * plain `call` is an unsafe replacement for a function call: use this
         * function instead.
         *
         * If `target` reverts with a revert reason, it is bubbled up by this
         * function (like regular Solidity function calls).
         *
         * Returns the raw returned data. To convert to the expected return value,
         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
         *
         * Requirements:
         *
         * - `target` must be a contract.
         * - calling `target` with `data` must not revert.
         *
         * _Available since v3.1._
         */
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, "Address: low-level call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
         * `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but also transferring `value` wei to `target`.
         *
         * Requirements:
         *
         * - the calling contract must have an ETH balance of at least `value`.
         * - the called Solidity function must be `payable`.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
         * with `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(
            address target,
            bytes memory data,
            uint256 value,
            string memory errorMessage
        ) internal returns (bytes memory) {
            require(address(this).balance >= value, "Address: insufficient balance for call");
            (bool success, bytes memory returndata) = target.call{value: value}(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            return functionStaticCall(target, data, "Address: low-level static call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            (bool success, bytes memory returndata) = target.staticcall(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
         * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
         *
         * _Available since v4.8._
         */
        function verifyCallResultFromTarget(
            address target,
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            if (success) {
                if (returndata.length == 0) {
                    // only check isContract if the call was successful and the return data is empty
                    // otherwise we already know that it was a contract
                    require(isContract(target), "Address: call to non-contract");
                }
                return returndata;
            } else {
                _revert(returndata, errorMessage);
            }
        }
    
        /**
         * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
         * revert reason or using the provided one.
         *
         * _Available since v4.3._
         */
        function verifyCallResult(
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal pure returns (bytes memory) {
            if (success) {
                return returndata;
            } else {
                _revert(returndata, errorMessage);
            }
        }
    
        function _revert(bytes memory returndata, string memory errorMessage) private pure {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly
                /// @solidity memory-safe-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
    
    /**
     * @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 Indicates that the contract has been initialized.
         * @custom:oz-retyped-from bool
         */
        uint8 private _initialized;
    
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool private _initializing;
    
        /**
         * @dev Triggered when the contract has been initialized or reinitialized.
         */
        event Initialized(uint8 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 functions marked with `initializer` can be nested in the context of a
         * constructor.
         *
         * Emits an {Initialized} event.
         */
        modifier initializer() {
            bool isTopLevelCall = !_initializing;
            require(
                (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
                "Initializable: contract is already initialized"
            );
            _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 255 will prevent any future reinitialization.
         *
         * Emits an {Initialized} event.
         */
        modifier reinitializer(uint8 version) {
            require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
            _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() {
            require(_initializing, "Initializable: contract is not initializing");
            _;
        }
    
        /**
         * @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 {
            require(!_initializing, "Initializable: contract is initializing");
            if (_initialized != type(uint8).max) {
                _initialized = type(uint8).max;
                emit Initialized(type(uint8).max);
            }
        }
    
        /**
         * @dev Returns the highest version that has been initialized. See {reinitializer}.
         */
        function _getInitializedVersion() internal view returns (uint8) {
            return _initialized;
        }
    
        /**
         * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
         */
        function _isInitializing() internal view returns (bool) {
            return _initializing;
        }
    }
    
    /**
     * @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;
        }
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[50] private __gap;
    }
    
    /**
     * @dev Contract module which provides a basic access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * By default, the owner account will be the one that deploys the contract. This
     * can later be changed with {transferOwnership}.
     *
     * This module is used through inheritance. It will make available the modifier
     * `onlyOwner`, which can be applied to your functions to restrict their use to
     * the owner.
     */
    abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
        address private _owner;
    
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
        /**
         * @dev Initializes the contract setting the deployer as the initial owner.
         */
        function __Ownable_init() internal onlyInitializing {
            __Ownable_init_unchained();
        }
    
        function __Ownable_init_unchained() internal onlyInitializing {
            _transferOwnership(_msgSender());
        }
    
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
            _checkOwner();
            _;
        }
    
        /**
         * @dev Returns the address of the current owner.
         */
        function owner() public view virtual returns (address) {
            return _owner;
        }
    
        /**
         * @dev Throws if the sender is not the owner.
         */
        function _checkOwner() internal view virtual {
            require(owner() == _msgSender(), "Ownable: caller is not the owner");
        }
    
        /**
         * @dev Leaves the contract without owner. It will not be possible to call
         * `onlyOwner` functions. Can only be called by the current owner.
         *
         * NOTE: Renouncing ownership will leave the contract without an owner,
         * thereby disabling any functionality that is only available to the owner.
         */
        function renounceOwnership() public virtual onlyOwner {
            _transferOwnership(address(0));
        }
    
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Can only be called by the current owner.
         */
        function transferOwnership(address newOwner) public virtual onlyOwner {
            require(newOwner != address(0), "Ownable: new owner is the zero address");
            _transferOwnership(newOwner);
        }
    
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Internal function without access restriction.
         */
        function _transferOwnership(address newOwner) internal virtual {
            address oldOwner = _owner;
            _owner = newOwner;
            emit OwnershipTransferred(oldOwner, newOwner);
        }
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[49] private __gap;
    }
    
    // OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/draft-EIP712.sol)
    
    // EIP-712 is Final as of 2022-08-11. This file is deprecated.
    
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol)
    
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/ShortStrings.sol)
    
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
    // This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
    
    /**
     * @dev Library for reading and writing primitive types to specific storage slots.
     *
     * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
     * This library helps with reading and writing to such slots without the need for inline assembly.
     *
     * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
     *
     * Example usage to set ERC1967 implementation slot:
     * ```solidity
     * contract ERC1967 {
     *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
     *
     *     function _getImplementation() internal view returns (address) {
     *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
     *     }
     *
     *     function _setImplementation(address newImplementation) internal {
     *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
     *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
     *     }
     * }
     * ```
     *
     * _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
     * _Available since v4.9 for `string`, `bytes`._
     */
    library StorageSlot {
        struct AddressSlot {
            address value;
        }
    
        struct BooleanSlot {
            bool value;
        }
    
        struct Bytes32Slot {
            bytes32 value;
        }
    
        struct Uint256Slot {
            uint256 value;
        }
    
        struct StringSlot {
            string value;
        }
    
        struct BytesSlot {
            bytes value;
        }
    
        /**
         * @dev Returns an `AddressSlot` with member `value` located at `slot`.
         */
        function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
         */
        function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
         */
        function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
         */
        function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `StringSlot` with member `value` located at `slot`.
         */
        function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
         */
        function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := store.slot
            }
        }
    
        /**
         * @dev Returns an `BytesSlot` with member `value` located at `slot`.
         */
        function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
         */
        function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := store.slot
            }
        }
    }
    
    // | string  | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |
    // | length  | 0x                                                              BB |
    type ShortString is bytes32;
    
    /**
     * @dev This library provides functions to convert short memory strings
     * into a `ShortString` type that can be used as an immutable variable.
     *
     * Strings of arbitrary length can be optimized using this library if
     * they are short enough (up to 31 bytes) by packing them with their
     * length (1 byte) in a single EVM word (32 bytes). Additionally, a
     * fallback mechanism can be used for every other case.
     *
     * Usage example:
     *
     * ```solidity
     * contract Named {
     *     using ShortStrings for *;
     *
     *     ShortString private immutable _name;
     *     string private _nameFallback;
     *
     *     constructor(string memory contractName) {
     *         _name = contractName.toShortStringWithFallback(_nameFallback);
     *     }
     *
     *     function name() external view returns (string memory) {
     *         return _name.toStringWithFallback(_nameFallback);
     *     }
     * }
     * ```
     */
    library ShortStrings {
        // Used as an identifier for strings longer than 31 bytes.
        bytes32 private constant _FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;
    
        error StringTooLong(string str);
        error InvalidShortString();
    
        /**
         * @dev Encode a string of at most 31 chars into a `ShortString`.
         *
         * This will trigger a `StringTooLong` error is the input string is too long.
         */
        function toShortString(string memory str) internal pure returns (ShortString) {
            bytes memory bstr = bytes(str);
            if (bstr.length > 31) {
                revert StringTooLong(str);
            }
            return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
        }
    
        /**
         * @dev Decode a `ShortString` back to a "normal" string.
         */
        function toString(ShortString sstr) internal pure returns (string memory) {
            uint256 len = byteLength(sstr);
            // using `new string(len)` would work locally but is not memory safe.
            string memory str = new string(32);
            /// @solidity memory-safe-assembly
            assembly {
                mstore(str, len)
                mstore(add(str, 0x20), sstr)
            }
            return str;
        }
    
        /**
         * @dev Return the length of a `ShortString`.
         */
        function byteLength(ShortString sstr) internal pure returns (uint256) {
            uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
            if (result > 31) {
                revert InvalidShortString();
            }
            return result;
        }
    
        /**
         * @dev Encode a string into a `ShortString`, or write it to storage if it is too long.
         */
        function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {
            if (bytes(value).length < 32) {
                return toShortString(value);
            } else {
                StorageSlot.getStringSlot(store).value = value;
                return ShortString.wrap(_FALLBACK_SENTINEL);
            }
        }
    
        /**
         * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
         */
        function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {
            if (ShortString.unwrap(value) != _FALLBACK_SENTINEL) {
                return toString(value);
            } else {
                return store;
            }
        }
    
        /**
         * @dev Return the length of a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
         *
         * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of
         * actual characters as the UTF-8 encoding of a single character can span over multiple bytes.
         */
        function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {
            if (ShortString.unwrap(value) != _FALLBACK_SENTINEL) {
                return byteLength(value);
            } else {
                return bytes(store).length;
            }
        }
    }
    
    // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5267.sol)
    
    interface IERC5267 {
        /**
         * @dev MAY be emitted to signal that the domain could have changed.
         */
        event EIP712DomainChanged();
    
        /**
         * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
         * signature.
         */
        function eip712Domain()
            external
            view
            returns (
                bytes1 fields,
                string memory name,
                string memory version,
                uint256 chainId,
                address verifyingContract,
                bytes32 salt,
                uint256[] memory extensions
            );
    }
    
    /**
     * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
     *
     * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
     * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
     * they need in their contracts using a combination of `abi.encode` and `keccak256`.
     *
     * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
     * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
     * ({_hashTypedDataV4}).
     *
     * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
     * the chain id to protect against replay attacks on an eventual fork of the chain.
     *
     * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
     * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
     *
     * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
     * separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the
     * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
     *
     * _Available since v3.4._
     *
     * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
     */
    abstract contract EIP712 is IERC5267 {
        using ShortStrings for *;
    
        bytes32 private constant _TYPE_HASH =
            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
    
        // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
        // invalidate the cached domain separator if the chain id changes.
        bytes32 private immutable _cachedDomainSeparator;
        uint256 private immutable _cachedChainId;
        address private immutable _cachedThis;
    
        bytes32 private immutable _hashedName;
        bytes32 private immutable _hashedVersion;
    
        ShortString private immutable _name;
        ShortString private immutable _version;
        string private _nameFallback;
        string private _versionFallback;
    
        /**
         * @dev Initializes the domain separator and parameter caches.
         *
         * The meaning of `name` and `version` is specified in
         * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
         *
         * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
         * - `version`: the current major version of the signing domain.
         *
         * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
         * contract upgrade].
         */
        constructor(string memory name, string memory version) {
            _name = name.toShortStringWithFallback(_nameFallback);
            _version = version.toShortStringWithFallback(_versionFallback);
            _hashedName = keccak256(bytes(name));
            _hashedVersion = keccak256(bytes(version));
    
            _cachedChainId = block.chainid;
            _cachedDomainSeparator = _buildDomainSeparator();
            _cachedThis = address(this);
        }
    
        /**
         * @dev Returns the domain separator for the current chain.
         */
        function _domainSeparatorV4() internal view returns (bytes32) {
            if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
                return _cachedDomainSeparator;
            } else {
                return _buildDomainSeparator();
            }
        }
    
        function _buildDomainSeparator() private view returns (bytes32) {
            return keccak256(abi.encode(_TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
        }
    
        /**
         * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
         * function returns the hash of the fully encoded EIP712 message for this domain.
         *
         * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
         *
         * ```solidity
         * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
         *     keccak256("Mail(address to,string contents)"),
         *     mailTo,
         *     keccak256(bytes(mailContents))
         * )));
         * address signer = ECDSA.recover(digest, signature);
         * ```
         */
        function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
            return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
        }
    
        /**
         * @dev See {EIP-5267}.
         *
         * _Available since v4.9._
         */
        function eip712Domain()
            public
            view
            virtual
            override
            returns (
                bytes1 fields,
                string memory name,
                string memory version,
                uint256 chainId,
                address verifyingContract,
                bytes32 salt,
                uint256[] memory extensions
            )
        {
            return (
                hex"0f", // 01111
                _name.toStringWithFallback(_nameFallback),
                _version.toStringWithFallback(_versionFallback),
                block.chainid,
                address(this),
                bytes32(0),
                new uint256[](0)
            );
        }
    }
    
    /**
     * @title Adds pausability to a contract, with pausing & unpausing controlled by the `pauser` and `unpauser` of a PauserRegistry contract.
     * @author Layr Labs, Inc.
     * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
     * @notice Contracts that inherit from this contract may define their own `pause` and `unpause` (and/or related) functions.
     * These functions should be permissioned as "onlyPauser" which defers to a `PauserRegistry` for determining access control.
     * @dev Pausability is implemented using a uint256, which allows up to 256 different single bit-flags; each bit can potentially pause different functionality.
     * Inspiration for this was taken from the NearBridge design here https://etherscan.io/address/0x3FEFc5A4B1c02f21cBc8D3613643ba0635b9a873#code.
     * For the `pause` and `unpause` functions we've implemented, if you pause, you can only flip (any number of) switches to on/1 (aka "paused"), and if you unpause,
     * you can only flip (any number of) switches to off/0 (aka "paused").
     * If you want a pauseXYZ function that just flips a single bit / "pausing flag", it will:
     * 1) 'bit-wise and' (aka `&`) a flag with the current paused state (as a uint256)
     * 2) update the paused state to this new value
     * @dev We note as well that we have chosen to identify flags by their *bit index* as opposed to their numerical value, so, e.g. defining `DEPOSITS_PAUSED = 3`
     * indicates specifically that if the *third bit* of `_paused` is flipped -- i.e. it is a '1' -- then deposits should be paused
     */
    contract Pausable is IPausable {
        /// @notice Address of the `PauserRegistry` contract that this contract defers to for determining access control (for pausing).
        IPauserRegistry public pauserRegistry;
    
        /// @dev whether or not the contract is currently paused
        uint256 private _paused;
    
        uint256 internal constant UNPAUSE_ALL = 0;
        uint256 internal constant PAUSE_ALL = type(uint256).max;
    
        /// @notice
        modifier onlyPauser() {
            require(pauserRegistry.isPauser(msg.sender), "msg.sender is not permissioned as pauser");
            _;
        }
    
        modifier onlyUnpauser() {
            require(msg.sender == pauserRegistry.unpauser(), "msg.sender is not permissioned as unpauser");
            _;
        }
    
        /// @notice Throws if the contract is paused, i.e. if any of the bits in `_paused` is flipped to 1.
        modifier whenNotPaused() {
            require(_paused == 0, "Pausable: contract is paused");
            _;
        }
    
        /// @notice Throws if the `indexed`th bit of `_paused` is 1, i.e. if the `index`th pause switch is flipped.
        modifier onlyWhenNotPaused(uint8 index) {
            require(!paused(index), "Pausable: index is paused");
            _;
        }
    
        /// @notice One-time function for setting the `pauserRegistry` and initializing the value of `_paused`.
        function _initializePauser(IPauserRegistry _pauserRegistry, uint256 initPausedStatus) internal {
            require(
                address(pauserRegistry) == address(0) && address(_pauserRegistry) != address(0),
                "Pausable._initializePauser: _initializePauser() can only be called once"
            );
            _paused = initPausedStatus;
            emit Paused(msg.sender, initPausedStatus);
            _setPauserRegistry(_pauserRegistry);
        }
    
        /**
         * @notice This function is used to pause an EigenLayer contract's functionality.
         * It is permissioned to the `pauser` address, which is expected to be a low threshold multisig.
         * @param newPausedStatus represents the new value for `_paused` to take, which means it may flip several bits at once.
         * @dev This function can only pause functionality, and thus cannot 'unflip' any bit in `_paused` from 1 to 0.
         */
        function pause(uint256 newPausedStatus) external onlyPauser {
            // verify that the `newPausedStatus` does not *unflip* any bits (i.e. doesn't unpause anything, all 1 bits remain)
            require((_paused & newPausedStatus) == _paused, "Pausable.pause: invalid attempt to unpause functionality");
            _paused = newPausedStatus;
            emit Paused(msg.sender, newPausedStatus);
        }
    
        /**
         * @notice Alias for `pause(type(uint256).max)`.
         */
        function pauseAll() external onlyPauser {
            _paused = type(uint256).max;
            emit Paused(msg.sender, type(uint256).max);
        }
    
        /**
         * @notice This function is used to unpause an EigenLayer contract's functionality.
         * It is permissioned to the `unpauser` address, which is expected to be a high threshold multisig or governance contract.
         * @param newPausedStatus represents the new value for `_paused` to take, which means it may flip several bits at once.
         * @dev This function can only unpause functionality, and thus cannot 'flip' any bit in `_paused` from 0 to 1.
         */
        function unpause(uint256 newPausedStatus) external onlyUnpauser {
            // verify that the `newPausedStatus` does not *flip* any bits (i.e. doesn't pause anything, all 0 bits remain)
            require(
                ((~_paused) & (~newPausedStatus)) == (~_paused),
                "Pausable.unpause: invalid attempt to pause functionality"
            );
            _paused = newPausedStatus;
            emit Unpaused(msg.sender, newPausedStatus);
        }
    
        /// @notice Returns the current paused status as a uint256.
        function paused() public view virtual returns (uint256) {
            return _paused;
        }
    
        /// @notice Returns 'true' if the `indexed`th bit of `_paused` is 1, and 'false' otherwise
        function paused(uint8 index) public view virtual returns (bool) {
            uint256 mask = 1 << index;
            return ((_paused & mask) == mask);
        }
    
        /// @notice Allows the unpauser to set a new pauser registry
        function setPauserRegistry(IPauserRegistry newPauserRegistry) external onlyUnpauser {
            _setPauserRegistry(newPauserRegistry);
        }
    
        /// internal function for setting pauser registry
        function _setPauserRegistry(IPauserRegistry newPauserRegistry) internal {
            require(
                address(newPauserRegistry) != address(0),
                "Pausable._setPauserRegistry: newPauserRegistry cannot be the zero address"
            );
            emit PauserRegistrySet(pauserRegistry, newPauserRegistry);
            pauserRegistry = newPauserRegistry;
        }
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[48] private __gap;
    }
    
    abstract contract RegistryCoordinatorStorage is IRegistryCoordinator {
    
        /*******************************************************************************
                                   CONSTANTS AND IMMUTABLES 
        *******************************************************************************/
    
        /// @notice The EIP-712 typehash for the `DelegationApproval` struct used by the contract
        bytes32 public constant OPERATOR_CHURN_APPROVAL_TYPEHASH =
            keccak256("OperatorChurnApproval(address registeringOperator,bytes32 registeringOperatorId,OperatorKickParam[] operatorKickParams,bytes32 salt,uint256 expiry)OperatorKickParam(uint8 quorumNumber,address operator)");
        /// @notice The EIP-712 typehash used for registering BLS public keys
        bytes32 public constant PUBKEY_REGISTRATION_TYPEHASH = keccak256("BN254PubkeyRegistration(address operator)");
        /// @notice The maximum value of a quorum bitmap
        uint256 internal constant MAX_QUORUM_BITMAP = type(uint192).max;
        /// @notice The basis point denominator
        uint16 internal constant BIPS_DENOMINATOR = 10000;
        /// @notice Index for flag that pauses operator registration
        uint8 internal constant PAUSED_REGISTER_OPERATOR = 0;
        /// @notice Index for flag that pauses operator deregistration
        uint8 internal constant PAUSED_DEREGISTER_OPERATOR = 1;
        /// @notice Index for flag pausing operator stake updates
        uint8 internal constant PAUSED_UPDATE_OPERATOR = 2;
        /// @notice The maximum number of quorums this contract supports
        uint8 internal constant MAX_QUORUM_COUNT = 192;
    
        /// @notice the ServiceManager for this AVS, which forwards calls onto EigenLayer's core contracts
        IServiceManager public immutable serviceManager;
        /// @notice the BLS Aggregate Pubkey Registry contract that will keep track of operators' aggregate BLS public keys per quorum
        IBLSApkRegistry public immutable blsApkRegistry;
        /// @notice the Stake Registry contract that will keep track of operators' stakes
        IStakeRegistry public immutable stakeRegistry;
        /// @notice the Index Registry contract that will keep track of operators' indexes
        IIndexRegistry public immutable indexRegistry;
    
        /*******************************************************************************
                                           STATE 
        *******************************************************************************/
    
        /// @notice the current number of quorums supported by the registry coordinator
        uint8 public quorumCount;
        /// @notice maps quorum number => operator cap and kick params
        mapping(uint8 => OperatorSetParam) internal _quorumParams;
        /// @notice maps operator id => historical quorums they registered for
        mapping(bytes32 => QuorumBitmapUpdate[]) internal _operatorBitmapHistory;
        /// @notice maps operator address => operator id and status
        mapping(address => OperatorInfo) internal _operatorInfo;
        /// @notice whether the salt has been used for an operator churn approval
        mapping(bytes32 => bool) public isChurnApproverSaltUsed;
        /// @notice mapping from quorum number to the latest block that all quorums were updated all at once
        mapping(uint8 => uint256) public quorumUpdateBlockNumber;
    
        /// @notice the dynamic-length array of the registries this coordinator is coordinating
        address[] public registries;
        /// @notice the address of the entity allowed to sign off on operators getting kicked out of the AVS during registration
        address public churnApprover;
        /// @notice the address of the entity allowed to eject operators from the AVS
        address public ejector;
    
        constructor(
            IServiceManager _serviceManager,
            IStakeRegistry _stakeRegistry,
            IBLSApkRegistry _blsApkRegistry,
            IIndexRegistry _indexRegistry
        ) {
            serviceManager = _serviceManager;
            stakeRegistry = _stakeRegistry;
            blsApkRegistry = _blsApkRegistry;
            indexRegistry = _indexRegistry;
        }
    
        // storage gap for upgradeability
        // slither-disable-next-line shadowing-state
        uint256[41] private __GAP;
    }
    
    /**
     * @title A `RegistryCoordinator` that has three registries:
     *      1) a `StakeRegistry` that keeps track of operators' stakes
     *      2) a `BLSApkRegistry` that keeps track of operators' BLS public keys and aggregate BLS public keys for each quorum
     *      3) an `IndexRegistry` that keeps track of an ordered list of operators for each quorum
     * 
     * @author Layr Labs, Inc.
     */
    contract RegistryCoordinator is 
        EIP712, 
        Initializable, 
        Pausable,
        OwnableUpgradeable,
        RegistryCoordinatorStorage, 
        ISocketUpdater, 
        ISignatureUtils
    {
        using BitmapUtils for *;
        using BN254 for BN254.G1Point;
    
        modifier onlyEjector {
            require(msg.sender == ejector, "RegistryCoordinator.onlyEjector: caller is not the ejector");
            _;
        }
    
        /// @dev Checks that `quorumNumber` corresponds to a quorum that has been created
        /// via `initialize` or `createQuorum`
        modifier quorumExists(uint8 quorumNumber) {
            require(
                quorumNumber < quorumCount, 
                "RegistryCoordinator.quorumExists: quorum does not exist"
            );
            _;
        }
    
        constructor(
            IServiceManager _serviceManager,
            IStakeRegistry _stakeRegistry,
            IBLSApkRegistry _blsApkRegistry,
            IIndexRegistry _indexRegistry
        ) 
            RegistryCoordinatorStorage(_serviceManager, _stakeRegistry, _blsApkRegistry, _indexRegistry)
            EIP712("AVSRegistryCoordinator", "v0.0.1") 
        {
            _disableInitializers();
        }
    
        /**
         * @param _initialOwner will hold the owner role
         * @param _churnApprover will hold the churnApprover role, which authorizes registering with churn
         * @param _ejector will hold the ejector role, which can force-eject operators from quorums
         * @param _pauserRegistry a registry of addresses that can pause the contract
         * @param _initialPausedStatus pause status after calling initialize
         * Config for initial quorums (see `createQuorum`):
         * @param _operatorSetParams max operator count and operator churn parameters
         * @param _minimumStakes minimum stake weight to allow an operator to register
         * @param _strategyParams which Strategies/multipliers a quorum considers when calculating stake weight
         */
        function initialize(
            address _initialOwner,
            address _churnApprover,
            address _ejector,
            IPauserRegistry _pauserRegistry,
            uint256 _initialPausedStatus,
            OperatorSetParam[] memory _operatorSetParams,
            uint96[] memory _minimumStakes,
            IStakeRegistry.StrategyParams[][] memory _strategyParams
        ) external initializer {
            require(
                _operatorSetParams.length == _minimumStakes.length && _minimumStakes.length == _strategyParams.length,
                "RegistryCoordinator.initialize: input length mismatch"
            );
            
            // Initialize roles
            _transferOwnership(_initialOwner);
            _initializePauser(_pauserRegistry, _initialPausedStatus);
            _setChurnApprover(_churnApprover);
            _setEjector(_ejector);
    
            // Add registry contracts to the registries array
            registries.push(address(stakeRegistry));
            registries.push(address(blsApkRegistry));
            registries.push(address(indexRegistry));
    
            // Create quorums
            for (uint256 i = 0; i < _operatorSetParams.length; i++) {
                _createQuorum(_operatorSetParams[i], _minimumStakes[i], _strategyParams[i]);
            }
        }
    
        /*******************************************************************************
                                EXTERNAL FUNCTIONS 
        *******************************************************************************/
    
        /**
         * @notice Registers msg.sender as an operator for one or more quorums. If any quorum exceeds its maximum
         * operator capacity after the operator is registered, this method will fail.
         * @param quorumNumbers is an ordered byte array containing the quorum numbers being registered for
         * @param socket is the socket of the operator (typically an IP address)
         * @param params contains the G1 & G2 public keys of the operator, and a signature proving their ownership
         * @param operatorSignature is the signature of the operator used by the AVS to register the operator in the delegation manager
         * @dev `params` is ignored if the caller has previously registered a public key
         * @dev `operatorSignature` is ignored if the operator's status is already REGISTERED
         */
        function registerOperator(
            bytes calldata quorumNumbers,
            string calldata socket,
            IBLSApkRegistry.PubkeyRegistrationParams calldata params,
            SignatureWithSaltAndExpiry memory operatorSignature
        ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
            /**
             * If the operator has NEVER registered a pubkey before, use `params` to register
             * their pubkey in blsApkRegistry
             *
             * If the operator HAS registered a pubkey, `params` is ignored and the pubkey hash
             * (operatorId) is fetched instead
             */
            bytes32 operatorId = _getOrCreateOperatorId(msg.sender, params);
    
            // Register the operator in each of the registry contracts and update the operator's
            // quorum bitmap and registration status
            uint32[] memory numOperatorsPerQuorum = _registerOperator({
                operator: msg.sender, 
                operatorId: operatorId,
                quorumNumbers: quorumNumbers, 
                socket: socket,
                operatorSignature: operatorSignature
            }).numOperatorsPerQuorum;
    
            // For each quorum, validate that the new operator count does not exceed the maximum
            // (If it does, an operator needs to be replaced -- see `registerOperatorWithChurn`)
            for (uint256 i = 0; i < quorumNumbers.length; i++) {
                uint8 quorumNumber = uint8(quorumNumbers[i]);
    
                require(
                    numOperatorsPerQuorum[i] <= _quorumParams[quorumNumber].maxOperatorCount,
                    "RegistryCoordinator.registerOperator: operator count exceeds maximum"
                );
            }
        }
    
        /**
         * @notice Registers msg.sender as an operator for one or more quorums. If any quorum reaches its maximum operator
         * capacity, `operatorKickParams` is used to replace an old operator with the new one.
         * @param quorumNumbers is an ordered byte array containing the quorum numbers being registered for
         * @param params contains the G1 & G2 public keys of the operator, and a signature proving their ownership
         * @param operatorKickParams used to determine which operator is removed to maintain quorum capacity as the
         * operator registers for quorums
         * @param churnApproverSignature is the signature of the churnApprover over the `operatorKickParams`
         * @param operatorSignature is the signature of the operator used by the AVS to register the operator in the delegation manager
         * @dev `params` is ignored if the caller has previously registered a public key
         * @dev `operatorSignature` is ignored if the operator's status is already REGISTERED
         */
        function registerOperatorWithChurn(
            bytes calldata quorumNumbers, 
            string calldata socket,
            IBLSApkRegistry.PubkeyRegistrationParams calldata params,
            OperatorKickParam[] calldata operatorKickParams,
            SignatureWithSaltAndExpiry memory churnApproverSignature,
            SignatureWithSaltAndExpiry memory operatorSignature
        ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
            require(operatorKickParams.length == quorumNumbers.length, "RegistryCoordinator.registerOperatorWithChurn: input length mismatch");
            
            /**
             * If the operator has NEVER registered a pubkey before, use `params` to register
             * their pubkey in blsApkRegistry
             *
             * If the operator HAS registered a pubkey, `params` is ignored and the pubkey hash
             * (operatorId) is fetched instead
             */
            bytes32 operatorId = _getOrCreateOperatorId(msg.sender, params);
    
            // Verify the churn approver's signature for the registering operator and kick params
            _verifyChurnApproverSignature({
                registeringOperator: msg.sender,
                registeringOperatorId: operatorId,
                operatorKickParams: operatorKickParams,
                churnApproverSignature: churnApproverSignature
            });
    
            // Register the operator in each of the registry contracts and update the operator's
            // quorum bitmap and registration status
            RegisterResults memory results = _registerOperator({
                operator: msg.sender,
                operatorId: operatorId,
                quorumNumbers: quorumNumbers,
                socket: socket,
                operatorSignature: operatorSignature
            });
    
            // Check that each quorum's operator count is below the configured maximum. If the max
            // is exceeded, use `operatorKickParams` to deregister an existing operator to make space
            for (uint256 i = 0; i < quorumNumbers.length; i++) {
                OperatorSetParam memory operatorSetParams = _quorumParams[uint8(quorumNumbers[i])];
                
                /**
                 * If the new operator count for any quorum exceeds the maximum, validate
                 * that churn can be performed, then deregister the specified operator
                 */
                if (results.numOperatorsPerQuorum[i] > operatorSetParams.maxOperatorCount) {
                    _validateChurn({
                        quorumNumber: uint8(quorumNumbers[i]),
                        totalQuorumStake: results.totalStakes[i],
                        newOperator: msg.sender,
                        newOperatorStake: results.operatorStakes[i],
                        kickParams: operatorKickParams[i],
                        setParams: operatorSetParams
                    });
    
                    _deregisterOperator(operatorKickParams[i].operator, quorumNumbers[i:i+1]);
                }
            }
        }
    
        /**
         * @notice Deregisters the caller from one or more quorums
         * @param quorumNumbers is an ordered byte array containing the quorum numbers being deregistered from
         */
        function deregisterOperator(
            bytes calldata quorumNumbers
        ) external onlyWhenNotPaused(PAUSED_DEREGISTER_OPERATOR) {
            _deregisterOperator({
                operator: msg.sender, 
                quorumNumbers: quorumNumbers
            });
        }
    
        /**
         * @notice Updates the StakeRegistry's view of one or more operators' stakes. If any operator
         * is found to be below the minimum stake for the quorum, they are deregistered.
         * @dev stakes are queried from the Eigenlayer core DelegationManager contract
         * @param operators a list of operator addresses to update
         */
        function updateOperators(address[] calldata operators) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) {
            for (uint256 i = 0; i < operators.length; i++) {
                address operator = operators[i];
                OperatorInfo memory operatorInfo = _operatorInfo[operator];
                bytes32 operatorId = operatorInfo.operatorId;
    
                // Update the operator's stake for their active quorums
                uint192 currentBitmap = _currentOperatorBitmap(operatorId);
                bytes memory quorumsToUpdate = BitmapUtils.bitmapToBytesArray(currentBitmap);
                _updateOperator(operator, operatorInfo, quorumsToUpdate);
            }
        }
    
        /**
         * @notice For each quorum in `quorumNumbers`, updates the StakeRegistry's view of ALL its registered operators' stakes.
         * Each quorum's `quorumUpdateBlockNumber` is also updated, which tracks the most recent block number when ALL registered
         * operators were updated.
         * @dev stakes are queried from the Eigenlayer core DelegationManager contract
         * @param operatorsPerQuorum for each quorum in `quorumNumbers`, this has a corresponding list of operators to update.
         * @dev Each list of operator addresses MUST be sorted in ascending order
         * @dev Each list of operator addresses MUST represent the entire list of registered operators for the corresponding quorum
         * @param quorumNumbers is an ordered byte array containing the quorum numbers being updated
         * @dev invariant: Each list of `operatorsPerQuorum` MUST be a sorted version of `IndexRegistry.getOperatorListAtBlockNumber`
         * for the corresponding quorum.
         * @dev note on race condition: if an operator registers/deregisters for any quorum in `quorumNumbers` after a txn to 
         * this method is broadcast (but before it is executed), the method will fail
         */
        function updateOperatorsForQuorum(
            address[][] calldata operatorsPerQuorum,
            bytes calldata quorumNumbers
        ) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) {
            // Input validation 
            // - all quorums should exist (checked against `quorumCount` in orderedBytesArrayToBitmap)
            // - there should be no duplicates in `quorumNumbers`
            // - there should be one list of operators per quorum
            uint192 quorumBitmap = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
            require(
                operatorsPerQuorum.length == quorumNumbers.length,
                "RegistryCoordinator.updateOperatorsForQuorum: input length mismatch"
            );
    
            // For each quorum, update ALL registered operators
            for (uint256 i = 0; i < quorumNumbers.length; ++i) {
                uint8 quorumNumber = uint8(quorumNumbers[i]);
    
                // Ensure we've passed in the correct number of operators for this quorum
                address[] calldata currQuorumOperators = operatorsPerQuorum[i];
                require(
                    currQuorumOperators.length == indexRegistry.totalOperatorsForQuorum(quorumNumber),
                    "RegistryCoordinator.updateOperatorsForQuorum: number of updated operators does not match quorum total"
                );
    
                address prevOperatorAddress = address(0);
                // For each operator:
                // - check that they are registered for this quorum
                // - check that their address is strictly greater than the last operator
                // ... then, update their stakes
                for (uint256 j = 0; j < currQuorumOperators.length; ++j) {
                    address operator = currQuorumOperators[j];
                    
                    OperatorInfo memory operatorInfo = _operatorInfo[operator];
                    bytes32 operatorId = operatorInfo.operatorId;
                    
                    {
                        uint192 currentBitmap = _currentOperatorBitmap(operatorId);
                        // Check that the operator is registered
                        require(
                            BitmapUtils.isSet(currentBitmap, quorumNumber),
                            "RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum"
                        );
                        // Prevent duplicate operators
                        require(
                            operator > prevOperatorAddress,
                            "RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order"
                        );
                    }
                    
                    // Update the operator
                    _updateOperator(operator, operatorInfo, quorumNumbers[i:i+1]);
                    prevOperatorAddress = operator;
                }
    
                // Update timestamp that all operators in quorum have been updated all at once
                quorumUpdateBlockNumber[quorumNumber] = block.number;
                emit QuorumBlockNumberUpdated(quorumNumber, block.number);
            }
        }
    
        /**
         * @notice Updates the socket of the msg.sender given they are a registered operator
         * @param socket is the new socket of the operator
         */
        function updateSocket(string memory socket) external {
            require(_operatorInfo[msg.sender].status == OperatorStatus.REGISTERED, "RegistryCoordinator.updateSocket: operator is not registered");
            emit OperatorSocketUpdate(_operatorInfo[msg.sender].operatorId, socket);
        }
    
        /*******************************************************************************
                                EXTERNAL FUNCTIONS - EJECTOR
        *******************************************************************************/
    
        /**
         * @notice Forcibly deregisters an operator from one or more quorums
         * @param operator the operator to eject
         * @param quorumNumbers the quorum numbers to eject the operator from
         */
        function ejectOperator(
            address operator, 
            bytes calldata quorumNumbers
        ) external onlyEjector {
            _deregisterOperator({
                operator: operator, 
                quorumNumbers: quorumNumbers
            });
        }
    
        /*******************************************************************************
                                EXTERNAL FUNCTIONS - OWNER
        *******************************************************************************/
    
        /**
         * @notice Creates a quorum and initializes it in each registry contract
         * @param operatorSetParams configures the quorum's max operator count and churn parameters
         * @param minimumStake sets the minimum stake required for an operator to register or remain
         * registered
         * @param strategyParams a list of strategies and multipliers used by the StakeRegistry to
         * calculate an operator's stake weight for the quorum
         */
        function createQuorum(
            OperatorSetParam memory operatorSetParams,
            uint96 minimumStake,
            IStakeRegistry.StrategyParams[] memory strategyParams
        ) external virtual onlyOwner {
            _createQuorum(operatorSetParams, minimumStake, strategyParams);
        }
    
        /**
         * @notice Updates an existing quorum's configuration with a new max operator count
         * and operator churn parameters
         * @param quorumNumber the quorum number to update
         * @param operatorSetParams the new config
         * @dev only callable by the owner
         */
        function setOperatorSetParams(
            uint8 quorumNumber, 
            OperatorSetParam memory operatorSetParams
        ) external onlyOwner quorumExists(quorumNumber) {
            _setOperatorSetParams(quorumNumber, operatorSetParams);
        }
    
        /**
         * @notice Sets the churnApprover, which approves operator registration with churn
         * (see `registerOperatorWithChurn`)
         * @param _churnApprover the new churn approver
         * @dev only callable by the owner
         */
        function setChurnApprover(address _churnApprover) external onlyOwner {
            _setChurnApprover(_churnApprover);
        }
    
        /**
         * @notice Sets the ejector, which can force-deregister operators from quorums
         * @param _ejector the new ejector
         * @dev only callable by the owner
         */
        function setEjector(address _ejector) external onlyOwner {
            _setEjector(_ejector);
        }
    
        /*******************************************************************************
                                INTERNAL FUNCTIONS
        *******************************************************************************/
    
        struct RegisterResults {
            uint32[] numOperatorsPerQuorum;
            uint96[] operatorStakes;
            uint96[] totalStakes;
        }
    
        /** 
         * @notice Register the operator for one or more quorums. This method updates the
         * operator's quorum bitmap, socket, and status, then registers them with each registry.
         */
        function _registerOperator(
            address operator, 
            bytes32 operatorId,
            bytes calldata quorumNumbers,
            string memory socket,
            SignatureWithSaltAndExpiry memory operatorSignature
        ) internal virtual returns (RegisterResults memory results) {
            /**
             * Get bitmap of quorums to register for and operator's current bitmap. Validate that:
             * - we're trying to register for at least 1 quorum
             * - the quorums we're registering for exist (checked against `quorumCount` in orderedBytesArrayToBitmap)
             * - the operator is not currently registered for any quorums we're registering for
             * Then, calculate the operator's new bitmap after registration
             */
            uint192 quorumsToAdd = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
            uint192 currentBitmap = _currentOperatorBitmap(operatorId);
            require(!quorumsToAdd.isEmpty(), "RegistryCoordinator._registerOperator: bitmap cannot be 0");
            require(quorumsToAdd.noBitsInCommon(currentBitmap), "RegistryCoordinator._registerOperator: operator already registered for some quorums being registered for");
            uint192 newBitmap = uint192(currentBitmap.plus(quorumsToAdd));
    
            /**
             * Update operator's bitmap, socket, and status. Only update operatorInfo if needed:
             * if we're `REGISTERED`, the operatorId and status are already correct.
             */
            _updateOperatorBitmap({
                operatorId: operatorId,
                newBitmap: newBitmap
            });
    
            emit OperatorSocketUpdate(operatorId, socket);
    
            // If the operator wasn't registered for any quorums, update their status
            // and register them with this AVS in EigenLayer core (DelegationManager)
            if (_operatorInfo[operator].status != OperatorStatus.REGISTERED) {
                _operatorInfo[operator] = OperatorInfo({
                    operatorId: operatorId,
                    status: OperatorStatus.REGISTERED
                });
    
                // Register the operator with the EigenLayer core contracts via this AVS's ServiceManager
                serviceManager.registerOperatorToAVS(operator, operatorSignature);
    
                emit OperatorRegistered(operator, operatorId);
            }
    
            // Register the operator with the BLSApkRegistry, StakeRegistry, and IndexRegistry
            blsApkRegistry.registerOperator(operator, quorumNumbers);
            (results.operatorStakes, results.totalStakes) = 
                stakeRegistry.registerOperator(operator, operatorId, quorumNumbers);
            results.numOperatorsPerQuorum = indexRegistry.registerOperator(operatorId, quorumNumbers);
    
            return results;
        }
    
        /**
         * @notice Fetches an operator's pubkey hash from the BLSApkRegistry. If the
         * operator has not registered a pubkey, attempts to register a pubkey using
         * `params`
         * @param operator the operator whose pubkey to query from the BLSApkRegistry
         * @param params contains the G1 & G2 public keys of the operator, and a signature proving their ownership
         * @dev `params` can be empty if the operator has already registered a pubkey in the BLSApkRegistry
         */
        function _getOrCreateOperatorId(
            address operator,
            IBLSApkRegistry.PubkeyRegistrationParams calldata params
        ) internal returns (bytes32 operatorId) {
            operatorId = blsApkRegistry.getOperatorId(operator);
            if (operatorId == 0) {
                operatorId = blsApkRegistry.registerBLSPublicKey(operator, params, pubkeyRegistrationMessageHash(operator));
            }
            return operatorId;
        }
    
        /**
         * @notice Validates that an incoming operator is eligible to replace an existing
         * operator based on the stake of both
         * @dev In order to churn, the incoming operator needs to have more stake than the
         * existing operator by a proportion given by `kickBIPsOfOperatorStake`
         * @dev In order to be churned out, the existing operator needs to have a proportion
         * of the total quorum stake less than `kickBIPsOfTotalStake`
         * @param quorumNumber `newOperator` is trying to replace an operator in this quorum
         * @param totalQuorumStake the total stake of all operators in the quorum, after the
         * `newOperator` registers
         * @param newOperator the incoming operator
         * @param newOperatorStake the incoming operator's stake
         * @param kickParams the quorum number and existing operator to replace
         * @dev the existing operator's registration to this quorum isn't checked here, but
         * if we attempt to deregister them, this will be checked in `_deregisterOperator`
         * @param setParams config for this quorum containing `kickBIPsX` stake proportions
         * mentioned above
         */
        function _validateChurn(
            uint8 quorumNumber, 
            uint96 totalQuorumStake,
            address newOperator, 
            uint96 newOperatorStake,
            OperatorKickParam memory kickParams, 
            OperatorSetParam memory setParams
        ) internal view {
            address operatorToKick = kickParams.operator;
            bytes32 idToKick = _operatorInfo[operatorToKick].operatorId;
            require(newOperator != operatorToKick, "RegistryCoordinator._validateChurn: cannot churn self");
            require(kickParams.quorumNumber == quorumNumber, "RegistryCoordinator._validateChurn: quorumNumber not the same as signed");
    
            // Get the target operator's stake and check that it is below the kick thresholds
            uint96 operatorToKickStake = stakeRegistry.getCurrentStake(idToKick, quorumNumber);
            require(
                newOperatorStake > _individualKickThreshold(operatorToKickStake, setParams),
                "RegistryCoordinator._validateChurn: incoming operator has insufficient stake for churn"
            );
            require(
                operatorToKickStake < _totalKickThreshold(totalQuorumStake, setParams),
                "RegistryCoordinator._validateChurn: cannot kick operator with more than kickBIPsOfTotalStake"
            );
        }
    
        /**
         * @dev Deregister the operator from one or more quorums
         * This method updates the operator's quorum bitmap and status, then deregisters
         * the operator with the BLSApkRegistry, IndexRegistry, and StakeRegistry
         */
        function _deregisterOperator(
            address operator, 
            bytes memory quorumNumbers
        ) internal virtual {
            // Fetch the operator's info and ensure they are registered
            OperatorInfo storage operatorInfo = _operatorInfo[operator];
            bytes32 operatorId = operatorInfo.operatorId;
            require(operatorInfo.status == OperatorStatus.REGISTERED, "RegistryCoordinator._deregisterOperator: operator is not registered");
            
            /**
             * Get bitmap of quorums to deregister from and operator's current bitmap. Validate that:
             * - we're trying to deregister from at least 1 quorum
             * - the quorums we're deregistering from exist (checked against `quorumCount` in orderedBytesArrayToBitmap)
             * - the operator is currently registered for any quorums we're trying to deregister from
             * Then, calculate the operator's new bitmap after deregistration
             */
            uint192 quorumsToRemove = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
            uint192 currentBitmap = _currentOperatorBitmap(operatorId);
            require(!quorumsToRemove.isEmpty(), "RegistryCoordinator._deregisterOperator: bitmap cannot be 0");
            require(quorumsToRemove.isSubsetOf(currentBitmap), "RegistryCoordinator._deregisterOperator: operator is not registered for specified quorums");
            uint192 newBitmap = uint192(currentBitmap.minus(quorumsToRemove));
    
            // Update operator's bitmap and status
            _updateOperatorBitmap({
                operatorId: operatorId,
                newBitmap: newBitmap
            });
    
            // If the operator is no longer registered for any quorums, update their status and deregister 
            // them from the AVS via the EigenLayer core contracts
            if (newBitmap.isEmpty()) {
                operatorInfo.status = OperatorStatus.DEREGISTERED;
                serviceManager.deregisterOperatorFromAVS(operator);
                emit OperatorDeregistered(operator, operatorId);
            }
    
            // Deregister operator with each of the registry contracts
            blsApkRegistry.deregisterOperator(operator, quorumNumbers);
            stakeRegistry.deregisterOperator(operatorId, quorumNumbers);
            indexRegistry.deregisterOperator(operatorId, quorumNumbers);
        }
    
        /**
         * @notice Updates the StakeRegistry's view of the operator's stake in one or more quorums.
         * For any quorums where the StakeRegistry finds the operator is under the configured minimum
         * stake, `quorumsToRemove` is returned and used to deregister the operator from those quorums
         * @dev does nothing if operator is not registered for any quorums.
         */
        function _updateOperator(
            address operator,
            OperatorInfo memory operatorInfo,
            bytes memory quorumsToUpdate
        ) internal {
            if (operatorInfo.status != OperatorStatus.REGISTERED) {
                return;
            }
            bytes32 operatorId = operatorInfo.operatorId;
            uint192 quorumsToRemove = stakeRegistry.updateOperatorStake(operator, operatorId, quorumsToUpdate);
    
            if (!quorumsToRemove.isEmpty()) {
                _deregisterOperator({
                    operator: operator,
                    quorumNumbers: BitmapUtils.bitmapToBytesArray(quorumsToRemove)
                });    
            }
        }
    
        /**
         * @notice Returns the stake threshold required for an incoming operator to replace an existing operator
         * The incoming operator must have more stake than the return value.
         */
        function _individualKickThreshold(uint96 operatorStake, OperatorSetParam memory setParams) internal pure returns (uint96) {
            return operatorStake * setParams.kickBIPsOfOperatorStake / BIPS_DENOMINATOR;
        }
    
        /**
         * @notice Returns the total stake threshold required for an operator to remain in a quorum.
         * The operator must have at least the returned stake amount to keep their position.
         */
        function _totalKickThreshold(uint96 totalStake, OperatorSetParam memory setParams) internal pure returns (uint96) {
            return totalStake * setParams.kickBIPsOfTotalStake / BIPS_DENOMINATOR;
        }
    
        /// @notice verifies churnApprover's signature on operator churn approval and increments the churnApprover nonce
        function _verifyChurnApproverSignature(
            address registeringOperator,
            bytes32 registeringOperatorId, 
            OperatorKickParam[] memory operatorKickParams, 
            SignatureWithSaltAndExpiry memory churnApproverSignature
        ) internal {
            // make sure the salt hasn't been used already
            require(!isChurnApproverSaltUsed[churnApproverSignature.salt], "RegistryCoordinator._verifyChurnApproverSignature: churnApprover salt already used");
            require(churnApproverSignature.expiry >= block.timestamp, "RegistryCoordinator._verifyChurnApproverSignature: churnApprover signature expired");   
    
            // set salt used to true
            isChurnApproverSaltUsed[churnApproverSignature.salt] = true;    
    
            // check the churnApprover's signature 
            EIP1271SignatureUtils.checkSignature_EIP1271(
                churnApprover, 
                calculateOperatorChurnApprovalDigestHash(registeringOperator, registeringOperatorId, operatorKickParams, churnApproverSignature.salt, churnApproverSignature.expiry), 
                churnApproverSignature.signature
            );
        }
    
        /**
         * @notice Creates a quorum and initializes it in each registry contract
         * @param operatorSetParams configures the quorum's max operator count and churn parameters
         * @param minimumStake sets the minimum stake required for an operator to register or remain
         * registered
         * @param strategyParams a list of strategies and multipliers used by the StakeRegistry to
         * calculate an operator's stake weight for the quorum
         */
        function _createQuorum(
            OperatorSetParam memory operatorSetParams,
            uint96 minimumStake,
            IStakeRegistry.StrategyParams[] memory strategyParams
        ) internal {
            // Increment the total quorum count. Fails if we're already at the max
            uint8 prevQuorumCount = quorumCount;
            require(prevQuorumCount < MAX_QUORUM_COUNT, "RegistryCoordinator.createQuorum: max quorums reached");
            quorumCount = prevQuorumCount + 1;
            
            // The previous count is the new quorum's number
            uint8 quorumNumber = prevQuorumCount;
    
            // Initialize the quorum here and in each registry
            _setOperatorSetParams(quorumNumber, operatorSetParams);
            stakeRegistry.initializeQuorum(quorumNumber, minimumStake, strategyParams);
            indexRegistry.initializeQuorum(quorumNumber);
            blsApkRegistry.initializeQuorum(quorumNumber);
        }
    
        /**
         * @notice Record an update to an operator's quorum bitmap.
         * @param newBitmap is the most up-to-date set of bitmaps the operator is registered for
         */
        function _updateOperatorBitmap(bytes32 operatorId, uint192 newBitmap) internal {
    
            uint256 historyLength = _operatorBitmapHistory[operatorId].length;
    
            if (historyLength == 0) {
                // No prior bitmap history - push our first entry
                _operatorBitmapHistory[operatorId].push(QuorumBitmapUpdate({
                    updateBlockNumber: uint32(block.number),
                    nextUpdateBlockNumber: 0,
                    quorumBitmap: newBitmap
                }));
            } else {
                // We have prior history - fetch our last-recorded update
                QuorumBitmapUpdate storage lastUpdate = _operatorBitmapHistory[operatorId][historyLength - 1];
    
                /**
                 * If the last update was made in the current block, update the entry.
                 * Otherwise, push a new entry and update the previous entry's "next" field
                 */
                if (lastUpdate.updateBlockNumber == uint32(block.number)) {
                    lastUpdate.quorumBitmap = newBitmap;
                } else {
                    lastUpdate.nextUpdateBlockNumber = uint32(block.number);
                    _operatorBitmapHistory[operatorId].push(QuorumBitmapUpdate({
                        updateBlockNumber: uint32(block.number),
                        nextUpdateBlockNumber: 0,
                        quorumBitmap: newBitmap
                    }));
                }
            }
        }
    
        /// @notice Get the most recent bitmap for the operator, returning an empty bitmap if
        /// the operator is not registered.
        function _currentOperatorBitmap(bytes32 operatorId) internal view returns (uint192) {
            uint256 historyLength = _operatorBitmapHistory[operatorId].length;
            if (historyLength == 0) {
                return 0;
            } else {
                return _operatorBitmapHistory[operatorId][historyLength - 1].quorumBitmap;
            }
        }
    
        /**
         * @notice Returns the index of the quorumBitmap for the provided `operatorId` at the given `blockNumber`
         * @dev Reverts if the operator had not yet (ever) registered at `blockNumber`
         * @dev This function is designed to find proper inputs to the `getQuorumBitmapAtBlockNumberByIndex` function
         */
        function _getQuorumBitmapIndexAtBlockNumber(
            uint32 blockNumber, 
            bytes32 operatorId
        ) internal view returns (uint32 index) {
            uint256 length = _operatorBitmapHistory[operatorId].length;
    
            // Traverse the operator's bitmap history in reverse, returning the first index
            // corresponding to an update made before or at `blockNumber`
            for (uint256 i = 0; i < length; i++) {
                index = uint32(length - i - 1);
    
                if (_operatorBitmapHistory[operatorId][index].updateBlockNumber <= blockNumber) {
                    return index;
                }
            }
    
            revert(
                "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number"
            );
        }
    
        function _setOperatorSetParams(uint8 quorumNumber, OperatorSetParam memory operatorSetParams) internal {
            _quorumParams[quorumNumber] = operatorSetParams;
            emit OperatorSetParamsUpdated(quorumNumber, operatorSetParams);
        }
        
        function _setChurnApprover(address newChurnApprover) internal {
            emit ChurnApproverUpdated(churnApprover, newChurnApprover);
            churnApprover = newChurnApprover;
        }
    
        function _setEjector(address newEjector) internal {
            emit EjectorUpdated(ejector, newEjector);
            ejector = newEjector;
        }
    
        /*******************************************************************************
                                VIEW FUNCTIONS
        *******************************************************************************/
    
        /// @notice Returns the operator set params for the given `quorumNumber`
        function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory) {
            return _quorumParams[quorumNumber];
        }
    
        /// @notice Returns the operator struct for the given `operator`
        function getOperator(address operator) external view returns (OperatorInfo memory) {
            return _operatorInfo[operator];
        }
    
        /// @notice Returns the operatorId for the given `operator`
        function getOperatorId(address operator) external view returns (bytes32) {
            return _operatorInfo[operator].operatorId;
        }
    
        /// @notice Returns the operator address for the given `operatorId`
        function getOperatorFromId(bytes32 operatorId) external view returns (address) {
            return blsApkRegistry.getOperatorFromPubkeyHash(operatorId);
        }
    
        /// @notice Returns the status for the given `operator`
        function getOperatorStatus(address operator) external view returns (IRegistryCoordinator.OperatorStatus) {
            return _operatorInfo[operator].status;
        }
    
        /**
         * @notice Returns the indices of the quorumBitmaps for the provided `operatorIds` at the given `blockNumber`
         * @dev Reverts if any of the `operatorIds` was not (yet) registered at `blockNumber`
         * @dev This function is designed to find proper inputs to the `getQuorumBitmapAtBlockNumberByIndex` function
         */
        function getQuorumBitmapIndicesAtBlockNumber(
            uint32 blockNumber, 
            bytes32[] memory operatorIds
        ) external view returns (uint32[] memory) {
            uint32[] memory indices = new uint32[](operatorIds.length);
            for (uint256 i = 0; i < operatorIds.length; i++) {
                indices[i] = _getQuorumBitmapIndexAtBlockNumber(blockNumber, operatorIds[i]);
            }
            return indices;
        }
    
        /**
         * @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index`,
         * reverting if `index` is incorrect
         * @dev This function is meant to be used in concert with `getQuorumBitmapIndicesAtBlockNumber`, which
         * helps off-chain processes to fetch the correct `index` input
         */ 
        function getQuorumBitmapAtBlockNumberByIndex(
            bytes32 operatorId, 
            uint32 blockNumber, 
            uint256 index
        ) external view returns (uint192) {
            QuorumBitmapUpdate memory quorumBitmapUpdate = _operatorBitmapHistory[operatorId][index];
            
            /**
             * Validate that the update is valid for the given blockNumber:
             * - blockNumber should be >= the update block number
             * - the next update block number should be either 0 or strictly greater than blockNumber
             */
            require(
                blockNumber >= quorumBitmapUpdate.updateBlockNumber, 
                "RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber"
            );
            require(
                quorumBitmapUpdate.nextUpdateBlockNumber == 0 || blockNumber < quorumBitmapUpdate.nextUpdateBlockNumber,
                "RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from before blockNumber"
            );
    
            return quorumBitmapUpdate.quorumBitmap;
        }
    
        /// @notice Returns the `index`th entry in the operator with `operatorId`'s bitmap history
        function getQuorumBitmapUpdateByIndex(
            bytes32 operatorId, 
            uint256 index
        ) external view returns (QuorumBitmapUpdate memory) {
            return _operatorBitmapHistory[operatorId][index];
        }
    
        /// @notice Returns the current quorum bitmap for the given `operatorId` or 0 if the operator is not registered for any quorum
        function getCurrentQuorumBitmap(bytes32 operatorId) external view returns (uint192) {
            return _currentOperatorBitmap(operatorId);
        }
    
        /// @notice Returns the length of the quorum bitmap history for the given `operatorId`
        function getQuorumBitmapHistoryLength(bytes32 operatorId) external view returns (uint256) {
            return _operatorBitmapHistory[operatorId].length;
        }
    
        /// @notice Returns the number of registries
        function numRegistries() external view returns (uint256) {
            return registries.length;
        }
    
        /**
         * @notice Public function for the the churnApprover signature hash calculation when operators are being kicked from quorums
         * @param registeringOperatorId The id of the registering operator 
         * @param operatorKickParams The parameters needed to kick the operator from the quorums that have reached their caps
         * @param salt The salt to use for the churnApprover's signature
         * @param expiry The desired expiry time of the churnApprover's signature
         */
        function calculateOperatorChurnApprovalDigestHash(
            address registeringOperator,
            bytes32 registeringOperatorId,
            OperatorKickParam[] memory operatorKickParams,
            bytes32 salt,
            uint256 expiry
        ) public view returns (bytes32) {
            // calculate the digest hash
            return _hashTypedDataV4(keccak256(abi.encode(OPERATOR_CHURN_APPROVAL_TYPEHASH, registeringOperator, registeringOperatorId, operatorKickParams, salt, expiry)));
        }
    
        /**
         * @notice Returns the message hash that an operator must sign to register their BLS public key.
         * @param operator is the address of the operator registering their BLS public key
         */
        function pubkeyRegistrationMessageHash(address operator) public view returns (BN254.G1Point memory) {
            return BN254.hashToG1(
                _hashTypedDataV4(
                    keccak256(abi.encode(PUBKEY_REGISTRATION_TYPEHASH, operator))
                )
            );
        }
    
        /// @dev need to override function here since its defined in both these contracts
        function owner()
            public
            view
            override(OwnableUpgradeable, IRegistryCoordinator)
            returns (address)
        {
            return OwnableUpgradeable.owner();
        }
    }