Avalanche Fuji Testnet

Contract

0xbf9e863cF9F00f48D3Ed9D009515114365502569
Source Code Source Code

Overview

AVAX Balance

Avalanche C-Chain LogoAvalanche C-Chain LogoAvalanche C-Chain Logo0 AVAX

More Info

Multichain Info

N/A
Transaction Hash
Method
Block
From
To
Amount
Complete Validat...414032172025-06-06 12:44:32226 days ago1749213872IN
0xbf9e863c...365502569
0 AVAX00
Remove Node414028722025-06-06 12:32:58226 days ago1749213178IN
0xbf9e863c...365502569
0 AVAX00
Manual Process N...414028652025-06-06 12:32:47226 days ago1749213167IN
0xbf9e863c...365502569
0 AVAX00
Complete Validat...413715312025-06-05 18:03:27226 days ago1749146607IN
0xbf9e863c...365502569
0 AVAX00
Manual Process N...413709872025-06-05 17:48:04226 days ago1749145684IN
0xbf9e863c...365502569
0 AVAX00
Add Node413665472025-06-05 15:54:47226 days ago1749138887IN
0xbf9e863c...365502569
0 AVAX00
Complete Validat...413662232025-06-05 15:47:18226 days ago1749138438IN
0xbf9e863c...365502569
0 AVAX00
Remove Node413661552025-06-05 15:45:41226 days ago1749138341IN
0xbf9e863c...365502569
0 AVAX00
Complete Validat...413654572025-06-05 15:29:26226 days ago1749137366IN
0xbf9e863c...365502569
0 AVAX00
Add Node413648202025-06-05 15:13:08226 days ago1749136388IN
0xbf9e863c...365502569
0 AVAX00
Complete Validat...413641152025-06-05 14:52:04226 days ago1749135124IN
0xbf9e863c...365502569
0 AVAX00
Manual Process N...413623192025-06-05 13:57:08227 days ago1749131828IN
0xbf9e863c...365502569
0 AVAX0.000177580.5
Remove Node413543932025-06-05 9:37:59227 days ago1749116279IN
0xbf9e863c...365502569
0 AVAX00
Manual Process N...413543872025-06-05 9:37:46227 days ago1749116266IN
0xbf9e863c...365502569
0 AVAX00
Complete Validat...413513922025-06-05 7:59:57227 days ago1749110397IN
0xbf9e863c...365502569
0 AVAX00
Add Node413511642025-06-05 7:51:58227 days ago1749109918IN
0xbf9e863c...365502569
0 AVAX00
Manual Process N...413511562025-06-05 7:51:41227 days ago1749109901IN
0xbf9e863c...365502569
0 AVAX00
Manual Process N...413234562025-06-04 16:22:17227 days ago1749054137IN
0xbf9e863c...365502569
0 AVAX00
Calc And Cache S...413233352025-06-04 16:18:29227 days ago1749053909IN
0xbf9e863c...365502569
0 AVAX00
Calc And Cache S...413233282025-06-04 16:18:16227 days ago1749053896IN
0xbf9e863c...365502569
0 AVAX00
Calc And Cache S...413233192025-06-04 16:17:56227 days ago1749053876IN
0xbf9e863c...365502569
0 AVAX00
Calc And Cache N...413233032025-06-04 16:17:25227 days ago1749053845IN
0xbf9e863c...365502569
0 AVAX00
Manual Process N...413232552025-06-04 16:15:54227 days ago1749053754IN
0xbf9e863c...365502569
0 AVAX00
Manual Process N...413231022025-06-04 16:10:51227 days ago1749053451IN
0xbf9e863c...365502569
0 AVAX00
Manual Process N...413216802025-06-04 15:24:28227 days ago1749050668IN
0xbf9e863c...365502569
0 AVAX00

Latest 25 internal transactions (View All)

Parent Transaction Hash Block From To Amount
435968392025-07-18 13:20:04184 days ago1752844804
0xbf9e863c...365502569
0 AVAX
435968392025-07-18 13:20:04184 days ago1752844804
0xbf9e863c...365502569
0 AVAX
435968392025-07-18 13:20:04184 days ago1752844804
0xbf9e863c...365502569
0 AVAX
435968392025-07-18 13:20:04184 days ago1752844804
0xbf9e863c...365502569
0 AVAX
414032172025-06-06 12:44:32226 days ago1749213872
0xbf9e863c...365502569
0 AVAX
414028722025-06-06 12:32:58226 days ago1749213178
0xbf9e863c...365502569
0 AVAX
414028722025-06-06 12:32:58226 days ago1749213178
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
414028652025-06-06 12:32:47226 days ago1749213167
0xbf9e863c...365502569
0 AVAX
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
AvalancheL1Middleware

Compiler Version
v0.8.25+commit.b61c2a91

Optimization Enabled:
Yes with 200 runs

Other Settings:
cancun EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 50 : AvalancheL1Middleware.sol
// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity 0.8.25;

import {Time} from "@openzeppelin/contracts/utils/types/Time.sol";
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import {
    Validator,
    ValidatorStatus,
    ValidatorRegistrationInput,
    PChainOwner
} from "@avalabs/teleporter/validator-manager/interfaces/IValidatorManager.sol";
import {BalancerValidatorManager} from
    "@suzaku/contracts-library/contracts/ValidatorManager/BalancerValidatorManager.sol";

import {IOperatorRegistry} from "../../interfaces/IOperatorRegistry.sol";
import {IVaultTokenized} from "../../interfaces/vault/IVaultTokenized.sol";
import {IAvalancheL1Middleware} from "../../interfaces/middleware/IAvalancheL1Middleware.sol";
import {IOptInService} from "../../interfaces/service/IOptInService.sol";

import {AssetClassRegistry} from "./AssetClassRegistry.sol";
import {MiddlewareVaultManager} from "./MiddlewareVaultManager.sol";
import {MapWithTimeData} from "./libraries/MapWithTimeData.sol";
import {StakeConversion} from "./libraries/StakeConversion.sol";
import {BaseDelegator} from "../../contracts/delegator/BaseDelegator.sol";

struct AvalancheL1MiddlewareSettings {
    address l1ValidatorManager;
    address operatorRegistry;
    address vaultRegistry;
    address operatorL1Optin;
    uint48 epochDuration;
    uint48 slashingWindow;
    uint48 stakeUpdateWindow;
}

/**
 * @title AvalancheL1Middleware
 * @notice Manages operator registration, vault registration, stake accounting, and slashing for Avalanche L1
 */
contract AvalancheL1Middleware is IAvalancheL1Middleware, AssetClassRegistry {
    using EnumerableMap for EnumerableMap.AddressToUintMap;
    using EnumerableSet for EnumerableSet.UintSet;
    using MapWithTimeData for EnumerableMap.AddressToUintMap;
    using EnumerableMap for EnumerableMap.Bytes32ToUintMap;
    using EnumerableSet for EnumerableSet.Bytes32Set;

    address public immutable L1_VALIDATOR_MANAGER;
    address public immutable OPERATOR_REGISTRY;
    address public immutable OPERATOR_L1_OPTIN;
    address public immutable PRIMARY_ASSET;
    uint48 public immutable EPOCH_DURATION;
    uint48 public immutable SLASHING_WINDOW;
    uint48 public immutable START_TIME;
    uint48 public immutable UPDATE_WINDOW;
    uint256 public immutable WEIGHT_SCALE_FACTOR;
    uint48 public lastGlobalNodeStakeUpdateEpoch;

    uint96 public constant PRIMARY_ASSET_CLASS = 1;
    uint48 public constant MAX_AUTO_EPOCH_UPDATES = 1;

    MiddlewareVaultManager private vaultManager;
    EnumerableMap.AddressToUintMap private operators;
    EnumerableSet.UintSet private secondaryAssetClasses;

    BalancerValidatorManager public balancerValidatorManager;

    mapping(address => mapping(uint48 => bool)) public rebalancedThisEpoch;
    mapping(uint48 => mapping(uint96 => uint256)) public totalStakeCache;
    mapping(address => bytes32[]) public operatorNodesArray;
    mapping(uint48 => mapping(uint96 => mapping(address => uint256))) public operatorStakeCache;
    mapping(uint48 => mapping(bytes32 => uint256)) public nodeStakeCache;
    mapping(bytes32 => bool) public nodePendingUpdate;
    mapping(bytes32 => bool) public nodePendingRemoval;
    mapping(address => uint256) public operatorLockedStake;
    mapping(uint48 => mapping(uint96 => bool)) public totalStakeCached;
    // operatorNodesArray[operator] is used for iteration during certain
    // rebalancing or node-update operations, and has nodes removed once
    // they are effectively retired. This means a node can remain in
    // operatorNodes while it is removed from operatorNodesArray.
    // operatorNodes[operator] is intended as a permanent record of all nodes
    // ever registered by the operator, used for historical/epoch-based queries.
    // We do *not* remove nodes from this set when they are "retired" so
    // getActiveNodesForEpoch(...) can still detect them for past epochs.
    mapping(address => EnumerableSet.Bytes32Set) private operatorNodes;

    /**
     * @notice Initializes contract settings
     * @param settings General contract parameters
     * @param owner Owner address
     * @param primaryAsset The primary asset address
     * @param primaryAssetMaxStake Max stake for the primary asset class
     * @param primaryAssetMinStake Min stake for the primary asset class
     */
    constructor(
        AvalancheL1MiddlewareSettings memory settings,
        address owner,
        address primaryAsset,
        uint256 primaryAssetMaxStake,
        uint256 primaryAssetMinStake,
        uint256 primaryAssetWeightScaleFactor
    ) AssetClassRegistry(owner) {
        if (settings.l1ValidatorManager == address(0)) {
            revert AvalancheL1Middleware__ZeroAddress("l1ValidatorManager");
        }
        if (settings.operatorRegistry == address(0)) {
            revert AvalancheL1Middleware__ZeroAddress("operatorRegistry");
        }
        if (settings.vaultRegistry == address(0)) {
            revert AvalancheL1Middleware__ZeroAddress("vaultRegistry");
        }
        if (settings.operatorL1Optin == address(0)) {
            revert AvalancheL1Middleware__ZeroAddress("operatorL1Optin");
        }
        if (owner == address(0)) {
            revert AvalancheL1Middleware__ZeroAddress("owner");
        }
        if (primaryAsset == address(0)) {
            revert AvalancheL1Middleware__ZeroAddress("primaryAsset");
        }
        if (settings.slashingWindow < settings.epochDuration) {
            revert AvalancheL1Middleware__SlashingWindowTooShort(settings.slashingWindow, settings.epochDuration);
        }
        if (primaryAssetWeightScaleFactor == 0) {
            revert AvalancheL1Middleware__InvalidScaleFactor();
        }

        START_TIME = Time.timestamp();
        EPOCH_DURATION = settings.epochDuration;
        L1_VALIDATOR_MANAGER = settings.l1ValidatorManager;
        OPERATOR_REGISTRY = settings.operatorRegistry;
        OPERATOR_L1_OPTIN = settings.operatorL1Optin;
        SLASHING_WINDOW = settings.slashingWindow;
        PRIMARY_ASSET = primaryAsset;
        UPDATE_WINDOW = settings.stakeUpdateWindow;
        WEIGHT_SCALE_FACTOR = primaryAssetWeightScaleFactor;

        balancerValidatorManager = BalancerValidatorManager(settings.l1ValidatorManager);
        _addAssetClass(PRIMARY_ASSET_CLASS, primaryAssetMinStake, primaryAssetMaxStake, PRIMARY_ASSET);
    }

    /**
     * @notice Updates stake cache before function execution
     * @param epoch The epoch to update
     * @param assetClassId The asset class ID
     */
    modifier updateStakeCache(uint48 epoch, uint96 assetClassId) {
        if (!totalStakeCached[epoch][assetClassId]) {
            calcAndCacheStakes(epoch, assetClassId);
        }
        _;
    }

    /**
     * @notice Window where a node update can be done manually, before the force update can be applied
     */
    modifier onlyDuringFinalWindowOfEpoch() {
        uint48 currentEpoch = getCurrentEpoch();
        uint48 epochStartTs = getEpochStartTs(currentEpoch);
        uint48 timeNow = Time.timestamp();
        uint48 epochUpdatePeriod = epochStartTs + UPDATE_WINDOW;

        if (timeNow < epochUpdatePeriod || timeNow > epochStartTs + EPOCH_DURATION) {
            revert AvalancheL1Middleware__NotEpochUpdatePeriod(timeNow, epochUpdatePeriod);
        }
        _;
    }

    modifier onlyRegisteredOperatorNode(address operator, bytes32 nodeId) {
        if (!operators.contains(operator)) {
            revert AvalancheL1Middleware__OperatorNotRegistered(operator);
        }
        if (!operatorNodes[operator].contains(nodeId)) {
            revert AvalancheL1Middleware__NodeNotFound(nodeId);
        }
        _;
    }

    modifier updateGlobalNodeStakeOncePerEpoch() {
        uint48 current = getCurrentEpoch();
        if (current > lastGlobalNodeStakeUpdateEpoch) {
            calcAndCacheNodeStakeForAllOperators();
            lastGlobalNodeStakeUpdateEpoch = current;
        }
        _;
    }

    function setVaultManager(
        address vaultManager_
    ) external onlyOwner {
        if (vaultManager_ == address(0)) {
            revert AvalancheL1Middleware__ZeroAddress("vaultManager");
        }
        emit VaultManagerUpdated(address(vaultManager), vaultManager_);
        vaultManager = MiddlewareVaultManager(vaultManager_);
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function activateSecondaryAssetClass(
        uint256 assetClassId
    ) external onlyOwner updateGlobalNodeStakeOncePerEpoch {
        if (!assetClassIds.contains(assetClassId)) {
            revert AssetClassRegistry__AssetClassNotFound();
        }
        if (assetClassId == PRIMARY_ASSET_CLASS) {
            revert AssetClassRegistry__AssetClassAlreadyExists();
        }
        bool added = secondaryAssetClasses.add(assetClassId);
        if (!added) {
            revert AssetClassRegistry__AssetClassAlreadyExists();
        }
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function deactivateSecondaryAssetClass(
        uint256 assetClassId
    ) external onlyOwner updateGlobalNodeStakeOncePerEpoch {
        if (_isUsedAssetClass(assetClassId)) {
            revert AvalancheL1Middleware__AssetStillInUse(assetClassId);
        }
        bool removed = secondaryAssetClasses.remove(assetClassId);
        if (!removed) {
            revert AssetClassRegistry__AssetClassNotFound();
        }
    }

    /**
     * @notice Removes an asset from an asset class, except primary asset
     * @param assetClassId The ID of the asset class
     * @param asset The address of the asset to remove
     */
    function removeAssetFromClass(
        uint256 assetClassId,
        address asset
    ) public override onlyOwner updateGlobalNodeStakeOncePerEpoch {
        if (assetClassId == 1 && asset == PRIMARY_ASSET) {
            revert AssetClassRegistry__AssetIsPrimaryAssetClass(assetClassId);
        }

        if (_isUsedAsset(assetClassId, asset)) {
            revert AvalancheL1Middleware__AssetStillInUse(assetClassId);
        }

        super.removeAssetFromClass(assetClassId, asset);
    }

    /**
     * @notice Removes an asset class
     * @param assetClassId The asset class ID
     */
    function removeAssetClass(
        uint256 assetClassId
    ) public override updateGlobalNodeStakeOncePerEpoch {
        if (secondaryAssetClasses.contains(assetClassId)) {
            revert AvalancheL1Middleware__ActiveSecondaryAssetCLass(assetClassId);
        }

        super.removeAssetClass(assetClassId);
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function registerOperator(
        address operator
    ) external onlyOwner updateGlobalNodeStakeOncePerEpoch {
        if (operators.contains(operator)) {
            revert AvalancheL1Middleware__OperatorAlreadyRegistered(operator);
        }
        if (!IOperatorRegistry(OPERATOR_REGISTRY).isRegistered(operator)) {
            revert AvalancheL1Middleware__OperatorNotRegistered(operator);
        }
        if (!IOptInService(OPERATOR_L1_OPTIN).isOptedIn(operator, L1_VALIDATOR_MANAGER)) {
            revert AvalancheL1Middleware__OperatorNotOptedIn(operator, L1_VALIDATOR_MANAGER);
        }

        operators.add(operator);
        operators.enable(operator);
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function disableOperator(
        address operator
    ) external onlyOwner updateGlobalNodeStakeOncePerEpoch {
        operators.disable(operator);
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function enableOperator(
        address operator
    ) external onlyOwner updateGlobalNodeStakeOncePerEpoch {
        operators.enable(operator);
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function removeOperator(
        address operator
    ) external onlyOwner updateGlobalNodeStakeOncePerEpoch {
        (, uint48 disabledTime) = operators.getTimes(operator);
        if (disabledTime == 0 || disabledTime + SLASHING_WINDOW > Time.timestamp()) {
            revert AvalancheL1Middleware__OperatorGracePeriodNotPassed(disabledTime, SLASHING_WINDOW);
        }
        operators.remove(operator);
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function addNode(
        bytes32 nodeId,
        bytes calldata blsKey,
        uint64 registrationExpiry,
        PChainOwner calldata remainingBalanceOwner,
        PChainOwner calldata disableOwner,
        uint256 stakeAmount // optional
    ) external updateStakeCache(getCurrentEpoch(), PRIMARY_ASSET_CLASS) updateGlobalNodeStakeOncePerEpoch {
        address operator = msg.sender;
        if (!operators.contains(operator)) {
            revert AvalancheL1Middleware__OperatorNotRegistered(operator);
        }
        if (!_requireMinSecondaryAssetClasses(1, operator)) {
            revert AvalancheL1Middleware__NotEnoughFreeStakeSecondaryAssetClasses();
        }

        bytes32 valId = balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
        uint256 available = _getOperatorAvailableStake(operator);
        if (nodePendingRemoval[valId]) revert AvalancheL1Middleware__NodePendingRemoval(nodeId);
        if (nodePendingUpdate[valId]) revert AvalancheL1Middleware__NodePendingUpdate(nodeId);

        uint256 minStake = assetClasses[PRIMARY_ASSET_CLASS].minValidatorStake;
        uint256 maxStake = assetClasses[PRIMARY_ASSET_CLASS].maxValidatorStake;
        uint256 newStake = (stakeAmount != 0) ? stakeAmount : available;

        newStake = (newStake > maxStake) ? maxStake : newStake;

        if (newStake < minStake || newStake > available) {
            revert AvalancheL1Middleware__NotEnoughFreeStake(newStake);
        }

        ValidatorRegistrationInput memory input = ValidatorRegistrationInput({
            nodeID: abi.encodePacked(uint160(uint256(nodeId))),
            blsPublicKey: blsKey,
            registrationExpiry: registrationExpiry,
            remainingBalanceOwner: remainingBalanceOwner,
            disableOwner: disableOwner
        });

        // Track node in our time-based map and dynamic array.
        operatorNodes[operator].add(nodeId);
        operatorNodesArray[operator].push(nodeId);
        uint48 epoch = getCurrentEpoch();

        bytes32 validationID = balancerValidatorManager.initializeValidatorRegistration(
            input, StakeConversion.stakeToWeight(newStake, WEIGHT_SCALE_FACTOR)
        );
        nodeStakeCache[epoch][validationID] = newStake;
        nodeStakeCache[epoch + 1][validationID] = newStake;
        nodePendingUpdate[validationID] = true;

        emit NodeAdded(operator, nodeId, newStake, validationID);
    }

    function removeNode(
        bytes32 nodeId
    ) external updateGlobalNodeStakeOncePerEpoch onlyRegisteredOperatorNode(msg.sender, nodeId) {
        _removeNode(msg.sender, nodeId);
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function forceUpdateNodes(
        address operator,
        uint256 limitStake
    )
        external
        updateStakeCache(getCurrentEpoch(), PRIMARY_ASSET_CLASS)
        onlyDuringFinalWindowOfEpoch
        updateGlobalNodeStakeOncePerEpoch
    {
        uint48 currentEpoch = getCurrentEpoch();
        if (rebalancedThisEpoch[operator][currentEpoch]) {
            revert AvalancheL1Middleware__AlreadyRebalanced(operator, currentEpoch);
        }
        rebalancedThisEpoch[operator][currentEpoch] = true;

        if (!operators.contains(operator)) {
            revert AvalancheL1Middleware__OperatorNotRegistered(operator);
        }

        // Calculate the new total stake for the operator and compare it to the registered stake
        uint256 newTotalStake = _getOperatorAvailableStake(operator);
        uint256 registeredStake = getOperatorUsedStakeCached(operator);
        uint256 leftoverStake;

        bytes32[] storage nodesArr = operatorNodesArray[operator];
        uint256 length = nodesArr.length;

        // If nothing changed, do nothing
        if (newTotalStake == registeredStake) {
            return;
        }

        if (newTotalStake > registeredStake) {
            leftoverStake = newTotalStake - registeredStake;
            emit OperatorHasLeftoverStake(operator, leftoverStake);
            emit AllNodeStakesUpdated(operator, newTotalStake);
            return;
        }
        // We only handle the scenario newTotalStake < registeredStake, when removing stake
        leftoverStake = registeredStake - newTotalStake;

        for (uint256 i = length; i > 0 && leftoverStake > 0;) {
            i--;
            bytes32 nodeId = nodesArr[i];
            bytes32 valID = balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
            if (balancerValidatorManager.isValidatorPendingWeightUpdate(valID)) {
                continue;
            }
            Validator memory validator = balancerValidatorManager.getValidator(valID);
            if (validator.status != ValidatorStatus.Active) {
                continue;
            }

            uint256 previousStake = getEffectiveNodeStake(currentEpoch, valID);

            // Remove stake
            if (previousStake == 0) {
                continue;
            }
            uint256 stakeToRemove = leftoverStake < previousStake ? leftoverStake : previousStake;
            if (limitStake > 0 && stakeToRemove > limitStake) {
                stakeToRemove = limitStake;
            }
            uint256 newStake = previousStake - stakeToRemove;
            leftoverStake -= stakeToRemove;

            if (
                (newStake < assetClasses[PRIMARY_ASSET_CLASS].minValidatorStake)
                    || !_requireMinSecondaryAssetClasses(0, operator)
            ) {
                newStake = 0;
                _initializeEndValidationAndFlag(operator, valID, nodeId);
            } else {
                _initializeValidatorStakeUpdate(operator, valID, newStake);
                emit NodeStakeUpdated(operator, nodeId, newStake, valID);
            }
        }

        // Finally emit updated stake
        emit AllNodeStakesUpdated(operator, newTotalStake);
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function initializeValidatorStakeUpdate(
        bytes32 nodeId,
        uint256 stakeAmount
    ) external updateGlobalNodeStakeOncePerEpoch {
        if (!operatorNodes[msg.sender].contains(nodeId)) {
            revert AvalancheL1Middleware__NodeNotFound(nodeId);
        }

        uint256 minStake = assetClasses[PRIMARY_ASSET_CLASS].minValidatorStake;
        uint256 maxStake = assetClasses[PRIMARY_ASSET_CLASS].maxValidatorStake;

        if (stakeAmount > maxStake) {
            revert AvalancheL1Middleware__StakeTooHigh(stakeAmount, maxStake);
        }

        if (stakeAmount < minStake) {
            revert AvalancheL1Middleware__StakeTooLow(stakeAmount, minStake);
        }

        bytes32 validationID = balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));

        _initializeValidatorStakeUpdate(msg.sender, validationID, stakeAmount);
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function completeValidatorRegistration(
        address operator,
        bytes32 nodeId,
        uint32 messageIndex
    ) external updateGlobalNodeStakeOncePerEpoch {
        _completeValidatorRegistration(operator, nodeId, messageIndex);
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function completeStakeUpdate(
        bytes32 nodeId,
        uint32 messageIndex
    ) external onlyRegisteredOperatorNode(msg.sender, nodeId) updateGlobalNodeStakeOncePerEpoch {
        _completeStakeUpdate(msg.sender, nodeId, messageIndex);
    }

    function completeValidatorRemoval(
        uint32 messageIndex
    ) external updateGlobalNodeStakeOncePerEpoch {
        _completeValidatorRemoval(messageIndex);
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function slash(
        uint48 epoch,
        address, /* operator */
        uint256, /* amount */
        uint96 assetClassId
    ) public onlyOwner updateStakeCache(epoch, assetClassId) updateGlobalNodeStakeOncePerEpoch {
        revert AvalancheL1Middleware__NotImplemented();
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function calcAndCacheStakes(uint48 epoch, uint96 assetClassId) public returns (uint256 totalStake) {
        uint48 epochStartTs = getEpochStartTs(epoch);

        uint256 length = operators.length();

        for (uint256 i; i < length; ++i) {
            (address operator, uint48 enabledTime, uint48 disabledTime) = operators.atWithTimes(i);
            if (!_wasActiveAt(enabledTime, disabledTime, epochStartTs)) {
                continue;
            }
            uint256 operatorStake = getOperatorStake(operator, epoch, assetClassId);

            operatorStakeCache[epoch][assetClassId][operator] = operatorStake;
            totalStake += operatorStake;
        }
        totalStakeCache[epoch][assetClassId] = totalStake;
        totalStakeCached[epoch][assetClassId] = true;
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */ 
    function calcAndCacheNodeStakeForAllOperators() public {
        uint48 current = getCurrentEpoch();
        if (current <= lastGlobalNodeStakeUpdateEpoch) {
            return; // Already up-to-date
        }

        uint48 epochsPending = current - lastGlobalNodeStakeUpdateEpoch;

        if (epochsPending > MAX_AUTO_EPOCH_UPDATES) {
            revert AvalancheL1Middleware__ManualEpochUpdateRequired(epochsPending, MAX_AUTO_EPOCH_UPDATES);
        }

        // Process pending epochs up to MAX_AUTO_EPOCH_UPDATES

        for (uint48 i = 0; i < epochsPending; i++) {
            bool processed = _processSingleEpochNodeStakeCacheUpdate();
            if (!processed) break; 
        }
    }

    /**
     * @notice Processes node stake cache updates for the next pending epoch.
     * @dev Updates lastGlobalNodeStakeUpdateEpoch if an epoch is processed.
     * @return processed True if an epoch was processed, false if already up-to-date.
     */
    function _processSingleEpochNodeStakeCacheUpdate() internal returns (bool) {
        uint48 current = getCurrentEpoch();
        if (current <= lastGlobalNodeStakeUpdateEpoch) {
            return false; // Already up-to-date
        }

        uint48 epochToProcess = lastGlobalNodeStakeUpdateEpoch + 1;

        // Process this single epochToProcess
        for (uint256 i = 0; i < operators.length(); i++) {
            (address operator,,) = operators.atWithTimes(i);
            // _calcAndCacheNodeStakeForOperatorAtEpoch itself handles carry-over from epochToProcess - 1
            _calcAndCacheNodeStakeForOperatorAtEpoch(operator, epochToProcess);
        }

        lastGlobalNodeStakeUpdateEpoch = epochToProcess;
        return true;
    }
    
    /**
     * @notice Manually processes node stake cache updates for a specified number of epochs.
     * @dev Useful if automatic updates via modifier fail due to too many pending epochs.
     * @param numEpochsToProcess The number of pending epochs to process in this call.
     */
    function manualProcessNodeStakeCache(uint48 numEpochsToProcess) external {
        if (numEpochsToProcess == 0) {
            revert AvalancheL1Middleware__NoEpochsToProcess();
        }

        uint48 currentEpoch = getCurrentEpoch();
        uint48 epochsActuallyPending = 0;
        if (currentEpoch > lastGlobalNodeStakeUpdateEpoch) {
            epochsActuallyPending = currentEpoch - lastGlobalNodeStakeUpdateEpoch;
        }

        if (numEpochsToProcess > epochsActuallyPending) {
            // Cap processing at what's actually pending to avoid processing non-existent future states.
            if (epochsActuallyPending == 0) {
                // Effectively, nothing to do, could emit an event or just succeed.
                emit NodeStakeCacheManuallyProcessed(lastGlobalNodeStakeUpdateEpoch, 0);
                return;
            }
            numEpochsToProcess = epochsActuallyPending;
        }
        
        uint48 epochsProcessedCount = 0;
        for (uint48 i = 0; i < numEpochsToProcess; i++) {
            if (lastGlobalNodeStakeUpdateEpoch >= currentEpoch) {
                break; // Caught up
            }
            bool processed = _processSingleEpochNodeStakeCacheUpdate();
            if (processed) {
                epochsProcessedCount++;
            } else {
                // Should not happen if currentEpoch > lastGlobalNodeStakeUpdateEpoch initially
                // and numEpochsToProcess is positive.
                break;
            }
        }

        emit NodeStakeCacheManuallyProcessed(lastGlobalNodeStakeUpdateEpoch, epochsProcessedCount);
    }
    
    /**
     * @notice Caches manager-based stake for each node of `operator` in epoch `currentEpoch`.
     * @param operator The operator address
     */
    function _calcAndCacheNodeStakeForOperatorAtEpoch(address operator, uint48 epoch) internal {
        uint48 prevEpoch = (epoch == 0) ? 0 : epoch - 1;
        bytes32[] storage nodeArray = operatorNodesArray[operator];
        for (uint256 i = nodeArray.length; i > 0;) {
            i--;
            bytes32 nodeId = nodeArray[i];
            bytes32 valID = balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));

            // If no removal/update, just carry over from prevEpoch (only if we haven’t set it yet)
            if (!nodePendingRemoval[valID] && !nodePendingUpdate[valID]) {
                if (nodeStakeCache[epoch][valID] == 0) {
                    nodeStakeCache[epoch][valID] = nodeStakeCache[prevEpoch][valID];
                }
                continue;
            }

            if (nodePendingRemoval[valID] && nodeStakeCache[epoch][valID] == 0 && nodeStakeCache[prevEpoch][valID] != 0)
            {
                _removeNodeFromArray(operator, nodeId);
                nodePendingRemoval[valID] = false;
            }

            // If there was a pending update, finalize and clear the pending markers
            if (nodePendingUpdate[valID]) {
                nodePendingUpdate[valID] = false;
            }
        }

        // Reset operator locked stake once per epoch
        if (operatorLockedStake[operator] > 0) {
            operatorLockedStake[operator] = 0;
        }
    }

    /**
     * @notice Remove a node => end its validator. Checks still to be done.
     * @param nodeId The node ID
     */
    function _removeNode(address operator, bytes32 nodeId) internal {
        bytes32 validationID = balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
        _initializeEndValidationAndFlag(operator, validationID, nodeId);
    }

    function _initializeEndValidationAndFlag(address operator, bytes32 validationID, bytes32 nodeId) internal {
        uint48 nextEpoch = getCurrentEpoch() + 1;
        nodeStakeCache[nextEpoch][validationID] = 0;
        nodePendingRemoval[validationID] = true;

        balancerValidatorManager.initializeEndValidation(validationID);

        emit NodeRemoved(operator, nodeId, validationID);
    }

    /**
     * @notice Remove the node from the dynamic array (swap and pop).
     * @param nodeId The node ID.
     */
    function _removeNodeFromArray(address operator, bytes32 nodeId) internal {
        bytes32[] storage nodesArr = operatorNodesArray[operator];
        // Find the node index by looping (O(n)), then swap+pop
        uint256 length = nodesArr.length;
        for (uint256 i = 0; i < length; i++) {
            if (nodesArr[i] == nodeId) {
                uint256 lastIndex = length - 1;
                if (i != lastIndex) {
                    nodesArr[i] = nodesArr[lastIndex];
                }
                nodesArr.pop();
                break;
            }
        }
    }

    /**
     * @notice Completes a validator's registration.
     * @param operator The operator who owns the validator
     * @param nodeId The unique ID of the validator whose registration is being finalized
     * @param messageIndex The message index from the BalancerValidatorManager (used for ordering/verification)
     */
    function _completeValidatorRegistration(
        address operator,
        bytes32 nodeId,
        uint32 messageIndex
    ) internal onlyRegisteredOperatorNode(operator, nodeId) {
        balancerValidatorManager.completeValidatorRegistration(messageIndex);
    }

    /**
     * @notice Completes a validator's removal.
     * @param messageIndex The message index from the BalancerValidatorManager (used for ordering/verification)
     */
    function _completeValidatorRemoval(
        uint32 messageIndex
    ) internal {
        balancerValidatorManager.completeEndValidation(messageIndex);
    }

    /**
     * @notice Completes a validator's stake update
     * @param operator The operator who owns the validator
     * @param nodeId The unique ID of the validator whose relative weight update is being finalized
     * @param messageIndex The message index from the BalancerValidatorManager (used for ordering/verification)
     */
    function _completeStakeUpdate(
        address operator,
        bytes32 nodeId,
        uint32 messageIndex
    ) internal onlyRegisteredOperatorNode(operator, nodeId) {
        bytes32 validationID = balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));

        if (!balancerValidatorManager.isValidatorPendingWeightUpdate(validationID)) {
            revert AvalancheL1Middleware__WeightUpdateNotPending(validationID);
        }
        // if the completeValidatorWeightUpdate fails, not sure if the previous bool is secure.
        balancerValidatorManager.completeValidatorWeightUpdate(validationID, messageIndex);
    }

    /**
     * @notice Sets the stake of a validator and updates the operator's locked stake accordingly.
     * @param operator The operator who owns the validator
     * @param validationID The unique ID of the validator whose stake is being updated
     * @param newStake The new stake for the validator
     * @dev When updating the relative weight of a validator, the operator's locked stake is increased or decreased
     */
    function _initializeValidatorStakeUpdate(address operator, bytes32 validationID, uint256 newStake) internal {
        uint48 currentEpoch = getCurrentEpoch();
        uint256 cachedStake = getEffectiveNodeStake(currentEpoch, validationID);

        if (balancerValidatorManager.isValidatorPendingWeightUpdate(validationID)) {
            revert AvalancheL1Middleware__WeightUpdatePending(validationID);
        }
        uint256 delta;
        if (newStake > cachedStake) {
            delta = newStake - cachedStake;
            if (delta > _getOperatorAvailableStake(operator)) {
                revert AvalancheL1Middleware__NotEnoughFreeStake(newStake);
            }
        }
        operatorLockedStake[operator] += delta;
        nodePendingUpdate[validationID] = true;
        nodeStakeCache[currentEpoch + 1][validationID] = newStake;
        // if newStake < cachedStake, no lock should happen, it's locked in the cache

        uint64 scaledWeight = StakeConversion.stakeToWeight(newStake, WEIGHT_SCALE_FACTOR);

        balancerValidatorManager.initializeValidatorWeightUpdate(validationID, scaledWeight);
    }

    function _requireMinSecondaryAssetClasses(uint256 extraNode, address operator) internal view returns (bool) {
        uint48 epoch = getCurrentEpoch();
        uint256 nodeCount = operatorNodesArray[operator].length; // existing nodes

        uint256 secCount = secondaryAssetClasses.length();
        if (secCount == 0) {
            return true;
        }
        for (uint256 i = 0; i < secCount; i++) {
            uint256 classId = secondaryAssetClasses.at(i);
            uint256 stake = getOperatorStake(operator, epoch, uint96(classId));
            // Check ratio vs. class's min stake, could add an emit here to debug
            if (stake / (nodeCount + extraNode) < assetClasses[classId].minValidatorStake) {
                return false;
            }
        }
        return true;
    }

    /**
     * @notice Checks if the classId is active
     * @param assetClassId The asset class ID
     * @return bool True if active
     */
    function _isActiveAssetClass(
        uint256 assetClassId
    ) internal view returns (bool) {
        return (assetClassId == PRIMARY_ASSET_CLASS || secondaryAssetClasses.contains(assetClassId));
    }

    /**
     * @notice Checks if the asset is still in use by a vault
     * @param assetClassId The asset class ID
     * @param asset The asset address
     * @return bool True if in use by any vault
     */
    function _isUsedAsset(uint256 assetClassId, address asset) internal view returns (bool) {
        for (uint256 i; i < vaultManager.getVaultCount(); ++i) {
            (address vault,,) = vaultManager.getVaultAtWithTimes(i);
            if (vaultManager.vaultToAssetClass(vault) == assetClassId && IVaultTokenized(vault).collateral() == asset) {
                return true;
            }
        }
        return false;
    }

    /**
     * @notice Checks if the asset class is still in use by a vault
     * @param assetClassId The asset class ID
     * @return bool True if in use by any vault
     */
    function _isUsedAssetClass(
        uint256 assetClassId
    ) internal view returns (bool) {
        for (uint256 i; i < vaultManager.getVaultCount(); ++i) {
            (address vault,,) = vaultManager.getVaultAtWithTimes(i);
            if (vaultManager.vaultToAssetClass(vault) == assetClassId) {
                return true;
            }
        }
        return false;
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function getActiveAssetClasses() external view returns (uint256 primary, uint256[] memory secondaries) {
        primary = PRIMARY_ASSET_CLASS;
        secondaries = secondaryAssetClasses.values();
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function getEpochStartTs(
        uint48 epoch
    ) public view returns (uint48 timestamp) {
        return START_TIME + epoch * EPOCH_DURATION;
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function getEpochAtTs(
        uint48 timestamp
    ) public view returns (uint48 epoch) {
        return (timestamp - START_TIME) / EPOCH_DURATION;
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function getCurrentEpoch() public view returns (uint48 epoch) {
        return getEpochAtTs(Time.timestamp());
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function getOperatorStake(
        address operator,
        uint48 epoch,
        uint96 assetClassId
    ) public view returns (uint256 stake) {
        if (totalStakeCached[epoch][assetClassId]) {
            uint256 cachedStake = operatorStakeCache[epoch][assetClassId][operator];

            return cachedStake;
        }

        uint48 epochStartTs = getEpochStartTs(epoch);

        uint256 totalVaults = vaultManager.getVaultCount();

        for (uint256 i; i < totalVaults; ++i) {
            (address vault, uint48 enabledTime, uint48 disabledTime) = vaultManager.getVaultAtWithTimes(i);

            // Skip if vault not active in the target epoch
            if (!_wasActiveAt(enabledTime, disabledTime, epochStartTs)) {
                continue;
            }

            // Skip if vault asset not in AssetClassID
            if (vaultManager.getVaultAssetClass(vault) != assetClassId) {
                continue;
            }

            uint256 vaultStake = BaseDelegator(IVaultTokenized(vault).delegator()).stakeAt(
                L1_VALIDATOR_MANAGER, assetClassId, operator, epochStartTs, new bytes(0)
            );

            stake += vaultStake;
        }
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function getTotalStake(uint48 epoch, uint96 assetClassId) public view returns (uint256) {
        if (totalStakeCached[epoch][assetClassId]) {
            return totalStakeCache[epoch][assetClassId];
        }
        return _calcTotalStake(epoch, assetClassId);
    }

    function getOperatorNodesLength(
        address operator
    ) public view returns (uint256) {
        return operatorNodesArray[operator].length;
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function getAllOperators() external view returns (address[] memory) {
        uint256 length = operators.length();
        address[] memory result = new address[](length);
        for (uint256 i; i < length; i++) {
            (address operator,,) = operators.atWithTimes(i);
            result[i] = operator;
        }
        return result;
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function getNodeStake(uint48 epoch, bytes32 validationID) external view returns (uint256) {
        return nodeStakeCache[epoch][validationID];
    }

    function isActiveAssetClass(
        uint96 assetClassId
    ) external view returns (bool) {
        return _isActiveAssetClass(assetClassId);
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function getActiveNodesForEpoch(
        address operator,
        uint48 epoch
    ) external view returns (bytes32[] memory activeNodeIds) {
        uint48 epochStartTs = getEpochStartTs(epoch);

        // Gather all nodes from the never-removed set
        bytes32[] memory allNodeIds = operatorNodes[operator].values();

        bytes32[] memory temp = new bytes32[](allNodeIds.length);
        uint256 activeCount;

        for (uint256 i = 0; i < allNodeIds.length; i++) {
            bytes32 nodeId = allNodeIds[i];
            bytes32 validationID =
                balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
            Validator memory validator = balancerValidatorManager.getValidator(validationID);

            if (_wasActiveAt(uint48(validator.startedAt), uint48(validator.endedAt), epochStartTs)) {
                temp[activeCount++] = nodeId;
            }
        }

        activeNodeIds = new bytes32[](activeCount);
        for (uint256 j = 0; j < activeCount; j++) {
            activeNodeIds[j] = temp[j];
        }
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function getOperatorAvailableStake(
        address operator
    ) external view returns (uint256) {
        return _getOperatorAvailableStake(operator);
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function getVaultManager() external view returns (address) {
        return address(vaultManager);
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function getOperatorUsedStakeCached(
        address operator
    ) public view returns (uint256 registeredStake) {
        bytes32[] storage nodesArr = operatorNodesArray[operator];
        for (uint256 i = 0; i < nodesArr.length; i++) {
            bytes32 nodeId = nodesArr[i];
            bytes32 validationID =
                balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
            registeredStake += getEffectiveNodeStake(getCurrentEpoch(), validationID);
        }
    }

    /**
     * @notice  Gets the effective stake for a specific ValidationID.
     * @param epoch The epoch number
     * @param validationID The validation ID
     */
    function getEffectiveNodeStake(uint48 epoch, bytes32 validationID) internal view returns (uint256) {
        return nodeStakeCache[epoch][validationID];
    }

    /**
     * @inheritdoc IAvalancheL1Middleware
     */
    function getOperatorUsedStakeCachedPerEpoch(
        uint48 epoch,
        address operator,
        uint96 assetClass
    ) external view returns (uint256) {
        if (assetClass == PRIMARY_ASSET_CLASS) {
            bytes32[] memory nodesArr = this.getActiveNodesForEpoch(operator, epoch);
            uint256 operatorStake = 0;

            for (uint256 i = 0; i < nodesArr.length; i++) {
                bytes32 nodeId = nodesArr[i];
                bytes32 validationID =
                    balancerValidatorManager.registeredValidators(abi.encodePacked(uint160(uint256(nodeId))));
                operatorStake += getEffectiveNodeStake(epoch, validationID);
            }
            return operatorStake;
        } else {
            return getOperatorStake(operator, epoch, assetClass);
        }
    }

    /**
     * @notice Get the validator per ValidationID.
     * @param validationID The validation ID.
     */
    function _getValidator(
        bytes32 validationID
    ) internal view returns (Validator memory) {
        return balancerValidatorManager.getValidator(validationID);
    }

    /**
     * @notice Returns the available stake for an operator
     * @param operator The operator address
     * @return The available stake
     */
    function _getOperatorAvailableStake(
        address operator
    ) internal view returns (uint256) {
        uint48 epoch = getCurrentEpoch();
        uint256 totalStake = getOperatorStake(operator, epoch, PRIMARY_ASSET_CLASS);

        // Enforce max security module weight
        (, uint64 securityModuleMaxWeight) = balancerValidatorManager.getSecurityModuleWeights(address(this));
        uint256 convertedSecurityModuleMaxWeight =
            StakeConversion.weightToStake(securityModuleMaxWeight, WEIGHT_SCALE_FACTOR);
        if (totalStake > convertedSecurityModuleMaxWeight) {
            totalStake = convertedSecurityModuleMaxWeight;
        }

        uint256 lockedStake = operatorLockedStake[operator];
        if (totalStake <= lockedStake) {
            return 0;
        }
        return totalStake - lockedStake;
    }

    /**
     * @notice Helper to calculate total stake for an epoch
     * @param epoch The epoch number
     * @param assetClassId The asset class ID
     * @return totalStake The total stake across all operators
     */
    function _calcTotalStake(uint48 epoch, uint96 assetClassId) private view returns (uint256 totalStake) {
        uint48 epochStartTs = getEpochStartTs(epoch);

        // for epoch older than SLASHING_WINDOW total stake can be invalidated (use cache)
        if (epochStartTs > Time.timestamp() || epochStartTs < Time.timestamp() - SLASHING_WINDOW) {
            revert AvalancheL1Middleware__EpochError(epochStartTs);
        }

        uint256 length = operators.length();

        for (uint256 i; i < length; ++i) {
            (address operator, uint48 enabledTime, uint48 disabledTime) = operators.atWithTimes(i);
            // just skip operator if it was added after the target epoch or paused
            if (!_wasActiveAt(enabledTime, disabledTime, epochStartTs)) {
                continue;
            }
            uint256 operatorStake = getOperatorStake(operator, epoch, assetClassId);
            totalStake += operatorStake;
        }
    }

    /**
     * @notice Checks if an operator or vault was active at a specific timestamp
     * @param enabledTime The time it was enabled
     * @param disabledTime The time it was disabled
     * @param timestamp The timestamp to check
     * @return bool True if active
     */
    function _wasActiveAt(uint48 enabledTime, uint48 disabledTime, uint48 timestamp) private pure returns (bool) {
        return enabledTime != 0 && enabledTime <= timestamp && (disabledTime == 0 || disabledTime >= timestamp);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/types/Time.sol)

pragma solidity ^0.8.20;

import {Math} from "../math/Math.sol";
import {SafeCast} from "../math/SafeCast.sol";

/**
 * @dev This library provides helpers for manipulating time-related objects.
 *
 * It uses the following types:
 * - `uint48` for timepoints
 * - `uint32` for durations
 *
 * While the library doesn't provide specific types for timepoints and duration, it does provide:
 * - a `Delay` type to represent duration that can be programmed to change value automatically at a given point
 * - additional helper functions
 */
library Time {
    using Time for *;

    /**
     * @dev Get the block timestamp as a Timepoint.
     */
    function timestamp() internal view returns (uint48) {
        return SafeCast.toUint48(block.timestamp);
    }

    /**
     * @dev Get the block number as a Timepoint.
     */
    function blockNumber() internal view returns (uint48) {
        return SafeCast.toUint48(block.number);
    }

    // ==================================================== Delay =====================================================
    /**
     * @dev A `Delay` is a uint32 duration that can be programmed to change value automatically at a given point in the
     * future. The "effect" timepoint describes when the transitions happens from the "old" value to the "new" value.
     * This allows updating the delay applied to some operation while keeping some guarantees.
     *
     * In particular, the {update} function guarantees that if the delay is reduced, the old delay still applies for
     * some time. For example if the delay is currently 7 days to do an upgrade, the admin should not be able to set
     * the delay to 0 and upgrade immediately. If the admin wants to reduce the delay, the old delay (7 days) should
     * still apply for some time.
     *
     *
     * The `Delay` type is 112 bits long, and packs the following:
     *
     * ```
     *   | [uint48]: effect date (timepoint)
     *   |           | [uint32]: value before (duration)
     *   ↓           ↓       ↓ [uint32]: value after (duration)
     * 0xAAAAAAAAAAAABBBBBBBBCCCCCCCC
     * ```
     *
     * NOTE: The {get} and {withUpdate} functions operate using timestamps. Block number based delays are not currently
     * supported.
     */
    type Delay is uint112;

    /**
     * @dev Wrap a duration into a Delay to add the one-step "update in the future" feature
     */
    function toDelay(uint32 duration) internal pure returns (Delay) {
        return Delay.wrap(duration);
    }

    /**
     * @dev Get the value at a given timepoint plus the pending value and effect timepoint if there is a scheduled
     * change after this timepoint. If the effect timepoint is 0, then the pending value should not be considered.
     */
    function _getFullAt(Delay self, uint48 timepoint) private pure returns (uint32, uint32, uint48) {
        (uint32 valueBefore, uint32 valueAfter, uint48 effect) = self.unpack();
        return effect <= timepoint ? (valueAfter, 0, 0) : (valueBefore, valueAfter, effect);
    }

    /**
     * @dev Get the current value plus the pending value and effect timepoint if there is a scheduled change. If the
     * effect timepoint is 0, then the pending value should not be considered.
     */
    function getFull(Delay self) internal view returns (uint32, uint32, uint48) {
        return _getFullAt(self, timestamp());
    }

    /**
     * @dev Get the current value.
     */
    function get(Delay self) internal view returns (uint32) {
        (uint32 delay, , ) = self.getFull();
        return delay;
    }

    /**
     * @dev Update a Delay object so that it takes a new duration after a timepoint that is automatically computed to
     * enforce the old delay at the moment of the update. Returns the updated Delay object and the timestamp when the
     * new delay becomes effective.
     */
    function withUpdate(
        Delay self,
        uint32 newValue,
        uint32 minSetback
    ) internal view returns (Delay updatedDelay, uint48 effect) {
        uint32 value = self.get();
        uint32 setback = uint32(Math.max(minSetback, value > newValue ? value - newValue : 0));
        effect = timestamp() + setback;
        return (pack(value, newValue, effect), effect);
    }

    /**
     * @dev Split a delay into its components: valueBefore, valueAfter and effect (transition timepoint).
     */
    function unpack(Delay self) internal pure returns (uint32 valueBefore, uint32 valueAfter, uint48 effect) {
        uint112 raw = Delay.unwrap(self);

        valueAfter = uint32(raw);
        valueBefore = uint32(raw >> 32);
        effect = uint48(raw >> 64);

        return (valueBefore, valueAfter, effect);
    }

    /**
     * @dev pack the components into a Delay object.
     */
    function pack(uint32 valueBefore, uint32 valueAfter, uint48 effect) internal pure returns (Delay) {
        return Delay.wrap((uint112(effect) << 64) | (uint112(valueBefore) << 32) | uint112(valueAfter));
    }
}

File 3 of 50 : EnumerableMap.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableMap.js.

pragma solidity ^0.8.20;

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

/**
 * @dev Library for managing an enumerable variant of Solidity's
 * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
 * type.
 *
 * Maps have the following properties:
 *
 * - Entries are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Entries are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableMap for EnumerableMap.UintToAddressMap;
 *
 *     // Declare a set state variable
 *     EnumerableMap.UintToAddressMap private myMap;
 * }
 * ```
 *
 * The following map types are supported:
 *
 * - `uint256 -> address` (`UintToAddressMap`) since v3.0.0
 * - `address -> uint256` (`AddressToUintMap`) since v4.6.0
 * - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0
 * - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0
 * - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableMap.
 * ====
 */
library EnumerableMap {
    using EnumerableSet for EnumerableSet.Bytes32Set;

    // To implement this library for multiple types with as little code repetition as possible, we write it in
    // terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions,
    // and user-facing implementations such as `UintToAddressMap` are just wrappers around the underlying Map.
    // This means that we can only create new EnumerableMaps for types that fit in bytes32.

    /**
     * @dev Query for a nonexistent map key.
     */
    error EnumerableMapNonexistentKey(bytes32 key);

    struct Bytes32ToBytes32Map {
        // Storage of keys
        EnumerableSet.Bytes32Set _keys;
        mapping(bytes32 key => bytes32) _values;
    }

    /**
     * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
    function set(Bytes32ToBytes32Map storage map, bytes32 key, bytes32 value) internal returns (bool) {
        map._values[key] = value;
        return map._keys.add(key);
    }

    /**
     * @dev Removes a key-value pair from a map. O(1).
     *
     * Returns true if the key was removed from the map, that is if it was present.
     */
    function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) {
        delete map._values[key];
        return map._keys.remove(key);
    }

    /**
     * @dev Returns true if the key is in the map. O(1).
     */
    function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) {
        return map._keys.contains(key);
    }

    /**
     * @dev Returns the number of key-value pairs in the map. O(1).
     */
    function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) {
        return map._keys.length();
    }

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

    /**
     * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
    function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) {
        bytes32 value = map._values[key];
        if (value == bytes32(0)) {
            return (contains(map, key), bytes32(0));
        } else {
            return (true, value);
        }
    }

    /**
     * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
    function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) {
        bytes32 value = map._values[key];
        if (value == 0 && !contains(map, key)) {
            revert EnumerableMapNonexistentKey(key);
        }
        return value;
    }

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

    // UintToUintMap

    struct UintToUintMap {
        Bytes32ToBytes32Map _inner;
    }

    /**
     * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
    function set(UintToUintMap storage map, uint256 key, uint256 value) internal returns (bool) {
        return set(map._inner, bytes32(key), bytes32(value));
    }

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

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

    /**
     * @dev Returns the number of elements in the map. O(1).
     */
    function length(UintToUintMap storage map) internal view returns (uint256) {
        return length(map._inner);
    }

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

    /**
     * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
    function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) {
        (bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
        return (success, uint256(value));
    }

    /**
     * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
    function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) {
        return uint256(get(map._inner, bytes32(key)));
    }

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

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // UintToAddressMap

    struct UintToAddressMap {
        Bytes32ToBytes32Map _inner;
    }

    /**
     * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
    function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) {
        return set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
    }

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

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

    /**
     * @dev Returns the number of elements in the map. O(1).
     */
    function length(UintToAddressMap storage map) internal view returns (uint256) {
        return length(map._inner);
    }

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

    /**
     * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
    function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) {
        (bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
        return (success, address(uint160(uint256(value))));
    }

    /**
     * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
    function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
        return address(uint160(uint256(get(map._inner, bytes32(key)))));
    }

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

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // AddressToUintMap

    struct AddressToUintMap {
        Bytes32ToBytes32Map _inner;
    }

    /**
     * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
    function set(AddressToUintMap storage map, address key, uint256 value) internal returns (bool) {
        return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value));
    }

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

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

    /**
     * @dev Returns the number of elements in the map. O(1).
     */
    function length(AddressToUintMap storage map) internal view returns (uint256) {
        return length(map._inner);
    }

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

    /**
     * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
    function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) {
        (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key))));
        return (success, uint256(value));
    }

    /**
     * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
    function get(AddressToUintMap storage map, address key) internal view returns (uint256) {
        return uint256(get(map._inner, bytes32(uint256(uint160(key)))));
    }

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

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // Bytes32ToUintMap

    struct Bytes32ToUintMap {
        Bytes32ToBytes32Map _inner;
    }

    /**
     * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
    function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) {
        return set(map._inner, key, bytes32(value));
    }

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

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

    /**
     * @dev Returns the number of elements in the map. O(1).
     */
    function length(Bytes32ToUintMap storage map) internal view returns (uint256) {
        return length(map._inner);
    }

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

    /**
     * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
    function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) {
        (bool success, bytes32 value) = tryGet(map._inner, key);
        return (success, uint256(value));
    }

    /**
     * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
    function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) {
        return uint256(get(map._inner, key));
    }

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

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}

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

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

            return true;
        } else {
            return false;
        }
    }

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

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

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

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

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

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

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

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

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

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

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

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

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

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

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

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

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

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

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

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

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

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

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

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

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

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}

// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.25;

/**
 * @dev Validator status
 */
enum ValidatorStatus {
    Unknown,
    PendingAdded,
    Active,
    PendingRemoved,
    Completed,
    Invalidated
}

/**
 * @dev Specifies the owner of a validator's remaining balance or disable owner on the P-Chain.
 * P-Chain addresses are also 20-bytes, so we use the address type to represent them.
 */
struct PChainOwner {
    uint32 threshold;
    address[] addresses;
}

/**
 * @dev Contains the active state of a Validator
 */
struct Validator {
    ValidatorStatus status;
    bytes nodeID;
    uint64 startingWeight;
    uint64 messageNonce;
    uint64 weight;
    uint64 startedAt;
    uint64 endedAt;
}

/**
 * @dev Describes the current churn period
 */
struct ValidatorChurnPeriod {
    uint256 startedAt;
    uint64 initialWeight;
    uint64 totalWeight;
    uint64 churnAmount;
}

/**
 * @notice Validator Manager settings, used to initialize the Validator Manager
 * @notice The l1ID is the ID of the L1 that the Validator Manager is managing
 * @notice The churnPeriodSeconds is the duration of the churn period in seconds
 * @notice The maximumChurnPercentage is the maximum percentage of the total weight that can be added or removed in a single churn period
 */
struct ValidatorManagerSettings {
    bytes32 l1ID;
    uint64 churnPeriodSeconds;
    uint8 maximumChurnPercentage;
}

/**
 * @dev Description of the conversion data used to convert
 * a subnet to an L1 on the P-Chain.
 * This data is the pre-image of a hash that is authenticated by the P-Chain
 * and verified by the Validator Manager.
 */
struct ConversionData {
    bytes32 l1ID;
    bytes32 validatorManagerBlockchainID;
    address validatorManagerAddress;
    InitialValidator[] initialValidators;
}

/**
 * @dev Specifies an initial validator, used in the conversion data.
 */
struct InitialValidator {
    bytes nodeID;
    bytes blsPublicKey;
    uint64 weight;
}

/**
 * @dev Specifies a validator to register.
 */
struct ValidatorRegistrationInput {
    bytes nodeID;
    bytes blsPublicKey;
    uint64 registrationExpiry;
    PChainOwner remainingBalanceOwner;
    PChainOwner disableOwner;
}

/**
 * @notice Interface for Validator Manager contracts that implement Subnet-only Validator management.
 */
interface IValidatorManager {
    /**
     * @notice Emitted when a new validation period is created by locking stake in the manager contract.
     * Note: This event does not mean that the validation period has been successfully registered on the P-Chain,
     * and rewards for this validation period will not begin accruing until the {ValidationPeriodRegistered} event is
     * emitted.
     * @param validationID The ID of the validation period being created.
     * @param nodeID The node ID of the validator being registered.
     * @param registerValidationMessageID The ID of the ICM message that will be sent to the P-Chain to register the
     * validation period.
     * @param weight The weight of the validator being registered.
     * @param registrationExpiry The Unix timestamp after which the reigistration is no longer valid on the P-Chain.
     */
    event ValidationPeriodCreated(
        bytes32 indexed validationID,
        bytes indexed nodeID,
        bytes32 indexed registerValidationMessageID,
        uint64 weight,
        uint64 registrationExpiry
    );

    event InitialValidatorCreated(
        bytes32 indexed validationID, bytes indexed nodeID, uint64 weight
    );

    /**
     * @notice Emitted when the staking manager learns that the validation period has been successfully registered
     * on the P-Chain. Rewards for this validation period will begin accruing when this event is emitted.
     * @param validationID The ID of the validation period being registered.
     * @param weight The weight of the validator being registered.
     * @param timestamp The time at which the validation period was registered with the contract.
     */
    event ValidationPeriodRegistered(
        bytes32 indexed validationID, uint64 weight, uint256 timestamp
    );

    /**
     * @notice Emitted when the process of ending a registered validation period is started by calling
     * {initializeEndValidation}.
     * Note: The stake for this validation period remains locked until a {ValidationPeriodRemoved} event is emitted.
     * @param validationID The ID of the validation period being removed.
     * @param setWeightMessageID The ID of the ICM message that updates the validator's weight on the P-Chain.
     * @param weight The weight of the validator being removed.
     * @param endTime The time at which the removal was initiated.
     */
    event ValidatorRemovalInitialized(
        bytes32 indexed validationID,
        bytes32 indexed setWeightMessageID,
        uint64 weight,
        uint256 endTime
    );

    /**
     * @notice Emitted when the stake for a validation period is unlocked and returned to the staker.
     * This is done by calling {completeEndValidation}, which provides proof from the P-Chain that the
     * validation period is not active and will never be active in the future.
     * @param validationID The ID of the validation period being removed.
     */
    event ValidationPeriodEnded(bytes32 indexed validationID, ValidatorStatus indexed status);

    /**
     * @notice Event emitted when validator weight is updated.
     * @param validationID The ID of the validation period being updated
     * @param nonce The message nonce used to update the validator weight
     * @param weight The updated validator weight that is sent to the P-Chain
     * @param setWeightMessageID The ID of the ICM message that updates the validator's weight on the P-Chain
     */
    event ValidatorWeightUpdate(
        bytes32 indexed validationID,
        uint64 indexed nonce,
        uint64 weight,
        bytes32 setWeightMessageID
    );

    /**
     * @notice Verifies and sets the initial validator set for the chain through a P-Chain SubnetToL1ConversionMessage.
     * @param conversionData The subnet conversion message data used to recompute and verify against the conversionID.
     * @param messsageIndex The index that contains the SubnetToL1ConversionMessage ICM message containing the conversionID to be verified against the provided {ConversionData}
     */
    function initializeValidatorSet(
        ConversionData calldata conversionData,
        uint32 messsageIndex
    ) external;

    /**
     * @notice Resubmits a validator registration message to be sent to the P-Chain.
     * Only necessary if the original message can't be delivered due to validator churn.
     * @param validationID The ID of the validation period being registered.
     */
    function resendRegisterValidatorMessage(bytes32 validationID) external;

    /**
     * @notice Completes the validator registration process by returning an acknowledgement of the registration of a
     * validationID from the P-Chain.
     * @param messageIndex The index of the ICM message to be received providing the acknowledgement.
     */
    function completeValidatorRegistration(uint32 messageIndex) external;

    /**
     * @notice Resubmits a validator end message to be sent to the P-Chain.
     * Only necessary if the original message can't be delivered due to validator churn.
     * @param validationID The ID of the validation period being ended.
     */
    function resendEndValidatorMessage(bytes32 validationID) external;

    /**
     * @notice Completes the process of ending a validation period by receiving an acknowledgement from the P-Chain
     * that the validation ID is not active and will never be active in the future. Returns the the stake associated
     * with the validation.
     * Note: This function can be used for successful validation periods that have been explicitly ended by calling
     * {initializeEndValidation} or for validation periods that never began on the P-Chain due to the {registrationExpiry} being reached.
     * @param messageIndex The index of the ICM message to be received providing the proof the validation is not active
     * and never will be active on the P-Chain.
     */
    function completeEndValidation(uint32 messageIndex) external;
}

// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity 0.8.25;

import {
    BalancerValidatorManagerSettings,
    IBalancerValidatorManager
} from "../../interfaces/ValidatorManager/IBalancerValidatorManager.sol";
import {ValidatorManager} from "@avalabs/icm-contracts/validator-manager/ValidatorManager.sol";
import {ValidatorMessages} from "@avalabs/icm-contracts/validator-manager/ValidatorMessages.sol";
import {
    IValidatorManager,
    Validator,
    ValidatorChurnPeriod,
    ValidatorManagerSettings,
    ValidatorRegistrationInput,
    ValidatorStatus
} from "@avalabs/icm-contracts/validator-manager/interfaces/IValidatorManager.sol";
import {OwnableUpgradeable} from
    "@openzeppelin/[email protected]/access/OwnableUpgradeable.sol";
import {EnumerableMap} from "@openzeppelin/[email protected]/utils/structs/EnumerableMap.sol";

/**
 * @title BalancerValidatorManager
 * @author ADDPHO
 * @notice The Balancer Validator Manager contract allows to balance the weight of an L1 between multiple security modules.
 * @custom:security-contact [email protected]
 * @custom:oz-upgrades-unsafe-allow external-library-linking
 * @custom:oz-upgrades-from PoAValidatorManager
 */
contract BalancerValidatorManager is
    IBalancerValidatorManager,
    ValidatorManager,
    OwnableUpgradeable
{
    using EnumerableMap for EnumerableMap.AddressToUintMap;

    /// @custom:storage-location erc7201:suzaku.storage.BalancerValidatorManager
    struct BalancerValidatorManagerStorage {
        /// @notice The registered security modules along with their maximum weight
        EnumerableMap.AddressToUintMap securityModules;
        /// @notice The total weight of all validators for a given security module
        mapping(address securityModule => uint64 weight) securityModuleWeight;
        /// @notice The security module to which each validator belongs
        mapping(bytes32 validationID => address securityModule) validatorSecurityModule;
        /// @notice Validators pending weight updates
        mapping(bytes32 validationID => bytes32 messageID) validatorPendingWeightUpdate;
    }

    // keccak256(abi.encode(uint256(keccak256("suzaku.storage.BalancerValidatorManager")) - 1)) & ~bytes32(uint256(0xff));
    bytes32 public constant BALANCER_VALIDATOR_MANAGER_STORAGE_LOCATION =
        0x9d2d7650aa35ca910e5b713f6b3de6524a06fbcb31ffc9811340c6f331a23400;

    // solhint-disable func-name-mixedcase, ordering
    function _getBalancerValidatorManagerStorage()
        private
        pure
        returns (BalancerValidatorManagerStorage storage $)
    {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            $.slot := BALANCER_VALIDATOR_MANAGER_STORAGE_LOCATION
        }
    }

    modifier onlySecurityModule() {
        BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();

        if (!$.securityModules.contains(msg.sender)) {
            revert BalancerValidatorManager__SecurityModuleNotRegistered(msg.sender);
        }
        _;
    }

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /**
     * @notice Initialize the Balancer Validator Manager
     * @dev This function is reinitializer(2) because it is upgrated from PoAValidatorManager
     * https://github.com/ava-labs/icm-contracts/blob/validator-manager-v1.0.0/contracts/validator-manager/PoAValidatorManager.sol
     * @param settings The settings for the Balancer Validator Manager
     */
    function initialize(
        BalancerValidatorManagerSettings calldata settings
    ) external reinitializer(2) {
        __BalancerValidatorManager_init(settings);
    }

    function __BalancerValidatorManager_init(
        BalancerValidatorManagerSettings calldata settings
    ) internal onlyInitializing {
        __ValidatorManager_init(settings.baseSettings);
        __Ownable_init(settings.initialOwner);
        __BalancerValidatorManager_init_unchained(
            settings.initialSecurityModule,
            settings.initialSecurityModuleMaxWeight,
            settings.migratedValidators
        );
    }

    function __BalancerValidatorManager_init_unchained(
        address initialSecurityModule,
        uint64 initialSecurityModuleMaxWeight,
        bytes[] calldata migratedValidators
    ) internal onlyInitializing {
        ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();

        // Ensure initial security module max weight is sufficient
        if (initialSecurityModuleMaxWeight < vms._churnTracker.totalWeight) {
            revert BalancerValidatorManager__InitialSecurityModuleMaxWeightLowerThanTotalWeight(
                initialSecurityModule, initialSecurityModuleMaxWeight, vms._churnTracker.totalWeight
            );
        }

        _setUpSecurityModule(initialSecurityModule, initialSecurityModuleMaxWeight);
        _migrateValidators(initialSecurityModule, migratedValidators);
    }

    // solhint-enable func-name-mixedcase

    /// @inheritdoc IBalancerValidatorManager
    function setUpSecurityModule(address securityModule, uint64 maxWeight) external onlyOwner {
        _setUpSecurityModule(securityModule, maxWeight);
    }

    /// @inheritdoc IBalancerValidatorManager
    function initializeValidatorRegistration(
        ValidatorRegistrationInput calldata registrationInput,
        uint64 weight
    ) external onlySecurityModule returns (bytes32 validationID) {
        BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();

        validationID = _initializeValidatorRegistration(registrationInput, weight);

        // Update the security module weight
        uint64 newSecurityModuleWeight = $.securityModuleWeight[msg.sender] + weight;
        _updateSecurityModuleWeight(msg.sender, newSecurityModuleWeight);

        $.validatorSecurityModule[validationID] = msg.sender;

        return validationID;
    }

    /// @inheritdoc IBalancerValidatorManager
    function initializeEndValidation(
        bytes32 validationID
    ) external onlySecurityModule returns (Validator memory validator) {
        BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();

        // Ensure the validator weight is not being updated
        if ($.validatorPendingWeightUpdate[validationID] != 0) {
            revert BalancerValidatorManager__PendingWeightUpdate(validationID);
        }

        _checkValidatorSecurityModule(validationID, msg.sender);
        validator = _initializeEndValidation(validationID);

        // Update the security module weight
        uint64 newSecurityModuleWeight = $.securityModuleWeight[msg.sender] - validator.weight;
        _updateSecurityModuleWeight(msg.sender, newSecurityModuleWeight);

        return validator;
    }

    /// @inheritdoc IValidatorManager
    function completeEndValidation(
        uint32 messageIndex
    ) external {
        (bytes32 validationID, Validator memory validator) = _completeEndValidation(messageIndex);

        // Update the security module weight only if the validation was invalidated
        if (validator.status == ValidatorStatus.Invalidated) {
            BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();

            address securityModule = $.validatorSecurityModule[validationID];
            uint64 newSecurityModuleWeight =
                $.securityModuleWeight[securityModule] - validator.weight;
            _updateSecurityModuleWeight(securityModule, newSecurityModuleWeight);
        }
    }

    /// @inheritdoc IBalancerValidatorManager
    function initializeValidatorWeightUpdate(
        bytes32 validationID,
        uint64 newWeight
    ) external onlySecurityModule returns (Validator memory) {
        BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
        ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();

        // Check that the newWeight is greater than zero
        if (newWeight == 0) {
            revert BalancerValidatorManager__NewWeightIsZero();
        }

        // Ensure the validation period is active and that the validator is not already being updated
        // The initial validator set must have been set already to have active validators.
        Validator storage validator = vms._validationPeriods[validationID];
        if (validator.status != ValidatorStatus.Active) {
            revert InvalidValidatorStatus(validator.status);
        }
        if ($.validatorPendingWeightUpdate[validationID] != 0) {
            revert BalancerValidatorManager__PendingWeightUpdate(validationID);
        }

        _checkValidatorSecurityModule(validationID, msg.sender);
        uint64 oldWeight = validator.weight;
        (, bytes32 messageID) = _setValidatorWeight(validationID, newWeight);

        // Update the security module weight
        uint64 newSecurityModuleWeight = $.securityModuleWeight[msg.sender] + newWeight - oldWeight;
        _updateSecurityModuleWeight(msg.sender, newSecurityModuleWeight);

        $.validatorPendingWeightUpdate[validationID] = messageID;

        return validator;
    }

    /// @inheritdoc IBalancerValidatorManager
    function completeValidatorWeightUpdate(bytes32 validationID, uint32 messageIndex) external {
        BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
        ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();
        Validator storage validator = vms._validationPeriods[validationID];

        // Check that the validator is active and being updated
        if (validator.status != ValidatorStatus.Active) {
            revert InvalidValidatorStatus(validator.status);
        }
        if ($.validatorPendingWeightUpdate[validationID] == 0) {
            revert BalancerValidatorManager__NoPendingWeightUpdate(validationID);
        }

        // Unpack the Warp message
        (bytes32 messageValidationID, uint64 nonce,) = ValidatorMessages
            .unpackL1ValidatorWeightMessage(_getPChainWarpMessage(messageIndex).payload);

        if (validationID != messageValidationID) {
            revert InvalidValidationID(validationID);
        }
        if (validator.messageNonce < nonce) {
            revert BalancerValidatorManager__InvalidNonce(nonce);
        }

        delete $.validatorPendingWeightUpdate[validationID];
    }

    function resendValidatorWeightUpdate(
        bytes32 validationID
    ) external {
        BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
        ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();
        Validator storage validator = vms._validationPeriods[validationID];

        if (validator.status != ValidatorStatus.Active) {
            revert InvalidValidatorStatus(validator.status);
        }
        if ($.validatorPendingWeightUpdate[validationID] == 0) {
            revert BalancerValidatorManager__NoPendingWeightUpdate(validationID);
        }

        if (validator.messageNonce == 0) {
            revert InvalidValidationID(validationID);
        }

        // Submit the message to the Warp precompile.
        WARP_MESSENGER.sendWarpMessage(
            ValidatorMessages.packL1ValidatorWeightMessage(
                validationID, validator.messageNonce, validator.weight
            )
        );
    }

    /// @inheritdoc IBalancerValidatorManager
    function getChurnPeriodSeconds() external view returns (uint64 churnPeriodSeconds) {
        return _getChurnPeriodSeconds();
    }

    /// @inheritdoc IBalancerValidatorManager
    function getMaximumChurnPercentage() external view returns (uint64 maximumChurnPercentage) {
        ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();

        return vms._maximumChurnPercentage;
    }

    /// @inheritdoc IBalancerValidatorManager
    function getCurrentChurnPeriod()
        external
        view
        returns (ValidatorChurnPeriod memory churnPeriod)
    {
        ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();

        return vms._churnTracker;
    }

    /// @inheritdoc IBalancerValidatorManager
    function getSecurityModules() external view returns (address[] memory securityModules) {
        BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();

        return $.securityModules.keys();
    }

    /// @inheritdoc IBalancerValidatorManager
    function getSecurityModuleWeights(
        address securityModule
    ) external view returns (uint64 weight, uint64 maxWeight) {
        BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();

        weight = $.securityModuleWeight[securityModule];
        maxWeight = uint64($.securityModules.get(securityModule));

        return (weight, maxWeight);
    }

    /// @inheritdoc IBalancerValidatorManager
    function getL1ID() external view returns (bytes32 l1ID) {
        ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();
        return vms._l1ID;
    }

    /// @inheritdoc IBalancerValidatorManager
    function isValidatorPendingWeightUpdate(
        bytes32 validationID
    ) external view returns (bool) {
        BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();

        return $.validatorPendingWeightUpdate[validationID] != 0;
    }

    function _checkValidatorSecurityModule(
        bytes32 validationID,
        address securityModule
    ) internal view {
        BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();

        // If the validator has no associated security module, it is an initial validator
        // and can be managed by any security module
        if ($.validatorSecurityModule[validationID] == address(0)) {
            return;
        } else if ($.validatorSecurityModule[validationID] != securityModule) {
            revert BalancerValidatorManager__ValidatorNotBelongingToSecurityModule(
                validationID, securityModule
            );
        }
    }

    function _setUpSecurityModule(address securityModule, uint64 maxWeight) internal {
        BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
        uint64 currentWeight = $.securityModuleWeight[securityModule];

        if (maxWeight < currentWeight) {
            revert BalancerValidatorManager__SecurityModuleNewMaxWeightLowerThanCurrentWeight(
                securityModule, maxWeight, currentWeight
            );
        }

        if (maxWeight == 0) {
            if (!$.securityModules.remove(securityModule)) {
                revert BalancerValidatorManager__SecurityModuleNotRegistered(securityModule);
            }
        } else {
            $.securityModules.set(securityModule, uint256(maxWeight));
        }

        emit SetUpSecurityModule(securityModule, maxWeight);
    }

    function _updateSecurityModuleWeight(address securityModule, uint64 weight) internal {
        BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
        uint64 maxWeight = uint64($.securityModules.get(securityModule));

        if (weight > maxWeight) {
            revert BalancerValidatorManager__SecurityModuleMaxWeightExceeded(
                securityModule, weight, maxWeight
            );
        }

        $.securityModuleWeight[securityModule] = weight;
    }

    function _migrateValidators(
        address initialSecurityModule,
        bytes[] calldata migratedValidators
    ) internal {
        BalancerValidatorManagerStorage storage $ = _getBalancerValidatorManagerStorage();
        ValidatorManager.ValidatorManagerStorage storage vms = _getValidatorManagerStorage();

        // Add the migrated validators to the initial security module
        uint64 migratedValidatorsTotalWeight = 0;
        for (uint256 i = 0; i < migratedValidators.length; i++) {
            bytes32 validationID = registeredValidators(migratedValidators[i]);

            // Ensure validator hasn't already been migrated
            if ($.validatorSecurityModule[validationID] != address(0)) {
                revert BalancerValidatorManager__ValidatorAlreadyMigrated(validationID);
            }

            Validator storage validator = vms._validationPeriods[validationID];
            $.validatorSecurityModule[validationID] = initialSecurityModule;
            migratedValidatorsTotalWeight += validator.weight;
        }

        // Check that the migrated validators total weight equals the current L1 total weight
        if (migratedValidatorsTotalWeight != vms._churnTracker.totalWeight) {
            revert BalancerValidatorManager__MigratedValidatorsTotalWeightMismatch(
                migratedValidatorsTotalWeight, vms._churnTracker.totalWeight
            );
        }

        // Update the initial security module weight directly since we've already validated the max weight
        $.securityModuleWeight[initialSecurityModule] = migratedValidatorsTotalWeight;
    }
}

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

interface IOperatorRegistry {
    event RegisterOperator(address indexed operator);
    event SetMetadataURL(address indexed operator, string metadataURL);

    error OperatorRegistry__OperatorAlreadyRegistered();
    error OperatorRegistry__OperatorNotRegistered();

    /// @notice Register an operator with its metadata URL
    function registerOperator(
        string calldata metadataURL
    ) external;

    /**
     * @notice Check if an address is registered as an operator
     * @param operator The address to check
     * @return True if the address is registered as an operator, false otherwise
     */
    function isRegistered(
        address operator
    ) external view returns (bool);

    /**
     * @notice Get the operator at a specific index
     * @param index The index of the operator to get
     * @return The address of the operator at the specified index
     * @return The metadata URL of the operator at the specified index
     */
    function getOperatorAt(
        uint256 index
    ) external view returns (address, string memory);

    /**
     * @notice Get the total number of operators
     * @return Total number of operators
     */
    function totalOperators() external view returns (uint256);

    /**
     * @notice Get all operators
     * @return Array of all operators
     * @return Array of all operators' metadata URLs
     */
    function getAllOperators() external view returns (address[] memory, string[] memory);

    /**
     * @notice Set the metadata URL of an operator
     * @param metadataURL The new metadata URL
     */
    function setMetadataURL(
        string calldata metadataURL
    ) external;
}

File 8 of 50 : IVaultTokenized.sol
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

// import {IMigratableEntity} from "../common/IMigratableEntity.sol";

interface IVaultTokenized {
    error Vault__AlreadyClaimed();
    error Vault__AlreadySet();
    error Vault__DelegatorAlreadyInitialized();
    error Vault__DepositLimitReached();
    error Vault__InsufficientClaim();
    error Vault__InsufficientDeposit();
    error Vault__InsufficientRedemption();
    error Vault__InsufficientWithdrawal();
    error Vault__InvalidAccount();
    error Vault__InvalidCaptureEpoch();
    error Vault__InvalidClaimer();
    error Vault__InvalidCollateral();
    error Vault__InvalidDelegator();
    error Vault__InvalidEpoch();
    error Vault__InvalidEpochDuration();
    error Vault__InvalidLengthEpochs();
    error Vault__InvalidOnBehalfOf();
    error Vault__InvalidRecipient();
    error Vault__InvalidSlasher();
    error Vault__MissingRoles();
    error Vault__InconsistentRoles();
    error Vault__NotDelegator();
    error Vault__NotSlasher();
    error Vault__NotWhitelistedDepositor();
    error Vault__SlasherAlreadyInitialized();
    error Vault__TooMuchRedeem();
    error Vault__TooMuchWithdraw();
    error Vault__InvalidTimestamp();
    error Vault__NoPreviousEpoch();
    error Vault__MigrationNotImplemented();
    error Vault__InvalidFactory();
    
    // Structs
    /**
     * @notice Initial parameters needed for a vault deployment.
     * @param collateral vault's underlying collateral
     * @param burner vault's burner to issue debt to (e.g., 0xdEaD or some unwrapper contract)
     * @param epochDuration duration of the vault epoch (it determines sync points for withdrawals)
     * @param depositWhitelist if enabling deposit whitelist
     * @param isDepositLimit if enabling deposit limit
     * @param depositLimit deposit limit (maximum amount of the collateral that can be in the vault simultaneously)
     * @param defaultAdminRoleHolder address of the initial DEFAULT_ADMIN_ROLE holder
     * @param depositWhitelistSetRoleHolder address of the initial DEPOSIT_WHITELIST_SET_ROLE holder
     * @param depositorWhitelistRoleHolder address of the initial DEPOSITOR_WHITELIST_ROLE holder
     * @param isDepositLimitSetRoleHolder address of the initial IS_DEPOSIT_LIMIT_SET_ROLE holder
     * @param depositLimitSetRoleHolder address of the initial DEPOSIT_LIMIT_SET_ROLE holder
     * @param name name for the ERC20 tokenized vault
     * @param symbol symbol for the ERC20 tokenized vault
     */
    struct InitParams {
        address collateral;
        address burner;
        uint48 epochDuration;
        bool depositWhitelist;
        bool isDepositLimit;
        uint256 depositLimit;
        address defaultAdminRoleHolder;
        address depositWhitelistSetRoleHolder;
        address depositorWhitelistRoleHolder;
        address isDepositLimitSetRoleHolder;
        address depositLimitSetRoleHolder;
        string name;
        string symbol;
    }

    /**
     * @notice Get the factory's address.
     * @return address of the factory
     */
    function FACTORY() external view returns (address);

    /**
     * @notice Get the entity's version.
     * @return version of the entity
     * @dev Starts from 1.
     */
    function version() external view returns (uint64);

    /**
     * @notice Initialize this entity contract by using a given data and setting a particular version and owner.
     * @param initialVersion initial version of the entity
     * @param owner initial owner of the entity
     * @param data some data to use
     * @param delegatorFactory Address of the delegator factory.
     * @param slasherFactory Address of the slasher factory.
     *
     */
    function initialize(
        uint64 initialVersion,
        address owner,
        bytes calldata data,
        address delegatorFactory,
        address slasherFactory
    ) external;

    /**
     * @notice Migrate this entity to a particular newer version using a given data.
     * @param newVersion new version of the entity
     * @param data some data to use
     */
    function migrate(uint64 newVersion, bytes calldata data) external;

    /**
     * @notice Hints for an active balance.
     * @param activeSharesOfHint hint for the active shares of checkpoint
     * @param activeStakeHint hint for the active stake checkpoint
     * @param activeSharesHint hint for the active shares checkpoint
     */
    struct ActiveBalanceOfHints {
        bytes activeSharesOfHint;
        bytes activeStakeHint;
        bytes activeSharesHint;
    }

    // Events
    /**
     * @notice Emitted when a deposit is made.
     * @param depositor account that made the deposit
     * @param onBehalfOf account the deposit was made on behalf of
     * @param amount amount of the collateral deposited
     * @param shares amount of the active shares minted
     */
    event Deposit(address indexed depositor, address indexed onBehalfOf, uint256 amount, uint256 shares);

    /**
     * @notice Emitted when a withdrawal is made.
     * @param withdrawer account that made the withdrawal
     * @param claimer account that needs to claim the withdrawal
     * @param amount amount of the collateral withdrawn
     * @param burnedShares amount of the active shares burned
     * @param mintedShares amount of the epoch withdrawal shares minted
     */
    event Withdraw(
        address indexed withdrawer, address indexed claimer, uint256 amount, uint256 burnedShares, uint256 mintedShares
    );

    /**
     * @notice Emitted when a claim is made.
     * @param claimer account that claimed
     * @param recipient account that received the collateral
     * @param epoch epoch the collateral was claimed for
     * @param amount amount of the collateral claimed
     */
    event Claim(address indexed claimer, address indexed recipient, uint256 epoch, uint256 amount);

    /**
     * @notice Emitted when a batch claim is made.
     * @param claimer account that claimed
     * @param recipient account that received the collateral
     * @param epochs epochs the collateral was claimed for
     * @param amount amount of the collateral claimed
     */
    event ClaimBatch(address indexed claimer, address indexed recipient, uint256[] epochs, uint256 amount);

    /**
     * @notice Emitted when a slash happens.
     * @param amount amount of the collateral to slash
     * @param captureTimestamp time point when the stake was captured
     * @param slashedAmount real amount of the collateral slashed
     */
    event OnSlash(uint256 amount, uint48 captureTimestamp, uint256 slashedAmount);

    /**
     * @notice Emitted when a deposit whitelist status is enabled/disabled.
     * @param status if enabled deposit whitelist
     */
    event SetDepositWhitelist(bool status);

    /**
     * @notice Emitted when a depositor whitelist status is set.
     * @param account account for which the whitelist status is set
     * @param status if whitelisted the account
     */
    event SetDepositorWhitelistStatus(address indexed account, bool status);

    /**
     * @notice Emitted when a deposit limit status is enabled/disabled.
     * @param status if enabled deposit limit
     */
    event SetIsDepositLimit(bool status);

    /**
     * @notice Emitted when a deposit limit is set.
     * @param limit deposit limit (maximum amount of the collateral that can be in the vault simultaneously)
     */
    event SetDepositLimit(uint256 limit);

    /**
     * @notice Emitted when a delegator is set.
     * @param delegator vault's delegator to delegate the stake to networks and operators
     * @dev Can be set only once.
     */
    event SetDelegator(address indexed delegator);

    /**
     * @notice Emitted when a slasher is set.
     * @param slasher vault's slasher to provide a slashing mechanism to networks
     * @dev Can be set only once.
     */
    event SetSlasher(address indexed slasher);

    // Functions
    // Ex IVaultStorage
    /**
     * @notice Get the delegator factory's address.
     * @return address of the delegator factory
     */
    function DELEGATOR_FACTORY() external view returns (address);

    /**
     * @notice Get the slasher factory's address.
     * @return address of the slasher factory
     */
    function SLASHER_FACTORY() external view returns (address);

    /**
     * @notice Get a vault collateral.
     * @return address of the underlying collateral
     */
    function collateral() external view returns (address);

    /**
     * @notice Get a burner to issue debt to (e.g., 0xdEaD or some unwrapper contract).
     * @return address of the burner
     */
    function burner() external view returns (address);

    /**
     * @notice Get a delegator (it delegates the vault's stake to networks and operators).
     * @return address of the delegator
     */
    function delegator() external view returns (address);

    /**
     * @notice Get if the delegator is initialized.
     * @return if the delegator is initialized
     */
    function isDelegatorInitialized() external view returns (bool);

    /**
     * @notice Get a slasher (it provides networks a slashing mechanism).
     * @return address of the slasher
     */
    function slasher() external view returns (address);

    /**
     * @notice Get if the slasher is initialized.
     * @return if the slasher is initialized
     */
    function isSlasherInitialized() external view returns (bool);

    /**
     * @notice Get a time point of the epoch duration set.
     * @return time point of the epoch duration set
     */
    function epochDurationInit() external view returns (uint48);

    /**
     * @notice Get a duration of the vault epoch.
     * @return duration of the epoch
     */
    function epochDuration() external view returns (uint48);

    /**
     * @notice Get an epoch at a given timestamp.
     * @param timestamp time point to get the epoch at
     * @return epoch at the timestamp
     * @dev Reverts if the timestamp is less than the start of the epoch 0.
     */
    function epochAt(
        uint48 timestamp
    ) external view returns (uint256);

    /**
     * @notice Get a current vault epoch.
     * @return current epoch
     */
    function currentEpoch() external view returns (uint256);

    /**
     * @notice Get a start of the current vault epoch.
     * @return start of the current epoch
     */
    function currentEpochStart() external view returns (uint48);

    /**
     * @notice Get a start of the previous vault epoch.
     * @return start of the previous epoch
     * @dev Reverts if the current epoch is 0.
     */
    function previousEpochStart() external view returns (uint48);

    /**
     * @notice Get a start of the next vault epoch.
     * @return start of the next epoch
     */
    function nextEpochStart() external view returns (uint48);

    /**
     * @notice Get if the deposit whitelist is enabled.
     * @return if the deposit whitelist is enabled
     */
    function depositWhitelist() external view returns (bool);

    /**
     * @notice Get if a given account is whitelisted as a depositor.
     * @param account address to check
     * @return if the account is whitelisted as a depositor
     */
    function isDepositorWhitelisted(
        address account
    ) external view returns (bool);

    /**
     * @notice Get if the deposit limit is set.
     * @return if the deposit limit is set
     */
    function isDepositLimit() external view returns (bool);

    /**
     * @notice Get a deposit limit (maximum amount of the active stake that can be in the vault simultaneously).
     * @return deposit limit
     */
    function depositLimit() external view returns (uint256);

    /**
     * @notice Get a total number of active shares in the vault at a given timestamp using a hint.
     * @param timestamp time point to get the total number of active shares at
     * @param hint hint for the checkpoint index
     * @return total number of active shares at the timestamp
     */
    function activeSharesAt(uint48 timestamp, bytes memory hint) external view returns (uint256);

    /**
     * @notice Get a total number of active shares in the vault.
     * @return total number of active shares
     */
    function activeShares() external view returns (uint256);

    /**
     * @notice Get a total amount of active stake in the vault at a given timestamp using a hint.
     * @param timestamp time point to get the total active stake at
     * @param hint hint for the checkpoint index
     * @return total amount of active stake at the timestamp
     */
    function activeStakeAt(uint48 timestamp, bytes memory hint) external view returns (uint256);

    /**
     * @notice Get a total amount of active stake in the vault.
     * @return total amount of active stake
     */
    function activeStake() external view returns (uint256);

    /**
     * @notice Get a total number of active shares for a particular account at a given timestamp using a hint.
     * @param account account to get the number of active shares for
     * @param timestamp time point to get the number of active shares for the account at
     * @param hint hint for the checkpoint index
     * @return number of active shares for the account at the timestamp
     */
    function activeSharesOfAt(address account, uint48 timestamp, bytes memory hint) external view returns (uint256);

    /**
     * @notice Get a number of active shares for a particular account.
     * @param account account to get the number of active shares for
     * @return number of active shares for the account
     */
    function activeSharesOf(
        address account
    ) external view returns (uint256);

    /**
     * @notice Get a total amount of the withdrawals at a given epoch.
     * @param epoch epoch to get the total amount of the withdrawals at
     * @return total amount of the withdrawals at the epoch
     */
    function withdrawals(
        uint256 epoch
    ) external view returns (uint256);

    /**
     * @notice Get a total number of withdrawal shares at a given epoch.
     * @param epoch epoch to get the total number of withdrawal shares at
     * @return total number of withdrawal shares at the epoch
     */
    function withdrawalShares(
        uint256 epoch
    ) external view returns (uint256);

    /**
     * @notice Get a number of withdrawal shares for a particular account at a given epoch (zero if claimed).
     * @param epoch epoch to get the number of withdrawal shares for the account at
     * @param account account to get the number of withdrawal shares for
     * @return number of withdrawal shares for the account at the epoch
     */
    function withdrawalSharesOf(uint256 epoch, address account) external view returns (uint256);

    /**
     * @notice Get if the withdrawals are claimed for a particular account at a given epoch.
     * @param epoch epoch to check the withdrawals for the account at
     * @param account account to check the withdrawals for
     * @return if the withdrawals are claimed for the account at the epoch
     */
    function isWithdrawalsClaimed(uint256 epoch, address account) external view returns (bool);

    //ex IVault

    /**
     * @notice Check if the vault is fully initialized (a delegator and a slasher are set).
     * @return if the vault is fully initialized
     */
    function isInitialized() external view returns (bool);

    /**
     * @notice Get a total amount of the collateral that can be slashed.
     * @return total amount of the slashable collateral
     */
    function totalStake() external view returns (uint256);

    /**
     * @notice Get an active balance for a particular account at a given timestamp using hints.
     * @param account account to get the active balance for
     * @param timestamp time point to get the active balance for the account at
     * @param hints hints for checkpoints' indexes
     * @return active balance for the account at the timestamp
     */
    function activeBalanceOfAt(
        address account,
        uint48 timestamp,
        bytes calldata hints
    ) external view returns (uint256);

    /**
     * @notice Get an active balance for a particular account.
     * @param account account to get the active balance for
     * @return active balance for the account
     */
    function activeBalanceOf(
        address account
    ) external view returns (uint256);

    /**
     * @notice Get withdrawals for a particular account at a given epoch (zero if claimed).
     * @param epoch epoch to get the withdrawals for the account at
     * @param account account to get the withdrawals for
     * @return withdrawals for the account at the epoch
     */
    function withdrawalsOf(uint256 epoch, address account) external view returns (uint256);

    /**
     * @notice Get a total amount of the collateral that can be slashed for a given account.
     * @param account account to get the slashable collateral for
     * @return total amount of the account's slashable collateral
     */
    function slashableBalanceOf(
        address account
    ) external view returns (uint256);

    /**
     * @notice Deposit collateral into the vault.
     * @param onBehalfOf account the deposit is made on behalf of
     * @param amount amount of the collateral to deposit
     * @return depositedAmount real amount of the collateral deposited
     * @return mintedShares amount of the active shares minted
     */
    function deposit(
        address onBehalfOf,
        uint256 amount
    ) external returns (uint256 depositedAmount, uint256 mintedShares);

    /**
     * @notice Withdraw collateral from the vault (it will be claimable after the next epoch).
     * @param claimer account that needs to claim the withdrawal
     * @param amount amount of the collateral to withdraw
     * @return burnedShares amount of the active shares burned
     * @return mintedShares amount of the epoch withdrawal shares minted
     */
    function withdraw(address claimer, uint256 amount) external returns (uint256 burnedShares, uint256 mintedShares);

    /**
     * @notice Redeem collateral from the vault (it will be claimable after the next epoch).
     * @param claimer account that needs to claim the withdrawal
     * @param shares amount of the active shares to redeem
     * @return withdrawnAssets amount of the collateral withdrawn
     * @return mintedShares amount of the epoch withdrawal shares minted
     */
    function redeem(address claimer, uint256 shares) external returns (uint256 withdrawnAssets, uint256 mintedShares);

    /**
     * @notice Claim collateral from the vault.
     * @param recipient account that receives the collateral
     * @param epoch epoch to claim the collateral for
     * @return amount amount of the collateral claimed
     */
    function claim(address recipient, uint256 epoch) external returns (uint256 amount);

    /**
     * @notice Claim collateral from the vault for multiple epochs.
     * @param recipient account that receives the collateral
     * @param epochs epochs to claim the collateral for
     * @return amount amount of the collateral claimed
     */
    function claimBatch(address recipient, uint256[] calldata epochs) external returns (uint256 amount);

    /**
     * @notice Slash callback for burning collateral.
     * @param amount amount to slash
     * @param captureTimestamp time point when the stake was captured
     * @return slashedAmount real amount of the collateral slashed
     * @dev Only the slasher can call this function.
     */
    function onSlash(uint256 amount, uint48 captureTimestamp) external returns (uint256 slashedAmount);

    /**
     * @notice Enable/disable deposit whitelist.
     * @param status if enabling deposit whitelist
     * @dev Only a DEPOSIT_WHITELIST_SET_ROLE holder can call this function.
     */
    function setDepositWhitelist(
        bool status
    ) external;

    /**
     * @notice Set a depositor whitelist status.
     * @param account account for which the whitelist status is set
     * @param status if whitelisting the account
     * @dev Only a DEPOSITOR_WHITELIST_ROLE holder can call this function.
     */
    function setDepositorWhitelistStatus(address account, bool status) external;

    /**
     * @notice Enable/disable deposit limit.
     * @param status if enabling deposit limit
     * @dev Only a IS_DEPOSIT_LIMIT_SET_ROLE holder can call this function.
     */
    function setIsDepositLimit(
        bool status
    ) external;

    /**
     * @notice Set a deposit limit.
     * @param limit deposit limit (maximum amount of the collateral that can be in the vault simultaneously)
     * @dev Only a DEPOSIT_LIMIT_SET_ROLE holder can call this function.
     */
    function setDepositLimit(
        uint256 limit
    ) external;

    /**
     * @notice Set a delegator.
     * @param delegator vault's delegator to delegate the stake to networks and operators
     * @dev Can be set only once.
     */
    function setDelegator(
        address delegator
    ) external;

    /**
     * @notice Set a slasher.
     * @param slasher vault's slasher to provide a slashing mechanism to networks
     * @dev Can be set only once.
     */
    function setSlasher(
        address slasher
    ) external;

    /**
     * @notice Make a delegatecall from this contract to a given target contract with a particular data (always reverts with a return data).
     * @param target address of the contract to make a delegatecall to
     * @param data data to make a delegatecall with
     * @dev It allows to use this contract's storage on-chain.
     */
    function staticDelegateCall(address target, bytes calldata data) external;
}

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity 0.8.25;

import {
    IValidatorManager,
    Validator,
    ValidatorStatus,
    ValidatorRegistrationInput,
    PChainOwner
} from "@avalabs/teleporter/validator-manager/interfaces/IValidatorManager.sol";

/**
 * @title IAvalancheL1Middleware
 * @notice Manages operator registration, asset classes, stake accounting, and slashing for Avalanche L1
 */
interface IAvalancheL1Middleware {
    // Errors
    error AvalancheL1Middleware__ActiveSecondaryAssetCLass(uint256 assetClassId);
    error AvalancheL1Middleware__AssetClassNotActive(uint256 assetClassId);
    error AvalancheL1Middleware__AssetStillInUse(uint256 assetClassId);
    error AvalancheL1Middleware__AlreadyRebalanced(address operator, uint48 epoch);
    error AvalancheL1Middleware__WeightUpdateNotPending(bytes32 validationId);
    error AvalancheL1Middleware__CollateralNotInAssetClass(address collateral, uint96 assetClassId);
    error AvalancheL1Middleware__EpochError(uint48 epochStartTs);
    error AvalancheL1Middleware__MaxL1LimitZero();
    error AvalancheL1Middleware__NoSlasher();
    error AvalancheL1Middleware__NotEnoughFreeStakeSecondaryAssetClasses();
    error AvalancheL1Middleware__NodeNotActive();
    error AvalancheL1Middleware__NotEnoughFreeStake(uint256 newStake);
    error AvalancheL1Middleware__StakeTooHigh(uint256 newStake, uint256 maxStake);
    error AvalancheL1Middleware__StakeTooLow(uint256 newStake, uint256 minStake);
    error AvalancheL1Middleware__OperatorGracePeriodNotPassed(uint48 disabledTime, uint48 slashingWindow);
    error AvalancheL1Middleware__OperatorAlreadyRegistered(address operator);
    error AvalancheL1Middleware__OperatorNotOptedIn(address operator, address l1ValidatorManager);
    error AvalancheL1Middleware__OperatorNotRegistered(address operator);
    error AvalancheL1Middleware__SlashingWindowTooShort(uint48 slashingWindow, uint48 epochDuration);
    error AvalancheL1Middleware__TooBigSlashAmount();
    error AvalancheL1Middleware__NodeNotFound(bytes32 nodeId);
    error AvalancheL1Middleware__SecurityModuleCapacityNotEnough(uint256 securityModuleCapacity, uint256 minStake);
    error AvalancheL1Middleware__WeightUpdatePending(bytes32 validationID);
    error AvalancheL1Middleware__NodeStateNotUpdated(bytes32 validationID);
    error AvalancheL1Middleware__NotEpochUpdatePeriod(uint48 timeNow, uint48 epochUpdatePeriod);
    error AvalancheL1Middleware__NotImplemented();
    error AvalancheL1Middleware__NodePendingRemoval(bytes32 nodeId);
    error AvalancheL1Middleware__NodePendingUpdate(bytes32 nodeId);
    error AvalancheL1Middleware__ZeroAddress(string name);
    error AvalancheL1Middleware__InvalidScaleFactor();
    error AvalancheL1Middleware__ManualEpochUpdateRequired(uint48 epochsPending, uint48 maxAutoUpdates);
    error AvalancheL1Middleware__NoEpochsToProcess();
    error AvalancheL1Middleware__TooManyEpochsRequested(uint48 requested, uint48 pending);

    // Events
    /**
     * @notice Emitted when a node is added
     * @param operator The operator who added the node
     * @param nodeId The ID of the node
     * @param stake The stake assigned to the node
     * @param validationID The validation identifier from BalancerValidatorManager
     */
    event NodeAdded(address indexed operator, bytes32 indexed nodeId, uint256 stake, bytes32 indexed validationID);

    /**
     * @notice Emitted when a node is removed
     * @param operator The operator who removed the node
     * @param nodeId The ID of the node
     * @param validationID The validation identifier from BalancerValidatorManager
     */
    event NodeRemoved(address indexed operator, bytes32 indexed nodeId, bytes32 indexed validationID);

    /**
     * @notice Emitted when a single node's stake is updated
     * @param operator The operator who owns the node
     * @param nodeId The ID of the node
     * @param newStake The new stake of the node
     * @param validationID The validation identifier from BalancerValidatorManager
     */
    event NodeStakeUpdated(
        address indexed operator, bytes32 indexed nodeId, uint256 newStake, bytes32 indexed validationID
    );

    /**
     * @notice Emitted when the operator has leftover stake after rebalancing
     * @param operator The operator who has leftover stake
     * @param leftoverStake The amount of leftover stake
     */
    event OperatorHasLeftoverStake(address indexed operator, uint256 leftoverStake);

    /**
     * @notice Emitted when all node stakes are updated for an operator
     * @param operator The operator
     * @param newStake The total new stake for the operator
     */
    event AllNodeStakesUpdated(address indexed operator, uint256 newStake);

    /**
     * @notice Emitted when the Vault Manager is updated
     * @param oldVaultManager The old Vault Manager address
     * @param newVaultManager The new Vault Manager address
     */
    event VaultManagerUpdated(address indexed oldVaultManager, address indexed newVaultManager);


    /**
     * @notice Emitted when the node stake cache is manually processed
     * @param upToEpoch The epoch to process up to
     * @param epochsProcessedCount The number of epochs processed
     */
    event NodeStakeCacheManuallyProcessed(uint48 upToEpoch, uint48 epochsProcessedCount);

    /**
     * @dev Simple struct to return operator stake and key.
     */
    struct OperatorData {
        uint256 stake;
        bytes32 key;
    }
    // External functions
    /**
     * @notice Activates a secondary asset class
     * @param assetClassId The asset class ID to activate
     */

    function activateSecondaryAssetClass(
        uint256 assetClassId
    ) external;

    /**
     * @notice Deactivates a secondary asset class
     * @param assetClassId The asset class ID to deactivate
     */
    function deactivateSecondaryAssetClass(
        uint256 assetClassId
    ) external;

    /**
     * @notice Registers a new operator and enables it
     * @param operator The operator address
     */
    function registerOperator(
        address operator
    ) external;

    /**
     * @notice Disables an operator
     * @param operator The operator address
     */
    function disableOperator(
        address operator
    ) external;

    /**
     * @notice Enables an operator
     * @param operator The operator address
     */
    function enableOperator(
        address operator
    ) external;

    /**
     * @notice Removes an operator if grace period has passed
     * @param operator The operator address
     */
    function removeOperator(
        address operator
    ) external;

    /**
     * @notice Add a new node => create a new validator.
     * Check the new node stake also ensure security module capacity.
     * @param nodeId The node ID
     * @param blsKey The BLS key
     * @param registrationExpiry The Unix timestamp after which the reigistration is no longer valid on the P-Chain
     * @param remainingBalanceOwner The owner of a validator's remaining balance
     * @param disableOwner The owner of a validator's disable owner on the P-Chain
     * @param stakeAmount The initial stake of the node to be added(optional)
     */
    function addNode(
        bytes32 nodeId,
        bytes calldata blsKey,
        uint64 registrationExpiry,
        PChainOwner calldata remainingBalanceOwner,
        PChainOwner calldata disableOwner,
        uint256 stakeAmount
    ) external;

    /**
     * @notice Remove a node => remove a validator.
     * @param nodeId The node ID
     */
    function removeNode(
        bytes32 nodeId
    ) external;

    /**
     * @notice Rebalance node stakes once per epoch for an operator.
     * @param operator The operator address
     * @param limitStake The maximum stake adjustment (add or remove) allowed per node per call.
     */
    function forceUpdateNodes(address operator, uint256 limitStake) external;

    /**
     * @notice Update the stake of a validator.
     * @param nodeId The node ID.
     * @param stakeAmount The new stake.
     */
    function initializeValidatorStakeUpdate(bytes32 nodeId, uint256 stakeAmount) external;

    /**
     * @notice Finalize a pending validator registration
     * @param operator The operator address
     * @param nodeId The node ID
     * @param messageIndex The message index
     */
    function completeValidatorRegistration(address operator, bytes32 nodeId, uint32 messageIndex) external;

    /**
     * @notice Finalize a pending stake update
     * @param nodeId The node ID
     * @param messageIndex The message index
     */
    function completeStakeUpdate(bytes32 nodeId, uint32 messageIndex) external;

    /**
     * @notice Finalize a pending validator removal
     * @param messageIndex The message index
     */
    function completeValidatorRemoval(
        uint32 messageIndex
    ) external;

    /**
     * @notice Slashes an operator's stake
     * @param epoch The epoch of the slash
     * @param operator The operator being slashed
     * @param amount The slash amount
     * @param assetClassId The asset class ID
     */
    function slash(uint48 epoch, address operator, uint256 amount, uint96 assetClassId) external;

    /**
     * @notice Calculates and caches total stake for an epoch
     * @param epoch The epoch number
     * @param assetClassId The asset class ID
     * @return totalStake The total stake calculated and cached
     */
    function calcAndCacheStakes(uint48 epoch, uint96 assetClassId) external returns (uint256);

    /**
     * @notice Calculates and caches node stakes for all operators retroactively for all epochs
     */
    function calcAndCacheNodeStakeForAllOperators() external;

    /**
     * @notice Fetches the primary and secondary asset classes
     * @return primary The primary asset class
     * @return secondaries An array of secondary asset classes
     */
    function getActiveAssetClasses() external view returns (uint256 primary, uint256[] memory secondaries);

    /**
     * @notice Checks if the classId is active
     * @param assetClassId The asset class ID
     * @return bool True if active
     */
    function isActiveAssetClass(
        uint96 assetClassId
    ) external view returns (bool);

    /**
     * @notice Gets the start timestamp for a given epoch
     * @param epoch The epoch number
     * @return timestamp The start time of that epoch
     */
    function getEpochStartTs(
        uint48 epoch
    ) external view returns (uint48);

    /**
     * @notice Gets the epoch number at a given timestamp
     * @param timestamp The timestamp
     * @return epoch The epoch at that time
     */
    function getEpochAtTs(
        uint48 timestamp
    ) external view returns (uint48);

    /**
     * @notice Gets the current epoch based on the current block time
     * @return epoch The current epoch
     */
    function getCurrentEpoch() external view returns (uint48);

    /**
     * @notice Returns an operator's stake at a given epoch for a specific asset class
     * @param operator The operator address
     * @param epoch The epoch number
     * @param assetClassId The asset class ID
     * @return stake The operator's stake
     */
    function getOperatorStake(address operator, uint48 epoch, uint96 assetClassId) external view returns (uint256);

    /**
     * @notice Returns total stake across all operators in a specific epoch
     * @param epoch The epoch number
     * @param assetClassId The asset class ID
     * @return The total stake in that epoch
     */
    function getTotalStake(uint48 epoch, uint96 assetClassId) external view returns (uint256);

    /**
     * @notice Returns all operators
     */
    function getAllOperators() external view returns (address[] memory);

    /**
     * @notice Returns the cached stake for a given node in the specified epoch, based on its Validation ID.
     * @param epoch The target Not enough free stake to add nodeepoch.
     * @param validationId The node ID.
     * @return The node stake from the cache.
     */
    function getNodeStake(uint48 epoch, bytes32 validationId) external view returns (uint256);

    /**
     * @notice Returns the current epoch number
     * @param operator The operator address
     * @param epoch The epoch number
     * @return activeNodeIds The list of nodes
     */
    function getActiveNodesForEpoch(address operator, uint48 epoch) external view returns (bytes32[] memory);

    /**
     * @notice Returns the available stake for an operator
     * @param operator The operator address
     * @return The available stake
     */
    function getOperatorAvailableStake(
        address operator
    ) external view returns (uint256);

    /**
     * @notice Summation of node stakes from the nodeStakeCache.
     * @param operator The operator address.
     * @return registeredStake The sum of node stakes.
     */
    function getOperatorUsedStakeCached(
        address operator
    ) external view returns (uint256);

    /**
     * @notice Returns the Vault Manager address associated to this middleware
     * @return Address Vault Manager
     */
    function getVaultManager() external view returns (address);

    /**
     * @notice Returns the true active stake for an operator in a given epoch and asset class
     * @param epoch The epoch number
     * @param operator The operator address
     * @param assetClass The asset class ID
     * @return The true active stake
     */
    function getOperatorUsedStakeCachedPerEpoch(
        uint48 epoch,
        address operator,
        uint96 assetClass
    ) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

interface IOptInService {
    error OptInService__AlreadyOptedIn();
    error OptInService__ExpiredSignature();
    error OptInService__InvalidSignature();
    error OptInService__NotOptedIn();
    error OptInService__NotWhereRegistered();
    error OptInService__NotWho();
    error OptInService__OptOutCooldown();
    error OptInService__NotWhereEntity();

    /**
     * @notice Emitted when a "who" opts into a "where" entity.
     * @param who address of the "who"
     * @param where address of the "where" entity
     */
    event OptIn(address indexed who, address indexed where);

    /**
     * @notice Emitted when a "who" opts out from a "where" entity.
     * @param who address of the "who"
     * @param where address of the "where" entity
     */
    event OptOut(address indexed who, address indexed where);

    /**
     * @notice Emitted when the nonce of a "who" to a "where" entity is increased.
     * @param who address of the "who"
     * @param where address of the "where" entity
     */
    event IncreaseNonce(address indexed who, address indexed where);

    /**
     * @notice Get the "who" registry's address.
     * @return address of the "who" registry
     */
    function WHO_REGISTRY() external view returns (address);

    /**
     * @notice Get the address of the registry where to opt-in.
     * @return address of the "where" registry
     */
    function WHERE_REGISTRY() external view returns (address);

    /**
     * @notice Get if a given "who" is opted-in to a particular "where" entity at a given timestamp using a hint.
     * @param who address of the "who"
     * @param where address of the "where" entity
     * @param timestamp time point to get if the "who" is opted-in at
     * @param hint hint for the checkpoint index
     * @return if the "who" is opted-in at the given timestamp
     */
    function isOptedInAt(
        address who,
        address where,
        uint48 timestamp,
        bytes calldata hint
    ) external view returns (bool);

    /**
     * @notice Check if a given "who" is opted-in to a particular "where" entity.
     * @param who address of the "who"
     * @param where address of the "where" entity
     * @return if the "who" is opted-in
     */
    function isOptedIn(address who, address where) external view returns (bool);

    /**
     * @notice Get the nonce of a given "who" to a particular "where" entity.
     * @param who address of the "who"
     * @param where address of the "where" entity
     * @return nonce
     */
    function nonces(address who, address where) external view returns (uint256);

    /**
     * @notice Opt-in a calling "who" to a particular "where" entity.
     * @param where address of the "where" entity
     */
    function optIn(
        address where
    ) external;

    /**
     * @notice Opt-in a "who" to a particular "where" entity with a signature.
     * @param who address of the "who"
     * @param where address of the "where" entity
     * @param deadline time point until the signature is valid (inclusively)
     * @param signature signature of the "who"
     */
    function optIn(address who, address where, uint48 deadline, bytes calldata signature) external;

    /**
     * @notice Opt-out a calling "who" from a particular "where" entity.
     * @param where address of the "where" entity
     */
    function optOut(
        address where
    ) external;

    /**
     * @notice Opt-out a "who" from a particular "where" entity with a signature.
     * @param who address of the "who"
     * @param where address of the "where" entity
     * @param deadline time point until the signature is valid (inclusively)
     * @param signature signature of the "who"
     */
    function optOut(address who, address where, uint48 deadline, bytes calldata signature) external;

    /**
     * @notice Increase the nonce of a given "who" to a particular "where" entity.
     * @param where address of the "where" entity
     * @dev It can be used to invalidate a given signature.
     */
    function increaseNonce(
        address where
    ) external;
}

// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import {IAssetClassRegistry} from "../../interfaces/middleware/IAssetClassRegistry.sol";

abstract contract AssetClassRegistry is IAssetClassRegistry, Ownable {
    using EnumerableSet for EnumerableSet.AddressSet;
    using EnumerableSet for EnumerableSet.UintSet;

    struct AssetClass {
        EnumerableSet.AddressSet assets;
        uint256 minValidatorStake;
        uint256 maxValidatorStake;
    }

    EnumerableSet.UintSet internal assetClassIds;
    mapping(uint256 => AssetClass) internal assetClasses;

    constructor(
        address initialOwner
    ) Ownable(initialOwner) {}

    /// @inheritdoc IAssetClassRegistry
    function addAssetClass(
        uint256 assetClassId,
        uint256 minValidatorStake,
        uint256 maxValidatorStake,
        address initialAsset
    ) external onlyOwner {
        _addAssetClass(assetClassId, minValidatorStake, maxValidatorStake, initialAsset);
    }

    /// @inheritdoc IAssetClassRegistry
    function addAssetToClass(uint256 assetClassId, address asset) external onlyOwner {
        if (!assetClassIds.contains(assetClassId)) {
            revert AssetClassRegistry__AssetClassNotFound();
        }
        if (asset == address(0)) {
            revert AssetClassRegistry__InvalidAsset();
        }

        _addAssetToClass(assetClassId, asset);
    }

    /// @inheritdoc IAssetClassRegistry
    function removeAssetFromClass(uint256 assetClassId, address asset) public virtual onlyOwner {
        _removeAssetFromClass(assetClassId, asset);
    }

    /// @inheritdoc IAssetClassRegistry
    function removeAssetClass(
        uint256 assetClassId
    ) public virtual onlyOwner {
        _removeAssetClass(assetClassId);
    }

    /// @inheritdoc IAssetClassRegistry
    function getClassAssets(
        uint256 assetClassId
    ) external view returns (address[] memory) {
        if (!assetClassIds.contains(assetClassId)) {
            revert AssetClassRegistry__AssetClassNotFound();
        }
        return assetClasses[assetClassId].assets.values();
    }

    /// @inheritdoc IAssetClassRegistry
    function getClassStakingRequirements(
        uint256 assetClassId
    ) external view returns (uint256 minStake, uint256 maxStake) {
        if (!assetClassIds.contains(assetClassId)) {
            revert AssetClassRegistry__AssetClassNotFound();
        }
        AssetClass storage cls = assetClasses[assetClassId];
        return (cls.minValidatorStake, cls.maxValidatorStake);
    }

    function _addAssetClass(
        uint256 assetClassId,
        uint256 minValidatorStake,
        uint256 maxValidatorStake,
        address initialAsset
    ) internal {
        if (initialAsset == address(0)) {
            revert AssetClassRegistry__InvalidAsset();
        }
        if (assetClassId == 1 && minValidatorStake > maxValidatorStake) {
            revert AssetClassRegistry__InvalidStakingRequirements();
        }

        bool added = assetClassIds.add(assetClassId);
        if (!added) {
            revert AssetClassRegistry__AssetClassAlreadyExists();
        }

        AssetClass storage cls = assetClasses[assetClassId];
        cls.minValidatorStake = minValidatorStake;
        cls.maxValidatorStake = maxValidatorStake;

        emit AssetClassAdded(assetClassId, minValidatorStake, maxValidatorStake);

        _addAssetToClass(assetClassId, initialAsset);
    }

    function _addAssetToClass(uint256 assetClassId, address asset) internal {
        AssetClass storage cls = assetClasses[assetClassId];

        bool added = cls.assets.add(asset);
        if (!added) {
            revert AssetClassRegistry__AssetAlreadyRegistered();
        }

        emit AssetAdded(assetClassId, asset);
    }

    function _removeAssetFromClass(uint256 assetClassId, address asset) internal {
        AssetClass storage cls = assetClasses[assetClassId];
        bool assetFound = cls.assets.remove(asset);
        
        if (!assetFound) {
            if (!assetClassIds.contains(assetClassId)) {
                revert AssetClassRegistry__AssetClassNotFound();
            }
            revert AssetClassRegistry__AssetNotFound();
        }

        emit AssetRemoved(assetClassId, asset);
    }

    function _removeAssetClass(
        uint256 assetClassId
    ) internal {
        if (assetClassId == 1) {
            revert AssetClassRegistry__AssetIsPrimaryAssetClass(assetClassId);
        }

        if (assetClasses[assetClassId].assets.length() != 0) {
            revert AssetClassRegistry__AssetsStillExist();
        }

        bool removed = assetClassIds.remove(assetClassId);
        if (!removed) {
            revert AssetClassRegistry__AssetClassNotFound();
        }

        delete assetClasses[assetClassId];

        emit AssetClassRemoved(assetClassId);
    }

    function isAssetInClass(uint256 assetClassId, address asset) external view returns (bool) {
        if (!assetClassIds.contains(assetClassId)) {
            revert AssetClassRegistry__AssetClassNotFound();
        }
        return assetClasses[assetClassId].assets.contains(asset);
    }

    function getAssetClassIds() external view returns (uint96[] memory) {
        uint256[] memory ids = assetClassIds.values();
        uint96[] memory assetClassIDs = new uint96[](ids.length);
        for (uint256 i = 0; i < ids.length; i++) {
            assetClassIDs[i] = uint96(ids[i]);
        }
        return assetClassIDs;
    }

    receive() external payable {}
}

// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity 0.8.25;

import {Time} from "@openzeppelin/contracts/utils/types/Time.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";

import {IMiddlewareVaultManager} from "../../interfaces/middleware/IMiddlewareVaultManager.sol";
import {IAvalancheL1Middleware} from "../../interfaces/middleware/IAvalancheL1Middleware.sol";
import {IRegistry} from "../../interfaces/common/IRegistry.sol";
import {IEntity} from "../../interfaces/common/IEntity.sol";
import {IVaultTokenized} from "../../interfaces/vault/IVaultTokenized.sol";
import {BaseDelegator} from "../../contracts/delegator/BaseDelegator.sol";
import {ISlasher} from "../../interfaces/slasher/ISlasher.sol";
import {IVetoSlasher} from "../../interfaces/slasher/IVetoSlasher.sol";

import {MapWithTimeData} from "./libraries/MapWithTimeData.sol";
import {AvalancheL1Middleware} from "./AvalancheL1Middleware.sol";

contract MiddlewareVaultManager is IMiddlewareVaultManager, Ownable {
    using EnumerableMap for EnumerableMap.AddressToUintMap;
    using MapWithTimeData for EnumerableMap.AddressToUintMap;

    mapping(address => uint96) public vaultToAssetClass;
    EnumerableMap.AddressToUintMap private vaults;

    address public immutable VAULT_REGISTRY;
    AvalancheL1Middleware public immutable middleware;

    uint48 private constant INSTANT_SLASHER_TYPE = 0;
    uint48 private constant VETO_SLASHER_TYPE = 1;

    constructor(address vaultRegistry, address owner, address middlewareAddress) Ownable(owner) {
        if (vaultRegistry == address(0)) {
            revert AvalancheL1Middleware__ZeroAddress("vaultRegistry");
        }
        if (middlewareAddress == address(0)) {
            revert AvalancheL1Middleware__ZeroAddress("middlewareAddress");
        }
        VAULT_REGISTRY = vaultRegistry;
        middleware = AvalancheL1Middleware(payable(middlewareAddress));
    }

    /**
     * @notice Registers a vault to a specific asset class, sets the max stake.
     * @param vault The vault address
     * @param assetClassId The asset class ID for that vault
     * @param vaultMaxL1Limit The maximum stake allowed for this vault
     */
    function registerVault(address vault, uint96 assetClassId, uint256 vaultMaxL1Limit) external onlyOwner {
        if (vaultMaxL1Limit == 0) {
            revert AvalancheL1Middleware__ZeroVaultMaxL1Limit();
        }
        if (vaults.contains(vault)) {
            revert AvalancheL1Middleware__VaultAlreadyRegistered();
        }

        uint48 vaultEpoch = IVaultTokenized(vault).epochDuration();
        address slasher = IVaultTokenized(vault).slasher();
        if (slasher != address(0) && IEntity(slasher).TYPE() == VETO_SLASHER_TYPE) {
            vaultEpoch -= IVetoSlasher(slasher).vetoDuration();
        }
        if (vaultEpoch < middleware.SLASHING_WINDOW()) {
            revert AvalancheL1Middleware__VaultEpochTooShort();
        }

        vaultToAssetClass[vault] = assetClassId;
        _setVaultMaxL1Limit(vault, assetClassId, vaultMaxL1Limit);

        vaults.add(vault);
        vaults.enable(vault);
    }

    /**
     * @notice Updates a vault's max L1 stake limit. Disables or enables the vault based on the new limit
     * @param vault The vault address
     * @param assetClassId The asset class ID
     * @param vaultMaxL1Limit The new maximum stake
     */
    function updateVaultMaxL1Limit(address vault, uint96 assetClassId, uint256 vaultMaxL1Limit) external onlyOwner {
        if (!vaults.contains(vault)) {
            revert AvalancheL1Middleware__NotVault(vault);
        }
        if (vaultToAssetClass[vault] != assetClassId) {
            revert AvalancheL1Middleware__WrongVaultAssetClass();
        }

        _setVaultMaxL1Limit(vault, assetClassId, vaultMaxL1Limit);

        if (vaultMaxL1Limit == 0) {
            vaults.disable(vault);
        } else {
            vaults.enable(vault);
        }
    }

    /**
     * @notice Removes a vault if the grace period has passed
     * @param vault The vault address
     */
    function removeVault(
        address vault
    ) external onlyOwner {
        if (!vaults.contains(vault)) {
            revert AvalancheL1Middleware__NotVault(vault);
        }

        (, uint48 disabledTime) = vaults.getTimes(vault);
        if (disabledTime == 0) {
            revert AvalancheL1Middleware__VaultNotDisabled();
        }

        if (disabledTime + middleware.SLASHING_WINDOW() > Time.timestamp()) {
            revert AvalancheL1Middleware__VaultGracePeriodNotPassed();
        }

        // Remove from vaults and clear mapping
        vaults.remove(vault);
        delete vaultToAssetClass[vault];
    }

    /**
     * @notice Sets a vault's max L1 stake limit
     * @param vault The vault address
     * @param assetClassId The asset class ID
     * @param amount The new maximum stake
     */
    function _setVaultMaxL1Limit(address vault, uint96 assetClassId, uint256 amount) internal onlyOwner {
        if (!IRegistry(VAULT_REGISTRY).isEntity(vault)) {
            revert AvalancheL1Middleware__NotVault(vault);
        }
        if (!middleware.isActiveAssetClass(assetClassId)) {
            revert IAvalancheL1Middleware.AvalancheL1Middleware__AssetClassNotActive(assetClassId);
        }
        address vaultCollateral = IVaultTokenized(vault).collateral();
        if (!middleware.isAssetInClass(assetClassId, vaultCollateral)) {
            revert IAvalancheL1Middleware.AvalancheL1Middleware__CollateralNotInAssetClass(
                vaultCollateral, assetClassId
            );
        }
        address delegator = IVaultTokenized(vault).delegator();
        BaseDelegator(delegator).setMaxL1Limit(middleware.L1_VALIDATOR_MANAGER(), assetClassId, amount);
    }

    function slashVault() external pure {
        revert AvalancheL1Middleware__SlasherNotImplemented();
    }

    function getVaultCount() external view returns (uint256) {
        return vaults.length();
    }

    function getVaultAtWithTimes(
        uint256 index
    ) external view returns (address vault, uint48 enabledTime, uint48 disabledTime) {
        return vaults.atWithTimes(index);
    }

    function getVaultAssetClass(
        address vault
    ) external view returns (uint96) {
        return vaultToAssetClass[vault];
    }

    function getVaults(
        uint48 epoch
    ) external view returns (address[] memory) {
        uint256 vaultCount = vaults.length();
        uint48 epochStart = middleware.getEpochStartTs(epoch);

        uint256 activeCount = 0;
        for (uint256 i = 0; i < vaultCount; i++) {
            (address vault, uint48 enabledTime, uint48 disabledTime) = vaults.atWithTimes(i);
            if (_wasActiveAt(enabledTime, disabledTime, epochStart)) {
                activeCount++;
            }
        }

        address[] memory activeVaults = new address[](activeCount);
        uint256 activeIndex = 0;
        for (uint256 i = 0; i < vaultCount; i++) {
            (address vault, uint48 enabledTime, uint48 disabledTime) = vaults.atWithTimes(i);
            if (_wasActiveAt(enabledTime, disabledTime, epochStart)) {
                activeVaults[activeIndex] = vault;
                activeIndex++;
            }
        }

        return activeVaults;
    }

    function _wasActiveAt(uint48 enabledTime, uint48 disabledTime, uint48 timestamp) private pure returns (bool) {
        return enabledTime != 0 && enabledTime <= timestamp && (disabledTime == 0 || disabledTime >= timestamp);
    }
}

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.25;

import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol";
import {Time} from "@openzeppelin/contracts/utils/types/Time.sol";
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";

library MapWithTimeData {
    using EnumerableMap for EnumerableMap.AddressToUintMap;

    error MapWithTimeData__AlreadyAdded();
    error MapWithTimeData__NotEnabled();
    error MapWithTimeData__AlreadyEnabled();
    error MapWithTimeData__AlreadyDisabled();

    function add(EnumerableMap.AddressToUintMap storage self, address addr) internal {
        if (!self.set(addr, uint256(0))) {
            revert MapWithTimeData__AlreadyAdded();
        }
    }

    function disable(EnumerableMap.AddressToUintMap storage self, address addr) internal {
        uint256 value = self.get(addr);
        uint48 enabledTime = uint48(value);
        uint48 disabledTime = uint48(value >> 48);

        if (enabledTime == 0) {
            revert MapWithTimeData__NotEnabled();
        }

        if (disabledTime != 0) {
            revert MapWithTimeData__AlreadyDisabled();
        }

        value |= uint256(Time.timestamp()) << 48;
        self.set(addr, value);
    }

    function enable(EnumerableMap.AddressToUintMap storage self, address addr) internal {
        uint256 value = self.get(addr);

        if (uint48(value) != 0 && uint48(value >> 48) == 0) {
            revert MapWithTimeData__AlreadyEnabled();
        }

        value = uint256(Time.timestamp());
        self.set(addr, value);
    }

    function atWithTimes(
        EnumerableMap.AddressToUintMap storage self,
        uint256 idx
    ) internal view returns (address key, uint48 enabledTime, uint48 disabledTime) {
        uint256 value;
        (key, value) = self.at(idx);
        enabledTime = uint48(value);
        disabledTime = uint48(value >> 48);
    }

    function getTimes(
        EnumerableMap.AddressToUintMap storage self,
        address addr
    ) internal view returns (uint48 enabledTime, uint48 disabledTime) {
        uint256 value = self.get(addr);
        enabledTime = uint48(value);
        disabledTime = uint48(value >> 48);
    }
}

// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity 0.8.25;

library StakeConversion {
    error MiddlewareUtils__OverflowInStakeToWeight();

    /**
     * @notice Convert a full 256-bit stake amount into a 64-bit weight
     * @dev Anything < WEIGHT_SCALE_FACTOR becomes 0
     */
    function stakeToWeight(uint256 stakeAmount, uint256 scaleFactor) internal pure returns (uint64) {
        uint256 weight = stakeAmount / scaleFactor;
        if (weight > type(uint64).max) {
            revert MiddlewareUtils__OverflowInStakeToWeight();
        }
        return uint64(weight);
    }

    /**
     * @notice Convert a 64-bit weight back into its 256-bit stake amount
     */
    function weightToStake(uint64 weight, uint256 scaleFactor) internal pure returns (uint256) {
        return uint256(weight) * scaleFactor;
    }

    /**
     * @notice Remove the node from the dynamic array (swap and pop).
     * @dev Matches logic from _removeNodeFromArray() unchanged.
     */
    function removeNodeFromArray(bytes32[] storage arr, bytes32 nodeId) internal {
        uint256 arrLength = arr.length;
        for (uint256 i = 0; i < arrLength; i++) {
            if (arr[i] == nodeId) {
                uint256 lastIndex;
                unchecked {
                    lastIndex = arrLength - 1;
                }
                if (i != lastIndex) {
                    arr[i] = arr[lastIndex];
                }
                arr.pop();
                break;
            }
        }
    }
}

// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity 0.8.25;

import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";

import {IBaseDelegator} from "../../interfaces/delegator/IBaseDelegator.sol";
import {IDelegatorHook} from "../../interfaces/delegator/IDelegatorHook.sol";
import {IOptInService} from "../../interfaces/service/IOptInService.sol";
import {IRegistry} from "../../interfaces/common/IRegistry.sol";
import {IVaultTokenized} from "../../interfaces/vault/IVaultTokenized.sol";
import {IVaultFactory} from "../../interfaces/IVaultFactory.sol";
import {IL1Registry} from "../../interfaces/IL1Registry.sol";
import {IEntity} from "../../interfaces/common/IEntity.sol";

import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

abstract contract BaseDelegator is AccessControlUpgradeable, ReentrancyGuardUpgradeable, IBaseDelegator, ERC165 {
    using ERC165Checker for address;

    /**
     * @inheritdoc IBaseDelegator
     */
    address public immutable FACTORY;

    /**
     * @inheritdoc IBaseDelegator
     */
    uint64 public immutable TYPE;

    /**
     * @inheritdoc IBaseDelegator
     */
    uint256 public constant HOOK_GAS_LIMIT = 250_000;

    /**
     * @inheritdoc IBaseDelegator
     */
    uint256 public constant HOOK_RESERVE = 20_000;

    /**
     * @inheritdoc IBaseDelegator
     */
    bytes32 public constant HOOK_SET_ROLE = keccak256("HOOK_SET_ROLE");

    /**
     * @inheritdoc IBaseDelegator
     */
    address public immutable L1_REGISTRY;

    /**
     * @inheritdoc IBaseDelegator
     */
    address public immutable VAULT_FACTORY;

    /**
     * @inheritdoc IBaseDelegator
     */
    address public immutable OPERATOR_VAULT_OPT_IN_SERVICE;

    /**
     * @inheritdoc IBaseDelegator
     */
    address public immutable OPERATOR_L1_OPT_IN_SERVICE;

    /**
     * @inheritdoc IBaseDelegator
     */
    address public vault;

    /**
     * @inheritdoc IBaseDelegator
     */
    address public hook;

    /**
     * @inheritdoc IBaseDelegator
     */
    mapping(address => mapping(uint96 => uint256)) public maxL1Limit;

    constructor(
        address l1Registry,
        address vaultFactory,
        address operatorVaultOptInService,
        address operatorL1OptInService,
        address delegatorFactory,
        uint64 entityType
    ) {
        if (l1Registry == address(0)) {
            revert BaseDelegator__ZeroAddress("l1Registry");
        }
        if (vaultFactory == address(0)) {
            revert BaseDelegator__ZeroAddress("vaultFactory");
        }
        if (operatorVaultOptInService == address(0)) {
            revert BaseDelegator__ZeroAddress("operatorVaultOptInService");
        }
        if (operatorL1OptInService == address(0)) {
            revert BaseDelegator__ZeroAddress("operatorL1OptInService");
        }
        if (delegatorFactory == address(0)) {
            revert BaseDelegator__ZeroAddress("delegatorFactory");
        }
        _disableInitializers();
        L1_REGISTRY = l1Registry;
        VAULT_FACTORY = vaultFactory;
        OPERATOR_VAULT_OPT_IN_SERVICE = operatorVaultOptInService;
        OPERATOR_L1_OPT_IN_SERVICE = operatorL1OptInService;
        FACTORY = delegatorFactory;
        TYPE = entityType;
    }

    /**
     * @inheritdoc IBaseDelegator
     */
    function VERSION() external pure returns (uint64) {
        return 1;
    }

    /**
     * @inheritdoc IBaseDelegator
     */
    function stakeAt(
        address l1,
        uint96 assetClass,
        address operator,
        uint48 timestamp,
        bytes memory hints
    ) public view returns (uint256) {
        (uint256 stake_, bytes memory baseHints) = _stakeAt(l1, assetClass, operator, timestamp, hints);
        StakeBaseHints memory stakeBaseHints;
        if (baseHints.length > 0) {
            stakeBaseHints = abi.decode(baseHints, (StakeBaseHints));
        }

        if (
            stake_ == 0
                || !IOptInService(OPERATOR_VAULT_OPT_IN_SERVICE).isOptedInAt(
                    operator, vault, timestamp, stakeBaseHints.operatorVaultOptInHint
                )
                || !IOptInService(OPERATOR_L1_OPT_IN_SERVICE).isOptedInAt(
                    operator, l1, timestamp, stakeBaseHints.operatorL1OptInHint
                )
        ) {
            return 0;
        }

        return stake_;
    }

    /**
     * @inheritdoc IBaseDelegator
     */
    function stake(address l1, uint96 assetClass, address operator) external view returns (uint256) {
        if (
            !IOptInService(OPERATOR_VAULT_OPT_IN_SERVICE).isOptedIn(operator, vault)
                || !IOptInService(OPERATOR_L1_OPT_IN_SERVICE).isOptedIn(operator, l1)
        ) {
            return 0;
        }

        return _stake(l1, assetClass, operator);
    }
    /**
     * @inheritdoc IBaseDelegator
     */

    function setMaxL1Limit(address l1, uint96 assetClass, uint256 amount) external nonReentrant {
        if (!IL1Registry(L1_REGISTRY).isRegistered(l1)) {
            revert BaseDelegator__NotL1();
        }

        // Check if the middleware is registered and matches the caller
        bool isRegisteredWithMiddleware = IL1Registry(L1_REGISTRY).isRegisteredWithMiddleware(l1, msg.sender);
        if (!isRegisteredWithMiddleware) {
            revert BaseDelegator__NotAuthorizedMiddleware();
        }

        uint256 currentLimit = maxL1Limit[l1][assetClass];
        if (currentLimit == amount) {
            revert BaseDelegator__AlreadySet();
        }

        maxL1Limit[l1][assetClass] = amount;

        _setMaxL1Limit(l1, assetClass, amount);

        emit SetMaxL1Limit(l1, assetClass, amount);
    }

    /**
     * @inheritdoc IBaseDelegator
     */
    function setHook(
        address hook_
    ) external nonReentrant onlyRole(HOOK_SET_ROLE) {
        if (hook == hook_) {
            revert BaseDelegator__AlreadySet();
        }

        hook = hook_;

        emit SetHook(hook_);
    }

    /**
     * @inheritdoc IBaseDelegator
     */
    function onSlash(
        address l1,
        uint96 assetClass,
        address operator,
        uint256 amount,
        uint48 captureTimestamp,
        bytes memory data
    ) external nonReentrant {
        if (msg.sender != IVaultTokenized(vault).slasher()) {
            revert IVaultTokenized.Vault__NotSlasher();
        }

        address hook_ = hook;
        if (hook_ != address(0)) {
            bytes memory calldata_ =
                abi.encodeCall(IDelegatorHook.onSlash, (l1, assetClass, operator, amount, captureTimestamp, data));

            if (gasleft() < HOOK_RESERVE + HOOK_GAS_LIMIT * 64 / 63) {
                revert BaseDelegator__InsufficientHookGas();
            }

            assembly ("memory-safe") {
                pop(call(HOOK_GAS_LIMIT, hook_, 0, add(calldata_, 0x20), mload(calldata_), 0, 0))
            }
        }

        emit OnSlash(l1, assetClass, operator, amount, captureTimestamp);
    }

    /**
     * @inheritdoc IBaseDelegator
     */
    function initialize(
        bytes calldata data
    ) external initializer {
        _initialize(data);
    }

    function _initialize(
        bytes calldata data
    ) internal {
        (address vault_, bytes memory data_) = abi.decode(data, (address, bytes));

        if (!IRegistry(VAULT_FACTORY).isEntity(vault_)) {
            revert BaseDelegator__NotVault();
        }

        __AccessControl_init();
        __ReentrancyGuard_init();

        vault = vault_;

        BaseParams memory baseParams = __initialize(vault_, data_);

        hook = baseParams.hook;

        if (baseParams.defaultAdminRoleHolder != address(0)) {
            _grantRole(DEFAULT_ADMIN_ROLE, baseParams.defaultAdminRoleHolder);
        }
        if (baseParams.hookSetRoleHolder != address(0)) {
            _grantRole(HOOK_SET_ROLE, baseParams.hookSetRoleHolder);
        }
    }

    function _stakeAt(
        address l1,
        uint96 assetClass,
        address operator,
        uint48 timestamp,
        bytes memory hints
    ) internal view virtual returns (uint256, bytes memory);

    function _stake(address l1, uint96 assetClass, address operator) internal view virtual returns (uint256);

    function _setMaxL1Limit(address l1, uint96 assetClass, uint256 amount) internal virtual;

    function __initialize(address vault_, bytes memory data) internal virtual returns (BaseParams memory);

    function supportsInterface(
        bytes4 interfaceId
    ) public view virtual override(ERC165, AccessControlUpgradeable) returns (bool) {
        return interfaceId == type(IEntity).interfaceId || super.supportsInterface(interfaceId);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @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 towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            return a / b;
        }

        // (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 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                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.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

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

            uint256 twos = denominator & (0 - denominator);
            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 (unsignedRoundsUp(rounding) && 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
     * towards zero.
     *
     * 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 + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}

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

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity 0.8.25;

import {
    IValidatorManager,
    Validator,
    ValidatorChurnPeriod,
    ValidatorManagerSettings,
    ValidatorRegistrationInput
} from "@avalabs/icm-contracts/validator-manager/interfaces/IValidatorManager.sol";

/**
 * @dev Balancer Validator Manager settings, used to initialize the Balancer Validator Manager
 */
struct BalancerValidatorManagerSettings {
    ValidatorManagerSettings baseSettings;
    address initialOwner;
    address initialSecurityModule;
    uint64 initialSecurityModuleMaxWeight;
    bytes[] migratedValidators;
}

/**
 * @title IBalancerValidatorManager
 * @author ADDPHO
 * @notice Interface for Balancer Validator Manager contracts
 * @custom:security-contact [email protected]
 */
interface IBalancerValidatorManager is IValidatorManager {
    /**
     * @notice Emitted when a security module is registered, updated, or removed (maxWeight = 0)
     * @param securityModule The address of the security module
     * @param maxWeight The maximum total weight allowed for validators managed by this module
     */
    event SetUpSecurityModule(address indexed securityModule, uint64 maxWeight);

    error BalancerValidatorManager__MigratedValidatorsTotalWeightMismatch(
        uint64 migratedValidatorsTotalWeight, uint64 currentL1TotalWeight
    );
    error BalancerValidatorManager__SecurityModuleAlreadyRegistered(address securityModule);
    error BalancerValidatorManager__SecurityModuleNotRegistered(address securityModule);
    error BalancerValidatorManager__SecurityModuleMaxWeightExceeded(
        address securityModule, uint64 weight, uint64 maxWeight
    );
    error BalancerValidatorManager__SecurityModuleNewMaxWeightLowerThanCurrentWeight(
        address securityModule, uint64 newMaxWeight, uint64 currentWeight
    );
    error BalancerValidatorManager__InitialSecurityModuleMaxWeightLowerThanTotalWeight(
        address securityModule, uint64 initialMaxWeight, uint64 totalWeight
    );
    error BalancerValidatorManager__NewWeightIsZero();
    error BalancerValidatorManager__ValidatorNotBelongingToSecurityModule(
        bytes32 validationID, address securityModule
    );
    error BalancerValidatorManager__PendingWeightUpdate(bytes32 validationID);
    error BalancerValidatorManager__NoPendingWeightUpdate(bytes32 validationID);
    error BalancerValidatorManager__InvalidNonce(uint64 nonce);
    error BalancerValidatorManager__ValidatorAlreadyMigrated(bytes32 validationID);

    /**
     * @notice Returns the ValidatorManager churn period in seconds
     * @return churnPeriodSeconds The churn period in seconds
     */
    function getChurnPeriodSeconds() external view returns (uint64 churnPeriodSeconds);

    /**
     * @notice Returns the maximum churn rate per churn period (in percentage)
     * @return maximumChurnPercentage The maximum churn percentage
     */
    function getMaximumChurnPercentage() external view returns (uint64 maximumChurnPercentage);

    /**
     * @notice Returns the current churn period
     * @return churnPeriod The current churn period
     */
    function getCurrentChurnPeriod()
        external
        view
        returns (ValidatorChurnPeriod memory churnPeriod);

    /**
     * @notice Returns the list of registered security modules
     * @return securityModules The list of registered security modules
     */
    function getSecurityModules() external view returns (address[] memory securityModules);

    /**
     * @notice Returns the weight associated with a security module
     * @param securityModule The address of the security module
     * @return weight The weight of the security module
     */
    function getSecurityModuleWeights(
        address securityModule
    ) external view returns (uint64 weight, uint64 maxWeight);

    /**
     * @notice Returns whether a validator has a pending weight update
     * @param validationID The ID of the validator
     * @return Whether the validator has a pending weight update
     */
    function isValidatorPendingWeightUpdate(
        bytes32 validationID
    ) external view returns (bool);

    /**
     * @notice Returns the L1ID associated with this validator manager
     * @return l1ID The L1ID
     */
    function getL1ID() external view returns (bytes32 l1ID);

    /**
     * @notice Registers a new security module with a maximum weight limit
     * @param securityModule The address of the security module to register
     * @param maxWeight The maximum total weight allowed for validators managed by this module
     */
    function setUpSecurityModule(address securityModule, uint64 maxWeight) external;

    /**
     * @notice Begins the validator registration process, and sets the {weight} of the validator.
     * @param registrationInput The inputs for a validator registration.
     * @param weight The weight of the validator being registered.
     * @return validationID The ID of the validator registration.
     */
    /**
     * @notice Begins the validator registration process, and sets the {weight} of the validator.
     * @dev Can only be called by registered security modules
     * @param registrationInput The inputs for a validator registration.
     * @param weight The weight of the validator being registered.
     * @return validationID The ID of the validator registration.
     */
    function initializeValidatorRegistration(
        ValidatorRegistrationInput calldata registrationInput,
        uint64 weight
    ) external returns (bytes32 validationID);

    /**
     * @notice Begins the process of ending an active validation period. The validation period must have been previously
     * started by a successful call to {completeValidatorRegistration} with the given validationID.
     * @dev Can only be called by the security module that registered the validator
     * @param validationID The ID of the validation period being ended.
     * @return validator The validator that is being ended
     */
    function initializeEndValidation(
        bytes32 validationID
    ) external returns (Validator memory validator);

    /**
     * @notice Initiates a weight update for a validator
     * @dev Can only be called by the security module that registered the validator
     * @param validationID The ID of the validation period being updated
     * @param newWeight The new weight to set for the validator
     */
    function initializeValidatorWeightUpdate(
        bytes32 validationID,
        uint64 newWeight
    ) external returns (Validator memory validator);

    /**
     * @notice Completes a pending validator weight update after P-Chain confirmation
     * @dev Can only be called by the security module that registered the validator
     * @param validationID The ID of the validation period being updated
     * @param messageIndex The index of the Warp message containing the weight update confirmation
     */
    function completeValidatorWeightUpdate(bytes32 validationID, uint32 messageIndex) external;

    /**
     * @notice Resends a pending validator weight update message to the P-Chain
     * @param validationID The ID of the validation period being updated
     */
    function resendValidatorWeightUpdate(
        bytes32 validationID
    ) external;
}

// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.25;

import {ValidatorMessages} from "./ValidatorMessages.sol";
import {
    InitialValidator,
    IValidatorManager,
    PChainOwner,
    ConversionData,
    Validator,
    ValidatorChurnPeriod,
    ValidatorManagerSettings,
    ValidatorRegistrationInput,
    ValidatorStatus
} from "./interfaces/IValidatorManager.sol";
import {
    IWarpMessenger,
    WarpMessage
} from "@avalabs/[email protected]/contracts/interfaces/IWarpMessenger.sol";
import {ContextUpgradeable} from
    "@openzeppelin/[email protected]/utils/ContextUpgradeable.sol";
import {Initializable} from
    "@openzeppelin/[email protected]/proxy/utils/Initializable.sol";

/**
 * @dev Implementation of the {IValidatorManager} interface.
 *
 * @custom:security-contact https://github.com/ava-labs/icm-contracts/blob/main/SECURITY.md
 */
abstract contract ValidatorManager is Initializable, ContextUpgradeable, IValidatorManager {
    // solhint-disable private-vars-leading-underscore
    /// @custom:storage-location erc7201:avalanche-icm.storage.ValidatorManager

    struct ValidatorManagerStorage {
        /// @notice The l1ID associated with this validator manager.
        bytes32 _l1ID;
        /// @notice The number of seconds after which to reset the churn tracker.
        uint64 _churnPeriodSeconds;
        /// @notice The maximum churn rate allowed per churn period.
        uint8 _maximumChurnPercentage;
        /// @notice The churn tracker used to track the amount of stake added or removed in the churn period.
        ValidatorChurnPeriod _churnTracker;
        /// @notice Maps the validationID to the registration message such that the message can be re-sent if needed.
        mapping(bytes32 => bytes) _pendingRegisterValidationMessages;
        /// @notice Maps the validationID to the validator information.
        mapping(bytes32 => Validator) _validationPeriods;
        /// @notice Maps the nodeID to the validationID for validation periods that have not ended.
        mapping(bytes => bytes32) _registeredValidators;
        /// @notice Boolean that indicates if the initial validator set has been set.
        bool _initializedValidatorSet;
    }
    // solhint-enable private-vars-leading-underscore

    // keccak256(abi.encode(uint256(keccak256("avalanche-icm.storage.ValidatorManager")) - 1)) & ~bytes32(uint256(0xff));
    bytes32 public constant VALIDATOR_MANAGER_STORAGE_LOCATION =
        0xe92546d698950ddd38910d2e15ed1d923cd0a7b3dde9e2a6a3f380565559cb00;

    uint8 public constant MAXIMUM_CHURN_PERCENTAGE_LIMIT = 20;
    uint64 public constant MAXIMUM_REGISTRATION_EXPIRY_LENGTH = 2 days;
    uint32 public constant ADDRESS_LENGTH = 20; // This is only used as a packed uint32
    uint8 public constant BLS_PUBLIC_KEY_LENGTH = 48;
    bytes32 public constant P_CHAIN_BLOCKCHAIN_ID = bytes32(0);

    error InvalidValidatorManagerAddress(address validatorManagerAddress);
    error InvalidWarpOriginSenderAddress(address senderAddress);
    error InvalidValidatorManagerBlockchainID(bytes32 blockchainID);
    error InvalidWarpSourceChainID(bytes32 sourceChainID);
    error InvalidRegistrationExpiry(uint64 registrationExpiry);
    error InvalidInitializationStatus();
    error InvalidMaximumChurnPercentage(uint8 maximumChurnPercentage);
    error InvalidBLSKeyLength(uint256 length);
    error InvalidNodeID(bytes nodeID);
    error InvalidConversionID(bytes32 encodedConversionID, bytes32 expectedConversionID);
    error InvalidTotalWeight(uint64 weight);
    error InvalidValidationID(bytes32 validationID);
    error InvalidValidatorStatus(ValidatorStatus status);
    error InvalidWarpMessage();
    error MaxChurnRateExceeded(uint64 churnAmount);
    error NodeAlreadyRegistered(bytes nodeID);
    error UnexpectedRegistrationStatus(bool validRegistration);
    error InvalidPChainOwnerThreshold(uint256 threshold, uint256 addressesLength);
    error PChainOwnerAddressesNotSorted();

    // solhint-disable ordering
    /**
     * @dev This storage is visible to child contracts for convenience.
     *      External getters would be better practice, but code size limitations are preventing this.
     *      Child contracts should probably never write to this storage.
     */
    function _getValidatorManagerStorage()
        internal
        pure
        returns (ValidatorManagerStorage storage $)
    {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            $.slot := VALIDATOR_MANAGER_STORAGE_LOCATION
        }
    }

    /**
     * @notice Warp precompile used for sending and receiving Warp messages.
     */
    IWarpMessenger public constant WARP_MESSENGER =
        IWarpMessenger(0x0200000000000000000000000000000000000005);

    // solhint-disable-next-line func-name-mixedcase
    function __ValidatorManager_init(ValidatorManagerSettings calldata settings)
        internal
        onlyInitializing
    {
        __Context_init();
        __ValidatorManager_init_unchained(settings);
    }

    // solhint-disable-next-line func-name-mixedcase
    function __ValidatorManager_init_unchained(ValidatorManagerSettings calldata settings)
        internal
        onlyInitializing
    {
        ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
        $._l1ID = settings.l1ID;

        if (
            settings.maximumChurnPercentage > MAXIMUM_CHURN_PERCENTAGE_LIMIT
                || settings.maximumChurnPercentage == 0
        ) {
            revert InvalidMaximumChurnPercentage(settings.maximumChurnPercentage);
        }

        $._maximumChurnPercentage = settings.maximumChurnPercentage;
        $._churnPeriodSeconds = settings.churnPeriodSeconds;
    }

    modifier initializedValidatorSet() {
        if (!_getValidatorManagerStorage()._initializedValidatorSet) {
            revert InvalidInitializationStatus();
        }
        _;
    }

    /**
     * @notice See {IValidatorManager-initializeValidatorSet}.
     */
    function initializeValidatorSet(
        ConversionData calldata conversionData,
        uint32 messageIndex
    ) external {
        ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
        if ($._initializedValidatorSet) {
            revert InvalidInitializationStatus();
        }
        // Check that the blockchainID and validator manager address in the ConversionData correspond to this contract.
        // Other validation checks are done by the P-Chain when converting the L1, so are not required here.
        if (conversionData.validatorManagerBlockchainID != WARP_MESSENGER.getBlockchainID()) {
            revert InvalidValidatorManagerBlockchainID(conversionData.validatorManagerBlockchainID);
        }
        if (address(conversionData.validatorManagerAddress) != address(this)) {
            revert InvalidValidatorManagerAddress(address(conversionData.validatorManagerAddress));
        }

        uint256 numInitialValidators = conversionData.initialValidators.length;

        uint64 totalWeight;
        for (uint32 i; i < numInitialValidators; ++i) {
            InitialValidator memory initialValidator = conversionData.initialValidators[i];
            if ($._registeredValidators[initialValidator.nodeID] != bytes32(0)) {
                revert NodeAlreadyRegistered(initialValidator.nodeID);
            }

            // Validation ID of the initial validators is the sha256 hash of the
            // convert subnet to L1 tx ID and the index of the initial validator.
            bytes32 validationID = sha256(abi.encodePacked(conversionData.l1ID, i));

            // Save the initial validator as an active validator.

            $._registeredValidators[initialValidator.nodeID] = validationID;
            $._validationPeriods[validationID].status = ValidatorStatus.Active;
            $._validationPeriods[validationID].nodeID = initialValidator.nodeID;
            $._validationPeriods[validationID].startingWeight = initialValidator.weight;
            $._validationPeriods[validationID].messageNonce = 0;
            $._validationPeriods[validationID].weight = initialValidator.weight;
            $._validationPeriods[validationID].startedAt = uint64(block.timestamp);
            $._validationPeriods[validationID].endedAt = 0;
            totalWeight += initialValidator.weight;

            emit InitialValidatorCreated(
                validationID, initialValidator.nodeID, initialValidator.weight
            );
        }
        $._churnTracker.totalWeight = totalWeight;

        // Rearranged equation for totalWeight < (100 / $._maximumChurnPercentage)
        // Total weight must be above this value in order to not trigger churn limits with an added/removed weight of 1.
        if (totalWeight * $._maximumChurnPercentage < 100) {
            revert InvalidTotalWeight(totalWeight);
        }

        // Verify that the sha256 hash of the L1 conversion data matches with the Warp message's conversionID.
        bytes32 conversionID = ValidatorMessages.unpackSubnetToL1ConversionMessage(
            _getPChainWarpMessage(messageIndex).payload
        );
        bytes memory encodedConversion = ValidatorMessages.packConversionData(conversionData);
        bytes32 encodedConversionID = sha256(encodedConversion);
        if (encodedConversionID != conversionID) {
            revert InvalidConversionID(encodedConversionID, conversionID);
        }

        $._initializedValidatorSet = true;
    }

    function _validatePChainOwner(PChainOwner calldata pChainOwner) internal pure {
        // If threshold is 0, addresses must be empty.
        if (pChainOwner.threshold == 0 && pChainOwner.addresses.length != 0) {
            revert InvalidPChainOwnerThreshold(pChainOwner.threshold, pChainOwner.addresses.length);
        }
        // Threshold must be less than or equal to the number of addresses.
        if (pChainOwner.threshold > pChainOwner.addresses.length) {
            revert InvalidPChainOwnerThreshold(pChainOwner.threshold, pChainOwner.addresses.length);
        }
        // Addresses must be sorted in ascending order
        for (uint256 i = 1; i < pChainOwner.addresses.length; i++) {
            // Compare current address with the previous one
            if (pChainOwner.addresses[i] < pChainOwner.addresses[i - 1]) {
                revert PChainOwnerAddressesNotSorted();
            }
        }
    }

    /**
     * @notice Begins the validator registration process, and sets the initial weight for the validator.
     * This is the only method related to validator registration and removal that needs the initializedValidatorSet
     * modifier. All others are guarded by checking the validator status changes initialized in this function.
     * @param input The inputs for a validator registration.
     * @param weight The weight of the validator being registered.
     */
    function _initializeValidatorRegistration(
        ValidatorRegistrationInput calldata input,
        uint64 weight
    ) internal virtual initializedValidatorSet returns (bytes32) {
        ValidatorManagerStorage storage $ = _getValidatorManagerStorage();

        if (
            input.registrationExpiry <= block.timestamp
                || input.registrationExpiry >= block.timestamp + MAXIMUM_REGISTRATION_EXPIRY_LENGTH
        ) {
            revert InvalidRegistrationExpiry(input.registrationExpiry);
        }

        // Ensure the new validator doesn't overflow the total weight
        if (uint256(weight) + uint256($._churnTracker.totalWeight) > type(uint64).max) {
            revert InvalidTotalWeight(weight);
        }

        _validatePChainOwner(input.remainingBalanceOwner);
        _validatePChainOwner(input.disableOwner);

        // Ensure the nodeID is not the zero address, and is not already an active validator.

        if (input.blsPublicKey.length != BLS_PUBLIC_KEY_LENGTH) {
            revert InvalidBLSKeyLength(input.blsPublicKey.length);
        }
        if (input.nodeID.length == 0) {
            revert InvalidNodeID(input.nodeID);
        }
        if ($._registeredValidators[input.nodeID] != bytes32(0)) {
            revert NodeAlreadyRegistered(input.nodeID);
        }

        // Check that adding this validator would not exceed the maximum churn rate.
        _checkAndUpdateChurnTracker(weight, 0);

        (bytes32 validationID, bytes memory registerL1ValidatorMessage) = ValidatorMessages
            .packRegisterL1ValidatorMessage(
            ValidatorMessages.ValidationPeriod({
                l1ID: $._l1ID,
                nodeID: input.nodeID,
                blsPublicKey: input.blsPublicKey,
                remainingBalanceOwner: input.remainingBalanceOwner,
                disableOwner: input.disableOwner,
                registrationExpiry: input.registrationExpiry,
                weight: weight
            })
        );
        $._pendingRegisterValidationMessages[validationID] = registerL1ValidatorMessage;
        $._registeredValidators[input.nodeID] = validationID;

        // Submit the message to the Warp precompile.
        bytes32 messageID = WARP_MESSENGER.sendWarpMessage(registerL1ValidatorMessage);
        $._validationPeriods[validationID].status = ValidatorStatus.PendingAdded;
        $._validationPeriods[validationID].nodeID = input.nodeID;
        $._validationPeriods[validationID].startingWeight = weight;
        $._validationPeriods[validationID].messageNonce = 0;
        $._validationPeriods[validationID].weight = weight;
        $._validationPeriods[validationID].startedAt = 0; // The validation period only starts once the registration is acknowledged.
        $._validationPeriods[validationID].endedAt = 0;

        emit ValidationPeriodCreated(
            validationID, input.nodeID, messageID, weight, input.registrationExpiry
        );

        return validationID;
    }

    /**
     * @notice See {IValidatorManager-resendRegisterValidatorMessage}.
     */
    function resendRegisterValidatorMessage(bytes32 validationID) external {
        ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
        // The initial validator set must have been set already to have pending register validation messages.
        if ($._pendingRegisterValidationMessages[validationID].length == 0) {
            revert InvalidValidationID(validationID);
        }
        if ($._validationPeriods[validationID].status != ValidatorStatus.PendingAdded) {
            revert InvalidValidatorStatus($._validationPeriods[validationID].status);
        }

        // Submit the message to the Warp precompile.
        WARP_MESSENGER.sendWarpMessage($._pendingRegisterValidationMessages[validationID]);
    }

    /**
     * @notice See {IValidatorManager-completeValidatorRegistration}.
     */
    function completeValidatorRegistration(uint32 messageIndex) external {
        ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
        (bytes32 validationID, bool validRegistration) = ValidatorMessages
            .unpackL1ValidatorRegistrationMessage(_getPChainWarpMessage(messageIndex).payload);

        if (!validRegistration) {
            revert UnexpectedRegistrationStatus(validRegistration);
        }
        // The initial validator set must have been set already to have pending register validation messages.
        if ($._pendingRegisterValidationMessages[validationID].length == 0) {
            revert InvalidValidationID(validationID);
        }
        if ($._validationPeriods[validationID].status != ValidatorStatus.PendingAdded) {
            revert InvalidValidatorStatus($._validationPeriods[validationID].status);
        }

        delete $._pendingRegisterValidationMessages[validationID];
        $._validationPeriods[validationID].status = ValidatorStatus.Active;
        $._validationPeriods[validationID].startedAt = uint64(block.timestamp);
        emit ValidationPeriodRegistered(
            validationID, $._validationPeriods[validationID].weight, block.timestamp
        );
    }

    /**
     * @notice Returns a validation ID registered to the given nodeID
     * @param nodeID ID of the node associated with the validation ID
     */
    function registeredValidators(bytes calldata nodeID) public view returns (bytes32) {
        ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
        return $._registeredValidators[nodeID];
    }

    /**
     * @notice Returns a validator registered to the given validationID
     * @param validationID ID of the validation period associated with the validator
     */
    function getValidator(bytes32 validationID) public view returns (Validator memory) {
        ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
        return $._validationPeriods[validationID];
    }

    /**
     * @notice Begins the process of ending an active validation period. The validation period must have been previously
     * started by a successful call to {completeValidatorRegistration} with the given validationID.
     * Any rewards for this validation period will stop accruing when this function is called.
     * @param validationID The ID of the validation period being ended.
     */
    function _initializeEndValidation(bytes32 validationID)
        internal
        virtual
        returns (Validator memory)
    {
        ValidatorManagerStorage storage $ = _getValidatorManagerStorage();

        // Ensure the validation period is active.
        // The initial validator set must have been set already to have active validators.
        Validator memory validator = $._validationPeriods[validationID];
        if (validator.status != ValidatorStatus.Active) {
            revert InvalidValidatorStatus($._validationPeriods[validationID].status);
        }

        // Update the validator status to pending removal.
        // They are not removed from the active validators mapping until the P-Chain acknowledges the removal.
        validator.status = ValidatorStatus.PendingRemoved;

        // Set the end time of the validation period, since it is no longer known to be an active validator
        // on the P-Chain.
        validator.endedAt = uint64(block.timestamp);

        // Save the validator updates.
        $._validationPeriods[validationID] = validator;

        (, bytes32 messageID) = _setValidatorWeight(validationID, 0);

        // Emit the event to signal the start of the validator removal process.
        emit ValidatorRemovalInitialized(validationID, messageID, validator.weight, block.timestamp);

        return validator;
    }

    /**
     * @notice See {IValidatorManager-resendEndValidatorMessage}.
     */
    function resendEndValidatorMessage(bytes32 validationID) external {
        ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
        Validator memory validator = $._validationPeriods[validationID];

        // The initial validator set must have been set already to have pending end validation messages.
        if (validator.status != ValidatorStatus.PendingRemoved) {
            revert InvalidValidatorStatus($._validationPeriods[validationID].status);
        }

        WARP_MESSENGER.sendWarpMessage(
            ValidatorMessages.packL1ValidatorWeightMessage(validationID, validator.messageNonce, 0)
        );
    }

    /**
     * @notice Completes the process of ending a validation period by receiving an acknowledgement from the P-Chain
     * that the validation ID is not active and will never be active in the future.
     * Note: that this function can be used for successful validation periods that have been explicitly
     * ended by calling {initializeEndValidation} or for validation periods that never began on the P-Chain due to the
     * {registrationExpiry} being reached.
     * @return (Validation ID, Validator instance) representing the completed validation period.
     */
    function _completeEndValidation(uint32 messageIndex)
        internal
        returns (bytes32, Validator memory)
    {
        ValidatorManagerStorage storage $ = _getValidatorManagerStorage();

        // Get the Warp message.
        (bytes32 validationID, bool validRegistration) = ValidatorMessages
            .unpackL1ValidatorRegistrationMessage(_getPChainWarpMessage(messageIndex).payload);
        if (validRegistration) {
            revert UnexpectedRegistrationStatus(validRegistration);
        }

        Validator memory validator = $._validationPeriods[validationID];

        // The validation status is PendingRemoved if validator removal was initiated with a call to {initiateEndValidation}.
        // The validation status is PendingAdded if the validator was never registered on the P-Chain.
        // The initial validator set must have been set already to have pending validation messages.
        if (
            validator.status != ValidatorStatus.PendingRemoved
                && validator.status != ValidatorStatus.PendingAdded
        ) {
            revert InvalidValidatorStatus(validator.status);
        }

        if (validator.status == ValidatorStatus.PendingRemoved) {
            validator.status = ValidatorStatus.Completed;
        } else {
            validator.status = ValidatorStatus.Invalidated;
        }
        // Remove the validator from the registered validators mapping.
        delete $._registeredValidators[validator.nodeID];

        // Update the validator.
        $._validationPeriods[validationID] = validator;

        // Emit event.
        emit ValidationPeriodEnded(validationID, validator.status);

        return (validationID, validator);
    }

    function _incrementAndGetNonce(bytes32 validationID) internal returns (uint64) {
        ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
        return ++$._validationPeriods[validationID].messageNonce;
    }

    function _getPChainWarpMessage(uint32 messageIndex)
        internal
        view
        returns (WarpMessage memory)
    {
        (WarpMessage memory warpMessage, bool valid) =
            WARP_MESSENGER.getVerifiedWarpMessage(messageIndex);
        if (!valid) {
            revert InvalidWarpMessage();
        }
        // Must match to P-Chain blockchain id, which is 0.
        if (warpMessage.sourceChainID != P_CHAIN_BLOCKCHAIN_ID) {
            revert InvalidWarpSourceChainID(warpMessage.sourceChainID);
        }
        if (warpMessage.originSenderAddress != address(0)) {
            revert InvalidWarpOriginSenderAddress(warpMessage.originSenderAddress);
        }

        return warpMessage;
    }

    function _setValidatorWeight(
        bytes32 validationID,
        uint64 newWeight
    ) internal returns (uint64, bytes32) {
        ValidatorManagerStorage storage $ = _getValidatorManagerStorage();
        uint64 validatorWeight = $._validationPeriods[validationID].weight;

        // Check that changing the validator weight would not exceed the maximum churn rate.
        _checkAndUpdateChurnTracker(newWeight, validatorWeight);

        uint64 nonce = _incrementAndGetNonce(validationID);

        $._validationPeriods[validationID].weight = newWeight;

        // Submit the message to the Warp precompile.
        bytes32 messageID = WARP_MESSENGER.sendWarpMessage(
            ValidatorMessages.packL1ValidatorWeightMessage(validationID, nonce, newWeight)
        );

        emit ValidatorWeightUpdate({
            validationID: validationID,
            nonce: nonce,
            weight: newWeight,
            setWeightMessageID: messageID
        });

        return (nonce, messageID);
    }

    function _getChurnPeriodSeconds() internal view returns (uint64) {
        return _getValidatorManagerStorage()._churnPeriodSeconds;
    }

    /**
     * @dev Helper function to check if the stake weight to be added or removed would exceed the maximum stake churn
     * rate for the past churn period. If the churn rate is exceeded, the function will revert. If the churn rate is
     * not exceeded, the function will update the churn tracker with the new weight.
     */
    function _checkAndUpdateChurnTracker(
        uint64 newValidatorWeight,
        uint64 oldValidatorWeight
    ) private {
        ValidatorManagerStorage storage $ = _getValidatorManagerStorage();

        uint64 weightChange;
        if (newValidatorWeight > oldValidatorWeight) {
            weightChange = newValidatorWeight - oldValidatorWeight;
        } else {
            weightChange = oldValidatorWeight - newValidatorWeight;
        }

        uint256 currentTime = block.timestamp;
        ValidatorChurnPeriod memory churnTracker = $._churnTracker;

        if (
            churnTracker.startedAt == 0
                || currentTime >= churnTracker.startedAt + $._churnPeriodSeconds
        ) {
            churnTracker.churnAmount = weightChange;
            churnTracker.startedAt = currentTime;
            churnTracker.initialWeight = churnTracker.totalWeight;
        } else {
            // Churn is always additive whether the weight is being added or removed.
            churnTracker.churnAmount += weightChange;
        }

        // Rearranged equation of maximumChurnPercentage >= currentChurnPercentage to avoid integer division truncation.
        if ($._maximumChurnPercentage * churnTracker.initialWeight < churnTracker.churnAmount * 100)
        {
            revert MaxChurnRateExceeded(churnTracker.churnAmount);
        }

        // Two separate calculations because we're using uints and (newValidatorWeight - oldValidatorWeight) could underflow.
        churnTracker.totalWeight += newValidatorWeight;
        churnTracker.totalWeight -= oldValidatorWeight;

        // Rearranged equation for totalWeight < (100 / $._maximumChurnPercentage)
        // Total weight must be above this value in order to not trigger churn limits with an added/removed weight of 1.
        if (churnTracker.totalWeight * $._maximumChurnPercentage < 100) {
            revert InvalidTotalWeight(churnTracker.totalWeight);
        }

        $._churnTracker = churnTracker;
    }
}

// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem
pragma solidity 0.8.25;

import {PChainOwner, ConversionData} from "./interfaces/IValidatorManager.sol";

/**
 * @dev Packing utilities for the ICM message types used by the Validator Manager contracts, as specified in ACP-77:
 * https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/77-reinventing-subnets
 */
library ValidatorMessages {
    // The information that uniquely identifies an L1 validation period.
    // The validationID is the SHA-256 hash of the concatenation of the CODEC_ID,
    // REGISTER_L1_VALIDATOR_MESSAGE_TYPE_ID, and the concatenated ValidationPeriod fields.
    struct ValidationPeriod {
        bytes32 l1ID;
        bytes nodeID;
        bytes blsPublicKey;
        uint64 registrationExpiry;
        PChainOwner remainingBalanceOwner;
        PChainOwner disableOwner;
        uint64 weight;
    }

    // The P-Chain uses a hardcoded codecID of 0 for all messages.
    uint16 internal constant CODEC_ID = 0;

    // The P-Chain signs a SubnetToL1ConversionMessage that is used to verify the L1's initial validators.
    uint32 internal constant SUBNET_TO_L1_CONVERSION_MESSAGE_TYPE_ID = 0;

    // L1s send a RegisterL1ValidatorMessage to the P-Chain to register a validator.
    uint32 internal constant REGISTER_L1_VALIDATOR_MESSAGE_TYPE_ID = 1;

    // The P-Chain responds with a RegisterL1ValidatorMessage indicating whether the registration was successful
    // for the given validation ID.
    uint32 internal constant L1_VALIDATOR_REGISTRATION_MESSAGE_TYPE_ID = 2;

    // L1s can send a L1ValidatorWeightMessage to the P-Chain to update a validator's weight.
    // The P-Chain responds with another L1ValidatorWeightMessage acknowledging the weight update.
    uint32 internal constant L1_VALIDATOR_WEIGHT_MESSAGE_TYPE_ID = 3;

    // The L1 will self-sign a ValidationUptimeMessage to be provided when a validator is initiating
    // the end of their validation period.
    uint32 internal constant VALIDATION_UPTIME_MESSAGE_TYPE_ID = 0;

    error InvalidMessageLength(uint32 actual, uint32 expected);
    error InvalidCodecID(uint32 id);
    error InvalidMessageType();
    error InvalidBLSPublicKey();

    /**
     * @notice Packs a SubnetToL1ConversionMessage message into a byte array.
     * The message format specification is:
     * +--------------------+----------+----------+
     * |            codecID :   uint16 |  2 bytes |
     * +--------------------+----------+----------+
     * |             typeID :   uint32 |  4 bytes |
     * +--------------------+----------+----------+
     * |       conversionID : [32]byte | 32 bytes |
     * +--------------------+----------+----------+
     *                                 | 38 bytes |
     *                                 +----------+
     *
     * @param conversionID The subnet conversion ID to pack into the message.
     * @return The packed message.
     */
    function packSubnetToL1ConversionMessage(bytes32 conversionID)
        external
        pure
        returns (bytes memory)
    {
        return abi.encodePacked(CODEC_ID, SUBNET_TO_L1_CONVERSION_MESSAGE_TYPE_ID, conversionID);
    }

    /**
     * @notice Unpacks a byte array as a SubnetToL1ConversionMessage message.
     * The message format specification is the same as the one used in above for packing.
     *
     * @param input The byte array to unpack.
     * @return The unpacked conversionID.
     */
    function unpackSubnetToL1ConversionMessage(bytes memory input)
        external
        pure
        returns (bytes32)
    {
        if (input.length != 38) {
            revert InvalidMessageLength(uint32(input.length), 38);
        }

        // Unpack the codec ID
        uint16 codecID;
        for (uint256 i; i < 2; ++i) {
            codecID |= uint16(uint8(input[i])) << uint16((8 * (1 - i)));
        }
        if (codecID != CODEC_ID) {
            revert InvalidCodecID(codecID);
        }

        // Unpack the type ID
        uint32 typeID;
        for (uint256 i; i < 4; ++i) {
            typeID |= uint32(uint8(input[i + 2])) << uint32((8 * (3 - i)));
        }
        if (typeID != SUBNET_TO_L1_CONVERSION_MESSAGE_TYPE_ID) {
            revert InvalidMessageType();
        }

        // Unpack the conversionID
        bytes32 conversionID;
        for (uint256 i; i < 32; ++i) {
            conversionID |= bytes32(uint256(uint8(input[i + 6])) << (8 * (31 - i)));
        }

        return conversionID;
    }

    /**
     * @notice Packs ConversionData into a byte array.
     * This byte array is the SHA256 pre-image of the conversionID hash
     * The message format specification is:
     *
     * ConversionData:
     * +----------------+-----------------+--------------------------------------------------------+
     * |       codecID  :          uint16 |                                                2 bytes |
     * +----------------+-----------------+--------------------------------------------------------+
     * |       l1ID :        [32]byte |                                               32 bytes |
     * +----------------+-----------------+--------------------------------------------------------+
     * | managerChainID :        [32]byte |                                               32 bytes |
     * +----------------+-----------------+--------------------------------------------------------+
     * | managerAddress :          []byte |                          4 + len(managerAddress) bytes |
     * +----------------+-----------------+--------------------------------------------------------+
     * |     validators : []ValidatorData |                        4 + sum(validatorLengths) bytes |
     * +----------------+-----------------+--------------------------------------------------------+
     *                                    | 74 + len(managerAddress) + len(validatorLengths) bytes |
     *                                    +--------------------------------------------------------+
     * ValidatorData:
     * +--------------+----------+------------------------+
     * |       nodeID :   []byte |  4 + len(nodeID) bytes |
     * +--------------+----------+------------------------+
     * | blsPublicKey : [48]byte |               48 bytes |
     * +--------------+----------+------------------------+
     * |       weight :   uint64 |                8 bytes |
     * +--------------+----------+------------------------+
     *                           | 60 + len(nodeID) bytes |
     *                           +------------------------+
     *
     * @dev Input validation is skipped, since the returned value is intended to be compared
     * directly with an authenticated ICM message.
     * @param conversionData The struct representing data to pack into the message.
     * @return The packed message.
     */
    function packConversionData(ConversionData memory conversionData)
        external
        pure
        returns (bytes memory)
    {
        // Hardcoded 20 is for length of the managerAddress on EVM chains
        // solhint-disable-next-line func-named-parameters
        bytes memory res = abi.encodePacked(
            CODEC_ID,
            conversionData.l1ID,
            conversionData.validatorManagerBlockchainID,
            uint32(20),
            conversionData.validatorManagerAddress,
            uint32(conversionData.initialValidators.length)
        );
        // The approach below of encoding initialValidators using `abi.encodePacked` in a loop
        // was tested against pre-allocating the array and doing manual byte by byte packing and
        // it was found to be more gas efficient.
        for (uint256 i; i < conversionData.initialValidators.length; ++i) {
            res = abi.encodePacked(
                res,
                uint32(conversionData.initialValidators[i].nodeID.length),
                conversionData.initialValidators[i].nodeID,
                conversionData.initialValidators[i].blsPublicKey,
                conversionData.initialValidators[i].weight
            );
        }
        return res;
    }

    /**
     * @notice Packs a RegisterL1ValidatorMessage message into a byte array.
     * The message format specification is:
     *
     * RegisterL1ValidatorMessage:
     * +-----------------------+-------------+--------------------------------------------------------------------+
     * |               codecID :      uint16 |                                                            2 bytes |
     * +-----------------------+-------------+--------------------------------------------------------------------+
     * |                typeID :      uint32 |                                                            4 bytes |
     * +-----------------------+-------------+-------------------------------------------------------------------+
     * |              l1ID :    [32]byte |                                                           32 bytes |
     * +-----------------------+-------------+--------------------------------------------------------------------+
     * |                nodeID :      []byte |                                              4 + len(nodeID) bytes |
     * +-----------------------+-------------+--------------------------------------------------------------------+
     * |          blsPublicKey :    [48]byte |                                                           48 bytes |
     * +-----------------------+-------------+--------------------------------------------------------------------+
     * |                expiry :      uint64 |                                                            8 bytes |
     * +-----------------------+-------------+--------------------------------------------------------------------+
     * | remainingBalanceOwner : PChainOwner |                                      8 + len(addresses) * 20 bytes |
     * +-----------------------+-------------+--------------------------------------------------------------------+
     * |          disableOwner : PChainOwner |                                      8 + len(addresses) * 20 bytes |
     * +-----------------------+-------------+--------------------------------------------------------------------+
     * |                weight :      uint64 |                                                            8 bytes |
     * +-----------------------+-------------+--------------------------------------------------------------------+
     *                                       | 122 + len(nodeID) + (len(addresses1) + len(addresses2)) * 20 bytes |
     *                                       +--------------------------------------------------------------------+
     *
     * PChainOwner:
     * +-----------+------------+-------------------------------+
     * | threshold :     uint32 |                       4 bytes |
     * +-----------+------------+-------------------------------+
     * | addresses : [][20]byte | 4 + len(addresses) * 20 bytes |
     * +-----------+------------+-------------------------------+
     *                          | 8 + len(addresses) * 20 bytes |
     *                          +-------------------------------+
     *
     * @param validationPeriod The information to pack into the message.
     * @return The validationID and the packed message.
     */
    function packRegisterL1ValidatorMessage(ValidationPeriod memory validationPeriod)
        external
        pure
        returns (bytes32, bytes memory)
    {
        if (validationPeriod.blsPublicKey.length != 48) {
            revert InvalidBLSPublicKey();
        }

        // solhint-disable-next-line func-named-parameters
        bytes memory res = abi.encodePacked(
            CODEC_ID,
            REGISTER_L1_VALIDATOR_MESSAGE_TYPE_ID,
            validationPeriod.l1ID,
            uint32(validationPeriod.nodeID.length),
            validationPeriod.nodeID,
            validationPeriod.blsPublicKey,
            validationPeriod.registrationExpiry,
            validationPeriod.remainingBalanceOwner.threshold,
            uint32(validationPeriod.remainingBalanceOwner.addresses.length)
        );
        for (uint256 i; i < validationPeriod.remainingBalanceOwner.addresses.length; ++i) {
            res = abi.encodePacked(res, validationPeriod.remainingBalanceOwner.addresses[i]);
        }
        res = abi.encodePacked(
            res,
            validationPeriod.disableOwner.threshold,
            uint32(validationPeriod.disableOwner.addresses.length)
        );
        for (uint256 i; i < validationPeriod.disableOwner.addresses.length; ++i) {
            res = abi.encodePacked(res, validationPeriod.disableOwner.addresses[i]);
        }
        res = abi.encodePacked(res, validationPeriod.weight);

        return (sha256(res), res);
    }

    /**
     * @notice Unpacks a byte array as a RegisterL1ValidatorMessage message.
     * The message format specification is the same as the one used in above for packing.
     *
     * @param input The byte array to unpack.
     * @return The unpacked ValidationPeriod.
     */
    function unpackRegisterL1ValidatorMessage(bytes memory input)
        external
        pure
        returns (ValidationPeriod memory)
    {
        uint32 index = 0;
        ValidationPeriod memory validation;

        // Unpack the codec ID
        // Individual fields are unpacked in their own scopes to avoid stack too deep errors.
        {
            uint16 codecID;
            for (uint256 i; i < 2; ++i) {
                codecID |= uint16(uint8(input[i + index])) << uint16((8 * (1 - i)));
            }
            if (codecID != CODEC_ID) {
                revert InvalidCodecID(codecID);
            }
            index += 2;
        }

        // Unpack the type ID
        {
            uint32 typeID;
            for (uint256 i; i < 4; ++i) {
                typeID |= uint32(uint8(input[i + index])) << uint32((8 * (3 - i)));
            }
            if (typeID != REGISTER_L1_VALIDATOR_MESSAGE_TYPE_ID) {
                revert InvalidMessageType();
            }
            index += 4;
        }

        // Unpack the l1ID
        {
            bytes32 l1ID;
            for (uint256 i; i < 32; ++i) {
                l1ID |= bytes32(uint256(uint8(input[i + index])) << (8 * (31 - i)));
            }
            validation.l1ID = l1ID;
            index += 32;
        }

        // Unpack the nodeID length
        uint32 nodeIDLength;
        {
            for (uint256 i; i < 4; ++i) {
                nodeIDLength |= uint32(uint8(input[i + index])) << uint32((8 * (3 - i)));
            }
            index += 4;

            // Unpack the nodeID
            bytes memory nodeID = new bytes(nodeIDLength);
            for (uint256 i; i < nodeIDLength; ++i) {
                nodeID[i] = input[i + index];
            }
            validation.nodeID = nodeID;
            index += nodeIDLength;
        }

        // Unpack the blsPublicKey
        {
            bytes memory blsPublicKey = new bytes(48);
            for (uint256 i; i < 48; ++i) {
                blsPublicKey[i] = input[i + index];
            }
            validation.blsPublicKey = blsPublicKey;
            index += 48;
        }

        // Unpack the registration expiry
        {
            uint64 expiry;
            for (uint256 i; i < 8; ++i) {
                expiry |= uint64(uint8(input[i + index])) << uint64((8 * (7 - i)));
            }
            validation.registrationExpiry = expiry;
            index += 8;
        }

        // Unpack the remainingBalanceOwner threshold
        uint32 remainingBalanceOwnerAddressesLength;
        {
            uint32 remainingBalanceOwnerThreshold;
            for (uint256 i; i < 4; ++i) {
                remainingBalanceOwnerThreshold |=
                    uint32(uint8(input[i + index])) << uint32((8 * (3 - i)));
            }
            index += 4;

            // Unpack the remainingBalanceOwner addresses length
            for (uint256 i; i < 4; ++i) {
                remainingBalanceOwnerAddressesLength |=
                    uint32(uint8(input[i + index])) << uint32((8 * (3 - i)));
            }
            index += 4;

            // Unpack the remainingBalanceOwner addresses
            address[] memory remainingBalanceOwnerAddresses =
                new address[](remainingBalanceOwnerAddressesLength);
            for (uint256 i; i < remainingBalanceOwnerAddressesLength; ++i) {
                bytes memory addrBytes = new bytes(20);
                for (uint256 j; j < 20; ++j) {
                    addrBytes[j] = input[j + index];
                }
                address addr;
                // solhint-disable-next-line no-inline-assembly
                assembly {
                    addr := mload(add(addrBytes, 20))
                }
                remainingBalanceOwnerAddresses[i] = addr;
                index += 20;
            }
            validation.remainingBalanceOwner = PChainOwner({
                threshold: remainingBalanceOwnerThreshold,
                addresses: remainingBalanceOwnerAddresses
            });
        }

        // Unpack the disableOwner threshold
        uint32 disableOwnerAddressesLength;
        {
            uint32 disableOwnerThreshold;
            for (uint256 i; i < 4; ++i) {
                disableOwnerThreshold |= uint32(uint8(input[i + index])) << uint32((8 * (3 - i)));
            }
            index += 4;

            // Unpack the disableOwner addresses length
            for (uint256 i; i < 4; ++i) {
                disableOwnerAddressesLength |=
                    uint32(uint8(input[i + index])) << uint32((8 * (3 - i)));
            }
            index += 4;

            // Unpack the disableOwner addresses
            address[] memory disableOwnerAddresses = new address[](disableOwnerAddressesLength);
            for (uint256 i; i < disableOwnerAddressesLength; ++i) {
                bytes memory addrBytes = new bytes(20);
                for (uint256 j; j < 20; ++j) {
                    addrBytes[j] = input[j + index];
                }
                address addr;
                // solhint-disable-next-line no-inline-assembly
                assembly {
                    addr := mload(add(addrBytes, 20))
                }
                disableOwnerAddresses[i] = addr;
                index += 20;
            }
            validation.disableOwner =
                PChainOwner({threshold: disableOwnerThreshold, addresses: disableOwnerAddresses});
        }
        // Now that we have all the variable lengths, validate the input length
        uint32 expectedLength = 122 + nodeIDLength
            + (remainingBalanceOwnerAddressesLength + disableOwnerAddressesLength) * 20;
        if (input.length != expectedLength) {
            revert InvalidMessageLength(uint32(input.length), expectedLength);
        }
        // Unpack the weight
        {
            uint64 weight;
            for (uint256 i; i < 8; ++i) {
                weight |= uint64(uint8(input[i + index])) << uint64((8 * (7 - i)));
            }
            validation.weight = weight;
        }

        return validation;
    }

    /**
     * @notice Packs a L1ValidatorRegistrationMessage into a byte array.
     * The message format specification is:
     * +--------------+----------+----------+
     * |      codecID :   uint16 |  2 bytes |
     * +--------------+----------+----------+
     * |       typeID :   uint32 |  4 bytes |
     * +--------------+----------+----------+
     * | validationID : [32]byte | 32 bytes |
     * +--------------+----------+----------+
     * |        valid :     bool |  1 byte  |
     * +--------------+----------+----------+
     *                           | 39 bytes |
     *                           +----------+
     *
     * @param validationID The ID of the validation period.
     * @param valid true if the validation period was registered, false if it was not and never will be.
     * @return The packed message.
     *
     */
    function packL1ValidatorRegistrationMessage(
        bytes32 validationID,
        bool valid
    ) external pure returns (bytes memory) {
        return abi.encodePacked(
            CODEC_ID, L1_VALIDATOR_REGISTRATION_MESSAGE_TYPE_ID, validationID, valid
        );
    }

    /**
     * @notice Unpacks a byte array as a L1ValidatorRegistrationMessage message.
     * The message format specification is the same as the one used in above for packing.
     *
     * @param input The byte array to unpack.
     * @return The validationID and whether the validation period was registered or is not a
     * validator and never will be a validator due to the expiry time passing.
     */
    function unpackL1ValidatorRegistrationMessage(bytes memory input)
        external
        pure
        returns (bytes32, bool)
    {
        if (input.length != 39) {
            revert InvalidMessageLength(uint32(input.length), 39);
        }
        // Unpack the codec ID
        uint16 codecID;
        for (uint256 i; i < 2; ++i) {
            codecID |= uint16(uint8(input[i])) << uint16((8 * (1 - i)));
        }
        if (codecID != CODEC_ID) {
            revert InvalidCodecID(codecID);
        }

        // Unpack the type ID
        uint32 typeID;
        for (uint256 i; i < 4; ++i) {
            typeID |= uint32(uint8(input[i + 2])) << uint32((8 * (3 - i)));
        }
        if (typeID != L1_VALIDATOR_REGISTRATION_MESSAGE_TYPE_ID) {
            revert InvalidMessageType();
        }

        // Unpack the validation ID.
        bytes32 validationID;
        for (uint256 i; i < 32; ++i) {
            validationID |= bytes32(uint256(uint8(input[i + 6])) << (8 * (31 - i)));
        }

        // Unpack the validity
        bool valid = input[38] != 0;

        return (validationID, valid);
    }

    /**
     * @notice Packs a L1ValidatorWeightMessage message into a byte array.
     * The message format specification is:
     * +--------------+----------+----------+
     * |      codecID :   uint16 |  2 bytes |
     * +--------------+----------+----------+
     * |       typeID :   uint32 |  4 bytes |
     * +--------------+----------+----------+
     * | validationID : [32]byte | 32 bytes |
     * +--------------+----------+----------+
     * |        nonce :   uint64 |  8 bytes |
     * +--------------+----------+----------+
     * |       weight :   uint64 |  8 bytes |
     * +--------------+----------+----------+
     *                           | 54 bytes |
     *                           +----------+
     *
     * @param validationID The ID of the validation period.
     * @param nonce The nonce of the validation ID.
     * @param weight The new weight of the validator.
     * @return The packed message.
     */
    function packL1ValidatorWeightMessage(
        bytes32 validationID,
        uint64 nonce,
        uint64 weight
    ) external pure returns (bytes memory) {
        return abi.encodePacked(
            CODEC_ID, L1_VALIDATOR_WEIGHT_MESSAGE_TYPE_ID, validationID, nonce, weight
        );
    }

    /**
     * @notice Unpacks a byte array as an L1ValidatorWeightMessage.
     * The message format specification is the same as the one used in above for packing.
     *
     * @param input The byte array to unpack.
     * @return The validationID, nonce, and weight.
     */
    function unpackL1ValidatorWeightMessage(bytes memory input)
        external
        pure
        returns (bytes32, uint64, uint64)
    {
        if (input.length != 54) {
            revert InvalidMessageLength(uint32(input.length), 54);
        }

        // Unpack the codec ID.
        uint16 codecID;
        for (uint256 i; i < 2; ++i) {
            codecID |= uint16(uint8(input[i])) << uint16((8 * (1 - i)));
        }
        if (codecID != CODEC_ID) {
            revert InvalidCodecID(codecID);
        }

        // Unpack the type ID.
        uint32 typeID;
        for (uint256 i; i < 4; ++i) {
            typeID |= uint32(uint8(input[i + 2])) << uint32((8 * (3 - i)));
        }
        if (typeID != L1_VALIDATOR_WEIGHT_MESSAGE_TYPE_ID) {
            revert InvalidMessageType();
        }

        // Unpack the validation ID.
        bytes32 validationID;
        for (uint256 i; i < 32; ++i) {
            validationID |= bytes32(uint256(uint8(input[i + 6])) << (8 * (31 - i)));
        }

        // Unpack the nonce.
        uint64 nonce;
        for (uint256 i; i < 8; ++i) {
            nonce |= uint64(uint8(input[i + 38])) << uint64((8 * (7 - i)));
        }

        // Unpack the weight.
        uint64 weight;
        for (uint256 i; i < 8; ++i) {
            weight |= uint64(uint8(input[i + 46])) << uint64((8 * (7 - i)));
        }

        return (validationID, nonce, weight);
    }

    /**
     * @notice Packs a ValidationUptimeMessage into a byte array.
     * The message format specification is:
     * +--------------+----------+----------+
     * |      codecID :   uint16 |  2 bytes |
     * +--------------+----------+----------+
     * |       typeID :   uint32 |  4 bytes |
     * +--------------+----------+----------+
     * | validationID : [32]byte | 32 bytes |
     * +--------------+----------+----------+
     * |       uptime :   uint64 |  8 bytes |
     * +--------------+----------+----------+
     *                           | 46 bytes |
     *                           +----------+
     *
     * @param validationID The ID of the validation period.
     * @param uptime The uptime of the validator.
     * @return The packed message.
     */
    function packValidationUptimeMessage(
        bytes32 validationID,
        uint64 uptime
    ) external pure returns (bytes memory) {
        return abi.encodePacked(CODEC_ID, VALIDATION_UPTIME_MESSAGE_TYPE_ID, validationID, uptime);
    }

    /**
     * @notice Unpacks a byte array as a ValidationUptimeMessage.
     * The message format specification is the same as the one used in above for packing.
     *
     * @param input The byte array to unpack.
     * @return The validationID and uptime.
     */
    function unpackValidationUptimeMessage(bytes memory input)
        external
        pure
        returns (bytes32, uint64)
    {
        if (input.length != 46) {
            revert InvalidMessageLength(uint32(input.length), 46);
        }

        // Unpack the codec ID.
        uint16 codecID;
        for (uint256 i; i < 2; ++i) {
            codecID |= uint16(uint8(input[i])) << uint16((8 * (1 - i)));
        }
        if (codecID != CODEC_ID) {
            revert InvalidCodecID(codecID);
        }

        // Unpack the type ID.
        uint32 typeID;
        for (uint256 i; i < 4; ++i) {
            typeID |= uint32(uint8(input[i + 2])) << uint32((8 * (3 - i)));
        }
        if (typeID != VALIDATION_UPTIME_MESSAGE_TYPE_ID) {
            revert InvalidMessageType();
        }

        // Unpack the validation ID.
        bytes32 validationID;
        for (uint256 i; i < 32; ++i) {
            validationID |= bytes32(uint256(uint8(input[i + 6])) << (8 * (31 - i)));
        }

        // Unpack the uptime.
        uint64 uptime;
        for (uint256 i; i < 8; ++i) {
            uptime |= uint64(uint8(input[i + 38])) << uint64((8 * (7 - i)));
        }

        return (validationID, uptime);
    }
}

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

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

File 22 of 50 : EnumerableMap.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableMap.js.

pragma solidity ^0.8.20;

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

/**
 * @dev Library for managing an enumerable variant of Solidity's
 * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
 * type.
 *
 * Maps have the following properties:
 *
 * - Entries are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Entries are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableMap for EnumerableMap.UintToAddressMap;
 *
 *     // Declare a set state variable
 *     EnumerableMap.UintToAddressMap private myMap;
 * }
 * ```
 *
 * The following map types are supported:
 *
 * - `uint256 -> address` (`UintToAddressMap`) since v3.0.0
 * - `address -> uint256` (`AddressToUintMap`) since v4.6.0
 * - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0
 * - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0
 * - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableMap.
 * ====
 */
library EnumerableMap {
    using EnumerableSet for EnumerableSet.Bytes32Set;

    // To implement this library for multiple types with as little code repetition as possible, we write it in
    // terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions,
    // and user-facing implementations such as `UintToAddressMap` are just wrappers around the underlying Map.
    // This means that we can only create new EnumerableMaps for types that fit in bytes32.

    /**
     * @dev Query for a nonexistent map key.
     */
    error EnumerableMapNonexistentKey(bytes32 key);

    struct Bytes32ToBytes32Map {
        // Storage of keys
        EnumerableSet.Bytes32Set _keys;
        mapping(bytes32 key => bytes32) _values;
    }

    /**
     * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
    function set(Bytes32ToBytes32Map storage map, bytes32 key, bytes32 value) internal returns (bool) {
        map._values[key] = value;
        return map._keys.add(key);
    }

    /**
     * @dev Removes a key-value pair from a map. O(1).
     *
     * Returns true if the key was removed from the map, that is if it was present.
     */
    function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) {
        delete map._values[key];
        return map._keys.remove(key);
    }

    /**
     * @dev Returns true if the key is in the map. O(1).
     */
    function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) {
        return map._keys.contains(key);
    }

    /**
     * @dev Returns the number of key-value pairs in the map. O(1).
     */
    function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) {
        return map._keys.length();
    }

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

    /**
     * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
    function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) {
        bytes32 value = map._values[key];
        if (value == bytes32(0)) {
            return (contains(map, key), bytes32(0));
        } else {
            return (true, value);
        }
    }

    /**
     * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
    function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) {
        bytes32 value = map._values[key];
        if (value == 0 && !contains(map, key)) {
            revert EnumerableMapNonexistentKey(key);
        }
        return value;
    }

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

    // UintToUintMap

    struct UintToUintMap {
        Bytes32ToBytes32Map _inner;
    }

    /**
     * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
    function set(UintToUintMap storage map, uint256 key, uint256 value) internal returns (bool) {
        return set(map._inner, bytes32(key), bytes32(value));
    }

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

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

    /**
     * @dev Returns the number of elements in the map. O(1).
     */
    function length(UintToUintMap storage map) internal view returns (uint256) {
        return length(map._inner);
    }

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

    /**
     * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
    function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) {
        (bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
        return (success, uint256(value));
    }

    /**
     * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
    function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) {
        return uint256(get(map._inner, bytes32(key)));
    }

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

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // UintToAddressMap

    struct UintToAddressMap {
        Bytes32ToBytes32Map _inner;
    }

    /**
     * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
    function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) {
        return set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
    }

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

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

    /**
     * @dev Returns the number of elements in the map. O(1).
     */
    function length(UintToAddressMap storage map) internal view returns (uint256) {
        return length(map._inner);
    }

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

    /**
     * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
    function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) {
        (bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
        return (success, address(uint160(uint256(value))));
    }

    /**
     * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
    function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
        return address(uint160(uint256(get(map._inner, bytes32(key)))));
    }

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

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // AddressToUintMap

    struct AddressToUintMap {
        Bytes32ToBytes32Map _inner;
    }

    /**
     * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
    function set(AddressToUintMap storage map, address key, uint256 value) internal returns (bool) {
        return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value));
    }

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

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

    /**
     * @dev Returns the number of elements in the map. O(1).
     */
    function length(AddressToUintMap storage map) internal view returns (uint256) {
        return length(map._inner);
    }

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

    /**
     * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
    function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) {
        (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key))));
        return (success, uint256(value));
    }

    /**
     * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
    function get(AddressToUintMap storage map, address key) internal view returns (uint256) {
        return uint256(get(map._inner, bytes32(uint256(uint160(key)))));
    }

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

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // Bytes32ToUintMap

    struct Bytes32ToUintMap {
        Bytes32ToBytes32Map _inner;
    }

    /**
     * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
    function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) {
        return set(map._inner, key, bytes32(value));
    }

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

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

    /**
     * @dev Returns the number of elements in the map. O(1).
     */
    function length(Bytes32ToUintMap storage map) internal view returns (uint256) {
        return length(map._inner);
    }

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

    /**
     * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
    function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) {
        (bool success, bytes32 value) = tryGet(map._inner, key);
        return (success, uint256(value));
    }

    /**
     * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
    function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) {
        return uint256(get(map._inner, key));
    }

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

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}

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

pragma solidity ^0.8.20;

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

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

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

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

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

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

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

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

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

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

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

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

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

interface IAssetClassRegistry {
    error AssetClassRegistry__InvalidAsset();
    error AssetClassRegistry__AssetNotFound();
    error AssetClassRegistry__AssetAlreadyRegistered();
    error AssetClassRegistry__AssetClassAlreadyExists();
    error AssetClassRegistry__AssetClassNotFound();
    error AssetClassRegistry__AssetIsPrimaryAssetClass(uint256 assetClassId);
    error AssetClassRegistry__AssetsStillExist();
    error AssetClassRegistry__InvalidStakingRequirements();

    event AssetClassAdded(uint256 indexed assetClassId, uint256 primaryAssetMinStake, uint256 primaryAssetMaxStake);
    event AssetAdded(uint256 indexed assetClassId, address indexed asset);
    event AssetRemoved(uint256 indexed assetClassId, address indexed asset);
    event AssetClassRemoved(uint256 indexed assetClassId);

    /**
     * @notice Adds a new asset class
     * @param assetClassId New asset class ID
     * @param minValidatorStake Minimum validator stake
     * @param maxValidatorStake Maximum validator stake
     * @param initialAsset Initial asset to add to the asset class
     */
    function addAssetClass(
        uint256 assetClassId,
        uint256 minValidatorStake,
        uint256 maxValidatorStake,
        address initialAsset
    ) external;

    /**
     * @notice Adds a asset to an asset class.
     * @param assetClassId The ID of the asset class.
     * @param asset The address of the asset to add.
     */
    function addAssetToClass(uint256 assetClassId, address asset) external;

    /**
     * @notice Removes a asset from an asset class, except .
     * @param assetClassId The ID of the asset class.
     * @param asset The address of the asset to remove.
     */
    function removeAssetFromClass(uint256 assetClassId, address asset) external;

    /**
     * @notice Removes an asset class.
     * @param assetClassId The ID of the asset class.
     */
    function removeAssetClass(
        uint256 assetClassId
    ) external;

    /**
     * @notice Returns all the assets in a specific asset class.
     * @param assetClassId The ID of the asset class.
     * @return An array of asset addresses in the asset class.
     */
    function getClassAssets(
        uint256 assetClassId
    ) external view returns (address[] memory);

    /**
     * @notice Returns the minimum validator stake for a specific asset class.
     * @param assetClassId The ID of the asset class.
     * @return The minimum and maximum validator stake.
     */
    function getClassStakingRequirements(
        uint256 assetClassId
    ) external view returns (uint256, uint256);

    /**
     * @notice Fetches the active asset class IDs
     * @return An array of asset class IDs
     */
    function getAssetClassIds() external view returns (uint96[] memory);
}

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity 0.8.25;

import {IAvalancheL1Middleware} from "../../interfaces/middleware/IAvalancheL1Middleware.sol";

/**
 * @title IMiddlewareVaultManager
 * @notice Manages vault registration, maximum L1 stake limits, and slash routing.
 */
interface IMiddlewareVaultManager {
    // -----------------------------------------------------------------------
    // Errors
    // -----------------------------------------------------------------------
    error AvalancheL1Middleware__VaultAlreadyRegistered();
    error AvalancheL1Middleware__VaultEpochTooShort();
    error AvalancheL1Middleware__NotVault(address vault);
    error AvalancheL1Middleware__WrongVaultAssetClass();
    error AvalancheL1Middleware__ZeroVaultMaxL1Limit();
    error AvalancheL1Middleware__VaultGracePeriodNotPassed();
    error AvalancheL1Middleware__VaultNotDisabled();
    error AvalancheL1Middleware__ZeroAddress(string name);
    error AvalancheL1Middleware__SlasherNotImplemented();

    // -----------------------------------------------------------------------
    // Public state variable getters
    // -----------------------------------------------------------------------
    /**
     * @notice Returns the VAULT_REGISTRY address.
     */
    function VAULT_REGISTRY() external view returns (address);

    //
    // Functions
    //
    /**
     * @notice Registers a vault to a specific asset class with a given maximum L1 stake limit.
     * @param vault The vault address
     * @param assetClassId The asset class ID for the vault
     * @param vaultMaxL1Limit The maximum stake allowed for this vault
     */
    function registerVault(address vault, uint96 assetClassId, uint256 vaultMaxL1Limit) external;

    /**
     * @notice Updates a vault's max L1 stake limit.
     * @param vault The vault address
     * @param assetClassId The asset class ID
     * @param vaultMaxL1Limit The new maximum stake
     */
    function updateVaultMaxL1Limit(address vault, uint96 assetClassId, uint256 vaultMaxL1Limit) external;

    /**
     * @notice Removes a vault if the grace period has passed.
     * @param vault The vault address
     */
    function removeVault(
        address vault
    ) external;

    /**
     * @notice Slashes a vault based on the operator’s share of stake.
     */
    function slashVault() external;

    /**
     * @notice Returns the number of vaults registered.
     * @return The count of vaults
     */
    function getVaultCount() external view returns (uint256);

    /**
     * @notice Returns the vault and its enable/disable times at the given index.
     * @param index The vault index
     * @return vault The vault address
     * @return enabledTime The time the vault was enabled
     * @return disabledTime The time the vault was disabled
     */
    function getVaultAtWithTimes(
        uint256 index
    ) external view returns (address vault, uint48 enabledTime, uint48 disabledTime);

    /**
     * @notice Returns the asset class ID for a given vault.
     * @param vault The vault address
     * @return The asset class ID
     */
    function getVaultAssetClass(
        address vault
    ) external view returns (uint96);

    /**
     * @notice Fetches the active vaults for a given epoch
     * @param epoch The epoch for which vaults are fetched
     * @return An array of active vault addresses
     */
    function getVaults(
        uint48 epoch
    ) external view returns (address[] memory);
}

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

interface IRegistry {
    error EntityNotExist();

    /**
     * @notice Emitted when an entity is added.
     * @param entity address of the added entity
     */
    event AddEntity(address indexed entity);

    /**
     * @notice Get if a given address is an entity.
     * @param account address to check
     * @return if the given address is an entity
     */
    function isEntity(
        address account
    ) external view returns (bool);

    /**
     * @notice Get a total number of entities.
     * @return total number of entities added
     */
    function totalEntities() external view returns (uint256);

    /**
     * @notice Get an entity given its index.
     * @param index index of the entity to get
     * @return address of the entity
     */
    function entity(
        uint256 index
    ) external view returns (address);
}

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/introspection/IERC165.sol";

interface IEntity is IERC165 {
    error Entity__NotInitialized();
    error Entity__ZeroAddress(string name);
    /**
     * @notice Get the factory's address.
     * @return address of the factory
     */

    function FACTORY() external view returns (address);

    /**
     * @notice Get the entity's type.
     * @return type of the entity
     */
    function TYPE() external view returns (uint64);

    /**
     * @notice Initialize this entity contract by using given data.
     * @param data some data to use
     */
    function initialize(
        bytes calldata data
    ) external;
}

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

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

interface ISlasher is IBaseSlasher {
    error InsufficientSlash();
    error InvalidCaptureTimestamp();

    /**
     * @notice Initial parameters needed for a slasher deployment.
     * @param baseParams base parameters for slashers' deployment
     */
    struct InitParams {
        IBaseSlasher.BaseParams baseParams;
    }

    /**
     * @notice Hints for a slash.
     * @param slashableStakeHints hints for the slashable stake checkpoints
     */
    struct SlashHints {
        bytes slashableStakeHints;
    }

    /**
     * @notice Extra data for the delegator.
     * @param slashableStake amount of the slashable stake before the slash (cache)
     * @param stakeAt amount of the stake at the capture time (cache)
     */
    struct DelegatorData {
        uint256 slashableStake;
        uint256 stakeAt;
    }

    /**
     * @notice Emitted when a slash is performed.
     * @param l1 address of the l1.
     * @param assetClass the uint96 assetClass.
     * @param operator operator that is slashed
     * @param slashedAmount virtual amount of the collateral slashed
     * @param captureTimestamp time point when the stake was captured
     */
    event Slash(
        address indexed l1,
        uint96 indexed assetClass,
        address indexed operator,
        uint256 slashedAmount,
        uint48 captureTimestamp
    );

    /**
     * @notice Perform a slash using a subnetwork for a particular operator by a given amount using hints.
     * @param l1 address of the l1
     * @param assetClass the uint96 assetClass
     * @param operator address of the operator
     * @param amount maximum amount of the collateral to be slashed
     * @param captureTimestamp time point when the stake was captured
     * @param hints hints for checkpoints' indexes
     * @return slashedAmount virtual amount of the collateral slashed
     * @dev Only a network middleware can call this function.
     */
    function slash(
        address l1,
        uint96 assetClass,
        address operator,
        uint256 amount,
        uint48 captureTimestamp,
        bytes calldata hints
    ) external returns (uint256 slashedAmount);
}

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

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

interface IVetoSlasher is IBaseSlasher {
    error AlreadySet();
    error InsufficientSlash();
    error InvalidCaptureTimestamp();
    error InvalidResolverSetEpochsDelay();
    error InvalidVetoDuration();
    error NoResolver();
    error NotNetwork();
    error NotResolver();
    error SlashPeriodEnded();
    error SlashRequestCompleted();
    error SlashRequestNotExist();
    error VetoPeriodEnded();
    error VetoPeriodNotEnded();

    /**
     * @notice Initial parameters needed for a slasher deployment.
     * @param baseParams base parameters for slashers' deployment
     * @param vetoDuration duration of the veto period for a slash request
     * @param resolverSetEpochsDelay delay in epochs for a network to update a resolver
     */
    struct InitParams {
        IBaseSlasher.BaseParams baseParams;
        uint48 vetoDuration;
        uint256 resolverSetEpochsDelay;
    }

    /**
     * @notice Structure for a slash request.
     * @param l1 address of the l1
     * @param assetClass the uint96 assetClass
     * @param operator operator that could be slashed (if the request is not vetoed)
     * @param amount maximum amount of the collateral to be slashed
     * @param captureTimestamp time point when the stake was captured
     * @param vetoDeadline deadline for the resolver to veto the slash (exclusively)
     * @param completed if the slash was vetoed/executed
     */
    struct SlashRequest {
        address l1;
        uint96 assetClass;
        address operator;
        uint256 amount;
        uint48 captureTimestamp;
        uint48 vetoDeadline;
        bool completed;
    }

    /**
     * @notice Hints for a slash request.
     * @param slashableStakeHints hints for the slashable stake checkpoints
     */
    struct RequestSlashHints {
        bytes slashableStakeHints;
    }

    /**
     * @notice Hints for a slash execute.
     * @param captureResolverHint hint for the resolver checkpoint at the capture time
     * @param currentResolverHint hint for the resolver checkpoint at the current time
     * @param slashableStakeHints hints for the slashable stake checkpoints
     */
    struct ExecuteSlashHints {
        bytes captureResolverHint;
        bytes currentResolverHint;
        bytes slashableStakeHints;
    }

    /**
     * @notice Hints for a slash veto.
     * @param captureResolverHint hint for the resolver checkpoint at the capture time
     * @param currentResolverHint hint for the resolver checkpoint at the current time
     */
    struct VetoSlashHints {
        bytes captureResolverHint;
        bytes currentResolverHint;
    }

    /**
     * @notice Hints for a resolver set.
     * @param resolverHint hint for the resolver checkpoint
     */
    struct SetResolverHints {
        bytes resolverHint;
    }

    /**
     * @notice Extra data for the delegator.
     * @param slashableStake amount of the slashable stake before the slash (cache)
     * @param stakeAt amount of the stake at the capture time (cache)
     * @param slashIndex index of the slash request
     */
    struct DelegatorData {
        uint256 slashableStake;
        uint256 stakeAt;
        uint256 slashIndex;
    }

    /**
     * @notice Emitted when a slash request is created.
     * @param slashIndex index of the slash request
     * @param l1 address of the l1
     * @param assetClass the uint96 assetClass
     * @param operator operator that could be slashed (if the request is not vetoed)
     * @param slashAmount maximum amount of the collateral to be slashed
     * @param captureTimestamp time point when the stake was captured
     * @param vetoDeadline deadline for the resolver to veto the slash (exclusively)
     */
    event RequestSlash(
        uint256 indexed slashIndex,
        address l1,
        uint96 assetClass,
        address indexed operator,
        uint256 slashAmount,
        uint48 captureTimestamp,
        uint48 vetoDeadline
    );

    /**
     * @notice Emitted when a slash request is executed.
     * @param slashIndex index of the slash request
     * @param slashedAmount virtual amount of the collateral slashed
     */
    event ExecuteSlash(uint256 indexed slashIndex, uint256 slashedAmount);

    /**
     * @notice Emitted when a slash request is vetoed.
     * @param slashIndex index of the slash request
     * @param resolver address of the resolver that vetoed the slash
     */
    event VetoSlash(uint256 indexed slashIndex, address indexed resolver);

    /**
     * @notice Emitted when a resolver is set.
     * @param l1 address of the l1
     * @param assetClass the uint96 assetClass
     * @param resolver address of the resolver
     */
    event SetResolver(address indexed l1, uint96 indexed assetClass, address resolver);

    /**
     * @notice Get the network registry's address.
     * @return address of the network registry
     */
    function NETWORK_REGISTRY() external view returns (address);

    /**
     * @notice Get a duration during which resolvers can veto slash requests.
     * @return duration of the veto period
     */
    function vetoDuration() external view returns (uint48);

    /**
     * @notice Get a total number of slash requests.
     * @return total number of slash requests
     */
    function slashRequestsLength() external view returns (uint256);

    /**
     * @notice Get a particular slash request.
     * @param slashIndex index of the slash request
     * @return l1 address of the l1
     * @return assetClass the uint96 assetClass
     * @return operator operator that could be slashed (if the request is not vetoed)
     * @return amount maximum amount of the collateral to be slashed
     * @return captureTimestamp time point when the stake was captured
     * @return vetoDeadline deadline for the resolver to veto the slash (exclusively)
     * @return completed if the slash was vetoed/executed
     */
    function slashRequests(
        uint256 slashIndex
    )
        external
        view
        returns (
            address l1,
            uint96 assetClass,
            address operator,
            uint256 amount,
            uint48 captureTimestamp,
            uint48 vetoDeadline,
            bool completed
        );

    /**
     * @notice Get a delay for networks in epochs to update a resolver.
     * @return updating resolver delay in epochs
     */
    function resolverSetEpochsDelay() external view returns (uint256);

    /**
     * @notice Get a resolver for a given assset class at a particular timestamp using a hint.
     * @param l1 address of the l1
     * @param assetClass the uint96 assetClass
     * @param timestamp timestamp to get the resolver at
     * @param hint hint for the checkpoint index
     * @return address of the resolver
     */
    function resolverAt(
        address l1,
        uint96 assetClass,
        uint48 timestamp,
        bytes memory hint
    ) external view returns (address);

    /**
     * @notice Get a resolver for a given assset class using a hint.
     * @param l1 address of the l1
     * @param assetClass the uint96 assetClass
     * @param hint hint for the checkpoint index
     * @return address of the resolver
     */
    function resolver(address l1, uint96 assetClass, bytes memory hint) external view returns (address);

    /**
     * @notice Request a slash using a assset class for a particular operator by a given amount using hints.
     * @param l1 address of the l1
     * @param assetClass the uint96 assetClass
     * @param operator address of the operator
     * @param amount maximum amount of the collateral to be slashed
     * @param captureTimestamp time point when the stake was captured
     * @param hints hints for checkpoints' indexes
     * @return slashIndex index of the slash request
     * @dev Only a network middleware can call this function.
     */
    function requestSlash(
        address l1,
        uint96 assetClass,
        address operator,
        uint256 amount,
        uint48 captureTimestamp,
        bytes calldata hints
    ) external returns (uint256 slashIndex);

    /**
     * @notice Execute a slash with a given slash index using hints.
     * @param slashIndex index of the slash request
     * @param hints hints for checkpoints' indexes
     * @return slashedAmount virtual amount of the collateral slashed
     * @dev Only a network middleware can call this function.
     */
    function executeSlash(uint256 slashIndex, bytes calldata hints) external returns (uint256 slashedAmount);

    /**
     * @notice Veto a slash with a given slash index using hints.
     * @param slashIndex index of the slash request
     * @param hints hints for checkpoints' indexes
     * @dev Only a resolver can call this function.
     */
    function vetoSlash(uint256 slashIndex, bytes calldata hints) external;

    /**
     * @notice Set a resolver for a assset class using hints.
     * identifier identifier of the assset class
     * @param resolver address of the resolver
     * @param hints hints for checkpoints' indexes
     * @dev Only a network can call this function.
     */
    function setResolver(uint96 identifier, address resolver, bytes calldata hints) external;
}

File 30 of 50 : Checkpoints.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/Checkpoints.sol)
// This file was procedurally generated from scripts/generate/templates/Checkpoints.js.

pragma solidity ^0.8.20;

import {Math} from "../math/Math.sol";

/**
 * @dev This library defines the `Trace*` struct, for checkpointing values as they change at different points in
 * time, and later looking up past values by block number. See {Votes} as an example.
 *
 * To create a history of checkpoints define a variable type `Checkpoints.Trace*` in your contract, and store a new
 * checkpoint for the current transaction block using the {push} function.
 */
library Checkpoints {
    /**
     * @dev A value was attempted to be inserted on a past checkpoint.
     */
    error CheckpointUnorderedInsertion();

    struct Trace224 {
        Checkpoint224[] _checkpoints;
    }

    struct Checkpoint224 {
        uint32 _key;
        uint224 _value;
    }

    /**
     * @dev Pushes a (`key`, `value`) pair into a Trace224 so that it is stored as the checkpoint.
     *
     * Returns previous value and new value.
     *
     * IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint32).max` key set will disable the
     * library.
     */
    function push(Trace224 storage self, uint32 key, uint224 value) internal returns (uint224, uint224) {
        return _insert(self._checkpoints, key, value);
    }

    /**
     * @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if
     * there is none.
     */
    function lowerLookup(Trace224 storage self, uint32 key) internal view returns (uint224) {
        uint256 len = self._checkpoints.length;
        uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len);
        return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value;
    }

    /**
     * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
     * if there is none.
     */
    function upperLookup(Trace224 storage self, uint32 key) internal view returns (uint224) {
        uint256 len = self._checkpoints.length;
        uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len);
        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
     * if there is none.
     *
     * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high
     * keys).
     */
    function upperLookupRecent(Trace224 storage self, uint32 key) internal view returns (uint224) {
        uint256 len = self._checkpoints.length;

        uint256 low = 0;
        uint256 high = len;

        if (len > 5) {
            uint256 mid = len - Math.sqrt(len);
            if (key < _unsafeAccess(self._checkpoints, mid)._key) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }

        uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high);

        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
     */
    function latest(Trace224 storage self) internal view returns (uint224) {
        uint256 pos = self._checkpoints.length;
        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value
     * in the most recent checkpoint.
     */
    function latestCheckpoint(Trace224 storage self) internal view returns (bool exists, uint32 _key, uint224 _value) {
        uint256 pos = self._checkpoints.length;
        if (pos == 0) {
            return (false, 0, 0);
        } else {
            Checkpoint224 memory ckpt = _unsafeAccess(self._checkpoints, pos - 1);
            return (true, ckpt._key, ckpt._value);
        }
    }

    /**
     * @dev Returns the number of checkpoint.
     */
    function length(Trace224 storage self) internal view returns (uint256) {
        return self._checkpoints.length;
    }

    /**
     * @dev Returns checkpoint at given position.
     */
    function at(Trace224 storage self, uint32 pos) internal view returns (Checkpoint224 memory) {
        return self._checkpoints[pos];
    }

    /**
     * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
     * or by updating the last one.
     */
    function _insert(Checkpoint224[] storage self, uint32 key, uint224 value) private returns (uint224, uint224) {
        uint256 pos = self.length;

        if (pos > 0) {
            // Copying to memory is important here.
            Checkpoint224 memory last = _unsafeAccess(self, pos - 1);

            // Checkpoint keys must be non-decreasing.
            if (last._key > key) {
                revert CheckpointUnorderedInsertion();
            }

            // Update or push new checkpoint
            if (last._key == key) {
                _unsafeAccess(self, pos - 1)._value = value;
            } else {
                self.push(Checkpoint224({_key: key, _value: value}));
            }
            return (last._value, value);
        } else {
            self.push(Checkpoint224({_key: key, _value: value}));
            return (0, value);
        }
    }

    /**
     * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high`
     * if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive
     * `high`.
     *
     * WARNING: `high` should not be greater than the array's length.
     */
    function _upperBinaryLookup(
        Checkpoint224[] storage self,
        uint32 key,
        uint256 low,
        uint256 high
    ) private view returns (uint256) {
        while (low < high) {
            uint256 mid = Math.average(low, high);
            if (_unsafeAccess(self, mid)._key > key) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }
        return high;
    }

    /**
     * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or
     * `high` if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and
     * exclusive `high`.
     *
     * WARNING: `high` should not be greater than the array's length.
     */
    function _lowerBinaryLookup(
        Checkpoint224[] storage self,
        uint32 key,
        uint256 low,
        uint256 high
    ) private view returns (uint256) {
        while (low < high) {
            uint256 mid = Math.average(low, high);
            if (_unsafeAccess(self, mid)._key < key) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return high;
    }

    /**
     * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
     */
    function _unsafeAccess(
        Checkpoint224[] storage self,
        uint256 pos
    ) private pure returns (Checkpoint224 storage result) {
        assembly {
            mstore(0, self.slot)
            result.slot := add(keccak256(0, 0x20), pos)
        }
    }

    struct Trace208 {
        Checkpoint208[] _checkpoints;
    }

    struct Checkpoint208 {
        uint48 _key;
        uint208 _value;
    }

    /**
     * @dev Pushes a (`key`, `value`) pair into a Trace208 so that it is stored as the checkpoint.
     *
     * Returns previous value and new value.
     *
     * IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint48).max` key set will disable the
     * library.
     */
    function push(Trace208 storage self, uint48 key, uint208 value) internal returns (uint208, uint208) {
        return _insert(self._checkpoints, key, value);
    }

    /**
     * @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if
     * there is none.
     */
    function lowerLookup(Trace208 storage self, uint48 key) internal view returns (uint208) {
        uint256 len = self._checkpoints.length;
        uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len);
        return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value;
    }

    /**
     * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
     * if there is none.
     */
    function upperLookup(Trace208 storage self, uint48 key) internal view returns (uint208) {
        uint256 len = self._checkpoints.length;
        uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len);
        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
     * if there is none.
     *
     * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high
     * keys).
     */
    function upperLookupRecent(Trace208 storage self, uint48 key) internal view returns (uint208) {
        uint256 len = self._checkpoints.length;

        uint256 low = 0;
        uint256 high = len;

        if (len > 5) {
            uint256 mid = len - Math.sqrt(len);
            if (key < _unsafeAccess(self._checkpoints, mid)._key) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }

        uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high);

        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
     */
    function latest(Trace208 storage self) internal view returns (uint208) {
        uint256 pos = self._checkpoints.length;
        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value
     * in the most recent checkpoint.
     */
    function latestCheckpoint(Trace208 storage self) internal view returns (bool exists, uint48 _key, uint208 _value) {
        uint256 pos = self._checkpoints.length;
        if (pos == 0) {
            return (false, 0, 0);
        } else {
            Checkpoint208 memory ckpt = _unsafeAccess(self._checkpoints, pos - 1);
            return (true, ckpt._key, ckpt._value);
        }
    }

    /**
     * @dev Returns the number of checkpoint.
     */
    function length(Trace208 storage self) internal view returns (uint256) {
        return self._checkpoints.length;
    }

    /**
     * @dev Returns checkpoint at given position.
     */
    function at(Trace208 storage self, uint32 pos) internal view returns (Checkpoint208 memory) {
        return self._checkpoints[pos];
    }

    /**
     * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
     * or by updating the last one.
     */
    function _insert(Checkpoint208[] storage self, uint48 key, uint208 value) private returns (uint208, uint208) {
        uint256 pos = self.length;

        if (pos > 0) {
            // Copying to memory is important here.
            Checkpoint208 memory last = _unsafeAccess(self, pos - 1);

            // Checkpoint keys must be non-decreasing.
            if (last._key > key) {
                revert CheckpointUnorderedInsertion();
            }

            // Update or push new checkpoint
            if (last._key == key) {
                _unsafeAccess(self, pos - 1)._value = value;
            } else {
                self.push(Checkpoint208({_key: key, _value: value}));
            }
            return (last._value, value);
        } else {
            self.push(Checkpoint208({_key: key, _value: value}));
            return (0, value);
        }
    }

    /**
     * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high`
     * if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive
     * `high`.
     *
     * WARNING: `high` should not be greater than the array's length.
     */
    function _upperBinaryLookup(
        Checkpoint208[] storage self,
        uint48 key,
        uint256 low,
        uint256 high
    ) private view returns (uint256) {
        while (low < high) {
            uint256 mid = Math.average(low, high);
            if (_unsafeAccess(self, mid)._key > key) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }
        return high;
    }

    /**
     * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or
     * `high` if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and
     * exclusive `high`.
     *
     * WARNING: `high` should not be greater than the array's length.
     */
    function _lowerBinaryLookup(
        Checkpoint208[] storage self,
        uint48 key,
        uint256 low,
        uint256 high
    ) private view returns (uint256) {
        while (low < high) {
            uint256 mid = Math.average(low, high);
            if (_unsafeAccess(self, mid)._key < key) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return high;
    }

    /**
     * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
     */
    function _unsafeAccess(
        Checkpoint208[] storage self,
        uint256 pos
    ) private pure returns (Checkpoint208 storage result) {
        assembly {
            mstore(0, self.slot)
            result.slot := add(keccak256(0, 0x20), pos)
        }
    }

    struct Trace160 {
        Checkpoint160[] _checkpoints;
    }

    struct Checkpoint160 {
        uint96 _key;
        uint160 _value;
    }

    /**
     * @dev Pushes a (`key`, `value`) pair into a Trace160 so that it is stored as the checkpoint.
     *
     * Returns previous value and new value.
     *
     * IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint96).max` key set will disable the
     * library.
     */
    function push(Trace160 storage self, uint96 key, uint160 value) internal returns (uint160, uint160) {
        return _insert(self._checkpoints, key, value);
    }

    /**
     * @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if
     * there is none.
     */
    function lowerLookup(Trace160 storage self, uint96 key) internal view returns (uint160) {
        uint256 len = self._checkpoints.length;
        uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len);
        return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value;
    }

    /**
     * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
     * if there is none.
     */
    function upperLookup(Trace160 storage self, uint96 key) internal view returns (uint160) {
        uint256 len = self._checkpoints.length;
        uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len);
        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
     * if there is none.
     *
     * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high
     * keys).
     */
    function upperLookupRecent(Trace160 storage self, uint96 key) internal view returns (uint160) {
        uint256 len = self._checkpoints.length;

        uint256 low = 0;
        uint256 high = len;

        if (len > 5) {
            uint256 mid = len - Math.sqrt(len);
            if (key < _unsafeAccess(self._checkpoints, mid)._key) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }

        uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high);

        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
     */
    function latest(Trace160 storage self) internal view returns (uint160) {
        uint256 pos = self._checkpoints.length;
        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value
     * in the most recent checkpoint.
     */
    function latestCheckpoint(Trace160 storage self) internal view returns (bool exists, uint96 _key, uint160 _value) {
        uint256 pos = self._checkpoints.length;
        if (pos == 0) {
            return (false, 0, 0);
        } else {
            Checkpoint160 memory ckpt = _unsafeAccess(self._checkpoints, pos - 1);
            return (true, ckpt._key, ckpt._value);
        }
    }

    /**
     * @dev Returns the number of checkpoint.
     */
    function length(Trace160 storage self) internal view returns (uint256) {
        return self._checkpoints.length;
    }

    /**
     * @dev Returns checkpoint at given position.
     */
    function at(Trace160 storage self, uint32 pos) internal view returns (Checkpoint160 memory) {
        return self._checkpoints[pos];
    }

    /**
     * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
     * or by updating the last one.
     */
    function _insert(Checkpoint160[] storage self, uint96 key, uint160 value) private returns (uint160, uint160) {
        uint256 pos = self.length;

        if (pos > 0) {
            // Copying to memory is important here.
            Checkpoint160 memory last = _unsafeAccess(self, pos - 1);

            // Checkpoint keys must be non-decreasing.
            if (last._key > key) {
                revert CheckpointUnorderedInsertion();
            }

            // Update or push new checkpoint
            if (last._key == key) {
                _unsafeAccess(self, pos - 1)._value = value;
            } else {
                self.push(Checkpoint160({_key: key, _value: value}));
            }
            return (last._value, value);
        } else {
            self.push(Checkpoint160({_key: key, _value: value}));
            return (0, value);
        }
    }

    /**
     * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high`
     * if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive
     * `high`.
     *
     * WARNING: `high` should not be greater than the array's length.
     */
    function _upperBinaryLookup(
        Checkpoint160[] storage self,
        uint96 key,
        uint256 low,
        uint256 high
    ) private view returns (uint256) {
        while (low < high) {
            uint256 mid = Math.average(low, high);
            if (_unsafeAccess(self, mid)._key > key) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }
        return high;
    }

    /**
     * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or
     * `high` if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and
     * exclusive `high`.
     *
     * WARNING: `high` should not be greater than the array's length.
     */
    function _lowerBinaryLookup(
        Checkpoint160[] storage self,
        uint96 key,
        uint256 low,
        uint256 high
    ) private view returns (uint256) {
        while (low < high) {
            uint256 mid = Math.average(low, high);
            if (_unsafeAccess(self, mid)._key < key) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return high;
    }

    /**
     * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
     */
    function _unsafeAccess(
        Checkpoint160[] storage self,
        uint256 pos
    ) private pure returns (Checkpoint160 storage result) {
        assembly {
            mstore(0, self.slot)
            result.slot := add(keccak256(0, 0x20), pos)
        }
    }
}

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

pragma solidity ^0.8.20;

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

/**
 * @dev Library used to query support of an interface declared via {IERC165}.
 *
 * Note that these functions return the actual result of the query: they do not
 * `revert` if an interface is not supported. It is up to the caller to decide
 * what to do in these cases.
 */
library ERC165Checker {
    // As per the EIP-165 spec, no interface should ever match 0xffffffff
    bytes4 private constant INTERFACE_ID_INVALID = 0xffffffff;

    /**
     * @dev Returns true if `account` supports the {IERC165} interface.
     */
    function supportsERC165(address account) internal view returns (bool) {
        // Any contract that implements ERC165 must explicitly indicate support of
        // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
        return
            supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
            !supportsERC165InterfaceUnchecked(account, INTERFACE_ID_INVALID);
    }

    /**
     * @dev Returns true if `account` supports the interface defined by
     * `interfaceId`. Support for {IERC165} itself is queried automatically.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
        // query support of both ERC165 as per the spec and support of _interfaceId
        return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
    }

    /**
     * @dev Returns a boolean array where each value corresponds to the
     * interfaces passed in and whether they're supported or not. This allows
     * you to batch check interfaces for a contract where your expectation
     * is that some interfaces may not be supported.
     *
     * See {IERC165-supportsInterface}.
     */
    function getSupportedInterfaces(
        address account,
        bytes4[] memory interfaceIds
    ) internal view returns (bool[] memory) {
        // an array of booleans corresponding to interfaceIds and whether they're supported or not
        bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);

        // query support of ERC165 itself
        if (supportsERC165(account)) {
            // query support of each interface in interfaceIds
            for (uint256 i = 0; i < interfaceIds.length; i++) {
                interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);
            }
        }

        return interfaceIdsSupported;
    }

    /**
     * @dev Returns true if `account` supports all the interfaces defined in
     * `interfaceIds`. Support for {IERC165} itself is queried automatically.
     *
     * Batch-querying can lead to gas savings by skipping repeated checks for
     * {IERC165} support.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
        // query support of ERC165 itself
        if (!supportsERC165(account)) {
            return false;
        }

        // query support of each interface in interfaceIds
        for (uint256 i = 0; i < interfaceIds.length; i++) {
            if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {
                return false;
            }
        }

        // all interfaces supported
        return true;
    }

    /**
     * @notice Query if a contract implements an interface, does not check ERC165 support
     * @param account The address of the contract to query for support of an interface
     * @param interfaceId The interface identifier, as specified in ERC-165
     * @return true if the contract at account indicates support of the interface with
     * identifier interfaceId, false otherwise
     * @dev Assumes that account contains a contract that supports ERC165, otherwise
     * the behavior of this method is undefined. This precondition can be checked
     * with {supportsERC165}.
     *
     * Some precompiled contracts will falsely indicate support for a given interface, so caution
     * should be exercised when using this function.
     *
     * Interface identification is specified in ERC-165.
     */
    function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
        // prepare call
        bytes memory encodedParams = abi.encodeCall(IERC165.supportsInterface, (interfaceId));

        // perform static call
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly {
            success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0x00)
        }

        return success && returnSize >= 0x20 && returnValue > 0;
    }
}

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

pragma solidity ^0.8.20;

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

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

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

interface IBaseDelegator {
    error BaseDelegator__AlreadySet();
    error BaseDelegator__InsufficientHookGas();
    error BaseDelegator__NotL1();
    error BaseDelegator__NotSlasher();
    error BaseDelegator__NotVault();
    error BaseDelegator__NotInitialized();
    error BaseDelegator__ZeroAddress(string name);
    error BaseDelegator__NotAuthorizedMiddleware();
    /**
     * @notice Base parameters needed for delegators' deployment.
     * @param defaultAdminRoleHolder address of the initial DEFAULT_ADMIN_ROLE holder
     * @param hook address of the hook contract
     * @param hookSetRoleHolder address of the initial HOOK_SET_ROLE holder
     */

    struct BaseParams {
        address defaultAdminRoleHolder;
        address hook;
        address hookSetRoleHolder;
    }

    /**
     * @notice Base hints for a stake.
     * @param operatorVaultOptInHint hint for the operator-vault opt-in
     * @param operatorL1OptInHint hint for the operator-l1 opt-in
     */
    struct StakeBaseHints {
        bytes operatorVaultOptInHint;
        bytes operatorL1OptInHint;
    }

    /**
     * @notice Emitted when a asset class maximum limit is set.
     * @param l1 address of the l1.
     * @param assetClass the uint96 assetClass.
     * @param amount new maximum asset class limit (how much stake the asset class is ready to get)
     */
    event SetMaxL1Limit(address indexed l1, uint96 indexed assetClass, uint256 amount);

    /**
     * @notice Emitted when a slash happens.
     * @param l1 address of the l1.
     * @param assetClass the uint96 assetClass.
     * @param operator address of the operator
     * @param amount amount of the collateral to be slashed
     * @param captureTimestamp time point when the stake was captured
     */
    event OnSlash(
        address indexed l1, uint96 indexed assetClass, address indexed operator, uint256 amount, uint48 captureTimestamp
    );

    /**
     * @notice Emitted when a hook is set.
     * @param hook address of the hook
     */
    event SetHook(address indexed hook);

    /**
     * @notice Get a version of the delegator (different versions mean different interfaces).
     * @return version of the delegator
     * @dev Must return 1 for this one.
     */

    /**
     * @notice Get the factory's address.
     * @return address of the factory
     */
    function FACTORY() external view returns (address);

    /**
     * @notice Get the entity's type.
     * @return type of the entity
     */
    function TYPE() external view returns (uint64);

    /**
     * @notice Initialize this entity contract by using a given data.
     * @param data some data to use
     */
    function initialize(
        bytes calldata data
    ) external;

    function VERSION() external view returns (uint64);

    /**
     * @notice Get the l1 registry's address.
     * @return address of the l1 registry
     */
    function L1_REGISTRY() external view returns (address);

    /**
     * @notice Get the vault factory's address.
     * @return address of the vault factory
     */
    function VAULT_FACTORY() external view returns (address);

    /**
     * @notice Get the operator-vault opt-in service's address.
     * @return address of the operator-vault opt-in service
     */
    function OPERATOR_VAULT_OPT_IN_SERVICE() external view returns (address);

    /**
     * @notice Get the operator-l1 opt-in service's address.
     * @return address of the operator-l1 opt-in service
     */
    function OPERATOR_L1_OPT_IN_SERVICE() external view returns (address);

    /**
     * @notice Get a gas limit for the hook.
     * @return value of the hook gas limit
     */
    function HOOK_GAS_LIMIT() external view returns (uint256);

    /**
     * @notice Get a reserve gas between the gas limit check and the hook's execution.
     * @return value of the reserve gas
     */
    function HOOK_RESERVE() external view returns (uint256);

    /**
     * @notice Get a hook setter's role.
     * @return assetClass of the hook setter role
     */
    function HOOK_SET_ROLE() external view returns (bytes32);

    /**
     * @notice Get the vault's address.
     * @return address of the vault
     */
    function vault() external view returns (address);

    /**
     * @notice Get the hook's address.
     * @return address of the hook
     * @dev The hook can have arbitrary logic under certain functions, however, it doesn't affect the stake guarantees.
     */
    function hook() external view returns (address);

    /**
     * @notice Get a particular asset class maximum limit
     *         (meaning the asset class is not ready to get more as a stake).
     * @param l1 address of the l1.
     * @param assetClass the uint96 assetClass.
     * @return maximum limit of the asset class
     */
    function maxL1Limit(address l1, uint96 assetClass) external view returns (uint256);

    /**
     * @notice Get a stake that a given asset class could be able to slash for a certain operator at a given timestamp
     *         until the end of the consequent epoch using hints (if no cross-slashing and no slashings by the asset class).
     * @param l1 address of the l1.
     * @param assetClass the uint96 assetClass.
     * @param operator address of the operator
     * @param timestamp time point to capture the stake at
     * @param hints hints for the checkpoints' indexes
     * @return slashable stake at the given timestamp until the end of the consequent epoch
     * @dev Warning: it is not safe to use timestamp >= current one for the stake capturing, as it can change later.
     */
    function stakeAt(
        address l1,
        uint96 assetClass,
        address operator,
        uint48 timestamp,
        bytes memory hints
    ) external view returns (uint256);

    /**
     * @notice Get a stake that a given asset class will be able to slash
     *         for a certain operator until the end of the next epoch (if no cross-slashing and no slashings by the asset class).
     * @param l1 address of the l1.
     * @param assetClass the uint96 assetClass.
     * @param operator address of the operator
     * @return slashable stake until the end of the next epoch
     * @dev Warning: this function is not safe to use for stake capturing, as it can change by the end of the block.
     */
    function stake(address l1, uint96 assetClass, address operator) external view returns (uint256);

    /**
     * @notice Set a maximum limit for a asset class (how much stake the asset class is ready to get).
     * assetClass assetClass of the asset class
     * @param l1 address of the l1
     * @param amount new maximum asset class limit
     * @dev Only a l1 can call this function.
     */
    function setMaxL1Limit(address l1, uint96 assetClass, uint256 amount) external;

    /**
     * @notice Set a new hook.
     * @param hook address of the hook
     * @dev Only a HOOK_SET_ROLE holder can call this function.
     *      The hook can have arbitrary logic under certain functions, however, it doesn't affect the stake guarantees.
     */
    function setHook(
        address hook
    ) external;

    /**
     * @notice Called when a slash happens.
     * @param l1 address of the l1.
     * @param assetClass the uint96 assetClass.
     * @param operator address of the operator
     * @param amount amount of the collateral slashed
     * @param captureTimestamp time point when the stake was captured
     * @param data some additional data
     * @dev Only the vault's slasher can call this function.
     */
    function onSlash(
        address l1,
        uint96 assetClass,
        address operator,
        uint256 amount,
        uint48 captureTimestamp,
        bytes calldata data
    ) external;
}

File 34 of 50 : IDelegatorHook.sol
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

interface IDelegatorHook {
    /**
     * @notice Called when a slash happens.
     * @param l1 address of the l1.
     * @param assetClass the uint96 assetClass.
     * @param operator address of the operator
     * @param amount amount of the collateral to be slashed
     * @param captureTimestamp time point when the stake was captured
     * @param data some additional data
     */
    function onSlash(
        address l1,
        uint96 assetClass,
        address operator,
        uint256 amount,
        uint48 captureTimestamp,
        bytes calldata data
    ) external;
}

File 35 of 50 : IVaultFactory.sol
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

import {IMigratablesFactory} from "./common/IMigratablesFactory.sol";

interface IVaultFactory is IMigratablesFactory {}

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

interface IL1Registry {
    event RegisterL1(address indexed l1);
    event SetL1Middleware(address indexed l1, address indexed l1Middleware);
    event SetMetadataURL(address indexed l1, string metadataURL);

    error L1Registry__L1AlreadyRegistered();
    error L1Registry__L1NotRegistered();
    error L1Registry__InvalidValidatorManager(address l1);
    error L1Registry__InvalidL1Middleware();
    error L1Registry__NotValidatorManagerOwner(address caller, address expectedOwner);
    error L1Registry__InsufficientFee();
    error L1Registry__FeeTransferFailed();
    error L1Registry__FeeExceedsMaximum(uint256 newFee, uint256 maxFee);
    error L1Registry__ZeroAddress(string name);
    error L1Registry__NotFeeCollector(address caller);
    error L1Registry__NoFeesToWithdraw();

    /**
     * @notice Register an Avalanche L1
     * @dev l1 must be the manager of the Avalanche L1
     * @dev msg.sender must be a SecurityModule of the l1
     * @dev l1Middleware must be a SecurityModule of the Avalanche L1
     * @param l1 The Avalanche L1. Should be The ValidatorManager.
     * @param l1Middleware The l1Middleware of the Avalanche L1
     * @param metadataURL The metadata URL of the Avalanche L1
     */
    function registerL1(
        address l1,
        address l1Middleware,
        string calldata metadataURL
    )
        /*, uint32 messageIndex, SubnetConversionData subnetConversionData*/
        external
        payable;

    /**
     * @notice Check if an address is registered as an L1
     * @param l1 The Avalanche L1. Should be The ValidatorManager.
     * @return True if the address is registered as an L1, false otherwise
     */
    function isRegistered(
        address l1
    ) external view returns (bool);

    /**
     * @notice Check if an address is registered as an L1 and if the Middleware is correct
     * @param l1 The Avalanche L1. Should be The ValidatorManager.
     * @param l1middleware_ The l1Middleware to check
     * @return True if the address is registered as an L1 and the middleware is correct, false otherwise
     */
    function isRegisteredWithMiddleware(address l1, address l1middleware_) external view returns (bool);

    /**
     * @notice Get the L1 at a specific index
     * @param index The index of the L1 to get
     * @return The address of the L1 at the specified index
     * @return The l1Middleware of the L1 at the specified index
     * @return The metadata URL of the L1 at the specified index
     */
    function getL1At(
        uint256 index
    ) external view returns (address, address, string memory);

    /**
     * @notice Get the total number of L1s
     * @return Total number of L1s
     */
    function totalL1s() external view returns (uint256);

    /**
     * @notice Get all L1s
     * @return Array of all L1s
     * @return Array of all L1s' l1Middlewares
     * @return Array of all L1s' metadata URLs
     */
    function getAllL1s() external view returns (address[] memory, address[] memory, string[] memory);

    /**
     * @notice Set the l1Middleware of an L1
     * @param l1 The Avalanche L1. Should be The ValidatorManager.
     * @param l1Middleware_ The new l1Middleware
     */
    function setL1Middleware(address l1, address l1Middleware_) external;

    /**
     * @notice Set the metadata URL of an L1
     * @param l1 The Avalanche L1. Should be The ValidatorManager.
     * @param metadataURL The new metadata URL
     */
    function setMetadataURL(address l1, string calldata metadataURL) external;
}

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

pragma solidity ^0.8.20;

import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {ERC165Upgradeable} from "../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

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

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;


    /// @custom:storage-location erc7201:openzeppelin.storage.AccessControl
    struct AccessControlStorage {
        mapping(bytes32 role => RoleData) _roles;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControl")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant AccessControlStorageLocation = 0x02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800;

    function _getAccessControlStorage() private pure returns (AccessControlStorage storage $) {
        assembly {
            $.slot := AccessControlStorageLocation
        }
    }

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

    function __AccessControl_init() internal onlyInitializing {
    }

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

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

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

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

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

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

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

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

        _revokeRole(role, callerConfirmation);
    }

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

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

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

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

pragma solidity ^0.8.20;
import {Initializable} from "../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;

    /// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
    struct ReentrancyGuardStorage {
        uint256 _status;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;

    function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
        assembly {
            $.slot := ReentrancyGuardStorageLocation
        }
    }

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

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

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        $._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 {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if ($._status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        $._status = ENTERED;
    }

    function _nonReentrantAfter() private {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        // 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) {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        return $._status == ENTERED;
    }
}

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

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
    struct InitializableStorage {
        /**
         * @dev Indicates that the contract has been initialized.
         */
        uint64 _initialized;
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool _initializing;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

    /**
     * @dev The contract is already initialized.
     */
    error InvalidInitialization();

    /**
     * @dev The contract is not initializing.
     */
    error NotInitializing();

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint64 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        // Cache values to avoid duplicated sloads
        bool isTopLevelCall = !$._initializing;
        uint64 initialized = $._initialized;

        // Allowed calls:
        // - initialSetup: the contract is not in the initializing state and no previous version was
        //                 initialized
        // - construction: the contract is initialized at version 1 (no reininitialization) and the
        //                 current contract is just being deployed
        bool initialSetup = initialized == 0 && isTopLevelCall;
        bool construction = initialized == 1 && address(this).code.length == 0;

        if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }
        $._initialized = 1;
        if (isTopLevelCall) {
            $._initializing = true;
        }
        _;
        if (isTopLevelCall) {
            $._initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint64 version) {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing || $._initialized >= version) {
            revert InvalidInitialization();
        }
        $._initialized = version;
        $._initializing = true;
        _;
        $._initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        _checkInitializing();
        _;
    }

    /**
     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
    function _checkInitializing() internal view virtual {
        if (!_isInitializing()) {
            revert NotInitializing();
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing) {
            revert InvalidInitialization();
        }
        if ($._initialized != type(uint64).max) {
            $._initialized = type(uint64).max;
            emit Initialized(type(uint64).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _getInitializableStorage()._initializing;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        assembly {
            $.slot := INITIALIZABLE_STORAGE
        }
    }
}

// (c) 2022-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

struct WarpMessage {
  bytes32 sourceChainID;
  address originSenderAddress;
  bytes payload;
}

struct WarpBlockHash {
  bytes32 sourceChainID;
  bytes32 blockHash;
}

interface IWarpMessenger {
  event SendWarpMessage(address indexed sender, bytes32 indexed messageID, bytes message);

  // sendWarpMessage emits a request for the subnet to send a warp message from [msg.sender]
  // with the specified parameters.
  // This emits a SendWarpMessage log from the precompile. When the corresponding block is accepted
  // the Accept hook of the Warp precompile is invoked with all accepted logs emitted by the Warp
  // precompile.
  // Each validator then adds the UnsignedWarpMessage encoded in the log to the set of messages
  // it is willing to sign for an off-chain relayer to aggregate Warp signatures.
  function sendWarpMessage(bytes calldata payload) external returns (bytes32 messageID);

  // getVerifiedWarpMessage parses the pre-verified warp message in the
  // predicate storage slots as a WarpMessage and returns it to the caller.
  // If the message exists and passes verification, returns the verified message
  // and true.
  // Otherwise, returns false and the empty value for the message.
  function getVerifiedWarpMessage(uint32 index) external view returns (WarpMessage calldata message, bool valid);

  // getVerifiedWarpBlockHash parses the pre-verified WarpBlockHash message in the
  // predicate storage slots as a WarpBlockHash message and returns it to the caller.
  // If the message exists and passes verification, returns the verified message
  // and true.
  // Otherwise, returns false and the empty value for the message.
  function getVerifiedWarpBlockHash(
    uint32 index
  ) external view returns (WarpBlockHash calldata warpBlockHash, bool valid);

  // getBlockchainID returns the snow.Context BlockchainID of this chain.
  // This blockchainID is the hash of the transaction that created this blockchain on the P-Chain
  // and is not related to the Ethereum ChainID.
  function getBlockchainID() external view returns (bytes32 blockchainID);
}

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

pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";

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

    function __Context_init_unchained() internal onlyInitializing {
    }
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

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

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

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

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
    struct InitializableStorage {
        /**
         * @dev Indicates that the contract has been initialized.
         */
        uint64 _initialized;
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool _initializing;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

    /**
     * @dev The contract is already initialized.
     */
    error InvalidInitialization();

    /**
     * @dev The contract is not initializing.
     */
    error NotInitializing();

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint64 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        // Cache values to avoid duplicated sloads
        bool isTopLevelCall = !$._initializing;
        uint64 initialized = $._initialized;

        // Allowed calls:
        // - initialSetup: the contract is not in the initializing state and no previous version was
        //                 initialized
        // - construction: the contract is initialized at version 1 (no reininitialization) and the
        //                 current contract is just being deployed
        bool initialSetup = initialized == 0 && isTopLevelCall;
        bool construction = initialized == 1 && address(this).code.length == 0;

        if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }
        $._initialized = 1;
        if (isTopLevelCall) {
            $._initializing = true;
        }
        _;
        if (isTopLevelCall) {
            $._initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint64 version) {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing || $._initialized >= version) {
            revert InvalidInitialization();
        }
        $._initialized = version;
        $._initializing = true;
        _;
        $._initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        _checkInitializing();
        _;
    }

    /**
     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
    function _checkInitializing() internal view virtual {
        if (!_isInitializing()) {
            revert NotInitializing();
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing) {
            revert InvalidInitialization();
        }
        if ($._initialized != type(uint64).max) {
            $._initialized = type(uint64).max;
            emit Initialized(type(uint64).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _getInitializableStorage()._initializing;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        assembly {
            $.slot := INITIALIZABLE_STORAGE
        }
    }
}

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

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

            return true;
        } else {
            return false;
        }
    }

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

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

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

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

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

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

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

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

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

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

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

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

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

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

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

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

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

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

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

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

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

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

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

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

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

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}

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

pragma solidity ^0.8.20;

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

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

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

pragma solidity ^0.8.20;

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

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

interface IBaseSlasher {
    error BaseSlasher__NoBurner();
    error BaseSlasher__InsufficientBurnerGas();
    error BaseSlasher__NotNetworkMiddleware();
    error BaseSlasher__NotVault();
    error BaseSlasher__NotInitialized();

    /**
     * @notice Base parameters needed for slashers' deployment.
     * @param isBurnerHook if the burner is needed to be called on a slashing
     */
    struct BaseParams {
        bool isBurnerHook;
    }

    /**
     * @notice Hints for a slashable stake.
     * @param stakeHints hints for the stake checkpoints
     * @param cumulativeSlashFromHint hint for the cumulative slash amount at a capture timestamp
     */
    struct SlashableStakeHints {
        bytes stakeHints;
        bytes cumulativeSlashFromHint;
    }

    /**
     * @notice General data for the delegator.
     * @param slasherType type of the slasher
     * @param data slasher-dependent data for the delegator
     */
    struct GeneralDelegatorData {
        uint64 slasherType;
        bytes data;
    }

    /**
     * @notice Get the factory's address.
     * @return address of the factory
     */
    function FACTORY() external view returns (address);

    /**
     * @notice Get the entity's type.
     * @return type of the entity
     */
    function TYPE() external view returns (uint64);

    /**
     * @notice Initialize this entity contract by using a given data.
     * @param data some data to use
     */
    function initialize(
        bytes calldata data
    ) external;

    /**
     * @notice Get a gas limit for the burner.
     * @return value of the burner gas limit
     */
    function BURNER_GAS_LIMIT() external view returns (uint256);

    /**
     * @notice Get a reserve gas between the gas limit check and the burner's execution.
     * @return value of the reserve gas
     */
    function BURNER_RESERVE() external view returns (uint256);

    /**
     * @notice Get the vault factory's address.
     * @return address of the vault factory
     */
    function VAULT_FACTORY() external view returns (address);

    /**
     * @notice Get the network middleware service's address.
     * @return address of the network middleware service
     */
    function NETWORK_MIDDLEWARE_SERVICE() external view returns (address);

    /**
     * @notice Get the vault's address.
     * @return address of the vault to perform slashings on
     */
    function vault() external view returns (address);

    /**
     * @notice Get if the burner is needed to be called on a slashing.
     * @return if the burner is a hook
     */
    function isBurnerHook() external view returns (bool);

    /**
     * @notice Get the latest capture timestamp that was slashed on a subnetwork.
     * @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
     * @param operator address of the operator
     * @return latest capture timestamp that was slashed
     */
    function latestSlashedCaptureTimestamp(bytes32 subnetwork, address operator) external view returns (uint48);

    /**
     * @notice Get a cumulative slash amount for an operator on a subnetwork until a given timestamp (inclusively) using a hint.
     * @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
     * @param operator address of the operator
     * @param timestamp time point to get the cumulative slash amount until (inclusively)
     * @param hint hint for the checkpoint index
     * @return cumulative slash amount until the given timestamp (inclusively)
     */
    function cumulativeSlashAt(
        bytes32 subnetwork,
        address operator,
        uint48 timestamp,
        bytes memory hint
    ) external view returns (uint256);

    /**
     * @notice Get a cumulative slash amount for an operator on a subnetwork.
     * @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
     * @param operator address of the operator
     * @return cumulative slash amount
     */
    function cumulativeSlash(bytes32 subnetwork, address operator) external view returns (uint256);

    /**
     * @notice Get a slashable amount of a stake got at a given capture timestamp using hints.
     * @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
     * @param operator address of the operator
     * @param captureTimestamp time point to get the stake amount at
     * @param hints hints for the checkpoints' indexes
     * @return slashable amount of the stake
     */
    function slashableStake(
        bytes32 subnetwork,
        address operator,
        uint48 captureTimestamp,
        bytes memory hints
    ) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2024 ADDPHO

pragma solidity ^0.8.0;

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

interface IMigratablesFactory is IRegistry {
    error MigratableFactory__AlreadyBlacklisted();
    error MigratableFactory__AlreadyWhitelisted();
    error MigratableFactory__InvalidImplementation();
    error MigratableFactory__InvalidVersion();
    error MigratableFactory__NotOwner();
    error MigratableFactory__OldVersion();
    error MigratableFactory__VersionBlacklisted();

    /**
     * @notice Emitted when a new implementation is whitelisted.
     * @param implementation address of the new implementation
     */
    event Whitelist(address indexed implementation);

    /**
     * @notice Emitted when a version is blacklisted (e.g., in case of invalid implementation).
     * @param version version that was blacklisted
     * @dev The given version is still deployable.
     */
    event Blacklist(uint64 indexed version);

    /**
     * @notice Emitted when an entity is migrated to a new version.
     * @param entity address of the entity
     * @param newVersion new version of the entity
     */
    event Migrate(address indexed entity, uint64 newVersion);

    /**
     * @notice Get the last available version.
     * @return version of the last implementation
     * @dev If zero, no implementations are whitelisted.
     */
    function lastVersion() external view returns (uint64);

    /**
     * @notice Get the implementation for a given version.
     * @param version version to get the implementation for
     * @return address of the implementation
     * @dev Reverts when an invalid version.
     */
    function implementation(
        uint64 version
    ) external view returns (address);

    /**
     * @notice Get if a version is blacklisted (e.g., in case of invalid implementation).
     * @param version version to check
     * @return whether the version is blacklisted
     * @dev The given version is still deployable.
     */
    function blacklisted(
        uint64 version
    ) external view returns (bool);

    /**
     * @notice Whitelist a new implementation for entities.
     * @param implementation address of the new implementation
     */
    function whitelist(
        address implementation
    ) external;

    /**
     * @notice Blacklist a version of entities.
     * @param version version to blacklist
     * @dev The given version will still be deployable.
     */
    function blacklist(
        uint64 version
    ) external;

    /**
     * @notice Create a new entity at the factory.
     * @param version entity's version to use
     * @param owner initial owner of the entity
     * @param data initial data for the entity creation
     * @param delegatorFactory address of the delegator factory
     * @param slasherFactory address of the slashers factory
     * @return address of the entity
     * @dev CREATE2 salt is constructed from the given parameters.
     */
    function create(
        uint64 version,
        address owner,
        bytes calldata data,
        address delegatorFactory,
        address slasherFactory
    ) external returns (address);

    /**
     * @notice Migrate a given entity to a given newer version.
     * @param entity address of the entity to migrate`
     * @param newVersion new version to migrate to
     * @param data some data to reinitialize the contract with
     * @dev Only the entity's owner can call this function.
     */
    function migrate(address entity, uint64 newVersion, bytes calldata data) external;
}

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

pragma solidity ^0.8.20;

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

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

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

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

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

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

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

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

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

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

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

pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";

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

    function __Context_init_unchained() internal onlyInitializing {
    }
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

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

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

pragma solidity ^0.8.20;

import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";

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

    function __ERC165_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

Settings
{
  "remappings": [
    "@suzaku/collateral/=lib/collateral/src/",
    "@suzaku/core/=lib/suzaku-core/",
    "@suzaku/contracts-lib/=lib/suzaku-contracts-library/",
    "@suzaku/contracts-library/=lib/suzaku-contracts-library/src/",
    "@avalabs/teleporter/=lib/suzaku-contracts-library/lib/icm-contracts/contracts/",
    "@avalabs/icm-contracts/=lib/suzaku-contracts-library/lib/icm-contracts/contracts/",
    "@avalabs/[email protected]/=lib/suzaku-contracts-library/lib/icm-contracts/lib/subnet-evm/contracts/",
    "@forge-std/=lib/suzaku-contracts-library/lib/icm-contracts/lib/forge-std/src/",
    "@mocks/=lib/suzaku-contracts-library/lib/icm-contracts/contracts/mocks/",
    "@openzeppelin/contracts-upgradeable/=lib/collateral/lib/openzeppelin-contracts-upgradeable/contracts/",
    "@openzeppelin/[email protected]/=lib/suzaku-contracts-library/lib/openzeppelin-contracts-upgradeable/contracts/",
    "@openzeppelin/contracts/=lib/collateral/lib/openzeppelin-contracts/contracts/",
    "@openzeppelin/[email protected]/=lib/suzaku-contracts-library/lib/openzeppelin-contracts/contracts/",
    "@openzeppelin/foundry-upgrades/=lib/suzaku-core/lib/openzeppelin-foundry-upgrades/src/",
    "@symbiotic/core/=lib/suzaku-core/lib/core/src/",
    "@teleporter/=lib/suzaku-contracts-library/lib/icm-contracts/contracts/teleporter/",
    "@utilities/=lib/suzaku-contracts-library/lib/icm-contracts/contracts/utilities/",
    "collateral/=lib/collateral/",
    "core/=lib/suzaku-core/lib/core/",
    "ds-test/=lib/collateral/lib/permit2/lib/solmate/lib/ds-test/src/",
    "erc4626-tests/=lib/collateral/lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
    "forge-gas-snapshot/=lib/collateral/lib/permit2/lib/forge-gas-snapshot/src/",
    "forge-std/=lib/forge-std/src/",
    "icm-contracts/=lib/suzaku-contracts-library/lib/icm-contracts/contracts/",
    "openzeppelin-contracts-upgradeable/=lib/collateral/lib/openzeppelin-contracts-upgradeable/",
    "openzeppelin-contracts/=lib/collateral/lib/openzeppelin-contracts/",
    "openzeppelin-foundry-upgrades/=lib/suzaku-core/lib/openzeppelin-foundry-upgrades/src/",
    "permit2/=lib/collateral/lib/permit2/",
    "solidity-stringutils/=lib/suzaku-core/lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/",
    "solmate/=lib/collateral/lib/permit2/lib/solmate/",
    "subnet-evm/=lib/suzaku-contracts-library/lib/icm-contracts/lib/subnet-evm/",
    "suzaku-contracts-library/=lib/suzaku-contracts-library/",
    "suzaku-core/=lib/suzaku-core/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "cancun",
  "viaIR": true,
  "libraries": {}
}

Contract ABI

API
[{"inputs":[{"components":[{"internalType":"address","name":"l1ValidatorManager","type":"address"},{"internalType":"address","name":"operatorRegistry","type":"address"},{"internalType":"address","name":"vaultRegistry","type":"address"},{"internalType":"address","name":"operatorL1Optin","type":"address"},{"internalType":"uint48","name":"epochDuration","type":"uint48"},{"internalType":"uint48","name":"slashingWindow","type":"uint48"},{"internalType":"uint48","name":"stakeUpdateWindow","type":"uint48"}],"internalType":"struct AvalancheL1MiddlewareSettings","name":"settings","type":"tuple"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"primaryAsset","type":"address"},{"internalType":"uint256","name":"primaryAssetMaxStake","type":"uint256"},{"internalType":"uint256","name":"primaryAssetMinStake","type":"uint256"},{"internalType":"uint256","name":"primaryAssetWeightScaleFactor","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AssetClassRegistry__AssetAlreadyRegistered","type":"error"},{"inputs":[],"name":"AssetClassRegistry__AssetClassAlreadyExists","type":"error"},{"inputs":[],"name":"AssetClassRegistry__AssetClassNotFound","type":"error"},{"inputs":[{"internalType":"uint256","name":"assetClassId","type":"uint256"}],"name":"AssetClassRegistry__AssetIsPrimaryAssetClass","type":"error"},{"inputs":[],"name":"AssetClassRegistry__AssetNotFound","type":"error"},{"inputs":[],"name":"AssetClassRegistry__AssetsStillExist","type":"error"},{"inputs":[],"name":"AssetClassRegistry__InvalidAsset","type":"error"},{"inputs":[],"name":"AssetClassRegistry__InvalidStakingRequirements","type":"error"},{"inputs":[{"internalType":"uint256","name":"assetClassId","type":"uint256"}],"name":"AvalancheL1Middleware__ActiveSecondaryAssetCLass","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint48","name":"epoch","type":"uint48"}],"name":"AvalancheL1Middleware__AlreadyRebalanced","type":"error"},{"inputs":[{"internalType":"uint256","name":"assetClassId","type":"uint256"}],"name":"AvalancheL1Middleware__AssetClassNotActive","type":"error"},{"inputs":[{"internalType":"uint256","name":"assetClassId","type":"uint256"}],"name":"AvalancheL1Middleware__AssetStillInUse","type":"error"},{"inputs":[{"internalType":"address","name":"collateral","type":"address"},{"internalType":"uint96","name":"assetClassId","type":"uint96"}],"name":"AvalancheL1Middleware__CollateralNotInAssetClass","type":"error"},{"inputs":[{"internalType":"uint48","name":"epochStartTs","type":"uint48"}],"name":"AvalancheL1Middleware__EpochError","type":"error"},{"inputs":[],"name":"AvalancheL1Middleware__InvalidScaleFactor","type":"error"},{"inputs":[{"internalType":"uint48","name":"epochsPending","type":"uint48"},{"internalType":"uint48","name":"maxAutoUpdates","type":"uint48"}],"name":"AvalancheL1Middleware__ManualEpochUpdateRequired","type":"error"},{"inputs":[],"name":"AvalancheL1Middleware__MaxL1LimitZero","type":"error"},{"inputs":[],"name":"AvalancheL1Middleware__NoEpochsToProcess","type":"error"},{"inputs":[],"name":"AvalancheL1Middleware__NoSlasher","type":"error"},{"inputs":[],"name":"AvalancheL1Middleware__NodeNotActive","type":"error"},{"inputs":[{"internalType":"bytes32","name":"nodeId","type":"bytes32"}],"name":"AvalancheL1Middleware__NodeNotFound","type":"error"},{"inputs":[{"internalType":"bytes32","name":"nodeId","type":"bytes32"}],"name":"AvalancheL1Middleware__NodePendingRemoval","type":"error"},{"inputs":[{"internalType":"bytes32","name":"nodeId","type":"bytes32"}],"name":"AvalancheL1Middleware__NodePendingUpdate","type":"error"},{"inputs":[{"internalType":"bytes32","name":"validationID","type":"bytes32"}],"name":"AvalancheL1Middleware__NodeStateNotUpdated","type":"error"},{"inputs":[{"internalType":"uint256","name":"newStake","type":"uint256"}],"name":"AvalancheL1Middleware__NotEnoughFreeStake","type":"error"},{"inputs":[],"name":"AvalancheL1Middleware__NotEnoughFreeStakeSecondaryAssetClasses","type":"error"},{"inputs":[{"internalType":"uint48","name":"timeNow","type":"uint48"},{"internalType":"uint48","name":"epochUpdatePeriod","type":"uint48"}],"name":"AvalancheL1Middleware__NotEpochUpdatePeriod","type":"error"},{"inputs":[],"name":"AvalancheL1Middleware__NotImplemented","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"AvalancheL1Middleware__OperatorAlreadyRegistered","type":"error"},{"inputs":[{"internalType":"uint48","name":"disabledTime","type":"uint48"},{"internalType":"uint48","name":"slashingWindow","type":"uint48"}],"name":"AvalancheL1Middleware__OperatorGracePeriodNotPassed","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"l1ValidatorManager","type":"address"}],"name":"AvalancheL1Middleware__OperatorNotOptedIn","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"AvalancheL1Middleware__OperatorNotRegistered","type":"error"},{"inputs":[{"internalType":"uint256","name":"securityModuleCapacity","type":"uint256"},{"internalType":"uint256","name":"minStake","type":"uint256"}],"name":"AvalancheL1Middleware__SecurityModuleCapacityNotEnough","type":"error"},{"inputs":[{"internalType":"uint48","name":"slashingWindow","type":"uint48"},{"internalType":"uint48","name":"epochDuration","type":"uint48"}],"name":"AvalancheL1Middleware__SlashingWindowTooShort","type":"error"},{"inputs":[{"internalType":"uint256","name":"newStake","type":"uint256"},{"internalType":"uint256","name":"maxStake","type":"uint256"}],"name":"AvalancheL1Middleware__StakeTooHigh","type":"error"},{"inputs":[{"internalType":"uint256","name":"newStake","type":"uint256"},{"internalType":"uint256","name":"minStake","type":"uint256"}],"name":"AvalancheL1Middleware__StakeTooLow","type":"error"},{"inputs":[],"name":"AvalancheL1Middleware__TooBigSlashAmount","type":"error"},{"inputs":[{"internalType":"uint48","name":"requested","type":"uint48"},{"internalType":"uint48","name":"pending","type":"uint48"}],"name":"AvalancheL1Middleware__TooManyEpochsRequested","type":"error"},{"inputs":[{"internalType":"bytes32","name":"validationId","type":"bytes32"}],"name":"AvalancheL1Middleware__WeightUpdateNotPending","type":"error"},{"inputs":[{"internalType":"bytes32","name":"validationID","type":"bytes32"}],"name":"AvalancheL1Middleware__WeightUpdatePending","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"AvalancheL1Middleware__ZeroAddress","type":"error"},{"inputs":[{"internalType":"bytes32","name":"key","type":"bytes32"}],"name":"EnumerableMapNonexistentKey","type":"error"},{"inputs":[],"name":"MapWithTimeData__AlreadyAdded","type":"error"},{"inputs":[],"name":"MapWithTimeData__AlreadyDisabled","type":"error"},{"inputs":[],"name":"MapWithTimeData__AlreadyEnabled","type":"error"},{"inputs":[],"name":"MapWithTimeData__NotEnabled","type":"error"},{"inputs":[],"name":"MiddlewareUtils__OverflowInStakeToWeight","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"uint8","name":"bits","type":"uint8"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"SafeCastOverflowedUintDowncast","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"uint256","name":"newStake","type":"uint256"}],"name":"AllNodeStakesUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"assetClassId","type":"uint256"},{"indexed":true,"internalType":"address","name":"asset","type":"address"}],"name":"AssetAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"assetClassId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"primaryAssetMinStake","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"primaryAssetMaxStake","type":"uint256"}],"name":"AssetClassAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"assetClassId","type":"uint256"}],"name":"AssetClassRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"assetClassId","type":"uint256"},{"indexed":true,"internalType":"address","name":"asset","type":"address"}],"name":"AssetRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"bytes32","name":"nodeId","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"stake","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"validationID","type":"bytes32"}],"name":"NodeAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"bytes32","name":"nodeId","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"validationID","type":"bytes32"}],"name":"NodeRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint48","name":"upToEpoch","type":"uint48"},{"indexed":false,"internalType":"uint48","name":"epochsProcessedCount","type":"uint48"}],"name":"NodeStakeCacheManuallyProcessed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"bytes32","name":"nodeId","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"newStake","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"validationID","type":"bytes32"}],"name":"NodeStakeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"uint256","name":"leftoverStake","type":"uint256"}],"name":"OperatorHasLeftoverStake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldVaultManager","type":"address"},{"indexed":true,"internalType":"address","name":"newVaultManager","type":"address"}],"name":"VaultManagerUpdated","type":"event"},{"inputs":[],"name":"EPOCH_DURATION","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"L1_VALIDATOR_MANAGER","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_AUTO_EPOCH_UPDATES","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OPERATOR_L1_OPTIN","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OPERATOR_REGISTRY","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRIMARY_ASSET","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRIMARY_ASSET_CLASS","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASHING_WINDOW","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"START_TIME","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UPDATE_WINDOW","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WEIGHT_SCALE_FACTOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assetClassId","type":"uint256"}],"name":"activateSecondaryAssetClass","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assetClassId","type":"uint256"},{"internalType":"uint256","name":"minValidatorStake","type":"uint256"},{"internalType":"uint256","name":"maxValidatorStake","type":"uint256"},{"internalType":"address","name":"initialAsset","type":"address"}],"name":"addAssetClass","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assetClassId","type":"uint256"},{"internalType":"address","name":"asset","type":"address"}],"name":"addAssetToClass","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"nodeId","type":"bytes32"},{"internalType":"bytes","name":"blsKey","type":"bytes"},{"internalType":"uint64","name":"registrationExpiry","type":"uint64"},{"components":[{"internalType":"uint32","name":"threshold","type":"uint32"},{"internalType":"address[]","name":"addresses","type":"address[]"}],"internalType":"struct PChainOwner","name":"remainingBalanceOwner","type":"tuple"},{"components":[{"internalType":"uint32","name":"threshold","type":"uint32"},{"internalType":"address[]","name":"addresses","type":"address[]"}],"internalType":"struct PChainOwner","name":"disableOwner","type":"tuple"},{"internalType":"uint256","name":"stakeAmount","type":"uint256"}],"name":"addNode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"balancerValidatorManager","outputs":[{"internalType":"contract BalancerValidatorManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"calcAndCacheNodeStakeForAllOperators","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"},{"internalType":"uint96","name":"assetClassId","type":"uint96"}],"name":"calcAndCacheStakes","outputs":[{"internalType":"uint256","name":"totalStake","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"nodeId","type":"bytes32"},{"internalType":"uint32","name":"messageIndex","type":"uint32"}],"name":"completeStakeUpdate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bytes32","name":"nodeId","type":"bytes32"},{"internalType":"uint32","name":"messageIndex","type":"uint32"}],"name":"completeValidatorRegistration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"messageIndex","type":"uint32"}],"name":"completeValidatorRemoval","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assetClassId","type":"uint256"}],"name":"deactivateSecondaryAssetClass","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"disableOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"enableOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint256","name":"limitStake","type":"uint256"}],"name":"forceUpdateNodes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getActiveAssetClasses","outputs":[{"internalType":"uint256","name":"primary","type":"uint256"},{"internalType":"uint256[]","name":"secondaries","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint48","name":"epoch","type":"uint48"}],"name":"getActiveNodesForEpoch","outputs":[{"internalType":"bytes32[]","name":"activeNodeIds","type":"bytes32[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllOperators","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAssetClassIds","outputs":[{"internalType":"uint96[]","name":"","type":"uint96[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assetClassId","type":"uint256"}],"name":"getClassAssets","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assetClassId","type":"uint256"}],"name":"getClassStakingRequirements","outputs":[{"internalType":"uint256","name":"minStake","type":"uint256"},{"internalType":"uint256","name":"maxStake","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentEpoch","outputs":[{"internalType":"uint48","name":"epoch","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"timestamp","type":"uint48"}],"name":"getEpochAtTs","outputs":[{"internalType":"uint48","name":"epoch","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"}],"name":"getEpochStartTs","outputs":[{"internalType":"uint48","name":"timestamp","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"},{"internalType":"bytes32","name":"validationID","type":"bytes32"}],"name":"getNodeStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"getOperatorAvailableStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"getOperatorNodesLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint48","name":"epoch","type":"uint48"},{"internalType":"uint96","name":"assetClassId","type":"uint96"}],"name":"getOperatorStake","outputs":[{"internalType":"uint256","name":"stake","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"getOperatorUsedStakeCached","outputs":[{"internalType":"uint256","name":"registeredStake","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"},{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint96","name":"assetClass","type":"uint96"}],"name":"getOperatorUsedStakeCachedPerEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"},{"internalType":"uint96","name":"assetClassId","type":"uint96"}],"name":"getTotalStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVaultManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"nodeId","type":"bytes32"},{"internalType":"uint256","name":"stakeAmount","type":"uint256"}],"name":"initializeValidatorStakeUpdate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"assetClassId","type":"uint96"}],"name":"isActiveAssetClass","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assetClassId","type":"uint256"},{"internalType":"address","name":"asset","type":"address"}],"name":"isAssetInClass","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastGlobalNodeStakeUpdateEpoch","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"numEpochsToProcess","type":"uint48"}],"name":"manualProcessNodeStakeCache","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"nodePendingRemoval","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"nodePendingUpdate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"","type":"uint48"},{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"nodeStakeCache","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"operatorLockedStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"operatorNodesArray","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"","type":"uint48"},{"internalType":"uint96","name":"","type":"uint96"},{"internalType":"address","name":"","type":"address"}],"name":"operatorStakeCache","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint48","name":"","type":"uint48"}],"name":"rebalancedThisEpoch","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"registerOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assetClassId","type":"uint256"}],"name":"removeAssetClass","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assetClassId","type":"uint256"},{"internalType":"address","name":"asset","type":"address"}],"name":"removeAssetFromClass","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"nodeId","type":"bytes32"}],"name":"removeNode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"removeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"vaultManager_","type":"address"}],"name":"setVaultManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint48","name":"epoch","type":"uint48"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint96","name":"assetClassId","type":"uint96"}],"name":"slash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint48","name":"","type":"uint48"},{"internalType":"uint96","name":"","type":"uint96"}],"name":"totalStakeCache","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint48","name":"","type":"uint48"},{"internalType":"uint96","name":"","type":"uint96"}],"name":"totalStakeCached","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

6101a0346105d8576001600160401b039061615d38819003601f8101601f19168301848111848210176105c45783928291604052833981010361018081126105d85760e0136105d8576040519160e08301908111838210176105c457604052610067816105dc565b8252610075602082016105dc565b6020830152610086604082016105dc565b6040830152610097606082016105dc565b60608301526100a8608082016105f0565b60808301526100b960a082016105f0565b60a08301526100ca60c082016105f0565b60c08301526100db60e082016105dc565b906100e961010082016105dc565b92610120820151926101606101408401519301519060018060a01b031680156105ac575f80546001600160a01b031981168317825560405192916001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a382516001600160a01b031615610574575060208201516001600160a01b03161561053b5760408201516001600160a01b0316156105055760608201516001600160a01b0316156104cd576001600160a01b038516156104985765ffffffffffff60a08301511665ffffffffffff60808401511680821061047a57505080156104685765ffffffffffff4211610449574265ffffffffffff9081166101405260808381015182166101005283516001600160a01b039081169091526020840151811660a09081526060850151821660c09081529085015183166101205260e088905284015190911661016052610180919091529051600a80546001600160a01b031916919092161790558181116104375761026b610603565b156104255760407f8828c5448d14dfabfef1825a63323c740b9f79563a375c10bfff3215f754c10991600193845f526003602052806003845f20846002820155015582519182526020820152a260015f5260036020526102f46001600160a01b0382167fa15bc60c955c405d20d9149c709e2460f1c2d9a497496a7f46004d1772c3054c610687565b1561041357604051906001600160a01b031660017fc53536963369dbfa4c398238ebb9b09fce3943a140928bd25d3052a8a9cacdaf5f80a3615a7990816106e482396080518181816115dc015281816124730152818161323f015281816135ae015261387a015260a051818181611858015261241d015260c051818181611d9a01526124a6015260e05181818161042d0152611fe1015261010051818181610a200152818161154c01528181612d3e0152613f2501526101205181818161140d0152818161144001528181612741015261405501526101405181818161066d01528181612d700152613ef60152610160518181816109670152611b8d0152610180518181816110c40152818161162301528181614ac601526152230152f35b60405163ad2ac00f60e01b8152600490fd5b604051638e2262ff60e01b8152600490fd5b604051633631e2dd60e21b8152600490fd5b6040516306dfcc6560e41b815260306004820152426024820152604490fd5b6040516309ccb72f60e41b8152600490fd5b604492506040519163255fb47f60e21b835260048301526024820152fd5b604051635d273a4360e11b815260206004820152600c60248201526b1c1c9a5b585c9e505cdcd95d60a21b6044820152606490fd5b604051635d273a4360e11b815260206004820152600f60248201526e37b832b930ba37b92618a7b83a34b760891b6044820152606490fd5b604051635d273a4360e11b815260206004820152600d60248201526c7661756c74526567697374727960981b6044820152606490fd5b604051635d273a4360e11b815260206004820152601060248201526f6f70657261746f72526567697374727960801b6044820152606490fd5b635d273a4360e11b81526020600482015260126024820152713618ab30b634b230ba37b926b0b730b3b2b960711b6044820152606490fd5b604051631e4fbdf760e01b81525f6004820152602490fd5b634e487b7160e01b5f52604160045260245ffd5b5f80fd5b51906001600160a01b03821682036105d857565b519065ffffffffffff821682036105d857565b60015f81905260026020527fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e054610682578054680100000000000000008110156105c45781810180835581101561066e578190815f5260205f2001558054600260205260405f205590565b634e487b7160e01b5f52603260045260245ffd5b505f90565b6001810190825f528160205260405f2054155f146106dc578054680100000000000000008110156105c4576001810180835581101561066e578390825f5260205f20015554915f5260205260405f2055600190565b5050505f9056fe608080604052600436101561001c575b50361561001a575f80fd5b005b5f905f3560e01c9081630e4e7bd514612b115750806311369f0f14612af357806316c58824146129d85780631af4b5af1461295e5780631b834ed3146129065780631d34a34f1461276557806322207cc71461272357806327eb17b2146125ee5780632b84d6b414610b025780632ee59f2d146125c85780633682a4501461239a5780633708d256146122e75780633b013969146122c55780633bc7daf11461207f5780633c7b1d371461201057806342cc179814611fcb5780634fe5b9af14611f5757806352c6f00c14611f22578063554bc85914611e6057806356e1c9e914611dc95780635c04a2d814611e0a5780635f3710d014611dc9578063642d5a2314611d84578063715018a614611d2b57806379b5c25e14611d125780637b426a3b14611cc05780637d3ef55d14611c095780637d7d7e4414611bb15780637ff0d8e514611b6e57806381b1d0be14611b1b578063822ca0461461188757806383ce0322146118425780638da5cb5b1461181b5780638fb4bfbd146117ec57806396523059146117835780639681d940146116be57806396f0bcd21461168957806399e3d630146116655780639d94a1e3146116465780639f4191251461160b578063a2702f5c146115c6578063a365bf8814611570578063a70b9f0c1461152d578063a73e2333146114f4578063a7949eca146114c7578063a7dab16714611498578063ac8a584a14611368578063aeb5803d14610da2578063b2834db714610c67578063b36248e814610c3e578063b543503e14610b7a578063b97dd9e214610b4b578063b9bbb6f514610b1e578063ba14c58c14610b02578063c1c2e42b14610a5f578063c84a1242146108f0578063cbbb83d1146108cc578063d911c63214610849578063dc625d92146106ff578063dd307b9914610691578063ddaa26ad1461064e578063e3839ba314610619578063f2fde38b14610591578063f56408ed146104af578063f944e5c4146104765763fa9952f80361000f573461047357604036600319011261047357600435610316612bcb565b9061031f614866565b61033061032b426158c4565b613ef0565b65ffffffffffff80600454169116908111610455575b50600181148061041f575b6104075761035f82826153fd565b6103ef5761036b614866565b8083526003602052604083206001600160a01b039092169161038e9083906157dd565b156103ba577f03c0a1eb08ee9b373b2eee0ff81874c2eecc632e962925ae0fa3cfac84c594f08380a380f35b825260026020526040822054156103dd57604051631595a2fb60e31b8152600490fd5b60405163366eedf560e01b8152600490fd5b6024906040519063c37ba64d60e01b82526004820152fd5b602490604051906318973d4360e11b82526004820152fd5b506001600160a01b038281167f000000000000000000000000000000000000000000000000000000000000000090911614610351565b61045d613b3a565b65ffffffffffff1960045416176004555f610346565b80fd5b5034610473576020366003190112610473576020906040906001600160a01b0361049e612bb5565b168152600d83522054604051908152f35b5034610473576020366003190112610473576104c9612bb5565b6104d1614866565b6104dd61032b426158c4565b60045465ffffffffffff929183169083168111610573575b506001600160a01b031661050881615a14565b9180831615610561578260301c1661054f5761054b916bffffffffffff000000000000610534426158c4565b60301b1617815f52600760205260405f20556158f6565b5080f35b60405163a4cf437160e01b8152600490fd5b60405163702a3be360e01b8152600490fd5b61057b613b3a565b65ffffffffffff1960045416176004555f6104f5565b5034610473576020366003190112610473576105ab612bb5565b6105b3614866565b6001600160a01b03908116908115610600575f54826001600160601b0360a01b8216175f55167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a380f35b604051631e4fbdf760e01b815260048101849052602490fd5b5034610473576040366003190112610473576020610646610638612b5f565b610640612b89565b90614783565b604051908152f35b5034610473578060031936011261047357602060405165ffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b5034610473576020366003190112610473576106de6106ae612bb5565b6106b6614866565b6106c261032b426158c4565b65ffffffffffff806004541691169081116106e1575b50614ba2565b80f35b6106e9613b3a565b65ffffffffffff1960045416176004555f6106d8565b50346104735760203660031901126104735760043561072061032b426158c4565b65ffffffffffff8060045416911690811161082b575b50805f52600960205260405f205461081357610750614866565b60018082146107fa57818352600360205260408320546107e85761077382615738565b156103dd57818352600360205260408320908154848355806107c5575b50506003815f60028194015501557f72eb970ea8827c3fd1e7e48a5011ec25c35d5ca2cf450465fc92e06579a927a58280a280f35b82855260208520908101905b8181106107de5750610790565b5f815582016107d1565b6040516302db767160e01b8152600490fd5b6040516318973d4360e11b815260048101839052602490fd5b6024906040519063464cd05560e01b82526004820152fd5b610833613b3a565b65ffffffffffff1960045416176004555f610736565b503461047357806003193601126104735760055461086681612fb9565b916108746040519384612de0565b818352601f1961088383612fb9565b013660208501375b8181106108a457604051806108a08582612be1565b0390f35b806108b06001926153c4565b50506108bc8286613002565b90838060a01b031690520161088b565b50346104735760203660031901126104735760206106466108eb612bb5565b6151c4565b50346104735760403660031901126104735761090a612bb5565b9061091761032b426158c4565b9165ffffffffffff92838116835260136020526040832060015f5260205260ff60405f20541615610a4f575b5061095861095361032b426158c4565b612d32565b92610962426158c4565b61098c7f000000000000000000000000000000000000000000000000000000000000000086612d18565b94828216838088168210928315610a17575b5050506109ee5750906106de916109b761032b426158c4565b600454908216911681116109d0575b5060243590614201565b6109d8613b3a565b65ffffffffffff196004541617600455836109c6565b604051633566e89d60e21b815265ffffffffffff91821660048201529085166024820152604490fd5b610a45919293507f000000000000000000000000000000000000000000000000000000000000000090612d18565b16105f838161099e565b610a58906146ab565b505f610943565b503461047357602036600319011261047357600435610a7c614866565b610a8861032b426158c4565b65ffffffffffff80600454169116908111610ae4575b50610ab4815f52600260205260405f2054151590565b156103dd5760018114610ad257610aca90615992565b15610ad25780f35b604051638e2262ff60e01b8152600490fd5b610aec613b3a565b65ffffffffffff1960045416176004555f610a9e565b5034610473578060031936011261047357602060405160018152f35b503461047357806003193601126104735760045460405160309190911c6001600160a01b03168152602090f35b50346104735780600319360112610473576020610b6a61032b426158c4565b65ffffffffffff60405191168152f35b503461047357602036600319011261047357610b94612bb5565b610b9c614866565b6001600160a01b03908082168015610c0957600454928360301c167f8cfaacab0869ad5307d9175b1a62164d7c9630958cbbc7bc9918133cd6fbb02d8580a36601000000000000600160d01b031990911660309190911b6601000000000000600160d01b03161760045580f35b604051635d273a4360e11b815260206004820152600c60248201526b3b30bab63a26b0b730b3b2b960a11b6044820152606490fd5b5034610473578060031936011261047357600a546040516001600160a01b039091168152602090f35b503461047357806003193601126104735760405180916001908154808452602080940190835f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6905f5b818110610d8f5750505084610cc8910385612de0565b8351610cec610cd682612fb9565b91610ce46040519384612de0565b808352612fb9565b8185019590601f190136873782845b610d41575b50506040519380850191818652518092526040850195925b828110610d255785870386f35b83516001600160601b0316875295810195928101928401610d18565b9084969491819693949651811015610d8257806001600160601b03610d67859385613002565b5116610d738288613002565b52019091949695939295610cfb565b5095939594929194610d00565b8254845292860192918501918501610cb2565b50346104735760031960c036820112611360576001600160401b036024351161136057366023602435011215611360576001600160401b03602435600401351161136057366024803560040135813501011161136057604435906001600160401b0382168203611364576001600160401b03606435116113645760408160643536030112611364576001600160401b0360843511611364576040906084353603011261136057610e5461032b426158c4565b65ffffffffffff8116835260136020526040832060015f5260205260ff60405f20541615611350575b50610e8a61032b426158c4565b65ffffffffffff80600454169116908111611332575b50610eb6335f52600660205260405f2054151590565b1561131a57610ec433615080565b1561130857610f20602060018060a01b03600a5416604051906001600160601b031960043560601b168383015260148252610efe82612daa565b604051808095819463fd7ac5e760e01b83528660048401526024830190612e01565b03915afa9081156112fd5783916112cb575b50610f3c336151c4565b90808452601160205260ff6040852054166112b2578352601060205260ff6040842054166112995760018352600360205260408320906003600283015492015460a43515155f146112935760a4355b8181111561128b5750915b8210908115611281575b506112695760405160043560601b6001600160601b031916602082015260148152610fca81612daa565b604051928360a08101106001600160401b0360a086011117611255576001600160401b039160a08501604052845261100760243560040135613016565b6110146040519182612de0565b602480356004810135808452910160208301378560206024356004013583010152602085015216604083015261104f366064356004016140a1565b6060830152611063366084356004016140a1565b6080830152338352601460205261107f600435604085206159e0565b50338352600d60205261109760043560408520614158565b6110a361032b426158c4565b600a546020906001600160401b03906001600160a01b031661116d876110e97f0000000000000000000000000000000000000000000000000000000000000000886152d3565b9760405198899586948593634dd4b5c360e11b85526040600486015261111b815160a0604488015260e4870190612e01565b608061115b61113b8d8501519360431994858b83030160648c0152612e01565b8660408601511660848a01526060850151848a83030160a48b0152614194565b920151908683030160c4870152614194565b9116602483015203925af192831561124a578493611210575b506111b18165ffffffffffff8093168652600f60205260408620858752602052836040872055612ced565b168352600f60205260408320828452602052806040842055601060205260408320600160ff19825416179055604051908152600435907f517e1badda2dba32b4f95f89957307097e82788cb2e6da8dcddc14850406538a60203392a480f35b9092506020813d602011611242575b8161122c60209383612de0565b8101031261123e5751916111b1611186565b5f80fd5b3d915061121f565b6040513d86823e3d90fd5b634e487b7160e01b5f52604160045260245ffd5b60249060405190630ae3d83f60e21b82526004820152fd5b905081115f610fa0565b905091610f96565b81610f8b565b602460405163566c728360e11b81526004356004820152fd5b6024604051635e75d9fb60e11b81526004356004820152fd5b90506020813d6020116112f5575b816112e660209383612de0565b8101031261123e57515f610f32565b3d91506112d9565b6040513d85823e3d90fd5b604051630462cfe760e41b8152600490fd5b604051634c383c5160e01b8152336004820152602490fd5b61133a613b3a565b65ffffffffffff1960045416176004555f610ea0565b611359906146ab565b505f610e7d565b5080fd5b8280fd5b503461047357602036600319011261047357611382612bb5565b61138a614866565b61139661032b426158c4565b60045465ffffffffffff92918316908316811161147a575b506001600160a01b031690806113c383615a14565b60301c16908115908115611439575b506113ef57508061054b91835260076020525f6040842055615693565b60405163e223232360e01b815265ffffffffffff91821660048201527f00000000000000000000000000000000000000000000000000000000000000009091166024820152604490fd5b90506114657f000000000000000000000000000000000000000000000000000000000000000083612d18565b9080611470426158c4565b169116115f6113d2565b611482613b3a565b65ffffffffffff1960045416176004555f6113ae565b50346104735760203660031901126104735760ff60406020926004358152601184522054166040519015158152f35b50346104735760403660031901126104735760206106466114e6612b5f565b6114ee612b89565b90613f65565b5034610473576020366003190112610473576020906040906001600160a01b0361151c612bb5565b168152601283522054604051908152f35b5034610473578060031936011261047357602060405165ffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50346104735760403660031901126104735761158a612bb5565b6001600160a01b03168152600d60205260408120805460243592908310156104735760206115b88484612cd8565b90546040519160031b1c8152f35b50346104735780600319360112610473576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b503461047357806003193601126104735760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b5034610473576020366003190112610473576020610b6a61032b612b5f565b5034610473576020366003190112610473576020610646611684612bb5565b613dd1565b50346104735760603660031901126104735760206106466116a8612b5f565b6116b0612bcb565b6116b8612b9f565b91613bc6565b50346104735760203660031901126104735760043563ffffffff811680910361123e5781906116ef61032b426158c4565b65ffffffffffff80600454169116908111611765575b50600a546001600160a01b031690813b1561176157829160248392604051948593849263467ef06f60e01b845260048401525af18015611756576117465750f35b61174f90612d97565b6104735780f35b6040513d84823e3d90fd5b5050fd5b61176d613b3a565b65ffffffffffff1960045416176004555f611705565b5034610473576040366003190112610473576004356117a0612bcb565b6117a8614866565b6117bd825f52600260205260405f2054151590565b156103dd576001600160a01b038116156117da576106de9161501a565b604051630a32281360e21b8152600490fd5b50346104735760203660031901126104735760ff60406020926004358152601084522054166040519015158152f35b5034610473578060031936011261047357546040516001600160a01b039091168152602090f35b50346104735780600319360112610473576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b5034610473576040366003190112610473576004359060249182359063ffffffff821680920361123e576118c6335f52600660205260405f2054151590565b15611b04573383526020601481526118ef82604086206001915f520160205260405f2054151590565b15611ab75761190061032b426158c4565b65ffffffffffff80600454169116908111611ae6575b5061192c335f52600660205260405f2054151590565b15611acf573384526014815261195382604086206001915f520160205260405f2054151590565b15611ab75790816119ad9260018060a01b03600a541692604051906001600160601b03199060601b16828201526014815261198d81612daa565b6040518095819263fd7ac5e760e01b83528460048401528a830190612e01565b0381855afa928315611aac578593611a7d575b50604051639b4d9d1360e01b81526004810184905281818881865afa918215611a72578692611a45575b505015611a2e57938484953b15611a2a5784928360449260405196879586946376c951cf60e01b865260048601528401525af18015611756576117465750f35b8480fd5b6040516259d43960e71b8152600481018390528590fd5b611a649250803d10611a6b575b611a5c8183612de0565b810190612e25565b5f806119ea565b503d611a52565b6040513d88823e3d90fd5b9080935081813d8311611aa5575b611a958183612de0565b8101031261123e5751915f6119c0565b503d611a8b565b6040513d87823e3d90fd5b60405163c50edab960e01b8152600481018390528590fd5b604051634c383c5160e01b81523360048201528590fd5b611aee613b3a565b65ffffffffffff1960045416176004555f611916565b604051634c383c5160e01b81523360048201528490fd5b50346104735760203660031901126104735760043590611b46825f52600260205260405f2054151590565b156103dd576040816108a093611b629352600360205220615874565b60405191829182612be1565b5034610473578060031936011261047357602060405165ffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50346104735760403660031901126104735765ffffffffffff6040611bd4612bb5565b92611bdd612b74565b9360018060a01b03168152600b6020522091165f52602052602060ff60405f2054166040519015158152f35b503461047357806003193601126104735760405180600854928383526020809301809460085f527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3905f5b818110611cac5750505082611c6a910383612de0565b6040519260408401906001936001865260406020870152518092526060850195925b828110611c995785870386f35b8351875295810195928101928401611c8c565b825484529286019260019283019201611c54565b503461047357602036600319011261047357600435611cea815f52600260205260405f2054151590565b156103dd57604082819281526003602052206003600282015491015482519182526020820152f35b50346104735780600319360112610473576106de613b3a565b5034610473578060031936011261047357611d44614866565b5f80546001600160a01b0319811682556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b50346104735780600319360112610473576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b503461047357604036600319011261047357604060209165ffffffffffff611def612b5f565b168152600f8352818120602435825283522054604051908152f35b5034610473576020366003190112610473576004356001600160601b03811680910361123e5760209160018214918215611e4b575b50506040519015158152f35b60409250815260098352205415155f80611e3f565b5034610473576080366003190112610473576024356064356004356044356001600160a01b03831680840361123e57611e97614866565b156117da576001821480611f19575b611f0757611eb382615944565b15610ad2577f8828c5448d14dfabfef1825a63323c740b9f79563a375c10bfff3215f754c10960406106de9584938489526003602052806003848b20846002820155015582519182526020820152a261501a565b604051633631e2dd60e21b8152600490fd5b50808411611ea6565b5034610473576060366003190112610473576020610646611f41612bb5565b611f49612b74565b611f51612b9f565b916137ea565b503461047357606036600319011261047357611f71612b5f565b90611f7a612b89565b906044359260018060a01b03841680940361123e576001600160601b039165ffffffffffff604092168152600e6020522091165f5260205260405f20905f52602052602060405f2054604051908152f35b50346104735780600319360112610473576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b5034610473576040366003190112610473576004359061202e612bcb565b612043835f52600260205260405f2054151590565b156103dd57604082602094612075945260038552209060018060a01b0316906001915f520160205260405f2054151590565b6040519015158152f35b503461047357604036600319011261047357612099612bb5565b6120a4610953612b74565b60018060a01b038092168352601491602091601483526120c660408620615874565b936120d18551612fd0565b9286928791600a5416915b8751811015612243576120ef8189613002565b51604051906001600160601b03198160601b168983015285825261211282612daa565b60405163fd7ac5e760e01b815289818061213760249684600484015287830190612e01565b0381895afa908115612238578c9161220b575b5060405190636af907fb60e11b825260048201528b808285818a5afa80156121fe5761219592828893926121db575b505065ffffffffffff60c08160a0840151169201511690614fc4565b6121a4575b50506001016120dc565b9086905f1982146121c95750906121c16001809493019789613002565b52905f61219a565b634e487b7160e01b8c5260116004528bfd5b6121f792503d8091833e6121ef8183612de0565b810190613045565b5f8e612179565b50604051903d90823e3d90fd5b90508981813d8311612231575b6122228183612de0565b8101031261123e57515f61214a565b503d612218565b6040513d8e823e3d90fd5b8887878761225081612fd0565b91845b82811061229e5750505060405192828493840190808552835180925280604086019401925b82811061228757505050500390f35b835185528695509381019392810192600101612278565b806122af6001928498969798613002565b516122ba8288613002565b520194939294612253565b5034610473576020366003190112610473576106de6122e2612b5f565b612e55565b503461047357608036600319011261047357612301612b5f565b612309612bcb565b506064356001600160601b03811680820361123e57612326614866565b604065ffffffffffff948585168152601360205220905f5260205260ff60405f20541615612389575b505061235d61032b426158c4565b600454821691161161237c575b60405163c736d3b160e01b8152600490fd5b612384613b3a565b61236a565b61239291614783565b505f8061234f565b503461047357602080600319360112611360576123b5612bb5565b906123be614866565b6123ca61032b426158c4565b65ffffffffffff806004541691169081116125aa575b506001600160a01b038281165f81815260066020526040902054909291906125915760405163c3c5a54760e01b81526004810184905282816024817f000000000000000000000000000000000000000000000000000000000000000086165afa908115611a72578691612574575b501561255b576040516308834cb560e21b81526001600160a01b0385811660048301527f00000000000000000000000000000000000000000000000000000000000000009081166024830152918390829060449082907f0000000000000000000000000000000000000000000000000000000000000000165afa908115611a7257869161253e575b501561251357509060076124f392828652528360408120556158f6565b15612501576106de90614ba2565b604051635335595160e11b8152600490fd5b60405163a2cb00e760e01b81526001600160a01b038581166004830152919091166024820152604490fd5b6125559150833d8511611a6b57611a5c8183612de0565b5f6124d6565b604051634c383c5160e01b815260048101849052602490fd5b61258b9150833d8511611a6b57611a5c8183612de0565b5f61244e565b604051631da9d69960e01b815260048101849052602490fd5b6125b2613b3a565b65ffffffffffff1960045416176004555f6123e0565b5034610473578060031936011261047357602065ffffffffffff60045416604051908152f35b503461123e57606036600319011261123e57612608612bb5565b602435906044359163ffffffff831680930361123e5761262a61032b426158c4565b65ffffffffffff80600454169116908111612705575b506001600160a01b039182165f81815260066020526040902054156126ed575f5260146020526126818160405f206001915f520160205260405f2054151590565b156126d55750600a541690813b1561123e575f91602483926040519485938492631474cbc960e31b845260048401525af180156126ca576126c0575080f35b61001a9150612d97565b6040513d5f823e3d90fd5b6024906040519063c50edab960e01b82526004820152fd5b60249060405190634c383c5160e01b82526004820152fd5b61270d613b3a565b65ffffffffffff1960045416176004555f612640565b3461123e575f36600319011261123e57602060405165ffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b3461123e57604036600319011261123e5760043560243561278861032b426158c4565b65ffffffffffff806004541691169081116128e8575b50335f526020601481526127c38360405f206001915f520160205260405f2054151590565b156128cf5760015f526003815260405f20600360028201549101548084116128b157508083106128935750600a5460405160609490941b6001600160601b03191684830152601484526128469382916001600160a01b031661282482612daa565b604051808097819463fd7ac5e760e01b83528660048401526024830190612e01565b03915afa9081156126ca575f91612863575b61001a8383336149eb565b905082813d831161288c575b6128798183612de0565b8101031261123e5761001a915183612858565b503d61286f565b82604491604051916353fe4d4b60e11b835260048301526024820152fd5b83604491604051916369367fbf60e01b835260048301526024820152fd5b60405163c50edab960e01b815260048101849052602490fd5b6128f0613b3a565b65ffffffffffff1960045416176004558261279e565b3461123e57604036600319011261123e5761291f612b5f565b65ffffffffffff61292e612b89565b91165f5260136020526001600160601b0360405f2091165f52602052602060ff60405f2054166040519015158152f35b3461123e57602036600319011261123e5760043561297a614866565b61298661032b426158c4565b65ffffffffffff806004541691169081116129ba575b506129a681614891565b6103ef576129b3906155ee565b156103dd57005b6129c2613b3a565b65ffffffffffff1960045416176004558161299c565b3461123e5760208060031936011261123e576004356129f961032b426158c4565b65ffffffffffff80600454169116908111612ad5575b50612a25335f52600660205260405f2054151590565b1561131a57335f5260148252612a4c8160405f206001915f520160205260405f2054151590565b156126d557600a54604051606083901b6001600160601b0319168185015260148152612a8893918291906001600160a01b031661282482612daa565b03915afa9081156126ca575f91612aa5575b61001a838333615303565b905082813d8311612ace575b612abb8183612de0565b8101031261123e5761001a915183612a9a565b503d612ab1565b612add613b3a565b65ffffffffffff19600454161760045582612a0f565b3461123e57602036600319011261123e576020610b6a610953612b5f565b3461123e57604036600319011261123e57602090612b2d612b5f565b65ffffffffffff612b3c612b89565b91165f52600c83526001600160601b0360405f2091165f52825260405f20548152f35b6004359065ffffffffffff8216820361123e57565b6024359065ffffffffffff8216820361123e57565b602435906001600160601b038216820361123e57565b604435906001600160601b038216820361123e57565b600435906001600160a01b038216820361123e57565b602435906001600160a01b038216820361123e57565b60209060206040818301928281528551809452019301915f5b828110612c08575050505090565b83516001600160a01b031685529381019392810192600101612bfa565b600854811015612c5a5760085f527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee301905f90565b634e487b7160e01b5f52603260045260245ffd5b600554811015612c5a5760055f527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db001905f90565b600154811015612c5a5760015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf601905f90565b8054821015612c5a575f5260205f2001905f90565b90600165ffffffffffff80931601918211612d0457565b634e487b7160e01b5f52601160045260245ffd5b91909165ffffffffffff80809416911601918211612d0457565b65ffffffffffff9081167f0000000000000000000000000000000000000000000000000000000000000000821602908116908103612d0457612d94907f0000000000000000000000000000000000000000000000000000000000000000612d18565b90565b6001600160401b03811161125557604052565b604081019081106001600160401b0382111761125557604052565b602081019081106001600160401b0382111761125557604052565b90601f801991011681019081106001600160401b0382111761125557604052565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b9081602091031261123e5751801515810361123e5790565b65ffffffffffff9182169082160391908211612d0457565b9065ffffffffffff91828116908115612fa757612e7461032b426158c4565b915f60049186600454169087861695828711612f93575b50878316809111612f46575b50505091905f925f925b86831687851610612ef7575b50506004546040805165ffffffffffff97909216871682529390951660208601525091927f4d292688ca328e445b76fb7747c31983e6367477e08b40b6a30f7cf280ee80b69250a1565b9091929381878454161015612f4057612f0e614c4a565b15612f40578616868114612f2d57600190810194018616929190612ea1565b601183634e487b7160e01b5f525260245ffd5b93612ead565b909192935015612f5a5750905f8080612e97565b9150507f4d292688ca328e445b76fb7747c31983e6367477e08b40b6a30f7cf280ee80b69293506040809250519081525f6020820152a1565b612fa09193508290612e3d565b915f612e8b565b604051631752afd760e31b8152600490fd5b6001600160401b0381116112555760051b60200190565b90612fda82612fb9565b612fe76040519182612de0565b8281528092612ff8601f1991612fb9565b0190602036910137565b8051821015612c5a5760209160051b010190565b6001600160401b03811161125557601f01601f191660200190565b51906001600160401b038216820361123e57565b90602090818382031261123e5782516001600160401b039384821161123e570160e08183031261123e576040519360e0850185811082821117611255576040528151600681101561123e5785528382015190811161123e5781019180601f8401121561123e578251936130b785613016565b916130c56040519384612de0565b85835281868601011161123e575f8186613133978260c098018387015e840101528501526130f560408201613031565b604085015261310660608201613031565b606085015261311760808201613031565b608085015261312860a08201613031565b60a085015201613031565b60c082015290565b51906001600160a01b038216820361123e57565b519065ffffffffffff8216820361123e57565b9081606091031261123e576131768161313b565b91612d9460406131886020850161314f565b930161314f565b9081602091031261123e57516001600160601b038116810361123e5790565b91908201809211612d0457565b91905f9265ffffffffffff80831691825f5260209360138552604090815f209460019560015f52875260ff835f2054166134fb57506131f990612d32565b9460018060a01b039560049680885460301c16958451916374d4e49160e01b835284838b818b5afa9283156134f1575f936134c2575b50989796905f96959493929699807f000000000000000000000000000000000000000000000000000000000000000016918416925b888c1061327957505050505050505050505050565b909192939495969798999a9c8d88519063328dc3d960e11b82528d820152606060249181818481885afa801561347e5789905f925f905f92613488575b506132c2939450614fc4565b15613447578951639374a51d60e01b8152908516818f018190529089818481885afa90811561347e578e916001600160601b03915f91613451575b50160361344757888e918b5192838092630ce9b79360e41b82525afa90811561343d579189918f8f9489918f8f908b935f966133f0575b50908361337f9282519261334784612dc5565b5f8452519a8b998a988997633dc5038960e01b89528801528601521660448401528d606484015260a0608484015260a4830190612e01565b0392165afa9081156133e657908c915f916133b3575b506133a091926131ae565b9d5b019a99989796959493929190613264565b809250898092503d83116133df575b6133cc8183612de0565b8101031261123e57518b906133a0613395565b503d6133c2565b89513d5f823e3d90fd5b9796985050505050505081813d8311613436575b61340e8183612de0565b8101031261123e578c918b8f87899161337f8f8f9761342d859161313b565b96919250613334565b503d613404565b8a513d5f823e3d90fd5b50509c8a906133a2565b61347191508c8d3d10613477575b6134698183612de0565b81019061318f565b5f6132fd565b503d61345f565b8b513d5f823e3d90fd5b9150506132c29250836134b09294903d106134bb575b6134a88183612de0565b810190613162565b9093919384936132b6565b503d61349e565b9092508481813d83116134ea575b6134da8183612de0565b8101031261123e5751915f61322f565b503d6134d0565b86513d5f823e3d90fd5b5f908152600e87528281206001825287528281206001600160a01b039094168152929095529020549450505050565b91905f9265ffffffffffff80831691825f5260209360138552604090815f209460019560015f52875260ff835f2054166134fb575061356890612d32565b9460018060a01b039560049680885460301c16958451916374d4e49160e01b835284838b818b5afa9283156134f1575f936137bb575b50989796905f96959493929699807f000000000000000000000000000000000000000000000000000000000000000016918416925b888c106135e857505050505050505050505050565b909192939495969798999a9c8d88519063328dc3d960e11b82528d820152606060249181818481885afa801561347e5789905f925f905f92613791575b50613631939450614fc4565b1561376a578951639374a51d60e01b8152908516818f018190529089818481885afa90811561347e578e916001600160601b03915f91613774575b50160361376a57888e918b5192838092630ce9b79360e41b82525afa90811561343d579189918f8f9489918f8f908b935f9661371d575b5090836136b69282519261334784612dc5565b0392165afa9081156133e657908c915f916136ea575b506136d791926131ae565b9d5b019a999897969594939291906135d3565b809250898092503d8311613716575b6137038183612de0565b8101031261123e57518b906136d76136cc565b503d6136f9565b9796985050505050505081813d8311613763575b61373b8183612de0565b8101031261123e578c918b8f8789916136b68f8f9761375a859161313b565b969192506136a3565b503d613731565b50509c8a906136d9565b61378b91508c8d3d10613477576134698183612de0565b5f61366c565b9150506136319250836137b09294903d106134bb576134a88183612de0565b909391938493613625565b9092508481813d83116137e3575b6137d38183612de0565b8101031261123e5751915f61359e565b503d6137c9565b929190925f9365ffffffffffff9283821690815f5260209060138252604093845f20936001600160601b0380931694855f52845260ff865f205416613b0b575061383390612d32565b9060018060a01b039660049780895460301c16978751936374d4e49160e01b855286858c818d5afa9485156133e6575f95613adc575b5099989291905f9897969594989a827f000000000000000000000000000000000000000000000000000000000000000016938616945b8a8d106138b55750505050505050505050505050565b909192939495969798999a9b9d8c8f8c519163328dc3d960e11b8352820152606060249181818481885afa8015613aa8578b905f925f905f92613ab2575b506138ff939450614fc4565b15613a80578e90878e5191639374a51d60e01b83521680928201528b818481885afa908115613aa8578d9187918e5f92613a8b575b50501603613a80578a8f918e5192838092630ce9b79360e41b82525afa908115613a7657918f91898f8f95948f95918e8c8e945f97613a25575b50908483926139b994519361398285612dc5565b5f8552519b8c9a8b998a98633dc5038960e01b8a52890152870152166044850152606484015260a0608484015260a4830190612e01565b0392165afa908115613a1b575f916139ed575b506139d9906001926131ae565b9e5b019b9a9998979695949392919061389f565b90508981813d8311613a14575b613a048183612de0565b8101031261123e575160016139cc565b503d6139fa565b8c513d5f823e3d90fd5b989799505050505050505081813d8311613a6f575b613a448183612de0565b8101031261123e578b918f898f89928f958c928f84613a656139b99461313b565b979192935061396e565b503d613a3a565b8d513d5f823e3d90fd5b50509d6001906139db565b613aa19250803d10613477576134698183612de0565b5f8e613934565b8e513d5f823e3d90fd5b9150506138ff925083613ad19294903d106134bb576134a88183612de0565b9093919384936138f3565b9094508681813d8311613b04575b613af48183612de0565b8101031261123e5751935f613869565b503d613aea565b5f908152600e845285812094815293835250508282206001600160a01b03909416825292909252902054925050565b613b4661032b426158c4565b65ffffffffffff90816004541690818382161115613bc1578291613b6991612e3d565b1660019060018111613ba2575f5b8184821610613b87575b50505050565b613b8f614c4a565b15613b9d5782018316613b77565b613b81565b60449060405190633c90bbed60e21b8252600482015260016024820152fd5b505050565b9091600191906001600160601b0381168303613dc6575060408051633bc7daf160e01b81526001600160a01b039490941660048086019190915265ffffffffffff831660248601529391905f83604481305afa928315613dbc575f93613d2b575b50600a545f969587959490916001600160a01b031690855b613c4d575b50505050505050565b8051871015613d2657613cab98613c648883613002565b5185519060209182916001600160601b03199060601b168282015260148152613c8c81612daa565b8751809d819263fd7ac5e760e01b8352848a8401526024830190612e01565b0381875afa9081156134f15788999a9b5f92613cf3575b5091613cea91899365ffffffffffff8a165f52600f8152885f20915f5252865f2054906131ae565b99980196613c3f565b8180949a508193503d8311613d1f575b613d0d8183612de0565b8101031261123e575188979188613cc2565b503d613d03565b613c44565b9092503d805f833e613d3d8183612de0565b810190602090818184031261123e578051906001600160401b03821161123e57019180601f8401121561123e578251613d7581612fb9565b93613d8287519586612de0565b818552838086019260051b82010192831161123e578301905b828210613dad5750505050915f613c27565b81518152908301908301613d9b565b50513d5f823e3d90fd5b9150612d94926137ea565b905f9160018060a01b038091165f52602090600d8252604090815f20925f93805492600a5416905b838610613e0857505050505050565b909192939496613e6584613e1c8a85612cd8565b9054895160039290921b1c60601b6001600160601b0319168282015260148152613e4581612daa565b88518093819263fd7ac5e760e01b83528460048401526024830190612e01565b0381875afa908115613ee6575f91613eb8575b50600191613eac9165ffffffffffff613e9361032b426158c4565b165f52600f8752885f20905f528652875f2054906131ae565b97019493929190613df9565b90508481813d8311613edf575b613ecf8183612de0565b8101031261123e57516001613e78565b503d613ec5565b87513d5f823e3d90fd5b613f1b907f000000000000000000000000000000000000000000000000000000000000000090612e3d565b65ffffffffffff807f000000000000000000000000000000000000000000000000000000000000000016918215613f5157160490565b634e487b7160e01b5f52601260045260245ffd5b9065ffffffffffff91828116805f52601360205260405f20906001600160601b03841691825f5260205260ff60405f2054166140835750505f92613fa882612d32565b9080613fb3426158c4565b1690808316918211908115614045575b5061402d575090600554915f935b838510613fe057505050505090565b909192939482613ffb613ff2886153c4565b90939193614fc4565b15614023576001916140118487614017946137ea565b906131ae565b955b0193929190613fd1565b5094600190614019565b6024906040519063c2ef899360e01b82526004820152fd5b905061407a614053426158c4565b7f000000000000000000000000000000000000000000000000000000000000000090612e3d565b1681105f613fc3565b92509250505f52600c60205260405f20905f5260205260405f205490565b919060408382031261123e57604080516001600160401b039491810185811182821017611255576040528094823563ffffffff8116810361123e5782526020928381013591821161123e57019280601f8501121561123e57833561410481612fb9565b946141126040519687612de0565b818652848087019260051b82010192831161123e578401905b828210614139575050500152565b81356001600160a01b038116810361123e57815290840190840161412b565b805490600160401b821015611255578161417a91600161419094018155612cd8565b819391549060031b91821b915f19901b19161790565b9055565b6040820163ffffffff8251168352602060608180940151946040838201528551809452019301915f5b8281106141cb575050505090565b83516001600160a01b0316855293810193928101926001016141bd565b91908203918211612d0457565b8015612d04575f190190565b61420d61032b426158c4565b9160018060a01b0382165f52600b60205260405f2065ffffffffffff84165f5260205260ff60405f205416614677576001600160a01b0382165f818152600b6020908152604080832065ffffffffffff881684528252808320805460ff191660011790559282526006905220541561465657614288826151c4565b61429183613dd1565b9060018060a01b0384165f52600d60205260405f209586549280831461464c578083116145cc57826142c2916141e8565b925b801515806145c3575b15614582576142db906141f5565b916143416142e9848a612cd8565b91905460018060a01b03600a541660206040516001600160601b0319848760031b1c60601b16828201526014815261432081612daa565b6040518095819263fd7ac5e760e01b83528460048401526024830190612e01565b0381845afa9283156126ca575f9361454e575b50604051639b4d9d1360e01b815260048101849052602081602481855afa9081156126ca575f9161452f575b50614526575f60249160405192838092636af907fb60e11b82528760048301525afa9081156126ca575f9161450c575b50519660068810156144f85760028098036144ee5765ffffffffffff8b165f52600f60205260405f20835f5260205260405f205480156144e35761441b90808310156144dd5782905b8b15158c83826144d3575b50506144cb575b81614415916141e8565b926141e8565b60015f5260036020527fa15bc60c955c405d20d9149c709e2460f1c2d9a497496a7f46004d1772c3054c90980154811080156144bb575b1561446d57506144679260031b1c9088615303565b916142c4565b919261447a83858c6149eb565b60405192835260031b1c906001600160a01b038916907f48d83a0359ece1af3ea738ea6e14f7f30eb31b2c57f44b98dbc41469c83a525a90602090a4614467565b506144c58a615137565b15614452565b8b915061440b565b1190508c5f614404565b806143f9565b509650505050614467565b9650505050614467565b634e487b7160e01b5f52602160045260245ffd5b61452091503d805f833e6121ef8183612de0565b5f6143b0565b50505050614467565b614548915060203d602011611a6b57611a5c8183612de0565b5f614380565b9092506020813d60201161457a575b8161456a60209383612de0565b8101031261123e5751915f614354565b3d915061455d565b5060405191825295506001600160a01b0390931693507fac43c581689bab0b319a9fc8e659012b23ee3494f1bf17edad354d457ccba3eb92602092509050a2565b508315156142cd565b90965081614603929650602094507fac43c581689bab0b319a9fc8e659012b23ee3494f1bf17edad354d457ccba3eb9593506141e8565b6040519081526001600160a01b038216907fb066d744c0abef0595669b34ee833c2d24b4d89382dc63f616513f3024a09464908490a26040519384526001600160a01b031692a2565b5095505050505050565b604051634c383c5160e01b81526001600160a01b0383166004820152602490fd5b5060405163316d57cf60e11b81526001600160a01b0391909116600482015265ffffffffffff919091166024820152604490fd5b905f916146b781612d32565b6005545f915b81831061470b5750505065ffffffffffff16805f52600c60205260405f2060015f526020528260405f20555f52601360205260405f2060015f5260205260405f20600160ff19825416179055565b829561472461471b8394956153c4565b90949194614fc4565b15614779578161476f9161473a8760019561352a565b809265ffffffffffff89165f52602090600e8252604091825f20885f528152825f2091888060a01b03165f52525f20556131ae565b955b0191906146bd565b9560019150614771565b91905f9261479081612d32565b6005545f915b8183106147ed5750505065ffffffffffff16805f52600c6020526001600160601b0360405f20921691825f526020528360405f20555f52601360205260405f20905f5260205260405f20600160ff19825416179055565b82966147fd61471b8394956153c4565b1561485c57816148529161481488886001966137ea565b809265ffffffffffff89165f52602090600e8252604091825f206001600160601b038d165f528152825f2091888060a01b03165f52525f20556131ae565b965b019190614796565b9660019150614854565b5f546001600160a01b0316330361487957565b60405163118cdaa760e01b8152336004820152602490fd5b6004805460301c6001600160a01b0390811692915f5b604080516374d4e49160e01b8152602090818186818b5afa9081156149e1575f916149b4575b508310156149a957815163328dc3d960e11b8152848101849052606090602490828183818d5afa92831561499f57918493918995935f9361497e575b50508551948592630d5d5d9360e21b84521688830152818b5afa9283156149755750916001600160601b039187935f92614958575b5050161461494e576001016148a7565b5050505050600190565b61496e9250803d10613477576134698183612de0565b5f8061493e565b513d5f823e3d90fd5b614995929350803d106134bb576134a88183612de0565b5050905f80614909565b85513d5f823e3d90fd5b505050505050505f90565b90508181813d83116149da575b6149cb8183612de0565b8101031261123e57515f6148cd565b503d6149c1565b83513d5f823e3d90fd5b9291906149fa61032b426158c4565b9165ffffffffffff8084165f52602090600f8252604094855f20855f528352855f205460018060a01b03986024858b600a54168a5192838092639b4d9d1360e01b82528c60048301525afa9081156133e6575f91614b85575b50614b6d575f91808711614b33575b50946044945f9794614ab08995614aeb958e6001600160401b039e9f9b16885260128552614a948d89209182546131ae565b9055878752601084528b8720805460ff19166001179055612ced565b168452600f8152888420908585525280888420557f0000000000000000000000000000000000000000000000000000000000000000906152d3565b93600a541686519788958694633aa2795360e01b865260048601521660248401525af19081156149755750614b1d5750565b614b30903d805f833e6121ef8183612de0565b50565b614b3f919250866141e8565b90614b49816151c4565b8211614b55575f614a62565b8751630ae3d83f60e21b815260048101879052602490fd5b8751638239160d60e01b815260048101889052602490fd5b614b9c9150863d8811611a6b57611a5c8183612de0565b5f614a53565b6001600160a01b03165f8181526007602052604090205490811580614c39575b614c215765ffffffffffff918280821615159182614c13575b5050614c0157614b3091614bee426158c4565b16815f52600760205260405f20556158f6565b6040516389faef9d60e01b8152600490fd5b60301c16159050825f614bdb565b6024906040519063015ab34360e11b82526004820152fd5b50600660205260405f205415614bc2565b65ffffffffffff614c5d61032b426158c4565b8160045416918291161115614fbf57614c7590612ced565b5f915b600554831015614fa057614c8b836153c4565b50509265ffffffffffff8316155f14614f81575f935b60018060a01b0381165f52600d60205260405f20948554805b614cf1575050600180809495965060a01b03165f52601260205260405f208054614ce8575b50019190614c78565b5f90555f614cdf565b614cfa906141f5565b614d3f614d078289612cd8565b905490602060018060a01b03600a5416604051906001600160601b0319858560031b1c60601b16838301526014825261282482612daa565b03915afa9283156126ca575f93614f4d575b50825f5260116020528360ff60405f2054161580614f37575b614ec35750601160205260ff60405f20541680614e9d575b80614e76575b614db9575b50505060106020528060405f20805460ff8116614dac575b5050614cba565b60ff191690555f80614da5565b6001600160a01b0386165f908152600d6020526040812080549290915b838110614dfe575b50505050505f52601160205260405f2060ff1981541690555f8080614d8d565b614e088184612cd8565b868460031b1c91549060031b1c14614e2257600101614dd6565b5f1984019450919284119050612d045782614e45938203614e4f575b50506155b1565b5f80808080614dde565b61417a614e5f614e6d9285612cd8565b90549060031b1c9284612cd8565b90555f80614e3e565b5065ffffffffffff85165f52600f60205260405f20835f5260205260405f20541515614d88565b5065ffffffffffff89165f52600f60205260405f20835f5260205260405f205415614d82565b9291505065ffffffffffff88165f52600f908160205260405f20815f5260205260405f205415614ef4575050614cba565b65ffffffffffff85165f528160205260405f20815f5260205260405f20549165ffffffffffff8a165f5260205260405f20905f5260205260405f20555f80614da5565b505060106020528360ff60405f20541615614d6a565b9092506020813d602011614f79575b81614f6960209383612de0565b8101031261123e5751915f614d51565b3d9150614f5c565b5f1965ffffffffffff84160165ffffffffffff8111612d045793614ca1565b915065ffffffffffff1665ffffffffffff196004541617600455600190565b505f90565b65ffffffffffff9081168015159392908461500d575b5083614fe7575b50505090565b811680159350918315615000575b5050505f8080614fe1565b16111590505f8080614ff5565b838316101593505f614fda565b805f52600360205261503960405f209260018060a01b031680936159e0565b15615064577fc53536963369dbfa4c398238ebb9b09fce3943a140928bd25d3052a8a9cacdaf5f80a3565b60405163ad2ac00f60e01b8152600490fd5b8115613f51570490565b61508c61032b426158c4565b60018060a01b0382165f52602090600d6020526040805f20549060085494851561512c57600195948387019384105f5b8781106150d157505050505050505050600190565b6150da81612c25565b9054600391821b1c90836150f86001600160601b0384168b896137ea565b90612d04578861510791615076565b915f5284526002865f2001541161511f5788016150bc565b5050505050505050505f90565b505050505050600190565b61514361032b426158c4565b60018060a01b0382165f52602091600d6020526040805f20549160085494851561512c575f5b86811061517c5750505050505050600190565b61518581612c25565b9054600391821b1c906151ab876151a66001600160601b0385168b896137ea565b615076565b915f5283526002855f200154116149a957600101615169565b6151d96151d361032b426158c4565b826131bb565b600a546040805163f113073360e01b8152306004820152929390926001600160a01b039284908290602490829087165afa9081156152c9575f91615287575b506001600160401b037f00000000000000000000000000000000000000000000000000000000000000009116818102918183041490151715612d045780851161527f575b50165f5260126020525f20548082111561527957612d94916141e8565b50505f90565b93505f61525c565b90508381813d83116152c2575b61529e8183612de0565b8101031261123e576020816152b56152bc93613031565b5001613031565b5f615218565b503d615294565b84513d5f823e3d90fd5b906152dd91615076565b6001600160401b03908181116152f1571690565b604051632a6bc64560e01b8152600490fd5b91909165ffffffffffff61532161531c61032b426158c4565b612ced565b165f52600f60205260405f20835f526020525f6040812055601160205260405f20600160ff1982541617905560018060a01b03905f8083600a54166024604051809481936325fedc3560e21b83528a60048401525af180156126ca576153aa575b50167f52dc76c098767cf70c407df0346869d074f6a3aa927b6497938715a0188a6d755f80a4565b6153bd903d805f833e6121ef8183612de0565b505f615382565b6153cd90612c6e565b90549060031b1c805f52600760205260405f20549060018060a01b03169165ffffffffffff8083169260301c1690565b5f9060018060a01b036004938160045460301c16935b604080516374d4e49160e01b81526020919082818a818b5afa9081156155a7575f9161557a575b5083101561556e5780519163328dc3d960e11b835283898401526060602493818186818d5afa9182156152c95791839189935f9261554f575b50508451630d5d5d9360e21b815292168b8301819052948290818c5afa9081156149e1576001600160601b038b93928a925f91615532575b50161493846154c5575b5050505061512c57600101615413565b838293949550519384809263d8dfeb4560e01b82525afa92831561497557505f926154fc575b50508316828416145f8781806154b5565b90809250813d831161552b575b6155138183612de0565b8101031261123e576155249061313b565b5f806154eb565b503d615509565b6155499150843d8611613477576134698183612de0565b5f6154ab565b6155659250803d106134bb576134a88183612de0565b50505f80615473565b50505050505050505f90565b90508281813d83116155a0575b6155918183612de0565b8101031261123e57515f61543a565b503d615587565b82513d5f823e3d90fd5b80549081156155da575f19918201916155ca8383612cd8565b909182549160031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b5f818152600960205260409020548015615279575f1990808201818111612d045760085490838201918211612d045781810361565f575b50505060085480156155da5781019061563d82612c25565b909182549160031b1b191690556008555f5260096020525f6040812055600190565b61567d61566e61417a93612c25565b90549060031b1c928392612c25565b90555f52600960205260405f20555f8080615625565b5f818152600660205260409020548015615279575f1990808201818111612d045760055490838201918211612d0457818103615704575b50505060055480156155da578101906156e282612c6e565b909182549160031b1b191690556005555f5260066020525f6040812055600190565b61572261571361417a93612c6e565b90549060031b1c928392612c6e565b90555f52600660205260405f20555f80806156ca565b5f818152600260205260409020548015615279575f1990808201818111612d045760015490838201918211612d04578181036157a9575b50505060015480156155da5781019061578782612ca3565b909182549160031b1b191690556001555f5260026020525f6040812055600190565b6157c76157b861417a93612ca3565b90549060031b1c928392612ca3565b90555f52600260205260405f20555f808061576f565b906001820191815f528260205260405f2054908115155f1461586c575f198281019290818411612d04578254908101908111612d045783816158279503615837575b5050506155b1565b5f526020525f6040812055600190565b61585761584761417a9386612cd8565b90549060031b1c92839286612cd8565b90555f528460205260405f20555f808061581f565b505050505f90565b90604051918281549182825260209260208301915f5260205f20935f905b8282106158aa575050506158a892500383612de0565b565b855484526001958601958895509381019390910190615892565b65ffffffffffff908181116158d7571690565b604490604051906306dfcc6560e41b8252603060048301526024820152fd5b805f52600660205260405f2054155f14614fbf57600554600160401b8110156112555761592d61417a826001859401600555612c6e565b9055600554905f52600660205260405f2055600190565b805f52600260205260405f2054155f14614fbf57600154600160401b8110156112555761597b61417a826001859401600155612ca3565b9055600154905f52600260205260405f2055600190565b805f52600960205260405f2054155f14614fbf57600854600160401b811015611255576159c961417a826001859401600855612c25565b9055600854905f52600960205260405f2055600190565b5f8281526001820160205260409020546152795780615a0183600193614158565b8054925f520160205260405f2055600190565b805f52600760205260405f205490811580615a32575b614c21575090565b50600660205260405f205415615a2a56fea26469706673582212202bc4a3b83242b07631b1b21ba90e919382b5d555006d1770b7ff96f18bc6e8dc64736f6c6343000819003300000000000000000000000084f2b4d4cf8da889701fbe83d127896880c0432500000000000000000000000046d45d6be6214f6bd8124187cad1a5302755d7a2000000000000000000000000c3b09a650c78dafb79e3c5b782402c8dc523fe880000000000000000000000000360c1cb32a20d97b358538d9db71339ce2c9592000000000000000000000000000000000000000000000000000000000000070800000000000000000000000000000000000000000000000000000000000008340000000000000000000000000000000000000000000000000000000000000384000000000000000000000000c15adc0c87db428b9fc0bb3246e7deb2a991aa6e00000000000000000000000059fbba1845690fb202d69a77cfec5564df12d3de00000000000000000000000000000000000000000000001b1ae4d6e2ef5000000000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000470de4df820000

Deployed Bytecode

0x608080604052600436101561001c575b50361561001a575f80fd5b005b5f905f3560e01c9081630e4e7bd514612b115750806311369f0f14612af357806316c58824146129d85780631af4b5af1461295e5780631b834ed3146129065780631d34a34f1461276557806322207cc71461272357806327eb17b2146125ee5780632b84d6b414610b025780632ee59f2d146125c85780633682a4501461239a5780633708d256146122e75780633b013969146122c55780633bc7daf11461207f5780633c7b1d371461201057806342cc179814611fcb5780634fe5b9af14611f5757806352c6f00c14611f22578063554bc85914611e6057806356e1c9e914611dc95780635c04a2d814611e0a5780635f3710d014611dc9578063642d5a2314611d84578063715018a614611d2b57806379b5c25e14611d125780637b426a3b14611cc05780637d3ef55d14611c095780637d7d7e4414611bb15780637ff0d8e514611b6e57806381b1d0be14611b1b578063822ca0461461188757806383ce0322146118425780638da5cb5b1461181b5780638fb4bfbd146117ec57806396523059146117835780639681d940146116be57806396f0bcd21461168957806399e3d630146116655780639d94a1e3146116465780639f4191251461160b578063a2702f5c146115c6578063a365bf8814611570578063a70b9f0c1461152d578063a73e2333146114f4578063a7949eca146114c7578063a7dab16714611498578063ac8a584a14611368578063aeb5803d14610da2578063b2834db714610c67578063b36248e814610c3e578063b543503e14610b7a578063b97dd9e214610b4b578063b9bbb6f514610b1e578063ba14c58c14610b02578063c1c2e42b14610a5f578063c84a1242146108f0578063cbbb83d1146108cc578063d911c63214610849578063dc625d92146106ff578063dd307b9914610691578063ddaa26ad1461064e578063e3839ba314610619578063f2fde38b14610591578063f56408ed146104af578063f944e5c4146104765763fa9952f80361000f573461047357604036600319011261047357600435610316612bcb565b9061031f614866565b61033061032b426158c4565b613ef0565b65ffffffffffff80600454169116908111610455575b50600181148061041f575b6104075761035f82826153fd565b6103ef5761036b614866565b8083526003602052604083206001600160a01b039092169161038e9083906157dd565b156103ba577f03c0a1eb08ee9b373b2eee0ff81874c2eecc632e962925ae0fa3cfac84c594f08380a380f35b825260026020526040822054156103dd57604051631595a2fb60e31b8152600490fd5b60405163366eedf560e01b8152600490fd5b6024906040519063c37ba64d60e01b82526004820152fd5b602490604051906318973d4360e11b82526004820152fd5b506001600160a01b038281167f00000000000000000000000059fbba1845690fb202d69a77cfec5564df12d3de90911614610351565b61045d613b3a565b65ffffffffffff1960045416176004555f610346565b80fd5b5034610473576020366003190112610473576020906040906001600160a01b0361049e612bb5565b168152600d83522054604051908152f35b5034610473576020366003190112610473576104c9612bb5565b6104d1614866565b6104dd61032b426158c4565b60045465ffffffffffff929183169083168111610573575b506001600160a01b031661050881615a14565b9180831615610561578260301c1661054f5761054b916bffffffffffff000000000000610534426158c4565b60301b1617815f52600760205260405f20556158f6565b5080f35b60405163a4cf437160e01b8152600490fd5b60405163702a3be360e01b8152600490fd5b61057b613b3a565b65ffffffffffff1960045416176004555f6104f5565b5034610473576020366003190112610473576105ab612bb5565b6105b3614866565b6001600160a01b03908116908115610600575f54826001600160601b0360a01b8216175f55167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a380f35b604051631e4fbdf760e01b815260048101849052602490fd5b5034610473576040366003190112610473576020610646610638612b5f565b610640612b89565b90614783565b604051908152f35b5034610473578060031936011261047357602060405165ffffffffffff7f0000000000000000000000000000000000000000000000000000000068272eb9168152f35b5034610473576020366003190112610473576106de6106ae612bb5565b6106b6614866565b6106c261032b426158c4565b65ffffffffffff806004541691169081116106e1575b50614ba2565b80f35b6106e9613b3a565b65ffffffffffff1960045416176004555f6106d8565b50346104735760203660031901126104735760043561072061032b426158c4565b65ffffffffffff8060045416911690811161082b575b50805f52600960205260405f205461081357610750614866565b60018082146107fa57818352600360205260408320546107e85761077382615738565b156103dd57818352600360205260408320908154848355806107c5575b50506003815f60028194015501557f72eb970ea8827c3fd1e7e48a5011ec25c35d5ca2cf450465fc92e06579a927a58280a280f35b82855260208520908101905b8181106107de5750610790565b5f815582016107d1565b6040516302db767160e01b8152600490fd5b6040516318973d4360e11b815260048101839052602490fd5b6024906040519063464cd05560e01b82526004820152fd5b610833613b3a565b65ffffffffffff1960045416176004555f610736565b503461047357806003193601126104735760055461086681612fb9565b916108746040519384612de0565b818352601f1961088383612fb9565b013660208501375b8181106108a457604051806108a08582612be1565b0390f35b806108b06001926153c4565b50506108bc8286613002565b90838060a01b031690520161088b565b50346104735760203660031901126104735760206106466108eb612bb5565b6151c4565b50346104735760403660031901126104735761090a612bb5565b9061091761032b426158c4565b9165ffffffffffff92838116835260136020526040832060015f5260205260ff60405f20541615610a4f575b5061095861095361032b426158c4565b612d32565b92610962426158c4565b61098c7f000000000000000000000000000000000000000000000000000000000000038486612d18565b94828216838088168210928315610a17575b5050506109ee5750906106de916109b761032b426158c4565b600454908216911681116109d0575b5060243590614201565b6109d8613b3a565b65ffffffffffff196004541617600455836109c6565b604051633566e89d60e21b815265ffffffffffff91821660048201529085166024820152604490fd5b610a45919293507f000000000000000000000000000000000000000000000000000000000000070890612d18565b16105f838161099e565b610a58906146ab565b505f610943565b503461047357602036600319011261047357600435610a7c614866565b610a8861032b426158c4565b65ffffffffffff80600454169116908111610ae4575b50610ab4815f52600260205260405f2054151590565b156103dd5760018114610ad257610aca90615992565b15610ad25780f35b604051638e2262ff60e01b8152600490fd5b610aec613b3a565b65ffffffffffff1960045416176004555f610a9e565b5034610473578060031936011261047357602060405160018152f35b503461047357806003193601126104735760045460405160309190911c6001600160a01b03168152602090f35b50346104735780600319360112610473576020610b6a61032b426158c4565b65ffffffffffff60405191168152f35b503461047357602036600319011261047357610b94612bb5565b610b9c614866565b6001600160a01b03908082168015610c0957600454928360301c167f8cfaacab0869ad5307d9175b1a62164d7c9630958cbbc7bc9918133cd6fbb02d8580a36601000000000000600160d01b031990911660309190911b6601000000000000600160d01b03161760045580f35b604051635d273a4360e11b815260206004820152600c60248201526b3b30bab63a26b0b730b3b2b960a11b6044820152606490fd5b5034610473578060031936011261047357600a546040516001600160a01b039091168152602090f35b503461047357806003193601126104735760405180916001908154808452602080940190835f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6905f5b818110610d8f5750505084610cc8910385612de0565b8351610cec610cd682612fb9565b91610ce46040519384612de0565b808352612fb9565b8185019590601f190136873782845b610d41575b50506040519380850191818652518092526040850195925b828110610d255785870386f35b83516001600160601b0316875295810195928101928401610d18565b9084969491819693949651811015610d8257806001600160601b03610d67859385613002565b5116610d738288613002565b52019091949695939295610cfb565b5095939594929194610d00565b8254845292860192918501918501610cb2565b50346104735760031960c036820112611360576001600160401b036024351161136057366023602435011215611360576001600160401b03602435600401351161136057366024803560040135813501011161136057604435906001600160401b0382168203611364576001600160401b03606435116113645760408160643536030112611364576001600160401b0360843511611364576040906084353603011261136057610e5461032b426158c4565b65ffffffffffff8116835260136020526040832060015f5260205260ff60405f20541615611350575b50610e8a61032b426158c4565b65ffffffffffff80600454169116908111611332575b50610eb6335f52600660205260405f2054151590565b1561131a57610ec433615080565b1561130857610f20602060018060a01b03600a5416604051906001600160601b031960043560601b168383015260148252610efe82612daa565b604051808095819463fd7ac5e760e01b83528660048401526024830190612e01565b03915afa9081156112fd5783916112cb575b50610f3c336151c4565b90808452601160205260ff6040852054166112b2578352601060205260ff6040842054166112995760018352600360205260408320906003600283015492015460a43515155f146112935760a4355b8181111561128b5750915b8210908115611281575b506112695760405160043560601b6001600160601b031916602082015260148152610fca81612daa565b604051928360a08101106001600160401b0360a086011117611255576001600160401b039160a08501604052845261100760243560040135613016565b6110146040519182612de0565b602480356004810135808452910160208301378560206024356004013583010152602085015216604083015261104f366064356004016140a1565b6060830152611063366084356004016140a1565b6080830152338352601460205261107f600435604085206159e0565b50338352600d60205261109760043560408520614158565b6110a361032b426158c4565b600a546020906001600160401b03906001600160a01b031661116d876110e97f00000000000000000000000000000000000000000000000000470de4df820000886152d3565b9760405198899586948593634dd4b5c360e11b85526040600486015261111b815160a0604488015260e4870190612e01565b608061115b61113b8d8501519360431994858b83030160648c0152612e01565b8660408601511660848a01526060850151848a83030160a48b0152614194565b920151908683030160c4870152614194565b9116602483015203925af192831561124a578493611210575b506111b18165ffffffffffff8093168652600f60205260408620858752602052836040872055612ced565b168352600f60205260408320828452602052806040842055601060205260408320600160ff19825416179055604051908152600435907f517e1badda2dba32b4f95f89957307097e82788cb2e6da8dcddc14850406538a60203392a480f35b9092506020813d602011611242575b8161122c60209383612de0565b8101031261123e5751916111b1611186565b5f80fd5b3d915061121f565b6040513d86823e3d90fd5b634e487b7160e01b5f52604160045260245ffd5b60249060405190630ae3d83f60e21b82526004820152fd5b905081115f610fa0565b905091610f96565b81610f8b565b602460405163566c728360e11b81526004356004820152fd5b6024604051635e75d9fb60e11b81526004356004820152fd5b90506020813d6020116112f5575b816112e660209383612de0565b8101031261123e57515f610f32565b3d91506112d9565b6040513d85823e3d90fd5b604051630462cfe760e41b8152600490fd5b604051634c383c5160e01b8152336004820152602490fd5b61133a613b3a565b65ffffffffffff1960045416176004555f610ea0565b611359906146ab565b505f610e7d565b5080fd5b8280fd5b503461047357602036600319011261047357611382612bb5565b61138a614866565b61139661032b426158c4565b60045465ffffffffffff92918316908316811161147a575b506001600160a01b031690806113c383615a14565b60301c16908115908115611439575b506113ef57508061054b91835260076020525f6040842055615693565b60405163e223232360e01b815265ffffffffffff91821660048201527f00000000000000000000000000000000000000000000000000000000000008349091166024820152604490fd5b90506114657f000000000000000000000000000000000000000000000000000000000000083483612d18565b9080611470426158c4565b169116115f6113d2565b611482613b3a565b65ffffffffffff1960045416176004555f6113ae565b50346104735760203660031901126104735760ff60406020926004358152601184522054166040519015158152f35b50346104735760403660031901126104735760206106466114e6612b5f565b6114ee612b89565b90613f65565b5034610473576020366003190112610473576020906040906001600160a01b0361151c612bb5565b168152601283522054604051908152f35b5034610473578060031936011261047357602060405165ffffffffffff7f0000000000000000000000000000000000000000000000000000000000000708168152f35b50346104735760403660031901126104735761158a612bb5565b6001600160a01b03168152600d60205260408120805460243592908310156104735760206115b88484612cd8565b90546040519160031b1c8152f35b50346104735780600319360112610473576040517f00000000000000000000000084f2b4d4cf8da889701fbe83d127896880c043256001600160a01b03168152602090f35b503461047357806003193601126104735760206040517f00000000000000000000000000000000000000000000000000470de4df8200008152f35b5034610473576020366003190112610473576020610b6a61032b612b5f565b5034610473576020366003190112610473576020610646611684612bb5565b613dd1565b50346104735760603660031901126104735760206106466116a8612b5f565b6116b0612bcb565b6116b8612b9f565b91613bc6565b50346104735760203660031901126104735760043563ffffffff811680910361123e5781906116ef61032b426158c4565b65ffffffffffff80600454169116908111611765575b50600a546001600160a01b031690813b1561176157829160248392604051948593849263467ef06f60e01b845260048401525af18015611756576117465750f35b61174f90612d97565b6104735780f35b6040513d84823e3d90fd5b5050fd5b61176d613b3a565b65ffffffffffff1960045416176004555f611705565b5034610473576040366003190112610473576004356117a0612bcb565b6117a8614866565b6117bd825f52600260205260405f2054151590565b156103dd576001600160a01b038116156117da576106de9161501a565b604051630a32281360e21b8152600490fd5b50346104735760203660031901126104735760ff60406020926004358152601084522054166040519015158152f35b5034610473578060031936011261047357546040516001600160a01b039091168152602090f35b50346104735780600319360112610473576040517f00000000000000000000000046d45d6be6214f6bd8124187cad1a5302755d7a26001600160a01b03168152602090f35b5034610473576040366003190112610473576004359060249182359063ffffffff821680920361123e576118c6335f52600660205260405f2054151590565b15611b04573383526020601481526118ef82604086206001915f520160205260405f2054151590565b15611ab75761190061032b426158c4565b65ffffffffffff80600454169116908111611ae6575b5061192c335f52600660205260405f2054151590565b15611acf573384526014815261195382604086206001915f520160205260405f2054151590565b15611ab75790816119ad9260018060a01b03600a541692604051906001600160601b03199060601b16828201526014815261198d81612daa565b6040518095819263fd7ac5e760e01b83528460048401528a830190612e01565b0381855afa928315611aac578593611a7d575b50604051639b4d9d1360e01b81526004810184905281818881865afa918215611a72578692611a45575b505015611a2e57938484953b15611a2a5784928360449260405196879586946376c951cf60e01b865260048601528401525af18015611756576117465750f35b8480fd5b6040516259d43960e71b8152600481018390528590fd5b611a649250803d10611a6b575b611a5c8183612de0565b810190612e25565b5f806119ea565b503d611a52565b6040513d88823e3d90fd5b9080935081813d8311611aa5575b611a958183612de0565b8101031261123e5751915f6119c0565b503d611a8b565b6040513d87823e3d90fd5b60405163c50edab960e01b8152600481018390528590fd5b604051634c383c5160e01b81523360048201528590fd5b611aee613b3a565b65ffffffffffff1960045416176004555f611916565b604051634c383c5160e01b81523360048201528490fd5b50346104735760203660031901126104735760043590611b46825f52600260205260405f2054151590565b156103dd576040816108a093611b629352600360205220615874565b60405191829182612be1565b5034610473578060031936011261047357602060405165ffffffffffff7f0000000000000000000000000000000000000000000000000000000000000384168152f35b50346104735760403660031901126104735765ffffffffffff6040611bd4612bb5565b92611bdd612b74565b9360018060a01b03168152600b6020522091165f52602052602060ff60405f2054166040519015158152f35b503461047357806003193601126104735760405180600854928383526020809301809460085f527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3905f5b818110611cac5750505082611c6a910383612de0565b6040519260408401906001936001865260406020870152518092526060850195925b828110611c995785870386f35b8351875295810195928101928401611c8c565b825484529286019260019283019201611c54565b503461047357602036600319011261047357600435611cea815f52600260205260405f2054151590565b156103dd57604082819281526003602052206003600282015491015482519182526020820152f35b50346104735780600319360112610473576106de613b3a565b5034610473578060031936011261047357611d44614866565b5f80546001600160a01b0319811682556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b50346104735780600319360112610473576040517f0000000000000000000000000360c1cb32a20d97b358538d9db71339ce2c95926001600160a01b03168152602090f35b503461047357604036600319011261047357604060209165ffffffffffff611def612b5f565b168152600f8352818120602435825283522054604051908152f35b5034610473576020366003190112610473576004356001600160601b03811680910361123e5760209160018214918215611e4b575b50506040519015158152f35b60409250815260098352205415155f80611e3f565b5034610473576080366003190112610473576024356064356004356044356001600160a01b03831680840361123e57611e97614866565b156117da576001821480611f19575b611f0757611eb382615944565b15610ad2577f8828c5448d14dfabfef1825a63323c740b9f79563a375c10bfff3215f754c10960406106de9584938489526003602052806003848b20846002820155015582519182526020820152a261501a565b604051633631e2dd60e21b8152600490fd5b50808411611ea6565b5034610473576060366003190112610473576020610646611f41612bb5565b611f49612b74565b611f51612b9f565b916137ea565b503461047357606036600319011261047357611f71612b5f565b90611f7a612b89565b906044359260018060a01b03841680940361123e576001600160601b039165ffffffffffff604092168152600e6020522091165f5260205260405f20905f52602052602060405f2054604051908152f35b50346104735780600319360112610473576040517f00000000000000000000000059fbba1845690fb202d69a77cfec5564df12d3de6001600160a01b03168152602090f35b5034610473576040366003190112610473576004359061202e612bcb565b612043835f52600260205260405f2054151590565b156103dd57604082602094612075945260038552209060018060a01b0316906001915f520160205260405f2054151590565b6040519015158152f35b503461047357604036600319011261047357612099612bb5565b6120a4610953612b74565b60018060a01b038092168352601491602091601483526120c660408620615874565b936120d18551612fd0565b9286928791600a5416915b8751811015612243576120ef8189613002565b51604051906001600160601b03198160601b168983015285825261211282612daa565b60405163fd7ac5e760e01b815289818061213760249684600484015287830190612e01565b0381895afa908115612238578c9161220b575b5060405190636af907fb60e11b825260048201528b808285818a5afa80156121fe5761219592828893926121db575b505065ffffffffffff60c08160a0840151169201511690614fc4565b6121a4575b50506001016120dc565b9086905f1982146121c95750906121c16001809493019789613002565b52905f61219a565b634e487b7160e01b8c5260116004528bfd5b6121f792503d8091833e6121ef8183612de0565b810190613045565b5f8e612179565b50604051903d90823e3d90fd5b90508981813d8311612231575b6122228183612de0565b8101031261123e57515f61214a565b503d612218565b6040513d8e823e3d90fd5b8887878761225081612fd0565b91845b82811061229e5750505060405192828493840190808552835180925280604086019401925b82811061228757505050500390f35b835185528695509381019392810192600101612278565b806122af6001928498969798613002565b516122ba8288613002565b520194939294612253565b5034610473576020366003190112610473576106de6122e2612b5f565b612e55565b503461047357608036600319011261047357612301612b5f565b612309612bcb565b506064356001600160601b03811680820361123e57612326614866565b604065ffffffffffff948585168152601360205220905f5260205260ff60405f20541615612389575b505061235d61032b426158c4565b600454821691161161237c575b60405163c736d3b160e01b8152600490fd5b612384613b3a565b61236a565b61239291614783565b505f8061234f565b503461047357602080600319360112611360576123b5612bb5565b906123be614866565b6123ca61032b426158c4565b65ffffffffffff806004541691169081116125aa575b506001600160a01b038281165f81815260066020526040902054909291906125915760405163c3c5a54760e01b81526004810184905282816024817f00000000000000000000000046d45d6be6214f6bd8124187cad1a5302755d7a286165afa908115611a72578691612574575b501561255b576040516308834cb560e21b81526001600160a01b0385811660048301527f00000000000000000000000084f2b4d4cf8da889701fbe83d127896880c043259081166024830152918390829060449082907f0000000000000000000000000360c1cb32a20d97b358538d9db71339ce2c9592165afa908115611a7257869161253e575b501561251357509060076124f392828652528360408120556158f6565b15612501576106de90614ba2565b604051635335595160e11b8152600490fd5b60405163a2cb00e760e01b81526001600160a01b038581166004830152919091166024820152604490fd5b6125559150833d8511611a6b57611a5c8183612de0565b5f6124d6565b604051634c383c5160e01b815260048101849052602490fd5b61258b9150833d8511611a6b57611a5c8183612de0565b5f61244e565b604051631da9d69960e01b815260048101849052602490fd5b6125b2613b3a565b65ffffffffffff1960045416176004555f6123e0565b5034610473578060031936011261047357602065ffffffffffff60045416604051908152f35b503461123e57606036600319011261123e57612608612bb5565b602435906044359163ffffffff831680930361123e5761262a61032b426158c4565b65ffffffffffff80600454169116908111612705575b506001600160a01b039182165f81815260066020526040902054156126ed575f5260146020526126818160405f206001915f520160205260405f2054151590565b156126d55750600a541690813b1561123e575f91602483926040519485938492631474cbc960e31b845260048401525af180156126ca576126c0575080f35b61001a9150612d97565b6040513d5f823e3d90fd5b6024906040519063c50edab960e01b82526004820152fd5b60249060405190634c383c5160e01b82526004820152fd5b61270d613b3a565b65ffffffffffff1960045416176004555f612640565b3461123e575f36600319011261123e57602060405165ffffffffffff7f0000000000000000000000000000000000000000000000000000000000000834168152f35b3461123e57604036600319011261123e5760043560243561278861032b426158c4565b65ffffffffffff806004541691169081116128e8575b50335f526020601481526127c38360405f206001915f520160205260405f2054151590565b156128cf5760015f526003815260405f20600360028201549101548084116128b157508083106128935750600a5460405160609490941b6001600160601b03191684830152601484526128469382916001600160a01b031661282482612daa565b604051808097819463fd7ac5e760e01b83528660048401526024830190612e01565b03915afa9081156126ca575f91612863575b61001a8383336149eb565b905082813d831161288c575b6128798183612de0565b8101031261123e5761001a915183612858565b503d61286f565b82604491604051916353fe4d4b60e11b835260048301526024820152fd5b83604491604051916369367fbf60e01b835260048301526024820152fd5b60405163c50edab960e01b815260048101849052602490fd5b6128f0613b3a565b65ffffffffffff1960045416176004558261279e565b3461123e57604036600319011261123e5761291f612b5f565b65ffffffffffff61292e612b89565b91165f5260136020526001600160601b0360405f2091165f52602052602060ff60405f2054166040519015158152f35b3461123e57602036600319011261123e5760043561297a614866565b61298661032b426158c4565b65ffffffffffff806004541691169081116129ba575b506129a681614891565b6103ef576129b3906155ee565b156103dd57005b6129c2613b3a565b65ffffffffffff1960045416176004558161299c565b3461123e5760208060031936011261123e576004356129f961032b426158c4565b65ffffffffffff80600454169116908111612ad5575b50612a25335f52600660205260405f2054151590565b1561131a57335f5260148252612a4c8160405f206001915f520160205260405f2054151590565b156126d557600a54604051606083901b6001600160601b0319168185015260148152612a8893918291906001600160a01b031661282482612daa565b03915afa9081156126ca575f91612aa5575b61001a838333615303565b905082813d8311612ace575b612abb8183612de0565b8101031261123e5761001a915183612a9a565b503d612ab1565b612add613b3a565b65ffffffffffff19600454161760045582612a0f565b3461123e57602036600319011261123e576020610b6a610953612b5f565b3461123e57604036600319011261123e57602090612b2d612b5f565b65ffffffffffff612b3c612b89565b91165f52600c83526001600160601b0360405f2091165f52825260405f20548152f35b6004359065ffffffffffff8216820361123e57565b6024359065ffffffffffff8216820361123e57565b602435906001600160601b038216820361123e57565b604435906001600160601b038216820361123e57565b600435906001600160a01b038216820361123e57565b602435906001600160a01b038216820361123e57565b60209060206040818301928281528551809452019301915f5b828110612c08575050505090565b83516001600160a01b031685529381019392810192600101612bfa565b600854811015612c5a5760085f527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee301905f90565b634e487b7160e01b5f52603260045260245ffd5b600554811015612c5a5760055f527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db001905f90565b600154811015612c5a5760015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf601905f90565b8054821015612c5a575f5260205f2001905f90565b90600165ffffffffffff80931601918211612d0457565b634e487b7160e01b5f52601160045260245ffd5b91909165ffffffffffff80809416911601918211612d0457565b65ffffffffffff9081167f0000000000000000000000000000000000000000000000000000000000000708821602908116908103612d0457612d94907f0000000000000000000000000000000000000000000000000000000068272eb9612d18565b90565b6001600160401b03811161125557604052565b604081019081106001600160401b0382111761125557604052565b602081019081106001600160401b0382111761125557604052565b90601f801991011681019081106001600160401b0382111761125557604052565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b9081602091031261123e5751801515810361123e5790565b65ffffffffffff9182169082160391908211612d0457565b9065ffffffffffff91828116908115612fa757612e7461032b426158c4565b915f60049186600454169087861695828711612f93575b50878316809111612f46575b50505091905f925f925b86831687851610612ef7575b50506004546040805165ffffffffffff97909216871682529390951660208601525091927f4d292688ca328e445b76fb7747c31983e6367477e08b40b6a30f7cf280ee80b69250a1565b9091929381878454161015612f4057612f0e614c4a565b15612f40578616868114612f2d57600190810194018616929190612ea1565b601183634e487b7160e01b5f525260245ffd5b93612ead565b909192935015612f5a5750905f8080612e97565b9150507f4d292688ca328e445b76fb7747c31983e6367477e08b40b6a30f7cf280ee80b69293506040809250519081525f6020820152a1565b612fa09193508290612e3d565b915f612e8b565b604051631752afd760e31b8152600490fd5b6001600160401b0381116112555760051b60200190565b90612fda82612fb9565b612fe76040519182612de0565b8281528092612ff8601f1991612fb9565b0190602036910137565b8051821015612c5a5760209160051b010190565b6001600160401b03811161125557601f01601f191660200190565b51906001600160401b038216820361123e57565b90602090818382031261123e5782516001600160401b039384821161123e570160e08183031261123e576040519360e0850185811082821117611255576040528151600681101561123e5785528382015190811161123e5781019180601f8401121561123e578251936130b785613016565b916130c56040519384612de0565b85835281868601011161123e575f8186613133978260c098018387015e840101528501526130f560408201613031565b604085015261310660608201613031565b606085015261311760808201613031565b608085015261312860a08201613031565b60a085015201613031565b60c082015290565b51906001600160a01b038216820361123e57565b519065ffffffffffff8216820361123e57565b9081606091031261123e576131768161313b565b91612d9460406131886020850161314f565b930161314f565b9081602091031261123e57516001600160601b038116810361123e5790565b91908201809211612d0457565b91905f9265ffffffffffff80831691825f5260209360138552604090815f209460019560015f52875260ff835f2054166134fb57506131f990612d32565b9460018060a01b039560049680885460301c16958451916374d4e49160e01b835284838b818b5afa9283156134f1575f936134c2575b50989796905f96959493929699807f00000000000000000000000084f2b4d4cf8da889701fbe83d127896880c0432516918416925b888c1061327957505050505050505050505050565b909192939495969798999a9c8d88519063328dc3d960e11b82528d820152606060249181818481885afa801561347e5789905f925f905f92613488575b506132c2939450614fc4565b15613447578951639374a51d60e01b8152908516818f018190529089818481885afa90811561347e578e916001600160601b03915f91613451575b50160361344757888e918b5192838092630ce9b79360e41b82525afa90811561343d579189918f8f9489918f8f908b935f966133f0575b50908361337f9282519261334784612dc5565b5f8452519a8b998a988997633dc5038960e01b89528801528601521660448401528d606484015260a0608484015260a4830190612e01565b0392165afa9081156133e657908c915f916133b3575b506133a091926131ae565b9d5b019a99989796959493929190613264565b809250898092503d83116133df575b6133cc8183612de0565b8101031261123e57518b906133a0613395565b503d6133c2565b89513d5f823e3d90fd5b9796985050505050505081813d8311613436575b61340e8183612de0565b8101031261123e578c918b8f87899161337f8f8f9761342d859161313b565b96919250613334565b503d613404565b8a513d5f823e3d90fd5b50509c8a906133a2565b61347191508c8d3d10613477575b6134698183612de0565b81019061318f565b5f6132fd565b503d61345f565b8b513d5f823e3d90fd5b9150506132c29250836134b09294903d106134bb575b6134a88183612de0565b810190613162565b9093919384936132b6565b503d61349e565b9092508481813d83116134ea575b6134da8183612de0565b8101031261123e5751915f61322f565b503d6134d0565b86513d5f823e3d90fd5b5f908152600e87528281206001825287528281206001600160a01b039094168152929095529020549450505050565b91905f9265ffffffffffff80831691825f5260209360138552604090815f209460019560015f52875260ff835f2054166134fb575061356890612d32565b9460018060a01b039560049680885460301c16958451916374d4e49160e01b835284838b818b5afa9283156134f1575f936137bb575b50989796905f96959493929699807f00000000000000000000000084f2b4d4cf8da889701fbe83d127896880c0432516918416925b888c106135e857505050505050505050505050565b909192939495969798999a9c8d88519063328dc3d960e11b82528d820152606060249181818481885afa801561347e5789905f925f905f92613791575b50613631939450614fc4565b1561376a578951639374a51d60e01b8152908516818f018190529089818481885afa90811561347e578e916001600160601b03915f91613774575b50160361376a57888e918b5192838092630ce9b79360e41b82525afa90811561343d579189918f8f9489918f8f908b935f9661371d575b5090836136b69282519261334784612dc5565b0392165afa9081156133e657908c915f916136ea575b506136d791926131ae565b9d5b019a999897969594939291906135d3565b809250898092503d8311613716575b6137038183612de0565b8101031261123e57518b906136d76136cc565b503d6136f9565b9796985050505050505081813d8311613763575b61373b8183612de0565b8101031261123e578c918b8f8789916136b68f8f9761375a859161313b565b969192506136a3565b503d613731565b50509c8a906136d9565b61378b91508c8d3d10613477576134698183612de0565b5f61366c565b9150506136319250836137b09294903d106134bb576134a88183612de0565b909391938493613625565b9092508481813d83116137e3575b6137d38183612de0565b8101031261123e5751915f61359e565b503d6137c9565b929190925f9365ffffffffffff9283821690815f5260209060138252604093845f20936001600160601b0380931694855f52845260ff865f205416613b0b575061383390612d32565b9060018060a01b039660049780895460301c16978751936374d4e49160e01b855286858c818d5afa9485156133e6575f95613adc575b5099989291905f9897969594989a827f00000000000000000000000084f2b4d4cf8da889701fbe83d127896880c0432516938616945b8a8d106138b55750505050505050505050505050565b909192939495969798999a9b9d8c8f8c519163328dc3d960e11b8352820152606060249181818481885afa8015613aa8578b905f925f905f92613ab2575b506138ff939450614fc4565b15613a80578e90878e5191639374a51d60e01b83521680928201528b818481885afa908115613aa8578d9187918e5f92613a8b575b50501603613a80578a8f918e5192838092630ce9b79360e41b82525afa908115613a7657918f91898f8f95948f95918e8c8e945f97613a25575b50908483926139b994519361398285612dc5565b5f8552519b8c9a8b998a98633dc5038960e01b8a52890152870152166044850152606484015260a0608484015260a4830190612e01565b0392165afa908115613a1b575f916139ed575b506139d9906001926131ae565b9e5b019b9a9998979695949392919061389f565b90508981813d8311613a14575b613a048183612de0565b8101031261123e575160016139cc565b503d6139fa565b8c513d5f823e3d90fd5b989799505050505050505081813d8311613a6f575b613a448183612de0565b8101031261123e578b918f898f89928f958c928f84613a656139b99461313b565b979192935061396e565b503d613a3a565b8d513d5f823e3d90fd5b50509d6001906139db565b613aa19250803d10613477576134698183612de0565b5f8e613934565b8e513d5f823e3d90fd5b9150506138ff925083613ad19294903d106134bb576134a88183612de0565b9093919384936138f3565b9094508681813d8311613b04575b613af48183612de0565b8101031261123e5751935f613869565b503d613aea565b5f908152600e845285812094815293835250508282206001600160a01b03909416825292909252902054925050565b613b4661032b426158c4565b65ffffffffffff90816004541690818382161115613bc1578291613b6991612e3d565b1660019060018111613ba2575f5b8184821610613b87575b50505050565b613b8f614c4a565b15613b9d5782018316613b77565b613b81565b60449060405190633c90bbed60e21b8252600482015260016024820152fd5b505050565b9091600191906001600160601b0381168303613dc6575060408051633bc7daf160e01b81526001600160a01b039490941660048086019190915265ffffffffffff831660248601529391905f83604481305afa928315613dbc575f93613d2b575b50600a545f969587959490916001600160a01b031690855b613c4d575b50505050505050565b8051871015613d2657613cab98613c648883613002565b5185519060209182916001600160601b03199060601b168282015260148152613c8c81612daa565b8751809d819263fd7ac5e760e01b8352848a8401526024830190612e01565b0381875afa9081156134f15788999a9b5f92613cf3575b5091613cea91899365ffffffffffff8a165f52600f8152885f20915f5252865f2054906131ae565b99980196613c3f565b8180949a508193503d8311613d1f575b613d0d8183612de0565b8101031261123e575188979188613cc2565b503d613d03565b613c44565b9092503d805f833e613d3d8183612de0565b810190602090818184031261123e578051906001600160401b03821161123e57019180601f8401121561123e578251613d7581612fb9565b93613d8287519586612de0565b818552838086019260051b82010192831161123e578301905b828210613dad5750505050915f613c27565b81518152908301908301613d9b565b50513d5f823e3d90fd5b9150612d94926137ea565b905f9160018060a01b038091165f52602090600d8252604090815f20925f93805492600a5416905b838610613e0857505050505050565b909192939496613e6584613e1c8a85612cd8565b9054895160039290921b1c60601b6001600160601b0319168282015260148152613e4581612daa565b88518093819263fd7ac5e760e01b83528460048401526024830190612e01565b0381875afa908115613ee6575f91613eb8575b50600191613eac9165ffffffffffff613e9361032b426158c4565b165f52600f8752885f20905f528652875f2054906131ae565b97019493929190613df9565b90508481813d8311613edf575b613ecf8183612de0565b8101031261123e57516001613e78565b503d613ec5565b87513d5f823e3d90fd5b613f1b907f0000000000000000000000000000000000000000000000000000000068272eb990612e3d565b65ffffffffffff807f000000000000000000000000000000000000000000000000000000000000070816918215613f5157160490565b634e487b7160e01b5f52601260045260245ffd5b9065ffffffffffff91828116805f52601360205260405f20906001600160601b03841691825f5260205260ff60405f2054166140835750505f92613fa882612d32565b9080613fb3426158c4565b1690808316918211908115614045575b5061402d575090600554915f935b838510613fe057505050505090565b909192939482613ffb613ff2886153c4565b90939193614fc4565b15614023576001916140118487614017946137ea565b906131ae565b955b0193929190613fd1565b5094600190614019565b6024906040519063c2ef899360e01b82526004820152fd5b905061407a614053426158c4565b7f000000000000000000000000000000000000000000000000000000000000083490612e3d565b1681105f613fc3565b92509250505f52600c60205260405f20905f5260205260405f205490565b919060408382031261123e57604080516001600160401b039491810185811182821017611255576040528094823563ffffffff8116810361123e5782526020928381013591821161123e57019280601f8501121561123e57833561410481612fb9565b946141126040519687612de0565b818652848087019260051b82010192831161123e578401905b828210614139575050500152565b81356001600160a01b038116810361123e57815290840190840161412b565b805490600160401b821015611255578161417a91600161419094018155612cd8565b819391549060031b91821b915f19901b19161790565b9055565b6040820163ffffffff8251168352602060608180940151946040838201528551809452019301915f5b8281106141cb575050505090565b83516001600160a01b0316855293810193928101926001016141bd565b91908203918211612d0457565b8015612d04575f190190565b61420d61032b426158c4565b9160018060a01b0382165f52600b60205260405f2065ffffffffffff84165f5260205260ff60405f205416614677576001600160a01b0382165f818152600b6020908152604080832065ffffffffffff881684528252808320805460ff191660011790559282526006905220541561465657614288826151c4565b61429183613dd1565b9060018060a01b0384165f52600d60205260405f209586549280831461464c578083116145cc57826142c2916141e8565b925b801515806145c3575b15614582576142db906141f5565b916143416142e9848a612cd8565b91905460018060a01b03600a541660206040516001600160601b0319848760031b1c60601b16828201526014815261432081612daa565b6040518095819263fd7ac5e760e01b83528460048401526024830190612e01565b0381845afa9283156126ca575f9361454e575b50604051639b4d9d1360e01b815260048101849052602081602481855afa9081156126ca575f9161452f575b50614526575f60249160405192838092636af907fb60e11b82528760048301525afa9081156126ca575f9161450c575b50519660068810156144f85760028098036144ee5765ffffffffffff8b165f52600f60205260405f20835f5260205260405f205480156144e35761441b90808310156144dd5782905b8b15158c83826144d3575b50506144cb575b81614415916141e8565b926141e8565b60015f5260036020527fa15bc60c955c405d20d9149c709e2460f1c2d9a497496a7f46004d1772c3054c90980154811080156144bb575b1561446d57506144679260031b1c9088615303565b916142c4565b919261447a83858c6149eb565b60405192835260031b1c906001600160a01b038916907f48d83a0359ece1af3ea738ea6e14f7f30eb31b2c57f44b98dbc41469c83a525a90602090a4614467565b506144c58a615137565b15614452565b8b915061440b565b1190508c5f614404565b806143f9565b509650505050614467565b9650505050614467565b634e487b7160e01b5f52602160045260245ffd5b61452091503d805f833e6121ef8183612de0565b5f6143b0565b50505050614467565b614548915060203d602011611a6b57611a5c8183612de0565b5f614380565b9092506020813d60201161457a575b8161456a60209383612de0565b8101031261123e5751915f614354565b3d915061455d565b5060405191825295506001600160a01b0390931693507fac43c581689bab0b319a9fc8e659012b23ee3494f1bf17edad354d457ccba3eb92602092509050a2565b508315156142cd565b90965081614603929650602094507fac43c581689bab0b319a9fc8e659012b23ee3494f1bf17edad354d457ccba3eb9593506141e8565b6040519081526001600160a01b038216907fb066d744c0abef0595669b34ee833c2d24b4d89382dc63f616513f3024a09464908490a26040519384526001600160a01b031692a2565b5095505050505050565b604051634c383c5160e01b81526001600160a01b0383166004820152602490fd5b5060405163316d57cf60e11b81526001600160a01b0391909116600482015265ffffffffffff919091166024820152604490fd5b905f916146b781612d32565b6005545f915b81831061470b5750505065ffffffffffff16805f52600c60205260405f2060015f526020528260405f20555f52601360205260405f2060015f5260205260405f20600160ff19825416179055565b829561472461471b8394956153c4565b90949194614fc4565b15614779578161476f9161473a8760019561352a565b809265ffffffffffff89165f52602090600e8252604091825f20885f528152825f2091888060a01b03165f52525f20556131ae565b955b0191906146bd565b9560019150614771565b91905f9261479081612d32565b6005545f915b8183106147ed5750505065ffffffffffff16805f52600c6020526001600160601b0360405f20921691825f526020528360405f20555f52601360205260405f20905f5260205260405f20600160ff19825416179055565b82966147fd61471b8394956153c4565b1561485c57816148529161481488886001966137ea565b809265ffffffffffff89165f52602090600e8252604091825f206001600160601b038d165f528152825f2091888060a01b03165f52525f20556131ae565b965b019190614796565b9660019150614854565b5f546001600160a01b0316330361487957565b60405163118cdaa760e01b8152336004820152602490fd5b6004805460301c6001600160a01b0390811692915f5b604080516374d4e49160e01b8152602090818186818b5afa9081156149e1575f916149b4575b508310156149a957815163328dc3d960e11b8152848101849052606090602490828183818d5afa92831561499f57918493918995935f9361497e575b50508551948592630d5d5d9360e21b84521688830152818b5afa9283156149755750916001600160601b039187935f92614958575b5050161461494e576001016148a7565b5050505050600190565b61496e9250803d10613477576134698183612de0565b5f8061493e565b513d5f823e3d90fd5b614995929350803d106134bb576134a88183612de0565b5050905f80614909565b85513d5f823e3d90fd5b505050505050505f90565b90508181813d83116149da575b6149cb8183612de0565b8101031261123e57515f6148cd565b503d6149c1565b83513d5f823e3d90fd5b9291906149fa61032b426158c4565b9165ffffffffffff8084165f52602090600f8252604094855f20855f528352855f205460018060a01b03986024858b600a54168a5192838092639b4d9d1360e01b82528c60048301525afa9081156133e6575f91614b85575b50614b6d575f91808711614b33575b50946044945f9794614ab08995614aeb958e6001600160401b039e9f9b16885260128552614a948d89209182546131ae565b9055878752601084528b8720805460ff19166001179055612ced565b168452600f8152888420908585525280888420557f00000000000000000000000000000000000000000000000000470de4df820000906152d3565b93600a541686519788958694633aa2795360e01b865260048601521660248401525af19081156149755750614b1d5750565b614b30903d805f833e6121ef8183612de0565b50565b614b3f919250866141e8565b90614b49816151c4565b8211614b55575f614a62565b8751630ae3d83f60e21b815260048101879052602490fd5b8751638239160d60e01b815260048101889052602490fd5b614b9c9150863d8811611a6b57611a5c8183612de0565b5f614a53565b6001600160a01b03165f8181526007602052604090205490811580614c39575b614c215765ffffffffffff918280821615159182614c13575b5050614c0157614b3091614bee426158c4565b16815f52600760205260405f20556158f6565b6040516389faef9d60e01b8152600490fd5b60301c16159050825f614bdb565b6024906040519063015ab34360e11b82526004820152fd5b50600660205260405f205415614bc2565b65ffffffffffff614c5d61032b426158c4565b8160045416918291161115614fbf57614c7590612ced565b5f915b600554831015614fa057614c8b836153c4565b50509265ffffffffffff8316155f14614f81575f935b60018060a01b0381165f52600d60205260405f20948554805b614cf1575050600180809495965060a01b03165f52601260205260405f208054614ce8575b50019190614c78565b5f90555f614cdf565b614cfa906141f5565b614d3f614d078289612cd8565b905490602060018060a01b03600a5416604051906001600160601b0319858560031b1c60601b16838301526014825261282482612daa565b03915afa9283156126ca575f93614f4d575b50825f5260116020528360ff60405f2054161580614f37575b614ec35750601160205260ff60405f20541680614e9d575b80614e76575b614db9575b50505060106020528060405f20805460ff8116614dac575b5050614cba565b60ff191690555f80614da5565b6001600160a01b0386165f908152600d6020526040812080549290915b838110614dfe575b50505050505f52601160205260405f2060ff1981541690555f8080614d8d565b614e088184612cd8565b868460031b1c91549060031b1c14614e2257600101614dd6565b5f1984019450919284119050612d045782614e45938203614e4f575b50506155b1565b5f80808080614dde565b61417a614e5f614e6d9285612cd8565b90549060031b1c9284612cd8565b90555f80614e3e565b5065ffffffffffff85165f52600f60205260405f20835f5260205260405f20541515614d88565b5065ffffffffffff89165f52600f60205260405f20835f5260205260405f205415614d82565b9291505065ffffffffffff88165f52600f908160205260405f20815f5260205260405f205415614ef4575050614cba565b65ffffffffffff85165f528160205260405f20815f5260205260405f20549165ffffffffffff8a165f5260205260405f20905f5260205260405f20555f80614da5565b505060106020528360ff60405f20541615614d6a565b9092506020813d602011614f79575b81614f6960209383612de0565b8101031261123e5751915f614d51565b3d9150614f5c565b5f1965ffffffffffff84160165ffffffffffff8111612d045793614ca1565b915065ffffffffffff1665ffffffffffff196004541617600455600190565b505f90565b65ffffffffffff9081168015159392908461500d575b5083614fe7575b50505090565b811680159350918315615000575b5050505f8080614fe1565b16111590505f8080614ff5565b838316101593505f614fda565b805f52600360205261503960405f209260018060a01b031680936159e0565b15615064577fc53536963369dbfa4c398238ebb9b09fce3943a140928bd25d3052a8a9cacdaf5f80a3565b60405163ad2ac00f60e01b8152600490fd5b8115613f51570490565b61508c61032b426158c4565b60018060a01b0382165f52602090600d6020526040805f20549060085494851561512c57600195948387019384105f5b8781106150d157505050505050505050600190565b6150da81612c25565b9054600391821b1c90836150f86001600160601b0384168b896137ea565b90612d04578861510791615076565b915f5284526002865f2001541161511f5788016150bc565b5050505050505050505f90565b505050505050600190565b61514361032b426158c4565b60018060a01b0382165f52602091600d6020526040805f20549160085494851561512c575f5b86811061517c5750505050505050600190565b61518581612c25565b9054600391821b1c906151ab876151a66001600160601b0385168b896137ea565b615076565b915f5283526002855f200154116149a957600101615169565b6151d96151d361032b426158c4565b826131bb565b600a546040805163f113073360e01b8152306004820152929390926001600160a01b039284908290602490829087165afa9081156152c9575f91615287575b506001600160401b037f00000000000000000000000000000000000000000000000000470de4df8200009116818102918183041490151715612d045780851161527f575b50165f5260126020525f20548082111561527957612d94916141e8565b50505f90565b93505f61525c565b90508381813d83116152c2575b61529e8183612de0565b8101031261123e576020816152b56152bc93613031565b5001613031565b5f615218565b503d615294565b84513d5f823e3d90fd5b906152dd91615076565b6001600160401b03908181116152f1571690565b604051632a6bc64560e01b8152600490fd5b91909165ffffffffffff61532161531c61032b426158c4565b612ced565b165f52600f60205260405f20835f526020525f6040812055601160205260405f20600160ff1982541617905560018060a01b03905f8083600a54166024604051809481936325fedc3560e21b83528a60048401525af180156126ca576153aa575b50167f52dc76c098767cf70c407df0346869d074f6a3aa927b6497938715a0188a6d755f80a4565b6153bd903d805f833e6121ef8183612de0565b505f615382565b6153cd90612c6e565b90549060031b1c805f52600760205260405f20549060018060a01b03169165ffffffffffff8083169260301c1690565b5f9060018060a01b036004938160045460301c16935b604080516374d4e49160e01b81526020919082818a818b5afa9081156155a7575f9161557a575b5083101561556e5780519163328dc3d960e11b835283898401526060602493818186818d5afa9182156152c95791839189935f9261554f575b50508451630d5d5d9360e21b815292168b8301819052948290818c5afa9081156149e1576001600160601b038b93928a925f91615532575b50161493846154c5575b5050505061512c57600101615413565b838293949550519384809263d8dfeb4560e01b82525afa92831561497557505f926154fc575b50508316828416145f8781806154b5565b90809250813d831161552b575b6155138183612de0565b8101031261123e576155249061313b565b5f806154eb565b503d615509565b6155499150843d8611613477576134698183612de0565b5f6154ab565b6155659250803d106134bb576134a88183612de0565b50505f80615473565b50505050505050505f90565b90508281813d83116155a0575b6155918183612de0565b8101031261123e57515f61543a565b503d615587565b82513d5f823e3d90fd5b80549081156155da575f19918201916155ca8383612cd8565b909182549160031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b5f818152600960205260409020548015615279575f1990808201818111612d045760085490838201918211612d045781810361565f575b50505060085480156155da5781019061563d82612c25565b909182549160031b1b191690556008555f5260096020525f6040812055600190565b61567d61566e61417a93612c25565b90549060031b1c928392612c25565b90555f52600960205260405f20555f8080615625565b5f818152600660205260409020548015615279575f1990808201818111612d045760055490838201918211612d0457818103615704575b50505060055480156155da578101906156e282612c6e565b909182549160031b1b191690556005555f5260066020525f6040812055600190565b61572261571361417a93612c6e565b90549060031b1c928392612c6e565b90555f52600660205260405f20555f80806156ca565b5f818152600260205260409020548015615279575f1990808201818111612d045760015490838201918211612d04578181036157a9575b50505060015480156155da5781019061578782612ca3565b909182549160031b1b191690556001555f5260026020525f6040812055600190565b6157c76157b861417a93612ca3565b90549060031b1c928392612ca3565b90555f52600260205260405f20555f808061576f565b906001820191815f528260205260405f2054908115155f1461586c575f198281019290818411612d04578254908101908111612d045783816158279503615837575b5050506155b1565b5f526020525f6040812055600190565b61585761584761417a9386612cd8565b90549060031b1c92839286612cd8565b90555f528460205260405f20555f808061581f565b505050505f90565b90604051918281549182825260209260208301915f5260205f20935f905b8282106158aa575050506158a892500383612de0565b565b855484526001958601958895509381019390910190615892565b65ffffffffffff908181116158d7571690565b604490604051906306dfcc6560e41b8252603060048301526024820152fd5b805f52600660205260405f2054155f14614fbf57600554600160401b8110156112555761592d61417a826001859401600555612c6e565b9055600554905f52600660205260405f2055600190565b805f52600260205260405f2054155f14614fbf57600154600160401b8110156112555761597b61417a826001859401600155612ca3565b9055600154905f52600260205260405f2055600190565b805f52600960205260405f2054155f14614fbf57600854600160401b811015611255576159c961417a826001859401600855612c25565b9055600854905f52600960205260405f2055600190565b5f8281526001820160205260409020546152795780615a0183600193614158565b8054925f520160205260405f2055600190565b805f52600760205260405f205490811580615a32575b614c21575090565b50600660205260405f205415615a2a56fea26469706673582212202bc4a3b83242b07631b1b21ba90e919382b5d555006d1770b7ff96f18bc6e8dc64736f6c63430008190033

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

00000000000000000000000084f2b4d4cf8da889701fbe83d127896880c0432500000000000000000000000046d45d6be6214f6bd8124187cad1a5302755d7a2000000000000000000000000c3b09a650c78dafb79e3c5b782402c8dc523fe880000000000000000000000000360c1cb32a20d97b358538d9db71339ce2c9592000000000000000000000000000000000000000000000000000000000000070800000000000000000000000000000000000000000000000000000000000008340000000000000000000000000000000000000000000000000000000000000384000000000000000000000000c15adc0c87db428b9fc0bb3246e7deb2a991aa6e00000000000000000000000059fbba1845690fb202d69a77cfec5564df12d3de00000000000000000000000000000000000000000000001b1ae4d6e2ef5000000000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000470de4df820000

-----Decoded View---------------
Arg [0] : settings (tuple):
Arg [1] : l1ValidatorManager (address): 0x84F2B4D4cF8DA889701fBe83d127896880c04325
Arg [2] : operatorRegistry (address): 0x46D45D6be6214F6bd8124187caD1a5302755d7A2
Arg [3] : vaultRegistry (address): 0xC3b09a650c78daFb79e3C5B782402C8dc523fE88
Arg [4] : operatorL1Optin (address): 0x0360C1cB32A20D97b358538D9Db71339ce2c9592
Arg [5] : epochDuration (uint48): 1800
Arg [6] : slashingWindow (uint48): 2100
Arg [7] : stakeUpdateWindow (uint48): 900

Arg [1] : owner (address): 0xC15adc0C87db428B9FC0Bb3246e7dEb2A991aa6E
Arg [2] : primaryAsset (address): 0x59fbBa1845690Fb202d69a77CFEc5564df12D3DE
Arg [3] : primaryAssetMaxStake (uint256): 500000000000000000000
Arg [4] : primaryAssetMinStake (uint256): 100000000000000000000
Arg [5] : primaryAssetWeightScaleFactor (uint256): 20000000000000000

-----Encoded View---------------
12 Constructor Arguments found :
Arg [0] : 00000000000000000000000084f2b4d4cf8da889701fbe83d127896880c04325
Arg [1] : 00000000000000000000000046d45d6be6214f6bd8124187cad1a5302755d7a2
Arg [2] : 000000000000000000000000c3b09a650c78dafb79e3c5b782402c8dc523fe88
Arg [3] : 0000000000000000000000000360c1cb32a20d97b358538d9db71339ce2c9592
Arg [4] : 0000000000000000000000000000000000000000000000000000000000000708
Arg [5] : 0000000000000000000000000000000000000000000000000000000000000834
Arg [6] : 0000000000000000000000000000000000000000000000000000000000000384
Arg [7] : 000000000000000000000000c15adc0c87db428b9fc0bb3246e7deb2a991aa6e
Arg [8] : 00000000000000000000000059fbba1845690fb202d69a77cfec5564df12d3de
Arg [9] : 00000000000000000000000000000000000000000000001b1ae4d6e2ef500000
Arg [10] : 0000000000000000000000000000000000000000000000056bc75e2d63100000
Arg [11] : 00000000000000000000000000000000000000000000000000470de4df820000


Block Transaction Gas Used Reward
view all blocks ##produced##

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

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
0xbf9e863cF9F00f48D3Ed9D009515114365502569
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

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