S Price: $0.592651 (+0.06%)

Contract

0x8657744c1DD56cA1c8e1E2d92525302466c55B70

Overview

S Balance

Sonic LogoSonic LogoSonic Logo0 S

S Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Create Vault116958232025-03-04 21:13:5637 hrs ago1741122836IN
0x8657744c...466c55B70
0 S0.178030850

Latest 3 internal transactions

Parent Transaction Hash Block From To
116958232025-03-04 21:13:5637 hrs ago1741122836
0x8657744c...466c55B70
 Contract Creation0 S
116958232025-03-04 21:13:5637 hrs ago1741122836
0x8657744c...466c55B70
 Contract Creation0 S
116958232025-03-04 21:13:5637 hrs ago1741122836
0x8657744c...466c55B70
 Contract Creation0 S
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
UpgradeableAutopoolFactory

Compiler Version
v0.8.26+commit.8a97fa7a

Optimization Enabled:
Yes with 200 runs

Other Settings:
cancun EvmVersion
File 1 of 87 : UpgradeableAutopoolFactory.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2025 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { ISystemRegistry } from "src/interfaces/ISystemRegistry.sol";
import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import { IUpgradeableAutopoolFactory, IAutopoolFactory } from "src/interfaces/vault/IUpgradeableAutopoolFactory.sol";

import { Roles } from "src/libs/Roles.sol";
import { Errors } from "src/utils/Errors.sol";
import { LibAdapter } from "src/libs/LibAdapter.sol";
import { SystemComponent } from "src/SystemComponent.sol";
import { SecurityBase } from "src/security/SecurityBase.sol";

import { Ownable } from "openzeppelin-contracts/access/Ownable.sol";
import { AutopoolETH } from "src/vault/AutopoolETH.sol";
import { AutopoolMainRewarder } from "src/rewarders/AutopoolMainRewarder.sol";
import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import { ProxyAdmin } from "openzeppelin-contracts/proxy/transparent/ProxyAdmin.sol";
import { TransparentUpgradeableProxy } from "openzeppelin-contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

/// @title Deploys upgradeable Autopools with a generalized base asset
/// @dev Factory can only create an autopool with a single base asset, meaning that a new implementation cannot
/// have a different base asset than the previous implementation
/// @dev Creates a ProxyAdmin.sol contract on construction. This contract is meant to be the admin of all of the
/// proxies created through this contract. See OpenZeppelin Transparent proxy pattern for more information
/// @dev Implements IAutopoolFactory as this interface is used by the SystemRegistry
contract UpgradeableAutopoolFactory is SystemComponent, SecurityBase, IUpgradeableAutopoolFactory {
    using SafeERC20 for IERC20;

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

    /// @notice Thrown when there is an invalid amount of base asset for autopool init
    error InvalidBaseAmount(uint256 amount);

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

    /// @notice Emitted when a new ProxyAdmin.sol contract is set
    event ProxyAdminSet(address oldAdmin, address newAdmin);

    /// @notice Emitted when a new logic / implementation contract is set
    event ImplementationSet(address oldImplementation, address newImplementation);

    /// @notice Emitted when a new default reward ratio is set
    event DefaultRewardRatioSet(uint256 oldRewardRatio, uint256 newRewardRatio);

    /// @notice Emitted when a new reward block duration is set
    event DefaultBlockDurationSet(uint256 oldBlockDuration, uint256 newBlockDuration);

    /// =====================================================
    /// State
    /// =====================================================

    /// @notice Base asset for autopool deployed from this factory
    address public immutable baseAsset;

    /// @notice Address of the proxy admin
    address public proxyAdmin;

    /// @notice Address of the implementation contract
    address public implementation;

    /// @notice Default reward ratio for rewarder set up
    uint256 public defaultRewardRatio;

    /// @notice Default block duration for rewarder set up
    uint256 public defaultRewardBlockDuration;

    /// =====================================================
    /// Functions - Constructor
    /// =====================================================

    constructor(
        ISystemRegistry _systemRegistry,
        address _proxyAdminOwner,
        address _implementation,
        uint256 _defaultRewardRatio,
        uint256 _defaultRewardBlockDuration
    ) SystemComponent(_systemRegistry) SecurityBase(address(_systemRegistry.accessController())) {
        // Implementation first to get checks out of the way before base asset set up
        Errors.verifyNotZero(_implementation, "_implementation");
        _setImplementation(_implementation);

        // Set base asset. Factory will only be able to support new implementations with the same base
        address localBaseAsset = AutopoolETH(_implementation).asset();
        Errors.verifyNotZero(localBaseAsset, "localBaseAsset");

        // slither-disable-next-line missing-zero-check
        baseAsset = localBaseAsset;

        _setDefaultRewardRatio(_defaultRewardRatio);
        _setDefaultRewardBlockDuration(_defaultRewardBlockDuration);

        // Automatically sets msg.sender as owner. In this case, msg.sender will be this contract
        address localProxyAdmin = address(new ProxyAdmin());

        // Transfer ownership to desired proxy owner. Zero address check included in `transferOwnership` call
        Ownable(localProxyAdmin).transferOwnership(_proxyAdminOwner);

        _setProxyAdmin(localProxyAdmin);
    }

    /// =====================================================
    /// Functions - External
    /// =====================================================

    /// @inheritdoc IUpgradeableAutopoolFactory
    function setProxyAdmin(
        address _newProxyAdmin
    ) external hasRole(Roles.AUTO_POOL_FACTORY_MANAGER) {
        Errors.verifyNotZero(_newProxyAdmin, "_newProxyAdmin");
        _setProxyAdmin(_newProxyAdmin);
    }

    /// @inheritdoc IUpgradeableAutopoolFactory
    /// @dev Any implementations that need to be updated on already deployed proxies will have to be done separately,
    /// this functionality only updates the implementation contract used for future proxies
    function setImplementation(
        address _newImplementation
    ) external hasRole(Roles.AUTO_POOL_FACTORY_MANAGER) {
        Errors.verifyNotZero(_newImplementation, "_newImplementation");
        if (AutopoolETH(_newImplementation).asset() != baseAsset) revert Errors.InvalidConfiguration();
        _setImplementation(_newImplementation);
    }

    /// @inheritdoc IUpgradeableAutopoolFactory
    function setDefaultRewardRatio(
        uint256 _newRewardRatio
    ) external hasRole(Roles.AUTO_POOL_FACTORY_MANAGER) {
        _setDefaultRewardRatio(_newRewardRatio);
    }

    /// @inheritdoc IUpgradeableAutopoolFactory
    function setDefaultRewardBlockDuration(
        uint256 _newBlockDuration
    ) external hasRole(Roles.AUTO_POOL_FACTORY_MANAGER) {
        _setDefaultRewardBlockDuration(_newBlockDuration);
    }

    /// @inheritdoc IAutopoolFactory
    /// @dev Compatibility with SystemRegistry. No strategy template in this factory
    function addStrategyTemplate(
        address
    ) external pure {
        revert Errors.NotImplemented();
    }

    /// @inheritdoc IAutopoolFactory
    /// @dev Compatibility with SystemRegistry. No strategy template in this factory
    function removeStrategyTemplate(
        address
    ) external pure {
        revert Errors.NotImplemented();
    }

    /// @notice Returns the current implementation used to create Autopools
    /// @dev Exists due to compatibility requirements with SystemRegistry
    function template() external view returns (address) {
        return implementation;
    }

    /// @inheritdoc IAutopoolFactory
    /// @dev Only payable for compatibility with `IAutopoolFactory`, will not accept Eth
    function createVault(
        address,
        string memory symbolSuffix,
        string memory descPrefix,
        bytes32,
        bytes calldata extraData
    ) external payable hasRole(Roles.AUTO_POOL_FACTORY_VAULT_CREATOR) returns (address newVault) {
        // Force weth for eth
        if (msg.value > 0) revert Errors.MustBeZero();

        // Create new proxy with implementation as logic contract
        newVault = address(new TransparentUpgradeableProxy(implementation, proxyAdmin, ""));

        // Create new rewarder
        AutopoolMainRewarder rewarder = new AutopoolMainRewarder(
            systemRegistry,
            address(systemRegistry.toke()),
            defaultRewardRatio,
            defaultRewardBlockDuration,
            true, // Allowing extra rewards
            newVault
        );

        // Autopool init deposit
        uint256 baseInitAmount = AutopoolETH(implementation).BASE_ASSET_INIT_DEPOSIT();
        IERC20 baseAssetERC20 = IERC20(baseAsset);
        baseAssetERC20.safeTransferFrom(msg.sender, address(this), baseInitAmount);
        LibAdapter._approve(baseAssetERC20, newVault, baseInitAmount);

        // Init and rewarder set
        AutopoolETH(newVault).initialize(symbolSuffix, descPrefix, extraData);
        AutopoolETH(newVault).setRewarder(address(rewarder));

        // Add to autopool registry
        systemRegistry.autoPoolRegistry().addVault(newVault);
    }

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

    function _setProxyAdmin(
        address _newProxyAdmin
    ) internal {
        if (_newProxyAdmin == proxyAdmin) revert Errors.ItemExists();

        emit ProxyAdminSet(proxyAdmin, _newProxyAdmin);
        proxyAdmin = _newProxyAdmin;
    }

    function _setImplementation(
        address _newImplementation
    ) internal {
        if (_newImplementation == implementation) revert Errors.ItemExists();
        if (address(systemRegistry) != SystemComponent(_newImplementation).getSystemRegistry()) {
            revert Errors.SystemMismatch(address(this), _newImplementation);
        }

        emit ImplementationSet(implementation, _newImplementation);
        implementation = _newImplementation;
    }

    /// @dev Zero valid
    function _setDefaultRewardRatio(
        uint256 _newRewardRatio
    ) internal {
        emit DefaultRewardRatioSet(defaultRewardRatio, _newRewardRatio);
        defaultRewardRatio = _newRewardRatio;
    }

    /// @dev Zero valid
    function _setDefaultRewardBlockDuration(
        uint256 _newBlockDuration
    ) internal {
        emit DefaultBlockDurationSet(defaultRewardBlockDuration, _newBlockDuration);
        defaultRewardBlockDuration = _newBlockDuration;
    }
}

File 2 of 87 : ISystemRegistry.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity ^0.8.24;

import { IWETH9 } from "src/interfaces/utils/IWETH9.sol";
import { IAccToke } from "src/interfaces/staking/IAccToke.sol";
import { IAutopoolRegistry } from "src/interfaces/vault/IAutopoolRegistry.sol";
import { IAccessController } from "src/interfaces/security/IAccessController.sol";
import { ISwapRouter } from "src/interfaces/swapper/ISwapRouter.sol";
import { ICurveResolver } from "src/interfaces/utils/ICurveResolver.sol";
import { IAutopilotRouter } from "src/interfaces/vault/IAutopilotRouter.sol";
import { IAutopoolFactory } from "src/interfaces/vault/IAutopoolFactory.sol";
import { ISystemSecurity } from "src/interfaces/security/ISystemSecurity.sol";
import { IDestinationRegistry } from "src/interfaces/destinations/IDestinationRegistry.sol";
import { IRootPriceOracle } from "src/interfaces/oracles/IRootPriceOracle.sol";
import { IDestinationVaultRegistry } from "src/interfaces/vault/IDestinationVaultRegistry.sol";
import { IAccessController } from "src/interfaces/security/IAccessController.sol";
import { IStatsCalculatorRegistry } from "src/interfaces/stats/IStatsCalculatorRegistry.sol";
import { IAsyncSwapperRegistry } from "src/interfaces/liquidation/IAsyncSwapperRegistry.sol";
import { IERC20Metadata } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IIncentivesPricingStats } from "src/interfaces/stats/IIncentivesPricingStats.sol";
import { IMessageProxy } from "src/interfaces/messageProxy/IMessageProxy.sol";

/// @notice Root most registry contract for the system
interface ISystemRegistry {
    /// @notice Get the TOKE contract for the system
    /// @return toke instance of TOKE used in the system
    function toke() external view returns (IERC20Metadata);

    /// @notice Get the referenced WETH contract for the system
    /// @return weth contract pointer
    function weth() external view returns (IWETH9);

    /// @notice Get the AccToke staking contract
    /// @return accToke instance of the accToke contract for the system
    function accToke() external view returns (IAccToke);

    /// @notice Get the AutopoolRegistry for this system
    /// @return registry instance of the registry for this system
    function autoPoolRegistry() external view returns (IAutopoolRegistry registry);

    /// @notice Get the destination Vault registry for this system
    /// @return registry instance of the registry for this system
    function destinationVaultRegistry() external view returns (IDestinationVaultRegistry registry);

    /// @notice Get the access Controller for this system
    /// @return controller instance of the access controller for this system
    function accessController() external view returns (IAccessController controller);

    /// @notice Get the destination template registry for this system
    /// @return registry instance of the registry for this system
    function destinationTemplateRegistry() external view returns (IDestinationRegistry registry);

    /// @notice Auto Pilot Router
    /// @return router instance of the system
    function autoPoolRouter() external view returns (IAutopilotRouter router);

    /// @notice Vault factory lookup by type
    /// @return vaultFactory instance of the vault factory for this vault type
    function getAutopoolFactoryByType(
        bytes32 vaultType
    ) external view returns (IAutopoolFactory vaultFactory);

    /// @notice Get the stats calculator registry for this system
    /// @return registry instance of the registry for this system
    function statsCalculatorRegistry() external view returns (IStatsCalculatorRegistry registry);

    /// @notice Get the root price oracle for this system
    /// @return oracle instance of the root price oracle for this system
    function rootPriceOracle() external view returns (IRootPriceOracle oracle);

    /// @notice Get the async swapper registry for this system
    /// @return registry instance of the registry for this system
    function asyncSwapperRegistry() external view returns (IAsyncSwapperRegistry registry);

    /// @notice Get the swap router for this system
    /// @return router instance of the swap router for this system
    function swapRouter() external view returns (ISwapRouter router);

    /// @notice Get the curve resolver for this system
    /// @return resolver instance of the curve resolver for this system
    function curveResolver() external view returns (ICurveResolver resolver);

    /// @notice Verify if given address is registered as Reward Token
    /// @param rewardToken token address to verify
    /// @return bool that indicates true if token is registered and false if not
    function isRewardToken(
        address rewardToken
    ) external view returns (bool);

    /// @notice Get the system security instance for this system
    /// @return security instance of system security for this system
    function systemSecurity() external view returns (ISystemSecurity security);

    /// @notice Get the Incentive Pricing Stats
    /// @return incentivePricing the incentive pricing contract
    function incentivePricing() external view returns (IIncentivesPricingStats);

    /// @notice Get the Message Proxy
    /// @return Message proxy contract
    function messageProxy() external view returns (IMessageProxy);

    /// @notice Get the receiving router contract.
    /// @return Receiving router contract
    function receivingRouter() external view returns (address);

    /// @notice Check if an additional contract of type is valid in the system
    /// @return True if the contract is a valid for the given type
    function isValidContract(bytes32 contractType, address contractAddress) external view returns (bool);

    /// @notice Returns the additional contract of the given type
    /// @dev Revert if not set
    function getUniqueContract(
        bytes32 contractType
    ) external view returns (address);

    /// @notice Returns all unique contracts configured
    function listUniqueContracts() external view returns (bytes32[] memory contractTypes, address[] memory addresses);

    /// @notice Returns all additional contract types configured
    function listAdditionalContractTypes() external view returns (bytes32[] memory);

    /// @notice Returns configured additional contracts by type
    /// @param contractType Type of contract to list
    function listAdditionalContracts(
        bytes32 contractType
    ) external view returns (address[] memory);
}

File 3 of 87 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

File 4 of 87 : IUpgradeableAutopoolFactory.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2025 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IAutopoolFactory } from "src/interfaces/vault/IAutopoolFactory.sol";

/// @dev Implements IAutopoolFactory for compatibility with system registry
interface IUpgradeableAutopoolFactory is IAutopoolFactory {
    /// @notice Sets a new ProxyAdmin.sol contract. Admin contract controls admin proxy functionalities
    /// @dev See OpenZeppelin transparent proxy pattern for more information
    /// @dev Must implement role based access control
    /// @param _newProxyAdmin Address of new ProxyAdmin.sol contract
    function setProxyAdmin(
        address _newProxyAdmin
    ) external;

    /// @notice Sets a new implementation / logic contract
    /// @dev Note that this does not update any already existing proxies to the new implementation, done separately
    /// @dev Must implement role based access control
    /// @param _newImplementation Address of the new autopool implementation / logic contract
    function setImplementation(
        address _newImplementation
    ) external;

    /// @notice Updates default reward ratio
    /// @dev Must implement role based access control
    /// @param _newRewardRatio New reward ratio for rewarder set up
    function setDefaultRewardRatio(
        uint256 _newRewardRatio
    ) external;

    /// @notice Updates default block duration for rewards
    /// @dev Must implement role based access control
    /// @param _newBlockDuration New reward block duration for rewarder set up
    function setDefaultRewardBlockDuration(
        uint256 _newBlockDuration
    ) external;
}

File 5 of 87 : Roles.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity ^0.8.24;

library Roles {
    // Naming Conventions:
    // - Use MANAGER, CREATOR, UPDATER, ..., for roles primarily managing on-chain activities.
    // - Use EXECUTOR for roles that trigger off-chain initiated actions.
    // - Group roles by functional area for clarity.

    // Destination Vault Management
    bytes32 public constant DESTINATION_VAULT_FACTORY_MANAGER = keccak256("CREATE_DESTINATION_VAULT_ROLE");
    bytes32 public constant DESTINATION_VAULT_REGISTRY_MANAGER = keccak256("DESTINATION_VAULT_REGISTRY_MANAGER");
    bytes32 public constant DESTINATION_VAULT_MANAGER = keccak256("DESTINATION_VAULT_MANAGER");

    // Auto Pool Factory and Registry Management
    bytes32 public constant AUTO_POOL_REGISTRY_UPDATER = keccak256("REGISTRY_UPDATER");
    bytes32 public constant AUTO_POOL_FACTORY_MANAGER = keccak256("AUTO_POOL_FACTORY_MANAGER");
    bytes32 public constant AUTO_POOL_FACTORY_VAULT_CREATOR = keccak256("CREATE_POOL_ROLE");

    // Auto Pool Management
    bytes32 public constant AUTO_POOL_DESTINATION_UPDATER = keccak256("DESTINATION_VAULTS_UPDATER");
    bytes32 public constant AUTO_POOL_FEE_UPDATER = keccak256("AUTO_POOL_FEE_SETTER_ROLE");
    bytes32 public constant AUTO_POOL_PERIODIC_FEE_UPDATER = keccak256("AUTO_POOL_PERIODIC_FEE_SETTER_ROLE");
    bytes32 public constant AUTO_POOL_REWARD_MANAGER = keccak256("AUTO_POOL_REWARD_MANAGER_ROLE");
    bytes32 public constant AUTO_POOL_MANAGER = keccak256("AUTO_POOL_ADMIN");
    bytes32 public constant REBALANCER = keccak256("REBALANCER_ROLE");
    bytes32 public constant STATS_HOOK_POINTS_ADMIN = keccak256("STATS_HOOK_POINTS_ADMIN");

    // Reward Management
    bytes32 public constant LIQUIDATOR_MANAGER = keccak256("LIQUIDATOR_ROLE");
    bytes32 public constant DV_REWARD_MANAGER = keccak256("DV_REWARD_MANAGER_ROLE");
    bytes32 public constant REWARD_LIQUIDATION_MANAGER = keccak256("REWARD_LIQUIDATION_MANAGER");
    bytes32 public constant EXTRA_REWARD_MANAGER = keccak256("EXTRA_REWARD_MANAGER_ROLE");
    bytes32 public constant REWARD_LIQUIDATION_EXECUTOR = keccak256("REWARD_LIQUIDATION_EXECUTOR");
    bytes32 public constant BANK_SWAP_MANAGER = keccak256("BANK_SWAP_MANAGER");

    // Statistics and Reporting
    bytes32 public constant STATS_CALC_REGISTRY_MANAGER = keccak256("STATS_CALC_REGISTRY_MANAGER");
    bytes32 public constant STATS_CALC_FACTORY_MANAGER = keccak256("CREATE_STATS_CALC_ROLE");
    bytes32 public constant STATS_CALC_FACTORY_TEMPLATE_MANAGER = keccak256("STATS_CALC_TEMPLATE_MGMT_ROLE");

    bytes32 public constant STATS_SNAPSHOT_EXECUTOR = keccak256("STATS_SNAPSHOT_ROLE");
    bytes32 public constant STATS_INCENTIVE_TOKEN_UPDATER = keccak256("STATS_INCENTIVE_TOKEN_UPDATER");
    bytes32 public constant STATS_GENERAL_MANAGER = keccak256("STATS_GENERAL_MANAGER");
    bytes32 public constant STATS_LST_ETH_TOKEN_EXECUTOR = keccak256("STATS_LST_ETH_TOKEN_EXECUTOR");
    bytes32 public constant STATS_CACHE_SET_TRANSIENT_EXECUTOR = keccak256("STATS_CACHE_SET_TRANSIENT_EXECUTOR");

    // Emergency Management
    bytes32 public constant EMERGENCY_PAUSER = keccak256("EMERGENCY_PAUSER");
    bytes32 public constant SEQUENCER_OVERRIDE_MANAGER = keccak256("SEQUENCER_OVERRIDE_MANAGER");

    // Miscellaneous Roles
    bytes32 public constant SOLVER = keccak256("SOLVER_ROLE");
    bytes32 public constant AUTO_POOL_REPORTING_EXECUTOR = keccak256("AUTO_POOL_UPDATE_DEBT_REPORTING_ROLE");
    bytes32 public constant STRATEGY_HOOK_CONFIGURATION = keccak256("STRATEGY_HOOK_CONFIGURATION");

    // Swapper Roles
    bytes32 public constant SWAP_ROUTER_MANAGER = keccak256("SWAP_ROUTER_MANAGER");

    // Price Oracles Roles
    bytes32 public constant ORACLE_MANAGER = keccak256("ORACLE_MANAGER_ROLE");
    bytes32 public constant CUSTOM_ORACLE_EXECUTOR = keccak256("CUSTOM_ORACLE_EXECUTOR");
    bytes32 public constant MAVERICK_FEE_ORACLE_EXECUTOR = keccak256("MAVERICK_FEE_ORACLE_MANAGER");

    // AccToke Roles
    bytes32 public constant ACC_TOKE_MANAGER = keccak256("ACC_TOKE_MANAGER");

    // Admin Roles
    bytes32 public constant TOKEN_RECOVERY_MANAGER = keccak256("TOKEN_RECOVERY_ROLE");
    bytes32 public constant INFRASTRUCTURE_MANAGER = keccak256("INFRASTRUCTURE_MANAGER");

    // Cross chain communications roles
    bytes32 public constant MESSAGE_PROXY_MANAGER = keccak256("MESSAGE_PROXY_MANAGER");
    bytes32 public constant MESSAGE_PROXY_EXECUTOR = keccak256("MESSAGE_PROXY_EXECUTOR");
    bytes32 public constant RECEIVING_ROUTER_MANAGER = keccak256("RECEIVING_ROUTER_MANAGER");
    bytes32 public constant RECEIVING_ROUTER_EXECUTOR = keccak256("RECEIVING_ROUTER_EXECUTOR");
}

File 6 of 87 : Errors.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { Address } from "openzeppelin-contracts/utils/Address.sol";
import { ISystemComponent } from "src/interfaces/ISystemComponent.sol";

// solhint-disable max-line-length
library Errors {
    using Address for address;
    ///////////////////////////////////////////////////////////////////
    //                       Set errors
    ///////////////////////////////////////////////////////////////////

    error AccessDenied();
    error ZeroAddress(string paramName);
    error ZeroAmount();
    error InsufficientBalance(address token);
    error AssetNotAllowed(address token);
    error NotImplemented();
    error InvalidAddress(address addr);
    error InvalidParam(string paramName);
    error InvalidParams();
    error UnsafePrice(address token, uint256 spotPrice, uint256 safePrice);
    error AlreadySet(string param);
    error AlreadyRegistered(address param);
    error SlippageExceeded(uint256 expected, uint256 actual);
    error ArrayLengthMismatch(uint256 length1, uint256 length2, string details);

    error ItemNotFound();
    error ItemExists();
    error MissingRole(bytes32 role, address user);
    error RegistryItemMissing(string item);
    error NotRegistered();
    // Used to check storage slot is empty before setting.
    error MustBeZero();
    // Used to check storage slot set before deleting.
    error MustBeSet();

    error ApprovalFailed(address token);
    error FlashLoanFailed(address token, uint256 amount);

    error SystemMismatch(address source1, address source2);

    error InvalidToken(address token);
    error UnreachableError();

    error InvalidSigner(address signer);

    error InvalidChainId(uint256 chainId);

    error SenderMismatch(address recipient, address sender);

    error UnsupportedMessage(bytes32 messageType, bytes message);

    error NotSupported();

    error InvalidConfiguration();

    error InvalidDataReturned();

    function verifyNotZero(address addr, string memory paramName) internal pure {
        if (addr == address(0)) {
            revert ZeroAddress(paramName);
        }
    }

    function verifyNotZero(bytes32 key, string memory paramName) internal pure {
        if (key == bytes32(0)) {
            revert InvalidParam(paramName);
        }
    }

    function verifyNotEmpty(string memory val, string memory paramName) internal pure {
        if (bytes(val).length == 0) {
            revert InvalidParam(paramName);
        }
    }

    function verifyNotZero(uint256 num, string memory paramName) internal pure {
        if (num == 0) {
            revert InvalidParam(paramName);
        }
    }

    function verifySystemsMatch(address component1, address component2) internal view {
        address registry1 =
            abi.decode(component1.functionStaticCall(abi.encodeCall(ISystemComponent.getSystemRegistry, ())), (address));
        address registry2 =
            abi.decode(component2.functionStaticCall(abi.encodeCall(ISystemComponent.getSystemRegistry, ())), (address));

        if (registry1 != registry2) {
            revert SystemMismatch(component1, component2);
        }
    }

    function verifyArrayLengths(uint256 length1, uint256 length2, string memory details) internal pure {
        if (length1 != length2) {
            revert ArrayLengthMismatch(length1, length2, details);
        }
    }
}

File 7 of 87 : LibAdapter.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";

library LibAdapter {
    using SafeERC20 for IERC20;

    address public constant CURVE_REGISTRY_ETH_ADDRESS_POINTER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    error MinLpAmountNotReached();
    error LpTokenAmountMismatch();
    error NoNonZeroAmountProvided();
    error InvalidBalanceChange();

    // Utils
    function _approve(IERC20 token, address spender, uint256 amount) internal {
        uint256 currentAllowance = token.allowance(address(this), spender);
        if (currentAllowance > 0) {
            token.safeDecreaseAllowance(spender, currentAllowance);
        }
        token.safeIncreaseAllowance(spender, amount);
    }
}

File 8 of 87 : SystemComponent.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { ISystemComponent } from "src/interfaces/ISystemComponent.sol";
import { ISystemRegistry } from "src/interfaces/ISystemRegistry.sol";
import { Errors } from "src/utils/Errors.sol";

contract SystemComponent is ISystemComponent {
    ISystemRegistry internal immutable systemRegistry;

    constructor(
        ISystemRegistry _systemRegistry
    ) {
        Errors.verifyNotZero(address(_systemRegistry), "_systemRegistry");
        systemRegistry = _systemRegistry;
    }

    /// @inheritdoc ISystemComponent
    function getSystemRegistry() external view returns (address) {
        return address(systemRegistry);
    }
}

File 9 of 87 : SecurityBase.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IAccessController } from "src/interfaces/security/IAccessController.sol";
import { Errors } from "src/utils/Errors.sol";

contract SecurityBase {
    IAccessController public immutable accessController;

    error UndefinedAddress();

    constructor(
        address _accessController
    ) {
        if (_accessController == address(0)) revert UndefinedAddress();

        accessController = IAccessController(_accessController);
    }

    modifier onlyOwner() {
        accessController.verifyOwner(msg.sender);
        _;
    }

    modifier hasRole(
        bytes32 role
    ) {
        if (!accessController.hasRole(role, msg.sender)) revert Errors.AccessDenied();
        _;
    }

    ///////////////////////////////////////////////////////////////////
    //
    //  Forward all the regular methods to central security module
    //
    ///////////////////////////////////////////////////////////////////

    function _hasRole(bytes32 role, address account) internal view returns (bool) {
        return accessController.hasRole(role, account);
    }
}

File 10 of 87 : Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../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.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

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

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

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

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

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

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

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

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

File 11 of 87 : AutopoolETH.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2025 Tokemak Foundation. All rights reserved.

pragma solidity ^0.8.24;

//                   ██
//                   ██
//                   ██
//                   ██
//                   ██
//      █████████████████████████████████████████
//                                 ██
//                                 ██
//                                 ██
//                                 ██
//                                 ██

import { Roles } from "src/libs/Roles.sol";
import { Errors } from "src/utils/Errors.sol";
import { AutopoolDebt } from "src/vault/libs/AutopoolDebt.sol";
import { Pausable } from "src/security/Pausable.sol";
import { VaultTypes } from "src/vault/VaultTypes.sol";
import { NonReentrantUpgradeable } from "src/utils/NonReentrantUpgradeable.sol";
import { SecurityBase } from "src/security/SecurityBase.sol";
import { IAutopool } from "src/interfaces/vault/IAutopool.sol";
import { AutopoolFees } from "src/vault/libs/AutopoolFees.sol";
import { AutopoolToken } from "src/vault/libs/AutopoolToken.sol";
import { Autopool4626 } from "src/vault/libs/Autopool4626.sol";
import { IStrategy } from "src/interfaces/strategy/IStrategy.sol";
import { ISystemSecurity } from "src/interfaces/security/ISystemSecurity.sol";
import { Math } from "openzeppelin-contracts/utils/math/Math.sol";
import { WithdrawalQueue } from "src/strategy/WithdrawalQueue.sol";
import { AutopoolDestinations } from "src/vault/libs/AutopoolDestinations.sol";
import { ISystemRegistry } from "src/interfaces/ISystemRegistry.sol";
import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import { ISystemComponent } from "src/interfaces/ISystemComponent.sol";
import { IAutopoolStrategy } from "src/interfaces/strategy/IAutopoolStrategy.sol";
import { IMainRewarder } from "src/interfaces/rewarders/IMainRewarder.sol";
import { StructuredLinkedList } from "src/strategy/StructuredLinkedList.sol";
import { Initializable } from "openzeppelin-contracts/proxy/utils/Initializable.sol";
import { EnumerableSet } from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import { IERC20Metadata } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IERC3156FlashBorrower } from "openzeppelin-contracts/interfaces/IERC3156FlashBorrower.sol";
import { AutopoolState, ProcessRebalanceParams, AutopoolStorage } from "src/vault/libs/AutopoolState.sol";
import { AutopoolStrategyHooks } from "src/vault/libs/AutopoolStrategyHooks.sol";
import { IStrategyHook, HookFunctionIndex } from "src/interfaces/strategy/IStrategyHook.sol";

// solhint-disable max-states-count,const-name-snakecase

contract AutopoolETH is
    ISystemComponent,
    Initializable,
    IAutopool,
    IStrategy,
    SecurityBase,
    Pausable,
    NonReentrantUpgradeable
{
    using EnumerableSet for EnumerableSet.AddressSet;
    using Math for uint256;
    using WithdrawalQueue for StructuredLinkedList.List;
    using AutopoolToken for AutopoolToken.TokenData;
    using AutopoolDestinations for AutopoolState;
    using AutopoolFees for AutopoolState;
    using Autopool4626 for AutopoolState;
    using AutopoolDebt for AutopoolState;
    using AutopoolStrategyHooks for AutopoolState;

    /// Be careful around the use of totalSupply and balanceOf. If you go directly to the _token struct you may miss
    /// out on the profit share unlock logic or the checking the balance of the pool itself

    /// =====================================================
    /// Constant Vars
    /// =====================================================

    /// @notice Amount of base asset to be sent to vault on initialization.
    uint256 public constant BASE_ASSET_INIT_DEPOSIT = 100_000;

    // @notice Decimals of the Autopool
    uint8 internal constant AUTOPOOL_DECIMALS = 18;

    /// @notice 100% == 10000
    uint256 internal constant FEE_DIVISOR = 10_000;

    /// @notice Dead address for init share burn.
    address internal constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;

    // solhint-disable-next-line var-name-mixedcase
    uint256 public constant ONE = 10 ** AUTOPOOL_DECIMALS;

    /// @notice The strategy logic for the Autopool
    /// @dev Intentionally set to dead
    IAutopoolStrategy public constant autoPoolStrategy = IAutopoolStrategy(DEAD_ADDRESS);

    /// =====================================================
    /// Immutable Vars
    /// =====================================================

    /// @notice Overarching baseAsset type
    bytes32 public immutable vaultType = VaultTypes.GENERAL_V1;

    /// @notice Amount to pad scaling operations by
    uint256 public immutable decimalPad;

    /// @notice Instance of this system this vault is tied to
    /// @dev Exposed via `getSystemRegistry()`
    ISystemRegistry internal immutable _systemRegistry;

    /// @notice The asset that is deposited into the vault
    /// @dev Exposed via `asset()`
    IERC20Metadata internal immutable _baseAsset;

    /// @notice Decimals of the base asset
    uint8 internal immutable _baseAssetDecimals;

    /// =====================================================
    /// Modifiers
    /// =====================================================

    /// @notice Reverts if nav/share decreases during a deposit/mint/withdraw/redeem
    /// @dev Increases are allowed. Ignored when supply is 0
    modifier noNavPerShareDecrease(
        TotalAssetPurpose purpose
    ) {
        (uint256 oldNav, uint256 startingTotalSupply) = _snapStartNav(purpose);
        _;
        _ensureNoNavPerShareDecrease(oldNav, startingTotalSupply, purpose);
    }

    /// @notice Reverts if any nav/share changing operations are in progress across the system
    /// @dev Any rebalance or debtReporting on any pool
    modifier ensureNoNavOps() {
        _checkNoNavOps();
        _;
    }

    /// @notice Globally track operations that change nav/share in a vault
    /// @dev Doesn't revert, only meant to track so that `ensureNoNavOps()` can revert when appropriate
    modifier trackNavOps() {
        ISystemSecurity systemSecurity = _systemRegistry.systemSecurity();
        systemSecurity.enterNavOperation();
        _;
        // slither-disable-next-line reentrancy-no-eth
        systemSecurity.exitNavOperation();
    }

    /// =====================================================
    /// Functions - Construction
    /// =====================================================

    constructor(
        ISystemRegistry systemRegistry,
        address _vaultAsset
    ) SecurityBase(address(systemRegistry.accessController())) Pausable(systemRegistry) {
        Errors.verifyNotZero(address(systemRegistry), "systemRegistry");

        AutopoolState storage $ = AutopoolStorage.load();

        _systemRegistry = systemRegistry;

        _baseAssetDecimals = IERC20Metadata(_vaultAsset).decimals();
        _baseAsset = IERC20Metadata(_vaultAsset);
        $.symbol = string(abi.encodePacked("autopool", IERC20Metadata(_vaultAsset).symbol(), "Template"));
        $.name = string(abi.encodePacked($.symbol, " Token"));

        _disableInitializers();

        if (_baseAssetDecimals > AUTOPOOL_DECIMALS) {
            revert InvalidDecimals();
        }
        decimalPad = 10 ** (AUTOPOOL_DECIMALS - _baseAssetDecimals);
    }

    function initialize(
        string memory symbolSuffix,
        string memory descPrefix,
        bytes memory
    ) external virtual initializer {
        NonReentrantUpgradeable.initialize();

        Errors.verifyNotEmpty(symbolSuffix, "symbolSuffix");
        Errors.verifyNotEmpty(descPrefix, "descPrefix");

        AutopoolState storage $ = AutopoolStorage.load();

        $.symbol = symbolSuffix;
        $.name = descPrefix;
        $.factory = msg.sender;

        $.initializeFeeSettings();

        // slither-disable-start reentrancy-no-eth

        // Send 100_000 shares to dead address to prevent nav / share inflation attack that can happen
        // with very small shares and totalAssets amount.
        uint256 sharesMinted = deposit(BASE_ASSET_INIT_DEPOSIT, DEAD_ADDRESS);

        if (sharesMinted != Autopool4626.changeDecimals(BASE_ASSET_INIT_DEPOSIT, _baseAssetDecimals, AUTOPOOL_DECIMALS))
        {
            revert ValueSharesMismatch(BASE_ASSET_INIT_DEPOSIT, sharesMinted);
        }
        // slither-disable-end reentrancy-no-eth

        AutopoolFees.setProfitUnlockPeriod($, 86_400);
    }

    /// =====================================================
    /// Functions - External
    /// =====================================================

    /// @notice Enable or disable the high water mark on the rebalance fee
    /// @dev Will revert if set to the same value
    function setRebalanceFeeHighWaterMarkEnabled(
        bool enabled
    ) external {
        _ensureCallerHasRole(Roles.AUTO_POOL_FEE_UPDATER);
        AutopoolState storage $ = AutopoolStorage.load();
        AutopoolFees.setRebalanceFeeHighWaterMarkEnabled($.feeSettings, enabled);
    }

    /// @notice Set the fee that will be taken when profit is realized
    /// @dev Resets the high water to current value
    /// @param fee Percent. 100% == 10000
    function setStreamingFeeBps(
        uint256 fee
    ) external nonReentrant {
        _ensureCallerHasRole(Roles.AUTO_POOL_FEE_UPDATER);
        AutopoolState storage $ = AutopoolStorage.load();
        $.setStreamingFeeBps(fee, $.oldestDebtReporting());
    }

    /// @notice Set the periodic fee taken.
    /// @dev Depending on time until next fee take, may update periodicFeeBps directly or queue fee.
    /// @param fee Fee to update periodic fee to.
    function setPeriodicFeeBps(
        uint256 fee
    ) external {
        _ensureCallerHasRole(Roles.AUTO_POOL_PERIODIC_FEE_UPDATER);
        AutopoolState storage $ = AutopoolStorage.load();
        $.setPeriodicFeeBps(fee, $.oldestDebtReporting());
    }

    /// @notice Set the address that will receive fees
    /// @param newFeeSink Address that will receive fees
    function setFeeSink(
        address newFeeSink
    ) external {
        _ensureCallerHasRole(Roles.AUTO_POOL_FEE_UPDATER);
        AutopoolFees.setFeeSink(AutopoolStorage.load().feeSettings, newFeeSink);
    }

    /// @notice Sets the address that will receive periodic fees.
    /// @dev Zero address allowable.  Disables fees.
    /// @param newPeriodicFeeSink New periodic fee address.
    function setPeriodicFeeSink(
        address newPeriodicFeeSink
    ) external {
        _ensureCallerHasRole(Roles.AUTO_POOL_PERIODIC_FEE_UPDATER);
        AutopoolFees.setPeriodicFeeSink(AutopoolStorage.load().feeSettings, newPeriodicFeeSink);
    }

    /// @notice Change the length of time it takes for profits to unlock
    /// @dev If set to 0, existing shares will unlock immediately and increase nav/share.
    function setProfitUnlockPeriod(
        uint48 newUnlockPeriodSeconds
    ) external {
        _ensureCallerHasRole(Roles.AUTO_POOL_MANAGER);
        AutopoolFees.setProfitUnlockPeriod(AutopoolStorage.load(), newUnlockPeriodSeconds);
    }

    /// @notice Set the rewarder contract used by the Autopool.
    /// @param newRewarder Address of new rewarder.
    function setRewarder(
        address newRewarder
    ) external {
        AutopoolState storage $ = AutopoolStorage.load();
        // Factory needs to be able to call for vault creation.
        if (msg.sender != $.factory && !_hasRole(Roles.AUTO_POOL_REWARD_MANAGER, msg.sender)) {
            revert Errors.AccessDenied();
        }

        $.setRewarder(newRewarder);
    }

    /// @notice Allow the updating of symbol/desc for the vault (only AFTER shutdown)
    /// @param newSymbol Symbol the Autopool will use going forward
    /// @param newName Name the Autopool will use going forward
    function setSymbolAndDescAfterShutdown(string memory newSymbol, string memory newName) external {
        _ensureCallerHasRole(Roles.AUTO_POOL_MANAGER);
        AutopoolStorage.load().setSymbolAndDescAfterShutdown(newSymbol, newName);
    }

    /// @notice Transfer out eligible tokens
    /// @param tokens List of tokens to transfer
    /// @param amounts Amount of those tokens to transfer
    /// @param destinations Recipient wallets of the transferred tokens
    function recover(
        address[] calldata tokens,
        uint256[] calldata amounts,
        address[] calldata destinations
    ) external virtual override {
        _ensureCallerHasRole(Roles.TOKEN_RECOVERY_MANAGER);
        Autopool4626.recover(tokens, amounts, destinations);
    }

    /// @inheritdoc IAutopool
    function shutdown(
        VaultShutdownStatus reason
    ) external {
        _ensureCallerHasRole(Roles.AUTO_POOL_MANAGER);
        Autopool4626.shutdownVault(AutopoolStorage.load(), reason);
    }

    /// @notice Add Destinations to the Autopool
    function addDestinations(
        address[] calldata destinations
    ) external {
        _ensureCallerHasRole(Roles.AUTO_POOL_DESTINATION_UPDATER);
        AutopoolStorage.load().addDestinations(destinations, _systemRegistry);
    }

    /// @notice Remove Destinations from the Autopool
    /// @dev If Destination currently has deployments the Destination will be removed when its empty
    function removeDestinations(
        address[] calldata destinations
    ) external {
        _ensureCallerHasRole(Roles.AUTO_POOL_DESTINATION_UPDATER);
        AutopoolStorage.load().removeDestinations(destinations);
    }

    /// @notice Update our cached values of the deployed Destinations
    /// @param numToProcess The number of Destinations in the list to process
    function updateDebtReporting(
        uint256 numToProcess
    ) external nonReentrant trackNavOps {
        _ensureCallerHasRole(Roles.AUTO_POOL_REPORTING_EXECUTOR);

        AutopoolState storage $ = AutopoolStorage.load();

        bytes memory hooks = $.getHookBytes();

        // slither-disable-next-line reentrancy-no-eth
        AutopoolDebt.AssetChanges memory result = AutopoolDebt.updateDebtReporting($, numToProcess, hooks);

        _feeAndProfitHandling(result, hooks, true);
    }

    /// @notice Add a set of hooks to the Autopool configuration
    /// @param newHooks Set of hooks to add to the Autopool
    /// @param data Set of onRegister data to pass to the onRegistered function of the hook
    function addHooks(IStrategyHook[] memory newHooks, bytes[] memory data) external {
        _ensureCallerHasRole(Roles.STRATEGY_HOOK_CONFIGURATION);
        AutopoolStorage.load().addHooks(newHooks, data);
    }

    /// @notice Remove a hook from the Autopools configuration
    /// @param hookToRemove Hook to remove from to the Autopool
    /// @param cleanupData Data to pass to the onUnregistered function of the hook
    function removeHook(IStrategyHook hookToRemove, bytes calldata cleanupData) external {
        _ensureCallerHasRole(Roles.STRATEGY_HOOK_CONFIGURATION);
        AutopoolStorage.load().removeHook(hookToRemove, cleanupData);
    }

    /// @notice Get hooks configured on the Autopool
    /// @dev Do not use in any executing code
    function getHooks() external view returns (AutopoolStrategyHooks.HookConfiguration memory) {
        return AutopoolStorage.load().getHooks();
    }

    /// @notice Returns the main rewarder for this contract
    function rewarder() external view returns (IMainRewarder) {
        return AutopoolStorage.load().rewarder;
    }

    /// @inheritdoc IAutopool
    function isPastRewarder(
        address _pastRewarder
    ) external view returns (bool) {
        return AutopoolStorage.load().pastRewarders.contains(_pastRewarder);
    }

    /// @inheritdoc IAutopool
    function isShutdown() external view returns (bool) {
        return AutopoolStorage.load().shutdown;
    }

    /// @inheritdoc IAutopool
    function shutdownStatus() external view returns (VaultShutdownStatus) {
        return AutopoolStorage.load().shutdownStatus;
    }

    /// @notice Returns state and settings related to gradual profit unlock
    function getProfitUnlockSettings() external view returns (IAutopool.ProfitUnlockSettings memory) {
        return AutopoolStorage.load().profitUnlockSettings;
    }

    /// @notice Returns state and settings related to periodic and streaming fees
    function getFeeSettings() external view returns (IAutopool.AutopoolFeeSettings memory) {
        return AutopoolStorage.load().feeSettings;
    }

    /// @notice Returns amount of assets for shares provided in an ideal scenario where all the conditions are met.
    function convertToAssets(
        uint256 shares
    ) external view virtual returns (uint256 assets) {
        assets = convertToAssets(shares, totalAssets(TotalAssetPurpose.Global), totalSupply(), Math.Rounding.Down);
    }

    /// @notice Returns the system instance this contract is tied to
    function getSystemRegistry() external view override returns (address) {
        return address(_systemRegistry);
    }

    /// @notice Returns the full list of Destinations configured on the Autopool
    function getDestinations() external view override(IAutopool, IStrategy) returns (address[] memory) {
        return AutopoolStorage.load().destinations.values();
    }

    /// @notice Returns the ordered list of Destinations that users will withdraw from when required
    function getWithdrawalQueue() external view returns (address[] memory) {
        return AutopoolStorage.load().withdrawalQueue.getList();
    }

    /// @notice Returns the list of Destinations that should be debt reported on
    function getDebtReportingQueue() external view returns (address[] memory) {
        return AutopoolStorage.load().debtReportQueue.getList();
    }

    /// @inheritdoc IAutopool
    function isDestinationRegistered(
        address destination
    ) external view returns (bool) {
        return AutopoolStorage.load().destinations.contains(destination);
    }

    /// @notice Returns all destination currently queued for removal
    function getRemovalQueue() external view override returns (address[] memory) {
        return AutopoolStorage.load().removalQueue.values();
    }

    /// @notice Factory contract that created this vault
    function factory() external view returns (address) {
        return AutopoolStorage.load().factory;
    }

    /// @inheritdoc IAutopool
    function getDestinationInfo(
        address destVault
    ) external view returns (AutopoolDebt.DestinationInfo memory) {
        return AutopoolStorage.load().destinationInfo[destVault];
    }

    /// @notice Return the timestamp of the oldest debt reporting
    function oldestDebtReporting() external view returns (uint256) {
        return AutopoolStorage.load().oldestDebtReporting();
    }

    /// @inheritdoc IAutopool
    function isDestinationQueuedForRemoval(
        address dest
    ) external view returns (bool) {
        return AutopoolStorage.load().removalQueue.contains(dest);
    }

    /// =====================================================
    /// Functions - Public
    /// =====================================================

    /// @notice Mints Vault shares to receiver by depositing exactly amount of underlying tokens
    /// @dev No nav/share changing operations, debt reportings or rebalances,
    /// can be happening throughout the entire system
    function deposit(
        uint256 assets,
        address receiver
    )
        public
        virtual
        override
        nonReentrant
        noNavPerShareDecrease(TotalAssetPurpose.Deposit)
        ensureNoNavOps
        returns (uint256 shares)
    {
        AutopoolState storage $ = AutopoolStorage.load();
        shares = Autopool4626.deposit($, address(_baseAsset), assets, receiver, paused(), _baseAssetDecimals);
        onDeposit(assets, shares, receiver);
    }

    /// @notice Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
    function mint(
        uint256 shares,
        address receiver
    )
        public
        virtual
        override
        nonReentrant
        noNavPerShareDecrease(TotalAssetPurpose.Deposit)
        ensureNoNavOps
        returns (uint256 assets)
    {
        AutopoolState storage $ = AutopoolStorage.load();
        // Handles the vault being paused, returns 0
        if (shares > maxMint(receiver)) {
            revert ERC4626MintExceedsMax(shares, maxMint(receiver));
        }

        uint256 ta = $.totalAssetsTimeChecked(TotalAssetPurpose.Deposit);
        assets = convertToAssets(shares, ta, totalSupply(), Math.Rounding.Up);

        $.transferAndMint(_baseAsset, assets, shares, receiver);

        onDeposit(assets, shares, receiver);
    }

    /// @notice Burns shares from owner and sends exactly assets of underlying tokens to receiver.
    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    )
        external
        virtual
        override
        nonReentrant
        whenNotPaused
        noNavPerShareDecrease(TotalAssetPurpose.Withdraw)
        ensureNoNavOps
        returns (uint256 shares)
    {
        Errors.verifyNotZero(assets, "assets");
        AutopoolState storage $ = AutopoolStorage.load();

        //slither-disable-next-line unused-return
        (uint256 actualAssets, uint256 actualShares,) =
            AutopoolDebt.withdraw($, assets, $.totalAssetsTimeChecked(TotalAssetPurpose.Withdraw));

        shares = actualShares;

        _completeWithdrawal($, actualAssets, shares, owner, receiver);
    }

    /// @notice Burns exactly shares from owner and sends assets of underlying tokens to receiver.
    function redeem(
        uint256 shares,
        address receiver,
        address owner
    )
        external
        virtual
        override
        nonReentrant
        whenNotPaused
        noNavPerShareDecrease(TotalAssetPurpose.Withdraw)
        ensureNoNavOps
        returns (uint256 assets)
    {
        AutopoolState storage $ = AutopoolStorage.load();

        uint256 ta = $.totalAssetsTimeChecked(TotalAssetPurpose.Withdraw);

        {
            uint256 maxShares = _maxRedeem(owner, ta);
            if (shares > maxShares) {
                revert ERC4626ExceededMaxRedeem(owner, shares, maxShares);
            }
        }

        uint256 possibleAssets = convertToAssets(shares, ta, totalSupply(), Math.Rounding.Down);
        Errors.verifyNotZero(possibleAssets, "possibleAssets");

        //slither-disable-next-line unused-return
        (uint256 actualAssets, uint256 actualShares,) = AutopoolDebt.redeem($, possibleAssets, ta);

        assets = actualAssets;

        assert(actualShares <= shares);

        _completeWithdrawal($, actualAssets, shares, owner, receiver);
    }

    /// @notice Sets a `value` amount of tokens as the allowance of `spender` over the caller's tokens.
    function approve(address spender, uint256 value) public virtual returns (bool) {
        AutopoolState storage $ = AutopoolStorage.load();
        return $.token.approve(spender, value);
    }

    /// @notice Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism.
    /// `value` is then deducted from the caller's allowance.
    function transferFrom(address from, address to, uint256 value) public virtual whenNotPaused returns (bool) {
        AutopoolState storage $ = AutopoolStorage.load();
        return $.token.transferFrom(from, to, value);
    }

    /// @notice Moves a `value` amount of tokens from the caller's account to `to`
    function transfer(address to, uint256 value) public virtual whenNotPaused returns (bool) {
        AutopoolState storage $ = AutopoolStorage.load();
        return $.token.transfer(to, value);
    }

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        AutopoolState storage $ = AutopoolStorage.load();
        $.token.permit(owner, spender, value, deadline, v, r, s);
    }

    /// @notice Returns the maximum amount of the underlying asset that can be
    /// deposited into the Vault for the receiver, through a deposit call
    function maxDeposit(
        address wallet
    ) public virtual override returns (uint256 maxAssets) {
        AutopoolState storage $ = AutopoolStorage.load();
        maxAssets = Autopool4626.maxDeposit($, wallet, paused(), _baseAssetDecimals);
    }

    /// @notice Simulate the effects of the deposit at the current block, given current on-chain conditions.
    function previewDeposit(
        uint256 assets
    ) public virtual returns (uint256 shares) {
        AutopoolState storage $ = AutopoolStorage.load();
        shares = convertToShares(
            assets, $.totalAssetsTimeChecked(TotalAssetPurpose.Deposit), totalSupply(), Math.Rounding.Down
        );
    }

    /// @notice Returns the maximum amount of the Vault shares that
    /// can be minted for the receiver, through a mint call.
    function maxMint(
        address wallet
    ) public virtual override returns (uint256 maxShares) {
        AutopoolState storage $ = AutopoolStorage.load();
        maxShares = Autopool4626.maxMint($, wallet, paused());
    }

    /// @notice Returns the maximum amount of the underlying asset that can
    /// be withdrawn from the owner balance in the Vault, through a withdraw call
    function maxWithdraw(
        address owner
    ) public virtual returns (uint256 maxAssets) {
        AutopoolState storage $ = AutopoolStorage.load();

        uint256 ownerShareBalance = balanceOf(owner);
        uint256 taChecked = $.totalAssetsTimeChecked(TotalAssetPurpose.Withdraw);

        if (paused() || ownerShareBalance == 0 || taChecked == 0) {
            return 0;
        }

        uint256 convertedAssets = convertToAssets(ownerShareBalance, taChecked, totalSupply(), Math.Rounding.Down);

        // slither-disable-next-line unused-return
        (maxAssets,) = AutopoolDebt.preview(
            $, true, convertedAssets, taChecked, abi.encodeCall(this.previewWithdraw, (convertedAssets))
        );
    }

    /// @notice Returns the maximum amount of Vault shares that can be redeemed
    /// from the owner balance in the Vault, through a redeem call
    function maxRedeem(
        address owner
    ) public virtual returns (uint256 maxShares) {
        AutopoolState storage $ = AutopoolStorage.load();
        maxShares = _maxRedeem(owner, $.totalAssetsTimeChecked(TotalAssetPurpose.Withdraw));
    }

    /// @notice Simulate the effects of a mint at the current block, given current on-chain conditions
    function previewMint(
        uint256 shares
    ) public virtual returns (uint256 assets) {
        AutopoolState storage $ = AutopoolStorage.load();
        uint256 ta = $.totalAssetsTimeChecked(TotalAssetPurpose.Deposit);
        assets = convertToAssets(shares, ta, totalSupply(), Math.Rounding.Up);
        Errors.verifyNotZero(assets, "assets");
    }

    /// @notice Simulate the effects of their withdrawal at the current block, given current on-chain conditions.
    function previewWithdraw(
        uint256 assets
    ) public virtual returns (uint256 shares) {
        AutopoolState storage $ = AutopoolStorage.load();
        // slither-disable-next-line unused-return
        (, shares) = AutopoolDebt.preview(
            $,
            true,
            assets,
            $.totalAssetsTimeChecked(TotalAssetPurpose.Withdraw),
            abi.encodeCall(this.previewWithdraw, (assets))
        );
    }

    /// @notice Simulate the effects of their redemption at the current block, given current on-chain conditions.
    function previewRedeem(
        uint256 shares
    ) public virtual override returns (uint256 assets) {
        AutopoolState storage $ = AutopoolStorage.load();

        // These values are not needed until the recursive call, gas savings.
        uint256 applicableTotalAssets = 0;
        uint256 convertedAssets = 0;
        if (msg.sender == address(this)) {
            applicableTotalAssets = $.totalAssetsTimeChecked(TotalAssetPurpose.Withdraw);
            convertedAssets = convertToAssets(shares, applicableTotalAssets, totalSupply(), Math.Rounding.Down);
        }

        // slither-disable-next-line unused-return
        (assets,) = AutopoolDebt.preview(
            $, false, convertedAssets, applicableTotalAssets, abi.encodeCall(this.previewRedeem, (shares))
        );
    }

    /// @inheritdoc IStrategy
    function flashRebalance(
        IERC3156FlashBorrower receiver,
        RebalanceParams memory rebalanceParams,
        bytes calldata data
    ) public nonReentrant whenNotPaused trackNavOps {
        _ensureCallerHasRole(Roles.SOLVER);

        AutopoolState storage $ = AutopoolStorage.load();
        bytes memory hooks = $.getHookBytes();

        ProcessRebalanceParams memory params =
            ProcessRebalanceParams({ baseAsset: _baseAsset, receiver: receiver, rebalanceParams: rebalanceParams });
        AutopoolDebt.AssetChanges memory updatedAssets = AutopoolDebt.processRebalance($, params, data, hooks);

        _feeAndProfitHandling(updatedAssets, hooks, false);

        AutopoolStrategyHooks.executeHooks(
            hooks,
            uint256(HookFunctionIndex.onRebalanceFeeProfitHandlingComplete),
            abi.encodeCall(IStrategyHook.onRebalanceFeeProfitHandlingComplete, (params, msg.sender))
        );

        // Ensure the destinations are in the queues they should be
        $.manageQueuesForDestination(rebalanceParams.destinationOut, false);
        $.manageQueuesForDestination(rebalanceParams.destinationIn, true);

        AutopoolStrategyHooks.executeHooks(
            hooks,
            uint256(HookFunctionIndex.onRebalanceComplete),
            abi.encodeCall(IStrategyHook.onRebalanceComplete, (params, msg.sender))
        );
    }

    /// @notice Returns the name of the token
    function name() public view virtual override returns (string memory) {
        return AutopoolStorage.load().name;
    }

    /// @notice Returns the symbol of the token
    function symbol() public view virtual override returns (string memory) {
        return AutopoolStorage.load().symbol;
    }

    /// @notice Returns the decimals of the autoPool token, always denominated in 18 decimals
    function decimals() public view virtual override returns (uint8) {
        return AUTOPOOL_DECIMALS;
    }

    /// @notice Returns the address of the underlying token used for the Vault for accounting, depositing, and
    /// withdrawing.
    function asset() public view virtual override returns (address) {
        return address(_baseAsset);
    }

    /// @notice Returns the total amount of the underlying asset that is “managed” by Vault.
    /// @dev Utilizes the "Global" purpose internally
    function totalAssets() public view override returns (uint256) {
        return Autopool4626.totalAssets(AutopoolStorage.load().assetBreakdown, TotalAssetPurpose.Global);
    }

    /// @notice Returns total amount of the asset() that is “managed” by the Autopool
    /// @dev Value changes based on purpose. Global is an avg. Deposit is valued higher. Withdraw is valued lower.
    /// @param purpose The calculation the total assets will be used in
    function totalAssets(
        TotalAssetPurpose purpose
    ) public view returns (uint256) {
        return Autopool4626.totalAssets(AutopoolStorage.load().assetBreakdown, purpose);
    }

    /// @notice Returns the amount of shares that the Vault would exchange for the amount of assets provided,
    /// in an ideal scenario where all the conditions are met
    function convertToShares(
        uint256 assets
    ) public view virtual returns (uint256 shares) {
        shares = convertToShares(assets, totalAssets(TotalAssetPurpose.Global), totalSupply(), Math.Rounding.Down);
    }

    /// @notice Returns the amount of shares that the Vault would exchange for the amount of assets provided,
    /// in an ideal scenario where all the conditions are met
    function convertToShares(
        uint256 assets,
        uint256 totalAssetsForPurpose,
        uint256 supply,
        Math.Rounding rounding
    ) public view virtual returns (uint256 shares) {
        shares = Autopool4626.convertToShares(assets, totalAssetsForPurpose, supply, rounding, _baseAssetDecimals);
    }

    /// @notice Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an
    /// ideal
    /// scenario where all the conditions are met.
    function convertToAssets(
        uint256 shares,
        uint256 totalAssetsForPurpose,
        uint256 supply,
        Math.Rounding rounding
    ) public view virtual returns (uint256 assets) {
        assets = Autopool4626.convertToAssets(shares, totalAssetsForPurpose, supply, rounding, _baseAssetDecimals);
    }

    /// @notice Returns the amount of tokens in existence.
    /// @dev Subtracts any unlocked profit shares that will be burned
    function totalSupply() public view virtual override(IERC20) returns (uint256 shares) {
        shares = Autopool4626.totalSupply();
    }

    /// @notice Returns the amount of tokens owned by account.
    /// @dev Subtracts any unlocked profit shares that will be burned when account is the Vault itself
    function balanceOf(
        address account
    ) public view override(IERC20) returns (uint256) {
        return AutopoolStorage.load().balanceOf(account);
    }

    /// @notice Returns the amount of tokens owned by wallet.
    /// @dev Does not subtract any unlocked profit shares that should be burned when wallet is the Vault itself
    function balanceOfActual(
        address account
    ) public view returns (uint256) {
        return AutopoolStorage.load().token.balances[account];
    }

    /// @notice Returns the remaining number of tokens that `spender` will be allowed to spend on
    /// behalf of `owner` through {transferFrom}. This is zero by default
    /// @dev This value changes when `approve` or `transferFrom` are called
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return AutopoolStorage.load().token.allowances[owner][spender];
    }

    function getAssetBreakdown() public view override returns (IAutopool.AssetBreakdown memory) {
        return AutopoolStorage.load().assetBreakdown;
    }

    /// @notice Returns the next unused nonce for an address.
    function nonces(
        address owner
    ) public view virtual returns (uint256) {
        AutopoolState storage $ = AutopoolStorage.load();
        return $.token.nonces[owner];
    }

    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return keccak256(
            abi.encode(
                AutopoolToken.TYPE_HASH,
                keccak256(bytes("Tokemak")),
                keccak256(bytes("1")),
                block.chainid,
                address(this)
            )
        );
    }

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

    function onDeposit(uint256 assets, uint256 shares, address receiver) internal virtual { }

    function _completeWithdrawal(
        AutopoolState storage $,
        uint256 assets,
        uint256 shares,
        address owner,
        address receiver
    ) internal virtual {
        AutopoolDebt.completeWithdrawal($, assets, shares, owner, receiver, _baseAsset);
    }

    function _feeAndProfitHandling(
        AutopoolDebt.AssetChanges memory assetChanges,
        bytes memory hooks,
        bool collectPeriodicFees
    ) internal {
        AutopoolState storage $ = AutopoolStorage.load();

        uint256 startingTotalAssets = assetChanges.startingIdle + assetChanges.startingDebt;
        uint256 newTotalAssets = assetChanges.newIdle + assetChanges.newDebt;

        // Collect any fees and lock any profit if appropriate
        $.burnUnlockedShares();

        assetChanges.startingTotalSupply = totalSupply();

        assetChanges.endingTotalSupply =
            _collectFees(newTotalAssets, assetChanges.startingTotalSupply, collectPeriodicFees);

        assetChanges.endingTotalSupply = AutopoolFees.calculateProfitLocking(
            $.profitUnlockSettings,
            $.token,
            assetChanges.endingTotalSupply - assetChanges.startingTotalSupply, // new feeShares
            newTotalAssets,
            startingTotalAssets,
            assetChanges.endingTotalSupply,
            balanceOfActual(address(this))
        );

        AutopoolStrategyHooks.executeHooks(
            hooks, uint256(HookFunctionIndex.onNavUpdate), abi.encodeCall(IStrategyHook.onNavUpdate, (assetChanges))
        );

        emit Nav(assetChanges.newIdle, assetChanges.newDebt, assetChanges.endingTotalSupply);
    }

    /// @dev This has been broken it out for testing purposes
    function _collectFees(
        uint256 currentTotalAssets,
        uint256 currentTotalSupply,
        bool collectPeriodicFees
    ) internal virtual returns (uint256) {
        AutopoolState storage $ = AutopoolStorage.load();
        return AutopoolFees.collectFees($, currentTotalAssets, currentTotalSupply, collectPeriodicFees);
    }

    /// @dev Revert if a nav-changing operation is in progress in the system
    function _checkNoNavOps() internal view {
        if (_systemRegistry.systemSecurity().navOpsInProgress() > 0) {
            revert NavOpsInProgress();
        }
    }

    /// @dev Revert if nav/share decreases on withdraw/redeem. No-op when totalSupply is zero.
    function _ensureNoNavPerShareDecrease(
        uint256 oldNav,
        uint256 startingTotalSupply,
        TotalAssetPurpose purpose
    ) internal view virtual {
        uint256 ts = totalSupply();
        // slither-disable-next-line incorrect-equality
        if (ts == 0 || startingTotalSupply == 0) {
            return;
        }
        uint256 newNav = (totalAssets(purpose) * decimalPad * FEE_DIVISOR) / ts;
        if (newNav < oldNav) {
            revert NavDecreased(oldNav, newNav);
        }
    }

    /// =====================================================
    /// Functions - Private
    /// =====================================================

    function _ensureCallerHasRole(
        bytes32 role
    ) private view {
        if (!accessController.hasRole(role, msg.sender)) revert Errors.AccessDenied();
    }

    function _snapStartNav(
        TotalAssetPurpose purpose
    ) private view returns (uint256 oldNav, uint256 startingTotalSupply) {
        startingTotalSupply = totalSupply();
        // slither-disable-next-line incorrect-equality
        if (startingTotalSupply == 0) {
            return (0, 0);
        }
        oldNav = (totalAssets(purpose) * decimalPad * FEE_DIVISOR) / startingTotalSupply;
    }

    /// @dev Local gas-saving function to pass pre-calculated total assets time checked value
    function _maxRedeem(address owner, uint256 _totalAssets) private returns (uint256 maxShares) {
        // If total assets are zero then we are considered uncollateralized and all redeem's will fail
        if (_totalAssets > 0) {
            maxShares = paused() ? 0 : balanceOf(owner);
        }
    }
}

File 12 of 87 : AutopoolMainRewarder.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { SafeERC20, IERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";

import { MainRewarder, ISystemRegistry, Errors } from "src/rewarders/MainRewarder.sol";
import { Roles } from "src/libs/Roles.sol";

/**
 * @title AutopoolMainRewarder
 * @notice Main rewarder for Autopool contracts.
 */
contract AutopoolMainRewarder is MainRewarder {
    using SafeERC20 for IERC20;

    /// @notice IERC20 instance of token being staked in rewarder.
    IERC20 public immutable stakingToken;

    // slither-disable-start similar-names
    constructor(
        ISystemRegistry _systemRegistry,
        address _rewardToken,
        uint256 _newRewardRatio,
        uint256 _durationInBlock,
        bool _allowExtraReward,
        address _stakingToken
    )
        MainRewarder(
            _systemRegistry,
            _rewardToken,
            _newRewardRatio,
            _durationInBlock,
            Roles.AUTO_POOL_REWARD_MANAGER,
            _allowExtraReward
        )
    {
        Errors.verifyNotZero(_stakingToken, "_stakingToken");

        stakingToken = IERC20(_stakingToken);
    }
    // slither-disable-end similar-names

    /**
     * @notice Withdraws autopilot vault token from rewarder.
     * @dev Balance updates, reward calculations taken care of in inherited contract.
     * @param account Account that is withdrawing assets.
     * @param amount Amount of assets to be withdrawn.
     * @param claim Whether or not to claim rewards.
     */
    function withdraw(address account, uint256 amount, bool claim) public {
        if (msg.sender != account && msg.sender != address(systemRegistry.autoPoolRouter())) {
            revert Errors.AccessDenied();
        }

        _withdraw(account, amount, claim);

        stakingToken.safeTransfer(account, amount);
    }

    /**
     * @notice Stakes autopilot vault token to rewarder.
     * @dev Balance updates, reward calculations taken care of in inherited contract.
     * @param account Account staking.
     * @param amount Amount of autopilot vault token to stake.
     */
    function stake(address account, uint256 amount) public {
        _stake(account, amount);

        // slither-disable-next-line arbitrary-send-erc20
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);
    }

    /**
     * @notice Gets reward for msg.sender.
     * @dev Used to enforce msg.sender check.
     * @param account Account to claim rewards for
     * @param recipient Address to send rewards to
     */
    function getReward(address account, address recipient, bool claimExtras) public {
        if (msg.sender != account && msg.sender != address(systemRegistry.autoPoolRouter())) {
            revert Errors.AccessDenied();
        }

        _getReward(account, recipient, claimExtras);
    }

    /**
     * @notice Checks if token can be recovered.
     * @dev staked token cant be recovered
     * @param token Token to check.
     * @return bool True if token can be recovered.
     */
    function canTokenBeRecovered(
        address token
    ) public view override returns (bool) {
        if (token == address(stakingToken)) {
            return false;
        }
        return true;
    }
}

File 13 of 87 : SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";

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

    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

File 14 of 87 : ProxyAdmin.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/transparent/ProxyAdmin.sol)

pragma solidity ^0.8.0;

import "./TransparentUpgradeableProxy.sol";
import "../../access/Ownable.sol";

/**
 * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
 * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
 */
contract ProxyAdmin is Ownable {
    /**
     * @dev Returns the current implementation of `proxy`.
     *
     * Requirements:
     *
     * - This contract must be the admin of `proxy`.
     */
    function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
        // We need to manually run the static call since the getter cannot be flagged as view
        // bytes4(keccak256("implementation()")) == 0x5c60da1b
        (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
        require(success);
        return abi.decode(returndata, (address));
    }

    /**
     * @dev Returns the current admin of `proxy`.
     *
     * Requirements:
     *
     * - This contract must be the admin of `proxy`.
     */
    function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
        // We need to manually run the static call since the getter cannot be flagged as view
        // bytes4(keccak256("admin()")) == 0xf851a440
        (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
        require(success);
        return abi.decode(returndata, (address));
    }

    /**
     * @dev Changes the admin of `proxy` to `newAdmin`.
     *
     * Requirements:
     *
     * - This contract must be the current admin of `proxy`.
     */
    function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {
        proxy.changeAdmin(newAdmin);
    }

    /**
     * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
     *
     * Requirements:
     *
     * - This contract must be the admin of `proxy`.
     */
    function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
        proxy.upgradeTo(implementation);
    }

    /**
     * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
     * {TransparentUpgradeableProxy-upgradeToAndCall}.
     *
     * Requirements:
     *
     * - This contract must be the admin of `proxy`.
     */
    function upgradeAndCall(
        TransparentUpgradeableProxy proxy,
        address implementation,
        bytes memory data
    ) public payable virtual onlyOwner {
        proxy.upgradeToAndCall{value: msg.value}(implementation, data);
    }
}

File 15 of 87 : TransparentUpgradeableProxy.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/transparent/TransparentUpgradeableProxy.sol)

pragma solidity ^0.8.0;

import "../ERC1967/ERC1967Proxy.sol";

/**
 * @dev This contract implements a proxy that is upgradeable by an admin.
 *
 * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
 * clashing], which can potentially be used in an attack, this contract uses the
 * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
 * things that go hand in hand:
 *
 * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
 * that call matches one of the admin functions exposed by the proxy itself.
 * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
 * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
 * "admin cannot fallback to proxy target".
 *
 * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
 * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
 * to sudden errors when trying to call a function from the proxy implementation.
 *
 * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
 * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
 */
contract TransparentUpgradeableProxy is ERC1967Proxy {
    /**
     * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
     * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
     */
    constructor(
        address _logic,
        address admin_,
        bytes memory _data
    ) payable ERC1967Proxy(_logic, _data) {
        _changeAdmin(admin_);
    }

    /**
     * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
     */
    modifier ifAdmin() {
        if (msg.sender == _getAdmin()) {
            _;
        } else {
            _fallback();
        }
    }

    /**
     * @dev Returns the current admin.
     *
     * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
     *
     * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
     * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
     * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
     */
    function admin() external ifAdmin returns (address admin_) {
        admin_ = _getAdmin();
    }

    /**
     * @dev Returns the current implementation.
     *
     * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
     *
     * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
     * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
     * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
     */
    function implementation() external ifAdmin returns (address implementation_) {
        implementation_ = _implementation();
    }

    /**
     * @dev Changes the admin of the proxy.
     *
     * Emits an {AdminChanged} event.
     *
     * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
     */
    function changeAdmin(address newAdmin) external virtual ifAdmin {
        _changeAdmin(newAdmin);
    }

    /**
     * @dev Upgrade the implementation of the proxy.
     *
     * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
     */
    function upgradeTo(address newImplementation) external ifAdmin {
        _upgradeToAndCall(newImplementation, bytes(""), false);
    }

    /**
     * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
     * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
     * proxied contract.
     *
     * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
     */
    function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
        _upgradeToAndCall(newImplementation, data, true);
    }

    /**
     * @dev Returns the current admin.
     */
    function _admin() internal view virtual returns (address) {
        return _getAdmin();
    }

    /**
     * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
     */
    function _beforeFallback() internal virtual override {
        require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
        super._beforeFallback();
    }
}

File 16 of 87 : IWETH9.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

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

interface IWETH9 is IERC20 {
    function symbol() external view returns (string memory);

    function deposit() external payable;
    function withdraw(
        uint256 amount
    ) external;
}

File 17 of 87 : IAccToke.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IERC20Metadata } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";

interface IAccToke {
    ///////////////////////////////////////////////////////////////////
    //                        Variables
    ///////////////////////////////////////////////////////////////////

    function startEpoch() external view returns (uint256);
    function minStakeDuration() external view returns (uint256);

    struct Lockup {
        uint128 amount;
        uint128 end;
        uint256 points;
    }

    function getLockups(
        address user
    ) external view returns (Lockup[] memory);
    function toke() external view returns (IERC20Metadata);

    ///////////////////////////////////////////////////////////////////
    //                        Errors
    ///////////////////////////////////////////////////////////////////

    error ZeroAddress();
    error StakingDurationTooShort();
    error StakingDurationTooLong();
    error StakingPointsExceeded();
    error IncorrectStakingAmount();
    error InsufficientFunds();
    error LockupDoesNotExist();
    error NotUnlockableYet();
    error AlreadyUnlocked();
    error ExtendDurationTooShort();
    error TransfersDisabled();
    error TransferFailed();
    error NoRewardsToClaim();
    error InsufficientAmount();
    error InvalidLockupIds();
    error InvalidDurationLength();
    error InvalidMinStakeDuration();
    error AdminUnlockActive();

    ///////////////////////////////////////////////////////////////////
    //                        Events
    ///////////////////////////////////////////////////////////////////
    event SetMaxStakeDuration(uint256 oldDuration, uint256 newDuration);
    event Stake(address indexed user, uint256 lockupId, uint256 amount, uint256 end, uint256 points);
    event Unstake(address indexed user, uint256 lockupId, uint256 amount, uint256 end, uint256 points);
    event Extend(
        address indexed user,
        uint256 lockupId,
        uint256 amount,
        uint256 oldEnd,
        uint256 newEnd,
        uint256 oldPoints,
        uint256 newPoints
    );
    event RewardsAdded(uint256 amount, uint256 accRewardPerShare);
    event RewardsCollected(address indexed user, uint256 amount);
    event RewardsClaimed(address indexed user, address indexed recipient, uint256 amount);
    event AdminUnlockSet(bool newUnlockState);

    ///////////////////////////////////////////////////////////////////
    //
    //                        Staking Methods
    //
    ///////////////////////////////////////////////////////////////////

    /**
     * @notice Stake TOKE to an address that may not be the same as the sender of the funds. This can be used to give
     * staked funds to someone else.
     *
     * If staking before the start of staking (epoch), then the lockup start and end dates are shifted forward so that
     * the lockup starts at the epoch.
     *
     * @param amount TOKE to lockup in the stake
     * @param duration in seconds for the stake
     * @param to address to receive ownership of the stake
     */
    function stake(uint256 amount, uint256 duration, address to) external;

    /**
     * @notice Stake TOKE
     *
     * If staking before the start of staking (epoch), then the lockup start and end dates are shifted forward so that
     * the lockup starts at the epoch.
     *
     * @notice Stake TOKE for myself.
     * @param amount TOKE to lockup in the stake
     * @param duration in seconds for the stake
     */
    function stake(uint256 amount, uint256 duration) external;

    /**
     * @notice Collect staked TOKE for a lockup and any earned rewards.
     * @param lockupIds the id of the lockup to unstake
     */
    function unstake(
        uint256[] memory lockupIds
    ) external;

    /**
     * @notice Collect staked TOKE for a lockup and any earned rewards.
     * @param lockupIds the id of the lockup to unstake
     * @param user address of the user to unstake for
     * @param to address to receive the unstaked TOKE
     */
    function unstake(uint256[] memory lockupIds, address user, address to) external;

    /**
     * @notice Extend a stake lockup for additional points.
     *
     * The stake end time is computed from the current time + duration, just like it is for new stakes. So a new stake
     * for seven days duration and an old stake extended with a seven days duration would have the same end.
     *
     * If an extend is made before the start of staking, the start time for the new stake is shifted forwards to the
     * start of staking, which also shifts forward the end date.
     *
     * @param lockupIds the id of the old lockup to extend
     * @param durations number of seconds from now to stake for
     */
    function extend(uint256[] memory lockupIds, uint256[] memory durations) external;

    ///////////////////////////////////////////////////////////////////
    //
    //                        Rewards
    //
    ///////////////////////////////////////////////////////////////////

    /// @notice The total amount of rewards earned for all stakes
    function totalRewardsEarned() external returns (uint256);

    /// @notice Total rewards claimed by all stakers
    function totalRewardsClaimed() external returns (uint256);

    /// @notice Rewards claimed by a specific wallet
    /// @param user Address of the wallet to check
    function rewardsClaimed(
        address user
    ) external returns (uint256);

    /**
     * @notice Calculate points based on duration from the staking system's start epoch to the user's staking end date
     *
     * @param amount TOKE to be staked
     * @param duration number of seconds to stake for
     * @return points staking points that would be returned
     * @return end staking period end date
     */
    function previewPoints(uint256 amount, uint256 duration) external view returns (uint256, uint256);

    /// @notice Preview the reward amount a caller can claim
    function previewRewards() external view returns (uint256);

    /// @notice Preview the reward amount a specified wallet can claim
    function previewRewards(
        address user
    ) external view returns (uint256);

    /// @notice Claim rewards for the caller
    function collectRewards() external returns (uint256);

    /// @notice Claim rewards for the user and send to recipient
    function collectRewards(address user, address recipient) external returns (uint256);

    /// @notice Check if amount can be staked
    function isStakeableAmount(
        uint256 amount
    ) external pure returns (bool);
}

File 18 of 87 : IAutopoolRegistry.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

/// @title Keep track of Vaults created through the Vault Factory
interface IAutopoolRegistry {
    ///////////////////////////////////////////////////////////////////
    //                        Errors
    ///////////////////////////////////////////////////////////////////

    error VaultNotFound(address vaultAddress);
    error VaultAlreadyExists(address vaultAddress);

    ///////////////////////////////////////////////////////////////////
    //                        Events
    ///////////////////////////////////////////////////////////////////
    event VaultAdded(address indexed asset, address indexed vault);
    event VaultRemoved(address indexed asset, address indexed vault);

    ///////////////////////////////////////////////////////////////////
    //                        Functions
    ///////////////////////////////////////////////////////////////////

    /// @notice Checks if an address is a valid vault
    /// @param vaultAddress Vault address to be added
    function isVault(
        address vaultAddress
    ) external view returns (bool);

    /// @notice Registers a vault
    /// @param vaultAddress Vault address to be added
    function addVault(
        address vaultAddress
    ) external;

    /// @notice Removes vault registration
    /// @param vaultAddress Vault address to be removed
    function removeVault(
        address vaultAddress
    ) external;

    /// @notice Returns a list of all registered vaults
    function listVaults() external view returns (address[] memory);

    /// @notice Returns a list of all registered vaults for a given asset
    /// @param asset Asset address
    function listVaultsForAsset(
        address asset
    ) external view returns (address[] memory);

    /// @notice Returns a list of all registered vaults for a given type
    /// @param _vaultType Vault type
    function listVaultsForType(
        bytes32 _vaultType
    ) external view returns (address[] memory);
}

File 19 of 87 : IAccessController.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IAccessControlEnumerable } from "openzeppelin-contracts/access/IAccessControlEnumerable.sol";

interface IAccessController is IAccessControlEnumerable {
    error AccessDenied();

    /**
     * @notice Setup a role for an account
     * @param role The role to setup
     * @param account The account to setup the role for
     */
    function setupRole(bytes32 role, address account) external;

    /**
     * @notice Verify if an account is an owner. Reverts if not
     * @param account The account to verify
     */
    function verifyOwner(
        address account
    ) external view;
}

File 20 of 87 : ISwapRouter.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { ISyncSwapper } from "src/interfaces/swapper/ISyncSwapper.sol";

interface ISwapRouter {
    struct SwapData {
        address token;
        address pool;
        ISyncSwapper swapper;
        bytes data;
    }

    error MaxSlippageExceeded();
    error SwapRouteLookupFailed(address from, address to);
    error SwapFailed();

    event SwapRouteSet(address indexed token, SwapData[] routes);
    event SwapForQuoteSuccessful(
        address indexed assetToken,
        uint256 sellAmount,
        address indexed quoteToken,
        uint256 minBuyAmount,
        uint256 buyAmount
    );

    /**
     * @notice Sets a new swap route for a given asset token.
     * @param assetToken The asset token for which the swap route is being set.
     * @param _swapRoute The new swap route as an array of SwapData. The last element represents the quoteToken.
     * @dev Each 'hop' in the swap route is validated using the respective swapper's validate function. The validate
     * function ensures that the encoded data contains the correct 'fromAddress' and 'toAddress' (swapData.token), and
     * verifies that these tokens are in the pool.
     */
    function setSwapRoute(address assetToken, SwapData[] calldata _swapRoute) external;

    /**
     * @notice Swaps the asset token for the quote token.
     * @dev We're adopting an "exact in, variable out" model for all our swaps. This ensures that the entire sellAmount
     * is used, eliminating the need for additional balance checks and refunds. This model is expected to be followed by
     * all swapper implementations to maintain consistency and to optimize for gas efficiency.
     * @param assetToken The address of the asset token to swap.
     * @param sellAmount The exact amount of the asset token to swap.
     * @param quoteToken The address of the quote token.
     * @param minBuyAmount The minimum amount of the quote token expected to be received from the swap.
     * @return The amount received from the swap.
     */
    function swapForQuote(
        address assetToken,
        uint256 sellAmount,
        address quoteToken,
        uint256 minBuyAmount
    ) external returns (uint256);
}

File 21 of 87 : ICurveResolver.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity ^0.8.24;

interface ICurveResolver {
    /// @notice Resolve details of a Curve pool regardless of type or version
    /// @dev This resolves tokens without unwrapping to underlying in the case of meta pools.
    /// @param poolAddress pool address to lookup
    /// @return tokens tokens that make up the pool
    /// @return numTokens the number of tokens. tokens are not unwrapped.
    /// @return isStableSwap is this a StableSwap pool. false = CryptoSwap
    function resolve(
        address poolAddress
    ) external view returns (address[8] memory tokens, uint256 numTokens, bool isStableSwap);

    /// @notice Resolve details of a Curve pool regardless of type or version
    /// @dev This resolves tokens without unwrapping to underlying in the case of meta pools.
    /// @dev Use the isStableSwap value to differentiate between StableSwap (V1) and CryptoSwap (V2) pools.
    /// @param poolAddress pool address to lookup
    /// @return tokens tokens that make up the pool
    /// @return numTokens the number of tokens. tokens are not unwrapped
    /// @return lpToken lp token of the pool
    /// @return isStableSwap is this a StableSwap pool. false = CryptoSwap
    function resolveWithLpToken(
        address poolAddress
    ) external view returns (address[8] memory tokens, uint256 numTokens, address lpToken, bool isStableSwap);

    /// @notice Get the lp token of a Curve pool
    /// @param poolAddress pool address to lookup
    function getLpToken(
        address poolAddress
    ) external view returns (address);

    /// @notice Get the reserves of a Curve pools' tokens
    /// @dev Actual balances length might differ from 8 and should be verified by the caller
    /// @param poolAddress pool address to lookup
    /// @return balances reserves of the pool tokens
    function getReservesInfo(
        address poolAddress
    ) external view returns (uint256[8] memory balances);
}

File 22 of 87 : IAutopilotRouter.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IAutopool } from "src/interfaces/vault/IAutopool.sol";
import { IAutopilotRouterBase } from "src/interfaces/vault/IAutopilotRouterBase.sol";
import { IRewards } from "src/interfaces/rewarders/IRewards.sol";
import { SwapParams } from "src/interfaces/liquidation/IAsyncSwapper.sol";
import { ISwapRouterV2 } from "src/interfaces/swapper/ISwapRouterV2.sol";

/**
 * @title IAutopilotRouter Interface
 * @notice Extends the IAutopilotRouterBase with specific flows to save gas
 */
interface IAutopilotRouter is IAutopilotRouterBase {
    /**
     * ***************************   Deposit ********************************
     */

    /**
     * @notice deposit available asset balance to a AutopoolETH.
     * @param vault The AutopoolETH to deposit assets to.
     * @param to The destination of ownership shares.
     * @param minSharesOut The min amount of `vault` shares received by `to`.
     * @return sharesOut the amount of shares received by `to`.
     * @dev throws MinSharesError
     */
    function depositBalance(
        IAutopool vault,
        address to,
        uint256 minSharesOut
    ) external payable returns (uint256 sharesOut);

    /**
     * @notice deposit max assets to a AutopoolETH.
     * @param vault The AutopoolETH to deposit assets to.
     * @param to The destination of ownership shares.
     * @param minSharesOut The min amount of `vault` shares received by `to`.
     * @return sharesOut the amount of shares received by `to`.
     * @dev throws MinSharesError
     */
    function depositMax(
        IAutopool vault,
        address to,
        uint256 minSharesOut
    ) external payable returns (uint256 sharesOut);

    /**
     * *************************   Withdraw   **********************************
     */

    /**
     * @notice withdraw `amount` to a AutopoolETH.
     * @param fromVault The AutopoolETH to withdraw assets from.
     * @param toVault The AutopoolETH to deposit assets to.
     * @param to The destination of ownership shares.
     * @param amount The amount of assets to withdraw from fromVault.
     * @param maxSharesIn The max amount of fromVault shares withdrawn by caller.
     * @param minSharesOut The min amount of toVault shares received by `to`.
     * @return sharesOut the amount of shares received by `to`.
     * @dev throws MaxSharesError, MinSharesError
     */
    function withdrawToDeposit(
        IAutopool fromVault,
        IAutopool toVault,
        address to,
        uint256 amount,
        uint256 maxSharesIn,
        uint256 minSharesOut
    ) external payable returns (uint256 sharesOut);

    /**
     * *************************   Redeem    ********************************
     */

    /**
     * @notice redeem `shares` to a AutopoolETH.
     * @param fromVault The AutopoolETH to redeem shares from.
     * @param toVault The AutopoolETH to deposit assets to.
     * @param to The destination of ownership shares.
     * @param shares The amount of shares to redeem from fromVault.
     * @param minSharesOut The min amount of toVault shares received by `to`.
     * @return sharesOut the amount of shares received by `to`.
     * @dev throws MinAmountError, MinSharesError
     */
    function redeemToDeposit(
        IAutopool fromVault,
        IAutopool toVault,
        address to,
        uint256 shares,
        uint256 minSharesOut
    ) external payable returns (uint256 sharesOut);

    /**
     * @notice redeem max shares to a AutopoolETH.
     * @param vault The AutopoolETH to redeem shares from.
     * @param to The destination of assets.
     * @param minAmountOut The min amount of assets received by `to`.
     * @return amountOut the amount of assets received by `to`.
     * @dev throws MinAmountError
     */
    function redeemMax(
        IAutopool vault,
        address to,
        uint256 minAmountOut
    ) external payable returns (uint256 amountOut);

    /**
     * @notice redeem `shares` shares from a AutopoolETH with a custom route
     * @param vault The AutopoolETH to redeem shares from.
     * @param to The destination of assets.
     * @param shares The amount of shares to redeem from vault.
     * @param minAmountOut The min amount of assets received by `to`.
     * @param customRoute The custom route to use for the swap.
     * @return amountOut the amount of assets received by `to`.
     * @dev throws MinAmountError
     */
    function redeemWithRoutes(
        IAutopool vault,
        address to,
        uint256 shares,
        uint256 minAmountOut,
        ISwapRouterV2.UserSwapData[] calldata customRoute
    ) external payable returns (uint256 amountOut);

    /**
     * @notice swaps token
     * @param swapper Address of the swapper to use
     * @param swapParams  Parameters for the swap
     * @return amountReceived Swap output amount
     */
    function swapToken(
        address swapper,
        SwapParams memory swapParams
    ) external payable returns (uint256 amountReceived);

    /**
     * @notice claims vault token rewards
     * @param rewarder Address of the rewarder to claim from
     * @param recipient Struct containing recipient details
     * @return amountReceived Swap output amount
     */
    function claimRewards(
        IRewards rewarder,
        IRewards.Recipient calldata recipient,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external payable returns (uint256);

    /**
     * @notice swaps Exact token balance in the contract
     * @param swapper Address of the swapper to use
     * @param swapParams  Parameters for the swap
     * @return amountReceived Swap output amount
     * @dev sets the sellAmount to the balance of the contract
     */
    function swapTokenBalance(
        address swapper,
        SwapParams memory swapParams
    ) external payable returns (uint256 amountReceived);

    /**
     * @notice stake Acc token balance
     * @param duration The duration of the stake
     * @param accToke contract address of the AccToke
     * @param to The destination of ownership shares.
     */
    function stakeAccBalance(address accToke, uint256 duration, address to) external payable;

    /**
     * @notice stake Acc token for specified amount
     * @param amount Amount of TOKE to stake
     * @param accToke contract address of the AccToke
     * @param duration The duration of the stake
     * @param to The destination of ownership shares.
     */
    function stakeAcc(address accToke, uint256 amount, uint256 duration, address to) external payable;

    /**
     * @notice unstake Acc token balance
     * @param accToke contract address of the AccToke
     * @param lockupIds The lockup ids to unstake
     * @param to The destination of staked TOKE.
     */
    function unstakeAcc(address accToke, uint256[] memory lockupIds, address to) external payable;

    /**
     * @notice Collect staking rewards
     * @dev rewards can only be sent to user or router
     * @param accToke contract address of the AccToke
     * @param recipient The recipient of the rewards
     * @return amountReceived Swap output amount
     */
    function collectAccTokeRewards(address accToke, address recipient) external payable returns (uint256);

    /**
     * @notice AccTokeV1 function to lock TOKE for `numOfCycles` cycles
     * @param amount Amount of TOKE to lock up
     * @param duration Number of cycles to lock for
     */
    function lockTokeFor(uint256 amount, uint256 duration) external payable;
}

File 23 of 87 : IAutopoolFactory.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

interface IAutopoolFactory {
    ///////////////////////////////////////////////////////////////////
    //                        Vault Creation
    ///////////////////////////////////////////////////////////////////

    /**
     * @notice Spin up a new AutopoolETH
     * @param strategy Strategy template address
     * @param symbolSuffix Symbol suffix of the new token
     * @param descPrefix Description prefix of the new token
     * @param salt Vault creation salt
     * @param extraParams Any extra data needed for the vault
     */
    function createVault(
        address strategy,
        string memory symbolSuffix,
        string memory descPrefix,
        bytes32 salt,
        bytes calldata extraParams
    ) external payable returns (address newVaultAddress);

    function addStrategyTemplate(
        address strategyTemplate
    ) external;

    function removeStrategyTemplate(
        address strategyTemplate
    ) external;

    /// @notice Returns the template used to create Autopools
    function template() external returns (address);
}

File 24 of 87 : ISystemSecurity.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

interface ISystemSecurity {
    /// @notice Get the number of NAV/share operations currently in progress
    /// @return Number of operations
    function navOpsInProgress() external view returns (uint256);

    /// @notice Called at the start of any NAV/share changing operation
    function enterNavOperation() external;

    /// @notice Called at the end of any NAV/share changing operation
    function exitNavOperation() external;

    /// @notice Whether or not the system as a whole is paused
    function isSystemPaused() external returns (bool);

    /// @notice Sets an autopool in transient storage , this is used to guard against malicious user payloads
    /// that could be used to reenter the system
    /// @param autopool The address of the autopool to set
    /// @dev This is used in the AutopilotRouter to guard against reentrancy via malicious payload in the
    /// swap routes when redeeming
    function setAllowedAutopool(
        address autopool
    ) external;

    /// @notice Clears the autopool from transient storage
    function clearAllowedAutopool() external;
}

File 25 of 87 : IDestinationRegistry.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IDestinationAdapter } from "src/interfaces/destinations/IDestinationAdapter.sol";

interface IDestinationRegistry {
    event Register(bytes32[] indexed destinationTypes, address[] indexed targets);
    event Replace(bytes32[] indexed destinationTypes, address[] indexed targets);
    event Unregister(bytes32[] indexed destinationTypes);

    event Whitelist(bytes32[] indexed destinationTypes);
    event RemoveFromWhitelist(bytes32[] indexed destinationTypes);

    error InvalidAddress(address addr);
    error NotAllowedDestination();
    error DestinationAlreadySet();

    /**
     * @notice Adds a new addresses of the given destination types
     * @dev Fails if trying to overwrite previous value of the same destination type
     * @param destinationTypes Ones from the destination type whitelist
     * @param targets addresses of the deployed DestinationAdapters, cannot be 0
     */
    function register(bytes32[] calldata destinationTypes, address[] calldata targets) external;

    /**
     * @notice Replaces an addresses of the given destination types
     * @dev Fails if given destination type was not set previously
     * @param destinationTypes Ones from the destination type whitelist
     * @param targets addresses of the deployed DestinationAdapters, cannot be 0
     */
    function replace(bytes32[] calldata destinationTypes, address[] calldata targets) external;

    /**
     * @notice Removes an addresses of the given pre-registered destination types
     * @param destinationTypes Ones from the destination types whitelist
     */
    function unregister(
        bytes32[] calldata destinationTypes
    ) external;

    /**
     * @notice Gives an address of the given destination type
     * @dev Should revert on missing destination
     * @param destination One from the destination type whitelist
     */
    function getAdapter(
        bytes32 destination
    ) external returns (IDestinationAdapter);

    /**
     * @notice Adds given destination types to the whitelist
     * @param destinationTypes Types to whitelist
     */
    function addToWhitelist(
        bytes32[] calldata destinationTypes
    ) external;

    /**
     * @notice Removes given pre-whitelisted destination types
     * @param destinationTypes Ones from the destination type whitelist
     */
    function removeFromWhitelist(
        bytes32[] calldata destinationTypes
    ) external;

    /**
     * @notice Checks if the given destination type is whitelisted
     * @param destinationType Type to verify
     */
    function isWhitelistedDestination(
        bytes32 destinationType
    ) external view returns (bool);
}

File 26 of 87 : IRootPriceOracle.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity ^0.8.24;

/// @notice Retrieve a price for any token used in the system
interface IRootPriceOracle {
    /// @notice Returns a fair price for the provided token in ETH
    /// @param token token to get the price of
    /// @return price the price of the token in ETH
    function getPriceInEth(
        address token
    ) external returns (uint256 price);

    /// @notice Returns a spot price for the provided token in ETH, utilizing specified liquidity pool
    /// @param token token to get the spot price of
    /// @param pool liquidity pool to be used for price determination
    /// @return price the spot price of the token in ETH based on the provided pool
    function getSpotPriceInEth(address token, address pool) external returns (uint256);

    /// @notice Returns a price for base token in quote token.
    /// @dev Requires both tokens to be registered.
    /// @param base Address of base token.
    /// @param quote Address of quote token.
    /// @return price Price of the base token in quote token.
    function getPriceInQuote(address base, address quote) external returns (uint256 price);

    /// @notice Retrieve the price of LP token based on the reserves
    /// @param lpToken LP token to get the price of
    /// @param pool liquidity pool to be used for price determination
    /// @param quoteToken token to quote the price in
    function getRangePricesLP(
        address lpToken,
        address pool,
        address quoteToken
    ) external returns (uint256 spotPriceInQuote, uint256 safePriceInQuote, bool isSpotSafe);

    /// @notice Returns floor or ceiling price of the supplied lp token in terms of requested quote.
    /// @dev  Floor price: the minimum price among all the spot prices and safe prices of the tokens in the pool.
    ///       Ceiling price: the maximum price among all the spot prices and safe prices of the tokens in the pool.
    /// @param pool Address of pool to get spot pricing from.
    /// @param lpToken Address of the lp token to price.
    /// @param inQuote Address of desired quote token.
    /// @param ceiling Bool indicating whether to get floor or ceiling price.
    /// @return floorOrCeilingPerLpToken Floor or ceiling price of the lp token.
    function getFloorCeilingPrice(
        address pool,
        address lpToken,
        address inQuote,
        bool ceiling
    ) external returns (uint256 floorOrCeilingPerLpToken);

    function getFloorPrice(address, address, address) external returns (uint256 price);

    function getCeilingPrice(address, address, address) external returns (uint256 price);
}

File 27 of 87 : IDestinationVaultRegistry.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IDestinationVaultFactory } from "src/interfaces/vault/IDestinationVaultFactory.sol";

/// @notice Tracks valid Destination Vaults for the system
interface IDestinationVaultRegistry {
    /// @notice Determines if a given address is a valid Destination Vault in the system
    /// @param destinationVault address to check
    /// @return True if vault is registered
    function isRegistered(
        address destinationVault
    ) external view returns (bool);

    /// @notice Registers a new Destination Vault
    /// @dev Should be locked down to only a factory
    /// @param newDestinationVault Address of the new vault
    function register(
        address newDestinationVault
    ) external;

    /// @notice Checks if an address is a valid Destination Vault and reverts if not
    /// @param destinationVault Destination Vault address to checked
    function verifyIsRegistered(
        address destinationVault
    ) external view;

    /// @notice Returns a list of all registered vaults
    function listVaults() external view returns (address[] memory);

    /// @notice Factory that is allowed to create and registry Destination Vaults
    function factory() external view returns (IDestinationVaultFactory);
}

File 28 of 87 : IStatsCalculatorRegistry.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IStatsCalculator } from "src/interfaces/stats/IStatsCalculator.sol";

/// @notice Track stat calculators for this instance of the system
interface IStatsCalculatorRegistry {
    /// @notice Get a registered calculator
    /// @dev Should revert if missing
    /// @param aprId key of the calculator to get
    /// @return calculator instance of the calculator
    function getCalculator(
        bytes32 aprId
    ) external view returns (IStatsCalculator calculator);

    /// @notice List all calculator addresses registered
    function listCalculators() external view returns (bytes32[] memory, address[] memory);

    /// @notice Register a new stats calculator
    /// @param calculator address of the calculator
    function register(
        address calculator
    ) external;

    /// @notice Remove a stats calculator
    /// @param aprId key of the calculator to remove
    function removeCalculator(
        bytes32 aprId
    ) external;

    /// @notice Set the factory that can register calculators
    /// @param factory address of the factory
    function setCalculatorFactory(
        address factory
    ) external;
}

File 29 of 87 : IAsyncSwapperRegistry.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

interface IAsyncSwapperRegistry {
    event SwapperAdded(address indexed item);
    event SwapperRemoved(address indexed item);

    /// @notice Registers an item
    /// @param item Item address to be added
    function register(
        address item
    ) external;

    /// @notice Removes item registration
    /// @param item Item address to be removed
    function unregister(
        address item
    ) external;

    /// @notice Returns a list of all registered items
    function list() external view returns (address[] memory);

    /// @notice Checks if an address is a valid item
    /// @param item Item address to be checked
    function isRegistered(
        address item
    ) external view returns (bool);

    /// @notice Checks if an address is a valid swapper and reverts if not
    /// @param item Swapper address to be checked
    function verifyIsRegistered(
        address item
    ) external view;
}

File 30 of 87 : IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

File 31 of 87 : IIncentivesPricingStats.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

/// @title EWMA pricing for incentive tokens
interface IIncentivesPricingStats {
    event TokenAdded(address indexed token);
    event TokenRemoved(address indexed token);
    event TokenSnapshot(
        address indexed token,
        uint40 lastSnapshot,
        uint256 fastFilterPrice,
        uint256 slowFilterPrice,
        uint256 initCount,
        bool initComplete
    );

    error TokenAlreadyRegistered(address token);
    error TokenNotFound(address token);
    error IncentiveTokenPriceStale(address token);
    error TokenSnapshotNotReady(address token);

    struct TokenSnapshotInfo {
        uint40 lastSnapshot;
        bool _initComplete;
        uint8 _initCount;
        uint256 _initAcc;
        uint256 fastFilterPrice;
        uint256 slowFilterPrice;
    }

    /// @notice add a token to snapshot
    /// @dev the token must be configured in the RootPriceOracle before adding here
    /// @param token the address of the token to add
    function setRegisteredToken(
        address token
    ) external;

    /// @notice remove a token from being snapshot
    /// @param token the address of the token to remove
    function removeRegisteredToken(
        address token
    ) external;

    /// @notice get the addresses for all currently registered tokens
    /// @return tokens all of the registered token addresses
    function getRegisteredTokens() external view returns (address[] memory tokens);

    /// @notice get all of the registered tokens with the latest snapshot info
    /// @return tokenAddresses token addresses in the same order as info
    /// @return info a list of snapshot info for the tokens
    function getTokenPricingInfo()
        external
        view
        returns (address[] memory tokenAddresses, TokenSnapshotInfo[] memory info);

    /// @notice update the snapshot for the specified tokens
    /// @dev if a token is not ready to be snapshot the entire call will fail
    function snapshot(
        address[] calldata tokensToSnapshot
    ) external;

    /// @notice get the latest prices for an incentive token. Reverts if token is not registered
    /// @return fastPrice the price based on the faster filter (weighted toward current prices)
    /// @return slowPrice the price based on the slower filter (weighted toward older prices, relative to fast)
    function getPrice(address token, uint40 staleCheck) external view returns (uint256 fastPrice, uint256 slowPrice);

    /// @notice get the latest prices for an incentive token or zero if the token is not registered
    /// @return fastPrice the price based on the faster filter (weighted toward current prices)
    /// @return slowPrice the price based on the slower filter (weighted toward older prices, relative to fast)
    function getPriceOrZero(
        address token,
        uint40 staleCheck
    ) external view returns (uint256 fastPrice, uint256 slowPrice);
}

File 32 of 87 : IMessageProxy.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

/// @title Send messages to our systems on other chains
interface IMessageProxy {
    function sendMessage(bytes32 messageType, bytes memory message) external;
}

File 33 of 87 : Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

File 34 of 87 : ISystemComponent.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

/// @notice Stores a reference to the registry for this system
interface ISystemComponent {
    /// @notice The system instance this contract is tied to
    function getSystemRegistry() external view returns (address registry);
}

File 35 of 87 : Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

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

File 36 of 87 : AutopoolDebt.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity ^0.8.24;

import { Errors } from "src/utils/Errors.sol";
import { LibAdapter } from "src/libs/LibAdapter.sol";
import { IDestinationVault } from "src/interfaces/vault/IDestinationVault.sol";
import { Math } from "openzeppelin-contracts/utils/math/Math.sol";
import { EnumerableSet } from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20Metadata as IERC20 } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { StructuredLinkedList } from "src/strategy/StructuredLinkedList.sol";
import { WithdrawalQueue } from "src/strategy/WithdrawalQueue.sol";
import { IAutopool } from "src/interfaces/vault/IAutopool.sol";
import { IMainRewarder } from "src/interfaces/rewarders/IMainRewarder.sol";
import { AutopoolToken } from "src/vault/libs/AutopoolToken.sol";
import { IRootPriceOracle } from "src/interfaces/oracles/IRootPriceOracle.sol";
import { ISystemRegistry } from "src/interfaces/ISystemRegistry.sol";
import { AutopoolState, ProcessRebalanceParams } from "src/vault/libs/AutopoolState.sol";
import { AutopoolStrategyHooks } from "src/vault/libs/AutopoolStrategyHooks.sol";
import { IStrategyHook, HookFunctionIndex } from "src/interfaces/strategy/IStrategyHook.sol";

library AutopoolDebt {
    using Math for uint256;
    using SafeERC20 for IERC20;
    using WithdrawalQueue for StructuredLinkedList.List;
    using EnumerableSet for EnumerableSet.AddressSet;
    using AutopoolToken for AutopoolToken.TokenData;

    /// @notice Max time a cached debt report can be used
    uint256 public constant MAX_DEBT_REPORT_AGE_SECONDS = 1 days;

    error VaultShutdown();
    error WithdrawShareCalcInvalid(uint256 currentShares, uint256 cachedShares);
    error RebalanceFailed(string message);
    error InvalidPrices();
    error InvalidTotalAssetPurpose();
    error InvalidDestination(address destination);
    error TooFewAssets(uint256 requested, uint256 actual);
    error SharesAndAssetsReceived(uint256 assets, uint256 shares);
    error AmountExceedsAllowance(uint256 shares, uint256 allowed);
    error PositivePriceRecoupNotCovered(uint256 remaining);
    error RebalanceDestinationsMatch();
    error InsufficientAssets(address asset);
    error RebalanceDestinationUnderlyerMismatch(address destination, address trueUnderlyer, address providedUnderlyer);
    error OnlyRebalanceToIdleAvailable();
    error UnregisteredDestination(address dest);

    event DestinationDebtReporting(
        address destination, AutopoolDebt.IdleDebtUpdates debtInfo, uint256 claimed, uint256 claimGasUsed
    );
    event NewNavShareFeeMark(uint256 navPerShare, uint256 timestamp);
    event Nav(uint256 idle, uint256 debt, uint256 totalSupply);
    event Withdraw(
        address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
    );

    struct DestinationInfo {
        /// @notice Current underlying value at the destination vault
        /// @dev Used for calculating totalDebt, mid point of min and max
        uint256 cachedDebtValue;
        /// @notice Current minimum underlying value at the destination vault
        /// @dev Used for calculating totalDebt during withdrawal
        uint256 cachedMinDebtValue;
        /// @notice Current maximum underlying value at the destination vault
        /// @dev Used for calculating totalDebt of the deposit
        uint256 cachedMaxDebtValue;
        /// @notice Last block timestamp this info was updated
        uint256 lastReport;
        /// @notice How many shares of the destination vault we owned at last report
        uint256 ownedShares;
    }

    struct IdleDebtUpdates {
        bool pricesWereSafe;
        uint256 totalIdleDecrease;
        uint256 totalIdleIncrease;
        uint256 totalDebtIncrease;
        uint256 totalDebtDecrease;
        uint256 totalMinDebtIncrease;
        uint256 totalMinDebtDecrease;
        uint256 totalMaxDebtIncrease;
        uint256 totalMaxDebtDecrease;
    }

    struct AssetChanges {
        uint256 startingIdle;
        uint256 startingDebt;
        uint256 startingTotalSupply;
        uint256 newIdle;
        uint256 newDebt;
        uint256 endingTotalSupply;
    }

    struct RebalanceOutParams {
        /// Address that will received the withdrawn underlyer
        address receiver;
        /// The "out" destination vault
        address destinationOut;
        /// The amount of tokenOut that will be withdrawn
        uint256 amountOut;
        /// The underlyer for destinationOut
        address tokenOut;
        IERC20 _baseAsset;
        bool _shutdown;
    }

    /// @dev In memory struct only for managing vars in _withdraw
    struct WithdrawInfo {
        uint256 currentIdle;
        uint256 assetsFromIdle;
        uint256 totalAssetsToPull;
        uint256 assetsToPull;
        uint256 assetsPulled;
        uint256 idleIncrease;
        uint256 debtDecrease;
        uint256 debtMinDecrease;
        uint256 debtMaxDecrease;
        uint256 totalMinDebt;
        uint256 destinationRound;
        uint256 lastRoundSlippage;
        uint256 expectedAssets;
        uint256 remainingRecoup;
    }

    struct FlashRebalanceParams {
        IERC20 baseAsset;
        bool shutdown;
    }

    struct FlashResultInfo {
        uint256 tokenInBalanceBefore;
        uint256 tokenInBalanceAfter;
        bytes32 flashResult;
    }

    function processRebalance(
        AutopoolState storage $,
        ProcessRebalanceParams memory args,
        bytes calldata data,
        bytes memory hooks
    ) external returns (AutopoolDebt.AssetChanges memory updates) {
        validateRebalanceParams($, args);

        updates.startingIdle = $.assetBreakdown.totalIdle;
        updates.startingDebt = $.assetBreakdown.totalDebt;

        AutopoolDebt.IdleDebtUpdates memory result = flashRebalance($, args, data, hooks);

        updates.newIdle = updates.startingIdle + result.totalIdleIncrease - result.totalIdleDecrease;
        updates.newDebt = updates.startingDebt + result.totalDebtIncrease - result.totalDebtDecrease;

        $.assetBreakdown.totalIdle = updates.newIdle;
        $.assetBreakdown.totalDebt = updates.newDebt;
        $.assetBreakdown.totalDebtMin =
            $.assetBreakdown.totalDebtMin + result.totalMinDebtIncrease - result.totalMinDebtDecrease;
        $.assetBreakdown.totalDebtMax =
            $.assetBreakdown.totalDebtMax + result.totalMaxDebtIncrease - result.totalMaxDebtDecrease;
    }

    function flashRebalance(
        AutopoolState storage $,
        ProcessRebalanceParams memory args,
        bytes calldata data,
        bytes memory hooks
    ) private returns (IdleDebtUpdates memory result) {
        DestinationInfo storage destInfoOut = $.destinationInfo[args.rebalanceParams.destinationOut];
        DestinationInfo storage destInfoIn = $.destinationInfo[args.rebalanceParams.destinationIn];

        AutopoolStrategyHooks.executeHooks(
            hooks,
            uint256(HookFunctionIndex.onRebalanceStart),
            abi.encodeCall(IStrategyHook.onRebalanceStart, (args, msg.sender))
        );

        // Handle decrease (shares going "Out", cashing in shares and sending underlying back to swapper)
        // If the tokenOut is _asset we assume they are taking idle
        // which is already in the contract
        result = _handleRebalanceOut(
            AutopoolDebt.RebalanceOutParams({
                receiver: address(args.receiver),
                destinationOut: args.rebalanceParams.destinationOut,
                amountOut: args.rebalanceParams.amountOut,
                tokenOut: args.rebalanceParams.tokenOut,
                _baseAsset: args.baseAsset,
                _shutdown: $.shutdown
            }),
            destInfoOut
        );

        if (!result.pricesWereSafe) {
            revert InvalidPrices();
        }

        AutopoolStrategyHooks.executeHooks(
            hooks,
            uint256(HookFunctionIndex.onRebalanceOutAssetsReady),
            abi.encodeCall(IStrategyHook.onRebalanceOutAssetsReady, (args, msg.sender))
        );

        // Handle increase (shares coming "In", getting underlying from the swapper and trading for new shares)

        FlashResultInfo memory flashResultInfo;
        // get "before" counts
        flashResultInfo.tokenInBalanceBefore = IERC20(args.rebalanceParams.tokenIn).balanceOf(address(this));

        // Give control back to the solver so they can make use of the "out" assets
        // and get our "in" asset
        flashResultInfo.flashResult =
            args.receiver.onFlashLoan(msg.sender, args.rebalanceParams.tokenIn, args.rebalanceParams.amountIn, 0, data);

        // We assume the solver will send us the assets
        flashResultInfo.tokenInBalanceAfter = IERC20(args.rebalanceParams.tokenIn).balanceOf(address(this));

        // Make sure the call was successful and verify we have at least the assets we think
        // we were getting
        if (
            flashResultInfo.flashResult != keccak256("ERC3156FlashBorrower.onFlashLoan")
                || flashResultInfo.tokenInBalanceAfter
                    < flashResultInfo.tokenInBalanceBefore + args.rebalanceParams.amountIn
        ) {
            revert Errors.FlashLoanFailed(args.rebalanceParams.tokenIn, args.rebalanceParams.amountIn);
        }

        AutopoolStrategyHooks.executeHooks(
            hooks,
            uint256(HookFunctionIndex.onRebalanceInAssetsReturned),
            abi.encodeCall(IStrategyHook.onRebalanceInAssetsReturned, (args, msg.sender))
        );

        if (args.rebalanceParams.tokenIn != address(args.baseAsset)) {
            IdleDebtUpdates memory inDebtResult = _handleRebalanceIn(
                destInfoIn,
                IDestinationVault(args.rebalanceParams.destinationIn),
                args.rebalanceParams.tokenIn,
                flashResultInfo.tokenInBalanceAfter
            );
            if (!inDebtResult.pricesWereSafe) {
                revert InvalidPrices();
            }
            result.totalDebtDecrease += inDebtResult.totalDebtDecrease;
            result.totalDebtIncrease += inDebtResult.totalDebtIncrease;
            result.totalMinDebtDecrease += inDebtResult.totalMinDebtDecrease;
            result.totalMinDebtIncrease += inDebtResult.totalMinDebtIncrease;
            result.totalMaxDebtDecrease += inDebtResult.totalMaxDebtDecrease;
            result.totalMaxDebtIncrease += inDebtResult.totalMaxDebtIncrease;
        } else {
            result.totalIdleIncrease += flashResultInfo.tokenInBalanceAfter - flashResultInfo.tokenInBalanceBefore;
        }

        AutopoolStrategyHooks.executeHooks(
            hooks,
            uint256(HookFunctionIndex.onRebalanceDestinationVaultUpdated),
            abi.encodeCall(IStrategyHook.onRebalanceDestinationVaultUpdated, (args, msg.sender))
        );
    }

    function validateRebalanceParams(AutopoolState storage $, ProcessRebalanceParams memory args) private view {
        address autopool = address(this);

        Errors.verifyNotZero(args.rebalanceParams.destinationIn, "destinationIn");
        Errors.verifyNotZero(args.rebalanceParams.destinationOut, "destinationOut");
        Errors.verifyNotZero(args.rebalanceParams.tokenIn, "tokenIn");
        Errors.verifyNotZero(args.rebalanceParams.tokenOut, "tokenOut");
        Errors.verifyNotZero(args.rebalanceParams.amountIn, "amountIn");
        Errors.verifyNotZero(args.rebalanceParams.amountOut, "amountOut");

        ensureDestinationRegistered(autopool, args.rebalanceParams.destinationIn);
        ensureDestinationRegistered(autopool, args.rebalanceParams.destinationOut);

        // when a vault is shutdown, rebalancing can only pull assets from destinations back to the vault
        if ($.shutdown && args.rebalanceParams.destinationIn != autopool) {
            revert OnlyRebalanceToIdleAvailable();
        }

        if (args.rebalanceParams.destinationIn == args.rebalanceParams.destinationOut) {
            revert RebalanceDestinationsMatch();
        }

        address baseAsset = address(args.baseAsset);

        // if the in/out destination is the AutopoolETH then the in/out token must be the baseAsset
        // if the in/out is not the AutopoolETH then the in/out token must match the destinations underlying token
        if (args.rebalanceParams.destinationIn == autopool) {
            if (args.rebalanceParams.tokenIn != baseAsset) {
                revert RebalanceDestinationUnderlyerMismatch(
                    args.rebalanceParams.destinationIn, args.rebalanceParams.tokenIn, baseAsset
                );
            }
        } else {
            IDestinationVault inDest = IDestinationVault(args.rebalanceParams.destinationIn);
            if (args.rebalanceParams.tokenIn != inDest.underlying()) {
                revert RebalanceDestinationUnderlyerMismatch(
                    args.rebalanceParams.destinationIn, inDest.underlying(), args.rebalanceParams.tokenIn
                );
            }
        }

        if (args.rebalanceParams.destinationOut == autopool) {
            if (args.rebalanceParams.tokenOut != baseAsset) {
                revert RebalanceDestinationUnderlyerMismatch(
                    args.rebalanceParams.destinationOut, args.rebalanceParams.tokenOut, baseAsset
                );
            }
            if (args.rebalanceParams.amountOut > $.assetBreakdown.totalIdle) {
                revert InsufficientAssets(args.rebalanceParams.tokenOut);
            }
        } else {
            IDestinationVault outDest = IDestinationVault(args.rebalanceParams.destinationOut);
            if (args.rebalanceParams.tokenOut != outDest.underlying()) {
                revert RebalanceDestinationUnderlyerMismatch(
                    args.rebalanceParams.destinationOut, outDest.underlying(), args.rebalanceParams.tokenOut
                );
            }
            if (args.rebalanceParams.amountOut > outDest.balanceOf(autopool)) {
                revert InsufficientAssets(args.rebalanceParams.tokenOut);
            }
        }
    }

    function ensureDestinationRegistered(address autopool, address dest) private view {
        if (dest == address(autopool)) return;
        if (
            !(
                IAutopool(autopool).isDestinationRegistered(dest)
                    || IAutopool(autopool).isDestinationQueuedForRemoval(dest)
            )
        ) {
            revert UnregisteredDestination(dest);
        }
    }

    /// @notice Perform deposit and debt info update for the "in" destination during a rebalance
    /// @dev This "in" function performs less validations than its "out" version
    /// @param dvIn The "in" destination vault
    /// @param tokenIn The underlyer for dvIn
    /// @param depositAmount The amount of tokenIn that will be deposited
    /// @return result Changes in debt values
    function _handleRebalanceIn(
        DestinationInfo storage destInfo,
        IDestinationVault dvIn,
        address tokenIn,
        uint256 depositAmount
    ) private returns (IdleDebtUpdates memory result) {
        LibAdapter._approve(IERC20(tokenIn), address(dvIn), depositAmount);

        // Snapshot our current shares so we know how much to back out
        uint256 originalShareBal = dvIn.balanceOf(address(this));

        // deposit to dv
        uint256 newShares = dvIn.depositUnderlying(depositAmount);

        // Update the debt info snapshot
        result = _recalculateDestInfo(destInfo, dvIn, originalShareBal, originalShareBal + newShares);
    }

    function oldestDebtReporting(
        AutopoolState storage $
    ) public view returns (uint256) {
        return $.destinationInfo[$.debtReportQueue.peekHead()].lastReport;
    }

    /**
     * @notice Perform withdraw and debt info update for the "out" destination during a rebalance
     * @dev This "out" function performs more validations and handles idle as opposed to "in" which does not
     *  debtDecrease The previous amount of debt destinationOut accounted for in totalDebt
     *  debtIncrease The current amount of debt destinationOut should account for in totalDebt
     *  idleDecrease Amount of baseAsset that was sent from the vault. > 0 only when tokenOut == baseAsset
     *  idleIncrease Amount of baseAsset that was claimed from Destination Vault
     * @param params Rebalance out params
     * @param destOutInfo The "out" destination vault info
     * @return assetChange debt and idle change data
     */
    function _handleRebalanceOut(
        RebalanceOutParams memory params,
        DestinationInfo storage destOutInfo
    ) private returns (IdleDebtUpdates memory assetChange) {
        // Handle decrease (shares going "Out", cashing in shares and sending underlying back to swapper)
        // If the tokenOut is _asset we assume they are taking idle
        // which is already in the contract

        if (params.tokenOut != address(params._baseAsset)) {
            IDestinationVault dvOut = IDestinationVault(params.destinationOut);

            // Snapshot our current shares so we know how much to back out
            uint256 originalShareBal = dvOut.balanceOf(address(this));

            // Burning our shares will claim any pending baseAsset
            // rewards and send them to us.
            // Get our starting balance
            uint256 beforeBaseAssetBal = params._baseAsset.balanceOf(address(this));

            // Withdraw underlying from the destination vault
            // Shares are sent directly to the flashRebalance receiver
            // slither-disable-next-line unused-return
            dvOut.withdrawUnderlying(params.amountOut, params.receiver);

            // Update the debt info snapshot
            assetChange =
                _recalculateDestInfo(destOutInfo, dvOut, originalShareBal, originalShareBal - params.amountOut);

            // Capture any rewards we may have claimed as part of withdrawing
            assetChange.totalIdleIncrease = params._baseAsset.balanceOf(address(this)) - beforeBaseAssetBal;
        } else {
            // Working with idle baseAsset which should be in the vault already
            // Just send it out
            IERC20(params.tokenOut).safeTransfer(params.receiver, params.amountOut);
            assetChange.totalIdleDecrease = params.amountOut;

            // We weren't dealing with any debt or pricing, just idle, so we can just mark
            // it as safe
            assetChange.pricesWereSafe = true;
        }
    }

    function recalculateDestInfo(
        DestinationInfo storage destInfo,
        IDestinationVault destVault,
        uint256 originalShares,
        uint256 currentShares
    ) external returns (IdleDebtUpdates memory result) {
        result = _recalculateDestInfo(destInfo, destVault, originalShares, currentShares);
    }

    /// @dev Will not revert on unsafe prices. Up to the caller.
    function _recalculateDestInfo(
        DestinationInfo storage destInfo,
        IDestinationVault destVault,
        uint256 originalShares,
        uint256 currentShares
    ) private returns (IdleDebtUpdates memory result) {
        // Figure out what to back out of our totalDebt number.
        // We could have had withdraws since the last snapshot which means our
        // cached currentDebt number should be decreased based on the remaining shares
        // totalDebt is decreased using the same proportion of shares method during withdrawals
        // so this should represent whatever is remaining.

        // Prices are per LP token and whether or not the prices are safe to use
        // If they aren't safe then just continue and we'll get it on the next go around

        (uint256 spotPrice, uint256 safePrice, bool isSpotSafe) = destVault.getRangePricesLP();

        // Calculate what we're backing out based on the original shares
        uint256 minPrice = spotPrice > safePrice ? safePrice : spotPrice;
        uint256 maxPrice = spotPrice > safePrice ? spotPrice : safePrice;

        // If we previously had shares, calculate how much of our cached numbers
        // still remain as this will be deducted from the overall debt numbers
        // over time
        uint256 prevOwnedShares = destInfo.ownedShares;
        if (prevOwnedShares > 0) {
            result.totalDebtDecrease = (destInfo.cachedDebtValue * originalShares) / prevOwnedShares;
            result.totalMinDebtDecrease = (destInfo.cachedMinDebtValue * originalShares) / prevOwnedShares;
            result.totalMaxDebtDecrease = (destInfo.cachedMaxDebtValue * originalShares) / prevOwnedShares;
        }

        // The overall debt value is the mid point of min and max
        uint256 div = 10 ** destVault.decimals();
        uint256 newDebtValue = (minPrice * currentShares + maxPrice * currentShares) / (div * 2);

        result.pricesWereSafe = isSpotSafe;
        result.totalDebtIncrease = newDebtValue;
        result.totalMinDebtIncrease = minPrice * currentShares / div;
        result.totalMaxDebtIncrease = maxPrice * currentShares / div;

        // Save our current new values
        destInfo.cachedDebtValue = newDebtValue;
        destInfo.cachedMinDebtValue = result.totalMinDebtIncrease;
        destInfo.cachedMaxDebtValue = result.totalMaxDebtIncrease;
        destInfo.lastReport = block.timestamp;
        destInfo.ownedShares = currentShares;
    }

    function totalAssetsTimeChecked(
        AutopoolState storage $,
        IAutopool.TotalAssetPurpose purpose
    ) external returns (uint256) {
        IDestinationVault destVault = IDestinationVault($.debtReportQueue.peekHead());
        uint256 recalculatedTotalAssets = IAutopool(address(this)).totalAssets(purpose);

        while (address(destVault) != address(0)) {
            uint256 lastReport = $.destinationInfo[address(destVault)].lastReport;

            if (lastReport + MAX_DEBT_REPORT_AGE_SECONDS > block.timestamp) {
                // Its not stale

                // This report is OK, we don't need to recalculate anything
                break;
            } else {
                // It is stale, recalculate

                //slither-disable-next-line unused-return
                uint256 currentShares = destVault.balanceOf(address(this));
                uint256 staleDebt;
                uint256 extremePrice;

                // Figure out exactly which price to use based on its purpose
                if (purpose == IAutopool.TotalAssetPurpose.Deposit) {
                    // We use max value so that anything deposited is worth less
                    extremePrice = destVault.getUnderlyerCeilingPrice();

                    // Round down. We are subtracting this value out of the total so some left
                    // behind just increases the value which is what we want
                    staleDebt = $.destinationInfo[address(destVault)].cachedMaxDebtValue.mulDiv(
                        currentShares, $.destinationInfo[address(destVault)].ownedShares, Math.Rounding.Down
                    );
                } else if (purpose == IAutopool.TotalAssetPurpose.Withdraw) {
                    // We use min value so that we value the shares as worth less
                    extremePrice = destVault.getUnderlyerFloorPrice();
                    // Round up. We are subtracting this value out of the total so if we take a little
                    // extra it just decreases the value which is what we want
                    staleDebt = $.destinationInfo[address(destVault)].cachedMinDebtValue.mulDiv(
                        currentShares, $.destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
                    );
                } else {
                    revert InvalidTotalAssetPurpose();
                }

                // Back out our stale debt, add in its new value
                // Our goal is to find the most conservative value in each situation. If the current
                // value we have represents that, then use it. Otherwise, use the new one.

                uint256 newValue = (currentShares * extremePrice) / destVault.ONE();

                if (purpose == IAutopool.TotalAssetPurpose.Deposit && staleDebt > newValue) {
                    newValue = staleDebt;
                } else if (purpose == IAutopool.TotalAssetPurpose.Withdraw && staleDebt < newValue) {
                    newValue = staleDebt;
                }

                recalculatedTotalAssets = recalculatedTotalAssets + newValue - staleDebt;
            }

            destVault = IDestinationVault($.debtReportQueue.getAdjacent(address(destVault), true));
        }

        return recalculatedTotalAssets;
    }

    function updateDebtReporting(
        AutopoolState storage $,
        uint256 numToProcess,
        bytes memory hooks
    ) external returns (AssetChanges memory changes) {
        IdleDebtUpdates memory result;

        // Persist our change in idle and debt
        changes.startingIdle = $.assetBreakdown.totalIdle;
        changes.startingDebt = $.assetBreakdown.totalDebt;

        numToProcess = Math.min(numToProcess, $.debtReportQueue.sizeOf());

        for (uint256 i = 0; i < numToProcess; ++i) {
            IDestinationVault destVault = IDestinationVault($.debtReportQueue.popHead());

            // Get the reward value we've earned. DV rewards are always in terms of base asset
            // We track the gas used purely for off-chain stats purposes
            // Main rewarder on DV's store the earned and liquidated rewards
            // Extra rewarders are disabled at the DV level
            uint256 claimGasUsed = gasleft();
            uint256 beforeBaseAsset = IERC20(IAutopool(address(this)).asset()).balanceOf(address(this));
            IMainRewarder(destVault.rewarder()).getReward(address(this), address(this), false);
            uint256 claimedRewardValue =
                IERC20(IAutopool(address(this)).asset()).balanceOf(address(this)) - beforeBaseAsset;
            result.totalIdleIncrease += claimedRewardValue;

            // Recalculate the debt info figuring out the change in
            // total debt value we can roll up later
            uint256 currentShareBalance = destVault.balanceOf(address(this));

            AutopoolDebt.IdleDebtUpdates memory debtResult = _recalculateDestInfo(
                $.destinationInfo[address(destVault)], destVault, currentShareBalance, currentShareBalance
            );

            result.totalDebtDecrease += debtResult.totalDebtDecrease;
            result.totalDebtIncrease += debtResult.totalDebtIncrease;
            result.totalMinDebtDecrease += debtResult.totalMinDebtDecrease;
            result.totalMinDebtIncrease += debtResult.totalMinDebtIncrease;
            result.totalMaxDebtDecrease += debtResult.totalMaxDebtDecrease;
            result.totalMaxDebtIncrease += debtResult.totalMaxDebtIncrease;

            // If we no longer have shares, then there's no reason to continue reporting on the destination.
            // The strategy will only call for the info if its moving "out" of the destination
            // and that will only happen if we have shares.
            // A rebalance where we move "in" to the position will refresh the data at that time
            if (currentShareBalance > 0) {
                $.debtReportQueue.addToTail(address(destVault));
            }

            claimGasUsed -= gasleft();

            emit DestinationDebtReporting(address(destVault), debtResult, claimedRewardValue, claimGasUsed);

            AutopoolStrategyHooks.executeHooks(
                hooks,
                uint256(HookFunctionIndex.onDestinationDebtReport),
                abi.encodeCall(IStrategyHook.onDestinationDebtReport, (address(destVault), debtResult))
            );
        }

        changes.newIdle = changes.startingIdle + result.totalIdleIncrease;
        changes.newDebt = changes.startingDebt + result.totalDebtIncrease - result.totalDebtDecrease;

        $.assetBreakdown.totalIdle = changes.newIdle;
        $.assetBreakdown.totalDebt = changes.newDebt;
        $.assetBreakdown.totalDebtMin =
            $.assetBreakdown.totalDebtMin + result.totalMinDebtIncrease - result.totalMinDebtDecrease;
        $.assetBreakdown.totalDebtMax =
            $.assetBreakdown.totalDebtMax + result.totalMaxDebtIncrease - result.totalMaxDebtDecrease;
    }

    function _initiateWithdrawInfo(
        uint256 assets,
        IAutopool.AssetBreakdown storage assetBreakdown
    ) private view returns (WithdrawInfo memory) {
        uint256 idle = assetBreakdown.totalIdle;
        WithdrawInfo memory info = WithdrawInfo({
            currentIdle: idle,
            // If idle can cover the full amount, then we want to pull all assets from there
            // Otherwise, we want to pull from the market and only get idle if we exhaust the market
            assetsFromIdle: assets > idle ? 0 : assets,
            totalAssetsToPull: 0,
            assetsToPull: 0,
            assetsPulled: 0,
            idleIncrease: 0,
            debtDecrease: 0,
            debtMinDecrease: 0,
            debtMaxDecrease: 0,
            totalMinDebt: assetBreakdown.totalDebtMin,
            destinationRound: 0,
            lastRoundSlippage: 0,
            expectedAssets: 0,
            remainingRecoup: 0
        });

        info.totalAssetsToPull = assets - info.assetsFromIdle;

        // This var we use to track our progress later
        info.assetsToPull = assets - info.assetsFromIdle;

        // Idle + minDebt is the maximum amount of assets/debt we could burn during a withdraw.
        // If the user is request more than that (like during a withdraw) we can just revert
        // early without trying
        if (info.totalAssetsToPull > info.currentIdle + info.totalMinDebt) {
            revert TooFewAssets(assets, info.currentIdle + info.totalMinDebt);
        }

        return info;
    }

    function withdraw(
        AutopoolState storage $,
        uint256 assets,
        uint256 applicableTotalAssets
    ) public returns (uint256 actualAssets, uint256 actualShares, uint256 debtBurned) {
        WithdrawInfo memory info = _initiateWithdrawInfo(assets, $.assetBreakdown);

        // Pull the market if there aren't enough funds in idle to cover the entire amount

        // This flow is not bounded by a set number of shares. The user has requested X assets
        // and a variable number of shares to burn so we don't have easy break out points like we do
        // during redeem (like using debt burned). When we get slippage here and don't meet the requested assets
        // we need to keep going if we can. This is tricky if we consider that (most of) our destinations are
        // LP positions and we'll be swapping assets, so we can expect some slippage. Even
        // if our minDebtValue numbers are up to date and perfectly accurate slippage could ensure we
        // are always receiving less than we expect/calculate and we never hit the requested assets
        // even though the owner would have shares to cover it. Under normal/expected conditions, our
        // minDebtValue is lower than actual and we expect overall value to be going up, so we burn a tad
        // more than we should and receive a tad more than we expect. This should cover us. However,
        // in other conditions we have to be sure we aren't endlessly trying to approach 0 so we are tracking
        // the slippage we received on the last pull, repricing, and applying an increasing multiplier until we either
        // pull enough to cover or pull them all and/or move to the next destination.

        uint256 dvSharesToBurn;
        while (info.assetsToPull > 0) {
            IDestinationVault destVault = IDestinationVault($.withdrawalQueue.peekHead());

            // We've run out of destinations
            if (address(destVault) == address(0)) {
                break;
            }

            uint256 dvShares = destVault.balanceOf(address(this));
            {
                uint256 dvSharesValue;
                if (info.destinationRound == 0) {
                    // First time pulling

                    // We use the min debt value here because its a withdrawal and we're trying to cover an amount
                    // of assets. Undervaluing the shares may mean we pull more but given that we expect slippage
                    // that is desirable.
                    dvSharesValue = $.destinationInfo[address(destVault)].cachedMinDebtValue * dvShares
                        / $.destinationInfo[address(destVault)].ownedShares;
                } else {
                    // When we've pulled from this destination before, i.e. destinationRound > 0, then we
                    // know a more accurate exchange rate and its worse than we were expecting.
                    // We even will pad it a bit as we want to account for any additional slippage we
                    // may receive by say being farther down an AMM curve.

                    // dvSharesToBurn is the last value we used when pulling from this destination
                    // info.expectedAssets is how much we expected to get on that last pull
                    // info.expectedAssets - info.lastRoundSlippage is how much we actually received

                    uint256 paddedSlippage = info.lastRoundSlippage * (info.destinationRound + 10_000) / 10_000;

                    if (paddedSlippage < info.expectedAssets) {
                        dvSharesValue = (info.expectedAssets - paddedSlippage) * dvShares / dvSharesToBurn;
                    } else {
                        // This will just mean we pull all shares
                        dvSharesValue = 0;
                    }
                }

                if (dvSharesValue > info.assetsToPull) {
                    dvSharesToBurn = (dvShares * info.assetsToPull) / dvSharesValue;

                    // On withdraw, we are trying to meet a specific number of assets without a limit
                    // on the debt we can burn. Burning 0 due to the valuations here would be an automatic failure
                    // as we still have assets to satisfy and debt to burn. We at least have to burn 1 even if it
                    // results in a larger over pull
                    if (dvSharesToBurn == 0) {
                        dvSharesToBurn = 1;
                    }

                    // Only need to set it here because the only time we'll use it is if
                    // we don't exhaust all shares and have to try the destination again
                    info.expectedAssets = info.assetsToPull;
                } else {
                    dvSharesToBurn = dvShares;
                }
            }

            uint256 pulledAssets;
            uint256 debtValueBurned;
            // Get the base asset back from the Destination. Also performs a check that we aren't receiving
            // poor execution on our swaps based on safe prices
            (info, pulledAssets, debtValueBurned) = _withdrawAssets(info, $.destinationInfo, destVault, dvSharesToBurn);

            info.assetsPulled += pulledAssets;

            if (info.remainingRecoup > 0) {
                // If the destination is so severely undervalued that it can't cover its own recoup then we have no
                // recourse but to burn the entire destination and the user would to have to cover the full overage
                // from the next destinations can get nothing from this one. Should not be allowed.
                revert PositivePriceRecoupNotCovered(info.remainingRecoup);
            }

            // If we've exhausted all shares we can remove the withdrawal from the queue
            // We need to leave it in the debt report queue though so that our destination specific
            // debt tracking values can be updated
            if (dvShares == dvSharesToBurn) {
                $.withdrawalQueue.popAddress(address(destVault));
                info.destinationRound = 0;
                info.lastRoundSlippage = 0;
            } else {
                // If we didn't burn all the shares and we received enough to cover our
                // expected that means we'll break out below as we've hit our target
                unchecked {
                    if (pulledAssets < info.expectedAssets) {
                        info.lastRoundSlippage = info.expectedAssets - pulledAssets;
                        if (info.destinationRound == 0) {
                            info.destinationRound = 100;
                        } else {
                            info.destinationRound *= 2;
                        }
                    }
                }
            }

            // It's possible we'll get back more assets than we anticipate from a swap
            // so if we do, throw it in idle and stop processing. You don't get more than we've calculated
            if (info.assetsPulled >= info.totalAssetsToPull) {
                info.idleIncrease += info.assetsPulled - info.totalAssetsToPull;
                info.assetsPulled = info.totalAssetsToPull;
                info.assetsToPull = 0;
                break;
            }

            info.assetsToPull -= pulledAssets;
        }

        // We didn't get enough assets from the debt pull
        // See if we can get the rest from idle
        if (info.assetsPulled < assets && info.currentIdle > 0) {
            uint256 remaining = assets - info.assetsPulled;
            if (remaining <= info.currentIdle) {
                info.assetsFromIdle = remaining;
            }
            // We don't worry about the else case because if currentIdle can't
            // cover remaining then we'll fail the `actualAssets < assets`
            // check below and revert
        }

        debtBurned = info.assetsFromIdle + info.debtMinDecrease;
        actualAssets = info.assetsFromIdle + info.assetsPulled;

        if (actualAssets < assets) {
            revert TooFewAssets(assets, actualAssets);
        }

        actualShares = IAutopool(address(this)).convertToShares(
            Math.max(actualAssets, debtBurned),
            applicableTotalAssets,
            IAutopool(address(this)).totalSupply(),
            Math.Rounding.Up
        );

        // Subtract what's taken out of idle from totalIdle
        // We may also have some increase to account for it we over pulled
        // or received better execution than we were anticipating
        // slither-disable-next-line events-maths
        $.assetBreakdown.totalIdle = info.currentIdle + info.idleIncrease - info.assetsFromIdle;

        // Save off our various debt numbers
        if (info.debtDecrease > $.assetBreakdown.totalDebt) {
            $.assetBreakdown.totalDebt = 0;
        } else {
            $.assetBreakdown.totalDebt -= info.debtDecrease;
        }

        if (info.debtMinDecrease > info.totalMinDebt) {
            $.assetBreakdown.totalDebtMin = 0;
        } else {
            $.assetBreakdown.totalDebtMin -= info.debtMinDecrease;
        }

        if (info.debtMaxDecrease > $.assetBreakdown.totalDebtMax) {
            $.assetBreakdown.totalDebtMax = 0;
        } else {
            $.assetBreakdown.totalDebtMax -= info.debtMaxDecrease;
        }
    }

    function _withdrawAssets(
        WithdrawInfo memory info,
        mapping(address => AutopoolDebt.DestinationInfo) storage destinationInfo,
        IDestinationVault destVault,
        uint256 dvSharesToBurn
    ) internal returns (WithdrawInfo memory, uint256 pulledAssets, uint256 debtValueBurned) {
        if (dvSharesToBurn > 0) {
            address[] memory tokensBurned;
            uint256[] memory amountsBurned;

            // Destination Vaults always burn the exact amount we instruct them to
            (pulledAssets, tokensBurned, amountsBurned) = destVault.withdrawBaseAsset(dvSharesToBurn, address(this));

            // Calculate the totalDebt we'll need to remove based on the shares we're burning
            // We're rounding up here so take care when actually applying to totalDebt
            debtValueBurned = destinationInfo[address(destVault)].cachedMinDebtValue.mulDiv(
                dvSharesToBurn, destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
            );
            info.debtMinDecrease += debtValueBurned;

            info.debtDecrease += destinationInfo[address(destVault)].cachedDebtValue.mulDiv(
                dvSharesToBurn, destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
            );

            uint256 maxDebtBurned = destinationInfo[address(destVault)].cachedMaxDebtValue.mulDiv(
                dvSharesToBurn, destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
            );
            info.debtMaxDecrease += maxDebtBurned;

            // See if we received a reasonable amount of the base asset back based on the value
            // of the tokens that were burned.
            uint256 totalValueBurned;
            {
                uint256 tokenLen = tokensBurned.length;
                IRootPriceOracle rootPriceOracle = ISystemRegistry(destVault.getSystemRegistry()).rootPriceOracle();
                for (uint256 i = 0; i < tokenLen;) {
                    totalValueBurned += amountsBurned[i]
                        * rootPriceOracle.getPriceInQuote(tokensBurned[i], destVault.baseAsset())
                        / (10 ** IERC20(tokensBurned[i]).decimals());
                    unchecked {
                        ++i;
                    }
                }
            }

            // How much, if any, should be dropping into idle?
            // Anything pulled over debtValueBurned goes to idle, user can't get more than we think its worth.
            // However, if we pulled less than the current value of the tokens we burned, so long as
            // that value is greater than debt min, we need to recoup that as well and put it into idle

            uint256 amountToRecoup;
            if (totalValueBurned > debtValueBurned) {
                // The shares we burned are worth more than we'll be recouping from the debt burn
                // the difference we still need to get
                amountToRecoup = totalValueBurned - debtValueBurned;

                uint256 maxCreditBps = destVault.recoupMaxCredit();
                uint256 gapCredit = maxDebtBurned - debtValueBurned;
                uint256 credit = Math.min(gapCredit, debtValueBurned * maxCreditBps / 10_000);

                if (credit > amountToRecoup) {
                    amountToRecoup = 0;
                } else {
                    amountToRecoup -= credit;
                }
            }

            // This is done regardless of whether we were under valued. User can still only
            // get what we've valued it at.
            if (pulledAssets > debtValueBurned) {
                uint256 overDebtValue = pulledAssets - debtValueBurned;
                info.idleIncrease += overDebtValue;
                pulledAssets -= overDebtValue;

                // Since this is going to idle it goes to satisfy the recoup as well
                if (amountToRecoup > 0) {
                    if (amountToRecoup > overDebtValue) {
                        amountToRecoup -= overDebtValue;
                    } else {
                        amountToRecoup = 0;
                    }
                }
            }

            // If we still have a value we need to recoup it means that the debt range credit
            // as well as what was pulled over the min debt value wasn't enough to cover
            // the under valued burn. Now we have to try and take it from what is going back
            // to the user
            if (amountToRecoup > 0) {
                if (amountToRecoup > pulledAssets) {
                    // Recoup is more than we pulled so we'll have some recoup left over
                    amountToRecoup -= pulledAssets;

                    // Everything that was pulled goes to idle
                    info.idleIncrease += pulledAssets;
                    pulledAssets = 0;

                    // We'll have to try and get the remaining amount from another destination
                    info.remainingRecoup += amountToRecoup;
                } else {
                    // We pulled enough assets to cover the recoup
                    pulledAssets -= amountToRecoup;

                    // Ensure the recoup goes to idle
                    info.idleIncrease += amountToRecoup;
                }
            }
        }

        return (info, pulledAssets, debtValueBurned);
    }

    /// @notice Perform a removal of assets via the redeem path where the shares are the limiting factor.
    /// This means we break out whenever we reach either `assets` retrieved or debt value equivalent to `assets` burned
    function redeem(
        AutopoolState storage $,
        uint256 assets,
        uint256 applicableTotalAssets
    ) public returns (uint256 actualAssets, uint256 actualShares, uint256 debtBurned) {
        WithdrawInfo memory info = _initiateWithdrawInfo(assets, $.assetBreakdown);

        // If not enough funds in idle, then pull what we need from destinations
        bool exhaustedDestinations = false;
        while (info.assetsToPull > 0) {
            IDestinationVault destVault = IDestinationVault($.withdrawalQueue.peekHead());
            if (address(destVault) == address(0)) {
                exhaustedDestinations = true;
                break;
            }

            uint256 dvShares = destVault.balanceOf(address(this));
            uint256 dvSharesToBurn = dvShares;
            {
                // Valuing these shares higher, rounding up, will result in us burning less of them
                // in the event we don't burn all of them. Good thing.
                uint256 dvSharesValue = $.destinationInfo[address(destVault)].cachedMinDebtValue.mulDiv(
                    dvSharesToBurn, $.destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
                );

                // If the dv shares we own are worth more than we need, limit the shares to burn
                // Any extra we get will be dropped into idle
                if (dvSharesValue > info.assetsToPull) {
                    uint256 limitedShares = (dvSharesToBurn * info.assetsToPull) / dvSharesValue;

                    // Final set for the actual shares we'll burn later
                    dvSharesToBurn = limitedShares;
                }
            }

            uint256 pulledAssets;
            uint256 debtValueBurned;
            // Get the base asset back from the Destination. Also performs a check that we aren't receiving
            // poor execution on our swaps based on safe prices
            // slither-disable-next-line unused-return
            (info, pulledAssets, debtValueBurned) = _withdrawAssets(info, $.destinationInfo, destVault, dvSharesToBurn);

            // If we've exhausted all shares we can remove the destination from the withdrawal queue
            // We need to leave it in the debt report queue though so that our destination specific
            // debt tracking values can be updated
            if (dvShares == dvSharesToBurn) {
                $.withdrawalQueue.popAddress(address(destVault));
            }

            info.assetsPulled += pulledAssets;

            // Any deficiency in the amount we received is slippage.
            // There is a round up on debtValueBurned so just making sure it never under flows here
            // _withdrawAssets ensures that pulledAssets is always lte debtValueBurned and we always
            // want to debit the max so we just use debtValueBurned
            if (debtValueBurned > info.assetsToPull) {
                info.assetsToPull = 0;
            } else {
                info.assetsToPull -= debtValueBurned;
            }

            // We either have enough assets, or we've burned the max debt we're allowed
            if (info.assetsToPull == 0) {
                break;
            }

            // If we didn't exhaust all of the shares from the destination it means we
            // assume we will get everything we need from there and everything else is slippage
            if (dvShares != dvSharesToBurn) {
                info.assetsToPull = 0;
                break;
            }
        }

        // See if we can pull the remaining recoup from other destinations we may have pulled from
        if (info.remainingRecoup > 0) {
            if (info.remainingRecoup > info.assetsPulled) {
                info.remainingRecoup -= info.assetsPulled;
                info.idleIncrease += info.assetsPulled;
                info.assetsPulled = 0;
            } else {
                info.assetsPulled -= info.remainingRecoup;
                info.idleIncrease += info.remainingRecoup;
                info.remainingRecoup = 0;
            }
        }

        // We didn't get enough assets from the debt pull
        // See if we can get the rest from idle
        if (info.assetsToPull > 0 && info.currentIdle > 0 && exhaustedDestinations) {
            if (info.assetsToPull < info.currentIdle) {
                info.assetsFromIdle = info.assetsToPull;
            } else {
                info.assetsFromIdle = info.currentIdle;
            }
        }

        debtBurned = info.assetsFromIdle + info.debtMinDecrease;
        actualAssets = info.assetsFromIdle + info.assetsPulled;

        // If we took from idle, and we have remaining assets to recoup
        // we need to put some back in idle
        if (info.remainingRecoup > 0 && info.assetsFromIdle > 0) {
            // We only need to do this if the idle assets can cover the remaining recoup fully because
            // we'll be reverting otherwise
            if (info.assetsFromIdle >= info.remainingRecoup) {
                // We still need to charge for the recoup so we're going to leave it in debtBurned
                // but we'll take it back out of actualAssets so it stays in idle. We need to lower
                // assetsFromIdle as well so that the final numbers get updated too
                actualAssets -= info.remainingRecoup;
                info.assetsFromIdle -= info.remainingRecoup;
                info.remainingRecoup = 0;
            } else {
                // Just updating this number so we get an accurate value in the revert below
                info.remainingRecoup -= info.assetsFromIdle;
            }
        }

        // We took everything we could and still can't cover, time to revert
        if (info.remainingRecoup > 0) {
            revert PositivePriceRecoupNotCovered(info.remainingRecoup);
        }

        actualShares = IAutopool(address(this)).convertToShares(
            debtBurned, applicableTotalAssets, IAutopool(address(this)).totalSupply(), Math.Rounding.Up
        );

        // Subtract what's taken out of idle from totalIdle
        // We may also have some increase to account for it we over pulled
        // or received better execution than we were anticipating
        // slither-disable-next-line events-maths
        $.assetBreakdown.totalIdle = info.currentIdle + info.idleIncrease - info.assetsFromIdle;

        // Save off our various debt numbers
        if (info.debtDecrease > $.assetBreakdown.totalDebt) {
            $.assetBreakdown.totalDebt = 0;
        } else {
            $.assetBreakdown.totalDebt -= info.debtDecrease;
        }

        if (info.debtMinDecrease > info.totalMinDebt) {
            $.assetBreakdown.totalDebtMin = 0;
        } else {
            $.assetBreakdown.totalDebtMin -= info.debtMinDecrease;
        }

        if (info.debtMaxDecrease > $.assetBreakdown.totalDebtMax) {
            $.assetBreakdown.totalDebtMax = 0;
        } else {
            $.assetBreakdown.totalDebtMax -= info.debtMaxDecrease;
        }
    }

    /**
     * @notice Function to complete a withdrawal or redeem.  This runs after shares to be burned and assets to be
     *    transferred are calculated.
     * @param $ Storage related to the calling Autopool
     * @param assets Amount of assets to be transferred to receiver.
     * @param shares Amount of shares to be burned from owner.
     * @param owner Owner of shares, user to burn shares from.
     * @param receiver The receiver of the baseAsset.
     * @param baseAsset Base asset of the Autopool.
     */
    function completeWithdrawal(
        AutopoolState storage $,
        uint256 assets,
        uint256 shares,
        address owner,
        address receiver,
        IERC20 baseAsset
    ) external {
        if (msg.sender != owner) {
            uint256 allowed = IAutopool(address(this)).allowance(owner, msg.sender);
            if (allowed != type(uint256).max) {
                if (shares > allowed) revert AmountExceedsAllowance(shares, allowed);

                unchecked {
                    $.token.approve(owner, msg.sender, allowed - shares);
                }
            }
        }

        $.token.burn(owner, shares);

        uint256 ts = IAutopool(address(this)).totalSupply();

        emit Withdraw(msg.sender, receiver, owner, assets, shares);

        emit Nav($.assetBreakdown.totalIdle, $.assetBreakdown.totalDebt, ts);

        baseAsset.safeTransfer(receiver, assets);
    }

    /**
     * @notice A helper function to get estimates of what would happen on a withdraw or redeem.
     * @dev Reverts all changing state.
     * @param $ Storage related to the calling Autopool.
     * @param previewWithdraw Bool denoting whether to preview a redeem or withdrawal.
     * @param assets Assets to be withdrawn or redeemed.
     * @param applicableTotalAssets Operation dependent assets in the Autopool.
     * @param functionCallEncoded Abi encoded function signature for recursive call.
     * @return assetsAmount Preview of amount of assets to send to receiver.
     * @return sharesAmount Preview of amount of assets to burn from owner.
     */
    function preview(
        AutopoolState storage $,
        bool previewWithdraw,
        uint256 assets,
        uint256 applicableTotalAssets,
        bytes memory functionCallEncoded
    ) external returns (uint256 assetsAmount, uint256 sharesAmount) {
        if (msg.sender != address(this)) {
            // Perform a recursive call the function in `funcCallEncoded`.  This will result in a call back to
            // the Autopool, and then this function. The intention is to reach the "else" block in this function.
            // solhint-disable avoid-low-level-calls
            // slither-disable-next-line missing-zero-check,low-level-calls
            (bool success, bytes memory returnData) = address(this).call(functionCallEncoded);
            // solhint-enable avoid-low-level-calls

            // If the recursive call is successful, it means an unintended code path was taken.
            if (success) {
                revert Errors.UnreachableError();
            }

            bytes4 sharesAmountSig = bytes4(keccak256("SharesAndAssetsReceived(uint256,uint256)"));

            // Extract the error signature (first 4 bytes) from the revert reason.
            bytes4 errorSignature;
            // solhint-disable no-inline-assembly
            assembly {
                errorSignature := mload(add(returnData, 0x20))
            }

            // If the error matches the expected signature, extract the amount from the revert reason and return.
            if (errorSignature == sharesAmountSig) {
                // Extract subsequent bytes for uint256.
                assembly {
                    assetsAmount := mload(add(returnData, 0x24))
                    sharesAmount := mload(add(returnData, 0x44))
                }
            } else {
                // If the error is not the expected one, forward the original revert reason.
                assembly {
                    revert(add(32, returnData), mload(returnData))
                }
            }
            // solhint-enable no-inline-assembly
        }
        // This branch is taken during the recursive call.
        else {
            // Perform the actual withdrawal or redeem logic to compute the amount. This will be reverted to
            // simulate the action.
            uint256 previewAssets;
            uint256 previewShares;
            if (previewWithdraw) {
                (previewAssets, previewShares,) = withdraw($, assets, applicableTotalAssets);
            } else {
                (previewAssets, previewShares,) = redeem($, assets, applicableTotalAssets);
            }

            // Revert with the computed amount as an error.
            revert SharesAndAssetsReceived(previewAssets, previewShares);
        }
    }
}

File 37 of 87 : Pausable.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { Roles } from "src/libs/Roles.sol";
import { Errors } from "src/utils/Errors.sol";
import { ISystemRegistry } from "src/interfaces/ISystemRegistry.sol";
import { IAccessController } from "src/interfaces/security/IAccessController.sol";
import { ISystemSecurity } from "src/interfaces/security/ISystemSecurity.sol";

/**
 * @notice Contract which allows children to implement an emergency stop mechanism that can be trigger
 * by an account that has been granted the EMERGENCY_PAUSER role.
 * Makes available the `whenNotPaused` and `whenPaused` modifiers.
 * Respects a system level pause from the System Security.
 */
abstract contract Pausable {
    IAccessController private immutable _accessController;
    ISystemSecurity private immutable _systemSecurity;

    /// @dev Emitted when the pause is triggered by `account`.
    event Paused(address account);

    /// @dev Emitted when the pause is lifted by `account`.
    event Unpaused(address account);

    error IsPaused();
    error IsNotPaused();

    bool private _paused;

    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    modifier whenPaused() {
        _requirePaused();
        _;
    }

    modifier isPauser() {
        if (!_accessController.hasRole(Roles.EMERGENCY_PAUSER, msg.sender)) {
            revert Errors.AccessDenied();
        }
        _;
    }

    constructor(
        ISystemRegistry systemRegistry
    ) {
        Errors.verifyNotZero(address(systemRegistry), "systemRegistry");

        // Validate the registry is in a state we can use it
        IAccessController accessController = systemRegistry.accessController();
        if (address(accessController) == address(0)) {
            revert Errors.RegistryItemMissing("accessController");
        }
        ISystemSecurity systemSecurity = systemRegistry.systemSecurity();
        if (address(systemSecurity) == address(0)) {
            revert Errors.RegistryItemMissing("systemSecurity");
        }

        _accessController = accessController;
        _systemSecurity = systemSecurity;
    }

    /// @notice Returns true if the contract or system is paused, and false otherwise.
    function paused() public virtual returns (bool) {
        return _paused || _systemSecurity.isSystemPaused();
    }

    /// @notice Pauses the contract
    /// @dev Reverts if already paused or not EMERGENCY_PAUSER role
    function pause() external virtual isPauser {
        if (_paused) {
            revert IsPaused();
        }

        _paused = true;

        emit Paused(msg.sender);
    }

    /// @notice Unpauses the contract
    /// @dev Reverts if not paused or not EMERGENCY_PAUSER role
    function unpause() external virtual isPauser {
        if (!_paused) {
            revert IsNotPaused();
        }

        _paused = false;

        emit Unpaused(msg.sender);
    }

    /// @dev Throws if the contract or system is paused.
    function _requireNotPaused() internal virtual {
        if (paused()) {
            revert IsPaused();
        }
    }

    /// @dev Throws if the contract or system is not paused.
    function _requirePaused() internal virtual {
        if (!paused()) {
            revert IsNotPaused();
        }
    }
}

File 38 of 87 : VaultTypes.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

library VaultTypes {
    bytes32 public constant LST = keccak256("LST");
    bytes32 public constant GENERAL_V1 = keccak256("GENERAL_V1");
}

File 39 of 87 : NonReentrantUpgradeable.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2025 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { Initializable } from "openzeppelin-contracts/proxy/utils/Initializable.sol";

/// @title Copy of OZ's ReentrancyGuard with a read only variant added
abstract contract NonReentrantUpgradeable is Initializable {
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    error ReentrancyGuardReentrantCall();

    function initialize() internal onlyInitializing {
        _status = NOT_ENTERED;
    }

    modifier nonReentrantReadOnly() {
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }
        _;
    }

    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // 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 {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = NOT_ENTERED;
    }
}

File 40 of 87 : IAutopool.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { AutopoolDebt } from "src/vault/libs/AutopoolDebt.sol";
import { IERC4626 } from "src/interfaces/vault/IERC4626.sol";
import { Math } from "openzeppelin-contracts/utils/math/Math.sol";
import { IAutopoolStrategy } from "src/interfaces/strategy/IAutopoolStrategy.sol";
import { IMainRewarder } from "src/interfaces/rewarders/IMainRewarder.sol";
import { IERC20Permit } from "openzeppelin-contracts/token/ERC20/extensions/draft-IERC20Permit.sol";

interface IAutopool is IERC4626, IERC20Permit {
    enum VaultShutdownStatus {
        Active,
        Deprecated,
        Exploit
    }

    /// @param unlockPeriodInSeconds Time it takes for profit to unlock in seconds
    /// @param fullProfitUnlockTime Time at which all profit will have been unlocked
    /// @param lastProfitUnlockTime Last time profits were unlocked
    /// @param profitUnlockRate Per second rate at which profit shares unlocks. Rate when calculated is denominated in
    /// MAX_BPS_PROFIT. TODO: Get into uint112
    struct ProfitUnlockSettings {
        uint48 unlockPeriodInSeconds;
        uint48 fullProfitUnlockTime;
        uint48 lastProfitUnlockTime;
        uint256 profitUnlockRate;
    }

    /// @param feeSink Where claimed fees are sent
    /// @param totalAssetsHighMark The last totalAssets amount we took fees at
    /// @param totalAssetsHighMarkTimestamp The last timestamp we updated the high water mark
    /// @param lastPeriodicFeeTake Timestamp of when the last periodic fee was taken.
    /// @param periodicFeeSink Address that receives periodic fee.
    /// @param periodicFeeBps Current periodic fee.  100% == 10000.
    /// @param streamingFeeBps Current streaming fee taken on profit. 100% == 10000
    /// @param navPerShareLastFeeMark The last nav/share height we took fees at
    /// @param navPerShareLastFeeMarkTimestamp The last timestamp we took fees at
    /// @param rebalanceFeeHighWaterMarkEnabled Returns whether the nav/share high water mark is enabled for the
    /// rebalance fee
    struct AutopoolFeeSettings {
        address feeSink;
        uint256 totalAssetsHighMark;
        uint256 totalAssetsHighMarkTimestamp;
        uint256 lastPeriodicFeeTake;
        address periodicFeeSink;
        uint256 periodicFeeBps;
        uint256 streamingFeeBps;
        uint256 navPerShareLastFeeMark;
        uint256 navPerShareLastFeeMarkTimestamp;
        bool rebalanceFeeHighWaterMarkEnabled;
    }

    /// @param totalIdle The amount of baseAsset deposited into the contract pending deployment
    /// @param totalDebt The current (though cached) value of assets we've deployed
    /// @param totalDebtMin The current (though cached) value of assets we use for valuing during deposits
    /// @param totalDebtMax The current (though cached) value of assets we use for valuing during withdrawals
    struct AssetBreakdown {
        uint256 totalIdle;
        uint256 totalDebt;
        uint256 totalDebtMin;
        uint256 totalDebtMax;
    }

    enum TotalAssetPurpose {
        Global,
        Deposit,
        Withdraw
    }

    /* ******************************** */
    /*      Events                      */
    /* ******************************** */

    // Autopool4626

    // event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
    event Nav(uint256 idle, uint256 debt, uint256 totalSupply);
    event TokensRecovered(address[] tokens, uint256[] amounts, address[] destinations);
    event Shutdown(IAutopool.VaultShutdownStatus reason);
    event RewarderSet(address newRewarder, address oldRewarder);
    event SymbolAndDescSet(string symbol, string desc);

    // AutopoolDebt

    event DestinationDebtReporting(
        address destination, AutopoolDebt.IdleDebtUpdates debtInfo, uint256 claimed, uint256 claimGasUsed
    );
    event NewNavShareFeeMark(uint256 navPerShare, uint256 timestamp);
    // event Nav(uint256 idle, uint256 debt, uint256 totalSupply);
    // event Withdraw(
    //     address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
    // );

    // AutopoolDestinations

    event DestinationVaultAdded(address destination);
    event DestinationVaultRemoved(address destination);
    event WithdrawalQueueSet(address[] destinations);
    event AddedToRemovalQueue(address destination);
    event RemovedFromRemovalQueue(address destination);

    // AutopoolFees

    event FeeCollected(uint256 fees, address feeSink, uint256 mintedShares, uint256 profit, uint256 totalAssets);
    event PeriodicFeeCollected(uint256 fees, address feeSink, uint256 mintedShares);
    // event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
    event PeriodicFeeSet(uint256 newFee);
    event PeriodicFeeSinkSet(address newPeriodicFeeSink);
    event LastPeriodicFeeTakeSet(uint256 lastPeriodicFeeTake);
    event RebalanceFeeHighWaterMarkEnabledSet(bool enabled);
    // event NewNavShareFeeMark(uint256 navPerShare, uint256 timestamp);
    event NewTotalAssetsHighWatermark(uint256 assets, uint256 timestamp);
    event StreamingFeeSet(uint256 newFee);
    event FeeSinkSet(address newFeeSink);
    event NewProfitUnlockTime(uint48 timeSeconds);

    // AutopoolToken

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

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

    /* ******************************** */
    /*      Errors                      */
    /* ******************************** */

    // Autopool

    error InvalidDecimals();
    error NavOpsInProgress();
    error NavDecreased(uint256 oldNav, uint256 newNav);
    error ValueSharesMismatch(uint256 value, uint256 shares);
    error ERC4626MintExceedsMax(uint256 shares, uint256 maxMint);
    error ERC4626DepositExceedsMax(uint256 assets, uint256 maxDeposit);
    error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max);
    error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max);

    // Autopool4626

    error InvalidTotalAssetPurpose();
    error InvalidShutdownStatus(IAutopool.VaultShutdownStatus status);
    error RecoveryFailed();

    // AutopoolDestination

    error BaseAssetMismatch(address destinationVault);

    // AutopoolDebt

    error VaultShutdown();
    error WithdrawShareCalcInvalid(uint256 currentShares, uint256 cachedShares);
    //error RebalanceFailed(string message);
    error InvalidPrices();
    //error InvalidTotalAssetPurpose();
    error InvalidDestination(address destination);
    error TooFewAssets(uint256 requested, uint256 actual);
    error SharesAndAssetsReceived(uint256 assets, uint256 shares);
    error AmountExceedsAllowance(uint256 shares, uint256 allowed);
    error PositivePriceRecoupNotCovered(uint256 remaining);
    error InsufficientAssets(address asset);
    error RebalanceDestinationUnderlyerMismatch(address destination, address trueUnderlyer, address providedUnderlyer);
    error OnlyRebalanceToIdleAvailable();
    error UnregisteredDestination(address dest);
    error RebalanceDestinationsMatch();

    // AutopoolFees

    error InvalidFee(uint256 newFee);
    error AlreadySet();
    error DebtReportingStale();

    // AutopoolStrategyHooks

    /// @notice Fires when are at the maximum number of configured hooks for a function
    error MaxHooksSet();

    /// @notice Fires when a hook is already registered for a function
    error HookAlreadySet(address hook, uint256 fn);

    /// @notice Fires on removal when a hook doesn't exist
    error HookNotSet(address hook);

    /// @notice Fires on removal when a function is supposed to be registered but isn't
    error FunctionNotSet(address hook, uint256 fn);

    /// @notice Fires when a hook execution fails
    error HookExecutionFailed(address hook, bytes underlyingError);

    // AutopoolToken

    /// @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
    /// @param sender Address whose tokens are being transferred.
    /// @param balance Current balance for the interacting account.
    /// @param needed Minimum amount required to perform a transfer.
    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);

    /// @dev Indicates a failure with the token `sender`. Used in transfers.
    /// @param sender Address whose tokens are being transferred.
    error ERC20InvalidSender(address sender);

    /// @dev Indicates a failure with the token `receiver`. Used in transfers.
    /// @param receiver Address to which tokens are being transferred.
    error ERC20InvalidReceiver(address receiver);

    /// @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
    ///@param spender Address that may be allowed to operate on tokens without being their owner.
    /// @param allowance Amount of tokens a `spender` is allowed to operate with.
    ///@param needed Minimum amount required to perform a transfer.
    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);

    /// @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
    /// @param approver Address initiating an approval operation.
    error ERC20InvalidApprover(address approver);

    /// @dev Indicates a failure with the `spender` to be approved. Used in approvals.
    /// @param spender Address that may be allowed to operate on tokens without being their owner.
    error ERC20InvalidSpender(address spender);

    /// @dev Permit deadline has expired.
    error ERC2612ExpiredSignature(uint256 deadline);

    /// @dev Mismatched signature.
    error ERC2612InvalidSigner(address signer, address owner);

    /// @dev The nonce used for an `account` is not the expected current nonce.
    error InvalidAccountNonce(address account, uint256 currentNonce);

    /// @notice A full unit of this pool
    // solhint-disable-next-line func-name-mixedcase
    function ONE() external view returns (uint256);

    /// @notice Amount to pad scaling operations by
    function decimalPad() external view returns (uint256);

    /// @notice Query the type of vault
    function vaultType() external view returns (bytes32);

    /// @notice Strategy governing the pools rebalances
    function autoPoolStrategy() external view returns (IAutopoolStrategy);

    /// @notice Allow token recoverer to collect dust / unintended transfers (non-tracked assets only)
    function recover(address[] calldata tokens, uint256[] calldata amounts, address[] calldata destinations) external;

    /// @notice Set the order of destination vaults used for withdrawals
    // NOTE: will be done going directly to strategy (IStrategy) vault points to.
    //       How it'll delegate is still being decided
    // function setWithdrawalQueue(address[] calldata destinations) external;

    /// @notice Get a list of destination vaults with pending assets to clear out
    function getRemovalQueue() external view returns (address[] memory);

    function getFeeSettings() external view returns (AutopoolFeeSettings memory);

    /// @notice Initiate the shutdown procedures for this vault
    function shutdown(
        VaultShutdownStatus reason
    ) external;

    /// @notice True if the vault has been shutdown
    function isShutdown() external view returns (bool);

    /// @notice Returns the reason for shutdown (or `Active` if not shutdown)
    function shutdownStatus() external view returns (VaultShutdownStatus);

    /// @notice gets the list of supported destination vaults for the Autopool/Strategy
    /// @return _destinations List of supported destination vaults
    function getDestinations() external view returns (address[] memory _destinations);

    function convertToShares(
        uint256 assets,
        uint256 totalAssetsForPurpose,
        uint256 supply,
        Math.Rounding rounding
    ) external view returns (uint256 shares);

    function convertToAssets(
        uint256 shares,
        uint256 totalAssetsForPurpose,
        uint256 supply,
        Math.Rounding rounding
    ) external view returns (uint256 assets);

    function totalAssets(
        TotalAssetPurpose purpose
    ) external view returns (uint256);

    function getAssetBreakdown() external view returns (AssetBreakdown memory);

    /// @notice get a destinations last reported debt value
    /// @param destVault the address of the target destination
    /// @return destinations last reported debt value
    function getDestinationInfo(
        address destVault
    ) external view returns (AutopoolDebt.DestinationInfo memory);

    /// @notice check if a destination is registered with the vault
    function isDestinationRegistered(
        address destination
    ) external view returns (bool);

    /// @notice get if a destinationVault is queued for removal by the AutopoolETH
    function isDestinationQueuedForRemoval(
        address destination
    ) external view returns (bool);

    /// @notice Returns instance of vault rewarder.
    function rewarder() external view returns (IMainRewarder);

    /// @notice Returns boolean telling whether address passed in is past rewarder.
    function isPastRewarder(
        address _pastRewarder
    ) external view returns (bool);
}

File 41 of 87 : AutopoolFees.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity ^0.8.24;

import { IAutopool } from "src/interfaces/vault/IAutopool.sol";
import { Math } from "openzeppelin-contracts/utils/math/Math.sol";
import { AutopoolToken } from "src/vault/libs/AutopoolToken.sol";
import { AutopoolState } from "src/vault/libs/AutopoolState.sol";

library AutopoolFees {
    using Math for uint256;
    using AutopoolToken for AutopoolToken.TokenData;

    /// @notice Profit denomination
    uint256 public constant MAX_BPS_PROFIT = 1_000_000_000;

    /// @notice 100% == 10000
    uint256 public constant FEE_DIVISOR = 10_000;

    /// @notice Max periodic fee, 10%.  100% = 10_000.
    uint256 public constant MAX_PERIODIC_FEE_BPS = 1000;

    uint256 public constant SECONDS_IN_YEAR = 365 days;

    event FeeCollected(uint256 fees, address feeSink, uint256 mintedShares, uint256 profit, uint256 totalAssets);
    event PeriodicFeeCollected(uint256 fees, address feeSink, uint256 mintedShares);
    event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
    event PeriodicFeeSet(uint256 newFee);
    event PeriodicFeeSinkSet(address newPeriodicFeeSink);
    event LastPeriodicFeeTakeSet(uint256 lastPeriodicFeeTake);
    event RebalanceFeeHighWaterMarkEnabledSet(bool enabled);
    event NewNavShareFeeMark(uint256 navPerShare, uint256 timestamp);
    event NewTotalAssetsHighWatermark(uint256 assets, uint256 timestamp);
    event StreamingFeeSet(uint256 newFee);
    event FeeSinkSet(address newFeeSink);
    event NewProfitUnlockTime(uint48 timeSeconds);

    error InvalidFee(uint256 newFee);
    error AlreadySet();
    error DebtReportingStale();

    /// @notice Returns the amount of unlocked profit shares that will be burned
    function unlockedShares(
        AutopoolState storage $
    ) public view returns (uint256 shares) {
        uint256 fullTime = $.profitUnlockSettings.fullProfitUnlockTime;
        if (fullTime > block.timestamp) {
            shares = $.profitUnlockSettings.profitUnlockRate
                * (block.timestamp - $.profitUnlockSettings.lastProfitUnlockTime) / MAX_BPS_PROFIT;
        } else if (fullTime != 0) {
            shares = $.token.balances[address(this)];
        }
    }

    function initializeFeeSettings(
        AutopoolState storage $
    ) external {
        uint256 timestamp = block.timestamp;

        // Stops fees from being able to be claimed before init timestamp.
        $.feeSettings.lastPeriodicFeeTake = timestamp;
        $.feeSettings.navPerShareLastFeeMark = FEE_DIVISOR;
        $.feeSettings.navPerShareLastFeeMarkTimestamp = timestamp;

        emit LastPeriodicFeeTakeSet(timestamp);
    }

    function burnUnlockedShares(
        AutopoolState storage $
    ) external {
        uint256 shares = unlockedShares($);
        if (shares == 0) {
            return;
        }
        if ($.profitUnlockSettings.fullProfitUnlockTime > block.timestamp) {
            $.profitUnlockSettings.lastProfitUnlockTime = uint48(block.timestamp);
        }
        $.token.burn(address(this), shares);
    }

    function _calculateEffectiveNavPerShareLastFeeMark(
        IAutopool.AutopoolFeeSettings storage settings,
        uint256 currentBlock,
        uint256 currentNavPerShare,
        uint256 aumCurrent
    ) private view returns (uint256) {
        uint256 workingHigh = settings.navPerShareLastFeeMark;

        if (workingHigh == 0) {
            // If we got 0, we shouldn't increase it
            return 0;
        }

        if (!settings.rebalanceFeeHighWaterMarkEnabled) {
            // No calculations or checks to do in this case
            return workingHigh;
        }

        uint256 daysSinceLastFeeEarned = (currentBlock - settings.navPerShareLastFeeMarkTimestamp) / 60 / 60 / 24;

        if (daysSinceLastFeeEarned > 600) {
            return currentNavPerShare;
        }
        if (daysSinceLastFeeEarned > 60 && daysSinceLastFeeEarned <= 600) {
            uint8 decimals = IAutopool(address(this)).decimals();

            uint256 one = 10 ** decimals;
            uint256 aumHighMark = settings.totalAssetsHighMark;

            // AUM_min = min(AUM_high, AUM_current)
            uint256 minAssets = aumCurrent < aumHighMark ? aumCurrent : aumHighMark;

            // AUM_max = max(AUM_high, AUM_current);
            uint256 maxAssets = aumCurrent > aumHighMark ? aumCurrent : aumHighMark;

            /// 0.999 * (AUM_min / AUM_max)
            // dividing by `one` because we need end up with a number in the 100's wei range
            uint256 g1 = ((999 * minAssets * one) / (maxAssets * one));

            /// 0.99 * (1 - AUM_min / AUM_max)
            // dividing by `10 ** (decimals() - 1)` because we need to divide 100 out for our % and then
            // we want to end up with a number in the 10's wei range
            uint256 g2 = (99 * (one - (minAssets * one / maxAssets))) / 10 ** (decimals - 1);

            uint256 gamma = g1 + g2;

            uint256 daysDiff = daysSinceLastFeeEarned - 60;
            for (uint256 i = 0; i < daysDiff / 25; ++i) {
                // slither-disable-next-line divide-before-multiply
                workingHigh = workingHigh * (gamma ** 25 / 1e72) / 1000;
            }
            // slither-disable-next-line weak-prng
            for (uint256 i = 0; i < daysDiff % 25; ++i) {
                // slither-disable-next-line divide-before-multiply
                workingHigh = workingHigh * gamma / 1000;
            }
        }
        return workingHigh;
    }

    function collectFees(
        AutopoolState storage $,
        uint256 totalAssets,
        uint256 currentTotalSupply,
        bool collectPeriodicFees
    ) external returns (uint256) {
        // If there's no supply then there should be no assets and so nothing
        // to actually take fees on
        // slither-disable-next-line incorrect-equality
        if (currentTotalSupply == 0) {
            return 0;
        }

        uint256 decimalPad = IAutopool(address(this)).decimalPad();

        // slither-disable-start timestamp
        if (collectPeriodicFees) {
            address periodicFeeSink = $.feeSettings.periodicFeeSink;
            uint256 periodicFeeBps = $.feeSettings.periodicFeeBps;
            // If there is a periodic fee and fee sink set, take the fee.
            if (periodicFeeBps > 0 && periodicFeeSink != address(0)) {
                uint256 durationSinceLastPeriodicFeeTake = block.timestamp - $.feeSettings.lastPeriodicFeeTake;
                uint256 timeAdjustedBps = durationSinceLastPeriodicFeeTake.mulDiv(
                    periodicFeeBps * FEE_DIVISOR, SECONDS_IN_YEAR, Math.Rounding.Up
                );

                uint256 periodicShares =
                    _collectPeriodicFees(periodicFeeSink, timeAdjustedBps, currentTotalSupply, totalAssets);

                currentTotalSupply += periodicShares;
                $.token.mint(periodicFeeSink, periodicShares);
            }

            // Needs to be kept up to date so if a fee is suddenly turned on a large part of assets do not get
            // claimed as fees.
            $.feeSettings.lastPeriodicFeeTake = block.timestamp;
            emit LastPeriodicFeeTakeSet(block.timestamp);
        }
        // slither-disable-end timestamp
        uint256 currentNavPerShare = (totalAssets * decimalPad * FEE_DIVISOR) / currentTotalSupply;

        // If the high mark is disabled then this just returns the `navPerShareLastFeeMark`
        // Otherwise, it'll check if it needs to decay
        uint256 effectiveNavPerShareLastFeeMark =
            _calculateEffectiveNavPerShareLastFeeMark($.feeSettings, block.timestamp, currentNavPerShare, totalAssets);

        if (currentNavPerShare > effectiveNavPerShareLastFeeMark) {
            // Even if we aren't going to take the fee (haven't set a sink)
            // We still want to calculate so we can emit for off-chain analysis
            uint256 profit = (currentNavPerShare - effectiveNavPerShareLastFeeMark).mulDiv(
                currentTotalSupply, decimalPad, Math.Rounding.Up
            );
            uint256 fees = profit.mulDiv($.feeSettings.streamingFeeBps, (FEE_DIVISOR ** 2), Math.Rounding.Up);

            if (fees > 0) {
                currentTotalSupply = _mintStreamingFee($, fees, profit, currentTotalSupply, totalAssets);
                currentNavPerShare = (totalAssets * decimalPad * FEE_DIVISOR) / currentTotalSupply;
            }
        }

        // Two situations we're covering here
        //   1. If the high mark is disabled then we just always need to know the last
        //      time we evaluated fees so we can catch any run up. i.e. the `navPerShareLastFeeMark`
        //      can go down
        //   2. When the high mark is enabled, then we only want to set `navPerShareLastFeeMark`
        //      when it is greater than the last time we captured fees (or would have)
        if (currentNavPerShare >= effectiveNavPerShareLastFeeMark || !$.feeSettings.rebalanceFeeHighWaterMarkEnabled) {
            $.feeSettings.navPerShareLastFeeMark = currentNavPerShare;
            $.feeSettings.navPerShareLastFeeMarkTimestamp = block.timestamp;
            emit NewNavShareFeeMark(currentNavPerShare, block.timestamp);
        }

        // Set our new high water mark for totalAssets, regardless if we took fees
        if ($.feeSettings.totalAssetsHighMark < totalAssets) {
            $.feeSettings.totalAssetsHighMark = totalAssets;
            $.feeSettings.totalAssetsHighMarkTimestamp = block.timestamp;
            emit NewTotalAssetsHighWatermark(
                $.feeSettings.totalAssetsHighMark, $.feeSettings.totalAssetsHighMarkTimestamp
            );
        }

        return currentTotalSupply;
    }

    function _mintStreamingFee(
        AutopoolState storage $,
        uint256 fees,
        uint256 profit,
        uint256 currentTotalSupply,
        uint256 totalAssets
    ) private returns (uint256) {
        address sink = $.feeSettings.feeSink;
        if (sink == address(0)) {
            return currentTotalSupply;
        }

        uint256 streamingFeeShares =
            _calculateSharesToMintFeeCollection($.feeSettings.streamingFeeBps, profit, totalAssets, currentTotalSupply);
        $.token.mint(sink, streamingFeeShares);
        currentTotalSupply += streamingFeeShares;

        emit Deposit(address(this), sink, 0, streamingFeeShares);
        emit FeeCollected(fees, sink, streamingFeeShares, profit, totalAssets);

        return currentTotalSupply;
    }

    /// @dev Collects periodic fees.
    function _collectPeriodicFees(
        address periodicSink,
        uint256 timeAdjustedFeeBps,
        uint256 currentTotalSupply,
        uint256 totalAssets
    ) private returns (uint256 newShares) {
        newShares =
            _calculateSharesToMintFeeCollection(timeAdjustedFeeBps, totalAssets, totalAssets, currentTotalSupply);

        // Fee in assets that we are taking.
        uint256 fees = (timeAdjustedFeeBps * totalAssets / FEE_DIVISOR).ceilDiv(FEE_DIVISOR);
        emit Deposit(address(this), periodicSink, 0, newShares);
        emit PeriodicFeeCollected(fees, periodicSink, newShares);

        return newShares;
    }

    function _calculateSharesToMintFeeCollection(
        uint256 feeBps,
        uint256 amountForFee,
        uint256 totalAssets,
        uint256 totalSupply
    ) private pure returns (uint256 toMint) {
        // Gas savings, this is used twice.
        uint256 feeTotalAssets = feeBps * amountForFee / FEE_DIVISOR;

        // Separate from other mints as normal share mint is round down
        // Mints shares taking into account the dilution so we end up with the expected amount
        // `feeBps` is padded by FEE_DIVISOR when taking periodic fee
        // `amountForFee` is padded by FEE_DIVISOR when taking streaming fee
        toMint =
            Math.mulDiv(feeTotalAssets, totalSupply, (totalAssets * FEE_DIVISOR) - (feeTotalAssets), Math.Rounding.Up);
    }

    /// @dev If set to 0, existing shares will unlock immediately and increase nav/share. This is intentional
    function setProfitUnlockPeriod(AutopoolState storage $, uint48 newUnlockPeriodInSeconds) external {
        $.profitUnlockSettings.unlockPeriodInSeconds = newUnlockPeriodInSeconds;

        // If we are turning off the unlock, setting it to 0, then
        // unlock all existing shares
        if (newUnlockPeriodInSeconds == 0) {
            uint256 currentShares = $.token.balances[address(this)];
            if (currentShares > 0) {
                $.profitUnlockSettings.lastProfitUnlockTime = uint48(block.timestamp);
                $.token.burn(address(this), currentShares);
            }

            // Reset vars so old values aren't used during a subsequent lockup
            $.profitUnlockSettings.fullProfitUnlockTime = 0;
            $.profitUnlockSettings.profitUnlockRate = 0;
        }

        emit NewProfitUnlockTime(newUnlockPeriodInSeconds);
    }

    function calculateProfitLocking(
        IAutopool.ProfitUnlockSettings storage settings,
        AutopoolToken.TokenData storage token,
        uint256 feeShares,
        uint256 newTotalAssets,
        uint256 startTotalAssets,
        uint256 startTotalSupply,
        uint256 previousLockShares
    ) external returns (uint256) {
        uint256 unlockPeriod = settings.unlockPeriodInSeconds;

        // If there were existing shares and we set the unlock period to 0 they are immediately unlocked
        // so we don't have to worry about existing shares here. And if the period is 0 then we
        // won't be locking any new shares
        if (unlockPeriod == 0 || startTotalAssets == 0) {
            return startTotalSupply;
        }

        uint256 newLockShares = 0;
        uint256 previousLockToBurn = 0;
        uint256 effectiveTs = startTotalSupply;

        // The total supply we would need to not see a change in nav/share
        uint256 targetTotalSupply = newTotalAssets * (effectiveTs - feeShares) / startTotalAssets;

        if (effectiveTs > targetTotalSupply) {
            // Our actual total supply is greater than our target.
            // This means we would see a decrease in nav/share
            // See if we can burn any profit shares to offset that
            if (previousLockShares > 0) {
                uint256 diff = effectiveTs - targetTotalSupply;
                if (previousLockShares >= diff) {
                    previousLockToBurn = diff;
                    effectiveTs -= diff;
                } else {
                    previousLockToBurn = previousLockShares;
                    effectiveTs -= previousLockShares;
                }
            }
        }

        if (targetTotalSupply > effectiveTs) {
            // Our actual total supply is less than our target.
            // This means we would see an increase in nav/share (due to gains) which we can't allow
            // We need to mint shares to the vault to offset
            newLockShares = targetTotalSupply - effectiveTs;
            effectiveTs += newLockShares;
        }

        // We know how many shares should be locked at this point
        // Mint or burn what we need to match if necessary
        uint256 totalLockShares = previousLockShares - previousLockToBurn + newLockShares;
        if (totalLockShares > previousLockShares) {
            uint256 mintAmount = totalLockShares - previousLockShares;
            token.mint(address(this), mintAmount);
            startTotalSupply += mintAmount;
        } else if (totalLockShares < previousLockShares) {
            uint256 burnAmount = previousLockShares - totalLockShares;
            token.burn(address(this), burnAmount);
            startTotalSupply -= burnAmount;
        }

        // If we're going to end up with no profit shares, zero the rate
        // We don't need to 0 the other timing vars if we just zero the rate
        if (totalLockShares == 0) {
            settings.profitUnlockRate = 0;
        }

        // We have shares and they are going to unlocked later
        if (totalLockShares > 0 && unlockPeriod > 0) {
            _updateProfitUnlockTimings(
                settings, unlockPeriod, previousLockToBurn, previousLockShares, newLockShares, totalLockShares
            );
        }

        return startTotalSupply;
    }

    function _updateProfitUnlockTimings(
        IAutopool.ProfitUnlockSettings storage settings,
        uint256 unlockPeriod,
        uint256 previousLockToBurn,
        uint256 previousLockShares,
        uint256 newLockShares,
        uint256 totalLockShares
    ) private {
        uint256 previousLockTime;
        uint256 fullUnlockTime = settings.fullProfitUnlockTime;

        // Determine how much time is left for the remaining previous profit shares
        if (fullUnlockTime > block.timestamp) {
            previousLockTime = (previousLockShares - previousLockToBurn) * (fullUnlockTime - block.timestamp);
        }

        // Amount of time it will take to unlock all shares, weighted avg over current and new shares
        uint256 newUnlockPeriod = (previousLockTime + newLockShares * unlockPeriod) / totalLockShares;

        if (newUnlockPeriod == 0) {
            settings.profitUnlockRate = 0;
        } else {
            // Rate at which totalLockShares will unlock
            settings.profitUnlockRate = totalLockShares * MAX_BPS_PROFIT / newUnlockPeriod;
        }

        // Time the full of amount of totalLockShares will be unlocked
        settings.fullProfitUnlockTime = uint48(block.timestamp + newUnlockPeriod);
        settings.lastProfitUnlockTime = uint48(block.timestamp);
    }

    /// @notice Enable or disable the high water mark on the rebalance fee
    /// @dev Will revert if set to the same value
    function setRebalanceFeeHighWaterMarkEnabled(
        IAutopool.AutopoolFeeSettings storage feeSettings,
        bool enabled
    ) external {
        if (feeSettings.rebalanceFeeHighWaterMarkEnabled == enabled) {
            revert AlreadySet();
        }

        feeSettings.rebalanceFeeHighWaterMarkEnabled = enabled;

        emit RebalanceFeeHighWaterMarkEnabledSet(enabled);
    }

    /// @notice Set the fee that will be taken when profit is realized
    /// @dev Resets the high water to current value
    /// @param $ Storage related to the calling Autopool.
    /// @param fee Percent. 100% == 10000
    /// @param oldestDebtReporting Debt reporting timestamp to be checked
    function setStreamingFeeBps(AutopoolState storage $, uint256 fee, uint256 oldestDebtReporting) external {
        if (fee >= FEE_DIVISOR) {
            revert InvalidFee(fee);
        }

        _checkLastDebtReportingTime(oldestDebtReporting, $.debtReportQueue.size);

        IAutopool vault = IAutopool(address(this));

        $.feeSettings.streamingFeeBps = fee;

        // Set the high mark when we change the fee so we aren't able to go farther back in
        // time than one debt reporting and claim fee's against past profits
        uint256 ts = vault.totalSupply();
        if (ts > 0) {
            uint256 ta = vault.totalAssets();
            if (ta > 0) {
                $.feeSettings.navPerShareLastFeeMark = (ta * vault.decimalPad() * FEE_DIVISOR) / ts;
            } else {
                $.feeSettings.navPerShareLastFeeMark = FEE_DIVISOR;
            }
        }
        emit StreamingFeeSet(fee);
    }

    /// @notice Set the periodic fee taken.
    /// @dev Zero is allowed, no fee taken.
    /// @param $ Storage related to the calling Autopool.
    /// @param fee Fee to update periodic fee to.
    /// @param oldestDebtReporting Debt reporting timestamp to be checked
    function setPeriodicFeeBps(AutopoolState storage $, uint256 fee, uint256 oldestDebtReporting) external {
        if (fee > MAX_PERIODIC_FEE_BPS) {
            revert InvalidFee(fee);
        }

        _checkLastDebtReportingTime(oldestDebtReporting, $.debtReportQueue.size);

        // Fee checked to fit into uint16 above, able to be wrapped without safe cast here.
        emit PeriodicFeeSet(fee);
        $.feeSettings.periodicFeeBps = uint16(fee);
    }

    /// @notice Set the address that will receive fees
    /// @param newFeeSink Address that will receive fees
    function setFeeSink(IAutopool.AutopoolFeeSettings storage feeSettings, address newFeeSink) external {
        emit FeeSinkSet(newFeeSink);

        // Zero is valid. One way to disable taking fees
        // slither-disable-next-line missing-zero-check
        feeSettings.feeSink = newFeeSink;
    }

    /// @notice Sets the address that will receive periodic fees.
    /// @dev Zero address allowable.  Disables fees.
    /// @param newPeriodicFeeSink New periodic fee address.
    function setPeriodicFeeSink(
        IAutopool.AutopoolFeeSettings storage feeSettings,
        address newPeriodicFeeSink
    ) external {
        emit PeriodicFeeSinkSet(newPeriodicFeeSink);

        // slither-disable-next-line missing-zero-check
        feeSettings.periodicFeeSink = newPeriodicFeeSink;
    }

    function _checkLastDebtReportingTime(uint256 oldestDebtReporting, uint256 debtReportQueueLength) private view {
        if (debtReportQueueLength > 0 && oldestDebtReporting < block.timestamp - 10 minutes) {
            revert DebtReportingStale();
        }
    }
}

File 42 of 87 : AutopoolToken.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity ^0.8.24;

import { ECDSA } from "openzeppelin-contracts/utils/cryptography/ECDSA.sol";
import { IERC20Permit } from "openzeppelin-contracts/token/ERC20/extensions/draft-IERC20Permit.sol";

/// @notice ERC20 token functionality converted into a library. Based on OZ's v5
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.1/contracts/token/ERC20/ERC20.sol
library AutopoolToken {
    struct TokenData {
        /// @notice Token balances
        /// @dev account => balance
        mapping(address => uint256) balances;
        /// @notice Account spender allowances
        /// @dev account => spender => allowance
        mapping(address => mapping(address => uint256)) allowances;
        /// @notice Total supply of the pool. Be careful when using this directly from the struct. The pool itself
        /// modifies this number based on unlocked profited shares
        uint256 totalSupply;
        /// @notice ERC20 Permit nonces
        /// @dev account -> nonce. Exposed via `nonces(owner)`
        mapping(address => uint256) nonces;
    }

    /// @notice EIP2612 permit type hash
    bytes32 public constant PERMIT_TYPEHASH =
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

    /// @notice EIP712 domain type hash
    bytes32 public constant TYPE_HASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");

    /// @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
    /// @param sender Address whose tokens are being transferred.
    /// @param balance Current balance for the interacting account.
    /// @param needed Minimum amount required to perform a transfer.
    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);

    /// @dev Indicates a failure with the token `sender`. Used in transfers.
    /// @param sender Address whose tokens are being transferred.
    error ERC20InvalidSender(address sender);

    /// @dev Indicates a failure with the token `receiver`. Used in transfers.
    /// @param receiver Address to which tokens are being transferred.
    error ERC20InvalidReceiver(address receiver);

    /// @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
    ///@param spender Address that may be allowed to operate on tokens without being their owner.
    /// @param allowance Amount of tokens a `spender` is allowed to operate with.
    ///@param needed Minimum amount required to perform a transfer.
    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);

    /// @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
    /// @param approver Address initiating an approval operation.
    error ERC20InvalidApprover(address approver);

    /// @dev Indicates a failure with the `spender` to be approved. Used in approvals.
    /// @param spender Address that may be allowed to operate on tokens without being their owner.
    error ERC20InvalidSpender(address spender);

    /// @dev Permit deadline has expired.
    error ERC2612ExpiredSignature(uint256 deadline);
    /// @dev Mismatched signature.
    error ERC2612InvalidSigner(address signer, address owner);
    /// @dev The nonce used for an `account` is not the expected current nonce.
    error InvalidAccountNonce(address account, uint256 currentNonce);

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

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

    /// @dev Sets a `value` amount of tokens as the allowance of `spender` over the caller's tokens.
    function approve(TokenData storage data, address spender, uint256 value) external returns (bool) {
        address owner = msg.sender;
        approve(data, owner, spender, value);
        return true;
    }

    /// @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
    function approve(TokenData storage data, address owner, address spender, uint256 value) public {
        _approve(data, owner, spender, value, true);
    }

    function transfer(TokenData storage data, address to, uint256 value) external returns (bool) {
        address owner = msg.sender;
        _transfer(data, owner, to, value);
        return true;
    }

    /// @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism.
    /// value` is then deducted from the caller's allowance.
    function transferFrom(TokenData storage data, address from, address to, uint256 value) external returns (bool) {
        address spender = msg.sender;
        _spendAllowance(data, from, spender, value);
        _transfer(data, from, to, value);
        return true;
    }

    /// @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
    function mint(TokenData storage data, address account, uint256 value) external {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(data, address(0), account, value);
    }

    /// @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
    function burn(TokenData storage data, address account, uint256 value) external {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(data, account, address(0), value);
    }

    function permit(
        TokenData storage data,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external {
        if (block.timestamp > deadline) {
            revert ERC2612ExpiredSignature(deadline);
        }

        uint256 nonce;
        // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be
        // decremented or reset. This guarantees that the nonce never overflows.
        unchecked {
            // It is important to do x++ and not ++x here. Nonces starts at 0
            nonce = data.nonces[owner]++;
        }

        bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonce, deadline));

        bytes32 hash = ECDSA.toTypedDataHash(IERC20Permit(address(this)).DOMAIN_SEPARATOR(), structHash);

        address signer = ECDSA.recover(hash, v, r, s);
        if (signer != owner) {
            revert ERC2612InvalidSigner(signer, owner);
        }

        approve(data, owner, spender, value);
    }

    /// @dev Moves a `value` amount of tokens from `from` to `to`.
    function _transfer(TokenData storage data, address from, address to, uint256 value) private {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(data, from, to, value);
    }

    /// @dev Updates `owner` s allowance for `spender` based on spent `value`.
    function _spendAllowance(TokenData storage data, address owner, address spender, uint256 value) private {
        uint256 currentAllowance = data.allowances[owner][spender];
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(data, owner, spender, currentAllowance - value, false);
            }
        }
    }

    /// @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
    /// (or `to`) is the zero address.
    function _update(TokenData storage data, address from, address to, uint256 value) private {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            data.totalSupply += value;
        } else {
            uint256 fromBalance = data.balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                data.balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                data.totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                data.balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /// @dev Variant of `_approve` with an optional flag to enable or disable the Approval event.
    function _approve(TokenData storage data, address owner, address spender, uint256 value, bool emitEvent) private {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        data.allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }
}

File 43 of 87 : Autopool4626.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity ^0.8.24;

import { Errors } from "src/utils/Errors.sol";
import { AutopoolFees } from "src/vault/libs/AutopoolFees.sol";
import { AutopoolToken } from "src/vault/libs/AutopoolToken.sol";
import { AutopoolDebt } from "src/vault/libs/AutopoolDebt.sol";
import { IAutopool } from "src/interfaces/vault/IAutopool.sol";
import { IERC20Metadata } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import { StructuredLinkedList } from "src/strategy/StructuredLinkedList.sol";
import { WithdrawalQueue } from "src/strategy/WithdrawalQueue.sol";
import { AutopoolState, AutopoolStorage } from "src/vault/libs/AutopoolState.sol";
import { IMainRewarder } from "src/interfaces/rewarders/IMainRewarder.sol";
import { EnumerableSet } from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import { Math } from "openzeppelin-contracts/utils/math/Math.sol";

library Autopool4626 {
    using Math for uint256;
    using SafeERC20 for IERC20Metadata;
    using WithdrawalQueue for StructuredLinkedList.List;
    using AutopoolToken for AutopoolToken.TokenData;
    using AutopoolFees for AutopoolState;
    using AutopoolDebt for AutopoolState;
    using EnumerableSet for EnumerableSet.AddressSet;

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

    error InvalidTotalAssetPurpose();
    error InvalidShutdownStatus(IAutopool.VaultShutdownStatus status);
    error RecoveryFailed();

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

    event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
    event Nav(uint256 idle, uint256 debt, uint256 totalSupply);
    event TokensRecovered(address[] tokens, uint256[] amounts, address[] destinations);
    event Shutdown(IAutopool.VaultShutdownStatus reason);
    event RewarderSet(address newRewarder, address oldRewarder);
    event SymbolAndDescSet(string symbol, string desc);

    /// @notice Mints Vault shares to receiver by depositing exactly amount of underlying tokens
    /// @dev No nav/share changing operations, debt reportings or rebalances,
    /// can be happening throughout the entire system
    function deposit(
        AutopoolState storage $,
        address baseAsset,
        uint256 assets,
        address receiver,
        bool paused,
        uint256 _baseAssetDecimals
    ) external returns (uint256 shares) {
        Errors.verifyNotZero(assets, "assets");

        uint256 ta = $.totalAssetsTimeChecked(IAutopool.TotalAssetPurpose.Deposit);

        // Handles the vault being paused, returns 0
        uint256 maxDepositAmount = maxDeposit($, receiver, paused, _baseAssetDecimals);
        if (assets > maxDepositAmount) {
            revert IAutopool.ERC4626DepositExceedsMax(assets, maxDepositAmount);
        }

        // Factor in base Asset decimals
        shares = convertToShares(assets, ta, totalSupply(), Math.Rounding.Down, _baseAssetDecimals);
        Errors.verifyNotZero(shares, "shares");

        transferAndMint($, IERC20Metadata(baseAsset), assets, shares, receiver);
    }

    /// @notice Returns the amount of tokens owned by account.
    /// @dev Subtracts any unlocked profit shares that will be burned when account is the Vault itself
    function balanceOf(AutopoolState storage $, address account) public view returns (uint256) {
        if (account == address(this)) {
            return $.token.balances[account] - $.unlockedShares();
        }
        return $.token.balances[account];
    }

    /// @notice Returns the total amount of the underlying asset that is “managed” by Vault.
    /// @dev Utilizes the "Global" purpose internally
    function totalAssets(
        IAutopool.AssetBreakdown storage assetBreakdown
    ) public view returns (uint256) {
        return totalAssets(assetBreakdown, IAutopool.TotalAssetPurpose.Global);
    }

    /// @notice Returns the total amount of the underlying asset that is “managed” by the Vault with respect to its
    /// usage
    /// @dev Value changes based on purpose. Global is an avg. Deposit is valued higher. Withdraw is valued lower.
    /// @param purpose The calculation the total assets will be used in
    function totalAssets(
        IAutopool.AssetBreakdown storage assetBreakdown,
        IAutopool.TotalAssetPurpose purpose
    ) public view returns (uint256) {
        if (purpose == IAutopool.TotalAssetPurpose.Global) {
            return assetBreakdown.totalIdle + assetBreakdown.totalDebt;
        } else if (purpose == IAutopool.TotalAssetPurpose.Deposit) {
            return assetBreakdown.totalIdle + assetBreakdown.totalDebtMax;
        } else if (purpose == IAutopool.TotalAssetPurpose.Withdraw) {
            return assetBreakdown.totalIdle + assetBreakdown.totalDebtMin;
        } else {
            revert InvalidTotalAssetPurpose();
        }
    }

    // @notice Scale the amount from existing decimals to newDecimals
    function changeDecimals(
        uint256 amount,
        uint256 existingDecimals,
        uint256 newDecimals
    ) internal pure returns (uint256) {
        if (existingDecimals == newDecimals) {
            return amount;
        }
        if (existingDecimals > newDecimals) {
            return amount / (10 ** (existingDecimals - newDecimals));
        } else {
            return amount * (10 ** (newDecimals - existingDecimals));
        }
    }

    /// @notice Returns the amount of shares that the Vault would exchange for the amount of assets provided,
    /// in an ideal scenario where all the conditions are met
    function convertToShares(
        uint256 assets,
        uint256 totalAssetsForPurpose,
        uint256 supply,
        Math.Rounding rounding,
        uint256 baseAssetDecimals
    ) internal pure returns (uint256 shares) {
        // slither-disable-next-line incorrect-equality
        shares = (assets == 0 || supply == 0)
            ? changeDecimals(assets, baseAssetDecimals, 18)
            : assets.mulDiv(supply, totalAssetsForPurpose, rounding);
    }

    /// @notice Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an
    /// ideal
    /// scenario where all the conditions are met.
    function convertToAssets(
        uint256 shares,
        uint256 totalAssetsForPurpose,
        uint256 supply,
        Math.Rounding rounding,
        uint256 baseAssetDecimals
    ) internal pure returns (uint256 assets) {
        // slither-disable-next-line incorrect-equality
        assets = (supply == 0)
            ? changeDecimals(shares, 18, baseAssetDecimals)
            : shares.mulDiv(totalAssetsForPurpose, supply, rounding);
    }

    /// @notice Returns the maximum amount of the underlying asset that can be
    /// deposited into the Vault for the receiver, through a deposit call
    function maxDeposit(
        AutopoolState storage $,
        address wallet,
        bool paused,
        uint256 baseAssetDecimals
    ) public returns (uint256 maxAssets) {
        uint256 aptTotalAssets = $.totalAssetsTimeChecked(IAutopool.TotalAssetPurpose.Deposit);
        uint256 maxMintShares = maxMint($, wallet, paused);
        maxAssets = convertToAssets(maxMintShares, aptTotalAssets, totalSupply(), Math.Rounding.Up, baseAssetDecimals);
    }

    /// @notice Returns the maximum amount of the Vault shares that
    /// can be minted for the receiver, through a mint call.
    function maxMint(AutopoolState storage $, address, bool paused) public returns (uint256) {
        // If we are temporarily paused, or in full shutdown mode,
        // no new shares are able to be minted
        if (paused || $.shutdown) {
            return 0;
        }

        // First deposit
        uint256 ts = totalSupply();
        if (ts == 0) {
            return type(uint112).max;
        }

        // We know totalSupply greater than zero now so if totalAssets is zero
        // the vault is in an invalid state and users would be able to mint shares for free
        uint256 ta = AutopoolDebt.totalAssetsTimeChecked($, IAutopool.TotalAssetPurpose.Deposit);
        if (ta == 0) {
            return 0;
        }

        if (ts > type(uint112).max) {
            return 0;
        }

        return type(uint112).max - ts;
    }

    /// @notice Set the rewarder contract used by the vault.
    /// @param _rewarder Address of new rewarder.
    function setRewarder(AutopoolState storage $, address _rewarder) external {
        Errors.verifyNotZero(_rewarder, "rewarder");

        address toBeReplaced = address($.rewarder);
        // Check that the new rewarder has not been a rewarder before, and that the current rewarder and
        //      new rewarder addresses are not the same.
        if ($.pastRewarders.contains(_rewarder) || toBeReplaced == _rewarder) {
            revert Errors.ItemExists();
        }

        if (toBeReplaced != address(0)) {
            // slither-disable-next-line unused-return
            $.pastRewarders.add(toBeReplaced);
        }

        $.rewarder = IMainRewarder(_rewarder);
        emit RewarderSet(_rewarder, toBeReplaced);
    }

    /// @notice Returns the amount of tokens in existence.
    /// @dev Subtracts any unlocked profit shares that will be burned
    function totalSupply() public view returns (uint256 shares) {
        AutopoolState storage $ = AutopoolStorage.load();
        shares = $.token.totalSupply - $.unlockedShares();
    }

    function transferAndMint(
        AutopoolState storage $,
        IERC20Metadata baseAsset,
        uint256 assets,
        uint256 shares,
        address receiver
    ) public {
        // From OZ documentation:
        // ----------------------
        // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the
        // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer,
        // calls the vault, which is assumed not malicious.
        //
        // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
        // assets are transferred and before the shares are minted, which is a valid state.
        // slither-disable-next-line reentrancy-no-eth

        Errors.verifyNotZero(assets, "assets");

        baseAsset.safeTransferFrom(msg.sender, address(this), assets);

        uint256 newIdle = $.assetBreakdown.totalIdle + assets;
        $.assetBreakdown.totalIdle = newIdle;

        $.token.mint(receiver, shares);

        emit Deposit(msg.sender, receiver, assets, shares);

        emit Nav(newIdle, $.assetBreakdown.totalDebt, totalSupply());
    }

    /// @notice Initiate the shutdown procedures for this vault
    function shutdownVault(AutopoolState storage $, IAutopool.VaultShutdownStatus reason) external {
        if (reason == IAutopool.VaultShutdownStatus.Active) {
            revert InvalidShutdownStatus(reason);
        }

        $.shutdown = true;
        $.shutdownStatus = reason;

        emit Shutdown(reason);
    }

    /// @notice Allow the updating of symbol/desc for the vault (only AFTER shutdown)
    /// @param newSymbol Symbol the Autopool will use going forward
    /// @param newName Name the Autopool will use going forward
    function setSymbolAndDescAfterShutdown(
        AutopoolState storage $,
        string memory newSymbol,
        string memory newName
    ) external {
        Errors.verifyNotEmpty(newSymbol, "newSymbol");
        Errors.verifyNotEmpty(newName, "newName");

        // make sure the vault is no longer active
        if ($.shutdownStatus == IAutopool.VaultShutdownStatus.Active) {
            revert InvalidShutdownStatus($.shutdownStatus);
        }

        emit SymbolAndDescSet(newSymbol, newName);

        $.symbol = newSymbol;
        $.name = newName;
    }

    /// @notice Transfer out non-tracked tokens
    function recover(address[] calldata tokens, uint256[] calldata amounts, address[] calldata destinations) external {
        // Makes sure our params are valid
        uint256 len = tokens.length;

        Errors.verifyNotZero(len, "len");
        Errors.verifyArrayLengths(len, amounts.length, "tokens+amounts");
        Errors.verifyArrayLengths(len, destinations.length, "tokens+destinations");

        emit TokensRecovered(tokens, amounts, destinations);

        //IAutopool autoPool = IAutopool(address(this));

        for (uint256 i = 0; i < len; ++i) {
            (address tokenAddress, uint256 amount, address destination) = (tokens[i], amounts[i], destinations[i]);

            // Ensure this isn't an asset we care about
            // if (
            //     tokenAddress == address(this) || tokenAddress == autoPool.asset()
            //         || autoPool.isDestinationRegistered(tokenAddress)
            //         || autoPool.isDestinationQueuedForRemoval(tokenAddress)
            // ) {
            //     revert Errors.AssetNotAllowed(tokenAddress);
            // }

            if (tokenAddress != 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) {
                IERC20Metadata(tokenAddress).safeTransfer(destination, amount);
            } else {
                // solhint-disable-next-line avoid-low-level-calls
                (bool result,) = payable(destination).call{ value: amount }("");
                if (!result) {
                    revert RecoveryFailed();
                }
            }
        }
    }
}

File 44 of 87 : IStrategy.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IERC3156FlashBorrower } from "openzeppelin-contracts/interfaces/IERC3156FlashBorrower.sol";

interface IStrategy {
    /* ******************************** */
    /*      Events                      */
    /* ******************************** */
    // event DestinationVaultAdded(address destination);
    // event DestinationVaultRemoved(address destination);
    // event WithdrawalQueueSet(address[] destinations);
    // event AddedToRemovalQueue(address destination);
    // event RemovedFromRemovalQueue(address destination);

    // error InvalidDestinationVault();

    // error RebalanceFailed(string message);

    /// @notice gets the list of supported destination vaults for the Autopool/Strategy
    /// @return _destinations List of supported destination vaults
    function getDestinations() external view returns (address[] memory _destinations);

    /// @notice add supported destination vaults for the Autopool/Strategy
    /// @param _destinations The list of destination vaults to add
    function addDestinations(
        address[] calldata _destinations
    ) external;

    /// @notice remove supported destination vaults for the Autopool/Strategy
    /// @param _destinations The list of destination vaults to remove
    function removeDestinations(
        address[] calldata _destinations
    ) external;

    /// @param destinationIn The address / lp token of the destination vault that will increase
    /// @param tokenIn The address of the underlyer token that will be provided by the swapper
    /// @param amountIn The amount of the underlying LP tokens that will be received
    /// @param destinationOut The address of the destination vault that will decrease
    /// @param tokenOut The address of the underlyer token that will be received by the swapper
    /// @param amountOut The amount of the tokenOut that will be received by the swapper
    struct RebalanceParams {
        address destinationIn;
        address tokenIn;
        uint256 amountIn;
        address destinationOut;
        address tokenOut;
        uint256 amountOut;
    }

    /// @param destination The address / lp token of the destination vault
    /// @param baseApr Base Apr is the yield generated by staking rewards
    /// @param feeApr Yield for pool trading fees
    /// @param incentiveApr Incentives for LP
    /// @param safeTotalSupply Safe supply for LP tokens
    /// @param priceReturn Return from price movement to & away from peg
    /// @param maxDiscount Max discount to peg
    /// @param maxPremium Max premium to peg
    /// @param ownedShares Shares owned for this destination
    /// @param compositeReturn Total return combined from the individual yield components
    /// @param pricePerShare Price per share
    struct SummaryStats {
        address destination;
        uint256 baseApr;
        uint256 feeApr;
        uint256 incentiveApr;
        uint256 safeTotalSupply;
        int256 priceReturn;
        int256 maxDiscount;
        int256 maxPremium;
        uint256 ownedShares;
        int256 compositeReturn;
        uint256 pricePerShare;
    }

    /// @notice rebalance the Autopool from the tokenOut (decrease) to the tokenIn (increase)
    /// This uses a flash loan to receive the tokenOut to reduce the working capital requirements of the swapper
    /// @param receiver The contract receiving the tokens, needs to implement the
    /// `onFlashLoan(address user, address token, uint256 amount, uint256 fee, bytes calldata)` interface
    /// @param params Parameters by which to perform the rebalance
    /// @param data A data parameter to be passed on to the `receiver` for any custom use
    function flashRebalance(
        IERC3156FlashBorrower receiver,
        RebalanceParams calldata params,
        bytes calldata data
    ) external;
}

File 45 of 87 : Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1);

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator,
        Rounding rounding
    ) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10**64) {
                value /= 10**64;
                result += 64;
            }
            if (value >= 10**32) {
                value /= 10**32;
                result += 32;
            }
            if (value >= 10**16) {
                value /= 10**16;
                result += 16;
            }
            if (value >= 10**8) {
                value /= 10**8;
                result += 8;
            }
            if (value >= 10**4) {
                value /= 10**4;
                result += 4;
            }
            if (value >= 10**2) {
                value /= 10**2;
                result += 2;
            }
            if (value >= 10**1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
        }
    }
}

File 46 of 87 : WithdrawalQueue.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24; // their version was using 8.12?

import { StructuredLinkedList } from "src/strategy/StructuredLinkedList.sol";

// https://github.com/Layr-Labs/eigenlayer-contracts/blob/master/src/contracts/libraries/StructuredLinkedList.sol
library WithdrawalQueue {
    using StructuredLinkedList for StructuredLinkedList.List;

    error CannotInsertZeroAddress();
    error UnexpectedNodeRemoved();
    error AddToHeadFailed();
    error AddToTailFailed();
    error NodeDoesNotExist();

    /// @notice Returns true if the address is in the queue.
    function addressExists(StructuredLinkedList.List storage queue, address addr) public view returns (bool) {
        return StructuredLinkedList.nodeExists(queue, _addressToUint(addr));
    }

    /// @notice Returns the current head.
    function peekHead(
        StructuredLinkedList.List storage queue
    ) public view returns (address) {
        return _uintToAddress(StructuredLinkedList.getHead(queue));
    }

    /// @notice Returns the current tail.
    function peekTail(
        StructuredLinkedList.List storage queue
    ) public view returns (address) {
        return _uintToAddress(StructuredLinkedList.getTail(queue));
    }

    /// @notice Returns the number of items in the queue
    function sizeOf(
        StructuredLinkedList.List storage queue
    ) public view returns (uint256) {
        return StructuredLinkedList.sizeOf(queue);
    }

    /// @notice Return all items in the queue
    /// @dev Enumerates from head to tail
    function getList(
        StructuredLinkedList.List storage self
    ) public view returns (address[] memory list) {
        uint256 size = self.sizeOf();
        list = new address[](size);

        if (size > 0) {
            uint256 lastNode = self.getHead();
            list[0] = _uintToAddress(lastNode);
            for (uint256 i = 1; i < size; ++i) {
                (bool exists, uint256 node) = self.getAdjacent(lastNode, true);

                if (!exists) {
                    revert NodeDoesNotExist();
                }

                list[i] = _uintToAddress(node);
                lastNode = node;
            }
        }
    }

    /// @notice Returns the current tail.
    function popHead(
        StructuredLinkedList.List storage queue
    ) public returns (address) {
        return _uintToAddress(StructuredLinkedList.popFront(queue));
    }

    /// @notice remove address toRemove from queue if it exists.
    function popAddress(StructuredLinkedList.List storage queue, address toRemove) public {
        uint256 addrAsUint = _addressToUint(toRemove);
        uint256 _removedNode = StructuredLinkedList.remove(queue, addrAsUint);
        if (!((_removedNode == addrAsUint) || (_removedNode == 0))) {
            revert UnexpectedNodeRemoved();
        }
    }

    /// @notice returns true if there are no addresses in queue.
    function isEmpty(
        StructuredLinkedList.List storage queue
    ) public view returns (bool) {
        return !StructuredLinkedList.listExists(queue);
    }

    /// @notice if addr in queue, move it to the top
    // if addr not in queue, add it to the top of the queue.
    // if queue is empty, make a new queue with addr as the only node
    function addToHead(StructuredLinkedList.List storage queue, address addr) public {
        if (addr == address(0)) {
            revert CannotInsertZeroAddress();
        }
        popAddress(queue, addr);
        bool success = StructuredLinkedList.pushFront(queue, _addressToUint(addr));
        if (!success) {
            revert AddToHeadFailed();
        }
    }

    function getAdjacent(
        StructuredLinkedList.List storage queue,
        address addr,
        bool direction
    ) public view returns (address) {
        (bool exists, uint256 addrNum) = queue.getAdjacent(_addressToUint(addr), direction);
        if (!exists) {
            return address(0);
        }
        return _uintToAddress(addrNum);
    }

    /// @notice if addr in queue, move it to the end
    // if addr not in queue, add it to the end of the queue.
    // if queue is empty, make a new queue with addr as the only node
    function addToTail(StructuredLinkedList.List storage queue, address addr) public {
        if (addr == address(0)) {
            revert CannotInsertZeroAddress();
        }

        popAddress(queue, addr);
        bool success = StructuredLinkedList.pushBack(queue, _addressToUint(addr));
        if (!success) {
            revert AddToTailFailed();
        }
    }

    function _addressToUint(
        address addr
    ) private pure returns (uint256) {
        return uint256(uint160(addr));
    }

    function _uintToAddress(
        uint256 x
    ) private pure returns (address) {
        return address(uint160(x));
    }
}

File 47 of 87 : AutopoolDestinations.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity ^0.8.24;

import { Errors } from "src/utils/Errors.sol";
import { WithdrawalQueue } from "src/strategy/WithdrawalQueue.sol";
import { StructuredLinkedList } from "src/strategy/StructuredLinkedList.sol";
import { IDestinationVault } from "src/interfaces/vault/IDestinationVault.sol";
import { EnumerableSet } from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import { ISystemRegistry } from "src/interfaces/ISystemRegistry.sol";
import { IDestinationVaultRegistry } from "src/interfaces/vault/IDestinationVaultRegistry.sol";
import { AutopoolState } from "src/vault/libs/AutopoolState.sol";
import { IAutopool } from "src/interfaces/vault/IAutopool.sol";

library AutopoolDestinations {
    using EnumerableSet for EnumerableSet.AddressSet;
    using WithdrawalQueue for StructuredLinkedList.List;

    event DestinationVaultAdded(address destination);
    event DestinationVaultRemoved(address destination);
    event WithdrawalQueueSet(address[] destinations);
    event AddedToRemovalQueue(address destination);
    event RemovedFromRemovalQueue(address destination);

    error TooManyDeployedDestinations();
    error BaseAssetMismatch(address destinationVault);

    /// @notice Maximum amount of destinations we can be deployed to a given time
    uint256 public constant MAX_DEPLOYED_DESTINATIONS = 50;

    /// @notice Remove, or queue to remove if necessary, destinations from the vault
    /// @dev No need to handle withdrawal queue as it will be popped once it hits balance 0 in withdraw or rebalance.
    /// Debt report queue is handled the same way
    /// @param $ Storage data of the calling Autopool
    /// @param _destinations Destinations to remove
    function removeDestinations(AutopoolState storage $, address[] calldata _destinations) external {
        for (uint256 i = 0; i < _destinations.length; ++i) {
            address dAddress = _destinations[i];
            IDestinationVault destination = IDestinationVault(dAddress);

            // remove from main list (NOTE: done here so balance check below doesn't explode if address is invalid)
            if (!$.destinations.remove(dAddress)) {
                revert Errors.ItemNotFound();
            }

            if (destination.balanceOf(address(this)) > 0 && !$.removalQueue.contains(dAddress)) {
                // we still have funds in it! move it to removalQueue for rebalancer to handle it later
                // slither-disable-next-line unused-return
                $.removalQueue.add(dAddress);

                emit AddedToRemovalQueue(dAddress);
            }

            emit DestinationVaultRemoved(dAddress);
        }
    }

    /// @notice Add a destination to the vault
    /// @dev No need to add to debtReport and withdrawal queue from the vault as the rebalance will take care of that
    /// @param $ Storage data of the calling Autopool
    /// @param destinations New destinations to add
    /// @param systemRegistry System registry reference for the vault
    function addDestinations(
        AutopoolState storage $,
        address[] calldata destinations,
        ISystemRegistry systemRegistry
    ) external {
        IDestinationVaultRegistry destinationRegistry = systemRegistry.destinationVaultRegistry();

        uint256 numDestinations = destinations.length;
        if (numDestinations == 0) {
            revert Errors.InvalidParams();
        }

        address autopoolAsset = IAutopool(address(this)).asset();

        address dAddress;
        for (uint256 i = 0; i < numDestinations; ++i) {
            dAddress = destinations[i];

            // Address must be setup in our registry
            if (dAddress == address(0) || !destinationRegistry.isRegistered(dAddress)) {
                revert Errors.InvalidAddress(dAddress);
            }

            // Don't allow duplicates
            if (!$.destinations.add(dAddress)) {
                revert Errors.ItemExists();
            }

            address dvBaseAsset = IDestinationVault(dAddress).baseAsset();

            if (dvBaseAsset != autopoolAsset) {
                revert BaseAssetMismatch(dAddress);
            }

            // A destination could be queued for removal but we decided
            // to keep it
            // slither-disable-next-line unused-return
            $.removalQueue.remove(dAddress);

            emit DestinationVaultAdded(dAddress);
        }
    }

    /// @notice Ensure a destination is in the queues it should be after a rebalance or debt report
    /// @param destination The destination to manage
    /// @param destinationIn Whether the destination was moved into, true, or out of, false.
    function manageQueuesForDestination(AutopoolState storage $, address destination, bool destinationIn) external {
        // The vault itself, when we are moving idle around, should never be
        // in the queues.
        if (destination != address(this)) {
            // If we have a balance, we need to continue to report on it
            if (IDestinationVault(destination).balanceOf(address(this)) > 0) {
                // For debt reporting, we just updated the values so we can put
                // it at the end of the queue.
                $.debtReportQueue.addToTail(destination);

                // Debt reporting queue is a proxy for destinations we are deployed to
                // Easiest to check after doing the add as "addToTail" can move
                // the destination when it already exists. Also, we run this fn for the "out"
                // destination first so we're sure to free the spots we can
                if ($.debtReportQueue.sizeOf() > MAX_DEPLOYED_DESTINATIONS) {
                    revert TooManyDeployedDestinations();
                }

                // For withdraws, if we moved into the position then we want to put it
                // at the end of the queue so we don't exit from our higher projected
                // apr positions first. If we exited, that means its a lower apr
                // and we want to continue to exit via user withdrawals so put it at the top
                if (destinationIn) {
                    $.withdrawalQueue.addToTail(destination);
                } else {
                    $.withdrawalQueue.addToHead(destination);
                }
            } else {
                // If we no longer have a balance we don't need to continue to report
                // on it and we also have nothing to withdraw from it

                // We don't remove the destination from the debt queue here as it
                // still might have unclaimed rewards.
                $.withdrawalQueue.popAddress(destination);

                if ($.removalQueue.remove(destination)) {
                    emit RemovedFromRemovalQueue(destination);
                }
            }
        }
    }
}

File 48 of 87 : IAutopoolStrategy.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IStrategy } from "src/interfaces/strategy/IStrategy.sol";

interface IAutopoolStrategy {
    enum RebalanceDirection {
        In,
        Out
    }

    /// @notice verify that a rebalance (swap between destinations) meets all the strategy constraints
    /// @dev Signature identical to IStrategy.verifyRebalance
    function verifyRebalance(
        IStrategy.RebalanceParams memory,
        IStrategy.SummaryStats memory
    ) external returns (bool, string memory message);

    /// @notice called by the Autopool when NAV is updated
    /// @dev can only be called by the strategy's registered Autopool
    /// @param navPerShare The navPerShare to record
    function navUpdate(
        uint256 navPerShare
    ) external;

    /// @notice called by the Autopool when a rebalance is completed
    /// @dev can only be called by the strategy's registered Autopool
    /// @param rebalanceParams The parameters for the rebalance that was executed
    function rebalanceSuccessfullyExecuted(
        IStrategy.RebalanceParams memory rebalanceParams
    ) external;

    /// @notice called by the Autopool during rebalance process
    /// @param rebalanceParams The parameters for the rebalance that was executed
    function getRebalanceOutSummaryStats(
        IStrategy.RebalanceParams memory rebalanceParams
    ) external returns (IStrategy.SummaryStats memory outSummary);

    /// @notice Returns stats for a given destination
    /// @dev Used to evaluate the current state of the destinations and decide best action
    /// @param destAddress Destination address. Can be a DestinationVault or the AutoPool
    /// @param direction Direction to evaluate the stats at
    /// @param amount Amount to evaluate the stats at
    function getDestinationSummaryStats(
        address destAddress,
        IAutopoolStrategy.RebalanceDirection direction,
        uint256 amount
    ) external returns (IStrategy.SummaryStats memory);

    /// @notice Returns all hooks registered on strategy
    /// @dev Will return zero addresses for unregistered hooks
    /// @return hooks Array of hook addresses
    function getHooks() external view returns (address[] memory hooks);

    /// @notice the number of days to pause rebalancing due to NAV decay
    function pauseRebalancePeriodInDays() external view returns (uint16);

    /// @notice the number of seconds gap between consecutive rebalances
    function rebalanceTimeGapInSeconds() external view returns (uint256);

    /// @notice destinations trading a premium above maxPremium will be blocked from new capital deployments
    function maxPremium() external view returns (int256); // 100% = 1e18

    /// @notice destinations trading a discount above maxDiscount will be blocked from new capital deployments
    function maxDiscount() external view returns (int256); // 100% = 1e18

    /// @notice the allowed staleness of stats data before a revert occurs
    function staleDataToleranceInSeconds() external view returns (uint40);

    /// @notice the swap cost offset period to initialize the strategy with
    function swapCostOffsetInitInDays() external view returns (uint16);

    /// @notice the number of violations required to trigger a tightening of the swap cost offset period (1 to 10)
    function swapCostOffsetTightenThresholdInViolations() external view returns (uint16);

    /// @notice the number of days to decrease the swap offset period for each tightening step
    function swapCostOffsetTightenStepInDays() external view returns (uint16);

    /// @notice the number of days since a rebalance required to trigger a relaxing of the swap cost offset period
    function swapCostOffsetRelaxThresholdInDays() external view returns (uint16);

    /// @notice the number of days to increase the swap offset period for each relaxing step
    function swapCostOffsetRelaxStepInDays() external view returns (uint16);

    // slither-disable-start similar-names
    /// @notice the maximum the swap cost offset period can reach. This is the loosest the strategy will be
    function swapCostOffsetMaxInDays() external view returns (uint16);

    /// @notice the minimum the swap cost offset period can reach. This is the most conservative the strategy will be
    function swapCostOffsetMinInDays() external view returns (uint16);

    /// @notice the number of days for the first NAV decay comparison (e.g., 30 days)
    function navLookback1InDays() external view returns (uint8);

    /// @notice the number of days for the second NAV decay comparison (e.g., 60 days)
    function navLookback2InDays() external view returns (uint8);

    /// @notice the number of days for the third NAV decay comparison (e.g., 90 days)
    function navLookback3InDays() external view returns (uint8);
    // slither-disable-end similar-names

    /// @notice the maximum slippage that is allowed for a normal rebalance
    function maxNormalOperationSlippage() external view returns (uint256); // 100% = 1e18

    /// @notice the maximum amount of slippage to allow when a destination is trimmed due to constraint violations
    /// recommend setting this higher than maxNormalOperationSlippage
    function maxTrimOperationSlippage() external view returns (uint256); // 100% = 1e18

    /// @notice the maximum amount of slippage to allow when a destinationVault has been shutdown
    /// shutdown for a vault is abnormal and means there is an issue at that destination
    /// recommend setting this higher than maxNormalOperationSlippage
    function maxEmergencyOperationSlippage() external view returns (uint256); // 100% = 1e18

    /// @notice the maximum amount of slippage to allow when the Autopool has been shutdown
    function maxShutdownOperationSlippage() external view returns (uint256); // 100% = 1e18

    /// @notice the maximum discount used for price return
    function maxAllowedDiscount() external view returns (int256); // 18 precision

    /// @notice model weight used for LSTs base yield, 1e6 is the highest
    function weightBase() external view returns (uint256);

    /// @notice model weight used for DEX fee yield, 1e6 is the highest
    function weightFee() external view returns (uint256);

    /// @notice model weight used for incentive yield
    function weightIncentive() external view returns (uint256);

    /// @notice model weight applied to an LST discount when exiting the position
    function weightPriceDiscountExit() external view returns (int256);

    /// @notice model weight applied to an LST discount when entering the position
    function weightPriceDiscountEnter() external view returns (int256);

    /// @notice model weight applied to an LST premium when entering or exiting the position
    function weightPricePremium() external view returns (int256);

    /// @notice initial value of the swap cost offset to use
    function swapCostOffsetInit() external view returns (uint16);

    /// @notice initial lst price gap tolerance
    function defaultLstPriceGapTolerance() external view returns (uint256);
}

File 49 of 87 : IMainRewarder.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IBaseRewarder } from "src/interfaces/rewarders/IBaseRewarder.sol";
import { IExtraRewarder } from "src/interfaces/rewarders/IExtraRewarder.sol";

interface IMainRewarder is IBaseRewarder {
    error ExtraRewardsNotAllowed();
    error MaxExtraRewardsReached();

    /// @notice Extra rewards can be added, but not removed, ref: https://github.com/Tokemak/v2-core/issues/659
    event ExtraRewardAdded(address reward);

    /**
     * @notice Adds an ExtraRewarder contract address to the extraRewards array.
     * @param reward The address of the ExtraRewarder contract.
     */
    function addExtraReward(
        address reward
    ) external;

    /**
     * @notice Withdraws the specified amount of tokens from the vault for the specified account, and transfers all
     * rewards for the account from this contract and any linked extra reward contracts.
     * @param account The address of the account to withdraw tokens and claim rewards for.
     * @param amount The amount of tokens to withdraw.
     * @param claim If true, claims all rewards for the account from this contract and any linked extra reward
     * contracts.
     */
    function withdraw(address account, uint256 amount, bool claim) external;

    /**
     * @notice Claims and transfers all rewards for the specified account from this contract and any linked extra reward
     * contracts.
     * @dev If claimExtras is true, also claims all rewards from linked extra reward contracts.
     * @param account The address of the account to claim rewards for.
     * @param recipient The address to send the rewards to.
     * @param claimExtras If true, claims rewards from linked extra reward contracts.
     */
    function getReward(address account, address recipient, bool claimExtras) external;

    /**
     * @notice Number of extra rewards currently registered
     */
    function extraRewardsLength() external view returns (uint256);

    /**
     * @notice Get the extra rewards array values
     */
    function extraRewards() external view returns (address[] memory);

    /**
     * @notice Get the rewarder at the specified index
     */
    function getExtraRewarder(
        uint256 index
    ) external view returns (IExtraRewarder);
}

File 50 of 87 : StructuredLinkedList.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

/**
 * @title StructuredLinkedList
 * @author Vittorio Minacori (https://github.com/vittominacori)
 * @dev An utility library for using sorted linked list data structures in your Solidity project.
 * @notice Adapted from
 * https://github.com/Layr-Labs/eigenlayer-contracts/blob/master/src/contracts/libraries/StructuredLinkedList.sol
 */
library StructuredLinkedList {
    uint256 private constant _NULL = 0;
    uint256 private constant _HEAD = 0;

    bool private constant _PREV = false;
    bool private constant _NEXT = true;

    struct List {
        uint256 size;
        mapping(uint256 => mapping(bool => uint256)) list;
    }

    /**
     * @dev Checks if the list exists
     * @param self stored linked list from contract
     * @return bool true if list exists, false otherwise
     */
    function listExists(
        List storage self
    ) public view returns (bool) {
        // if the head nodes previous or next pointers both point to itself, then there are no items in the list
        if (self.list[_HEAD][_PREV] != _HEAD || self.list[_HEAD][_NEXT] != _HEAD) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Checks if the node exists
     * @param self stored linked list from contract
     * @param _node a node to search for
     * @return bool true if node exists, false otherwise
     */
    function nodeExists(List storage self, uint256 _node) public view returns (bool) {
        if (self.list[_node][_PREV] == _HEAD && self.list[_node][_NEXT] == _HEAD) {
            if (self.list[_HEAD][_NEXT] == _node) {
                return true;
            } else {
                return false;
            }
        } else {
            return true;
        }
    }

    /**
     * @dev Returns the number of elements in the list
     * @param self stored linked list from contract
     * @return uint256
     */
    // slither-disable-next-line dead-code
    function sizeOf(
        List storage self
    ) public view returns (uint256) {
        return self.size;
    }

    /**
     * @dev Gets the head of the list
     * @param self stored linked list from contract
     * @return uint256 the head of the list
     */
    function getHead(
        List storage self
    ) public view returns (uint256) {
        return self.list[_HEAD][_NEXT];
    }

    /**
     * @dev Gets the head of the list
     * @param self stored linked list from contract
     * @return uint256 the head of the list
     */
    function getTail(
        List storage self
    ) public view returns (uint256) {
        return self.list[_HEAD][_PREV];
    }

    /**
     * @dev Returns the links of a node as a tuple
     * @param self stored linked list from contract
     * @param _node id of the node to get
     * @return bool, uint256, uint256 true if node exists or false otherwise, previous node, next node
     */
    // slither-disable-next-line dead-code
    function getNode(List storage self, uint256 _node) public view returns (bool, uint256, uint256) {
        if (!nodeExists(self, _node)) {
            return (false, 0, 0);
        } else {
            return (true, self.list[_node][_PREV], self.list[_node][_NEXT]);
        }
    }

    /**
     * @dev Returns the link of a node `_node` in direction `_direction`.
     * @param self stored linked list from contract
     * @param _node id of the node to step from
     * @param _direction direction to step in
     * @return bool, uint256 true if node exists or false otherwise, node in _direction
     */
    // slither-disable-next-line dead-code
    function getAdjacent(List storage self, uint256 _node, bool _direction) public view returns (bool, uint256) {
        if (!nodeExists(self, _node)) {
            return (false, 0);
        } else {
            uint256 adjacent = self.list[_node][_direction];
            return (adjacent != _HEAD, adjacent);
        }
    }

    /**
     * @dev Returns the link of a node `_node` in direction `_NEXT`.
     * @param self stored linked list from contract
     * @param _node id of the node to step from
     * @return bool, uint256 true if node exists or false otherwise, next node
     */
    // slither-disable-next-line dead-code
    function getNextNode(List storage self, uint256 _node) public view returns (bool, uint256) {
        return getAdjacent(self, _node, _NEXT);
    }

    /**
     * @dev Returns the link of a node `_node` in direction `_PREV`.
     * @param self stored linked list from contract
     * @param _node id of the node to step from
     * @return bool, uint256 true if node exists or false otherwise, previous node
     */
    // slither-disable-next-line dead-code
    function getPreviousNode(List storage self, uint256 _node) public view returns (bool, uint256) {
        return getAdjacent(self, _node, _PREV);
    }

    /**
     * @dev Insert node `_new` beside existing node `_node` in direction `_NEXT`.
     * @param self stored linked list from contract
     * @param _node existing node
     * @param _new  new node to insert
     * @return bool true if success, false otherwise
     */
    // slither-disable-next-line dead-code
    function insertAfter(List storage self, uint256 _node, uint256 _new) public returns (bool) {
        return _insert(self, _node, _new, _NEXT);
    }

    /**
     * @dev Insert node `_new` beside existing node `_node` in direction `_PREV`.
     * @param self stored linked list from contract
     * @param _node existing node
     * @param _new  new node to insert
     * @return bool true if success, false otherwise
     */
    // slither-disable-next-line dead-code
    function insertBefore(List storage self, uint256 _node, uint256 _new) public returns (bool) {
        return _insert(self, _node, _new, _PREV);
    }

    /**
     * @dev Removes an entry from the linked list
     * @param self stored linked list from contract
     * @param _node node to remove from the list
     * @return uint256 the removed node
     */
    function remove(List storage self, uint256 _node) public returns (uint256) {
        if ((_node == _NULL) || (!nodeExists(self, _node))) {
            return 0;
        }
        _createLink(self, self.list[_node][_PREV], self.list[_node][_NEXT], _NEXT);
        delete self.list[_node][_PREV];
        delete self.list[_node][_NEXT];

        self.size -= 1;

        return _node;
    }

    /**
     * @dev Pushes an entry to the head of the linked list
     * @param self stored linked list from contract
     * @param _node new entry to push to the head
     * @return bool true if success, false otherwise
     */
    function pushFront(List storage self, uint256 _node) public returns (bool) {
        return _push(self, _node, _NEXT);
    }

    /**
     * @dev Pushes an entry to the tail of the linked list
     * @param self stored linked list from contract
     * @param _node new entry to push to the tail
     * @return bool true if success, false otherwise
     */
    function pushBack(List storage self, uint256 _node) public returns (bool) {
        return _push(self, _node, _PREV);
    }

    /**
     * @dev Pops the first entry from the head of the linked list
     * @param self stored linked list from contract
     * @return uint256 the removed node
     */
    // slither-disable-next-line dead-code
    function popFront(
        List storage self
    ) public returns (uint256) {
        return _pop(self, _NEXT);
    }

    /**
     * @dev Pops the first entry from the tail of the linked list
     * @param self stored linked list from contract
     * @return uint256 the removed node
     */
    // slither-disable-next-line dead-code
    function popBack(
        List storage self
    ) public returns (uint256) {
        return _pop(self, _PREV);
    }

    /**
     * @dev Pushes an entry to the head of the linked list
     * @param self stored linked list from contract
     * @param _node new entry to push to the head
     * @param _direction push to the head (_NEXT) or tail (_PREV)
     * @return bool true if success, false otherwise
     */
    function _push(List storage self, uint256 _node, bool _direction) private returns (bool) {
        return _insert(self, _HEAD, _node, _direction);
    }

    /**
     * @dev Pops the first entry from the linked list
     * @param self stored linked list from contract
     * @param _direction pop from the head (_NEXT) or the tail (_PREV)
     * @return uint256 the removed node
     */
    // slither-disable-next-line dead-code
    function _pop(List storage self, bool _direction) private returns (uint256) {
        uint256 adj;
        (, adj) = getAdjacent(self, _HEAD, _direction);
        return remove(self, adj);
    }

    /**
     * @dev Insert node `_new` beside existing node `_node` in direction `_direction`.
     * @param self stored linked list from contract
     * @param _node existing node
     * @param _new  new node to insert
     * @param _direction direction to insert node in
     * @return bool true if success, false otherwise
     */
    function _insert(List storage self, uint256 _node, uint256 _new, bool _direction) private returns (bool) {
        if (!nodeExists(self, _new) && nodeExists(self, _node)) {
            uint256 c = self.list[_node][_direction];
            _createLink(self, _node, _new, _direction);
            _createLink(self, _new, c, _direction);

            self.size += 1;

            return true;
        }

        return false;
    }

    /**
     * @dev Creates a bidirectional link between two nodes on direction `_direction`
     * @param self stored linked list from contract
     * @param _node existing node
     * @param _link node to link to in the _direction
     * @param _direction direction to insert node in
     */
    function _createLink(List storage self, uint256 _node, uint256 _link, bool _direction) private {
        self.list[_link][!_direction] = _node;
        self.list[_node][_direction] = _link;
    }
}

File 51 of 87 : Initializable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.1) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.2;

import "../../utils/Address.sol";

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     * @custom:oz-retyped-from bool
     */
    uint8 private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint8 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
     * constructor.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        bool isTopLevelCall = !_initializing;
        require(
            (isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1),
            "Initializable: contract is already initialized"
        );
        _initialized = 1;
        if (isTopLevelCall) {
            _initializing = true;
        }
        _;
        if (isTopLevelCall) {
            _initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: setting the version to 255 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint8 version) {
        require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
        _initialized = version;
        _initializing = true;
        _;
        _initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        require(_initializing, "Initializable: contract is not initializing");
        _;
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        require(!_initializing, "Initializable: contract is initializing");
        if (_initialized < type(uint8).max) {
            _initialized = type(uint8).max;
            emit Initialized(type(uint8).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint8) {
        return _initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _initializing;
    }
}

File 52 of 87 : EnumerableSet.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.0;

/**
 * @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.
 *
 * ```
 * 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 of the value in the `values` array, plus 1 because index 0
        // means a value is not in the set.
        mapping(bytes32 => uint256) _indexes;
    }

    /**
     * @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._indexes[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 read and store the value's index to prevent multiple reads from the same storage slot
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 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 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

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

                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastValue;
                // Update the index for the moved value
                set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
            }

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

            // Delete the index for the deleted slot
            delete set._indexes[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._indexes[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;
    }
}

File 53 of 87 : IERC3156FlashBorrower.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (interfaces/IERC3156FlashBorrower.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC3156 FlashBorrower, as defined in
 * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].
 *
 * _Available since v4.1._
 */
interface IERC3156FlashBorrower {
    /**
     * @dev Receive a flash loan.
     * @param initiator The initiator of the loan.
     * @param token The loan currency.
     * @param amount The amount of tokens lent.
     * @param fee The additional amount of tokens to repay.
     * @param data Arbitrary data structure, intended to contain user-defined parameters.
     * @return The keccak256 hash of "IERC3156FlashBorrower.onFlashLoan"
     */
    function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external returns (bytes32);
}

File 54 of 87 : AutopoolState.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity ^0.8.24;

// solhint-disable no-inline-assembly

import { IAutopool } from "src/interfaces/vault/IAutopool.sol";
import { EnumerableSet } from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import { AutopoolDebt } from "src/vault/libs/AutopoolDebt.sol";
import { StructuredLinkedList } from "src/strategy/StructuredLinkedList.sol";
import { AutopoolToken } from "src/vault/libs/AutopoolToken.sol";
import { IMainRewarder } from "src/interfaces/rewarders/IMainRewarder.sol";
import { IERC20Metadata } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IERC3156FlashBorrower } from "openzeppelin-contracts/interfaces/IERC3156FlashBorrower.sol";
import { IStrategy } from "src/interfaces/strategy/IStrategy.sol";

struct AutopoolState {
    /// @notice Balances, allowances, and supply for the pool
    /// @dev Want to keep this var in this position
    AutopoolToken.TokenData token;
    /// @notice Full list of possible destinations that could be deployed to
    /// @dev Exposed via `getDestinations()`
    EnumerableSet.AddressSet destinations;
    /// @notice Destinations that are queued for removal
    /// @dev Exposed via `getRemovalQueue`
    EnumerableSet.AddressSet removalQueue;
    /// @notice Whether or not the vault has been shutdown
    /// @dev Exposed via `isShutdown()`
    bool shutdown;
    /// @notice Reason for shutdown (or `Active` if not shutdown)
    /// @dev Exposed via `shutdownStatus()`
    IAutopool.VaultShutdownStatus shutdownStatus;
    /// @notice Lookup of destinationVaultAddress -> Info .. Debt reporting snapshot info
    /// @dev Exposed via `getDestinationInfo`
    mapping(address => AutopoolDebt.DestinationInfo) destinationInfo;
    /// @notice Ordered list of destinations to withdraw from
    /// @dev Exposed via `getWithdrawalQueue()`
    StructuredLinkedList.List withdrawalQueue;
    /// @notice Ordered list of destinations to debt report on. Ordered from oldest to newest
    /// @dev Exposed via `getDebtReportingQueue()`
    StructuredLinkedList.List debtReportQueue;
    /// @notice State and settings related to gradual profit unlock
    /// @dev Exposed via `getProfitUnlockSettings()`
    IAutopool.ProfitUnlockSettings profitUnlockSettings;
    /// @notice State and settings related to periodic and streaming fees
    /// @dev Exposed via `getFeeSettings()`
    IAutopool.AutopoolFeeSettings feeSettings;
    /// @notice Rewarders that have been replaced.
    /// @dev Exposed via `isPastRewarder()`
    EnumerableSet.AddressSet pastRewarders;
    /// @notice Main rewarder for this contract
    IMainRewarder rewarder;
    /// @notice Pool/token name
    string name;
    /// @notice Pool/token symbol
    string symbol;
    /// @notice Storage address of hook configurations
    address hookStore;
    /// @notice Factory contract that created this vault
    address factory;
    /// @notice Asset tracking for idle and debt values
    /// @dev Exposed via `getAssetBreakdown()`
    IAutopool.AssetBreakdown assetBreakdown;
}

struct ProcessRebalanceParams {
    IERC20Metadata baseAsset;
    IERC3156FlashBorrower receiver;
    IStrategy.RebalanceParams rebalanceParams;
}

library AutopoolStorage {
    // keccak256(abi.encode(uint256(keccak256("autopilot.storage.AutopoolState")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant SLOT = 0x17264fbcd79a365fd3ccff89407ad487986f8b37b9035d6bc8b51cacd5832200;

    function load() internal pure returns (AutopoolState storage $) {
        assembly {
            $.slot := SLOT
        }
    }
}

File 55 of 87 : AutopoolStrategyHooks.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity ^0.8.24;

// solhint-disable no-inline-assembly,avoid-low-level-calls

import { Errors } from "src/utils/Errors.sol";
import { SSTORE2 } from "src/external/solady/SSTORE2.sol";
import { LibBytes } from "src/external/solady/LibBytes.sol";
import { AutopoolState } from "src/vault/libs/AutopoolState.sol";
import { IStrategyHook } from "src/interfaces/strategy/IStrategyHook.sol";

library AutopoolStrategyHooks {
    /// =====================================================
    /// Hook configurations are read and stored via SSTORE2.
    /// The storage is formatted as fixed length packed byte20(address) arrays
    /// that are themselves packed in the order of the function definitions
    /// on the interface.
    /// For example, lets say we have 3 hooks configured (with 8 functions and 10 hooks supported):
    ///   - Hook 1, address(1), supports onRebalanceStart (1) and onRebalanceFeeProfitHandlingComplete (16)
    ///   - Hook 2, address(2), supports onRebalanceStart (1) and onRebalanceInAssetsReturned (4)
    ///   - Hook 3, address(3), supports onRebalanceComplete (32)
    /// Storage would look like:
    ///   0000000000000000000000000000000000000001 - Start of hooks to call onRebalanceStart() (flag 1) for
    ///   0000000000000000000000000000000000000002
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000 - Hooks to call onRebalanceOutAssetsReady() (flag 2) for
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000002 - Hooks to call onRebalanceInAssetsReturned() (flag 4) for
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000 - Hooks to call onRebalanceDestinationVaultUpdated() (flag 8) for
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000001 - Hooks to call onRebalanceFeeProfitHandlingComplete() (flag 16) for
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000003 - Start of hooks to call onRebalanceComplete() (flag 32) for
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000 - Start of hooks to call onDestinationDebtReport() (flag 64) for
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000 - Start of hooks to call onNavUpdate() (flag 128) for
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000
    ///   0000000000000000000000000000000000000000

    /// Adding hooks should append to the lowest empty of the section
    /// Removing hooks should shift remainder down to maintain order

    /// =====================================================
    /// Constants
    /// =====================================================

    /// @notice Returns maximum number of hooks supported
    uint256 public constant NUM_HOOKS = 10;

    /// @notice Returns the number of hook functions supported
    uint256 public constant NUM_FUNCTIONS = 8;

    /// @notice Max function flag given the number of functions
    uint256 private constant MAX_FN_FLAG = 2 ** NUM_FUNCTIONS;

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

    /// @notice Fires when are at the maximum number of configured hooks for a function
    error MaxHooksSet();

    /// @notice Fires when a hook is already registered for a function
    error HookAlreadySet(address hook, uint256 fn);

    /// @notice Fires on removal when a hook doesn't exist
    error HookNotSet(address hook);

    /// @notice Fires on removal when a function is supposed to be registered but isn't
    error FunctionNotSet(address hook, uint256 fn);

    /// @notice Fires when a hook execution fails
    error HookExecutionFailed(address hook, bytes underlyingError);

    /// =====================================================
    /// Structs
    /// =====================================================

    /// @notice Used for view/display purposes
    struct HookConfiguration {
        address[NUM_HOOKS] onRebalanceStart;
        address[NUM_HOOKS] onRebalanceOutAssetsReady;
        address[NUM_HOOKS] onRebalanceInAssetsReturned;
        address[NUM_HOOKS] onRebalanceDestinationVaultUpdated;
        address[NUM_HOOKS] onRebalanceFeeProfitHandlingComplete;
        address[NUM_HOOKS] onRebalanceComplete;
        address[NUM_HOOKS] onDestinationDebtReport;
        address[NUM_HOOKS] onNavUpdate;
    }

    /// =====================================================
    /// Functions - External
    /// =====================================================

    /// @notice Execute all hooks, in order, based on the provided configuration
    /// @dev Caller is responsible for ensuring fnIndex and call are correctly paired
    /// @param hooks Packed hooks configuration data
    /// @param fnIndex Index of the function to execute
    /// @param call Call to make to the hook
    function executeHooks(bytes memory hooks, uint256 fnIndex, bytes memory call) external {
        uint256 hookIx = (fnIndex * NUM_HOOKS * 20) + 32; // 32 for the length of bytes
        uint256 hookEndIx = hookIx + (20 * NUM_HOOKS);

        while (hookIx < hookEndIx) {
            // Pull the bytes out at the index and convert to address
            address addrVal;
            assembly {
                addrVal := shr(96, mload(add(hooks, hookIx)))
            }

            // If its there an address, we execute, otherwise we're done
            if (addrVal != address(0)) {
                (bool result, bytes memory errorData) = addrVal.call(call);
                if (!result) {
                    revert HookExecutionFailed(addrVal, errorData);
                }
            } else {
                break;
            }

            unchecked {
                hookIx += 20;
            }
        }
    }

    /// @notice Add a set of hooks to the Autopools configuration
    /// @param $ Storage data of the calling Autopool
    /// @param newHooks Set of hooks to add to the Autopool
    /// @param configDatas Set of datas to pass to the onRegistered function of the hook
    function addHooks(AutopoolState storage $, IStrategyHook[] memory newHooks, bytes[] memory configDatas) external {
        bytes memory flags = getHookBytes($);

        uint256 len = newHooks.length;
        Errors.verifyNotZero(len, "len");
        Errors.verifyArrayLengths(len, configDatas.length, "ars");

        for (uint256 i = 0; i < len;) {
            flags = _addHook(flags, newHooks[i], configDatas[i]);

            unchecked {
                ++i;
            }
        }

        $.hookStore = SSTORE2.write(flags);
    }

    /// @notice Remove a hook from the Autopools configuration
    /// @param $ Storage data of the calling Autopool
    /// @param hookToRemove Hook to remove from to the Autopool
    /// @param cleanupData Data to pass to the onUnregistered function of the hook
    function removeHook(AutopoolState storage $, IStrategyHook hookToRemove, bytes calldata cleanupData) external {
        Errors.verifyNotZero(address(hookToRemove), "hookToRemove");

        bytes memory flags;

        address hookStorage = $.hookStore;
        if (hookStorage != address(0)) {
            flags = SSTORE2.read(hookStorage);
        } else {
            revert HookNotSet(address(hookToRemove));
        }

        // We are OK with assumption that supported flags can't change between
        // register and unregister
        uint8 supportedHookFunctions = hookToRemove.getFnFlags();
        Errors.verifyNotZero(supportedHookFunctions, "supportedHookFunctions");
        uint256 fnToCheck = 1;

        bytes memory newFlagsData;
        uint256 bytesIndex = 0;

        // Loop over all the functions hook slots
        // build our replacement storage data, newFlagsData
        // Replace the address with empty where needed
        bytes memory empty = hex"0000000000000000000000000000000000000000";

        while (fnToCheck < MAX_FN_FLAG) {
            uint256 hookIx = 0;
            bool needToRemove = supportedHookFunctions & fnToCheck == fnToCheck;
            bool removed = false;

            while (hookIx < NUM_HOOKS) {
                // Get our existing value
                address addrVal;
                {
                    bytes memory val = LibBytes.slice(flags, bytesIndex, bytesIndex + 20);
                    assembly {
                        addrVal := shr(96, mload(add(val, 32)))
                    }
                }
                if (addrVal == address(hookToRemove)) {
                    // Don't put anything in its spot so that the remaining
                    // values will shift down. We'll tack an empty onto the end
                    removed = true;
                } else {
                    // Not the value we're looking for, forward it on
                    newFlagsData = LibBytes.concat(newFlagsData, abi.encodePacked(addrVal));
                }

                unchecked {
                    ++hookIx;
                    bytesIndex += 20;
                }
            }

            if (needToRemove && !removed) {
                revert FunctionNotSet(address(hookToRemove), fnToCheck);
            }

            // We don't support duplicates so we know it was only in once
            // We will revert if didn't remove, so we know we removed one item
            // Add an empty onto the end in its place
            if (removed) {
                newFlagsData = LibBytes.concat(newFlagsData, empty);
            }

            unchecked {
                fnToCheck *= 2;
            }
        }

        // We removed it so run the cleanup
        hookToRemove.onUnregistered(cleanupData);

        $.hookStore = SSTORE2.write(newFlagsData);
    }

    /// @notice Get hooks in a proper format
    /// @dev Do not use in any executing code
    /// @param $ Storage data of the calling Autopool
    function getHooks(
        AutopoolState storage $
    ) external view returns (HookConfiguration memory) {
        bytes memory flags = getHookBytes($);

        uint256 memSize = NUM_HOOKS * NUM_FUNCTIONS * 32;
        assembly {
            // Get our working space for building the struct
            let structPtr := mload(0x40)

            // Update pointer to new position
            mstore(0x40, add(structPtr, memSize))

            // Get the store of our addresses, first 32 is array size
            let dataPtr := add(flags, 32)
            let dataEnd := add(dataPtr, mload(flags))
            let offset := 0

            // Loop our data and set the addresses
            for { } lt(dataPtr, dataEnd) { } {
                let word := mload(dataPtr)

                // Convert to address
                word := shr(96, word)

                // Add address to struct
                mstore(add(structPtr, offset), word)

                // Increment our counters
                dataPtr := add(dataPtr, 20)
                offset := add(offset, 32)
            }

            return(structPtr, memSize)
        }
    }

    /// =====================================================
    /// Functions - Public
    /// =====================================================

    /// @notice Get hooks in a proper format
    /// @dev Do not use in any executing code
    /// @param $ Storage data of the calling Autopool
    function getHookBytes(
        AutopoolState storage $
    ) public view returns (bytes memory) {
        bytes memory flags;

        address hookStorage = $.hookStore;
        if (hookStorage != address(0)) {
            flags = SSTORE2.read(hookStorage);
        } else {
            // If we haven't set any data initialize the array
            // to all empty addresses
            flags = new bytes(NUM_HOOKS * NUM_FUNCTIONS * 20);
        }

        return flags;
    }

    /// =====================================================
    /// Functions - Private
    /// =====================================================

    /// @notice Add a hook to the Autopools configuration
    /// @param flags Existing hook data
    /// @param newHook Hook to add to the Autopool
    /// @param configData Data to pass to the onRegistered function of the hook
    /// @return New flags configuration storage data
    function _addHook(
        bytes memory flags,
        IStrategyHook newHook,
        bytes memory configData
    ) private returns (bytes memory) {
        Errors.verifyNotZero(address(newHook), "newHook");

        uint8 supportedHookFunctions = newHook.getFnFlags();
        Errors.verifyNotZero(supportedHookFunctions, "supportedHookFunctions");
        uint256 fnToCheck = 1;

        bytes memory newFlagsData;
        uint256 bytesIndex = 0;

        // Loop over all the functions hook slots and
        // build our replacement storage data, newFlagsData
        // Splice in our new values where we need
        while (fnToCheck < MAX_FN_FLAG) {
            uint256 hookIx = 0;
            bool needToSet = supportedHookFunctions & fnToCheck == fnToCheck;
            bool set = false;
            while (hookIx < NUM_HOOKS) {
                // Get our existing value
                bytes memory val = LibBytes.slice(flags, bytesIndex, bytesIndex + 20);

                if (needToSet && !set) {
                    address addrVal;
                    assembly {
                        addrVal := shr(96, mload(add(val, 32)))
                    }

                    if (addrVal == address(newHook)) {
                        revert HookAlreadySet(addrVal, fnToCheck);
                    } else if (addrVal == address(0)) {
                        // If the space is empty we can use it
                        set = true;
                        newFlagsData = LibBytes.concat(newFlagsData, abi.encodePacked(newHook));
                    } else {
                        // Space isn't empty so forward the existing value
                        newFlagsData = LibBytes.concat(newFlagsData, val);
                    }
                } else {
                    // We don't need to set, or already set, so just forward the current value
                    newFlagsData = LibBytes.concat(newFlagsData, val);
                }

                unchecked {
                    ++hookIx;
                    bytesIndex += 20;
                }
            }

            if (needToSet && !set) {
                // We needed to configure the hook but couldn't. Means we couldn't find
                // a spot which implies we have the maximum number already configured
                revert MaxHooksSet();
            }

            unchecked {
                fnToCheck *= 2;
            }
        }

        // We were able to add it to the config, so set it up
        newHook.onRegistered(configData);

        return newFlagsData;
    }
}

File 56 of 87 : IStrategyHook.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2025 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { AutopoolDebt } from "src/vault/libs/AutopoolDebt.sol";
import { ProcessRebalanceParams } from "src/vault/libs/AutopoolState.sol";

// @dev Do not change the order of these, we rely on the index
enum HookFunctionIndex {
    onRebalanceStart,
    onRebalanceOutAssetsReady,
    onRebalanceInAssetsReturned,
    onRebalanceDestinationVaultUpdated,
    onRebalanceFeeProfitHandlingComplete,
    onRebalanceComplete,
    onDestinationDebtReport,
    onNavUpdate
}

interface IStrategyHook {
    /// @notice Returns the flags that represent the functions to call on this hook
    function getFnFlags() external view returns (uint8);

    /// @notice Fires when the hook has been registered with an Autopool
    /// @param registrationData Any data needed during registration such as initial configuration
    function onRegistered(
        bytes memory registrationData
    ) external;

    /// @notice Fires when the hook as been unregistered with an Autopool
    /// @param cleanupData Any information needed to run cleanup operations
    function onUnregistered(
        bytes memory cleanupData
    ) external;

    /// =====================================================
    /// Rebalance Flow
    /// - Functions are defined in the order that they fire
    /// - Any function may revert to stop a rebalance
    /// - OnNavUpdate interjects at the end
    /// =====================================================

    /// Flag 1
    /// @notice Fires at the start of a rebalance before any assets are moved
    /// @dev No LP/Idle changes have occurred yet
    /// @param params Rebalance parameters
    /// @param solverCaller Solver who initiated the rebalance
    function onRebalanceStart(ProcessRebalanceParams calldata params, address solverCaller) external;

    /// Flag 2
    /// @notice Fires when LP has been removed from a DestinationVault but before solver has control
    /// @param params Rebalance parameters
    /// @param solverCaller Solver who initiated the rebalance
    /// @dev When this is an idle-out, the state of assets between here and Start() is the same
    function onRebalanceOutAssetsReady(ProcessRebalanceParams calldata params, address solverCaller) external;

    /// Flag 4
    /// @notice Fires when LP/Idle has been returned from the Solver
    /// @param params Rebalance parameters
    /// @param solverCaller Solver who initiated the rebalance
    /// @dev Solver has performed all market operations at this point
    function onRebalanceInAssetsReturned(ProcessRebalanceParams calldata params, address solverCaller) external;

    /// Flag 8
    /// @notice Fires after assets have been deposited into DestinationVault
    /// @param params Rebalance parameters
    /// @param solverCaller Solver who initiated the rebalance
    /// @dev When this is an idle-in, the state of assets doesn't change between InAssetsReturned() and here
    function onRebalanceDestinationVaultUpdated(
        ProcessRebalanceParams calldata params,
        address solverCaller
    ) external;

    /// Flag 16
    /// @notice Fires after any fee and profit handling has occurred
    /// @param params Rebalance parameters
    /// @param solverCaller Solver who initiated the rebalance
    /// @dev Autopool totalSupply() should be steady at this point
    function onRebalanceFeeProfitHandlingComplete(
        ProcessRebalanceParams calldata params,
        address solverCaller
    ) external;

    /// Flag 32
    /// @notice Fires at the end of the rebalance
    /// @param params Rebalance parameters
    /// @param solverCaller Solver who initiated the rebalance
    function onRebalanceComplete(ProcessRebalanceParams calldata params, address solverCaller) external;

    /// =====================================================
    /// Debt Reporting
    /// - Functions should not revert
    /// =====================================================

    /// Flag 64
    /// @notice Fires for any custom tracking of debt valuations
    /// @dev Autopool has possession of auto-compounded rewards
    /// @param destination Target DestinationVault address
    /// @param debtResult Change in values due to debt reporting
    function onDestinationDebtReport(address destination, AutopoolDebt.IdleDebtUpdates memory debtResult) external;

    /// =====================================================
    /// Nav Updates
    /// - Functions should not revert
    /// =====================================================

    /// Flag 128
    /// @notice Fires after any nav update
    /// @param assetChanges New and old assets and totalSupply
    /// @dev Also fires immediately before onRebalanceFeeProfitHandlingComplete()
    function onNavUpdate(
        AutopoolDebt.AssetChanges memory assetChanges
    ) external;
}

File 57 of 87 : MainRewarder.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { ReentrancyGuard } from "openzeppelin-contracts/security/ReentrancyGuard.sol";
import { EnumerableSet } from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";

import { ISystemRegistry } from "src/interfaces/ISystemRegistry.sol";

import { IBaseRewarder } from "src/interfaces/rewarders/IBaseRewarder.sol";
import { IMainRewarder } from "src/interfaces/rewarders/IMainRewarder.sol";
import { IExtraRewarder } from "src/interfaces/rewarders/IExtraRewarder.sol";
import { AbstractRewarder } from "src/rewarders/AbstractRewarder.sol";

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

/**
 * @title MainRewarder
 * @dev Contract is abstract to enforce proper role designation on construction
 * @notice The MainRewarder contract extends the AbstractRewarder and
 * manages the distribution of main rewards along with additional rewards
 * from ExtraRewarder contracts.
 */
abstract contract MainRewarder is AbstractRewarder, IMainRewarder, ReentrancyGuard {
    using EnumerableSet for EnumerableSet.AddressSet;

    /// @notice Maximum amount of extra rewards addresses that can be registered
    uint256 public constant MAX_EXTRA_REWARDS = 15;

    /// @notice True if additional reward tokens/contracts are allowed to be added
    /// @dev Destination Vaults should not allow extras. Autopool's should.
    bool public immutable allowExtraRewards;

    /// @notice Extra rewards can be added, but not removed, ref: https://github.com/Tokemak/v2-core/issues/659
    EnumerableSet.AddressSet private _extraRewards;

    uint256 private _totalSupply;
    mapping(address => uint256) private _balances;

    constructor(
        ISystemRegistry _systemRegistry,
        address _rewardToken,
        uint256 _newRewardRatio,
        uint256 _durationInBlock,
        bytes32 _rewardRole,
        bool _allowExtraRewards
    ) AbstractRewarder(_systemRegistry, _rewardToken, _newRewardRatio, _durationInBlock, _rewardRole) {
        // slither-disable-next-line missing-zero-check
        allowExtraRewards = _allowExtraRewards;
    }

    /// @inheritdoc IMainRewarder
    function extraRewardsLength() external view returns (uint256) {
        return _extraRewards.length();
    }

    /// @inheritdoc IMainRewarder
    function addExtraReward(
        address reward
    ) external hasRole(rewardRole) {
        if (!allowExtraRewards) {
            revert ExtraRewardsNotAllowed();
        }
        if (_extraRewards.length() >= MAX_EXTRA_REWARDS) {
            revert MaxExtraRewardsReached();
        }
        Errors.verifyNotZero(reward, "reward");

        if (!_extraRewards.add(reward)) {
            revert Errors.ItemExists();
        }

        emit ExtraRewardAdded(reward);
    }

    /// @inheritdoc IMainRewarder
    function getExtraRewarder(
        uint256 index
    ) external view returns (IExtraRewarder rewarder) {
        return IExtraRewarder(_extraRewards.at(index));
    }

    /// @inheritdoc IMainRewarder
    function extraRewards() external view returns (address[] memory) {
        return _extraRewards.values();
    }

    function _withdraw(address account, uint256 amount, bool claim) internal {
        _updateReward(account);
        _withdrawAbstractRewarder(account, amount);

        uint256 length = _extraRewards.length();
        for (uint256 i = 0; i < length; ++i) {
            // No need to worry about reentrancy here
            // slither-disable-next-line reentrancy-no-eth
            IExtraRewarder(_extraRewards.at(i)).withdraw(account, amount);
        }

        if (claim) {
            _processRewards(account, account, true);
        }

        // slither-disable-next-line events-maths
        _totalSupply -= amount;
        _balances[account] -= amount;
    }

    function _stake(address account, uint256 amount) internal {
        _updateReward(account);
        _stakeAbstractRewarder(account, amount);

        uint256 length = _extraRewards.length();
        for (uint256 i = 0; i < length; ++i) {
            // No need to worry about reentrancy here
            // slither-disable-next-line reentrancy-no-eth
            IExtraRewarder(_extraRewards.at(i)).stake(account, amount);
        }

        // slither-disable-next-line events-maths
        _totalSupply += amount;
        _balances[account] += amount;
    }

    /// @inheritdoc IBaseRewarder
    function getReward() external nonReentrant {
        _updateReward(msg.sender);
        _processRewards(msg.sender, msg.sender, true);
    }

    function _getReward(address account, address recipient, bool claimExtras) internal nonReentrant {
        _updateReward(account);
        _processRewards(account, recipient, claimExtras);
    }

    /// @inheritdoc IBaseRewarder
    function totalSupply() public view override(AbstractRewarder, IBaseRewarder) returns (uint256) {
        return _totalSupply;
    }

    /// @inheritdoc IBaseRewarder
    function balanceOf(
        address account
    ) public view override(AbstractRewarder, IBaseRewarder) returns (uint256) {
        return _balances[account];
    }

    function _processRewards(address account, address recipient, bool claimExtras) internal {
        _getReward(account, recipient);
        uint256 length = _extraRewards.length();

        //also get rewards from linked rewards
        if (claimExtras) {
            for (uint256 i = 0; i < length; ++i) {
                IExtraRewarder(_extraRewards.at(i)).getReward(account, recipient);
            }
        }
    }
}

File 58 of 87 : draft-IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

File 59 of 87 : ERC1967Proxy.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/ERC1967/ERC1967Proxy.sol)

pragma solidity ^0.8.0;

import "../Proxy.sol";
import "./ERC1967Upgrade.sol";

/**
 * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
 * implementation address that can be changed. This address is stored in storage in the location specified by
 * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
 * implementation behind the proxy.
 */
contract ERC1967Proxy is Proxy, ERC1967Upgrade {
    /**
     * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
     *
     * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
     * function call, and allows initializing the storage of the proxy like a Solidity constructor.
     */
    constructor(address _logic, bytes memory _data) payable {
        _upgradeToAndCall(_logic, _data, false);
    }

    /**
     * @dev Returns the current implementation address.
     */
    function _implementation() internal view virtual override returns (address impl) {
        return ERC1967Upgrade._getImplementation();
    }
}

File 60 of 87 : IAccessControlEnumerable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)

pragma solidity ^0.8.0;

import "./IAccessControl.sol";

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

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

File 61 of 87 : ISyncSwapper.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { ISwapRouter } from "src/interfaces/swapper/ISwapRouter.sol";

interface ISyncSwapper {
    error DataMismatch(string element);
    error InvalidIndex();

    /**
     * @notice Returns address of swap router that can access SyncSwapper contract
     */
    function router() external view returns (ISwapRouter);

    /**
     * @notice Swaps sellToken for buyToken
     * @param pool The address of the pool for the swapper
     * @param sellTokenAddress The address of the token to sell
     * @param sellAmount The amount of sellToken to sell
     * @param buyTokenAddress The address of the token to buy
     * @param minBuyAmount The minimum amount of buyToken expected
     * @param data Additional data used differently by the different swappers
     * @return actualBuyAmount The actual amount received from the swap
     */
    function swap(
        address pool,
        address sellTokenAddress,
        uint256 sellAmount,
        address buyTokenAddress,
        uint256 minBuyAmount,
        bytes memory data
    ) external returns (uint256 actualBuyAmount);

    /**
     * @notice Validates that the swapData contains the correct information, ensuring that the encoded data contains the
     * correct 'fromAddress' and 'toAddress' (swapData.token), and verifies that these tokens are in the pool
     * @dev This function should revert with a DataMismatch error if the swapData is invalid
     * @param fromAddress The address from which the swap originates
     * @param swapData The data associated with the swap that needs to be validated
     */
    function validate(address fromAddress, ISwapRouter.SwapData memory swapData) external view;
}

File 62 of 87 : IAutopilotRouterBase.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IAutopool } from "src/interfaces/vault/IAutopool.sol";
import { IMainRewarder } from "src/interfaces/rewarders/IMainRewarder.sol";
import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol";

/**
 * @title AutopoolETH Router Base Interface
 * @notice A canonical router between AutopoolETHs
 *
 * The base router is a multicall style router inspired by Uniswap v3 with built-in features for permit,
 * WETH9 wrap/unwrap, and ERC20 token pulling/sweeping/approving. It includes methods for the four mutable
 * ERC4626 functions deposit/mint/withdraw/redeem as well.
 *
 * These can all be arbitrarily composed using the multicall functionality of the router.
 *
 * NOTE the router is capable of pulling any approved token from your wallet. This is only possible when
 * your address is msg.sender, but regardless be careful when interacting with the router or ERC4626 Vaults.
 * The router makes no special considerations for unique ERC20 implementations such as fee on transfer.
 * There are no built in protections for unexpected behavior beyond enforcing the minSharesOut is received.
 */
interface IAutopilotRouterBase {
    /// @notice thrown when amount of assets received is below the min set by caller
    error MinAmountError();

    /// @notice thrown when amount of shares received is below the min set by caller
    error MinSharesError();

    /// @notice thrown when amount of assets received is above the max set by caller
    error MaxAmountError();

    /// @notice thrown when amount of shares received is above the max set by caller
    error MaxSharesError();

    /// @notice thrown when timestamp is too old
    error TimestampTooOld();

    /**
     * @notice mint `shares` from an ERC4626 vault.
     * @param vault The AutopoolETH to mint shares from.
     * @param to The destination of ownership shares.
     * @param shares The amount of shares to mint from `vault`.
     * @param maxAmountIn The max amount of assets used to mint.
     * @return amountIn the amount of assets used to mint by `to`.
     * @dev throws MaxAmountError
     */
    function mint(
        IAutopool vault,
        address to,
        uint256 shares,
        uint256 maxAmountIn
    ) external payable returns (uint256 amountIn);

    /**
     * @notice deposit `amount` to an ERC4626 vault.
     * @param vault The AutopoolETH to deposit assets to.
     * @param to The destination of ownership shares.
     * @param amount The amount of assets to deposit to `vault`.
     * @param minSharesOut The min amount of `vault` shares received by `to`.
     * @return sharesOut the amount of shares received by `to`.
     * @dev throws MinSharesError
     */
    function deposit(
        IAutopool vault,
        address to,
        uint256 amount,
        uint256 minSharesOut
    ) external payable returns (uint256 sharesOut);

    /**
     * @notice withdraw `amount` from an ERC4626 vault.
     * @param vault The AutopoolETH to withdraw assets from.
     * @param to The destination of assets.
     * @param amount The amount of assets to withdraw from vault.
     * @param maxSharesOut The max amount of shares burned for assets requested.
     * @return sharesOut the amount of shares received by `to`.
     * @dev throws MaxSharesError
     */
    function withdraw(
        IAutopool vault,
        address to,
        uint256 amount,
        uint256 maxSharesOut
    ) external payable returns (uint256 sharesOut);

    /**
     * @notice redeem `shares` shares from a AutopoolETH
     * @param vault The AutopoolETH to redeem shares from.
     * @param to The destination of assets.
     * @param shares The amount of shares to redeem from vault.
     * @param minAmountOut The min amount of assets received by `to`.
     * @return amountOut the amount of assets received by `to`.
     * @dev throws MinAmountError
     */
    function redeem(
        IAutopool vault,
        address to,
        uint256 shares,
        uint256 minAmountOut
    ) external payable returns (uint256 amountOut);

    /// @notice Stakes vault token to corresponding rewarder.
    /// @param vault IERC20 instance of an Autopool to stake to.
    /// @param maxAmount Maximum amount for user to stake.  Amount > balanceOf(user) will stake all present tokens.
    /// @return staked Returns total amount staked.
    function stakeVaultToken(IERC20 vault, uint256 maxAmount) external payable returns (uint256 staked);

    /// @notice Unstakes vault token from corresponding rewarder.
    /// @param vault IAutopool instance of the vault token to withdraw.
    /// @param rewarder Rewarder to withdraw from.
    /// @param maxAmount Amount of vault token to withdraw Amount > balanceOf(user) will withdraw all owned tokens.
    /// @param claim Claiming rewards or not on unstaking.
    /// @return withdrawn Amount of vault token withdrawn.
    function withdrawVaultToken(
        IAutopool vault,
        IMainRewarder rewarder,
        uint256 maxAmount,
        bool claim
    ) external payable returns (uint256 withdrawn);

    /// @notice Claims rewards on user stake of vault token.
    /// @param vault IAutopool instance of vault token to claim rewards for.
    /// @param rewarder Rewarder to claim rewards from.
    /// @param recipient Address to claim rewards for.
    function claimAutopoolRewards(IAutopool vault, IMainRewarder rewarder, address recipient) external payable;

    /// @notice Checks if timestamp is expired. Purpose is to check the execution deadline with the multicall.
    /// @param timestamp Timestamp to check.
    /// @dev throws TimestampTooOld. Payable to allow for multicall.
    function expiration(
        uint256 timestamp
    ) external payable;
}

File 63 of 87 : IRewards.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2024 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

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

/**
 *  @title Validates and distributes Vault token rewards based on the
 *  the signed and submitted payloads
 */
interface IRewards {
    struct Recipient {
        uint256 chainId;
        uint256 cycle;
        address wallet;
        uint256 amount;
    }

    event SignerSet(address newSigner);
    event Claimed(uint256 cycle, address recipient, uint256 amount);

    /// @notice Get the underlying token rewards are paid in
    /// @return Token address
    function rewardToken() external view returns (IERC20);

    /// @notice Get the current payload signer;
    /// @return Signer address
    function rewardsSigner() external view returns (address);

    /// @notice Check the amount an account has already claimed
    /// @param account Account to check
    /// @return Amount already claimed
    function claimedAmounts(
        address account
    ) external view returns (uint256);

    /// @notice Get the amount that is claimable based on the provided payload
    /// @param recipient Published rewards payload
    /// @return Amount claimable if the payload is signed
    function getClaimableAmount(
        Recipient calldata recipient
    ) external view returns (uint256);

    /// @notice Change the signer used to validate payloads
    /// @param newSigner The new address that will be signing rewards payloads
    function setSigner(
        address newSigner
    ) external;

    /// @notice Claim your rewards
    /// @param recipient Published rewards payload
    /// @param v v component of the payload signature
    /// @param r r component of the payload signature
    /// @param s s component of the payload signature
    function claim(Recipient calldata recipient, uint8 v, bytes32 r, bytes32 s) external returns (uint256);

    /// @notice Claim rewards on behalf of another account , invoked primarily by the router
    /// @param recipient Published rewards payload
    /// @param v v component of the payload signature
    /// @param r r component of the payload signature
    /// @param s s component of the payload signature
    function claimFor(Recipient calldata recipient, uint8 v, bytes32 r, bytes32 s) external returns (uint256);

    /// @notice Generate the hash of the payload
    /// @param recipient Published rewards payload
    /// @return Hash of the payload
    function genHash(
        Recipient memory recipient
    ) external view returns (bytes32);
}

File 64 of 87 : IAsyncSwapper.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

struct SwapParams {
    /// @dev The address of the token to be sold.
    address sellTokenAddress;
    /// @dev The amount of tokens to be sold.
    uint256 sellAmount;
    /// @dev The address of the token to be bought.
    address buyTokenAddress;
    /// @dev The expected minimum amount of tokens to be bought.
    uint256 buyAmount;
    /// @dev Data payload to be used for complex swap operations.
    bytes data;
    /// @dev Extra data payload reserved for future development. This field allows for additional information
    /// or functionality to be added without changing the struct and interface.
    bytes extraData;
    /// @dev Execution deadline in timestamp format
    uint256 deadline;
}

interface IAsyncSwapper {
    error TokenAddressZero();
    error SwapFailed();
    error InsufficientBuyAmountReceived(uint256 buyTokenAmountReceived, uint256 buyAmount);
    error InsufficientSellAmount();
    error InsufficientBuyAmount();
    error InsufficientBalance(uint256 balanceNeeded, uint256 balanceAvailable);

    event Swapped(
        address indexed sellTokenAddress,
        address indexed buyTokenAddress,
        uint256 sellAmount,
        uint256 buyAmount,
        uint256 buyTokenAmountReceived
    );

    /**
     * @notice Swaps sellToken for buyToken
     * @param swapParams Encoded swap data
     * @return buyTokenAmountReceived The amount of buyToken received from the swap
     */
    function swap(
        SwapParams memory swapParams
    ) external returns (uint256 buyTokenAmountReceived);
}

File 65 of 87 : ISwapRouterV2.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { ISwapRouter } from "src/interfaces/swapper/ISwapRouter.sol";

interface ISwapRouterV2 is ISwapRouter {
    struct UserSwapData {
        address fromToken;
        address toToken;
        address target;
        bytes data;
    }

    function initTransientSwap(
        UserSwapData[] memory customRoutes
    ) external;

    function exitTransientSwap() external;
}

File 66 of 87 : IDestinationAdapter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/**
 * @title IDestinationAdapter
 * @dev This is a super-interface to unify different types of adapters to be registered in Destination Registry.
 *      Specific interface type is defined by extending from this interface.
 */
interface IDestinationAdapter {
    error MustBeMoreThanZero();
    error ArraysLengthMismatch();
    error BalanceMustIncrease();
    error MinLpAmountNotReached();
    error LpTokenAmountMismatch();
    error NoNonZeroAmountProvided();
    error InvalidBalanceChange();
    error InvalidAddress(address);
}

File 67 of 87 : IDestinationVaultFactory.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { ISystemComponent } from "src/interfaces/ISystemComponent.sol";

/// @notice Creates and registers Destination Vaults for the system
interface IDestinationVaultFactory is ISystemComponent {
    /// @notice Creates a vault of the specified type
    /// @dev vaultType will be bytes32 encoded and checked that a template is registered
    /// @param vaultType human readable key of the vault template
    /// @param baseAsset Base asset of the system. WETH/USDC/etc
    /// @param underlyer Underlying asset the vault will wrap
    /// @param incentiveCalculator Incentive calculator of the vault
    /// @param additionalTrackedTokens Any tokens in addition to base and underlyer that should be tracked
    /// @param salt Contracts are created via CREATE2 with this value
    /// @param params params to be passed to vaults initialize function
    /// @return vault address of the newly created destination vault
    function create(
        string memory vaultType,
        address baseAsset,
        address underlyer,
        address incentiveCalculator,
        address[] memory additionalTrackedTokens,
        bytes32 salt,
        bytes memory params
    ) external returns (address vault);

    /// @notice Sets the default reward ratio
    /// @param rewardRatio new default reward ratio
    function setDefaultRewardRatio(
        uint256 rewardRatio
    ) external;

    /// @notice Sets the default reward block duration
    /// @param blockDuration new default reward block duration
    function setDefaultRewardBlockDuration(
        uint256 blockDuration
    ) external;
}

File 68 of 87 : IStatsCalculator.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

/// @title Capture information about a pool or destination
interface IStatsCalculator {
    /// @notice thrown when no snapshot is taken
    error NoSnapshotTaken();

    /// @notice The id for this instance of a calculator
    function getAprId() external view returns (bytes32);

    /// @notice The id of the underlying asset/pool/destination this calculator represents
    /// @dev This may be a generated address
    function getAddressId() external view returns (address);

    /// @notice Setup the calculator after it has been copied
    /// @dev Should only be executed one time
    /// @param dependentAprIds apr ids that cover the dependencies of this calculator
    /// @param initData setup data specific to this type of calculator
    function initialize(bytes32[] calldata dependentAprIds, bytes calldata initData) external;

    /// @notice Capture stat data about this setup
    function snapshot() external;

    /// @notice Indicates if a snapshot should be taken
    /// @return takeSnapshot if true then a snapshot should be taken. If false, calling snapshot will do nothing
    function shouldSnapshot() external view returns (bool takeSnapshot);

    /// @dev Enum representing the snapshot status for a given rewarder (Convex and Aura) or reward token (Maverick)
    enum SnapshotStatus {
        noSnapshot, // Indicates that no snapshot has been taken yet for the rewarder.
        tooSoon, // Indicates that it's too soon to take another snapshot since the last one.
        shouldFinalize, // Indicates that the conditions are met for finalizing a snapshot.
        shouldRestart // Indicates that the conditions are met for restarting a snapshot.

    }
}

File 69 of 87 : IDestinationVault.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IERC20Metadata as IERC20 } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";

import { IBaseAssetVault } from "src/interfaces/vault/IBaseAssetVault.sol";
import { IMainRewarder } from "src/interfaces/rewarders/IMainRewarder.sol";
import { IDexLSTStats } from "src/interfaces/stats/IDexLSTStats.sol";
import { ISystemComponent } from "src/interfaces/ISystemComponent.sol";

interface IDestinationVault is ISystemComponent, IBaseAssetVault, IERC20 {
    enum VaultShutdownStatus {
        Active,
        Deprecated,
        Exploit
    }

    error LogicDefect();
    error BaseAmountReceived(uint256 amount);

    /* ******************************** */
    /* View                             */
    /* ******************************** */

    /// @notice A full unit of this vault
    // solhint-disable-next-line func-name-mixedcase
    function ONE() external view returns (uint256);

    /// @notice The asset that is deposited into the vault
    function underlying() external view returns (address);

    /// @notice The total supply of the underlying asset
    function underlyingTotalSupply() external view returns (uint256);

    /// @notice The asset that rewards and withdrawals to the Autopool are denominated in
    /// @inheritdoc IBaseAssetVault
    function baseAsset() external view override returns (address);

    /// @notice Debt balance of underlying asset that is in contract.  This
    ///     value includes only assets that are known as debt by the rest of the
    ///     system (i.e. transferred in on rebalance), and does not include
    ///     extraneous amounts of underlyer that may have ended up in this contract.
    function internalDebtBalance() external view returns (uint256);

    /// @notice Debt balance of underlying asset staked externally.  This value only
    ///     includes assets known as debt to the rest of the system, and does not include
    ///     any assets staked on behalf of the DV in external contracts.
    function externalDebtBalance() external view returns (uint256);

    /// @notice Returns true value of _underlyer in DV.  Debt + tokens that may have
    ///     been transferred into the contract outside of rebalance.
    function internalQueriedBalance() external view returns (uint256);

    /// @notice Returns true value of staked _underlyer in external contract.  This
    ///     will include any _underlyer that has been staked on behalf of the DV.
    function externalQueriedBalance() external view returns (uint256);

    /// @notice Balance of underlying debt, sum of `externalDebtBalance()` and `internalDebtBalance()`.
    function balanceOfUnderlyingDebt() external view returns (uint256);

    /// @notice Rewarder for this vault
    function rewarder() external view returns (address);

    /// @notice Exchange this destination vault points to
    function exchangeName() external view returns (string memory);

    /// @notice The type of pool associated with this vault
    function poolType() external view returns (string memory);

    /// @notice If the pool only deals in ETH when adding or removing liquidity
    function poolDealInEth() external view returns (bool);

    /// @notice Tokens that base asset can be swapped into
    function underlyingTokens() external view returns (address[] memory);

    /// @notice Gets the reserves of the underlying tokens
    function underlyingReserves() external view returns (address[] memory tokens, uint256[] memory amounts);

    /* ******************************** */
    /* Events                           */
    /* ******************************** */

    event Donated(address sender, uint256 amount);
    event Withdraw(
        uint256 target, uint256 actual, uint256 debtLoss, uint256 claimLoss, uint256 fromIdle, uint256 fromDebt
    );
    event UpdateSignedMessage(bytes32 hash, bool flag);

    /* ******************************** */
    /* Errors                           */
    /* ******************************** */

    error ZeroAddress(string paramName);
    error InvalidShutdownStatus(VaultShutdownStatus status);

    /* ******************************** */
    /* Functions                        */
    /* ******************************** */

    /// @notice Setup the contract. These will be cloned so no constructor
    /// @param baseAsset_ Base asset of the system. WETH/USDC/etc
    /// @param underlyer_ Underlying asset the vault will wrap
    /// @param rewarder_ Reward tracker for this vault
    /// @param incentiveCalculator_ Incentive calculator for this vault
    /// @param additionalTrackedTokens_ Additional tokens that should be considered 'tracked'
    /// @param params_ Any extra parameters needed to setup the contract
    function initialize(
        IERC20 baseAsset_,
        IERC20 underlyer_,
        IMainRewarder rewarder_,
        address incentiveCalculator_,
        address[] memory additionalTrackedTokens_,
        bytes memory params_
    ) external;

    function getRangePricesLP() external returns (uint256 spotPrice, uint256 safePrice, bool isSpotSafe);

    /// @notice Calculates the current value of a portion of the debt based on shares
    /// @dev Queries the current value of all tokens we have deployed, whether its a single place, multiple, staked, etc
    /// @param shares The number of shares to value
    /// @return value The current value of our debt in terms of the baseAsset
    function debtValue(
        uint256 shares
    ) external returns (uint256 value);

    /// @notice Collects any earned rewards from staking, incentives, etc. Transfers to sender
    /// @dev Should be limited to LIQUIDATOR_MANAGER. Rewards must be collected before claimed
    /// @return amounts amount of rewards claimed for each token
    /// @return tokens tokens claimed
    function collectRewards() external returns (uint256[] memory amounts, address[] memory tokens);

    /// @notice Pull any non-tracked token to the specified destination
    /// @dev Should be limited to TOKEN_RECOVERY_MANAGER
    function recover(address[] calldata tokens, uint256[] calldata amounts, address[] calldata destinations) external;

    /// @notice Recovers any extra underlying both in DV and staked externally not tracked as debt.
    /// @dev Should be limited to TOKEN_SAVER_ROLE.
    /// @param destination The address to send excess underlyer to.
    function recoverUnderlying(
        address destination
    ) external;

    /// @notice Deposit underlying to receive destination vault shares
    /// @param amount amount of base lp asset to deposit
    function depositUnderlying(
        uint256 amount
    ) external returns (uint256 shares);

    /// @notice Withdraw underlying by burning destination vault shares
    /// @param shares amount of destination vault shares to burn
    /// @param to destination of the underlying asset
    /// @return amount underlyer amount 'to' received
    function withdrawUnderlying(uint256 shares, address to) external returns (uint256 amount);

    /// @notice Burn specified shares for underlyer swapped to base asset
    /// @param shares amount of vault shares to burn
    /// @param to destination of the base asset
    /// @return amount base asset amount 'to' received
    /// @return tokens the tokens burned to get the base asset
    /// @return tokenAmounts the amount of the tokens burned to get the base asset
    function withdrawBaseAsset(
        uint256 shares,
        address to
    ) external returns (uint256 amount, address[] memory tokens, uint256[] memory tokenAmounts);

    /// @notice Mark this vault as shutdown so that autoPools can react
    function shutdown(
        VaultShutdownStatus reason
    ) external;

    /// @notice True if the vault has been shutdown
    function isShutdown() external view returns (bool);

    /// @notice Returns the reason for shutdown (or `Active` if not shutdown)
    function shutdownStatus() external view returns (VaultShutdownStatus);

    /// @notice Stats contract for this vault
    function getStats() external view returns (IDexLSTStats);

    /// @notice get the marketplace rewards
    /// @return rewardTokens list of reward token addresses
    /// @return rewardRates list of reward rates
    function getMarketplaceRewards() external returns (uint256[] memory rewardTokens, uint256[] memory rewardRates);

    /// @notice Get the address of the underlying pool the vault points to
    /// @return poolAddress address of the underlying pool
    function getPool() external view returns (address poolAddress);

    /// @notice Gets the spot price of the underlying LP token
    /// @dev Price validated to be inside our tolerance against safe price. Will revert if outside.
    /// @return price Value of 1 unit of the underlying LP token in terms of the base asset
    function getValidatedSpotPrice() external returns (uint256 price);

    /// @notice Gets the safe price of the underlying LP token
    /// @dev Price validated to be inside our tolerance against spot price. Will revert if outside.
    /// @return price Value of 1 unit of the underlying LP token in terms of the base asset
    function getValidatedSafePrice() external returns (uint256 price);

    /// @notice Get the lowest price we can get for the LP token
    /// @dev This price can be attacked is not validate to be in any range
    /// @return price Value of 1 unit of the underlying LP token in terms of the base asset
    function getUnderlyerFloorPrice() external returns (uint256 price);

    /// @notice Get the highest price we can get for the LP token
    /// @dev This price can be attacked is not validate to be in any range
    /// @return price Value of 1 unit of the underlying LP token in terms of the base asset
    function getUnderlyerCeilingPrice() external returns (uint256 price);

    /// @notice Set or unset  a hash as a signed message
    /// @dev Should be limited to DESTINATION_VAULTS_UPDATER. The set hash is used to validate a signature.
    /// This signature can be potentially used to claim offchain rewards earned by Destination Vaults.
    /// @param hash bytes32 hash of a payload
    /// @param flag boolean flag to indicate a validity of hash
    function setMessage(bytes32 hash, bool flag) external;

    /// @notice Allows to change the incentive calculator of destination vault
    /// @dev Only works when vault is shutdown, also validates the calculator before updating
    /// @param incentiveCalculator address of the new incentive calculator
    function setIncentiveCalculator(
        address incentiveCalculator
    ) external;

    /// @notice Allows to change the extension contract
    /// @dev Should be limited to DESTINATION_VAULT_MANAGER
    /// @param extension contract address
    function setExtension(
        address extension
    ) external;

    /// @notice Calls the execute function of the extension contract
    /// @dev Should be limited to DESTINATION_VAULT_MANAGER
    /// @dev Special care should be taken to ensure that balances hasn't been manipulated
    /// @param data any data that the extension contract needs
    function executeExtension(
        bytes calldata data
    ) external;

    /// @notice Returns the max recoup credit given during the withdraw of an undervalued destination
    function recoupMaxCredit() external view returns (uint256);
}

File 70 of 87 : IERC4626.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { IERC20Metadata } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";

/// @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in https://eips.ethereum.org/EIPS/eip-4626
/// @dev Due to the nature of obtaining estimates for previewing withdraws and redeems, a few functions are not
///     view and therefore do not conform to eip 4626.  These functions use state changing operations
///     to get accurate estimates, reverting after the preview amounts have been obtained.
interface IERC4626 is IERC20Metadata {
    event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);

    event Withdraw(
        address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
    );

    /// @notice Returns the address of the underlying token used for the Vault for accounting, depositing, and
    /// withdrawing.
    /// @dev
    /// - MUST be an ERC-20 token contract.
    /// - MUST NOT revert.
    function asset() external view returns (address assetTokenAddress);

    /// @notice Returns the total amount of the underlying asset that is “managed” by Vault.
    /// @dev
    /// - SHOULD include any compounding that occurs from yield.
    /// - MUST be inclusive of any fees that are charged against assets in the Vault.
    /// - MUST NOT revert.
    function totalAssets() external view returns (uint256 totalManagedAssets);

    /// @notice Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an
    /// ideal
    /// scenario where all the conditions are met.
    /// @dev
    /// - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
    /// - MUST NOT show any variations depending on the caller.
    /// - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
    /// - MUST NOT revert.
    ///
    /// NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
    /// “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
    /// from.
    function convertToShares(
        uint256 assets
    ) external view returns (uint256 shares);

    /// @notice Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an
    /// ideal
    /// scenario where all the conditions are met.
    /// @dev
    /// - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
    /// - MUST NOT show any variations depending on the caller.
    /// - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
    /// - MUST NOT revert.
    ///
    /// NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
    /// “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
    /// from.
    function convertToAssets(
        uint256 shares
    ) external view returns (uint256 assets);

    /// @notice Returns the maximum amount of the underlying asset that can be deposited into the Vault for the
    /// receiver,
    /// through a deposit call.
    /// @dev
    /// - MUST return a limited value if receiver is subject to some deposit limit.
    /// - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
    /// - MUST NOT revert.
    function maxDeposit(
        address receiver
    ) external returns (uint256 maxAssets);

    /// @notice Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block,
    /// given
    /// current on-chain conditions.
    /// @dev
    /// - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
    ///   call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
    ///   in the same transaction.
    /// - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
    ///   deposit would be accepted, regardless if the user has enough tokens approved, etc.
    /// - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
    /// - MUST NOT revert.
    ///
    /// NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
    /// share price or some other type of condition, meaning the depositor will lose assets by depositing.
    function previewDeposit(
        uint256 assets
    ) external returns (uint256 shares);

    /// @notice Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
    /// @dev
    /// - MUST emit the Deposit event.
    /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
    ///   deposit execution, and are accounted for during deposit.
    /// - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
    ///   approving enough underlying tokens to the Vault contract, etc).
    ///
    /// NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    /// @notice Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
    /// @dev
    /// - MUST return a limited value if receiver is subject to some mint limit.
    /// - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
    /// - MUST NOT revert.
    function maxMint(
        address receiver
    ) external returns (uint256 maxShares);

    /// @notice Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
    /// current on-chain conditions.
    /// @dev
    /// - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
    ///   in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
    ///   same transaction.
    /// - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
    ///   would be accepted, regardless if the user has enough tokens approved, etc.
    /// - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
    /// - MUST NOT revert.
    ///
    /// NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
    /// share price or some other type of condition, meaning the depositor will lose assets by minting.
    function previewMint(
        uint256 shares
    ) external returns (uint256 assets);

    /// @notice Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
    /// @dev
    /// - MUST emit the Deposit event.
    /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
    ///   execution, and are accounted for during mint.
    /// - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
    ///   approving enough underlying tokens to the Vault contract, etc).
    ///
    /// NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    /// @notice Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
    /// Vault, through a withdraw call.
    /// @dev
    /// - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
    /// - MUST NOT revert.
    function maxWithdraw(
        address owner
    ) external returns (uint256 maxAssets);

    /// @notice Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
    /// given current on-chain conditions.
    /// @dev
    /// - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
    ///   call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
    ///   called
    ///   in the same transaction.
    /// - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
    ///   the withdrawal would be accepted, regardless if the user has enough shares, etc.
    /// - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
    /// - MUST NOT revert.
    ///
    /// NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
    /// share price or some other type of condition, meaning the depositor will lose assets by depositing.
    function previewWithdraw(
        uint256 assets
    ) external returns (uint256 shares);

    /// @notice Burns shares from owner and sends exactly assets of underlying tokens to receiver.
    /// @dev
    /// - MUST emit the Withdraw event.
    /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
    ///   withdraw execution, and are accounted for during withdraw.
    /// - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
    ///   not having enough shares, etc).
    ///
    /// Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
    /// Those methods should be performed separately.
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);

    /// @notice Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
    /// through a redeem call.
    /// @dev
    /// - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
    /// - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
    /// - MUST NOT revert.
    function maxRedeem(
        address owner
    ) external returns (uint256 maxShares);

    /// @notice Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
    /// given current on-chain conditions.
    /// @dev
    /// - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
    ///   in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
    ///   same transaction.
    /// - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
    ///   redemption would be accepted, regardless if the user has enough shares, etc.
    /// - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
    /// - MUST NOT revert.
    ///
    /// NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
    /// share price or some other type of condition, meaning the depositor will lose assets by redeeming.
    function previewRedeem(
        uint256 shares
    ) external returns (uint256 assets);

    /// @notice Burns exactly shares from owner and sends assets of underlying tokens to receiver.
    /// @dev
    /// - MUST emit the Withdraw event.
    /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
    ///   redeem execution, and are accounted for during redeem.
    /// - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
    ///   not having enough shares, etc).
    ///
    /// NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
    /// Those methods should be performed separately.
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}

File 71 of 87 : ECDSA.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.0;

import "../Strings.sol";

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS,
        InvalidSignatureV // Deprecated in v4.8
    }

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature` or error string. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, signature);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address, RecoverError) {
        bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
        uint8 v = uint8((uint256(vs) >> 255) + 27);
        return tryRecover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     *
     * _Available since v4.2._
     */
    function recover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, r, vs);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address, RecoverError) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature);
        }

        return (signer, RecoverError.NoError);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from `s`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
    }
}

File 72 of 87 : IBaseRewarder.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

interface IBaseRewarder {
    error RecoverDurationPending();

    event RewardAdded(
        uint256 reward,
        uint256 rewardRate,
        uint256 lastUpdateBlock,
        uint256 periodInBlockFinish,
        uint256 historicalRewards
    );
    event UserRewardUpdated(
        address indexed user, uint256 amount, uint256 rewardPerTokenStored, uint256 lastUpdateBlock
    );
    event Staked(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);
    event RewardPaid(address indexed user, address indexed recipient, uint256 reward);
    event QueuedRewardsUpdated(uint256 startingQueuedRewards, uint256 startingNewRewards, uint256 queuedRewards);
    event AddedToWhitelist(address indexed wallet);
    event RemovedFromWhitelist(address indexed wallet);

    event TokeLockDurationUpdated(uint256 newDuration);

    event Recovered(address token, address recipient, uint256 amount);

    /**
     * @notice Claims and transfers all rewards for the specified account
     */
    function getReward() external;

    /**
     * @notice Stakes the specified amount of tokens for the specified account.
     * @param account The address of the account to stake tokens for.
     * @param amount The amount of tokens to stake.
     */
    function stake(address account, uint256 amount) external;

    /**
     * @notice Calculate the earned rewards for an account.
     * @param account Address of the account.
     * @return The earned rewards for the given account.
     */
    function earned(
        address account
    ) external view returns (uint256);

    /**
     * @notice Calculates the rewards per token for the current block.
     * @dev The total amount of rewards available in the system is fixed, and it needs to be distributed among the users
     * based on their token balances and staking duration.
     * Rewards per token represent the amount of rewards that each token is entitled to receive at the current block.
     * The calculation takes into account the reward rate, the time duration since the last update,
     * and the total supply of tokens in the staking pool.
     * @return The updated rewards per token value for the current block.
     */
    function rewardPerToken() external view returns (uint256);

    /**
     * @notice Get the current reward rate per block.
     * @return The current reward rate per block.
     */
    function rewardRate() external view returns (uint256);

    /**
     * @notice Get the current TOKE lock duration.
     * @return The current TOKE lock duration.
     */
    function tokeLockDuration() external view returns (uint256);

    /**
     * @notice Get the last block where rewards are applicable.
     * @return The last block number where rewards are applicable.
     */
    function lastBlockRewardApplicable() external view returns (uint256);

    /**
     * @notice The total amount of tokens staked
     */
    function totalSupply() external view returns (uint256);

    /**
     * @notice The amount of tokens staked for the specified account
     * @param account The address of the account to get the balance of
     */
    function balanceOf(
        address account
    ) external view returns (uint256);

    /**
     * @notice Queue new rewards to be distributed.
     * @param newRewards The amount of new rewards to be queued.
     */
    function queueNewRewards(
        uint256 newRewards
    ) external;

    /**
     * @notice Token distributed as rewards
     * @return reward token address
     */
    function rewardToken() external view returns (address);

    /**
     * @notice Add an address to the whitelist.
     * @param wallet The address to be added to the whitelist.
     */
    function addToWhitelist(
        address wallet
    ) external;

    /**
     * @notice Remove an address from the whitelist.
     * @param wallet The address to be removed from the whitelist.
     */
    function removeFromWhitelist(
        address wallet
    ) external;

    /**
     * @notice Recovers tokens from the rewarder. However, a recovery duration of 1 year is applicable for reward token
     * @param token Address of token
     * @param recipient recipient Address of recipient
     */
    function recover(address token, address recipient) external;

    /**
     * @notice Check if an address is whitelisted.
     * @param wallet The address to be checked.
     * @return bool indicating if the address is whitelisted.
     */
    function isWhitelisted(
        address wallet
    ) external view returns (bool);
}

File 73 of 87 : IExtraRewarder.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IBaseRewarder } from "src/interfaces/rewarders/IBaseRewarder.sol";

interface IExtraRewarder is IBaseRewarder {
    /**
     * @notice Withdraws the specified amount of tokens from the vault for the specified account.
     * @param account The address of the account to withdraw tokens for.
     * @param amount The amount of tokens to withdraw.
     */
    function withdraw(address account, uint256 amount) external;

    /**
     * @notice Claims and transfers all rewards for the specified account from this contract.
     * @param account The address of the account to claim rewards for.
     * @param recipient The address to send the rewards to.
     */
    function getReward(address account, address recipient) external;
}

File 74 of 87 : SSTORE2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// solhint-disable no-inline-assembly

/// @notice Read and write to persistent storage at a fraction of the cost.
/// @notice Copied at
/// https://github.com/Vectorized/solady/blob/ccaed15a964891aa729c9d22670304e88584a480/src/utils/SSTORE2.sol
/// @notice Pragma updated from 8.4 to 8.24
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SSTORE2.sol)
/// @author Saw-mon-and-Natalie (https://github.com/Saw-mon-and-Natalie)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol)
/// @author Modified from SSTORE3 (https://github.com/Philogy/sstore3)
library SSTORE2 {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The proxy initialization code.
    uint256 private constant _CREATE3_PROXY_INITCODE = 0x67363d3d37363d34f03d5260086018f3;

    /// @dev Hash of the `_CREATE3_PROXY_INITCODE`.
    /// Equivalent to `keccak256(abi.encodePacked(hex"67363d3d37363d34f03d5260086018f3"))`.
    bytes32 internal constant CREATE3_PROXY_INITCODE_HASH =
        0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                        CUSTOM ERRORS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Unable to deploy the storage contract.
    error DeploymentFailed();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         WRITE LOGIC                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Writes `data` into the bytecode of a storage contract and returns its address.
    function write(
        bytes memory data
    ) internal returns (address pointer) {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(data) // Let `l` be `n + 1`. +1 as we prefix a STOP opcode.
            /**
             * ---------------------------------------------------+
             * Opcode | Mnemonic       | Stack     | Memory       |
             * ---------------------------------------------------|
             * 61 l   | PUSH2 l        | l         |              |
             * 80     | DUP1           | l l       |              |
             * 60 0xa | PUSH1 0xa      | 0xa l l   |              |
             * 3D     | RETURNDATASIZE | 0 0xa l l |              |
             * 39     | CODECOPY       | l         | [0..l): code |
             * 3D     | RETURNDATASIZE | 0 l       | [0..l): code |
             * F3     | RETURN         |           | [0..l): code |
             * 00     | STOP           |           |              |
             * ---------------------------------------------------+
             * @dev Prefix the bytecode with a STOP opcode to ensure it cannot be called.
             * Also PUSH2 is used since max contract size cap is 24,576 bytes which is less than 2 ** 16.
             */
            // Do a out-of-gas revert if `n + 1` is more than 2 bytes.
            mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
            // Deploy a new contract with the generated creation code.
            pointer := create(0, add(data, 0x15), add(n, 0xb))
            if iszero(pointer) {
                mstore(0x00, 0x30116425) // `DeploymentFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(data, n) // Restore the length of `data`.
        }
    }

    /// @dev Writes `data` into the bytecode of a storage contract with `salt`
    /// and returns its normal CREATE2 deterministic address.
    function writeCounterfactual(bytes memory data, bytes32 salt) internal returns (address pointer) {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(data)
            // Do a out-of-gas revert if `n + 1` is more than 2 bytes.
            mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
            // Deploy a new contract with the generated creation code.
            pointer := create2(0, add(data, 0x15), add(n, 0xb), salt)
            if iszero(pointer) {
                mstore(0x00, 0x30116425) // `DeploymentFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(data, n) // Restore the length of `data`.
        }
    }

    /// @dev Writes `data` into the bytecode of a storage contract and returns its address.
    /// This uses the so-called "CREATE3" workflow,
    /// which means that `pointer` is agnostic to `data, and only depends on `salt`.
    function writeDeterministic(bytes memory data, bytes32 salt) internal returns (address pointer) {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(data)
            mstore(0x00, _CREATE3_PROXY_INITCODE) // Store the `_PROXY_INITCODE`.
            let proxy := create2(0, 0x10, 0x10, salt)
            if iszero(proxy) {
                mstore(0x00, 0x30116425) // `DeploymentFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x14, proxy) // Store the proxy's address.
            // 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).
            // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).
            mstore(0x00, 0xd694)
            mstore8(0x34, 0x01) // Nonce of the proxy contract (1).
            pointer := keccak256(0x1e, 0x17)

            // Do a out-of-gas revert if `n + 1` is more than 2 bytes.
            mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
            if iszero(
                mul( // The arguments of `mul` are evaluated last to first.
                extcodesize(pointer), call(gas(), proxy, 0, add(data, 0x15), add(n, 0xb), codesize(), 0x00))
            ) {
                mstore(0x00, 0x30116425) // `DeploymentFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(data, n) // Restore the length of `data`.
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                    ADDRESS CALCULATIONS                    */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the initialization code hash of the storage contract for `data`.
    /// Used for mining vanity addresses with create2crunch.
    function initCodeHash(
        bytes memory data
    ) internal pure returns (bytes32 hash) {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(data)
            // Do a out-of-gas revert if `n + 1` is more than 2 bytes.
            returndatacopy(returndatasize(), returndatasize(), gt(n, 0xfffe))
            mstore(data, add(0x61000180600a3d393df300, shl(0x40, n)))
            hash := keccak256(add(data, 0x15), add(n, 0xb))
            mstore(data, n) // Restore the length of `data`.
        }
    }

    /// @dev Equivalent to `predictCounterfactualAddress(data, salt, address(this))`
    function predictCounterfactualAddress(bytes memory data, bytes32 salt) internal view returns (address pointer) {
        pointer = predictCounterfactualAddress(data, salt, address(this));
    }

    /// @dev Returns the CREATE2 address of the storage contract for `data`
    /// deployed with `salt` by `deployer`.
    /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
    function predictCounterfactualAddress(
        bytes memory data,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        bytes32 hash = initCodeHash(data);
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and store the bytecode hash.
            mstore8(0x00, 0xff) // Write the prefix.
            mstore(0x35, hash)
            mstore(0x01, shl(96, deployer))
            mstore(0x15, salt)
            predicted := keccak256(0x00, 0x55)
            // Restore the part of the free memory pointer that has been overwritten.
            mstore(0x35, 0)
        }
    }

    /// @dev Equivalent to `predictDeterministicAddress(salt, address(this))`.
    function predictDeterministicAddress(
        bytes32 salt
    ) internal view returns (address pointer) {
        pointer = predictDeterministicAddress(salt, address(this));
    }

    /// @dev Returns the "CREATE3" deterministic address for `salt` with `deployer`.
    function predictDeterministicAddress(bytes32 salt, address deployer) internal pure returns (address pointer) {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x00, deployer) // Store `deployer`.
            mstore8(0x0b, 0xff) // Store the prefix.
            mstore(0x20, salt) // Store the salt.
            mstore(0x40, CREATE3_PROXY_INITCODE_HASH) // Store the bytecode hash.

            mstore(0x14, keccak256(0x0b, 0x55)) // Store the proxy's address.
            mstore(0x40, m) // Restore the free memory pointer.
            // 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).
            // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).
            mstore(0x00, 0xd694)
            mstore8(0x34, 0x01) // Nonce of the proxy contract (1).
            pointer := keccak256(0x1e, 0x17)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         READ LOGIC                         */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Equivalent to `read(pointer, 0, 2 ** 256 - 1)`.
    function read(
        address pointer
    ) internal view returns (bytes memory data) {
        /// @solidity memory-safe-assembly
        assembly {
            data := mload(0x40)
            let n := and(0xffffffffff, sub(extcodesize(pointer), 0x01))
            extcodecopy(pointer, add(data, 0x1f), 0x00, add(n, 0x21))
            mstore(data, n) // Store the length.
            mstore(0x40, add(n, add(data, 0x40))) // Allocate memory.
        }
    }

    /// @dev Equivalent to `read(pointer, start, 2 ** 256 - 1)`.
    function read(address pointer, uint256 start) internal view returns (bytes memory data) {
        /// @solidity memory-safe-assembly
        assembly {
            data := mload(0x40)
            let n := and(0xffffffffff, sub(extcodesize(pointer), 0x01))
            let l := sub(n, and(0xffffff, mul(lt(start, n), start)))
            extcodecopy(pointer, add(data, 0x1f), start, add(l, 0x21))
            mstore(data, mul(sub(n, start), lt(start, n))) // Store the length.
            mstore(0x40, add(data, add(0x40, mload(data)))) // Allocate memory.
        }
    }

    /// @dev Returns a slice of the data on `pointer` from `start` to `end`.
    /// `start` and `end` will be clamped to the range `[0, args.length]`.
    /// The `pointer` MUST be deployed via the SSTORE2 write functions.
    /// Otherwise, the behavior is undefined.
    /// Out-of-gas reverts if `pointer` does not have any code.
    function read(address pointer, uint256 start, uint256 end) internal view returns (bytes memory data) {
        /// @solidity memory-safe-assembly
        assembly {
            data := mload(0x40)
            if iszero(lt(end, 0xffff)) { end := 0xffff }
            let d := mul(sub(end, start), lt(start, end))
            extcodecopy(pointer, add(data, 0x1f), start, add(d, 0x01))
            if iszero(and(0xff, mload(add(data, d)))) {
                let n := sub(extcodesize(pointer), 0x01)
                returndatacopy(returndatasize(), returndatasize(), shr(40, n))
                d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n))))
            }
            mstore(data, d) // Store the length.
            mstore(add(add(data, 0x20), d), 0) // Zeroize the slot after the bytes.
            mstore(0x40, add(add(data, 0x40), d)) // Allocate memory.
        }
    }
}

File 75 of 87 : LibBytes.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// solhint-disable no-inline-assembly

/// @notice Library for byte related operations.
/// @notice Copied at
/// https://github.com/Vectorized/solady/blob/ccaed15a964891aa729c9d22670304e88584a480/src/utils/LibBytes.sol
/// @notice Pragma updated from 8.4 to 8.24
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBytes.sol)
library LibBytes {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STRUCTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Goated bytes storage struct that totally MOGs, no cap, fr.
    /// Uses less gas and bytecode than Solidity's native bytes storage. It's meta af.
    /// Packs length with the first 31 bytes if <255 bytes, so it’s mad tight.
    struct BytesStorage {
        bytes32 _spacer;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The constant returned when the `search` is not found in the bytes.
    uint256 internal constant NOT_FOUND = type(uint256).max;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                  BYTE STORAGE OPERATIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sets the value of the bytes storage `$` to `s`.
    function set(BytesStorage storage $, bytes memory s) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(s)
            let packed := or(0xff, shl(8, n))
            for { let i := 0 } 1 { } {
                if iszero(gt(n, 0xfe)) {
                    i := 0x1f
                    packed := or(n, shl(8, mload(add(s, i))))
                    if iszero(gt(n, i)) { break }
                }
                let o := add(s, 0x20)
                mstore(0x00, $.slot)
                for { let p := keccak256(0x00, 0x20) } 1 { } {
                    sstore(add(p, shr(5, i)), mload(add(o, i)))
                    i := add(i, 0x20)
                    if iszero(lt(i, n)) { break }
                }
                break
            }
            sstore($.slot, packed)
        }
    }

    /// @dev Sets the value of the bytes storage `$` to `s`.
    function setCalldata(BytesStorage storage $, bytes calldata s) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let packed := or(0xff, shl(8, s.length))
            for { let i := 0 } 1 { } {
                if iszero(gt(s.length, 0xfe)) {
                    i := 0x1f
                    packed := or(s.length, shl(8, shr(8, calldataload(s.offset))))
                    if iszero(gt(s.length, i)) { break }
                }
                mstore(0x00, $.slot)
                for { let p := keccak256(0x00, 0x20) } 1 { } {
                    sstore(add(p, shr(5, i)), calldataload(add(s.offset, i)))
                    i := add(i, 0x20)
                    if iszero(lt(i, s.length)) { break }
                }
                break
            }
            sstore($.slot, packed)
        }
    }

    /// @dev Sets the value of the bytes storage `$` to the empty bytes.
    function clear(
        BytesStorage storage $
    ) internal {
        delete $._spacer;
    }

    /// @dev Returns whether the value stored is `$` is the empty bytes "".
    function isEmpty(
        BytesStorage storage $
    ) internal view returns (bool) {
        return uint256($._spacer) & 0xff == uint256(0);
    }

    /// @dev Returns the length of the value stored in `$`.
    function length(
        BytesStorage storage $
    ) internal view returns (uint256 result) {
        result = uint256($._spacer);
        /// @solidity memory-safe-assembly
        assembly {
            let n := and(0xff, result)
            result := or(mul(shr(8, result), eq(0xff, n)), mul(n, iszero(eq(0xff, n))))
        }
    }

    /// @dev Returns the value stored in `$`.
    function get(
        BytesStorage storage $
    ) internal view returns (bytes memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := mload(0x40)
            let o := add(result, 0x20)
            let packed := sload($.slot)
            let n := shr(8, packed)
            for { let i := 0 } 1 { } {
                if iszero(eq(or(packed, 0xff), packed)) {
                    mstore(o, packed)
                    n := and(0xff, packed)
                    i := 0x1f
                    if iszero(gt(n, i)) { break }
                }
                mstore(0x00, $.slot)
                for { let p := keccak256(0x00, 0x20) } 1 { } {
                    mstore(add(o, i), sload(add(p, shr(5, i))))
                    i := add(i, 0x20)
                    if iszero(lt(i, n)) { break }
                }
                break
            }
            mstore(result, n) // Store the length of the memory.
            mstore(add(o, n), 0) // Zeroize the slot after the bytes.
            mstore(0x40, add(add(o, n), 0x20)) // Allocate memory.
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      BYTES OPERATIONS                      */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns `subject` all occurrences of `needle` replaced with `replacement`.
    function replace(
        bytes memory subject,
        bytes memory needle,
        bytes memory replacement
    ) internal pure returns (bytes memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := mload(0x40)
            let needleLen := mload(needle)
            let replacementLen := mload(replacement)
            let d := sub(result, subject) // Memory difference.
            let i := add(subject, 0x20) // Subject bytes pointer.
            mstore(0x00, add(i, mload(subject))) // End of subject.
            if iszero(gt(needleLen, mload(subject))) {
                let subjectSearchEnd := add(sub(mload(0x00), needleLen), 1)
                let h := 0 // The hash of `needle`.
                if iszero(lt(needleLen, 0x20)) { h := keccak256(add(needle, 0x20), needleLen) }
                let s := mload(add(needle, 0x20))
                for { let m := shl(3, sub(0x20, and(needleLen, 0x1f))) } 1 { } {
                    let t := mload(i)
                    // Whether the first `needleLen % 32` bytes of `subject` and `needle` matches.
                    if iszero(shr(m, xor(t, s))) {
                        if h {
                            if iszero(eq(keccak256(i, needleLen), h)) {
                                mstore(add(i, d), t)
                                i := add(i, 1)
                                if iszero(lt(i, subjectSearchEnd)) { break }
                                continue
                            }
                        }
                        // Copy the `replacement` one word at a time.
                        for { let j := 0 } 1 { } {
                            mstore(add(add(i, d), j), mload(add(add(replacement, 0x20), j)))
                            j := add(j, 0x20)
                            if iszero(lt(j, replacementLen)) { break }
                        }
                        d := sub(add(d, replacementLen), needleLen)
                        if needleLen {
                            i := add(i, needleLen)
                            if iszero(lt(i, subjectSearchEnd)) { break }
                            continue
                        }
                    }
                    mstore(add(i, d), t)
                    i := add(i, 1)
                    if iszero(lt(i, subjectSearchEnd)) { break }
                }
            }
            let end := mload(0x00)
            let n := add(sub(d, add(result, 0x20)), end)
            // Copy the rest of the bytes one word at a time.
            for { } lt(i, end) { i := add(i, 0x20) } { mstore(add(i, d), mload(i)) }
            let o := add(i, d)
            mstore(o, 0) // Zeroize the slot after the bytes.
            mstore(0x40, add(o, 0x20)) // Allocate memory.
            mstore(result, n) // Store the length.
        }
    }

    /// @dev Returns the byte index of the first location of `needle` in `subject`,
    /// needleing from left to right, starting from `from`.
    /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
    function indexOf(bytes memory subject, bytes memory needle, uint256 from) internal pure returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := not(0) // Initialize to `NOT_FOUND`.
            for { let subjectLen := mload(subject) } 1 { } {
                if iszero(mload(needle)) {
                    result := from
                    if iszero(gt(from, subjectLen)) { break }
                    result := subjectLen
                    break
                }
                let needleLen := mload(needle)
                let subjectStart := add(subject, 0x20)

                subject := add(subjectStart, from)
                let end := add(sub(add(subjectStart, subjectLen), needleLen), 1)
                let m := shl(3, sub(0x20, and(needleLen, 0x1f)))
                let s := mload(add(needle, 0x20))

                if iszero(and(lt(subject, end), lt(from, subjectLen))) { break }

                if iszero(lt(needleLen, 0x20)) {
                    for { let h := keccak256(add(needle, 0x20), needleLen) } 1 { } {
                        if iszero(shr(m, xor(mload(subject), s))) {
                            if eq(keccak256(subject, needleLen), h) {
                                result := sub(subject, subjectStart)
                                break
                            }
                        }
                        subject := add(subject, 1)
                        if iszero(lt(subject, end)) { break }
                    }
                    break
                }
                for { } 1 { } {
                    if iszero(shr(m, xor(mload(subject), s))) {
                        result := sub(subject, subjectStart)
                        break
                    }
                    subject := add(subject, 1)
                    if iszero(lt(subject, end)) { break }
                }
                break
            }
        }
    }

    /// @dev Returns the byte index of the first location of `needle` in `subject`,
    /// needleing from left to right.
    /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
    function indexOf(bytes memory subject, bytes memory needle) internal pure returns (uint256) {
        return indexOf(subject, needle, 0);
    }

    /// @dev Returns the byte index of the first location of `needle` in `subject`,
    /// needleing from right to left, starting from `from`.
    /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
    function lastIndexOf(
        bytes memory subject,
        bytes memory needle,
        uint256 from
    ) internal pure returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            for { } 1 { } {
                result := not(0) // Initialize to `NOT_FOUND`.
                let needleLen := mload(needle)
                if gt(needleLen, mload(subject)) { break }
                let w := result

                let fromMax := sub(mload(subject), needleLen)
                if iszero(gt(fromMax, from)) { from := fromMax }

                let end := add(add(subject, 0x20), w)
                subject := add(add(subject, 0x20), from)
                if iszero(gt(subject, end)) { break }
                // As this function is not too often used,
                // we shall simply use keccak256 for smaller bytecode size.
                for { let h := keccak256(add(needle, 0x20), needleLen) } 1 { } {
                    if eq(keccak256(subject, needleLen), h) {
                        result := sub(subject, add(end, 1))
                        break
                    }
                    subject := add(subject, w) // `sub(subject, 1)`.
                    if iszero(gt(subject, end)) { break }
                }
                break
            }
        }
    }

    /// @dev Returns the byte index of the first location of `needle` in `subject`,
    /// needleing from right to left.
    /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
    function lastIndexOf(bytes memory subject, bytes memory needle) internal pure returns (uint256) {
        return lastIndexOf(subject, needle, type(uint256).max);
    }

    /// @dev Returns true if `needle` is found in `subject`, false otherwise.
    function contains(bytes memory subject, bytes memory needle) internal pure returns (bool) {
        return indexOf(subject, needle) != NOT_FOUND;
    }

    /// @dev Returns whether `subject` starts with `needle`.
    function startsWith(bytes memory subject, bytes memory needle) internal pure returns (bool result) {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(needle)
            // Just using keccak256 directly is actually cheaper.
            let t := eq(keccak256(add(subject, 0x20), n), keccak256(add(needle, 0x20), n))
            result := lt(gt(n, mload(subject)), t)
        }
    }

    /// @dev Returns whether `subject` ends with `needle`.
    function endsWith(bytes memory subject, bytes memory needle) internal pure returns (bool result) {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(needle)
            let notInRange := gt(n, mload(subject))
            // `subject + 0x20 + max(subject.length - needle.length, 0)`.
            let t := add(add(subject, 0x20), mul(iszero(notInRange), sub(mload(subject), n)))
            // Just using keccak256 directly is actually cheaper.
            result := gt(eq(keccak256(t, n), keccak256(add(needle, 0x20), n)), notInRange)
        }
    }

    /// @dev Returns `subject` repeated `times`.
    function repeat(bytes memory subject, uint256 times) internal pure returns (bytes memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            let l := mload(subject) // Subject length.
            if iszero(or(iszero(times), iszero(l))) {
                result := mload(0x40)
                subject := add(subject, 0x20)
                let o := add(result, 0x20)
                for { } 1 { } {
                    // Copy the `subject` one word at a time.
                    for { let j := 0 } 1 { } {
                        mstore(add(o, j), mload(add(subject, j)))
                        j := add(j, 0x20)
                        if iszero(lt(j, l)) { break }
                    }
                    o := add(o, l)
                    times := sub(times, 1)
                    if iszero(times) { break }
                }
                mstore(o, 0) // Zeroize the slot after the bytes.
                mstore(0x40, add(o, 0x20)) // Allocate memory.
                mstore(result, sub(o, add(result, 0x20))) // Store the length.
            }
        }
    }

    /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive).
    /// `start` and `end` are byte offsets.
    function slice(bytes memory subject, uint256 start, uint256 end) internal pure returns (bytes memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            let l := mload(subject) // Subject length.
            if iszero(gt(l, end)) { end := l }
            if iszero(gt(l, start)) { start := l }
            if lt(start, end) {
                result := mload(0x40)
                let n := sub(end, start)
                let i := add(subject, start)
                let w := not(0x1f)
                // Copy the `subject` one word at a time, backwards.
                for { let j := and(add(n, 0x1f), w) } 1 { } {
                    mstore(add(result, j), mload(add(i, j)))
                    j := add(j, w) // `sub(j, 0x20)`.
                    if iszero(j) { break }
                }
                let o := add(add(result, 0x20), n)
                mstore(o, 0) // Zeroize the slot after the bytes.
                mstore(0x40, add(o, 0x20)) // Allocate memory.
                mstore(result, n) // Store the length.
            }
        }
    }

    /// @dev Returns a copy of `subject` sliced from `start` to the end of the bytes.
    /// `start` is a byte offset.
    function slice(bytes memory subject, uint256 start) internal pure returns (bytes memory result) {
        result = slice(subject, start, type(uint256).max);
    }

    /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive).
    /// `start` and `end` are byte offsets. Faster than Solidity's native slicing.
    function sliceCalldata(
        bytes calldata subject,
        uint256 start,
        uint256 end
    ) internal pure returns (bytes calldata result) {
        /// @solidity memory-safe-assembly
        assembly {
            end := xor(end, mul(xor(end, subject.length), lt(subject.length, end)))
            start := xor(start, mul(xor(start, subject.length), lt(subject.length, start)))
            result.offset := add(subject.offset, start)
            result.length := mul(lt(start, end), sub(end, start))
        }
    }

    /// @dev Returns a copy of `subject` sliced from `start` to the end of the bytes.
    /// `start` is a byte offset. Faster than Solidity's native slicing.
    function sliceCalldata(bytes calldata subject, uint256 start) internal pure returns (bytes calldata result) {
        /// @solidity memory-safe-assembly
        assembly {
            start := xor(start, mul(xor(start, subject.length), lt(subject.length, start)))
            result.offset := add(subject.offset, start)
            result.length := mul(lt(start, subject.length), sub(subject.length, start))
        }
    }

    /// @dev Reduces the size of `subject` to `n`.
    /// If `n` is greater than the size of `subject`, this will be a no-op.
    function truncate(bytes memory subject, uint256 n) internal pure returns (bytes memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := subject
            mstore(mul(lt(n, mload(result)), result), n)
        }
    }

    /// @dev Returns a copy of `subject`, with the length reduced to `n`.
    /// If `n` is greater than the size of `subject`, this will be a no-op.
    function truncatedCalldata(bytes calldata subject, uint256 n) internal pure returns (bytes calldata result) {
        /// @solidity memory-safe-assembly
        assembly {
            result.offset := subject.offset
            result.length := xor(n, mul(xor(n, subject.length), lt(subject.length, n)))
        }
    }

    /// @dev Returns all the indices of `needle` in `subject`.
    /// The indices are byte offsets.
    function indicesOf(bytes memory subject, bytes memory needle) internal pure returns (uint256[] memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            let searchLen := mload(needle)
            if iszero(gt(searchLen, mload(subject))) {
                result := mload(0x40)
                let i := add(subject, 0x20)
                let o := add(result, 0x20)
                let subjectSearchEnd := add(sub(add(i, mload(subject)), searchLen), 1)
                let h := 0 // The hash of `needle`.
                if iszero(lt(searchLen, 0x20)) { h := keccak256(add(needle, 0x20), searchLen) }
                let s := mload(add(needle, 0x20))
                for { let m := shl(3, sub(0x20, and(searchLen, 0x1f))) } 1 { } {
                    let t := mload(i)
                    // Whether the first `searchLen % 32` bytes of `subject` and `needle` matches.
                    if iszero(shr(m, xor(t, s))) {
                        if h {
                            if iszero(eq(keccak256(i, searchLen), h)) {
                                i := add(i, 1)
                                if iszero(lt(i, subjectSearchEnd)) { break }
                                continue
                            }
                        }
                        mstore(o, sub(i, add(subject, 0x20))) // Append to `result`.
                        o := add(o, 0x20)
                        i := add(i, searchLen) // Advance `i` by `searchLen`.
                        if searchLen {
                            if iszero(lt(i, subjectSearchEnd)) { break }
                            continue
                        }
                    }
                    i := add(i, 1)
                    if iszero(lt(i, subjectSearchEnd)) { break }
                }
                mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store the length of `result`.
                // Allocate memory for result.
                // We allocate one more word, so this array can be recycled for {split}.
                mstore(0x40, add(o, 0x20))
            }
        }
    }

    /// @dev Returns a arrays of bytess based on the `delimiter` inside of the `subject` bytes.
    function split(bytes memory subject, bytes memory delimiter) internal pure returns (bytes[] memory result) {
        uint256[] memory indices = indicesOf(subject, delimiter);
        /// @solidity memory-safe-assembly
        assembly {
            let w := not(0x1f)
            let indexPtr := add(indices, 0x20)
            let indicesEnd := add(indexPtr, shl(5, add(mload(indices), 1)))
            mstore(add(indicesEnd, w), mload(subject))
            mstore(indices, add(mload(indices), 1))
            for { let prevIndex := 0 } 1 { } {
                let index := mload(indexPtr)
                mstore(indexPtr, 0x60)
                if iszero(eq(index, prevIndex)) {
                    let element := mload(0x40)
                    let l := sub(index, prevIndex)
                    mstore(element, l) // Store the length of the element.
                    // Copy the `subject` one word at a time, backwards.
                    for { let o := and(add(l, 0x1f), w) } 1 { } {
                        mstore(add(element, o), mload(add(add(subject, prevIndex), o)))
                        o := add(o, w) // `sub(o, 0x20)`.
                        if iszero(o) { break }
                    }
                    mstore(add(add(element, 0x20), l), 0) // Zeroize the slot after the bytes.
                    // Allocate memory for the length and the bytes, rounded up to a multiple of 32.
                    mstore(0x40, add(element, and(add(l, 0x3f), w)))
                    mstore(indexPtr, element) // Store the `element` into the array.
                }
                prevIndex := add(index, mload(delimiter))
                indexPtr := add(indexPtr, 0x20)
                if iszero(lt(indexPtr, indicesEnd)) { break }
            }
            result := indices
            if iszero(mload(delimiter)) {
                result := add(indices, 0x20)
                mstore(result, sub(mload(indices), 2))
            }
        }
    }

    /// @dev Returns a concatenated bytes of `a` and `b`.
    /// Cheaper than `bytes.concat()` and does not de-align the free memory pointer.
    function concat(bytes memory a, bytes memory b) internal pure returns (bytes memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := mload(0x40)
            let w := not(0x1f)
            let aLen := mload(a)
            // Copy `a` one word at a time, backwards.
            for { let o := and(add(aLen, 0x20), w) } 1 { } {
                mstore(add(result, o), mload(add(a, o)))
                o := add(o, w) // `sub(o, 0x20)`.
                if iszero(o) { break }
            }
            let bLen := mload(b)
            let output := add(result, aLen)
            // Copy `b` one word at a time, backwards.
            for { let o := and(add(bLen, 0x20), w) } 1 { } {
                mstore(add(output, o), mload(add(b, o)))
                o := add(o, w) // `sub(o, 0x20)`.
                if iszero(o) { break }
            }
            let totalLen := add(aLen, bLen)
            let last := add(add(result, 0x20), totalLen)
            mstore(last, 0) // Zeroize the slot after the bytes.
            mstore(result, totalLen) // Store the length.
            mstore(0x40, add(last, 0x20)) // Allocate memory.
        }
    }

    /// @dev Returns whether `a` equals `b`.
    function eq(bytes memory a, bytes memory b) internal pure returns (bool result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := eq(keccak256(add(a, 0x20), mload(a)), keccak256(add(b, 0x20), mload(b)))
        }
    }

    /// @dev Returns whether `a` equals `b`, where `b` is a null-terminated small bytes.
    function eqs(bytes memory a, bytes32 b) internal pure returns (bool result) {
        /// @solidity memory-safe-assembly
        assembly {
            // These should be evaluated on compile time, as far as possible.
            let m := not(shl(7, div(not(iszero(b)), 255))) // `0x7f7f ...`.
            let x := not(or(m, or(b, add(m, and(b, m)))))
            let r := shl(7, iszero(iszero(shr(128, x))))
            r := or(r, shl(6, iszero(iszero(shr(64, shr(r, x))))))
            r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
            r := or(r, shl(4, lt(0xffff, shr(r, x))))
            r := or(r, shl(3, lt(0xff, shr(r, x))))
            // forgefmt: disable-next-item
            result := gt(eq(mload(a), add(iszero(x), xor(31, shr(3, r)))),
                xor(shr(add(8, r), b), shr(add(8, r), mload(add(a, 0x20)))))
        }
    }

    /// @dev Directly returns `a` without copying.
    function directReturn(
        bytes memory a
    ) internal pure {
        assembly {
            // Assumes that the bytes does not start from the scratch space.
            let retStart := sub(a, 0x20)
            let retUnpaddedSize := add(mload(a), 0x40)
            // Right pad with zeroes. Just in case the bytes is produced
            // by a method that doesn't zero right pad.
            mstore(add(retStart, retUnpaddedSize), 0)
            mstore(retStart, 0x20) // Store the return offset.
            // End the transaction, returning the bytes.
            return(retStart, and(not(0x1f), add(0x1f, retUnpaddedSize)))
        }
    }

    /// @dev Directly returns `a` with minimal copying.
    function directReturn(
        bytes[] memory a
    ) internal pure {
        assembly {
            let n := mload(a) // `a.length`.
            let o := add(a, 0x20) // Start of elements in `a`.
            let u := a // Highest memory slot.
            let w := not(0x1f)
            for { let i := 0 } iszero(eq(i, n)) { i := add(i, 1) } {
                let c := add(o, shl(5, i)) // Location of pointer to `a[i]`.
                let s := mload(c) // `a[i]`.
                let l := mload(s) // `a[i].length`.
                let r := and(l, 0x1f) // `a[i].length % 32`.
                let z := add(0x20, and(l, w)) // Offset of last word in `a[i]` from `s`.
                // If `s` comes before `o`, or `s` is not zero right padded.
                if iszero(lt(lt(s, o), or(iszero(r), iszero(shl(shl(3, r), mload(add(s, z))))))) {
                    let m := mload(0x40)
                    mstore(m, l) // Copy `a[i].length`.
                    for { } 1 { } {
                        mstore(add(m, z), mload(add(s, z))) // Copy `a[i]`, backwards.
                        z := add(z, w) // `sub(z, 0x20)`.
                        if iszero(z) { break }
                    }
                    let e := add(add(m, 0x20), l)
                    mstore(e, 0) // Zeroize the slot after the copied bytes.
                    mstore(0x40, add(e, 0x20)) // Allocate memory.
                    s := m
                }
                mstore(c, sub(s, o)) // Convert to calldata offset.
                let t := add(l, add(s, 0x20))
                if iszero(lt(t, u)) { u := t }
            }
            let retStart := add(a, w) // Assumes `a` doesn't start from scratch space.
            mstore(retStart, 0x20) // Store the return offset.
            return(retStart, add(0x40, sub(u, retStart))) // End the transaction.
        }
    }

    /// @dev Returns the word at `offset`, without any bounds checks.
    /// To load an address, you can use `address(bytes20(load(a, offset)))`.
    function load(bytes memory a, uint256 offset) internal pure returns (bytes32 result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := mload(add(add(a, 0x20), offset))
        }
    }

    /// @dev Returns the word at `offset`, without any bounds checks.
    /// To load an address, you can use `address(bytes20(loadCalldata(a, offset)))`.
    function loadCalldata(bytes calldata a, uint256 offset) internal pure returns (bytes32 result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := calldataload(add(a.offset, offset))
        }
    }

    /// @dev Returns empty calldata bytes. For silencing the compiler.
    function emptyCalldata() internal pure returns (bytes calldata result) {
        /// @solidity memory-safe-assembly
        assembly {
            result.length := 0
        }
    }
}

File 76 of 87 : ReentrancyGuard.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @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 ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

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

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

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

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

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

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

File 77 of 87 : AbstractRewarder.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import { Address } from "openzeppelin-contracts/utils/Address.sol";

import { ISystemRegistry } from "src/interfaces/ISystemRegistry.sol";
import { SecurityBase } from "src/security/SecurityBase.sol";

import { IBaseRewarder } from "src/interfaces/rewarders/IBaseRewarder.sol";

import { IAccToke } from "src/interfaces/staking/IAccToke.sol";

import { LibAdapter } from "src/libs/LibAdapter.sol";
import { Roles } from "src/libs/Roles.sol";
import { Errors } from "src/utils/Errors.sol";

/**
 * @dev An abstract contract that serves as the base for rewarder contracts.
 * It implements common functionalities for reward distribution, including calculating rewards per token,
 * tracking user rewards, and handling stake-related operations.
 * Inherited by rewarder contracts, such as MainRewarder and ExtraRewarder.
 * The contract is inspired by the Convex contract but uses block-based duration instead of timestamp-based duration.
 */
abstract contract AbstractRewarder is IBaseRewarder, SecurityBase {
    using SafeERC20 for IERC20;

    /// @notice The minimum duration for recovering tokens (1 year).
    uint256 public constant MINIMUM_RECOVER_DURATION = 31_536_000;

    /// @notice The duration of the reward period in blocks.
    uint256 public immutable durationInBlock;

    ///  @notice It is used to determine if the new rewards should be distributed immediately or queued for later. If
    /// the ratio of current rewards to the sum of new and queued rewards is less than newRewardRatio, the new rewards
    /// are distributed immediately; otherwise, they are added to the queue.
    uint256 public immutable newRewardRatio;

    /// @notice An instance of the system registry contract.
    ISystemRegistry internal immutable systemRegistry;

    /// @notice The address of the token to be distributed as rewards.
    address public immutable rewardToken;

    /// @notice The block number when the current reward period ends.
    uint256 public periodInBlockFinish;

    /// @notice The rate of reward distribution per block.
    uint256 public rewardRate;

    /// @notice The block number when rewards were last updated.
    uint256 public lastUpdateBlock;

    /// @notice The amount of rewards distributed per staked token stored.
    uint256 public rewardPerTokenStored;

    /// @notice The amount of rewards waiting in the queue to be distributed.
    uint256 public queuedRewards;

    /// @notice The amount of current rewards being distributed.
    uint256 public currentRewards;

    /// @notice The total amount of rewards distributed historically.
    uint256 public historicalRewards;

    /// @notice The amount of reward per token paid to each user.
    mapping(address => uint256) public userRewardPerTokenPaid;

    /// @notice The amount of rewards for each user.
    mapping(address => uint256) public rewards;

    /// @notice The duration for locking the Toke token rewards.
    uint256 public tokeLockDuration;

    /// @notice Whitelisted addresses for queuing new rewards.
    mapping(address => bool) public whitelistedAddresses;

    /// @notice Role that manages rewarder contract.
    bytes32 internal immutable rewardRole;

    /**
     * @param _systemRegistry Address of the system registry.
     * @param _rewardToken Address of the reward token.
     * @param _newRewardRatio The new reward rate.
     * @param _durationInBlock The duration of the reward period in blocks.
     * @param _rewardRole Role that controls role based functions in Rewarder.
     */
    constructor(
        ISystemRegistry _systemRegistry,
        address _rewardToken,
        uint256 _newRewardRatio,
        uint256 _durationInBlock,
        bytes32 _rewardRole
    ) SecurityBase(address(_systemRegistry.accessController())) {
        Errors.verifyNotZero(_rewardToken, "_rewardToken");
        Errors.verifyNotZero(_durationInBlock, "_durationInBlock");
        Errors.verifyNotZero(_newRewardRatio, "_newRewardRatio");
        Errors.verifyNotZero(_rewardRole, "_rewardRole");

        systemRegistry = _systemRegistry;
        if (!systemRegistry.isRewardToken(_rewardToken)) {
            revert Errors.InvalidParam("_rewardToken");
        }
        rewardToken = _rewardToken;
        newRewardRatio = _newRewardRatio;
        durationInBlock = _durationInBlock;
        rewardRole = _rewardRole;
    }

    /// @notice Restricts access to whitelisted addresses or holders of the liquidator role.
    modifier onlyWhitelisted() {
        if (!whitelistedAddresses[msg.sender] && !_hasRole(Roles.LIQUIDATOR_MANAGER, msg.sender)) {
            revert Errors.AccessDenied();
        }
        _;
    }

    /**
     * @notice Internal function that updates the user's rewards.
     * @param account The address of the user to update the rewards for.
     */
    function _updateReward(
        address account
    ) internal {
        uint256 earnedRewards = 0;
        rewardPerTokenStored = rewardPerToken();
        lastUpdateBlock = lastBlockRewardApplicable();

        if (rewardPerTokenStored > 0) {
            if (account != address(0)) {
                earnedRewards = earned(account);
                rewards[account] = earnedRewards;
                userRewardPerTokenPaid[account] = rewardPerTokenStored;
            }
        }

        emit UserRewardUpdated(account, earnedRewards, rewardPerTokenStored, lastUpdateBlock);
    }

    /// @inheritdoc IBaseRewarder
    function lastBlockRewardApplicable() public view returns (uint256) {
        return block.number < periodInBlockFinish ? block.number : periodInBlockFinish;
    }

    /// @inheritdoc IBaseRewarder
    function rewardPerToken() public view returns (uint256) {
        uint256 total = totalSupply();
        if (total == 0) {
            return rewardPerTokenStored;
        }

        return rewardPerTokenStored + ((lastBlockRewardApplicable() - lastUpdateBlock) * rewardRate * 1e18 / total);
    }

    /**
     * @inheritdoc IBaseRewarder
     * @dev
     * The function calculates the earned rewards based on the balance of the account,
     * the total supply of the staked tokens, the rewards per token and the last reward rate
     * the user has been paid at. The reward rate is determined by the `rewardPerToken`
     * function and is a measure of the amount of rewards distributed per staked token
     * per block.
     *
     * The amount of earned rewards is calculated as follows:
     * - First, it calculates the difference between the current reward per token and
     *   the last reward rate the user was paid at, which gives the reward rate per token
     *   since the user last claimed rewards.
     * - This difference is multiplied by the balance of the account to find the total
     *   amount of rewards the account has earned since it last claimed rewards.
     * - Finally, the function adds the rewards that have not yet been claimed by the
     *   user to find the total amount of earned rewards.
     */
    function earned(
        address account
    ) public view returns (uint256) {
        return (balanceOf(account) * (rewardPerToken() - userRewardPerTokenPaid[account]) / 1e18) + rewards[account];
    }

    /**
     * @inheritdoc IBaseRewarder
     * @dev The function transfers the new rewards from the caller to this contract,
     *      ensuring that the deposited amount matches the declared rewards.
     *      Irrespective of whether we're near the start or the end of a reward period, if the accrued rewards
     *      are too large relative to the new rewards (i.e., queuedRatio is greater than newRewardRatio), the new
     *      rewards will be added to the queue rather than being immediately distributed.
     */
    function queueNewRewards(
        uint256 newRewards
    ) external onlyWhitelisted {
        uint256 startingQueuedRewards = queuedRewards;
        uint256 startingNewRewards = newRewards;

        newRewards += startingQueuedRewards;

        if (block.number >= periodInBlockFinish) {
            notifyRewardAmount(newRewards);
            queuedRewards = 0;
        } else {
            uint256 elapsedBlock = block.number - (periodInBlockFinish - durationInBlock);
            uint256 currentAtNow = rewardRate * elapsedBlock;
            uint256 queuedRatio = currentAtNow * 1000 / newRewards;

            if (queuedRatio < newRewardRatio) {
                notifyRewardAmount(newRewards);
                queuedRewards = 0;
            } else {
                queuedRewards = newRewards;
            }
        }

        emit QueuedRewardsUpdated(startingQueuedRewards, startingNewRewards, queuedRewards);

        // Transfer the new rewards from the caller to this contract.
        IERC20(rewardToken).safeTransferFrom(msg.sender, address(this), startingNewRewards);
    }

    /**
     * @notice Notifies the contract about the amount of reward tokens to be distributed.
     * @param reward The amount of reward tokens to be distributed.
     * @dev The function updates the rewardRate, lastUpdateBlock, periodInBlockFinish, and historicalRewards.
     *      It calculates the remaining reward based on the current block number and adjusts the reward rate
     *      accordingly.
     *
     *      If the current block number is within the reward period, the remaining reward is added to the reward queue
     *      and will be distributed gradually over the remaining duration.
     *      If the current block number exceeds the reward period, the remaining reward is distributed immediately.
     */
    function notifyRewardAmount(
        uint256 reward
    ) internal {
        historicalRewards += reward;

        // Correctly calculate leftover reward when totalSupply() is 0.
        if (totalSupply() == 0) {
            if (lastUpdateBlock < periodInBlockFinish) {
                // slither-disable-next-line divide-before-multiply
                reward += (periodInBlockFinish - lastUpdateBlock) * rewardRate;
            }
        } else if (block.number < periodInBlockFinish) {
            uint256 remaining = periodInBlockFinish - block.number;

            // slither-disable-next-line divide-before-multiply
            uint256 leftover = remaining * rewardRate;
            reward += leftover;
        }

        _updateReward(address(0));

        // slither-disable-next-line divide-before-multiply
        rewardRate = reward / durationInBlock;
        // If `reward` < `durationInBlock`, it will result in a `rewardRate` of 0, which we want to prevent.
        if (rewardRate <= 0) revert Errors.ZeroAmount();

        currentRewards = reward;
        lastUpdateBlock = block.number;
        periodInBlockFinish = block.number + durationInBlock;

        emit RewardAdded(reward, rewardRate, lastUpdateBlock, periodInBlockFinish, historicalRewards);
    }

    /**
     * inheritdoc IBaseRewarder
     * @dev If the lock duration is set to 0, it turns off the staking functionality for Toke tokens.
     * @dev If the lock duration is greater than 0, it should be long enough to satisfy the minimum staking duration
     * requirement of the accToke contract.
     */
    function setTokeLockDuration(
        uint256 _tokeLockDuration
    ) external hasRole(rewardRole) {
        // if duration is not set to 0 (that would turn off functionality), make sure it's long enough for accToke
        if (_tokeLockDuration > 0) {
            Errors.verifyNotZero(address(systemRegistry.accToke()), "accToke");
            if (_tokeLockDuration < systemRegistry.accToke().minStakeDuration()) {
                revert IAccToke.StakingDurationTooShort();
            }
        }

        tokeLockDuration = _tokeLockDuration;
        emit TokeLockDurationUpdated(_tokeLockDuration);
    }

    /// @inheritdoc IBaseRewarder
    function addToWhitelist(
        address wallet
    ) external override hasRole(rewardRole) {
        Errors.verifyNotZero(wallet, "wallet");
        if (whitelistedAddresses[wallet]) {
            revert Errors.ItemExists();
        }
        whitelistedAddresses[wallet] = true;

        emit AddedToWhitelist(wallet);
    }

    /// @inheritdoc IBaseRewarder
    function removeFromWhitelist(
        address wallet
    ) external override hasRole(rewardRole) {
        if (!whitelistedAddresses[wallet]) {
            revert Errors.ItemNotFound();
        }

        whitelistedAddresses[wallet] = false;

        emit RemovedFromWhitelist(wallet);
    }

    /// @inheritdoc IBaseRewarder
    function isWhitelisted(
        address wallet
    ) external view override returns (bool) {
        return whitelistedAddresses[wallet];
    }

    /**
     * @notice Internal function to distribute rewards to a specific account.
     * @param account The address of the user to distribute rewards to.
     * @param recipient The address to send the rewards to.
     */
    function _getReward(address account, address recipient) internal {
        Errors.verifyNotZero(account, "account");
        Errors.verifyNotZero(recipient, "recipient");

        uint256 reward = earned(account);
        (IAccToke accToke, address tokeAddress) = (systemRegistry.accToke(), address(systemRegistry.toke()));

        // slither-disable-next-line incorrect-equality
        if (reward == 0) return;

        // if NOT toke, or staking is turned off (by duration = 0), just send reward back
        if (rewardToken != tokeAddress || tokeLockDuration == 0) {
            rewards[account] = 0;
            emit RewardPaid(account, recipient, reward);

            IERC20(rewardToken).safeTransfer(recipient, reward);
        } else if (accToke.isStakeableAmount(reward)) {
            rewards[account] = 0;
            emit RewardPaid(account, recipient, reward);
            // authorize accToke to get our reward Toke
            LibAdapter._approve(IERC20(tokeAddress), address(accToke), reward);

            // stake Toke
            accToke.stake(reward, tokeLockDuration, recipient);
        }
    }

    /**
     * @notice Internal function to handle withdrawals.
     * @param account The address of the user to handle withdrawal.
     * @dev This function primarily checks for valid parameters and emits an event.
     *      It adopts a pattern established by Convex. It helps with:
     *      - Identifying system errors (if a revert happens here, there is an issue within our system).
     *      - Enhancing system monitoring capabilities through emitted events.
     * @param amount The amount to be withdrawn.
     */
    function _withdrawAbstractRewarder(address account, uint256 amount) internal {
        Errors.verifyNotZero(account, "account");
        Errors.verifyNotZero(amount, "amount");

        emit Withdrawn(account, amount);
    }

    /**
     * @notice Internal function to handle staking.
     * @dev This function primarily checks for valid parameters and emits an event.
     *      It adopts a pattern established by Convex. It helps with:
     *      - Identifying system errors (if a revert happens here, there is an issue within our system).
     *      - Enhancing system monitoring capabilities through emitted events.
     * @param account The address of the user to handle staking.
     * @param amount The amount to be staked.
     */
    function _stakeAbstractRewarder(address account, uint256 amount) internal {
        Errors.verifyNotZero(account, "account");
        Errors.verifyNotZero(amount, "amount");

        emit Staked(account, amount);
    }

    /// @inheritdoc IBaseRewarder
    function totalSupply() public view virtual returns (uint256);

    /// @inheritdoc IBaseRewarder
    function recover(address token, address recipient) external override hasRole(Roles.TOKEN_RECOVERY_MANAGER) {
        Errors.verifyNotZero(token, "token");
        Errors.verifyNotZero(recipient, "recipient");
        if (recipient == address(this)) revert Errors.InvalidAddress(recipient);

        if (!canTokenBeRecovered(token)) revert Errors.AssetNotAllowed(token);
        if (block.number < lastUpdateBlock + MINIMUM_RECOVER_DURATION && token == rewardToken) {
            revert RecoverDurationPending();
        }

        if (token == LibAdapter.CURVE_REGISTRY_ETH_ADDRESS_POINTER) {
            uint256 tokenBalance = address(this).balance;
            if (tokenBalance > 0) {
                emit Recovered(token, recipient, tokenBalance);
                Address.sendValue(payable(recipient), tokenBalance);
            }
        } else {
            uint256 tokenBalance = IERC20(token).balanceOf(address(this));
            if (tokenBalance > 0) {
                emit Recovered(token, recipient, tokenBalance);
                IERC20(token).safeTransfer(recipient, tokenBalance);
            }
        }
    }

    /**
     * @notice Check if a token is recoverable.
     * @param token The address to be checked.
     * @return bool indicating if the token is recoverable.
     */
    function canTokenBeRecovered(
        address token
    ) public view virtual returns (bool);

    /// @inheritdoc IBaseRewarder
    function balanceOf(
        address account
    ) public view virtual returns (uint256);
}

File 78 of 87 : Proxy.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)

pragma solidity ^0.8.0;

/**
 * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
 * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
 * be specified by overriding the virtual {_implementation} function.
 *
 * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
 * different contract through the {_delegate} function.
 *
 * The success and return data of the delegated call will be returned back to the caller of the proxy.
 */
abstract contract Proxy {
    /**
     * @dev Delegates the current call to `implementation`.
     *
     * This function does not return to its internal call site, it will return directly to the external caller.
     */
    function _delegate(address implementation) internal virtual {
        assembly {
            // Copy msg.data. We take full control of memory in this inline assembly
            // block because it will not return to Solidity code. We overwrite the
            // Solidity scratch pad at memory position 0.
            calldatacopy(0, 0, calldatasize())

            // Call the implementation.
            // out and outsize are 0 because we don't know the size yet.
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)

            // Copy the returned data.
            returndatacopy(0, 0, returndatasize())

            switch result
            // delegatecall returns 0 on error.
            case 0 {
                revert(0, returndatasize())
            }
            default {
                return(0, returndatasize())
            }
        }
    }

    /**
     * @dev This is a virtual function that should be overridden so it returns the address to which the fallback function
     * and {_fallback} should delegate.
     */
    function _implementation() internal view virtual returns (address);

    /**
     * @dev Delegates the current call to the address returned by `_implementation()`.
     *
     * This function does not return to its internal call site, it will return directly to the external caller.
     */
    function _fallback() internal virtual {
        _beforeFallback();
        _delegate(_implementation());
    }

    /**
     * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
     * function in the contract matches the call data.
     */
    fallback() external payable virtual {
        _fallback();
    }

    /**
     * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
     * is empty.
     */
    receive() external payable virtual {
        _fallback();
    }

    /**
     * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
     * call, or as part of the Solidity `fallback` or `receive` functions.
     *
     * If overridden should call `super._beforeFallback()`.
     */
    function _beforeFallback() internal virtual {}
}

File 79 of 87 : ERC1967Upgrade.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (proxy/ERC1967/ERC1967Upgrade.sol)

pragma solidity ^0.8.2;

import "../beacon/IBeacon.sol";
import "../../interfaces/draft-IERC1822.sol";
import "../../utils/Address.sol";
import "../../utils/StorageSlot.sol";

/**
 * @dev This abstract contract provides getters and event emitting update functions for
 * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
 *
 * _Available since v4.1._
 *
 * @custom:oz-upgrades-unsafe-allow delegatecall
 */
abstract contract ERC1967Upgrade {
    // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
    bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;

    /**
     * @dev Storage slot with the address of the current implementation.
     * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
     * validated in the constructor.
     */
    bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    /**
     * @dev Emitted when the implementation is upgraded.
     */
    event Upgraded(address indexed implementation);

    /**
     * @dev Returns the current implementation address.
     */
    function _getImplementation() internal view returns (address) {
        return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
    }

    /**
     * @dev Stores a new address in the EIP1967 implementation slot.
     */
    function _setImplementation(address newImplementation) private {
        require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
        StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
    }

    /**
     * @dev Perform implementation upgrade
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeTo(address newImplementation) internal {
        _setImplementation(newImplementation);
        emit Upgraded(newImplementation);
    }

    /**
     * @dev Perform implementation upgrade with additional setup call.
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeToAndCall(
        address newImplementation,
        bytes memory data,
        bool forceCall
    ) internal {
        _upgradeTo(newImplementation);
        if (data.length > 0 || forceCall) {
            Address.functionDelegateCall(newImplementation, data);
        }
    }

    /**
     * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeToAndCallUUPS(
        address newImplementation,
        bytes memory data,
        bool forceCall
    ) internal {
        // Upgrades from old implementations will perform a rollback test. This test requires the new
        // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
        // this special case will break upgrade paths from old UUPS implementation to new ones.
        if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
            _setImplementation(newImplementation);
        } else {
            try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
                require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
            } catch {
                revert("ERC1967Upgrade: new implementation is not UUPS");
            }
            _upgradeToAndCall(newImplementation, data, forceCall);
        }
    }

    /**
     * @dev Storage slot with the admin of the contract.
     * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
     * validated in the constructor.
     */
    bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

    /**
     * @dev Emitted when the admin account has changed.
     */
    event AdminChanged(address previousAdmin, address newAdmin);

    /**
     * @dev Returns the current admin.
     */
    function _getAdmin() internal view returns (address) {
        return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
    }

    /**
     * @dev Stores a new address in the EIP1967 admin slot.
     */
    function _setAdmin(address newAdmin) private {
        require(newAdmin != address(0), "ERC1967: new admin is the zero address");
        StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
    }

    /**
     * @dev Changes the admin of the proxy.
     *
     * Emits an {AdminChanged} event.
     */
    function _changeAdmin(address newAdmin) internal {
        emit AdminChanged(_getAdmin(), newAdmin);
        _setAdmin(newAdmin);
    }

    /**
     * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
     * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
     */
    bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;

    /**
     * @dev Emitted when the beacon is upgraded.
     */
    event BeaconUpgraded(address indexed beacon);

    /**
     * @dev Returns the current beacon.
     */
    function _getBeacon() internal view returns (address) {
        return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
    }

    /**
     * @dev Stores a new beacon in the EIP1967 beacon slot.
     */
    function _setBeacon(address newBeacon) private {
        require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract");
        require(
            Address.isContract(IBeacon(newBeacon).implementation()),
            "ERC1967: beacon implementation is not a contract"
        );
        StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
    }

    /**
     * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
     * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
     *
     * Emits a {BeaconUpgraded} event.
     */
    function _upgradeBeaconToAndCall(
        address newBeacon,
        bytes memory data,
        bool forceCall
    ) internal {
        _setBeacon(newBeacon);
        emit BeaconUpgraded(newBeacon);
        if (data.length > 0 || forceCall) {
            Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
        }
    }
}

File 80 of 87 : IAccessControl.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity ^0.8.0;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControl {
    /**
     * @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.
     *
     * _Available since v3.1._
     */
    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 `account`.
     */
    function renounceRole(bytes32 role, address account) external;
}

File 81 of 87 : IBaseAssetVault.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

interface IBaseAssetVault {
    /// @notice Asset that this Vault primarily manages
    /// @dev Vault decimals should be the same as the baseAsset
    function baseAsset() external view returns (address);
}

File 82 of 87 : IDexLSTStats.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

import { ILSTStats } from "src/interfaces/stats/ILSTStats.sol";

/// @title Return stats DEXs with LSTs
interface IDexLSTStats {
    event DexSnapshotTaken(uint256 snapshotTimestamp, uint256 priorFeeApr, uint256 newFeeApr, uint256 unfilteredFeeApr);

    struct StakingIncentiveStats {
        // time-weighted average total supply to prevent spikes/attacks from impacting rebalancing
        uint256 safeTotalSupply;
        // rewardTokens, annualizedRewardAmounts, and periodFinishForRewards will match indexes
        // they are split to workaround an issue with forge having nested structs
        // address of the reward tokens
        address[] rewardTokens;
        // the annualized reward rate for the reward token
        uint256[] annualizedRewardAmounts;
        // the timestamp for when the rewards are set to terminate
        uint40[] periodFinishForRewards;
        // incentive rewards score. max 48, min 0
        uint8 incentiveCredits;
    }

    struct DexLSTStatsData {
        uint256 lastSnapshotTimestamp;
        uint256 feeApr;
        uint256[] reservesInEth;
        StakingIncentiveStats stakingIncentiveStats;
        ILSTStats.LSTStatsData[] lstStatsData;
    }

    /// @notice Get the current stats for the DEX with underlying LST tokens
    /// @dev Returned data is a combination of current data and filtered snapshots
    /// @return dexLSTStatsData current data on the DEX
    function current() external returns (DexLSTStatsData memory dexLSTStatsData);
}

File 83 of 87 : Strings.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }
}

File 84 of 87 : IBeacon.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)

pragma solidity ^0.8.0;

/**
 * @dev This is the interface that {BeaconProxy} expects of its beacon.
 */
interface IBeacon {
    /**
     * @dev Must return an address that can be used as a delegate call target.
     *
     * {BeaconProxy} will check that this address is a contract.
     */
    function implementation() external view returns (address);
}

File 85 of 87 : draft-IERC1822.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)

pragma solidity ^0.8.0;

/**
 * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
 * proxy whose upgrades are fully controlled by the current implementation.
 */
interface IERC1822Proxiable {
    /**
     * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
     * address.
     *
     * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
     * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
     * function revert if invoked through a proxy.
     */
    function proxiableUUID() external view returns (bytes32);
}

File 86 of 87 : StorageSlot.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/StorageSlot.sol)

pragma solidity ^0.8.0;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC1967 implementation slot:
 * ```
 * contract ERC1967 {
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }
}

File 87 of 87 : ILSTStats.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity ^0.8.24;

/// @title Return stats on base LSTs
interface ILSTStats {
    struct LSTStatsData {
        uint256 lastSnapshotTimestamp;
        uint256 baseApr;
        int256 discount; // positive number is a discount, negative is a premium
        uint24[10] discountHistory; // 7 decimal precision
        uint40 discountTimestampByPercent; // timestamp that the token reached 1pct discount
    }

    /// @notice Get the current stats for the LST
    /// @dev Returned data is a combination of current data and filtered snapshots
    /// @return lstStatsData current data on the LST
    function current() external returns (LSTStatsData memory lstStatsData);

    /// @notice Get the EthPerToken (or Share) for the LST
    /// @return ethPerShare the backing eth for the LST
    function calculateEthPerToken() external view returns (uint256 ethPerShare);

    /// @notice Returns whether to use the market price when calculating discount
    /// @dev Will be true for rebasing tokens and other non-standard tokens
    function usePriceAsDiscount() external view returns (bool useAsDiscount);
}

Settings
{
  "remappings": [
    "forge-std/=lib/forge-std/src/",
    "ds-test/=lib/forge-std/lib/ds-test/src/",
    "src/=src/",
    "test/=test/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",
    "erc4626-tests/=lib/erc4626-tests/",
    "prb-math/=lib/prb-math/",
    "crytic/properties/=lib/properties/",
    "redstone-finance/=lib/redstone-evm-connector/packages/evm-connector/contracts/",
    "ERC4626/=lib/properties/lib/ERC4626/contracts/",
    "properties/=lib/properties/contracts/",
    "redstone-evm-connector/=lib/redstone-evm-connector/",
    "solmate/=lib/properties/lib/solmate/src/",
    "usingtellor/=lib/usingtellor/contracts/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "cancun",
  "viaIR": false,
  "libraries": {
    "src/strategy/StructuredLinkedList.sol": {
      "StructuredLinkedList": "0x9a4007F77a6374Ebf8b5DF249E0ab5D38c32eE9e"
    },
    "src/strategy/WithdrawalQueue.sol": {
      "WithdrawalQueue": "0x4e8A14fCf4F13F0f95eE5c5706CD4dD2D75CcC50"
    },
    "src/vault/libs/Autopool4626.sol": {
      "Autopool4626": "0xdf4B7a41c68e238c22f3c85757616D5e54e89365"
    },
    "src/vault/libs/AutopoolDebt.sol": {
      "AutopoolDebt": "0xD1E2C5a00CE5953c3b58Cc3ef15d9cf53f01aF87"
    },
    "src/vault/libs/AutopoolDestinations.sol": {
      "AutopoolDestinations": "0xdd9E8A9A6946D6775EF1BB3156c98aA6F65C899F"
    },
    "src/vault/libs/AutopoolFees.sol": {
      "AutopoolFees": "0x70595538080D7763BB955cb795660922F938C83C"
    },
    "src/vault/libs/AutopoolStrategyHooks.sol": {
      "AutopoolStrategyHooks": "0x88e91D0A3133f586bAA38f5A972Ad43E0A276d0D"
    },
    "src/vault/libs/AutopoolToken.sol": {
      "AutopoolToken": "0x8233f06346d0752a46730A0adF39ad8c0eC1c323"
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"contract ISystemRegistry","name":"_systemRegistry","type":"address"},{"internalType":"address","name":"_proxyAdminOwner","type":"address"},{"internalType":"address","name":"_implementation","type":"address"},{"internalType":"uint256","name":"_defaultRewardRatio","type":"uint256"},{"internalType":"uint256","name":"_defaultRewardBlockDuration","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccessDenied","type":"error"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"InvalidBaseAmount","type":"error"},{"inputs":[],"name":"InvalidConfiguration","type":"error"},{"inputs":[],"name":"ItemExists","type":"error"},{"inputs":[],"name":"MustBeZero","type":"error"},{"inputs":[],"name":"NotImplemented","type":"error"},{"inputs":[{"internalType":"address","name":"source1","type":"address"},{"internalType":"address","name":"source2","type":"address"}],"name":"SystemMismatch","type":"error"},{"inputs":[],"name":"UndefinedAddress","type":"error"},{"inputs":[{"internalType":"string","name":"paramName","type":"string"}],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldBlockDuration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newBlockDuration","type":"uint256"}],"name":"DefaultBlockDurationSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldRewardRatio","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newRewardRatio","type":"uint256"}],"name":"DefaultRewardRatioSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldImplementation","type":"address"},{"indexed":false,"internalType":"address","name":"newImplementation","type":"address"}],"name":"ImplementationSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"ProxyAdminSet","type":"event"},{"inputs":[],"name":"accessController","outputs":[{"internalType":"contract IAccessController","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"addStrategyTemplate","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"baseAsset","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"symbolSuffix","type":"string"},{"internalType":"string","name":"descPrefix","type":"string"},{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"name":"createVault","outputs":[{"internalType":"address","name":"newVault","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"defaultRewardBlockDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"defaultRewardRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSystemRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxyAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"removeStrategyTemplate","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newBlockDuration","type":"uint256"}],"name":"setDefaultRewardBlockDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newRewardRatio","type":"uint256"}],"name":"setDefaultRewardRatio","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newImplementation","type":"address"}],"name":"setImplementation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newProxyAdmin","type":"address"}],"name":"setProxyAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"template","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]

60e060405234801561000f575f80fd5b5060405161610838038061610883398101604081905261002e9161053a565b846001600160a01b031663bc43cbaf6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561006a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061008e9190610594565b856100c6816040518060400160405280600f81526020016e5f73797374656d526567697374727960881b81525061028d60201b60201c565b6001600160a01b0390811660805281166100f357604051630cbe126f60e11b815260040160405180910390fd5b6001600160a01b031660a05260408051808201909152600f81526e2fb4b6b83632b6b2b73a30ba34b7b760891b602082015261013090849061028d565b610139836102c3565b5f836001600160a01b03166338d52e0f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610176573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061019a9190610594565b90506101d2816040518060400160405280600e81526020016d1b1bd8d85b10985cd9505cdcd95d60921b81525061028d60201b60201c565b6001600160a01b03811660c0526101e8836103ff565b6101f182610440565b5f6040516101fe90610516565b604051809103905ff080158015610217573d5f803e3d5ffd5b5060405163f2fde38b60e01b81526001600160a01b0388811660048301529192509082169063f2fde38b906024015f604051808303815f87803b15801561025c575f80fd5b505af115801561026e573d5f803e3d5ffd5b505050506102818161048160201b60201c565b505050505050506105eb565b6001600160a01b0382166102bf578060405163eac0d38960e01b81526004016102b691906105b6565b60405180910390fd5b5050565b6001546001600160a01b03908116908216036102f257604051633e04f87160e01b815260040160405180910390fd5b806001600160a01b031663f12baf5c6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561032e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103529190610594565b6001600160a01b03166080516001600160a01b03161461039657604051632f6b3b6360e01b81523060048201526001600160a01b03821660248201526044016102b6565b600154604080516001600160a01b03928316815291831660208301527f1acacd74b878778c66cc2abd6987699d6602bfb4b7f244c64c6ed0d61054bb5e910160405180910390a1600180546001600160a01b0319166001600160a01b0392909216919091179055565b60025460408051918252602082018390527fd527863f4f3ab7a5b484cd73e646ba29392452df01668b796a5a143bcd032aef910160405180910390a1600255565b60035460408051918252602082018390527f945131392c364f55e7dd69d3aefc9f5b625be9913dc0558dbb7feb538eb35ac9910160405180910390a1600355565b5f546001600160a01b03908116908216036104af57604051633e04f87160e01b815260040160405180910390fd5b5f54604080516001600160a01b03928316815291831660208301527f8b229dfa15ad44db21d7fe7e9dca521b2dcc866b16f1c9ffa6fde044782b2368910160405180910390a15f80546001600160a01b0319166001600160a01b0392909216919091179055565b6106c880615a4083390190565b6001600160a01b0381168114610537575f80fd5b50565b5f805f805f60a0868803121561054e575f80fd5b855161055981610523565b602087015190955061056a81610523565b604087015190945061057b81610523565b6060870151608090970151959894975095949392505050565b5f602082840312156105a4575f80fd5b81516105af81610523565b9392505050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b60805160a05160c0516153de6106625f395f81816102440152818161066101526109ea01525f81816102110152818161030a01528181610419015281816104db0152818161059d015261076001525f81816102a60152818161086a0152818161088b01528181610ae00152610da701526153de5ff3fe6080604052600436106100e4575f3560e01c806390a4551911610087578063d784d42611610057578063d784d42614610266578063db475d3914610285578063f12baf5c14610298578063f5b7fe87146102ca575f80fd5b806390a4551914610144578063b6d72520146101dd578063bc43cbaf14610200578063cdf456e114610233575f80fd5b80635c60da1b116100c25780635c60da1b146101635780635e3119b6146101825780636a306d61146101a15780636f2ddd93146101c0575f80fd5b80633e47158c146100e857806347c02661146101235780635c24feaf14610144575b5f80fd5b3480156100f3575f80fd5b505f54610106906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561012e575f80fd5b5061014261013d3660046113c9565b6102df565b005b34801561014f575f80fd5b5061014261015e3660046113c9565b6103d5565b34801561016e575f80fd5b50600154610106906001600160a01b031681565b34801561018d575f80fd5b5061014261019c3660046113eb565b6103ee565b3480156101ac575f80fd5b506101426101bb3660046113eb565b6104b0565b3480156101cb575f80fd5b506001546001600160a01b0316610106565b3480156101e8575f80fd5b506101f260025481565b60405190815260200161011a565b34801561020b575f80fd5b506101067f000000000000000000000000000000000000000000000000000000000000000081565b34801561023e575f80fd5b506101067f000000000000000000000000000000000000000000000000000000000000000081565b348015610271575f80fd5b506101426102803660046113c9565b610572565b6101066102933660046114a1565b610719565b3480156102a3575f80fd5b507f0000000000000000000000000000000000000000000000000000000000000000610106565b3480156102d5575f80fd5b506101f260035481565b604051632474521560e21b81525f8051602061538983398151915260048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610357573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061037b9190611585565b61039857604051634ca8886760e01b815260040160405180910390fd5b6103c8826040518060400160405280600e81526020016d2fb732bba83937bc3ca0b236b4b760911b815250610bc4565b6103d182610bf6565b5050565b60405163d623472560e01b815260040160405180910390fd5b604051632474521560e21b81525f8051602061538983398151915260048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610466573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061048a9190611585565b6104a757604051634ca8886760e01b815260040160405180910390fd5b6103d182610c8b565b604051632474521560e21b81525f8051602061538983398151915260048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610528573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061054c9190611585565b61056957604051634ca8886760e01b815260040160405180910390fd5b6103d182610ccc565b604051632474521560e21b81525f8051602061538983398151915260048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa1580156105ea573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061060e9190611585565b61062b57604051634ca8886760e01b815260040160405180910390fd5b61065f82604051806040016040528060128152602001712fb732bba4b6b83632b6b2b73a30ba34b7b760711b815250610bc4565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b03166338d52e0f6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106c5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106e991906115a4565b6001600160a01b0316146107105760405163c52a9bd360e01b815260040160405180910390fd5b6103d182610d0d565b604051632474521560e21b81527f643a332a1a6de98a36f48a7596dfafd4ea1b69babd8e98b38d6b2a652b59d09b600482018190523360248301525f916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906391d1485490604401602060405180830381865afa1580156107a5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107c99190611585565b6107e657604051634ca8886760e01b815260040160405180910390fd5b341561080557604051630b47f1cd60e31b815260040160405180910390fd5b6001545f546040516001600160a01b03928316929091169061082690611398565b6001600160a01b039283168152911660208201526060604082018190525f90820152608001604051809103905ff080158015610864573d5f803e3d5ffd5b5091505f7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663f543bb0e6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108e5573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061090991906115a4565b60025460035460018760405161091e906113a5565b6001600160a01b0396871681529486166020860152604085019390935260608401919091521515608083015290911660a082015260c001604051809103905ff08015801561096e573d5f803e3d5ffd5b5090505f60015f9054906101000a90046001600160a01b03166001600160a01b0316639dbbabf46040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109c2573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109e691906115bf565b90507f0000000000000000000000000000000000000000000000000000000000000000610a1e6001600160a01b038216333085610e67565b610a29818684610ed8565b60405163e649e2fb60e01b81526001600160a01b0386169063e649e2fb90610a5b908d908d908c908c90600401611604565b5f604051808303815f87803b158015610a72575f80fd5b505af1158015610a84573d5f803e3d5ffd5b5050604051630e9918b960e21b81526001600160a01b03868116600483015288169250633a6462e491506024015f604051808303815f87803b158015610ac8575f80fd5b505af1158015610ada573d5f803e3d5ffd5b505050507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663047e51386040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b3a573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b5e91906115a4565b6040516312b5ad0160e11b81526001600160a01b038781166004830152919091169063256b5a02906024015f604051808303815f87803b158015610ba0575f80fd5b505af1158015610bb2573d5f803e3d5ffd5b50505050505050509695505050505050565b6001600160a01b0382166103d1578060405163eac0d38960e01b8152600401610bed919061165d565b60405180910390fd5b5f546001600160a01b0390811690821603610c2457604051633e04f87160e01b815260040160405180910390fd5b5f54604080516001600160a01b03928316815291831660208301527f8b229dfa15ad44db21d7fe7e9dca521b2dcc866b16f1c9ffa6fde044782b2368910160405180910390a15f80546001600160a01b0319166001600160a01b0392909216919091179055565b60035460408051918252602082018390527f945131392c364f55e7dd69d3aefc9f5b625be9913dc0558dbb7feb538eb35ac9910160405180910390a1600355565b60025460408051918252602082018390527fd527863f4f3ab7a5b484cd73e646ba29392452df01668b796a5a143bcd032aef910160405180910390a1600255565b6001546001600160a01b0390811690821603610d3c57604051633e04f87160e01b815260040160405180910390fd5b806001600160a01b031663f12baf5c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d78573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d9c91906115a4565b6001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614610dfe57604051632f6b3b6360e01b81523060048201526001600160a01b0382166024820152604401610bed565b600154604080516001600160a01b03928316815291831660208301527f1acacd74b878778c66cc2abd6987699d6602bfb4b7f244c64c6ed0d61054bb5e910160405180910390a1600180546001600160a01b0319166001600160a01b0392909216919091179055565b6040516001600160a01b0380851660248301528316604482015260648101829052610ed29085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610f79565b50505050565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301525f919085169063dd62ed3e90604401602060405180830381865afa158015610f25573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f4991906115bf565b90508015610f6557610f656001600160a01b038516848361104f565b610ed26001600160a01b038516848461115f565b5f610fcd826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031661120e9092919063ffffffff16565b80519091501561104a5780806020019051810190610feb9190611585565b61104a5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610bed565b505050565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301525f919085169063dd62ed3e90604401602060405180830381865afa15801561109c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110c091906115bf565b9050818110156111245760405162461bcd60e51b815260206004820152602960248201527f5361666545524332303a2064656372656173656420616c6c6f77616e63652062604482015268656c6f77207a65726f60b81b6064820152608401610bed565b6040516001600160a01b0384166024820152828203604482018190529061115890869063095ea7b360e01b90606401610e9b565b5050505050565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301525f91839186169063dd62ed3e90604401602060405180830381865afa1580156111ad573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111d191906115bf565b6111db919061166f565b6040516001600160a01b038516602482015260448101829052909150610ed290859063095ea7b360e01b90606401610e9b565b606061121c84845f85611224565b949350505050565b6060824710156112855760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610bed565b5f80866001600160a01b031685876040516112a09190611694565b5f6040518083038185875af1925050503d805f81146112da576040519150601f19603f3d011682016040523d82523d5f602084013e6112df565b606091505b50915091506112f0878383876112fb565b979650505050505050565b606083156113695782515f03611362576001600160a01b0385163b6113625760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610bed565b508161121c565b61121c838381511561137e5781518083602001fd5b8060405162461bcd60e51b8152600401610bed919061165d565b610d0a806116ab83390190565b612fd4806123b583390190565b6001600160a01b03811681146113c6575f80fd5b50565b5f602082840312156113d9575f80fd5b81356113e4816113b2565b9392505050565b5f602082840312156113fb575f80fd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f830112611425575f80fd5b813567ffffffffffffffff81111561143f5761143f611402565b604051601f8201601f19908116603f0116810167ffffffffffffffff8111828210171561146e5761146e611402565b604052818152838201602001851015611485575f80fd5b816020850160208301375f918101602001919091529392505050565b5f805f805f8060a087890312156114b6575f80fd5b86356114c1816113b2565b9550602087013567ffffffffffffffff8111156114dc575f80fd5b6114e889828a01611416565b955050604087013567ffffffffffffffff811115611504575f80fd5b61151089828a01611416565b94505060608701359250608087013567ffffffffffffffff811115611533575f80fd5b87015f80601f83018b13611545575f80fd5b50813567ffffffffffffffff81111561155c575f80fd5b6020830191508a6020828501011115611573575f80fd5b979a9699509497509295939492505050565b5f60208284031215611595575f80fd5b815180151581146113e4575f80fd5b5f602082840312156115b4575f80fd5b81516113e4816113b2565b5f602082840312156115cf575f80fd5b5051919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b606081525f61161660608301876115d6565b828103602084015261162881876115d6565b90508281036040840152838152838560208301375f602085830101526020601f19601f86011682010191505095945050505050565b602081525f6113e460208301846115d6565b8082018082111561168e57634e487b7160e01b5f52601160045260245ffd5b92915050565b5f82518060208501845e5f92019182525091905056fe6080604052604051610d0a380380610d0a833981016040819052610022916103c3565b828161002f82825f610043565b5061003b90508261006e565b5050506104df565b61004c836100db565b5f825111806100585750805b1561006957610067838361011a565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6100ad5f80516020610cc3833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a16100d881610146565b50565b6100e4816101e1565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061013f8383604051806060016040528060278152602001610ce360279139610275565b9392505050565b6001600160a01b0381166101b05760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b805f80516020610cc38339815191525b80546001600160a01b0319166001600160a01b039290921691909117905550565b6001600160a01b0381163b61024e5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016101a7565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6101c0565b60605f80856001600160a01b0316856040516102919190610494565b5f60405180830381855af49150503d805f81146102c9576040519150601f19603f3d011682016040523d82523d5f602084013e6102ce565b606091505b5090925090506102e0868383876102ea565b9695505050505050565b606083156103585782515f03610351576001600160a01b0385163b6103515760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016101a7565b5081610362565b610362838361036a565b949350505050565b81511561037a5781518083602001fd5b8060405162461bcd60e51b81526004016101a791906104aa565b80516001600160a01b03811681146103aa575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f805f606084860312156103d5575f80fd5b6103de84610394565b92506103ec60208501610394565b60408501519092506001600160401b03811115610407575f80fd5b8401601f81018613610417575f80fd5b80516001600160401b03811115610430576104306103af565b604051601f8201601f19908116603f011681016001600160401b038111828210171561045e5761045e6103af565b604052818152828201602001881015610475575f80fd5b8160208401602083015e5f602083830101528093505050509250925092565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b6107d7806104ec5f395ff3fe60806040526004361061004d575f3560e01c80633659cfe6146100645780634f1ef286146100835780635c60da1b146100965780638f283970146100c6578063f851a440146100e55761005c565b3661005c5761005a6100f9565b005b61005a6100f9565b34801561006f575f80fd5b5061005a61007e366004610698565b610113565b61005a6100913660046106b1565b61014e565b3480156100a1575f80fd5b506100aa6101b4565b6040516001600160a01b03909116815260200160405180910390f35b3480156100d1575f80fd5b5061005a6100e0366004610698565b6101e4565b3480156100f0575f80fd5b506100aa610204565b610101610224565b61011161010c6102b9565b6102c2565b565b61011b6102e0565b6001600160a01b03163303610146576101438160405180602001604052805f8152505f610312565b50565b6101436100f9565b6101566102e0565b6001600160a01b031633036101ac576101a78383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060019250610312915050565b505050565b6101a76100f9565b5f6101bd6102e0565b6001600160a01b031633036101d9576101d46102b9565b905090565b6101e16100f9565b90565b6101ec6102e0565b6001600160a01b03163303610146576101438161033c565b5f61020d6102e0565b6001600160a01b031633036101d9576101d46102e0565b61022c6102e0565b6001600160a01b031633036101115760405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b5f6101d4610390565b365f80375f80365f845af43d5f803e8080156102dc573d5ff35b3d5ffd5b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b61031b836103b7565b5f825111806103275750805b156101a75761033683836103f6565b50505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103656102e0565b604080516001600160a01b03928316815291841660208301520160405180910390a161014381610422565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610303565b6103c0816104cb565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061041b838360405180606001604052806027815260200161077b6027913961055f565b9392505050565b6001600160a01b0381166104875760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b60648201526084016102b0565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b6001600160a01b0381163b6105385760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016102b0565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6104aa565b60605f80856001600160a01b03168560405161057b919061072f565b5f60405180830381855af49150503d805f81146105b3576040519150601f19603f3d011682016040523d82523d5f602084013e6105b8565b606091505b50915091506105c9868383876105d3565b9695505050505050565b606083156106415782515f0361063a576001600160a01b0385163b61063a5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016102b0565b508161064b565b61064b8383610653565b949350505050565b8151156106635781518083602001fd5b8060405162461bcd60e51b81526004016102b09190610745565b80356001600160a01b0381168114610693575f80fd5b919050565b5f602082840312156106a8575f80fd5b61041b8261067d565b5f805f604084860312156106c3575f80fd5b6106cc8461067d565b9250602084013567ffffffffffffffff8111156106e7575f80fd5b8401601f810186136106f7575f80fd5b803567ffffffffffffffff81111561070d575f80fd5b86602082840101111561071e575f80fd5b939660209190910195509293505050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220416fb094790ccffff95b19794e9b2cb987a33ce6bba82c21d77b2431561c524464736f6c634300081a0033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564610180604052348015610010575f80fd5b50604051612fd4380380612fd483398101604081905261002f9161037b565b858585857ffde2f69a846a71295e563d91ade82dc70e9eda278403d1aece24d0ded949403a868585858585846001600160a01b031663bc43cbaf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610096573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906100ba91906103e7565b6001600160a01b0381166100e157604051630cbe126f60e11b815260040160405180910390fd5b6001600160a01b031660805260408051808201909152600c81526b2fb932bbb0b9322a37b5b2b760a11b602082015261011b9085906102e1565b610153826040518060400160405280601081526020016f5f6475726174696f6e496e426c6f636b60801b81525061030e60201b60201c565b61018a836040518060400160405280600f81526020016e5f6e6577526577617264526174696f60881b81525061030e60201b60201c565b6101bd816040518060400160405280600b81526020016a5f726577617264526f6c6560a81b81525061033060201b60201c565b6001600160a01b0385811660e08190526040516316bfae7f60e31b815291861660048301529063b5fd73f890602401602060405180830381865afa158015610207573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061022b9190610409565b61026c57604051634389d5ab60e01b815260206004820152600c60248201526b2fb932bbb0b9322a37b5b2b760a11b60448201526064015b60405180910390fd5b6001600160a01b039093166101005260c09190915260a05261012052506001600b55151561014052505060408051808201909152600d81526c2fb9ba30b5b4b733aa37b5b2b760991b60208201526102c9935084925090506102e1565b6001600160a01b031661016052506104579350505050565b6001600160a01b03821661030a578060405163eac0d38960e01b81526004016102639190610422565b5050565b815f0361030a5780604051634389d5ab60e01b81526004016102639190610422565b8161030a5780604051634389d5ab60e01b81526004016102639190610422565b6001600160a01b0381168114610364575f80fd5b50565b80518015158114610376575f80fd5b919050565b5f805f805f8060c08789031215610390575f80fd5b865161039b81610350565b60208801519096506103ac81610350565b60408801516060890151919650945092506103c960808801610367565b915060a08701516103d981610350565b809150509295509295509295565b5f602082840312156103f7575f80fd5b815161040281610350565b9392505050565b5f60208284031215610419575f80fd5b61040282610367565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b60805160a05160c05160e05161010051610120516101405161016051612a6c6105685f395f81816103cd015281816110f401528181611167015261155001525f81816104870152610b6801525f818161061601528181610aa901528181610fb301526112fa01525f818161056a01528181610a6d01528181610dfc01528181611ed80152611f7801525f81816106de01528181610783015281816111a90152818161148601528181611dbc0152611e3c01525f818161037501526109db01525f81816102d0015281816109770152818161188e01526118e601525f81816104ae0152818161064501528181610ad801528181610c9501528181610fe201528181611329015261177d0152612a6c5ff3fe608060405234801561000f575f80fd5b5060043610610233575f3560e01c80638b87634711610135578063cd8e33d4116100b4578063e43252d711610079578063e43252d714610521578063e665b41414610534578063ead5d35914610547578063ee495fb61461055a578063f7c618c114610565575f80fd5b8063cd8e33d4146104eb578063ce60089d146104f3578063d55a23f4146104fb578063df136d6514610503578063e21c81d31461050c575f80fd5b8063adc9772e116100fa578063adc9772e1461046f578063b263487f14610482578063bc43cbaf146104a9578063c5285794146104d0578063cd3daf9d146104e3575f80fd5b80638b87634714610423578063901a7d53146104425780639a8c1c701461044b578063a218141b1461045e578063abe0429c14610467575f80fd5b8063590a41f5116101c15780636f73a38f116101865780636f73a38f1461039757806370a08231146103a057806372f702f3146103c85780637b0a47ee146104075780638ab1d68114610410575f80fd5b8063590a41f51461032e5780635e43c47b1461034157806363d38c3b14610354578063648bf7741461035d5780636c8bcee814610370575f80fd5b806318160ddd1161020757806318160ddd146102c35780631fc93059146102cb578063262d3d6d146102f25780633af32abf146102fb5780633d18b91214610326575f80fd5b80628cc2621461023757806306c933d81461025d5780630700037d1461028f57806314d09249146102ae575b5f80fd5b61024a6102453660046127b3565b61058c565b6040519081526020015b60405180910390f35b61027f61026b3660046127b3565b600a6020525f908152604090205460ff1681565b6040519015158152602001610254565b61024a61029d3660046127b3565b60086020525f908152604090205481565b6102c16102bc3660046127ce565b610607565b005b600e5461024a565b61024a7f000000000000000000000000000000000000000000000000000000000000000081565b61024a60065481565b61027f6103093660046127b3565b6001600160a01b03165f908152600a602052604090205460ff1690565b6102c16108bd565b6102c161033c3660046127ce565b6108e6565b6102c161034f3660046127b3565b610a9a565b61024a60045481565b6102c161036b3660046127e5565b610c57565b61024a7f000000000000000000000000000000000000000000000000000000000000000081565b61024a60095481565b61024a6103ae3660046127b3565b6001600160a01b03165f908152600f602052604090205490565b6103ef7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610254565b61024a60015481565b6102c161041e3660046127b3565b610fa4565b61024a6104313660046127b3565b60076020525f908152604090205481565b61024a60055481565b61027f6104593660046127b3565b6110f1565b61024a60025481565b61024a61113a565b6102c161047d36600461281c565b611150565b61027f7f000000000000000000000000000000000000000000000000000000000000000081565b6103ef7f000000000000000000000000000000000000000000000000000000000000000081565b6102c16104de366004612853565b611193565b61024a611266565b61024a5f5481565b61024a600f81565b61024a6112d4565b61024a60035481565b6105146112df565b604051610254919061289b565b6102c161052f3660046127b3565b6112eb565b6103ef6105423660046127ce565b611464565b6102c16105553660046128e6565b611470565b61024a6301e1338081565b6103ef7f000000000000000000000000000000000000000000000000000000000000000081565b6001600160a01b0381165f908152600860209081526040808320546007909252822054670de0b6b3a7640000906105c1611266565b6105cb919061292e565b6001600160a01b0385165f908152600f60205260409020546105ed9190612941565b6105f79190612958565b6106019190612977565b92915050565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610692573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106b6919061298a565b6106d357604051634ca8886760e01b815260040160405180910390fd5b8115610880576107817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663024d381b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610738573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061075c91906129a5565b60405180604001604052806007815260200166616363546f6b6560c81b815250611577565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663024d381b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107dd573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061080191906129a5565b6001600160a01b0316635fec5c646040518163ffffffff1660e01b8152600401602060405180830381865afa15801561083c573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061086091906129c0565b8210156108805760405163bcec4c5360e01b815260040160405180910390fd5b60098290556040518281527f62ff17080925adbeb90d914efe5f615d059f19c2e728740b500d1e84ba65989a906020015b60405180910390a15050565b6108c56115a0565b6108ce336115f9565b6108da333360016116b1565b6108e46001600b55565b565b335f908152600a602052604090205460ff1615801561092c575061092a7f5e17fc5225d4a099df75359ce1f405503ca79498a8dc46a7d583235a0ee45c1633611755565b155b1561094a57604051634ca8886760e01b815260040160405180910390fd5b600454816109588282612977565b92505f5443106109745761096b836117ef565b5f600455610a1d565b5f7f00000000000000000000000000000000000000000000000000000000000000005f546109a2919061292e565b6109ac904361292e565b90505f816001546109bd9190612941565b90505f856109cd836103e8612941565b6109d79190612958565b90507f0000000000000000000000000000000000000000000000000000000000000000811015610a1357610a0a866117ef565b5f600455610a19565b60048690555b5050505b600454604080518481526020810184905280820192909252517fe4a19739e7048ef5e90c7a157e8fb37a6e90cb8de298625227540d2443b9769c9181900360600190a1610a956001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633308461196d565b505050565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610b25573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b49919061298a565b610b6657604051634ca8886760e01b815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000000610ba457604051639fe610b960e01b815260040160405180910390fd5b600f610bb0600c6119d8565b10610bce57604051635024a38f60e01b815260040160405180910390fd5b610bf682604051806040016040528060068152602001651c995dd85c9960d21b815250611577565b610c01600c836119e1565b610c1e57604051633e04f87160e01b815260040160405180910390fd5b6040516001600160a01b03831681527fd432e6f46dbf91c120fdfa95a1f4bf5c43f04d957fbc3a32e693be0d29bf17b0906020016108b1565b604051632474521560e21b81527f712b73613835f525c3c675d2245c737ba70afbd0b97264479c97d623f214159460048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610ce2573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d06919061298a565b610d2357604051634ca8886760e01b815260040160405180910390fd5b610d4a83604051806040016040528060058152602001643a37b5b2b760d91b815250611577565b610d7582604051806040016040528060098152602001681c9958da5c1a595b9d60ba1b815250611577565b306001600160a01b03831603610dae57604051634726455360e11b81526001600160a01b03831660048201526024015b60405180910390fd5b610db7836110f1565b610ddf57604051630188053b60e41b81526001600160a01b0384166004820152602401610da5565b6301e13380600254610df19190612977565b43108015610e3057507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b0316145b15610e4e57604051633d38b78360e01b815260040160405180910390fd5b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeed196001600160a01b03841601610ed557478015610ecf57604080516001600160a01b038087168252851660208201529081018290527ffff3b3844276f57024e0b42afec1a37f75db36511e43819a4f2a63ab7862b6489060600160405180910390a1610ecf83826119f5565b50505050565b6040516370a0823160e01b81523060048201525f906001600160a01b038516906370a0823190602401602060405180830381865afa158015610f19573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f3d91906129c0565b90508015610ecf57604080516001600160a01b038087168252851660208201529081018290527ffff3b3844276f57024e0b42afec1a37f75db36511e43819a4f2a63ab7862b6489060600160405180910390a1610ecf6001600160a01b0385168483611b0a565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa15801561102f573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611053919061298a565b61107057604051634ca8886760e01b815260040160405180910390fd5b6001600160a01b0382165f908152600a602052604090205460ff166110a85760405163d3ed043d60e01b815260040160405180910390fd5b6001600160a01b0382165f818152600a6020526040808220805460ff19169055517fcdd2e9b91a56913d370075169cefa1602ba36be5301664f752192bb1709df7579190a25050565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b03160361113257505f919050565b506001919050565b5f8054431061114957505f5490565b435b905090565b61115a8282611b3a565b61118f6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633308461196d565b5050565b336001600160a01b0384161480159061123d57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166330d960af6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611203573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061122791906129a5565b6001600160a01b0316336001600160a01b031614155b1561125b57604051634ca8886760e01b815260040160405180910390fd5b610a95838383611c21565b5f80611271600e5490565b9050805f0361128257505060035490565b8060015460025461129161113a565b61129b919061292e565b6112a59190612941565b6112b790670de0b6b3a7640000612941565b6112c19190612958565b6003546112ce9190612977565b91505090565b5f61114b600c6119d8565b606061114b600c611c47565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015611376573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061139a919061298a565b6113b757604051634ca8886760e01b815260040160405180910390fd5b6113df82604051806040016040528060068152602001651dd85b1b195d60d21b815250611577565b6001600160a01b0382165f908152600a602052604090205460ff161561141857604051633e04f87160e01b815260040160405180910390fd5b6001600160a01b0382165f818152600a6020526040808220805460ff19166001179055517fa850ae9193f515cbae8d35e8925bd2be26627fc91bce650b8652ed254e9cab039190a25050565b5f610601600c83611c53565b336001600160a01b0384161480159061151a57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166330d960af6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156114e0573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061150491906129a5565b6001600160a01b0316336001600160a01b031614155b1561153857604051634ca8886760e01b815260040160405180910390fd5b611543838383611c5e565b610a956001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168484611b0a565b6001600160a01b03821661118f578060405163eac0d38960e01b8152600401610da591906129d7565b6002600b54036115f25760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610da5565b6002600b55565b5f611602611266565b60035561160d61113a565b6002556003541561165c576001600160a01b0382161561165c576116308261058c565b6001600160a01b0383165f90815260086020908152604080832084905560035460079092529091205590505b6003546002546040805184815260208101939093528201526001600160a01b038316907f469d38647ec007a9c93421468c92550d50fccc01ae12e149b1216aa9b0136fc7906060015b60405180910390a25050565b6116bb8383611d58565b5f6116c6600c6119d8565b90508115610ecf575f5b8181101561174e576116e3600c82611c53565b604051636b09169560e01b81526001600160a01b03878116600483015286811660248301529190911690636b091695906044015f604051808303815f87803b15801561172d575f80fd5b505af115801561173f573d5f803e3d5ffd5b505050508060010190506116d0565b5050505050565b604051632474521560e21b8152600481018390526001600160a01b0382811660248301525f917f0000000000000000000000000000000000000000000000000000000000000000909116906391d1485490604401602060405180830381865afa1580156117c4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117e8919061298a565b9392505050565b8060065f8282546118009190612977565b9091555050600e545f03611847575f546002541015611842576001546002545f5461182b919061292e565b6118359190612941565b61183f9082612977565b90505b611880565b5f54431015611880575f435f5461185e919061292e565b90505f6001548261186f9190612941565b905061187b8184612977565b925050505b6118895f6115f9565b6118b37f000000000000000000000000000000000000000000000000000000000000000082612958565b60018190556118d557604051631f2a200560e01b815260040160405180910390fd5b600581905543600281905561190b907f000000000000000000000000000000000000000000000000000000000000000090612977565b5f819055600154600254600654604080518681526020810194909452830191909152606082019290925260808101919091527f8ce8cbe5f803930b0c6afe4640018bbfb02cbb5b0bfbe051b25a155201e80dac9060a00160405180910390a150565b6040516001600160a01b0380851660248301528316604482015260648101829052610ecf9085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526120e3565b5f610601825490565b5f6117e8836001600160a01b0384166121b4565b80471015611a455760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e63650000006044820152606401610da5565b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f8114611a8e576040519150601f19603f3d011682016040523d82523d5f602084013e611a93565b606091505b5050905080610a955760405162461bcd60e51b815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d617920686176652072657665727465640000000000006064820152608401610da5565b6040516001600160a01b038316602482015260448101829052610a9590849063a9059cbb60e01b906064016119a1565b611b43826115f9565b611b4d8282612200565b5f611b58600c6119d8565b90505f5b81811015611bd957611b6f600c82611c53565b6040516356e4bb9760e11b81526001600160a01b03868116600483015260248201869052919091169063adc9772e906044015f604051808303815f87803b158015611bb8575f80fd5b505af1158015611bca573d5f803e3d5ffd5b50505050806001019050611b5c565b5081600e5f828254611beb9190612977565b90915550506001600160a01b0383165f908152600f602052604081208054849290611c17908490612977565b9091555050505050565b611c296115a0565b611c32836115f9565b611c3d8383836116b1565b610a956001600b55565b60605f6117e88361228c565b5f6117e883836122e5565b611c67836115f9565b611c71838361230b565b5f611c7c600c6119d8565b90505f5b81811015611cfd57611c93600c82611c53565b60405163f3fef3a360e01b81526001600160a01b03878116600483015260248201879052919091169063f3fef3a3906044015f604051808303815f87803b158015611cdc575f80fd5b505af1158015611cee573d5f803e3d5ffd5b50505050806001019050611c80565b508115611d1057611d10848560016116b1565b82600e5f828254611d21919061292e565b90915550506001600160a01b0384165f908152600f602052604081208054859290611d4d90849061292e565b909155505050505050565b611d8182604051806040016040528060078152602001661858d8dbdd5b9d60ca1b815250611577565b611dac81604051806040016040528060098152602001681c9958da5c1a595b9d60ba1b815250611577565b5f611db68361058c565b90505f807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663024d381b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e16573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e3a91906129a5565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663f543bb0e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e96573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611eba91906129a5565b91509150825f03611ecc575050505050565b806001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316141580611f0d5750600954155b15611fa4576001600160a01b038086165f818152600860205260408082209190915551918616917f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e90611f639087815260200190565b60405180910390a3611f9f6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168585611b0a565b61174e565b604051633f6e925b60e01b8152600481018490526001600160a01b03831690633f6e925b90602401602060405180830381865afa158015611fe7573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061200b919061298a565b1561174e576001600160a01b038086165f818152600860205260408082209190915551918616917f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e906120619087815260200190565b60405180910390a3612074818385612397565b600954604051637628a37d60e01b81526004810185905260248101919091526001600160a01b038581166044830152831690637628a37d906064015f604051808303815f87803b1580156120c6575f80fd5b505af11580156120d8573d5f803e3d5ffd5b505050505050505050565b5f612137826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166124389092919063ffffffff16565b805190915015610a955780806020019051810190612155919061298a565b610a955760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610da5565b5f8181526001830160205260408120546121f957508154600181810184555f848152602080822090930184905584548482528286019093526040902091909155610601565b505f610601565b61222982604051806040016040528060078152602001661858d8dbdd5b9d60ca1b815250611577565b6122518160405180604001604052806006815260200165185b5bdd5b9d60d21b81525061244e565b816001600160a01b03167f9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d826040516116a591815260200190565b6060815f018054806020026020016040519081016040528092919081815260200182805480156122d957602002820191905f5260205f20905b8154815260200190600101908083116122c5575b50505050509050919050565b5f825f0182815481106122fa576122fa612a0c565b905f5260205f200154905092915050565b61233482604051806040016040528060078152602001661858d8dbdd5b9d60ca1b815250611577565b61235c8160405180604001604052806006815260200165185b5bdd5b9d60d21b81525061244e565b816001600160a01b03167f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5826040516116a591815260200190565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301525f919085169063dd62ed3e90604401602060405180830381865afa1580156123e4573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061240891906129c0565b90508015612424576124246001600160a01b0385168483612470565b610ecf6001600160a01b0385168484612579565b606061244684845f85612628565b949350505050565b815f0361118f5780604051634389d5ab60e01b8152600401610da591906129d7565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301525f919085169063dd62ed3e90604401602060405180830381865afa1580156124bd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906124e191906129c0565b9050818110156125455760405162461bcd60e51b815260206004820152602960248201527f5361666545524332303a2064656372656173656420616c6c6f77616e63652062604482015268656c6f77207a65726f60b81b6064820152608401610da5565b6040516001600160a01b0384166024820152828203604482018190529061174e90869063095ea7b360e01b906064016119a1565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301525f91839186169063dd62ed3e90604401602060405180830381865afa1580156125c7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906125eb91906129c0565b6125f59190612977565b6040516001600160a01b038516602482015260448101829052909150610ecf90859063095ea7b360e01b906064016119a1565b6060824710156126895760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610da5565b5f80866001600160a01b031685876040516126a49190612a20565b5f6040518083038185875af1925050503d805f81146126de576040519150601f19603f3d011682016040523d82523d5f602084013e6126e3565b606091505b50915091506126f4878383876126ff565b979650505050505050565b6060831561276d5782515f03612766576001600160a01b0385163b6127665760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610da5565b5081612446565b61244683838151156127825781518083602001fd5b8060405162461bcd60e51b8152600401610da591906129d7565b6001600160a01b03811681146127b0575f80fd5b50565b5f602082840312156127c3575f80fd5b81356117e88161279c565b5f602082840312156127de575f80fd5b5035919050565b5f80604083850312156127f6575f80fd5b82356128018161279c565b915060208301356128118161279c565b809150509250929050565b5f806040838503121561282d575f80fd5b82356128388161279c565b946020939093013593505050565b80151581146127b0575f80fd5b5f805f60608486031215612865575f80fd5b83356128708161279c565b925060208401356128808161279c565b9150604084013561289081612846565b809150509250925092565b602080825282518282018190525f918401906040840190835b818110156128db5783516001600160a01b03168352602093840193909201916001016128b4565b509095945050505050565b5f805f606084860312156128f8575f80fd5b83356129038161279c565b925060208401359150604084013561289081612846565b634e487b7160e01b5f52601160045260245ffd5b818103818111156106015761060161291a565b80820281158282048414176106015761060161291a565b5f8261297257634e487b7160e01b5f52601260045260245ffd5b500490565b808201808211156106015761060161291a565b5f6020828403121561299a575f80fd5b81516117e881612846565b5f602082840312156129b5575f80fd5b81516117e88161279c565b5f602082840312156129d0575f80fd5b5051919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b634e487b7160e01b5f52603260045260245ffd5b5f82518060208501845e5f92019182525091905056fea2646970667358221220b151f97e63fd28dba4d1bf01ed4e693c5b097535bc3d04e6dae214bb2f98c8ee64736f6c634300081a0033bb7154c024c448c6ea6d002f0df26c89f4a55783a3b480be7dfb28e216145f78a26469706673582212204cbd0896841166569afaeaa9853a8619bdfbd74e0044e3acd730c78b6bced62364736f6c634300081a00336080604052348015600e575f80fd5b50601633601a565b6069565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b610652806100765f395ff3fe608060405260043610610079575f3560e01c80639623609d1161004c5780639623609d1461010957806399a88ec41461011c578063f2fde38b1461013b578063f3b7dead1461015a575f80fd5b8063204e1c7a1461007d578063715018a6146100b85780637eff275e146100ce5780638da5cb5b146100ed575b5f80fd5b348015610088575f80fd5b5061009c610097366004610479565b610179565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c3575f80fd5b506100cc610204565b005b3480156100d9575f80fd5b506100cc6100e836600461049b565b610217565b3480156100f8575f80fd5b505f546001600160a01b031661009c565b6100cc6101173660046104e6565b61027a565b348015610127575f80fd5b506100cc61013636600461049b565b6102e5565b348015610146575f80fd5b506100cc610155366004610479565b61031b565b348015610165575f80fd5b5061009c610174366004610479565b610399565b5f805f836001600160a01b031660405161019d90635c60da1b60e01b815260040190565b5f60405180830381855afa9150503d805f81146101d5576040519150601f19603f3d011682016040523d82523d5f602084013e6101da565b606091505b5091509150816101e8575f80fd5b808060200190518101906101fc91906105bd565b949350505050565b61020c6103bd565b6102155f610416565b565b61021f6103bd565b6040516308f2839760e41b81526001600160a01b038281166004830152831690638f283970906024015b5f604051808303815f87803b158015610260575f80fd5b505af1158015610272573d5f803e3d5ffd5b505050505050565b6102826103bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef2869034906102b290869086906004016105d8565b5f604051808303818588803b1580156102c9575f80fd5b505af11580156102db573d5f803e3d5ffd5b5050505050505050565b6102ed6103bd565b604051631b2ce7f360e11b81526001600160a01b038281166004830152831690633659cfe690602401610249565b6103236103bd565b6001600160a01b03811661038d5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b61039681610416565b50565b5f805f836001600160a01b031660405161019d906303e1469160e61b815260040190565b5f546001600160a01b031633146102155760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610384565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b0381168114610396575f80fd5b5f60208284031215610489575f80fd5b813561049481610465565b9392505050565b5f80604083850312156104ac575f80fd5b82356104b781610465565b915060208301356104c781610465565b809150509250929050565b634e487b7160e01b5f52604160045260245ffd5b5f805f606084860312156104f8575f80fd5b833561050381610465565b9250602084013561051381610465565b9150604084013567ffffffffffffffff81111561052e575f80fd5b8401601f8101861361053e575f80fd5b803567ffffffffffffffff811115610558576105586104d2565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610587576105876104d2565b60405281815282820160200188101561059e575f80fd5b816020840160208301375f602083830101528093505050509250925092565b5f602082840312156105cd575f80fd5b815161049481610465565b60018060a01b0383168152604060208201525f82518060408401528060208501606085015e5f606082850101526060601f19601f830116840101915050939250505056fea264697066735822122002804c7d35068de8b830aebba96502681e149d52c8342255d9daccb463ddd0a664736f6c634300081a0033000000000000000000000000734f854c33a149ff7efe120a921c9b39b397d5b10000000000000000000000005552e8b0bd553c299fbf9731c92dd6c3b5ab39500000000000000000000000000591c1f3e0d587f88261509c0f8295ce8474eb4d000000000000000000000000000000000000000000000000000000000000033e0000000000000000000000000000000000000000000000000000000000309235

Deployed Bytecode

0x6080604052600436106100e4575f3560e01c806390a4551911610087578063d784d42611610057578063d784d42614610266578063db475d3914610285578063f12baf5c14610298578063f5b7fe87146102ca575f80fd5b806390a4551914610144578063b6d72520146101dd578063bc43cbaf14610200578063cdf456e114610233575f80fd5b80635c60da1b116100c25780635c60da1b146101635780635e3119b6146101825780636a306d61146101a15780636f2ddd93146101c0575f80fd5b80633e47158c146100e857806347c02661146101235780635c24feaf14610144575b5f80fd5b3480156100f3575f80fd5b505f54610106906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561012e575f80fd5b5061014261013d3660046113c9565b6102df565b005b34801561014f575f80fd5b5061014261015e3660046113c9565b6103d5565b34801561016e575f80fd5b50600154610106906001600160a01b031681565b34801561018d575f80fd5b5061014261019c3660046113eb565b6103ee565b3480156101ac575f80fd5b506101426101bb3660046113eb565b6104b0565b3480156101cb575f80fd5b506001546001600160a01b0316610106565b3480156101e8575f80fd5b506101f260025481565b60405190815260200161011a565b34801561020b575f80fd5b506101067f000000000000000000000000b99357afcc888cc995f2fecbdeb5d02f68a5d00681565b34801561023e575f80fd5b506101067f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d403889481565b348015610271575f80fd5b506101426102803660046113c9565b610572565b6101066102933660046114a1565b610719565b3480156102a3575f80fd5b507f000000000000000000000000734f854c33a149ff7efe120a921c9b39b397d5b1610106565b3480156102d5575f80fd5b506101f260035481565b604051632474521560e21b81525f8051602061538983398151915260048201819052336024830152907f000000000000000000000000b99357afcc888cc995f2fecbdeb5d02f68a5d0066001600160a01b0316906391d1485490604401602060405180830381865afa158015610357573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061037b9190611585565b61039857604051634ca8886760e01b815260040160405180910390fd5b6103c8826040518060400160405280600e81526020016d2fb732bba83937bc3ca0b236b4b760911b815250610bc4565b6103d182610bf6565b5050565b60405163d623472560e01b815260040160405180910390fd5b604051632474521560e21b81525f8051602061538983398151915260048201819052336024830152907f000000000000000000000000b99357afcc888cc995f2fecbdeb5d02f68a5d0066001600160a01b0316906391d1485490604401602060405180830381865afa158015610466573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061048a9190611585565b6104a757604051634ca8886760e01b815260040160405180910390fd5b6103d182610c8b565b604051632474521560e21b81525f8051602061538983398151915260048201819052336024830152907f000000000000000000000000b99357afcc888cc995f2fecbdeb5d02f68a5d0066001600160a01b0316906391d1485490604401602060405180830381865afa158015610528573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061054c9190611585565b61056957604051634ca8886760e01b815260040160405180910390fd5b6103d182610ccc565b604051632474521560e21b81525f8051602061538983398151915260048201819052336024830152907f000000000000000000000000b99357afcc888cc995f2fecbdeb5d02f68a5d0066001600160a01b0316906391d1485490604401602060405180830381865afa1580156105ea573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061060e9190611585565b61062b57604051634ca8886760e01b815260040160405180910390fd5b61065f82604051806040016040528060128152602001712fb732bba4b6b83632b6b2b73a30ba34b7b760711b815250610bc4565b7f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388946001600160a01b0316826001600160a01b03166338d52e0f6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106c5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106e991906115a4565b6001600160a01b0316146107105760405163c52a9bd360e01b815260040160405180910390fd5b6103d182610d0d565b604051632474521560e21b81527f643a332a1a6de98a36f48a7596dfafd4ea1b69babd8e98b38d6b2a652b59d09b600482018190523360248301525f916001600160a01b037f000000000000000000000000b99357afcc888cc995f2fecbdeb5d02f68a5d00616906391d1485490604401602060405180830381865afa1580156107a5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107c99190611585565b6107e657604051634ca8886760e01b815260040160405180910390fd5b341561080557604051630b47f1cd60e31b815260040160405180910390fd5b6001545f546040516001600160a01b03928316929091169061082690611398565b6001600160a01b039283168152911660208201526060604082018190525f90820152608001604051809103905ff080158015610864573d5f803e3d5ffd5b5091505f7f000000000000000000000000734f854c33a149ff7efe120a921c9b39b397d5b17f000000000000000000000000734f854c33a149ff7efe120a921c9b39b397d5b16001600160a01b031663f543bb0e6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108e5573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061090991906115a4565b60025460035460018760405161091e906113a5565b6001600160a01b0396871681529486166020860152604085019390935260608401919091521515608083015290911660a082015260c001604051809103905ff08015801561096e573d5f803e3d5ffd5b5090505f60015f9054906101000a90046001600160a01b03166001600160a01b0316639dbbabf46040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109c2573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109e691906115bf565b90507f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d4038894610a1e6001600160a01b038216333085610e67565b610a29818684610ed8565b60405163e649e2fb60e01b81526001600160a01b0386169063e649e2fb90610a5b908d908d908c908c90600401611604565b5f604051808303815f87803b158015610a72575f80fd5b505af1158015610a84573d5f803e3d5ffd5b5050604051630e9918b960e21b81526001600160a01b03868116600483015288169250633a6462e491506024015f604051808303815f87803b158015610ac8575f80fd5b505af1158015610ada573d5f803e3d5ffd5b505050507f000000000000000000000000734f854c33a149ff7efe120a921c9b39b397d5b16001600160a01b031663047e51386040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b3a573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b5e91906115a4565b6040516312b5ad0160e11b81526001600160a01b038781166004830152919091169063256b5a02906024015f604051808303815f87803b158015610ba0575f80fd5b505af1158015610bb2573d5f803e3d5ffd5b50505050505050509695505050505050565b6001600160a01b0382166103d1578060405163eac0d38960e01b8152600401610bed919061165d565b60405180910390fd5b5f546001600160a01b0390811690821603610c2457604051633e04f87160e01b815260040160405180910390fd5b5f54604080516001600160a01b03928316815291831660208301527f8b229dfa15ad44db21d7fe7e9dca521b2dcc866b16f1c9ffa6fde044782b2368910160405180910390a15f80546001600160a01b0319166001600160a01b0392909216919091179055565b60035460408051918252602082018390527f945131392c364f55e7dd69d3aefc9f5b625be9913dc0558dbb7feb538eb35ac9910160405180910390a1600355565b60025460408051918252602082018390527fd527863f4f3ab7a5b484cd73e646ba29392452df01668b796a5a143bcd032aef910160405180910390a1600255565b6001546001600160a01b0390811690821603610d3c57604051633e04f87160e01b815260040160405180910390fd5b806001600160a01b031663f12baf5c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d78573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d9c91906115a4565b6001600160a01b03167f000000000000000000000000734f854c33a149ff7efe120a921c9b39b397d5b16001600160a01b031614610dfe57604051632f6b3b6360e01b81523060048201526001600160a01b0382166024820152604401610bed565b600154604080516001600160a01b03928316815291831660208301527f1acacd74b878778c66cc2abd6987699d6602bfb4b7f244c64c6ed0d61054bb5e910160405180910390a1600180546001600160a01b0319166001600160a01b0392909216919091179055565b6040516001600160a01b0380851660248301528316604482015260648101829052610ed29085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610f79565b50505050565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301525f919085169063dd62ed3e90604401602060405180830381865afa158015610f25573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f4991906115bf565b90508015610f6557610f656001600160a01b038516848361104f565b610ed26001600160a01b038516848461115f565b5f610fcd826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031661120e9092919063ffffffff16565b80519091501561104a5780806020019051810190610feb9190611585565b61104a5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610bed565b505050565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301525f919085169063dd62ed3e90604401602060405180830381865afa15801561109c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110c091906115bf565b9050818110156111245760405162461bcd60e51b815260206004820152602960248201527f5361666545524332303a2064656372656173656420616c6c6f77616e63652062604482015268656c6f77207a65726f60b81b6064820152608401610bed565b6040516001600160a01b0384166024820152828203604482018190529061115890869063095ea7b360e01b90606401610e9b565b5050505050565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301525f91839186169063dd62ed3e90604401602060405180830381865afa1580156111ad573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111d191906115bf565b6111db919061166f565b6040516001600160a01b038516602482015260448101829052909150610ed290859063095ea7b360e01b90606401610e9b565b606061121c84845f85611224565b949350505050565b6060824710156112855760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610bed565b5f80866001600160a01b031685876040516112a09190611694565b5f6040518083038185875af1925050503d805f81146112da576040519150601f19603f3d011682016040523d82523d5f602084013e6112df565b606091505b50915091506112f0878383876112fb565b979650505050505050565b606083156113695782515f03611362576001600160a01b0385163b6113625760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610bed565b508161121c565b61121c838381511561137e5781518083602001fd5b8060405162461bcd60e51b8152600401610bed919061165d565b610d0a806116ab83390190565b612fd4806123b583390190565b6001600160a01b03811681146113c6575f80fd5b50565b5f602082840312156113d9575f80fd5b81356113e4816113b2565b9392505050565b5f602082840312156113fb575f80fd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f830112611425575f80fd5b813567ffffffffffffffff81111561143f5761143f611402565b604051601f8201601f19908116603f0116810167ffffffffffffffff8111828210171561146e5761146e611402565b604052818152838201602001851015611485575f80fd5b816020850160208301375f918101602001919091529392505050565b5f805f805f8060a087890312156114b6575f80fd5b86356114c1816113b2565b9550602087013567ffffffffffffffff8111156114dc575f80fd5b6114e889828a01611416565b955050604087013567ffffffffffffffff811115611504575f80fd5b61151089828a01611416565b94505060608701359250608087013567ffffffffffffffff811115611533575f80fd5b87015f80601f83018b13611545575f80fd5b50813567ffffffffffffffff81111561155c575f80fd5b6020830191508a6020828501011115611573575f80fd5b979a9699509497509295939492505050565b5f60208284031215611595575f80fd5b815180151581146113e4575f80fd5b5f602082840312156115b4575f80fd5b81516113e4816113b2565b5f602082840312156115cf575f80fd5b5051919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b606081525f61161660608301876115d6565b828103602084015261162881876115d6565b90508281036040840152838152838560208301375f602085830101526020601f19601f86011682010191505095945050505050565b602081525f6113e460208301846115d6565b8082018082111561168e57634e487b7160e01b5f52601160045260245ffd5b92915050565b5f82518060208501845e5f92019182525091905056fe6080604052604051610d0a380380610d0a833981016040819052610022916103c3565b828161002f82825f610043565b5061003b90508261006e565b5050506104df565b61004c836100db565b5f825111806100585750805b1561006957610067838361011a565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6100ad5f80516020610cc3833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a16100d881610146565b50565b6100e4816101e1565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061013f8383604051806060016040528060278152602001610ce360279139610275565b9392505050565b6001600160a01b0381166101b05760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b805f80516020610cc38339815191525b80546001600160a01b0319166001600160a01b039290921691909117905550565b6001600160a01b0381163b61024e5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016101a7565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6101c0565b60605f80856001600160a01b0316856040516102919190610494565b5f60405180830381855af49150503d805f81146102c9576040519150601f19603f3d011682016040523d82523d5f602084013e6102ce565b606091505b5090925090506102e0868383876102ea565b9695505050505050565b606083156103585782515f03610351576001600160a01b0385163b6103515760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016101a7565b5081610362565b610362838361036a565b949350505050565b81511561037a5781518083602001fd5b8060405162461bcd60e51b81526004016101a791906104aa565b80516001600160a01b03811681146103aa575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f805f606084860312156103d5575f80fd5b6103de84610394565b92506103ec60208501610394565b60408501519092506001600160401b03811115610407575f80fd5b8401601f81018613610417575f80fd5b80516001600160401b03811115610430576104306103af565b604051601f8201601f19908116603f011681016001600160401b038111828210171561045e5761045e6103af565b604052818152828201602001881015610475575f80fd5b8160208401602083015e5f602083830101528093505050509250925092565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b6107d7806104ec5f395ff3fe60806040526004361061004d575f3560e01c80633659cfe6146100645780634f1ef286146100835780635c60da1b146100965780638f283970146100c6578063f851a440146100e55761005c565b3661005c5761005a6100f9565b005b61005a6100f9565b34801561006f575f80fd5b5061005a61007e366004610698565b610113565b61005a6100913660046106b1565b61014e565b3480156100a1575f80fd5b506100aa6101b4565b6040516001600160a01b03909116815260200160405180910390f35b3480156100d1575f80fd5b5061005a6100e0366004610698565b6101e4565b3480156100f0575f80fd5b506100aa610204565b610101610224565b61011161010c6102b9565b6102c2565b565b61011b6102e0565b6001600160a01b03163303610146576101438160405180602001604052805f8152505f610312565b50565b6101436100f9565b6101566102e0565b6001600160a01b031633036101ac576101a78383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060019250610312915050565b505050565b6101a76100f9565b5f6101bd6102e0565b6001600160a01b031633036101d9576101d46102b9565b905090565b6101e16100f9565b90565b6101ec6102e0565b6001600160a01b03163303610146576101438161033c565b5f61020d6102e0565b6001600160a01b031633036101d9576101d46102e0565b61022c6102e0565b6001600160a01b031633036101115760405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b5f6101d4610390565b365f80375f80365f845af43d5f803e8080156102dc573d5ff35b3d5ffd5b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b61031b836103b7565b5f825111806103275750805b156101a75761033683836103f6565b50505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103656102e0565b604080516001600160a01b03928316815291841660208301520160405180910390a161014381610422565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610303565b6103c0816104cb565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061041b838360405180606001604052806027815260200161077b6027913961055f565b9392505050565b6001600160a01b0381166104875760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b60648201526084016102b0565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b6001600160a01b0381163b6105385760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016102b0565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6104aa565b60605f80856001600160a01b03168560405161057b919061072f565b5f60405180830381855af49150503d805f81146105b3576040519150601f19603f3d011682016040523d82523d5f602084013e6105b8565b606091505b50915091506105c9868383876105d3565b9695505050505050565b606083156106415782515f0361063a576001600160a01b0385163b61063a5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016102b0565b508161064b565b61064b8383610653565b949350505050565b8151156106635781518083602001fd5b8060405162461bcd60e51b81526004016102b09190610745565b80356001600160a01b0381168114610693575f80fd5b919050565b5f602082840312156106a8575f80fd5b61041b8261067d565b5f805f604084860312156106c3575f80fd5b6106cc8461067d565b9250602084013567ffffffffffffffff8111156106e7575f80fd5b8401601f810186136106f7575f80fd5b803567ffffffffffffffff81111561070d575f80fd5b86602082840101111561071e575f80fd5b939660209190910195509293505050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220416fb094790ccffff95b19794e9b2cb987a33ce6bba82c21d77b2431561c524464736f6c634300081a0033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564610180604052348015610010575f80fd5b50604051612fd4380380612fd483398101604081905261002f9161037b565b858585857ffde2f69a846a71295e563d91ade82dc70e9eda278403d1aece24d0ded949403a868585858585846001600160a01b031663bc43cbaf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610096573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906100ba91906103e7565b6001600160a01b0381166100e157604051630cbe126f60e11b815260040160405180910390fd5b6001600160a01b031660805260408051808201909152600c81526b2fb932bbb0b9322a37b5b2b760a11b602082015261011b9085906102e1565b610153826040518060400160405280601081526020016f5f6475726174696f6e496e426c6f636b60801b81525061030e60201b60201c565b61018a836040518060400160405280600f81526020016e5f6e6577526577617264526174696f60881b81525061030e60201b60201c565b6101bd816040518060400160405280600b81526020016a5f726577617264526f6c6560a81b81525061033060201b60201c565b6001600160a01b0385811660e08190526040516316bfae7f60e31b815291861660048301529063b5fd73f890602401602060405180830381865afa158015610207573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061022b9190610409565b61026c57604051634389d5ab60e01b815260206004820152600c60248201526b2fb932bbb0b9322a37b5b2b760a11b60448201526064015b60405180910390fd5b6001600160a01b039093166101005260c09190915260a05261012052506001600b55151561014052505060408051808201909152600d81526c2fb9ba30b5b4b733aa37b5b2b760991b60208201526102c9935084925090506102e1565b6001600160a01b031661016052506104579350505050565b6001600160a01b03821661030a578060405163eac0d38960e01b81526004016102639190610422565b5050565b815f0361030a5780604051634389d5ab60e01b81526004016102639190610422565b8161030a5780604051634389d5ab60e01b81526004016102639190610422565b6001600160a01b0381168114610364575f80fd5b50565b80518015158114610376575f80fd5b919050565b5f805f805f8060c08789031215610390575f80fd5b865161039b81610350565b60208801519096506103ac81610350565b60408801516060890151919650945092506103c960808801610367565b915060a08701516103d981610350565b809150509295509295509295565b5f602082840312156103f7575f80fd5b815161040281610350565b9392505050565b5f60208284031215610419575f80fd5b61040282610367565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b60805160a05160c05160e05161010051610120516101405161016051612a6c6105685f395f81816103cd015281816110f401528181611167015261155001525f81816104870152610b6801525f818161061601528181610aa901528181610fb301526112fa01525f818161056a01528181610a6d01528181610dfc01528181611ed80152611f7801525f81816106de01528181610783015281816111a90152818161148601528181611dbc0152611e3c01525f818161037501526109db01525f81816102d0015281816109770152818161188e01526118e601525f81816104ae0152818161064501528181610ad801528181610c9501528181610fe201528181611329015261177d0152612a6c5ff3fe608060405234801561000f575f80fd5b5060043610610233575f3560e01c80638b87634711610135578063cd8e33d4116100b4578063e43252d711610079578063e43252d714610521578063e665b41414610534578063ead5d35914610547578063ee495fb61461055a578063f7c618c114610565575f80fd5b8063cd8e33d4146104eb578063ce60089d146104f3578063d55a23f4146104fb578063df136d6514610503578063e21c81d31461050c575f80fd5b8063adc9772e116100fa578063adc9772e1461046f578063b263487f14610482578063bc43cbaf146104a9578063c5285794146104d0578063cd3daf9d146104e3575f80fd5b80638b87634714610423578063901a7d53146104425780639a8c1c701461044b578063a218141b1461045e578063abe0429c14610467575f80fd5b8063590a41f5116101c15780636f73a38f116101865780636f73a38f1461039757806370a08231146103a057806372f702f3146103c85780637b0a47ee146104075780638ab1d68114610410575f80fd5b8063590a41f51461032e5780635e43c47b1461034157806363d38c3b14610354578063648bf7741461035d5780636c8bcee814610370575f80fd5b806318160ddd1161020757806318160ddd146102c35780631fc93059146102cb578063262d3d6d146102f25780633af32abf146102fb5780633d18b91214610326575f80fd5b80628cc2621461023757806306c933d81461025d5780630700037d1461028f57806314d09249146102ae575b5f80fd5b61024a6102453660046127b3565b61058c565b6040519081526020015b60405180910390f35b61027f61026b3660046127b3565b600a6020525f908152604090205460ff1681565b6040519015158152602001610254565b61024a61029d3660046127b3565b60086020525f908152604090205481565b6102c16102bc3660046127ce565b610607565b005b600e5461024a565b61024a7f000000000000000000000000000000000000000000000000000000000000000081565b61024a60065481565b61027f6103093660046127b3565b6001600160a01b03165f908152600a602052604090205460ff1690565b6102c16108bd565b6102c161033c3660046127ce565b6108e6565b6102c161034f3660046127b3565b610a9a565b61024a60045481565b6102c161036b3660046127e5565b610c57565b61024a7f000000000000000000000000000000000000000000000000000000000000000081565b61024a60095481565b61024a6103ae3660046127b3565b6001600160a01b03165f908152600f602052604090205490565b6103ef7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610254565b61024a60015481565b6102c161041e3660046127b3565b610fa4565b61024a6104313660046127b3565b60076020525f908152604090205481565b61024a60055481565b61027f6104593660046127b3565b6110f1565b61024a60025481565b61024a61113a565b6102c161047d36600461281c565b611150565b61027f7f000000000000000000000000000000000000000000000000000000000000000081565b6103ef7f000000000000000000000000000000000000000000000000000000000000000081565b6102c16104de366004612853565b611193565b61024a611266565b61024a5f5481565b61024a600f81565b61024a6112d4565b61024a60035481565b6105146112df565b604051610254919061289b565b6102c161052f3660046127b3565b6112eb565b6103ef6105423660046127ce565b611464565b6102c16105553660046128e6565b611470565b61024a6301e1338081565b6103ef7f000000000000000000000000000000000000000000000000000000000000000081565b6001600160a01b0381165f908152600860209081526040808320546007909252822054670de0b6b3a7640000906105c1611266565b6105cb919061292e565b6001600160a01b0385165f908152600f60205260409020546105ed9190612941565b6105f79190612958565b6106019190612977565b92915050565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610692573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106b6919061298a565b6106d357604051634ca8886760e01b815260040160405180910390fd5b8115610880576107817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663024d381b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610738573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061075c91906129a5565b60405180604001604052806007815260200166616363546f6b6560c81b815250611577565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663024d381b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107dd573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061080191906129a5565b6001600160a01b0316635fec5c646040518163ffffffff1660e01b8152600401602060405180830381865afa15801561083c573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061086091906129c0565b8210156108805760405163bcec4c5360e01b815260040160405180910390fd5b60098290556040518281527f62ff17080925adbeb90d914efe5f615d059f19c2e728740b500d1e84ba65989a906020015b60405180910390a15050565b6108c56115a0565b6108ce336115f9565b6108da333360016116b1565b6108e46001600b55565b565b335f908152600a602052604090205460ff1615801561092c575061092a7f5e17fc5225d4a099df75359ce1f405503ca79498a8dc46a7d583235a0ee45c1633611755565b155b1561094a57604051634ca8886760e01b815260040160405180910390fd5b600454816109588282612977565b92505f5443106109745761096b836117ef565b5f600455610a1d565b5f7f00000000000000000000000000000000000000000000000000000000000000005f546109a2919061292e565b6109ac904361292e565b90505f816001546109bd9190612941565b90505f856109cd836103e8612941565b6109d79190612958565b90507f0000000000000000000000000000000000000000000000000000000000000000811015610a1357610a0a866117ef565b5f600455610a19565b60048690555b5050505b600454604080518481526020810184905280820192909252517fe4a19739e7048ef5e90c7a157e8fb37a6e90cb8de298625227540d2443b9769c9181900360600190a1610a956001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633308461196d565b505050565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610b25573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b49919061298a565b610b6657604051634ca8886760e01b815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000000610ba457604051639fe610b960e01b815260040160405180910390fd5b600f610bb0600c6119d8565b10610bce57604051635024a38f60e01b815260040160405180910390fd5b610bf682604051806040016040528060068152602001651c995dd85c9960d21b815250611577565b610c01600c836119e1565b610c1e57604051633e04f87160e01b815260040160405180910390fd5b6040516001600160a01b03831681527fd432e6f46dbf91c120fdfa95a1f4bf5c43f04d957fbc3a32e693be0d29bf17b0906020016108b1565b604051632474521560e21b81527f712b73613835f525c3c675d2245c737ba70afbd0b97264479c97d623f214159460048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610ce2573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d06919061298a565b610d2357604051634ca8886760e01b815260040160405180910390fd5b610d4a83604051806040016040528060058152602001643a37b5b2b760d91b815250611577565b610d7582604051806040016040528060098152602001681c9958da5c1a595b9d60ba1b815250611577565b306001600160a01b03831603610dae57604051634726455360e11b81526001600160a01b03831660048201526024015b60405180910390fd5b610db7836110f1565b610ddf57604051630188053b60e41b81526001600160a01b0384166004820152602401610da5565b6301e13380600254610df19190612977565b43108015610e3057507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b0316145b15610e4e57604051633d38b78360e01b815260040160405180910390fd5b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeed196001600160a01b03841601610ed557478015610ecf57604080516001600160a01b038087168252851660208201529081018290527ffff3b3844276f57024e0b42afec1a37f75db36511e43819a4f2a63ab7862b6489060600160405180910390a1610ecf83826119f5565b50505050565b6040516370a0823160e01b81523060048201525f906001600160a01b038516906370a0823190602401602060405180830381865afa158015610f19573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f3d91906129c0565b90508015610ecf57604080516001600160a01b038087168252851660208201529081018290527ffff3b3844276f57024e0b42afec1a37f75db36511e43819a4f2a63ab7862b6489060600160405180910390a1610ecf6001600160a01b0385168483611b0a565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa15801561102f573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611053919061298a565b61107057604051634ca8886760e01b815260040160405180910390fd5b6001600160a01b0382165f908152600a602052604090205460ff166110a85760405163d3ed043d60e01b815260040160405180910390fd5b6001600160a01b0382165f818152600a6020526040808220805460ff19169055517fcdd2e9b91a56913d370075169cefa1602ba36be5301664f752192bb1709df7579190a25050565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b03160361113257505f919050565b506001919050565b5f8054431061114957505f5490565b435b905090565b61115a8282611b3a565b61118f6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633308461196d565b5050565b336001600160a01b0384161480159061123d57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166330d960af6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611203573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061122791906129a5565b6001600160a01b0316336001600160a01b031614155b1561125b57604051634ca8886760e01b815260040160405180910390fd5b610a95838383611c21565b5f80611271600e5490565b9050805f0361128257505060035490565b8060015460025461129161113a565b61129b919061292e565b6112a59190612941565b6112b790670de0b6b3a7640000612941565b6112c19190612958565b6003546112ce9190612977565b91505090565b5f61114b600c6119d8565b606061114b600c611c47565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015611376573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061139a919061298a565b6113b757604051634ca8886760e01b815260040160405180910390fd5b6113df82604051806040016040528060068152602001651dd85b1b195d60d21b815250611577565b6001600160a01b0382165f908152600a602052604090205460ff161561141857604051633e04f87160e01b815260040160405180910390fd5b6001600160a01b0382165f818152600a6020526040808220805460ff19166001179055517fa850ae9193f515cbae8d35e8925bd2be26627fc91bce650b8652ed254e9cab039190a25050565b5f610601600c83611c53565b336001600160a01b0384161480159061151a57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166330d960af6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156114e0573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061150491906129a5565b6001600160a01b0316336001600160a01b031614155b1561153857604051634ca8886760e01b815260040160405180910390fd5b611543838383611c5e565b610a956001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168484611b0a565b6001600160a01b03821661118f578060405163eac0d38960e01b8152600401610da591906129d7565b6002600b54036115f25760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610da5565b6002600b55565b5f611602611266565b60035561160d61113a565b6002556003541561165c576001600160a01b0382161561165c576116308261058c565b6001600160a01b0383165f90815260086020908152604080832084905560035460079092529091205590505b6003546002546040805184815260208101939093528201526001600160a01b038316907f469d38647ec007a9c93421468c92550d50fccc01ae12e149b1216aa9b0136fc7906060015b60405180910390a25050565b6116bb8383611d58565b5f6116c6600c6119d8565b90508115610ecf575f5b8181101561174e576116e3600c82611c53565b604051636b09169560e01b81526001600160a01b03878116600483015286811660248301529190911690636b091695906044015f604051808303815f87803b15801561172d575f80fd5b505af115801561173f573d5f803e3d5ffd5b505050508060010190506116d0565b5050505050565b604051632474521560e21b8152600481018390526001600160a01b0382811660248301525f917f0000000000000000000000000000000000000000000000000000000000000000909116906391d1485490604401602060405180830381865afa1580156117c4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117e8919061298a565b9392505050565b8060065f8282546118009190612977565b9091555050600e545f03611847575f546002541015611842576001546002545f5461182b919061292e565b6118359190612941565b61183f9082612977565b90505b611880565b5f54431015611880575f435f5461185e919061292e565b90505f6001548261186f9190612941565b905061187b8184612977565b925050505b6118895f6115f9565b6118b37f000000000000000000000000000000000000000000000000000000000000000082612958565b60018190556118d557604051631f2a200560e01b815260040160405180910390fd5b600581905543600281905561190b907f000000000000000000000000000000000000000000000000000000000000000090612977565b5f819055600154600254600654604080518681526020810194909452830191909152606082019290925260808101919091527f8ce8cbe5f803930b0c6afe4640018bbfb02cbb5b0bfbe051b25a155201e80dac9060a00160405180910390a150565b6040516001600160a01b0380851660248301528316604482015260648101829052610ecf9085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526120e3565b5f610601825490565b5f6117e8836001600160a01b0384166121b4565b80471015611a455760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e63650000006044820152606401610da5565b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f8114611a8e576040519150601f19603f3d011682016040523d82523d5f602084013e611a93565b606091505b5050905080610a955760405162461bcd60e51b815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d617920686176652072657665727465640000000000006064820152608401610da5565b6040516001600160a01b038316602482015260448101829052610a9590849063a9059cbb60e01b906064016119a1565b611b43826115f9565b611b4d8282612200565b5f611b58600c6119d8565b90505f5b81811015611bd957611b6f600c82611c53565b6040516356e4bb9760e11b81526001600160a01b03868116600483015260248201869052919091169063adc9772e906044015f604051808303815f87803b158015611bb8575f80fd5b505af1158015611bca573d5f803e3d5ffd5b50505050806001019050611b5c565b5081600e5f828254611beb9190612977565b90915550506001600160a01b0383165f908152600f602052604081208054849290611c17908490612977565b9091555050505050565b611c296115a0565b611c32836115f9565b611c3d8383836116b1565b610a956001600b55565b60605f6117e88361228c565b5f6117e883836122e5565b611c67836115f9565b611c71838361230b565b5f611c7c600c6119d8565b90505f5b81811015611cfd57611c93600c82611c53565b60405163f3fef3a360e01b81526001600160a01b03878116600483015260248201879052919091169063f3fef3a3906044015f604051808303815f87803b158015611cdc575f80fd5b505af1158015611cee573d5f803e3d5ffd5b50505050806001019050611c80565b508115611d1057611d10848560016116b1565b82600e5f828254611d21919061292e565b90915550506001600160a01b0384165f908152600f602052604081208054859290611d4d90849061292e565b909155505050505050565b611d8182604051806040016040528060078152602001661858d8dbdd5b9d60ca1b815250611577565b611dac81604051806040016040528060098152602001681c9958da5c1a595b9d60ba1b815250611577565b5f611db68361058c565b90505f807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663024d381b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e16573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e3a91906129a5565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663f543bb0e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e96573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611eba91906129a5565b91509150825f03611ecc575050505050565b806001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316141580611f0d5750600954155b15611fa4576001600160a01b038086165f818152600860205260408082209190915551918616917f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e90611f639087815260200190565b60405180910390a3611f9f6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168585611b0a565b61174e565b604051633f6e925b60e01b8152600481018490526001600160a01b03831690633f6e925b90602401602060405180830381865afa158015611fe7573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061200b919061298a565b1561174e576001600160a01b038086165f818152600860205260408082209190915551918616917f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e906120619087815260200190565b60405180910390a3612074818385612397565b600954604051637628a37d60e01b81526004810185905260248101919091526001600160a01b038581166044830152831690637628a37d906064015f604051808303815f87803b1580156120c6575f80fd5b505af11580156120d8573d5f803e3d5ffd5b505050505050505050565b5f612137826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166124389092919063ffffffff16565b805190915015610a955780806020019051810190612155919061298a565b610a955760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610da5565b5f8181526001830160205260408120546121f957508154600181810184555f848152602080822090930184905584548482528286019093526040902091909155610601565b505f610601565b61222982604051806040016040528060078152602001661858d8dbdd5b9d60ca1b815250611577565b6122518160405180604001604052806006815260200165185b5bdd5b9d60d21b81525061244e565b816001600160a01b03167f9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d826040516116a591815260200190565b6060815f018054806020026020016040519081016040528092919081815260200182805480156122d957602002820191905f5260205f20905b8154815260200190600101908083116122c5575b50505050509050919050565b5f825f0182815481106122fa576122fa612a0c565b905f5260205f200154905092915050565b61233482604051806040016040528060078152602001661858d8dbdd5b9d60ca1b815250611577565b61235c8160405180604001604052806006815260200165185b5bdd5b9d60d21b81525061244e565b816001600160a01b03167f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5826040516116a591815260200190565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301525f919085169063dd62ed3e90604401602060405180830381865afa1580156123e4573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061240891906129c0565b90508015612424576124246001600160a01b0385168483612470565b610ecf6001600160a01b0385168484612579565b606061244684845f85612628565b949350505050565b815f0361118f5780604051634389d5ab60e01b8152600401610da591906129d7565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301525f919085169063dd62ed3e90604401602060405180830381865afa1580156124bd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906124e191906129c0565b9050818110156125455760405162461bcd60e51b815260206004820152602960248201527f5361666545524332303a2064656372656173656420616c6c6f77616e63652062604482015268656c6f77207a65726f60b81b6064820152608401610da5565b6040516001600160a01b0384166024820152828203604482018190529061174e90869063095ea7b360e01b906064016119a1565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301525f91839186169063dd62ed3e90604401602060405180830381865afa1580156125c7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906125eb91906129c0565b6125f59190612977565b6040516001600160a01b038516602482015260448101829052909150610ecf90859063095ea7b360e01b906064016119a1565b6060824710156126895760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610da5565b5f80866001600160a01b031685876040516126a49190612a20565b5f6040518083038185875af1925050503d805f81146126de576040519150601f19603f3d011682016040523d82523d5f602084013e6126e3565b606091505b50915091506126f4878383876126ff565b979650505050505050565b6060831561276d5782515f03612766576001600160a01b0385163b6127665760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610da5565b5081612446565b61244683838151156127825781518083602001fd5b8060405162461bcd60e51b8152600401610da591906129d7565b6001600160a01b03811681146127b0575f80fd5b50565b5f602082840312156127c3575f80fd5b81356117e88161279c565b5f602082840312156127de575f80fd5b5035919050565b5f80604083850312156127f6575f80fd5b82356128018161279c565b915060208301356128118161279c565b809150509250929050565b5f806040838503121561282d575f80fd5b82356128388161279c565b946020939093013593505050565b80151581146127b0575f80fd5b5f805f60608486031215612865575f80fd5b83356128708161279c565b925060208401356128808161279c565b9150604084013561289081612846565b809150509250925092565b602080825282518282018190525f918401906040840190835b818110156128db5783516001600160a01b03168352602093840193909201916001016128b4565b509095945050505050565b5f805f606084860312156128f8575f80fd5b83356129038161279c565b925060208401359150604084013561289081612846565b634e487b7160e01b5f52601160045260245ffd5b818103818111156106015761060161291a565b80820281158282048414176106015761060161291a565b5f8261297257634e487b7160e01b5f52601260045260245ffd5b500490565b808201808211156106015761060161291a565b5f6020828403121561299a575f80fd5b81516117e881612846565b5f602082840312156129b5575f80fd5b81516117e88161279c565b5f602082840312156129d0575f80fd5b5051919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b634e487b7160e01b5f52603260045260245ffd5b5f82518060208501845e5f92019182525091905056fea2646970667358221220b151f97e63fd28dba4d1bf01ed4e693c5b097535bc3d04e6dae214bb2f98c8ee64736f6c634300081a0033bb7154c024c448c6ea6d002f0df26c89f4a55783a3b480be7dfb28e216145f78a26469706673582212204cbd0896841166569afaeaa9853a8619bdfbd74e0044e3acd730c78b6bced62364736f6c634300081a0033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000734f854c33a149ff7efe120a921c9b39b397d5b10000000000000000000000005552e8b0bd553c299fbf9731c92dd6c3b5ab39500000000000000000000000000591c1f3e0d587f88261509c0f8295ce8474eb4d000000000000000000000000000000000000000000000000000000000000033e0000000000000000000000000000000000000000000000000000000000309235

-----Decoded View---------------
Arg [0] : _systemRegistry (address): 0x734F854c33A149ff7EfE120a921C9b39B397D5B1
Arg [1] : _proxyAdminOwner (address): 0x5552e8b0BD553C299Fbf9731c92dD6C3B5AB3950
Arg [2] : _implementation (address): 0x0591C1F3E0D587F88261509c0f8295Ce8474EB4d
Arg [3] : _defaultRewardRatio (uint256): 830
Arg [4] : _defaultRewardBlockDuration (uint256): 3183157

-----Encoded View---------------
5 Constructor Arguments found :
Arg [0] : 000000000000000000000000734f854c33a149ff7efe120a921c9b39b397d5b1
Arg [1] : 0000000000000000000000005552e8b0bd553c299fbf9731c92dd6c3b5ab3950
Arg [2] : 0000000000000000000000000591c1f3e0d587f88261509c0f8295ce8474eb4d
Arg [3] : 000000000000000000000000000000000000000000000000000000000000033e
Arg [4] : 0000000000000000000000000000000000000000000000000000000000309235


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