OperatorTableUpdater

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/contracts/multichain/OperatorTableUpdater.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";

import "../libraries/Merkle.sol";
import "../permissions/Pausable.sol";
import "../mixins/SemVerMixin.sol";
import "../mixins/LeafCalculatorMixin.sol";
import "./OperatorTableUpdaterStorage.sol";

contract OperatorTableUpdater is
    Initializable,
    OwnableUpgradeable,
    Pausable,
    OperatorTableUpdaterStorage,
    SemVerMixin,
    LeafCalculatorMixin,
    ReentrancyGuardUpgradeable
{
    /**
     *
     *                         INITIALIZING FUNCTIONS
     *
     */
    constructor(
        IBN254CertificateVerifier _bn254CertificateVerifier,
        IECDSACertificateVerifier _ecdsaCertificateVerifier,
        IPauserRegistry _pauserRegistry,
        string memory _version
    )
        OperatorTableUpdaterStorage(_bn254CertificateVerifier, _ecdsaCertificateVerifier)
        Pausable(_pauserRegistry)
        SemVerMixin(_version)
    {
        _disableInitializers();
    }

    /**
     * @notice Initializes the OperatorTableUpdater
     * @param _owner The owner of the OperatorTableUpdater
     * @param initialPausedStatus The initial paused status of the OperatorTableUpdater
     * @param _initialGenerator The operatorSet which certifies against global roots
     * @param _globalRootConfirmationThreshold The threshold, in bps, for a global root to be signed off on and updated
     * @param generatorInfo The operatorSetInfo for the Generator
     * @dev We also update the operator table for the Generator, to begin signing off on global roots
     * @dev We set the `_latestReferenceTimestamp` to the current timestamp, so that only *new* roots can be confirmed
     */
    function initialize(
        address _owner,
        uint256 initialPausedStatus,
        OperatorSet calldata _initialGenerator,
        uint16 _globalRootConfirmationThreshold,
        BN254OperatorSetInfo calldata generatorInfo
    ) external initializer {
        _transferOwnership(_owner);
        _setPausedStatus(initialPausedStatus);

        // Set the `operatorSetConfig` for the `Generator`
        _generatorConfig.maxStalenessPeriod = GENERATOR_MAX_STALENESS_PERIOD;
        _generatorConfig.owner = address(this);

        _updateGenerator(_initialGenerator, generatorInfo);
        _setGlobalRootConfirmationThreshold(_globalRootConfirmationThreshold);

        // The generator's global table root is the `GENERATOR_GLOBAL_TABLE_ROOT`.
        // The constant is used to enable the call to `confirmGlobalTableRoot` to pass since the `BN254CertificateVerifier` expects the `Generator` to have a valid root associated with it.
        _globalTableRoots[GENERATOR_REFERENCE_TIMESTAMP] = GENERATOR_GLOBAL_TABLE_ROOT;
        _isRootValid[GENERATOR_GLOBAL_TABLE_ROOT] = true;

        // Set the `latestReferenceTimestamp` so that only *new* roots can be confirmed
        _latestReferenceTimestamp = uint32(block.timestamp);
    }

    /**
     *
     *                         ACTIONS
     *
     */

    /// @inheritdoc IOperatorTableUpdater
    function confirmGlobalTableRoot(
        BN254Certificate calldata globalTableRootCert,
        bytes32 globalTableRoot,
        uint32 referenceTimestamp,
        uint32 referenceBlockNumber
    ) external onlyWhenNotPaused(PAUSED_GLOBAL_ROOT_UPDATE) nonReentrant {
        // Table roots can only be updated for current or past timestamps and after the latest reference timestamp
        require(referenceTimestamp <= block.timestamp, GlobalTableRootInFuture());
        require(referenceTimestamp > _latestReferenceTimestamp, GlobalTableRootStale());
        require(
            globalTableRootCert.messageHash
                == getGlobalTableUpdateMessageHash(globalTableRoot, referenceTimestamp, referenceBlockNumber),
            InvalidMessageHash()
        );

        // Verify certificate by using the stake proportion thresholds
        uint16[] memory stakeProportionThresholds = new uint16[](1);
        stakeProportionThresholds[0] = globalRootConfirmationThreshold;
        bool isValid = bn254CertificateVerifier.verifyCertificateProportion(
            _generator, globalTableRootCert, stakeProportionThresholds
        );

        require(isValid, CertificateInvalid());

        // Update the global table root & reference timestamps
        _latestReferenceTimestamp = referenceTimestamp;
        _referenceBlockNumbers[referenceTimestamp] = referenceBlockNumber;
        _referenceTimestamps[referenceBlockNumber] = referenceTimestamp;
        _globalTableRoots[referenceTimestamp] = globalTableRoot;
        _isRootValid[globalTableRoot] = true;

        emit NewGlobalTableRoot(referenceTimestamp, globalTableRoot);
    }

    /// @inheritdoc IOperatorTableUpdater
    function updateOperatorTable(
        uint32 referenceTimestamp,
        bytes32 globalTableRoot,
        uint32 operatorSetIndex,
        bytes calldata proof,
        bytes calldata operatorTableBytes
    ) external onlyWhenNotPaused(PAUSED_OPERATOR_TABLE_UPDATE) nonReentrant {
        (
            OperatorSet memory operatorSet,
            CurveType curveType,
            OperatorSetConfig memory operatorSetConfig,
            bytes memory operatorTableInfo
        ) = _decodeOperatorTableBytes(operatorTableBytes);

        // Check that the `globalTableRoot` is not disabled
        require(_isRootValid[globalTableRoot], InvalidRoot());

        // Check that the `operatorSet` is not the `Generator`
        require(operatorSet.key() != _generator.key(), InvalidOperatorSet());

        // Silently return if the `referenceTimestamp` has already been updated for the `operatorSet`
        // We do this to avoid race conditions with the offchain transport of the operator table
        if (
            IBaseCertificateVerifier(getCertificateVerifier(curveType)).isReferenceTimestampSet(
                operatorSet, referenceTimestamp
            )
        ) {
            return;
        }

        // Check that the `referenceTimestamp` is greater than the latest reference timestamp
        require(
            referenceTimestamp
                > IBaseCertificateVerifier(getCertificateVerifier(curveType)).latestReferenceTimestamp(operatorSet),
            TableUpdateForPastTimestamp()
        );

        // Check that the `globalTableRoot` matches the `referenceTimestamp`
        require(_globalTableRoots[referenceTimestamp] == globalTableRoot, InvalidGlobalTableRoot());

        // Verify the operator table update
        _verifyMerkleInclusion({
            globalTableRoot: globalTableRoot,
            operatorSetIndex: operatorSetIndex,
            proof: proof,
            operatorSetLeafHash: calculateOperatorTableLeaf(operatorTableBytes)
        });

        // Update the operator table
        if (curveType == CurveType.BN254) {
            bn254CertificateVerifier.updateOperatorTable(
                operatorSet, referenceTimestamp, _getBN254OperatorInfo(operatorTableInfo), operatorSetConfig
            );
        } else if (curveType == CurveType.ECDSA) {
            ecdsaCertificateVerifier.updateOperatorTable(
                operatorSet, referenceTimestamp, _getECDSAOperatorInfo(operatorTableInfo), operatorSetConfig
            );
        } else {
            revert InvalidCurveType();
        }
    }

    /**
     *
     *                         SETTERS
     *
     */

    /// @inheritdoc IOperatorTableUpdater
    function setGlobalRootConfirmationThreshold(
        uint16 bps
    ) external onlyOwner {
        _setGlobalRootConfirmationThreshold(bps);
    }

    /// @inheritdoc IOperatorTableUpdater
    function disableRoot(
        bytes32 globalTableRoot
    ) external onlyPauser {
        // Check that the root already exists and is not disabled
        require(_isRootValid[globalTableRoot], InvalidRoot());

        // Check that the root is not the generator's global table root
        require(globalTableRoot != GENERATOR_GLOBAL_TABLE_ROOT, CannotDisableGeneratorRoot());

        _isRootValid[globalTableRoot] = false;
        emit GlobalRootDisabled(globalTableRoot);
    }

    /// @inheritdoc IOperatorTableUpdater
    function updateGenerator(
        OperatorSet calldata generator,
        BN254OperatorSetInfo calldata generatorInfo
    ) external onlyOwner {
        _updateGenerator(generator, generatorInfo);
    }

    /**
     *
     *                         GETTERS
     *
     */

    /// @inheritdoc IOperatorTableUpdater
    function getGlobalTableRootByTimestamp(
        uint32 referenceTimestamp
    ) external view returns (bytes32) {
        return _globalTableRoots[referenceTimestamp];
    }

    /// @inheritdoc IOperatorTableUpdater
    function getCurrentGlobalTableRoot() external view returns (bytes32) {
        return _globalTableRoots[_latestReferenceTimestamp];
    }

    /// @inheritdoc IOperatorTableUpdater
    function getGenerator() external view returns (OperatorSet memory) {
        return _generator;
    }

    /// @inheritdoc IOperatorTableUpdater
    function getCertificateVerifier(
        CurveType curveType
    ) public view returns (address) {
        if (curveType == CurveType.BN254) {
            return address(bn254CertificateVerifier);
        } else if (curveType == CurveType.ECDSA) {
            return address(ecdsaCertificateVerifier);
        } else {
            revert InvalidCurveType();
        }
    }

    /// @inheritdoc IOperatorTableUpdater
    function getLatestReferenceTimestamp() external view returns (uint32) {
        return _latestReferenceTimestamp;
    }

    /// @inheritdoc IOperatorTableUpdater
    function getLatestReferenceBlockNumber() external view returns (uint32) {
        return _referenceBlockNumbers[_latestReferenceTimestamp];
    }

    /// @inheritdoc IOperatorTableUpdater
    function getReferenceBlockNumberByTimestamp(
        uint32 referenceTimestamp
    ) external view returns (uint32) {
        return _referenceBlockNumbers[referenceTimestamp];
    }

    /// @inheritdoc IOperatorTableUpdater
    function getReferenceTimestampByBlockNumber(
        uint32 referenceBlockNumber
    ) external view returns (uint32) {
        return _referenceTimestamps[referenceBlockNumber];
    }

    /// @inheritdoc IOperatorTableUpdater
    function getGlobalTableUpdateMessageHash(
        bytes32 globalTableRoot,
        uint32 referenceTimestamp,
        uint32 referenceBlockNumber
    ) public pure returns (bytes32) {
        return keccak256(
            abi.encode(GLOBAL_TABLE_ROOT_CERT_TYPEHASH, globalTableRoot, referenceTimestamp, referenceBlockNumber)
        );
    }

    /// @inheritdoc IOperatorTableUpdater
    function getGeneratorReferenceTimestamp() external view returns (uint32) {
        return IBaseCertificateVerifier(address(bn254CertificateVerifier)).latestReferenceTimestamp(_generator);
    }

    /// @inheritdoc IOperatorTableUpdater
    function getGeneratorConfig() external view returns (OperatorSetConfig memory) {
        return _generatorConfig;
    }

    /// @inheritdoc IOperatorTableUpdater
    function isRootValid(
        bytes32 globalTableRoot
    ) public view returns (bool) {
        return _isRootValid[globalTableRoot];
    }

    /// @inheritdoc IOperatorTableUpdater
    function isRootValidByTimestamp(
        uint32 referenceTimestamp
    ) external view returns (bool) {
        return _isRootValid[_globalTableRoots[referenceTimestamp]];
    }

    /// @inheritdoc IOperatorTableUpdater
    function getGlobalTableUpdateSignableDigest(
        bytes32 globalTableRoot,
        uint32 referenceTimestamp,
        uint32 referenceBlockNumber
    ) public view returns (bytes32) {
        bytes32 messageHash = getGlobalTableUpdateMessageHash(globalTableRoot, referenceTimestamp, referenceBlockNumber);
        return bn254CertificateVerifier.calculateCertificateDigest(GENERATOR_REFERENCE_TIMESTAMP, messageHash);
    }

    /**
     *
     *                         INTERNAL HELPERS
     *
     */

    /**
     * @notice Verifies that the operator table update is valid by checking the `proof` against a `globalTableRoot`
     * @param globalTableRoot The global table root of the operator table update
     * @param operatorSetIndex The index of the operator set in the operator table
     * @param proof The proof of the operator table update
     * @param operatorSetLeafHash The leaf hash of the operator set
     * @dev Reverts if there does not exist a `globalTableRoot` for the given `referenceTimestamp`
     */
    function _verifyMerkleInclusion(
        bytes32 globalTableRoot,
        uint32 operatorSetIndex,
        bytes calldata proof,
        bytes32 operatorSetLeafHash
    ) internal pure {
        // Verify inclusion of the operatorSet and operatorSetLeaf in the merkle tree
        require(
            Merkle.verifyInclusionKeccak({
                proof: proof,
                root: globalTableRoot,
                leaf: operatorSetLeafHash,
                index: operatorSetIndex
            }),
            InvalidOperatorSetProof()
        );
    }

    /**
     * @notice Sets the global root confirmation threshold
     * @param bps The threshold, in bps, for a global root to be signed off on and updated
     */
    function _setGlobalRootConfirmationThreshold(
        uint16 bps
    ) internal {
        require(bps <= MAX_BPS, InvalidConfirmationThreshold());
        globalRootConfirmationThreshold = bps;
        emit GlobalRootConfirmationThresholdUpdated(bps);
    }

    /**
     * @notice Updates the `Generator` to a new operatorSet
     * @param generator The operatorSet which certifies against global roots
     * @param generatorInfo The operatorSetInfo for the generator
     * @dev We have a separate function for updating this operatorSet since it's not transported and updated
     *      in the same way as the other operatorSets
     * @dev Only callable by the owner of the contract
     * @dev Uses GENERATOR_GLOBAL_TABLE_ROOT constant to break circular dependency for certificate verification
     * @dev We ensure that there are no collisions with other reference timestamps because we expect the generator to have an initial reference timestamp of 0
     * @dev The `_latestReferenceTimestamp` is not updated since this root is ONLY used for the `Generator`
     * @dev The `_referenceBlockNumber` and `_referenceTimestamps` mappings are not updated since they are only used for introspection for official operatorSets
     */
    function _updateGenerator(OperatorSet calldata generator, BN254OperatorSetInfo calldata generatorInfo) internal {
        // Set the generator
        _generator = generator;

        // Get the latest reference timestamp for the Generator
        uint32 referenceTimestamp = bn254CertificateVerifier.latestReferenceTimestamp(generator);
        require(referenceTimestamp == 0, InvalidGenerator());

        // Update the operator table for the Generator
        bn254CertificateVerifier.updateOperatorTable(
            generator, GENERATOR_REFERENCE_TIMESTAMP, generatorInfo, _generatorConfig
        );

        emit GeneratorUpdated(generator);
    }

    /**
     * @notice Gets the operator table info from a bytes array
     * @param operatorTable The bytes containing the operator table
     * @return operatorSet The operator set
     * @return curveType The curve type
     * @return operatorSetInfo The operator set info
     * @return operatorTableInfo The operator table info. This is encoded as a bytes array, and its value is dependent on the curve type, see `_getBN254OperatorInfo` and `_getECDSAOperatorInfo`
     */
    function _decodeOperatorTableBytes(
        bytes calldata operatorTable
    )
        internal
        pure
        returns (
            OperatorSet memory operatorSet,
            CurveType curveType,
            OperatorSetConfig memory operatorSetInfo,
            bytes memory operatorTableInfo
        )
    {
        (operatorSet, curveType, operatorSetInfo, operatorTableInfo) =
            abi.decode(operatorTable, (OperatorSet, CurveType, OperatorSetConfig, bytes));
    }

    /**
     * @notice Gets the BN254 operator set info from a bytes array
     * @param BN254OperatorSetInfoBytes The bytes containing the operator set info
     * @return operatorSetInfo The BN254 operator set info
     */
    function _getBN254OperatorInfo(
        bytes memory BN254OperatorSetInfoBytes
    ) internal pure returns (BN254OperatorSetInfo memory) {
        return abi.decode(BN254OperatorSetInfoBytes, (BN254OperatorSetInfo));
    }

    /**
     * @notice Gets the ECDSA operator set info from a bytes array
     * @param ECDSAOperatorInfoBytes The bytes containing the operator table info
     * @return operatorSetInfo The ECDSA operator set info
     */
    function _getECDSAOperatorInfo(
        bytes memory ECDSAOperatorInfoBytes
    ) internal pure returns (ECDSAOperatorInfo[] memory) {
        return abi.decode(ECDSAOperatorInfoBytes, (ECDSAOperatorInfo[]));
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable-v4.9.0/contracts/proxy/utils/Initializable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.2;

import "../../utils/AddressUpgradeable.sol";

/**
 * @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;
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable-v4.9.0/contracts/access/OwnableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * 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;
}
"
    },
    "lib/openzeppelin-contracts-upgradeable-v4.9.0/contracts/security/ReentrancyGuardUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuardUpgradeable is Initializable {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    function __ReentrancyGuard_init() internal onlyInitializing {
        __ReentrancyGuard_init_unchained();
    }

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }

    /**
     * @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;
}
"
    },
    "src/contracts/libraries/Merkle.sol": {
      "content": "// SPDX-License-Identifier: MIT
// Adapted from OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/MerkleProof.sol)

pragma solidity ^0.8.0;

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, salt the leaves, or hash the leaves with a hash function other than
 * what is used for the Merkle tree's internal nodes. This is because the
 * concatenation of a sorted pair of internal nodes in the Merkle tree could
 * be reinterpreted as a leaf value.
 */
library Merkle {
    /// @notice Thrown when the provided proof was not a multiple of 32, or was empty for SHA256.
    /// @dev Error code: 0x4dc5f6a4
    error InvalidProofLength();

    /// @notice Thrown when the provided index was outside the max index for the tree.
    /// @dev Error code: 0x63df8171
    error InvalidIndex();

    /// @notice Thrown when the provided leaves' length was not a power of two.
    /// @dev Error code: 0xf6558f51
    error LeavesNotPowerOfTwo();

    /// @notice Thrown when the provided leaves' length was 0.
    /// @dev Error code: 0xbaec3d9a
    error NoLeaves();

    /// @notice Thrown when the provided leaves' length was insufficient.
    /// @dev Error code: 0xf8ef0367
    /// @dev This is used for the SHA256 Merkle tree, where the tree must have more than 1 leaf.
    error NotEnoughLeaves();

    /// @notice Thrown when the root is empty.
    /// @dev Error code: 0x53ce4ece
    /// @dev Empty roots should never be valid. We prevent them to avoid issues like the Nomad bridge attack: <https://medium.com/nomad-xyz-blog/nomad-bridge-hack-root-cause-analysis-875ad2e5aacd>
    error EmptyRoot();

    /**
     * @notice Verifies that a given leaf is included in a Merkle tree
     * @param proof The proof of inclusion for the leaf
     * @param root The root of the Merkle tree
     * @param leaf The leaf to verify
     * @param index The index of the leaf in the Merkle tree
     * @return True if the leaf is included in the Merkle tree, false otherwise
     * @dev A `proof` is valid if and only if the rebuilt hash matches the root of the tree.
     * @dev Reverts for:
     *      - InvalidProofLength: proof.length is not a multiple of 32.
     *      - InvalidIndex: index is not 0 at conclusion of computation (implying outside the max index for the tree).
     */
    function verifyInclusionKeccak(
        bytes memory proof,
        bytes32 root,
        bytes32 leaf,
        uint256 index
    ) internal pure returns (bool) {
        require(root != bytes32(0), EmptyRoot());
        return processInclusionProofKeccak(proof, leaf, index) == root;
    }

    /**
     * @notice Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`.
     * @param proof The proof of inclusion for the leaf
     * @param leaf The leaf to verify
     * @param index The index of the leaf in the Merkle tree
     * @return The rebuilt hash
     * @dev Reverts for:
     *      - InvalidProofLength: proof.length is not a multiple of 32.
     *      - InvalidIndex: index is not 0 at conclusion of computation (implying outside the max index for the tree).
     * @dev The tree is built assuming `leaf` is the 0 indexed `index`'th leaf from the bottom left of the tree.
     */
    function processInclusionProofKeccak(
        bytes memory proof,
        bytes32 leaf,
        uint256 index
    ) internal pure returns (bytes32) {
        if (proof.length == 0) {
            return leaf;
        }

        require(proof.length % 32 == 0, InvalidProofLength());

        bytes32 computedHash = leaf;
        for (uint256 i = 32; i <= proof.length; i += 32) {
            if (index % 2 == 0) {
                // if index is even, 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 index is odd, then computedHash is a right sibling
                assembly {
                    mstore(0x00, mload(add(proof, i)))
                    mstore(0x20, computedHash)
                    computedHash := keccak256(0x00, 0x40)
                    index := div(index, 2)
                }
            }
        }

        // Confirm proof was fully consumed by end of computation
        require(index == 0, InvalidIndex());

        return computedHash;
    }

    /**
     * @notice Verifies that a given leaf is included in a Merkle tree
     * @param proof The proof of inclusion for the leaf
     * @param root The root of the Merkle tree
     * @param leaf The leaf to verify
     * @param index The index of the leaf in the Merkle tree
     * @return True if the leaf is included in the Merkle tree, false otherwise
     * @dev A `proof` is valid if and only if the rebuilt hash matches the root of the tree.
     * @dev Reverts for:
     *      - InvalidProofLength: proof.length is 0 or not a multiple of 32.
     *      - InvalidIndex: index is not 0 at conclusion of computation (implying outside the max index for the tree).
     */
    function verifyInclusionSha256(
        bytes memory proof,
        bytes32 root,
        bytes32 leaf,
        uint256 index
    ) internal view returns (bool) {
        require(root != bytes32(0), EmptyRoot());
        return processInclusionProofSha256(proof, leaf, index) == root;
    }

    /**
     * @notice Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`.
     * @param proof The proof of inclusion for the leaf
     * @param leaf The leaf to verify
     * @param index The index of the leaf in the Merkle tree
     * @return The rebuilt hash
     * @dev Reverts for:
     *      - InvalidProofLength: proof.length is 0 or not a multiple of 32.
     *      - InvalidIndex: index is not 0 at conclusion of computation (implying outside the max index for the tree).
     * @dev The tree is built assuming `leaf` is the 0 indexed `index`'th leaf from the bottom left of the tree.
     */
    function processInclusionProofSha256(
        bytes memory proof,
        bytes32 leaf,
        uint256 index
    ) internal view returns (bytes32) {
        require(proof.length != 0 && proof.length % 32 == 0, InvalidProofLength());
        bytes32[1] memory computedHash = [leaf];
        for (uint256 i = 32; i <= proof.length; i += 32) {
            if (index % 2 == 0) {
                // if index is even, 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 index is odd, 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)
                }
            }
        }

        // Confirm proof was fully consumed by end of computation
        require(index == 0, InvalidIndex());

        return computedHash[0];
    }

    /**
     * @notice Returns the Merkle root of a tree created from a set of leaves using SHA-256 as its hash function
     * @param leaves the leaves of the Merkle tree
     * @return The computed Merkle root of the tree.
     * @dev Reverts for:
     *      - NotEnoughLeaves: leaves.length is less than 2.
     *      - LeavesNotPowerOfTwo: leaves.length is not a power of two.
     * @dev Unlike the Keccak version, this function does not allow a single-leaf tree.
     */
    function merkleizeSha256(
        bytes32[] memory leaves
    ) internal pure returns (bytes32) {
        require(leaves.length > 1, NotEnoughLeaves());
        require(isPowerOfTwo(leaves.length), LeavesNotPowerOfTwo());

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

        // While we haven't computed the root
        while (numNodesInLayer != 1) {
            // The next layer above has half as many nodes
            numNodesInLayer /= 2;
            // 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 first node in the layer is the root
        return layer[0];
    }

    /**
     * @notice Returns the Merkle root of a tree created from a set of leaves using Keccak as its hash function
     * @param leaves the leaves of the Merkle tree
     * @return The computed Merkle root of the tree.
     * @dev Reverts for:
     *      - NoLeaves: leaves.length is 0.
     */
    function merkleizeKeccak(
        bytes32[] memory leaves
    ) internal pure returns (bytes32) {
        require(leaves.length > 0, NoLeaves());

        uint256 numNodesInLayer;
        if (!isPowerOfTwo(leaves.length)) {
            // Pad to the next power of 2
            numNodesInLayer = 1;
            while (numNodesInLayer < leaves.length) {
                numNodesInLayer *= 2;
            }
        } else {
            numNodesInLayer = leaves.length;
        }

        // Create a layer to store the internal nodes
        bytes32[] memory layer = new bytes32[](numNodesInLayer);
        for (uint256 i = 0; i < leaves.length; i++) {
            layer[i] = leaves[i];
        }

        // While we haven't computed the root
        while (numNodesInLayer != 1) {
            // The next layer above has half as many nodes
            numNodesInLayer /= 2;
            // Overwrite the first numNodesInLayer nodes in layer with the pairwise hashes of their children
            for (uint256 i = 0; i < numNodesInLayer; i++) {
                layer[i] = keccak256(abi.encodePacked(layer[2 * i], layer[2 * i + 1]));
            }
        }
        // The first node in the layer is the root
        return layer[0];
    }

    /**
     * @notice Returns the Merkle proof for a given index in a tree created from a set of leaves using Keccak as its hash function
     * @param leaves the leaves of the Merkle tree
     * @param index the index of the leaf to get the proof for
     * @return proof The computed Merkle proof for the leaf at index.
     * @dev Reverts for:
     *      - InvalidIndex: index is outside the max index for the tree.
     */
    function getProofKeccak(bytes32[] memory leaves, uint256 index) internal pure returns (bytes memory proof) {
        require(leaves.length > 0, NoLeaves());
        // TODO: very inefficient, use ZERO_HASHES
        // pad to the next power of 2
        uint256 numNodesInLayer = 1;
        while (numNodesInLayer < leaves.length) {
            numNodesInLayer *= 2;
        }
        bytes32[] memory layer = new bytes32[](numNodesInLayer);
        for (uint256 i = 0; i < leaves.length; i++) {
            layer[i] = leaves[i];
        }

        if (index >= layer.length) revert InvalidIndex();

        // While we haven't computed the root
        while (numNodesInLayer != 1) {
            // Flip the least significant bit of index to get the sibling index
            uint256 siblingIndex = index ^ 1;
            // Add the sibling to the proof
            proof = abi.encodePacked(proof, layer[siblingIndex]);
            index /= 2;

            // The next layer above has half as many nodes
            numNodesInLayer /= 2;
            // Overwrite the first numNodesInLayer nodes in layer with the pairwise hashes of their children
            for (uint256 i = 0; i < numNodesInLayer; i++) {
                layer[i] = keccak256(abi.encodePacked(layer[2 * i], layer[2 * i + 1]));
            }
        }
    }

    /**
     * @notice Returns the Merkle proof for a given index in a tree created from a set of leaves using SHA-256 as its hash function
     * @param leaves the leaves of the Merkle tree
     * @param index the index of the leaf to get the proof for
     * @return proof The computed Merkle proof for the leaf at index.
     * @dev Reverts for:
     *      - NotEnoughLeaves: leaves.length is less than 2.
     * @dev Unlike the Keccak version, this function does not allow a single-leaf proof.
     */
    function getProofSha256(bytes32[] memory leaves, uint256 index) internal pure returns (bytes memory proof) {
        require(leaves.length > 1, NotEnoughLeaves());
        // TODO: very inefficient, use ZERO_HASHES
        // pad to the next power of 2
        uint256 numNodesInLayer = 1;
        while (numNodesInLayer < leaves.length) {
            numNodesInLayer *= 2;
        }
        bytes32[] memory layer = new bytes32[](numNodesInLayer);
        for (uint256 i = 0; i < leaves.length; i++) {
            layer[i] = leaves[i];
        }

        if (index >= layer.length) revert InvalidIndex();

        // While we haven't computed the root
        while (numNodesInLayer != 1) {
            // Flip the least significant bit of index to get the sibling index
            uint256 siblingIndex = index ^ 1;
            // Add the sibling to the proof
            proof = abi.encodePacked(proof, layer[siblingIndex]);
            index /= 2;

            // The next layer above has half as many nodes
            numNodesInLayer /= 2;
            // 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]));
            }
        }
    }

    /**
     * @notice Returns whether the input is a power of two
     * @param value the value to check
     * @return True if the input is a power of two, false otherwise
     */
    function isPowerOfTwo(
        uint256 value
    ) internal pure returns (bool) {
        return value != 0 && (value & (value - 1)) == 0;
    }
}
"
    },
    "src/contracts/permissions/Pausable.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import "../interfaces/IPausable.sol";

/**
 * @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
 */
abstract contract Pausable is IPausable {
    /// Constants

    uint256 internal constant _UNPAUSE_ALL = 0;

    uint256 internal constant _PAUSE_ALL = type(uint256).max;

    /// @notice Address of the `PauserRegistry` contract that this contract defers to for determining access control (for pausing).
    IPauserRegistry public immutable pauserRegistry;

    /// Storage

    /// @dev Do not remove, deprecated storage.
    IPauserRegistry private __deprecated_pauserRegistry;

    /// @dev Returns a bitmap representing the paused status of the contract.
    uint256 private _paused;

    /// Modifiers

    /// @dev Thrown if the caller is not a valid pauser according to the pauser registry.
    modifier onlyPauser() {
        _checkOnlyPauser();
        _;
    }

    /// @dev Thrown if the caller is not a valid unpauser according to the pauser registry.
    modifier onlyUnpauser() {
        _checkOnlyUnpauser();
        _;
    }

    /// @dev Thrown if the contract is paused, i.e. if any of the bits in `_paused` is flipped to 1.
    modifier whenNotPaused() {
        _checkOnlyWhenNotPaused();
        _;
    }

    /// @dev Thrown if the `indexed`th bit of `_paused` is 1, i.e. if the `index`th pause switch is flipped.
    modifier onlyWhenNotPaused(
        uint8 index
    ) {
        _checkOnlyWhenNotPaused(index);
        _;
    }

    function _checkOnlyPauser() internal view {
        require(pauserRegistry.isPauser(msg.sender), OnlyPauser());
    }

    function _checkOnlyUnpauser() internal view {
        require(msg.sender == pauserRegistry.unpauser(), OnlyUnpauser());
    }

    function _checkOnlyWhenNotPaused() internal view {
        require(_paused == 0, CurrentlyPaused());
    }

    function _checkOnlyWhenNotPaused(
        uint8 index
    ) internal view {
        require(!paused(index), CurrentlyPaused());
    }

    /// Construction

    constructor(
        IPauserRegistry _pauserRegistry
    ) {
        require(address(_pauserRegistry) != address(0), InputAddressZero());
        pauserRegistry = _pauserRegistry;
    }

    /// @inheritdoc IPausable
    function pause(
        uint256 newPausedStatus
    ) external onlyPauser {
        uint256 currentPausedStatus = _paused;
        // verify that the `newPausedStatus` does not *unflip* any bits (i.e. doesn't unpause anything, all 1 bits remain)
        require((currentPausedStatus & newPausedStatus) == currentPausedStatus, InvalidNewPausedStatus());
        _setPausedStatus(newPausedStatus);
    }

    /// @inheritdoc IPausable
    function pauseAll() external onlyPauser {
        _setPausedStatus(_PAUSE_ALL);
    }

    /// @inheritdoc IPausable
    function unpause(
        uint256 newPausedStatus
    ) external onlyUnpauser {
        uint256 currentPausedStatus = _paused;
        // verify that the `newPausedStatus` does not *flip* any bits (i.e. doesn't pause anything, all 0 bits remain)
        require(((~currentPausedStatus) & (~newPausedStatus)) == (~currentPausedStatus), InvalidNewPausedStatus());
        _paused = newPausedStatus;
        emit Unpaused(msg.sender, newPausedStatus);
    }

    /// @inheritdoc IPausable
    function paused() public view virtual returns (uint256) {
        return _paused;
    }

    /// @inheritdoc IPausable
    function paused(
        uint8 index
    ) public view virtual returns (bool) {
        uint256 mask = 1 << index;
        return ((_paused & mask) == mask);
    }

    /// @dev Internal helper for setting the paused status, and emitting the corresponding event.
    function _setPausedStatus(
        uint256 pausedStatus
    ) internal {
        _paused = pausedStatus;
        emit Paused(msg.sender, pausedStatus);
    }

    /**
     * @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;
}
"
    },
    "src/contracts/mixins/SemVerMixin.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import "../interfaces/ISemVerMixin.sol";
import "@openzeppelin-upgrades/contracts/utils/ShortStringsUpgradeable.sol";

/// @title SemVerMixin
/// @notice A mixin contract that provides semantic versioning functionality.
/// @dev Follows SemVer 2.0.0 specification (https://semver.org/).
abstract contract SemVerMixin is ISemVerMixin {
    using ShortStringsUpgradeable for *;

    /// @notice The semantic version string for this contract, stored as a ShortString for gas efficiency.
    /// @dev Follows SemVer 2.0.0 specification (https://semver.org/).
    ShortString internal immutable _VERSION;

    /// @notice Initializes the contract with a semantic version string.
    /// @param _version The SemVer-formatted version string (e.g., "1.2.3")
    /// @dev Version should follow SemVer 2.0.0 format: MAJOR.MINOR.PATCH
    constructor(
        string memory _version
    ) {
        _VERSION = _version.toShortString();
    }

    /// @inheritdoc ISemVerMixin
    function version() public view virtual returns (string memory) {
        return _VERSION.toString();
    }

    /// @notice Returns the major version of the contract.
    /// @dev Supports single digit major versions (e.g., "1" for version "1.2.3")
    /// @return The major version string (e.g., "1" for version "1.2.3")
    function _majorVersion() internal view returns (string memory) {
        bytes memory v = bytes(_VERSION.toString());
        return string(abi.encodePacked(v[0]));
    }
}
"
    },
    "src/contracts/mixins/LeafCalculatorMixin.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import {IOperatorTableCalculatorTypes} from "../interfaces/IOperatorTableCalculator.sol";

/**
 * @title LeafCalculatorMixin
 * @notice Reusable mixin for calculating operator info and operator table leaf hashes
 * @dev Provides standardized leaf calculation functions for use across multiple contracts and repositories.
 *      This mixin centralizes the leaf hashing logic to ensure consistency across the EigenLayer ecosystem
 *      and maintains proper cryptographic security through salt-based domain separation.
 */
abstract contract LeafCalculatorMixin {
    /// @dev Salt for operator info leaf hash calculation
    /// @dev The salt is used to prevent against second preimage attacks: attacks where an
    /// attacker can create a partial proof using an internal node rather than a leaf to
    /// validate a proof. The salt ensures that leaves cannot be concatenated together to
    /// form a valid proof, as well as reducing the likelihood of an internal node matching
    /// the salt prefix.
    /// @dev Value derived from keccak256("OPERATOR_INFO_LEAF_SALT") = 0x75...
    /// This ensures collision resistance and semantic meaning.
    uint8 public constant OPERATOR_INFO_LEAF_SALT = 0x75;

    /// @dev Salt for operator table leaf hash calculation
    /// @dev The salt is used to prevent against second preimage attacks: attacks where an
    /// attacker can create a partial proof using an internal node rather than a leaf to
    /// validate a proof. The salt ensures that leaves cannot be concatenated together to
    /// form a valid proof, as well as reducing the likelihood of an internal node matching
    /// the salt prefix.
    /// @dev Value derived from keccak256("OPERATOR_TABLE_LEAF_SALT") = 0x8e...
    /// This ensures collision resistance and semantic meaning.
    uint8 public constant OPERATOR_TABLE_LEAF_SALT = 0x8e;

    /**
     * @notice Calculate the leaf hash for an operator info
     * @param operatorInfo The BN254 operator info struct containing the operator's public key and stake weights
     * @return The leaf hash (keccak256 of salt and encoded operator info)
     * @dev The salt is used to prevent against second preimage attacks: attacks where an
     * attacker can create a partial proof using an internal node rather than a leaf to
     * validate a proof. The salt ensures that leaves cannot be concatenated together to
     * form a valid proof, as well as reducing the likelihood of an internal node matching
     * the salt prefix.
     *
     * This is a standard "domain separation" technique in Merkle tree implementations
     * to ensure leaf nodes and internal nodes can never be confused with each other.
     * See Section 2.1 of <https://www.rfc-editor.org/rfc/rfc9162#name-merkle-trees> for more.
     *
     * Uses abi.encodePacked for the salt and abi.encode for the struct to handle complex types
     * (structs with dynamic arrays) while maintaining gas efficiency where possible.
     */
    function calculateOperatorInfoLeaf(
        IOperatorTableCalculatorTypes.BN254OperatorInfo memory operatorInfo
    ) public pure returns (bytes32) {
        return keccak256(abi.encodePacked(OPERATOR_INFO_LEAF_SALT, abi.encode(operatorInfo)));
    }

    /**
     * @notice Calculate the leaf hash for an operator table
     * @param operatorTableBytes The encoded operator table as bytes containing operator set data
     * @return The leaf hash (keccak256 of salt and operator table bytes)
     * @dev The salt is used to prevent against second preimage attacks: attacks where an
     * attacker can create a partial proof using an internal node rather than a leaf to
     * validate a proof. The salt ensures that leaves cannot be concatenated together to
     * form a valid proof, as well as reducing the likelihood of an internal node matching
     * the salt prefix.
     *
     * This is a standard "domain separation" technique in Merkle tree implementations
     * to ensure leaf nodes and internal nodes can never be confused with each other.
     * See Section 2.1 of <https://www.rfc-editor.org/rfc/rfc9162#name-merkle-trees> for more.
     *
     * Uses abi.encodePacked for both salt and bytes for optimal gas efficiency since both
     * are simple byte arrays without complex nested structures.
     */
    function calculateOperatorTableLeaf(
        bytes calldata operatorTableBytes
    ) public pure returns (bytes32) {
        return keccak256(abi.encodePacked(OPERATOR_TABLE_LEAF_SALT, operatorTableBytes));
    }
}
"
    },
    "src/contracts/multichain/OperatorTableUpdaterStorage.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import "../interfaces/IOperatorTableUpdater.sol";
import "../interfaces/IBN254CertificateVerifier.sol";
import "../interfaces/IECDSACertificateVerifier.sol";

abstract contract OperatorTableUpdaterStorage is IOperatorTableUpdater {
    // Constants

    /// @notice Index for flag that pauses calling `updateGlobalTableRoot`
    uint8 internal constant PAUSED_GLOBAL_ROOT_UPDATE = 0;

    /// @notice Index for flag that pauses calling `updateOperatorTable`
    uint8 internal constant PAUSED_OPERATOR_TABLE_UPDATE = 1;

    // OPERATOR_TABLE_LEAF_SALT is now inherited from LeafCalculatorMixin

    bytes32 public constant GLOBAL_TABLE_ROOT_CERT_TYPEHASH =
        keccak256("GlobalTableRootCert(bytes32 globalTableRoot,uint32 referenceTimestamp,uint32 referenceBlockNumber)");

    /// @notice The maximum BPS value
    uint16 public constant MAX_BPS = 10_000;

    /// @notice Dummy initial global table root to break circular dependency for certificate verification
    bytes32 public constant GENERATOR_GLOBAL_TABLE_ROOT = keccak256("GENERATOR_GLOBAL_TABLE_ROOT");

    /// @notice The reference timestamp for the generator
    uint32 public constant GENERATOR_REFERENCE_TIMESTAMP = 1;

    /// @notice The `maxStalenessPeriods` for the generator
    /// @dev This is set to 0 to allow certificates to always be valid, regardless of the `referenceTimestamp`
    uint32 public constant GENERATOR_MAX_STALENESS_PERIOD = 0;

    // Immutable Storage

    /// @notice The BN254 certificate verifier
    IBN254CertificateVerifier public immutable bn254CertificateVerifier;

    /// @notice The ECDSA certificate verifier
    IECDSACertificateVerifier public immutable ecdsaCertificateVerifier;

    // Mutable Storage

    /// @notice The threshold, in bps, for a global root to be signed off on and updated
    uint16 public globalRootConfirmationThreshold;

    /// @notice The latest reference timestamp
    uint32 internal _latestReferenceTimestamp;

    /// @notice The operatorSet which certifies against global roots
    OperatorSet internal _generator;

    /// @notice The global table roots by timestamp
    mapping(uint32 timestamp => bytes32 globalTableRoot) internal _globalTableRoots;

    /// @notice Mapping from latest reference timestamp to reference block number
    mapping(uint32 referenceTimestamp => uint32 referenceBlockNumber) internal _referenceBlockNumbers;

    /// @notice Mapping from reference block number to reference timestamp
    mapping(uint32 referenceBlockNumber => uint32 referenceTimestamp) internal _referenceTimestamps;

    /// @notice Mapping from global table root to validity status
    mapping(bytes32 globalTableRoot => bool valid) internal _isRootValid;

    /// @notice The operatorSetConfig for the generator
    /// @dev The `maxStalenessPeriod` is set to `GENERATOR_MAX_STALENESS_PERIOD` to allow certificates to always be valid, regardless of the `referenceTimestamp`
    /// @dev The `owner` is set to the address of the `OperatorTableUpdater`
    OperatorSetConfig internal _generatorConfig;

    // Constructor
    constructor(
        IBN254CertificateVerifier _bn254CertificateVerifier,
        IECDSACertificateVerifier _ecdsaCertificateVerifier
    ) {
        bn254CertificateVerifier = _bn254CertificateVerifier;
        ecdsaCertificateVerifier = _ecdsaCertificateVerifier;
    }

    /**
     * @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[43] private __gap;
}
"
    },
    "lib/openzeppelin-contracts-upgradeable-v4.9.0/contracts/utils/AddressUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)

pragma solidity ^0.8.1;

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

Tags:
Multisig, Voting, Upgradeable, Multi-Signature, Factory|addr:0x64ad668f6ca7104e452041555a857cce6267f22b|verified:true|block:23435295|tx:0x2e18cb58f4e4ff8fae768e0c9582f1b8c24c3a47a8379d44c917fb6b1aa107aa|first_check:1758787516

Submitted on: 2025-09-25 10:05:16

Comments

Log in to comment.

No comments yet.