ETH Price: $2,378.60 (+0.44%)

Contract

0xF701fEE9d4094c6FaBd0645c5feC0Ca43DE40167
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Create Vault198833122024-05-16 15:04:11140 days ago1715871851IN
0xF701fEE9...43DE40167
0 ETH0.046487614.36515762
Add Strategy Tem...198833082024-05-16 15:03:23140 days ago1715871803IN
0xF701fEE9...43DE40167
0 ETH0.0013639313.58031933
0x61010060198833042024-05-16 15:02:35140 days ago1715871755IN
 Create: AutopoolFactory
0 ETH0.0506883411.62113815

Latest 4 internal transactions

Advanced mode:
Parent Transaction Hash Block From To
198833122024-05-16 15:04:11140 days ago1715871851
0xF701fEE9...43DE40167
 Contract Creation0 ETH
198833122024-05-16 15:04:11140 days ago1715871851
0xF701fEE9...43DE40167
0 ETH
198833122024-05-16 15:04:11140 days ago1715871851
0xF701fEE9...43DE40167
 Contract Creation0 ETH
198833122024-05-16 15:04:11140 days ago1715871851
0xF701fEE9...43DE40167
 Contract Creation0 ETH
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
AutopoolFactory

Compiler Version
v0.8.17+commit.8df45f5f

Optimization Enabled:
Yes with 200 runs

Other Settings:
london EvmVersion
File 1 of 80 : AutopoolFactory.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

import { EnumerableSet } from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import { ISystemRegistry, IWETH9 } from "src/interfaces/ISystemRegistry.sol";
import { IAutopoolFactory } from "src/interfaces/vault/IAutopoolFactory.sol";
import { IAutopoolRegistry } from "src/interfaces/vault/IAutopoolRegistry.sol";
import { AutopoolETH } from "src/vault/AutopoolETH.sol";
import { SecurityBase } from "src/security/SecurityBase.sol";
import { Clones } from "openzeppelin-contracts/proxy/Clones.sol";
import { AutopoolMainRewarder } from "src/rewarders/AutopoolMainRewarder.sol";
import { Roles } from "src/libs/Roles.sol";
import { Errors } from "src/utils/Errors.sol";
import { SystemComponent } from "src/SystemComponent.sol";
import { AutopoolETHStrategy } from "src/strategy/AutopoolETHStrategy.sol";
import { LibAdapter } from "src/libs/LibAdapter.sol";
import { Roles } from "src/libs/Roles.sol";

contract AutopoolFactory is SystemComponent, IAutopoolFactory, SecurityBase {
    using Clones for address;
    using EnumerableSet for EnumerableSet.AddressSet;

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

    /// @notice Strategy templates that can be used with this vault template
    /// @dev Exposed via `getStrategyTemplates() and isStrategyTemplate()`
    EnumerableSet.AddressSet internal _strategyTemplates;

    IAutopoolRegistry public immutable vaultRegistry;

    address public immutable template;

    /// =====================================================
    /// Public Vars
    /// =====================================================

    mapping(bytes32 => address) public vaultTypeToPrototype;

    uint256 public defaultRewardRatio;

    uint256 public defaultRewardBlockDuration;

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

    modifier onlyVaultCreator() {
        if (!_hasRole(Roles.AUTO_POOL_FACTORY_VAULT_CREATOR, msg.sender)) {
            revert Errors.AccessDenied();
        }
        _;
    }

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

    event DefaultRewardRatioSet(uint256 rewardRatio);
    event DefaultBlockDurationSet(uint256 blockDuration);
    event StrategyTemplateAdded(address template);
    event StrategyTemplateRemoved(address template);

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

    error InvalidStrategy();
    error InvalidEthAmount(uint256 amount);

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

    constructor(
        ISystemRegistry _systemRegistry,
        address _template,
        uint256 _defaultRewardRatio,
        uint256 _defaultRewardBlockDuration
    ) SystemComponent(_systemRegistry) SecurityBase(address(_systemRegistry.accessController())) {
        Errors.verifyNotZero(_template, "template");

        if (address(_systemRegistry) != SystemComponent(_template).getSystemRegistry()) {
            revert Errors.SystemMismatch(address(this), _template);
        }

        // slither-disable-next-line missing-zero-check
        template = _template;
        vaultRegistry = systemRegistry.autoPoolRegistry();

        // Zero is valid here
        _setDefaultRewardRatio(_defaultRewardRatio);
        _setDefaultRewardBlockDuration(_defaultRewardBlockDuration);
    }

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

    function addStrategyTemplate(address strategyTemplate) external hasRole(Roles.AUTO_POOL_FACTORY_MANAGER) {
        Errors.verifySystemsMatch(address(this), strategyTemplate);

        if (!_strategyTemplates.add(strategyTemplate)) {
            revert Errors.ItemExists();
        }

        emit StrategyTemplateAdded(strategyTemplate);
    }

    function removeStrategyTemplate(address strategyTemplate) external hasRole(Roles.AUTO_POOL_FACTORY_MANAGER) {
        if (!_strategyTemplates.remove(strategyTemplate)) {
            revert Errors.ItemNotFound();
        }

        emit StrategyTemplateRemoved(strategyTemplate);
    }

    function setDefaultRewardRatio(uint256 rewardRatio) external hasRole(Roles.AUTO_POOL_FACTORY_MANAGER) {
        _setDefaultRewardRatio(rewardRatio);
    }

    function setDefaultRewardBlockDuration(uint256 blockDuration) external hasRole(Roles.AUTO_POOL_FACTORY_MANAGER) {
        _setDefaultRewardBlockDuration(blockDuration);
    }

    function createVault(
        address strategyTemplate,
        string memory symbolSuffix,
        string memory descPrefix,
        bytes32 salt,
        bytes calldata extraParams
    ) external payable onlyVaultCreator returns (address newVaultAddress) {
        // verify params
        Errors.verifyNotZero(salt, "salt");

        if (!_strategyTemplates.contains(strategyTemplate)) {
            revert InvalidStrategy();
        }

        address newToken = template.predictDeterministicAddress(salt);
        address newStrategy = strategyTemplate.predictDeterministicAddress(salt);

        AutopoolMainRewarder mainRewarder = new AutopoolMainRewarder{ salt: salt }(
            systemRegistry,
            address(systemRegistry.toke()),
            defaultRewardRatio,
            defaultRewardBlockDuration,
            true, // allowExtraRewards
            newToken
        );

        newVaultAddress = template.cloneDeterministic(salt);

        // For Autopool deposit on initialization.
        uint256 wethInitAmount = AutopoolETH(newVaultAddress).WETH_INIT_DEPOSIT();
        IWETH9 weth = systemRegistry.weth();
        if (msg.value != wethInitAmount) revert InvalidEthAmount(msg.value);
        weth.deposit{ value: wethInitAmount }();
        LibAdapter._approve(weth, newVaultAddress, wethInitAmount);

        AutopoolETH(newVaultAddress).initialize(newStrategy, symbolSuffix, descPrefix, extraParams);
        AutopoolETH(newVaultAddress).setRewarder(address(mainRewarder));
        AutopoolETHStrategy(strategyTemplate.cloneDeterministic(salt)).initialize(newVaultAddress);

        // add to VaultRegistry
        vaultRegistry.addVault(newVaultAddress);
    }

    function getStrategyTemplates() public view returns (address[] memory) {
        return _strategyTemplates.values();
    }

    function isStrategyTemplate(address addr) public view returns (bool) {
        return _strategyTemplates.contains(addr);
    }

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

    function _setDefaultRewardRatio(uint256 rewardRatio) private {
        defaultRewardRatio = rewardRatio;

        emit DefaultRewardRatioSet(rewardRatio);
    }

    function _setDefaultRewardBlockDuration(uint256 blockDuration) private {
        defaultRewardBlockDuration = blockDuration;

        emit DefaultBlockDurationSet(blockDuration);
    }
}

File 2 of 80 : 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 3 of 80 : ISystemRegistry.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity 0.8.17;

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 Messagy Proxy
    /// @return Message proxy contract
    function messageProxy() external view returns (IMessageProxy);
}

File 4 of 80 : IAutopoolFactory.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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

File 5 of 80 : IAutopoolRegistry.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

/// @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 6 of 80 : AutopoolETH.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

// solhint-disable max-states-count

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 { NonReentrant } from "src/utils/NonReentrant.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 { 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";

contract AutopoolETH is ISystemComponent, Initializable, IAutopool, IStrategy, SecurityBase, Pausable, NonReentrant {
    using EnumerableSet for EnumerableSet.AddressSet;
    using Math for uint256;
    using WithdrawalQueue for StructuredLinkedList.List;
    using AutopoolToken for AutopoolToken.TokenData;

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

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

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

    /// @notice Amount of weth to be sent to vault on initialization.
    uint256 public constant WETH_INIT_DEPOSIT = 100_000;

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

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

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

    // solhint-disable-next-line var-name-mixedcase
    uint256 public immutable ONE;

    /// @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. Used as the decimals for the vault itself
    /// @dev Exposed via `decimals()`
    uint8 internal immutable _baseAssetDecimals;

    /// =====================================================
    /// Internal Vars
    /// =====================================================

    /// @notice Balances, allowances, and supply for the pool
    /// @dev Want to keep this var in this position
    AutopoolToken.TokenData internal _tokenData;

    /// @notice Pool/token name
    string internal _name;

    /// @notice Pool/token symbol
    string internal _symbol;

    /// @notice Full list of possible destinations that could be deployed to
    /// @dev Exposed via `getDestinations()`
    EnumerableSet.AddressSet internal _destinations;

    /// @notice Destinations that are queued for removal
    /// @dev Exposed via `getRemovalQueue`
    EnumerableSet.AddressSet internal _removalQueue;

    /// @notice Lookup of destinationVaultAddress -> Info .. Debt reporting snapshot info
    /// @dev Exposed via `getDestinationInfo`
    mapping(address => AutopoolDebt.DestinationInfo) internal _destinationInfo;

    /// @notice Whether or not the vault has been shutdown
    /// @dev Exposed via `isShutdown()`
    bool internal _shutdown;

    /// @notice Reason for shutdown (or `Active` if not shutdown)
    /// @dev Exposed via `shutdownStatus()`
    VaultShutdownStatus internal _shutdownStatus;

    /// @notice Ordered list of destinations to withdraw from
    /// @dev Exposed via `getWithdrawalQueue()`
    StructuredLinkedList.List internal _withdrawalQueue;

    /// @notice Ordered list of destinations to debt report on. Ordered from oldest to newest
    /// @dev Exposed via `getDebtReportingQueue()`
    StructuredLinkedList.List internal _debtReportQueue;

    /// @notice State and settings related to gradual profit unlock
    /// @dev Exposed via `getProfitUnlockSettings()`
    IAutopool.ProfitUnlockSettings internal _profitUnlockSettings;

    /// @notice State and settings related to periodic and streaming fees
    /// @dev Exposed via `getFeeSettings()`
    IAutopool.AutopoolFeeSettings internal _feeSettings;

    /// @notice Asset tracking for idle and debt values
    /// @dev Exposed via `getAssetBreakdown()`
    IAutopool.AssetBreakdown internal _assetBreakdown;

    /// @notice Rewarders that have been replaced.
    /// @dev Exposed via `getPastRewarders()`
    EnumerableSet.AddressSet internal _pastRewarders;

    /// =====================================================
    /// Public Vars
    /// =====================================================

    /// @notice Factory contract that created this vault
    address public factory;

    /// @notice Main rewarder for this contract
    IMainRewarder public rewarder;

    /// @notice The strategy logic for the Autopool
    IAutopoolStrategy public autoPoolStrategy;

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

    event SymbolAndDescSet(string symbol, string desc);

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

    error WithdrawShareCalcInvalid(uint256 currentShares, uint256 cachedShares);
    error RewarderAlreadySet();
    error RebalanceDestinationsMatch(address destinationVault);
    error InvalidDestination(address destination);
    error NavChanged(uint256 oldNav, uint256 newNav);
    error NavOpsInProgress();
    error VaultShutdown();
    error NavDecreased(uint256 oldNav, uint256 newNav);
    error InvalidUser();

    /// =====================================================
    /// 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() {
        _systemRegistry.systemSecurity().enterNavOperation();
        _;
        // slither-disable-next-line reentrancy-no-eth
        _systemRegistry.systemSecurity().exitNavOperation();
    }

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

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

        uint8 dec = IERC20Metadata(_vaultAsset).decimals();

        ONE = 10 ** dec;

        _baseAssetDecimals = dec;
        _baseAsset = IERC20Metadata(_vaultAsset);
        _symbol = string(abi.encodePacked("autoPool", IERC20Metadata(_vaultAsset).symbol(), "Template"));
        _name = string(abi.encodePacked(_symbol, " Token"));

        _disableInitializers();
    }

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

    function initialize(
        address strategy,
        string memory symbolSuffix,
        string memory descPrefix,
        bytes memory
    ) external virtual initializer {
        Errors.verifyNotEmpty(symbolSuffix, "symbolSuffix");
        Errors.verifyNotEmpty(descPrefix, "descPrefix");
        Errors.verifyNotZero(strategy, "autoPoolStrategyAddress");

        factory = msg.sender;

        _symbol = string(abi.encodePacked(symbolSuffix));
        _name = string(abi.encodePacked(descPrefix));

        AutopoolFees.initializeFeeSettings(_feeSettings);

        autoPoolStrategy = IAutopoolStrategy(strategy);

        // 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(WETH_INIT_DEPOSIT, DEAD_ADDRESS);

        // First mint, must be 1:1
        if (sharesMinted != WETH_INIT_DEPOSIT) revert ValueSharesMismatch(WETH_INIT_DEPOSIT, sharesMinted);

        // slither-disable-end reentrancy-no-eth

        AutopoolFees.setProfitUnlockPeriod(_profitUnlockSettings, _tokenData, 86_400);
    }

    /// @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)
    {
        Errors.verifyNotZero(assets, "assets");

        // Handles the vault being paused, returns 0
        if (assets > maxDeposit(receiver)) {
            revert ERC4626DepositExceedsMax(assets, maxDeposit(receiver));
        }

        uint256 ta = _totalAssetsTimeChecked(TotalAssetPurpose.Deposit);
        shares = convertToShares(assets, ta, totalSupply(), Math.Rounding.Down);

        Errors.verifyNotZero(shares, "shares");

        _transferAndMint(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)
    {
        // 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(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
    )
        public
        virtual
        override
        nonReentrant
        whenNotPaused
        noNavPerShareDecrease(TotalAssetPurpose.Withdraw)
        ensureNoNavOps
        returns (uint256 shares)
    {
        Errors.verifyNotZero(assets, "assets");

        //slither-disable-next-line unused-return
        (uint256 actualAssets, uint256 actualShares,) = AutopoolDebt.withdraw(
            assets,
            _totalAssetsTimeChecked(TotalAssetPurpose.Withdraw),
            _assetBreakdown,
            _withdrawalQueue,
            _destinationInfo
        );

        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
    )
        public
        virtual
        override
        nonReentrant
        whenNotPaused
        noNavPerShareDecrease(TotalAssetPurpose.Withdraw)
        ensureNoNavOps
        returns (uint256 assets)
    {
        uint256 maxShares = maxRedeem(owner);
        if (shares > maxShares) {
            revert ERC4626ExceededMaxRedeem(owner, shares, maxShares);
        }

        uint256 ta = _totalAssetsTimeChecked(TotalAssetPurpose.Withdraw);
        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, _assetBreakdown, _withdrawalQueue, _destinationInfo);

        assets = actualAssets;

        assert(actualShares <= shares);

        _completeWithdrawal(assets, shares, owner, receiver);
    }

    /// @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 hasRole(Roles.AUTO_POOL_FEE_UPDATER) {
        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 hasRole(Roles.AUTO_POOL_FEE_UPDATER) {
        AutopoolFees.setStreamingFeeBps(_feeSettings, fee);
    }

    /// @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 hasRole(Roles.AUTO_POOL_PERIODIC_FEE_UPDATER) {
        AutopoolFees.setPeriodicFeeBps(_feeSettings, fee);
    }

    /// @notice Set the address that will receive fees
    /// @param newFeeSink Address that will receive fees
    function setFeeSink(address newFeeSink) external hasRole(Roles.AUTO_POOL_FEE_UPDATER) {
        AutopoolFees.setFeeSink(_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 hasRole(Roles.AUTO_POOL_PERIODIC_FEE_UPDATER) {
        AutopoolFees.setPeriodicFeeSink(_feeSettings, newPeriodicFeeSink);
    }

    function setProfitUnlockPeriod(uint48 newUnlockPeriodSeconds) external hasRole(Roles.AUTO_POOL_MANAGER) {
        AutopoolFees.setProfitUnlockPeriod(_profitUnlockSettings, _tokenData, newUnlockPeriodSeconds);
    }

    /// @notice Set the rewarder contract used by the vault.
    /// @param _rewarder Address of new rewarder.
    function setRewarder(address _rewarder) external {
        // 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();
        }

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

    /// @inheritdoc IAutopool
    function getPastRewarders() external view returns (address[] memory) {
        return _pastRewarders.values();
    }

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

    /// @notice Allow the updating of symbol/desc for the vault (only AFTER shutdown)
    function setSymbolAndDescAfterShutdown(
        string memory newSymbol,
        string memory newName
    ) external hasRole(Roles.AUTO_POOL_MANAGER) {
        Errors.verifyNotEmpty(newSymbol, "newSymbol");
        Errors.verifyNotEmpty(newName, "newName");

        // make sure the vault is no longer active
        if (_shutdownStatus == VaultShutdownStatus.Active) {
            revert InvalidShutdownStatus(_shutdownStatus);
        }

        emit SymbolAndDescSet(newSymbol, newName);

        _symbol = newSymbol;
        _name = newName;
    }

    /// @inheritdoc IAutopool
    function isShutdown() external view returns (bool) {
        return _shutdown;
    }

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

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

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

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

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

    /// @notice Returns the decimals of the token, same as the underlying asset
    function decimals() public view virtual override returns (uint8) {
        return _baseAssetDecimals;
    }

    /// @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(_assetBreakdown, 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(TotalAssetPurpose purpose) public view returns (uint256) {
        return Autopool4626.totalAssets(_assetBreakdown, purpose);
    }

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

    /// @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) {
        // slither-disable-next-line incorrect-equality
        shares = (assets == 0 || supply == 0) ? assets : 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) external view virtual returns (uint256 assets) {
        assets = convertToAssets(shares, totalAssets(TotalAssetPurpose.Global), totalSupply(), Math.Rounding.Down);
    }

    /// @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) {
        // slither-disable-next-line incorrect-equality
        assets = (supply == 0) ? shares : shares.mulDiv(totalAssetsForPurpose, supply, rounding);
    }

    /// @notice Returns the amount of unlocked profit shares that will be burned
    function unlockedShares() external view returns (uint256 shares) {
        shares = AutopoolFees.unlockedShares(_profitUnlockSettings, _tokenData);
    }

    /// @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(_tokenData, _profitUnlockSettings);
    }

    /// @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 Autopool4626.balanceOf(_tokenData, _profitUnlockSettings, 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 _tokenData.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 _tokenData.allowances[owner][spender];
    }

    /// @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) {
        return _tokenData.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) {
        return _tokenData.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) {
        return _tokenData.transfer(to, value);
    }

    /// @notice Returns the next unused nonce for an address.
    function nonces(address owner) public view virtual returns (uint256) {
        return _tokenData.nonces[owner];
    }

    function getSystemRegistry() external view override returns (address) {
        return address(_systemRegistry);
    }

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

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        _tokenData.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) {
        uint256 ta = _totalAssetsTimeChecked(TotalAssetPurpose.Deposit);
        maxAssets = _maxDeposit(wallet, ta);
    }

    function _maxDeposit(address wallet, uint256 aptTotalAssets) private returns (uint256) {
        uint256 mintAmt = maxMint(wallet);
        return convertToAssets(mintAmt, aptTotalAssets, totalSupply(), Math.Rounding.Up);
    }

    /// @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) {
        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) {
        maxShares = Autopool4626.maxMint(
            _tokenData, _profitUnlockSettings, _debtReportQueue, _destinationInfo, wallet, paused(), _shutdown
        );
    }

    /// @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) {
        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.encodeWithSelector(this.previewWithdraw.selector, convertedAssets),
            _assetBreakdown,
            _withdrawalQueue,
            _destinationInfo
        );
    }

    /// @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) {
        uint256 ta = _totalAssetsTimeChecked(TotalAssetPurpose.Withdraw);
        // If total assets are zero then we are considered uncollateralized and all redeem's will fail
        if (ta > 0) {
            maxShares = paused() ? 0 : balanceOf(owner);
        }
    }

    function _totalAssetsTimeChecked(TotalAssetPurpose purpose) private returns (uint256) {
        return AutopoolDebt.totalAssetsTimeChecked(_debtReportQueue, _destinationInfo, purpose);
    }

    /// @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) {
        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) {
        // slither-disable-next-line unused-return
        (, shares) = AutopoolDebt.preview(
            true,
            assets,
            _totalAssetsTimeChecked(TotalAssetPurpose.Withdraw),
            abi.encodeWithSelector(this.previewWithdraw.selector, assets),
            _assetBreakdown,
            _withdrawalQueue,
            _destinationInfo
        );
    }

    /// @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) {
        // 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.encodeWithSelector(this.previewRedeem.selector, shares),
            _assetBreakdown,
            _withdrawalQueue,
            _destinationInfo
        );
    }

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

    /// @notice Transfer out non-tracked tokens
    function recover(
        address[] calldata tokens,
        uint256[] calldata amounts,
        address[] calldata destinations
    ) external virtual override hasRole(Roles.TOKEN_RECOVERY_MANAGER) {
        Autopool4626.recover(tokens, amounts, destinations);
    }

    /// @inheritdoc IAutopool
    function shutdown(VaultShutdownStatus reason) external hasRole(Roles.AUTO_POOL_MANAGER) {
        if (reason == VaultShutdownStatus.Active) {
            revert InvalidShutdownStatus(reason);
        }

        _shutdown = true;
        _shutdownStatus = reason;

        emit Shutdown(reason);
    }

    function _transferAndMint(uint256 assets, uint256 shares, address receiver) internal virtual {
        Autopool4626.transferAndMint(
            _baseAsset, _assetBreakdown, _tokenData, _profitUnlockSettings, assets, shares, receiver
        );
    }

    function updateDebtReporting(uint256 numToProcess)
        external
        nonReentrant
        hasRole(Roles.AUTO_POOL_REPORTING_EXECUTOR)
        trackNavOps
    {
        // Persist our change in idle and debt
        uint256 startingIdle = _assetBreakdown.totalIdle;
        uint256 startingDebt = _assetBreakdown.totalDebt;

        // slither-disable-next-line reentrancy-no-eth
        AutopoolDebt.IdleDebtUpdates memory result =
            AutopoolDebt._updateDebtReporting(_debtReportQueue, _destinationInfo, numToProcess);

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

        _assetBreakdown.totalIdle = newIdle;
        _assetBreakdown.totalDebt = newDebt;
        _assetBreakdown.totalDebtMin =
            _assetBreakdown.totalDebtMin + result.totalMinDebtIncrease - result.totalMinDebtDecrease;
        _assetBreakdown.totalDebtMax =
            _assetBreakdown.totalDebtMax + result.totalMaxDebtIncrease - result.totalMaxDebtDecrease;

        uint256 newTotalSupply = _feeAndProfitHandling(newIdle + newDebt, startingIdle + startingDebt, true);

        _updateStrategyNav(newIdle + newDebt, newTotalSupply);

        emit Nav(newIdle, newDebt, newTotalSupply);
    }

    function _feeAndProfitHandling(
        uint256 newTotalAssets,
        uint256 startingTotalAssets,
        bool collectPeriodicFees
    ) internal returns (uint256 newTotalSupply) {
        // Collect any fees and lock any profit if appropriate
        AutopoolFees.burnUnlockedShares(_profitUnlockSettings, _tokenData);

        uint256 startingTotalSupply = totalSupply();

        newTotalSupply = _collectFees(newTotalAssets, startingTotalSupply, collectPeriodicFees);

        newTotalSupply = AutopoolFees.calculateProfitLocking(
            _profitUnlockSettings,
            _tokenData,
            newTotalSupply - startingTotalSupply, // new feeShares
            newTotalAssets,
            startingTotalAssets,
            newTotalSupply,
            balanceOfActual(address(this))
        );
    }

    function _collectFees(
        uint256 currentTotalAssets,
        uint256 currentTotalSupply,
        bool collectPeriodicFees
    ) internal virtual returns (uint256) {
        return AutopoolFees.collectFees(
            currentTotalAssets, currentTotalSupply, _feeSettings, _tokenData, collectPeriodicFees
        );
    }

    function getDestinations() public view override(IAutopool, IStrategy) returns (address[] memory) {
        return _destinations.values();
    }

    function getWithdrawalQueue() public view returns (address[] memory) {
        return _withdrawalQueue.getList();
    }

    function getDebtReportingQueue() public view returns (address[] memory) {
        return _debtReportQueue.getList();
    }

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

    function addDestinations(address[] calldata destinations) public hasRole(Roles.AUTO_POOL_DESTINATION_UPDATER) {
        AutopoolDestinations.addDestinations(_removalQueue, _destinations, destinations, _systemRegistry);
    }

    function removeDestinations(address[] calldata destinations) public hasRole(Roles.AUTO_POOL_DESTINATION_UPDATER) {
        AutopoolDestinations.removeDestinations(_removalQueue, _destinations, destinations);
    }

    function getRemovalQueue() public view override returns (address[] memory) {
        return _removalQueue.values();
    }

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

    /// @inheritdoc IStrategy
    function flashRebalance(
        IERC3156FlashBorrower receiver,
        RebalanceParams memory rebalanceParams,
        bytes calldata data
    ) public whenNotPaused nonReentrant hasRole(Roles.SOLVER) trackNavOps {
        AutopoolDebt.IdleDebtUpdates memory result = _processRebalance(receiver, rebalanceParams, data);

        uint256 idle = _assetBreakdown.totalIdle;
        uint256 debt = _assetBreakdown.totalDebt;
        uint256 startTotalAssets = idle + debt;

        idle = idle + result.totalIdleIncrease - result.totalIdleDecrease;
        debt = debt + result.totalDebtIncrease - result.totalDebtDecrease;

        _assetBreakdown.totalIdle = idle;
        _assetBreakdown.totalDebt = debt;
        _assetBreakdown.totalDebtMin =
            _assetBreakdown.totalDebtMin + result.totalMinDebtIncrease - result.totalMinDebtDecrease;
        _assetBreakdown.totalDebtMax =
            _assetBreakdown.totalDebtMax + result.totalMaxDebtIncrease - result.totalMaxDebtDecrease;

        uint256 newTotalSupply = _feeAndProfitHandling(idle + debt, startTotalAssets, false);

        // Ensure the destinations are in the queues they should be
        AutopoolDestinations._manageQueuesForDestination(
            rebalanceParams.destinationOut, false, _withdrawalQueue, _debtReportQueue, _removalQueue
        );
        AutopoolDestinations._manageQueuesForDestination(
            rebalanceParams.destinationIn, true, _withdrawalQueue, _debtReportQueue, _removalQueue
        );

        // Signal to the strategy that everything went well
        // and it can gather its final state/stats
        autoPoolStrategy.rebalanceSuccessfullyExecuted(rebalanceParams);

        _updateStrategyNav(idle + debt, newTotalSupply);

        emit Nav(idle, debt, newTotalSupply);
    }

    function _updateStrategyNav(uint256 assets, uint256 supply) internal virtual {
        autoPoolStrategy.navUpdate(assets * ONE / supply);
    }

    function _processRebalance(
        IERC3156FlashBorrower receiver,
        RebalanceParams memory rebalanceParams,
        bytes calldata data
    ) internal virtual returns (AutopoolDebt.IdleDebtUpdates memory result) {
        // make sure there's something to do
        if (rebalanceParams.amountIn == 0 && rebalanceParams.amountOut == 0) {
            revert Errors.InvalidParams();
        }

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

        // Get out destination summary stats
        IStrategy.SummaryStats memory outSummary = autoPoolStrategy.getRebalanceOutSummaryStats(rebalanceParams);
        result = AutopoolDebt.flashRebalance(
            _destinationInfo[rebalanceParams.destinationOut],
            _destinationInfo[rebalanceParams.destinationIn],
            receiver,
            rebalanceParams,
            outSummary,
            autoPoolStrategy,
            AutopoolDebt.FlashRebalanceParams({
                totalIdle: _assetBreakdown.totalIdle,
                totalDebt: _assetBreakdown.totalDebt,
                baseAsset: _baseAsset,
                shutdown: _shutdown
            }),
            data
        );
    }

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

    function oldestDebtReporting() public view returns (uint256) {
        address destVault = _debtReportQueue.peekHead();
        return _destinationInfo[destVault].lastReport;
    }

    function _checkNoNavOps() internal view {
        if (_systemRegistry.systemSecurity().navOpsInProgress() > 0) {
            revert NavOpsInProgress();
        }
    }

    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) * FEE_DIVISOR) / startingTotalSupply;
    }

    /// @notice Vault nav/share shouldn't decrease on withdraw/redeem within rounding tolerance
    /// @dev No check when no shares
    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) * FEE_DIVISOR) / ts;
        if (newNav < oldNav) {
            revert NavDecreased(oldNav, newNav);
        }
    }
}

File 7 of 80 : SecurityBase.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 8 of 80 : Clones.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (proxy/Clones.sol)

pragma solidity ^0.8.0;

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 *
 * _Available since v3.4._
 */
library Clones {
    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create(0, 0x09, 0x37)
        }
        require(instance != address(0), "ERC1167: create failed");
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple time will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create2(0, 0x09, 0x37, salt)
        }
        require(instance != address(0), "ERC1167: create2 failed");
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(add(ptr, 0x38), deployer)
            mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
            mstore(add(ptr, 0x14), implementation)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
            mstore(add(ptr, 0x58), salt)
            mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
            predicted := keccak256(add(ptr, 0x43), 0x55)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(address implementation, bytes32 salt)
        internal
        view
        returns (address predicted)
    {
        return predictDeterministicAddress(implementation, salt, address(this));
    }
}

File 9 of 80 : AutopoolMainRewarder.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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
     */
    function getReward(address account, bool claimExtras) public {
        if (msg.sender != account && msg.sender != address(systemRegistry.autoPoolRouter())) {
            revert Errors.AccessDenied();
        }

        _getReward(account, claimExtras);
    }
}

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

pragma solidity 0.8.17;

library Roles {
    // --------------------------------------------------------------------
    // Central roles list used by all contracts that call AccessController
    // --------------------------------------------------------------------
    // TODO: Update the hash values to match the variable names for new deployments.

    // 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 = 0x00; // keccak256("LMP_VAULT_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");

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

    // Statistics and Reporting
    bytes32 public constant STATS_CALC_REGISTRY_MANAGER = 0x00; // 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");

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

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

    // Swapper Roles
    bytes32 public constant SWAP_ROUTER_MANAGER = 0x00; // 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");

    // Message Proxy Roles
    bytes32 public constant MESSAGE_PROXY_MANAGER = keccak256("MESSAGE_PROXY_MANAGER");
}

File 11 of 80 : Errors.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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

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

    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 {
        bytes memory call = abi.encodeWithSignature("getSystemRegistry()");

        address registry1 = abi.decode(component1.functionStaticCall(call), (address));
        address registry2 = abi.decode(component2.functionStaticCall(call), (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 12 of 80 : SystemComponent.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 13 of 80 : AutopoolETHStrategy.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

import { Roles } from "src/libs/Roles.sol";
import { ISystemRegistry } from "src/interfaces/ISystemRegistry.sol";
import { Initializable } from "openzeppelin-contracts/proxy/utils/Initializable.sol";
import { IDestinationVault } from "src/interfaces/vault/IDestinationVault.sol";
import { Errors } from "src/utils/Errors.sol";
import { IStrategy } from "src/interfaces/strategy/IStrategy.sol";
import { IAutopoolStrategy } from "src/interfaces/strategy/IAutopoolStrategy.sol";
import { IAutopool } from "src/interfaces/vault/IAutopool.sol";
import { SecurityBase } from "src/security/SecurityBase.sol";
import { IDexLSTStats } from "src/interfaces/stats/IDexLSTStats.sol";
import { ViolationTracking } from "src/strategy/ViolationTracking.sol";
import { NavTracking } from "src/strategy/NavTracking.sol";
import { IRootPriceOracle } from "src/interfaces/oracles/IRootPriceOracle.sol";
import { ILSTStats } from "src/interfaces/stats/ILSTStats.sol";
import { AutopoolETHStrategyConfig } from "src/strategy/AutopoolETHStrategyConfig.sol";
import { IERC20Metadata } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { Math } from "openzeppelin-contracts/utils/math/Math.sol";
import { AutopoolDebt } from "src/vault/libs/AutopoolDebt.sol";
import { ISystemComponent } from "src/interfaces/ISystemComponent.sol";
import { Initializable } from "openzeppelin-contracts/proxy/utils/Initializable.sol";
import { StrategyUtils } from "src/strategy/libs/StrategyUtils.sol";
import { SubSaturateMath } from "src/strategy/libs/SubSaturateMath.sol";
import { SummaryStats } from "src/strategy/libs/SummaryStats.sol";
import { SystemComponent } from "src/SystemComponent.sol";

contract AutopoolETHStrategy is SystemComponent, Initializable, IAutopoolStrategy, SecurityBase {
    using ViolationTracking for ViolationTracking.State;
    using NavTracking for NavTracking.State;
    using SubSaturateMath for uint256;
    using SubSaturateMath for int256;
    using Math for uint256;

    /* ******************************** */
    /* Immutable Config                 */
    /* ******************************** */

    /// @notice the number of days to pause rebalancing due to NAV decay
    uint16 public immutable pauseRebalancePeriodInDays;

    /// @notice the number of seconds gap between consecutive rebalances
    uint256 public immutable rebalanceTimeGapInSeconds;

    /// @notice destinations trading a premium above maxPremium will be blocked from new capital deployments
    int256 public immutable maxPremium; // 100% = 1e18

    /// @notice destinations trading a discount above maxDiscount will be blocked from new capital deployments
    int256 public immutable maxDiscount; // 100% = 1e18

    /// @notice the allowed staleness of stats data before a revert occurs
    uint40 public immutable staleDataToleranceInSeconds;

    /// @notice the swap cost offset period to initialize the strategy with
    uint16 public immutable swapCostOffsetInitInDays;

    /// @notice the number of violations required to trigger a tightening of the swap cost offset period (1 to 10)
    uint16 public immutable swapCostOffsetTightenThresholdInViolations;

    /// @notice the number of days to decrease the swap offset period for each tightening step
    uint16 public immutable swapCostOffsetTightenStepInDays;

    /// @notice the number of days since a rebalance required to trigger a relaxing of the swap cost offset period
    uint16 public immutable swapCostOffsetRelaxThresholdInDays;

    /// @notice the number of days to increase the swap offset period for each relaxing step
    uint16 public immutable swapCostOffsetRelaxStepInDays;

    // slither-disable-start similar-names
    /// @notice the maximum the swap cost offset period can reach. This is the loosest the strategy will be
    uint16 public immutable swapCostOffsetMaxInDays;

    /// @notice the minimum the swap cost offset period can reach. This is the most conservative the strategy will be
    uint16 public immutable swapCostOffsetMinInDays;

    /// @notice the number of days for the first NAV decay comparison (e.g., 30 days)
    uint8 public immutable navLookback1InDays;

    /// @notice the number of days for the second NAV decay comparison (e.g., 60 days)
    uint8 public immutable navLookback2InDays;

    /// @notice the number of days for the third NAV decay comparison (e.g., 90 days)
    uint8 public immutable navLookback3InDays;
    // slither-disable-end similar-names

    /// @notice the maximum slippage that is allowed for a normal rebalance
    uint256 public immutable maxNormalOperationSlippage; // 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
    uint256 public immutable maxTrimOperationSlippage; // 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
    uint256 public immutable maxEmergencyOperationSlippage; // 100% = 1e18

    /// @notice the maximum amount of slippage to allow when the AutopoolETH has been shutdown
    uint256 public immutable maxShutdownOperationSlippage; // 100% = 1e18

    /// @notice the maximum discount used for price return
    int256 public immutable maxAllowedDiscount; // 18 precision

    /// @notice model weight used for LSTs base yield, 1e6 is the highest
    uint256 public immutable weightBase;

    /// @notice model weight used for DEX fee yield, 1e6 is the highest
    uint256 public immutable weightFee;

    /// @notice model weight used for incentive yield
    uint256 public immutable weightIncentive;

    /// @notice model weight used slashing costs
    uint256 public immutable weightSlashing;

    /// @notice model weight applied to an LST discount when exiting the position
    int256 public immutable weightPriceDiscountExit;

    /// @notice model weight applied to an LST discount when entering the position
    int256 public immutable weightPriceDiscountEnter;

    /// @notice model weight applied to an LST premium when entering or exiting the position
    int256 public immutable weightPricePremium;

    /// @notice initial value of the swap cost offset to use
    uint16 public immutable swapCostOffsetInit;

    uint256 public immutable defaultLstPriceGapTolerance;

    /* ******************************** */
    /* State Variables                  */
    /* ******************************** */

    /// @notice model weight applied to an LST premium when entering or exiting the position
    uint256 public lstPriceGapTolerance;

    /// @notice Dust position portions
    /// @dev Value is determined as 1/f then truncated to a integer value, where f is a fractional value < 1.0.
    /// For ex. f = 0.02 means a position < 0.02 will be treated as a dust position
    uint256 public dustPositionPortions;

    /// @notice Idle Threshold Low
    /// @dev Fractional value < 1.0 represented in 18 decimals
    /// For ex. a value = 4e16 means 4% of total assets in the vault
    uint256 public idleLowThreshold;

    /// @notice Idle Threshold High
    /// @dev Fractional value < 1.0 represented in 18 decimals
    /// For ex. a value = 7e16 means 7% of total assets in the vault
    /// Low & high idle thresholds trigger different behaviors. When idle is less than low threshold, idle level must be
    /// brought up to high threshold. Any amount > high threshold is free to be deployed to destinations. When idle lies
    /// between low & high threshold, it does not trigger new idle to be added.
    uint256 public idleHighThreshold;

    /// @notice The AutopoolETH that this strategy is associated with
    IAutopool public autoPool;

    /// @notice The timestamp for when rebalancing was last paused
    uint40 public lastPausedTimestamp;

    /// @notice The last timestamp that a destination was added to
    mapping(address => uint40) public lastAddTimestampByDestination;

    /// @notice The last timestamp a rebalance was completed
    uint40 public lastRebalanceTimestamp;

    /// @notice Rebalance violation tracking state
    ViolationTracking.State public violationTrackingState;

    /// @notice NAV tracking state
    NavTracking.State public navTrackingState;

    uint16 private _swapCostOffsetPeriod;

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

    enum RebalanceToIdleReasonEnum {
        DestinationIsShutdown,
        AutopoolETHIsShutdown,
        TrimDustPosition,
        DestinationIsQueuedForRemoval,
        DestinationViolatedConstraint,
        ReplenishIdlePosition,
        UnknownReason
    }

    event RebalanceToIdleReason(RebalanceToIdleReasonEnum reason, uint256 maxSlippage, uint256 slippage);

    event DestTrimRebalanceDetails(
        uint256 assetIndex, uint256 numViolationsOne, uint256 numViolationsTwo, int256 discount
    );
    event DestViolationMaxTrimAmount(uint256 trimAmount);
    event RebalanceToIdle(
        RebalanceValueStats valueStats, IStrategy.SummaryStats outSummary, IStrategy.RebalanceParams params
    );

    event RebalanceBetweenDestinations(
        RebalanceValueStats valueStats,
        IStrategy.RebalanceParams params,
        IStrategy.SummaryStats outSummaryStats,
        IStrategy.SummaryStats inSummaryStats,
        uint256 swapOffsetPeriod,
        int256 predictedAnnualizedGain
    );

    event SuccessfulRebalanceBetweenDestinations(
        address destinationOut,
        uint40 lastRebalanceTimestamp,
        uint40 lastTimestampAddedToDestination,
        uint40 swapCostOffsetPeriod
    );

    event PauseStart(uint256 navPerShare, uint256 nav1, uint256 nav2, uint256 nav3);
    event PauseStop();

    event LstPriceGapSet(uint256 newPriceGap);
    event DustPositionPortionSet(uint256 newValue);
    event IdleThresholdsSet(uint256 newLowValue, uint256 newHighValue);

    /* ******************************** */
    /* Errors                           */
    /* ******************************** */
    error NotAutopoolETH();
    error StrategyPaused();
    error RebalanceTimeGapNotMet();
    error RebalanceDestinationsMatch();
    error RebalanceDestinationUnderlyerMismatch(address destination, address trueUnderlyer, address providedUnderlyer);

    error StaleData(string name);
    error SwapCostExceeded();
    error MaxSlippageExceeded();
    error MaxDiscountExceeded();
    error MaxPremiumExceeded();
    error OnlyRebalanceToIdleAvailable();
    error InvalidRebalanceToIdle();
    error CannotConvertUintToInt();
    error InsufficientAssets(address asset);
    error SystemRegistryMismatch();
    error UnregisteredDestination(address dest);
    error LSTPriceGapToleranceExceeded();
    error InconsistentIdleThresholds();
    error IdleHighThresholdViolated();

    struct RebalanceValueStats {
        uint256 inPrice;
        uint256 outPrice;
        uint256 inEthValue;
        uint256 outEthValue;
        uint256 swapCost;
        uint256 slippage;
    }

    modifier onlyAutopool() {
        if (msg.sender != address(autoPool)) revert NotAutopoolETH();
        _;
    }

    constructor(
        ISystemRegistry _systemRegistry,
        AutopoolETHStrategyConfig.StrategyConfig memory conf
    ) SystemComponent(_systemRegistry) SecurityBase(address(_systemRegistry.accessController())) {
        AutopoolETHStrategyConfig.validate(conf);

        rebalanceTimeGapInSeconds = conf.rebalanceTimeGapInSeconds;
        pauseRebalancePeriodInDays = conf.pauseRebalancePeriodInDays;
        maxPremium = conf.maxPremium;
        maxDiscount = conf.maxDiscount;
        staleDataToleranceInSeconds = conf.staleDataToleranceInSeconds;
        swapCostOffsetInitInDays = conf.swapCostOffset.initInDays;
        swapCostOffsetTightenThresholdInViolations = conf.swapCostOffset.tightenThresholdInViolations;
        swapCostOffsetTightenStepInDays = conf.swapCostOffset.tightenStepInDays;
        swapCostOffsetRelaxThresholdInDays = conf.swapCostOffset.relaxThresholdInDays;
        swapCostOffsetRelaxStepInDays = conf.swapCostOffset.relaxStepInDays;
        swapCostOffsetMaxInDays = conf.swapCostOffset.maxInDays;
        swapCostOffsetMinInDays = conf.swapCostOffset.minInDays;
        navLookback1InDays = conf.navLookback.lookback1InDays;
        navLookback2InDays = conf.navLookback.lookback2InDays;
        navLookback3InDays = conf.navLookback.lookback3InDays;
        maxNormalOperationSlippage = conf.slippage.maxNormalOperationSlippage;
        maxTrimOperationSlippage = conf.slippage.maxTrimOperationSlippage;
        maxEmergencyOperationSlippage = conf.slippage.maxEmergencyOperationSlippage;
        maxShutdownOperationSlippage = conf.slippage.maxShutdownOperationSlippage;
        maxAllowedDiscount = conf.maxAllowedDiscount;
        weightBase = conf.modelWeights.baseYield;
        weightFee = conf.modelWeights.feeYield;
        weightIncentive = conf.modelWeights.incentiveYield;
        weightSlashing = conf.modelWeights.slashing;
        weightPriceDiscountExit = conf.modelWeights.priceDiscountExit;
        weightPriceDiscountEnter = conf.modelWeights.priceDiscountEnter;
        weightPricePremium = conf.modelWeights.pricePremium;
        defaultLstPriceGapTolerance = conf.lstPriceGapTolerance;
        swapCostOffsetInit = conf.swapCostOffset.initInDays;

        _disableInitializers();
    }

    function initialize(address _autoPool) external virtual initializer {
        _initialize(_autoPool);
    }

    function _initialize(address _autoPool) internal virtual {
        Errors.verifyNotZero(_autoPool, "_autoPool");

        if (ISystemComponent(_autoPool).getSystemRegistry() != address(systemRegistry)) {
            revert SystemRegistryMismatch();
        }

        autoPool = IAutopool(_autoPool);

        lastRebalanceTimestamp = uint40(block.timestamp) - uint40(rebalanceTimeGapInSeconds);
        _swapCostOffsetPeriod = swapCostOffsetInit;
        lstPriceGapTolerance = defaultLstPriceGapTolerance;
        dustPositionPortions = 50;
        idleLowThreshold = 0;
        idleHighThreshold = 0;
    }

    /// @notice Sets the LST price gap tolerance to the provided value
    function setLstPriceGapTolerance(uint256 priceGapTolerance) external hasRole(Roles.AUTO_POOL_MANAGER) {
        lstPriceGapTolerance = priceGapTolerance;

        emit LstPriceGapSet(priceGapTolerance);
    }

    /// @notice Sets the dust position portions to the provided value
    function setDustPositionPortions(uint256 newValue) external hasRole(Roles.AUTO_POOL_MANAGER) {
        dustPositionPortions = newValue;

        emit DustPositionPortionSet(newValue);
    }

    /// @notice Sets the Idle low/high threshold
    function setIdleThresholds(uint256 newLowValue, uint256 newHighValue) external hasRole(Roles.AUTO_POOL_MANAGER) {
        idleLowThreshold = newLowValue;
        idleHighThreshold = newHighValue;

        // Check for consistency in values i.e. low threshold should be strictly < high threshold
        if (((idleLowThreshold > 0 && idleHighThreshold > 0)) && (idleLowThreshold >= idleHighThreshold)) {
            revert InconsistentIdleThresholds();
        }
        // Setting both thresholds to 0 allows no minimum requirement for idle
        if ((idleLowThreshold == 0 && idleHighThreshold != 0) || (idleLowThreshold != 0 && idleHighThreshold == 0)) {
            revert InconsistentIdleThresholds();
        }

        emit IdleThresholdsSet(newLowValue, newHighValue);
    }

    /// @inheritdoc IAutopoolStrategy
    function verifyRebalance(
        IStrategy.RebalanceParams memory params,
        IStrategy.SummaryStats memory outSummary
    ) external returns (bool success, string memory message) {
        RebalanceValueStats memory valueStats = getRebalanceValueStats(params);

        // moves from a destination back to eth only happen under specific scenarios
        // if the move is valid, the constraints are different than if assets are moving to a normal destination
        if (params.destinationIn == address(autoPool)) {
            verifyRebalanceToIdle(params, valueStats.slippage);
            // slither-disable-next-line reentrancy-events
            emit RebalanceToIdle(valueStats, outSummary, params);
            // exit early b/c the remaining constraints only apply when moving to a normal destination
            return (true, "");
        }

        // rebalances back to idle are allowed even when a strategy is paused
        // all other rebalances should be blocked in a paused state
        ensureNotPaused();

        // Ensure enough time has passed between rebalances
        if ((uint40(block.timestamp) - lastRebalanceTimestamp) < rebalanceTimeGapInSeconds) {
            revert RebalanceTimeGapNotMet();
        }

        // ensure that we're not exceeding top-level max slippage
        if (valueStats.slippage > maxNormalOperationSlippage) {
            revert MaxSlippageExceeded();
        }

        // ensure that idle is not depleted below the high threshold if we are pulling from Idle assets
        // totalAssets will be reduced by swap cost amount.
        if (params.destinationOut == address(autoPool)) {
            uint256 totalAssets = autoPool.totalAssets().subSaturate(valueStats.swapCost);
            if (autoPool.getAssetBreakdown().totalIdle < valueStats.outEthValue) {
                revert IdleHighThresholdViolated();
            } else if (
                (autoPool.getAssetBreakdown().totalIdle - valueStats.outEthValue)
                    < ((totalAssets * idleHighThreshold) / 1e18)
            ) {
                revert IdleHighThresholdViolated();
            }
        }

        IStrategy.SummaryStats memory inSummary = getRebalanceInSummaryStats(params);

        // ensure that the destination that is being added to doesn't exceed top-level premium/discount constraints
        if (inSummary.maxDiscount > maxDiscount) revert MaxDiscountExceeded();
        if (-inSummary.maxPremium > maxPremium) revert MaxPremiumExceeded();

        uint256 swapOffsetPeriod = swapCostOffsetPeriodInDays();

        // if the swap is only moving lp tokens from one destination to another
        // make the swap offset period more conservative b/c the gas costs/complexity is lower
        // Discard the fractional part resulting from div by 2 to be conservative
        if (params.tokenIn == params.tokenOut) {
            swapOffsetPeriod = swapOffsetPeriod / 2;
        }
        // slither-disable-start divide-before-multiply
        // equation is `compositeReturn * ethValue` / 1e18, which is multiply before divide
        // compositeReturn and ethValue are both 1e18 precision
        int256 predictedAnnualizedGain = (
            inSummary.compositeReturn * StrategyUtils.convertUintToInt(valueStats.inEthValue)
        ).subSaturate(outSummary.compositeReturn * StrategyUtils.convertUintToInt(valueStats.outEthValue)) / 1e18;

        // slither-disable-end divide-before-multiply
        int256 predictedGainAtOffsetEnd =
            (predictedAnnualizedGain * StrategyUtils.convertUintToInt(swapOffsetPeriod) / 365);

        // if the predicted gain in Eth by the end of the swap offset period is less than
        // the swap cost then revert b/c the vault will not offset slippage in sufficient time
        // slither-disable-next-line timestamp
        if (predictedGainAtOffsetEnd <= StrategyUtils.convertUintToInt(valueStats.swapCost)) revert SwapCostExceeded();
        // slither-disable-next-line reentrancy-events
        emit RebalanceBetweenDestinations(
            valueStats, params, outSummary, inSummary, swapOffsetPeriod, predictedAnnualizedGain
        );

        return (true, "");
    }

    /// @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) {
        address token =
            destAddress == address(autoPool) ? autoPool.asset() : IDestinationVault(destAddress).underlying();
        uint256 outPrice = _getInOutTokenPriceInEth(token, destAddress);
        return SummaryStats.getDestinationSummaryStats(
            autoPool, systemRegistry.incentivePricing(), destAddress, outPrice, direction, amount
        );
    }

    function validateRebalanceParams(IStrategy.RebalanceParams memory params) internal view {
        Errors.verifyNotZero(params.destinationIn, "destinationIn");
        Errors.verifyNotZero(params.destinationOut, "destinationOut");
        Errors.verifyNotZero(params.tokenIn, "tokenIn");
        Errors.verifyNotZero(params.tokenOut, "tokenOut");
        Errors.verifyNotZero(params.amountIn, "amountIn");
        Errors.verifyNotZero(params.amountOut, "amountOut");

        ensureDestinationRegistered(params.destinationIn);
        ensureDestinationRegistered(params.destinationOut);

        // when a vault is shutdown, rebalancing can only pull assets from destinations back to the vault
        if (autoPool.isShutdown() && params.destinationIn != address(autoPool)) revert OnlyRebalanceToIdleAvailable();

        if (params.destinationIn == params.destinationOut) revert RebalanceDestinationsMatch();

        address baseAsset = autoPool.asset();

        // 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 (params.destinationIn == address(autoPool)) {
            if (params.tokenIn != baseAsset) {
                revert RebalanceDestinationUnderlyerMismatch(params.destinationIn, params.tokenIn, baseAsset);
            }
        } else {
            IDestinationVault inDest = IDestinationVault(params.destinationIn);
            if (params.tokenIn != inDest.underlying()) {
                revert RebalanceDestinationUnderlyerMismatch(params.destinationIn, inDest.underlying(), params.tokenIn);
            }
        }

        if (params.destinationOut == address(autoPool)) {
            if (params.tokenOut != baseAsset) {
                revert RebalanceDestinationUnderlyerMismatch(params.destinationOut, params.tokenOut, baseAsset);
            }
            if (params.amountOut > autoPool.getAssetBreakdown().totalIdle) {
                revert InsufficientAssets(params.tokenOut);
            }
        } else {
            IDestinationVault outDest = IDestinationVault(params.destinationOut);
            if (params.tokenOut != outDest.underlying()) {
                revert RebalanceDestinationUnderlyerMismatch(
                    params.destinationOut, outDest.underlying(), params.tokenOut
                );
            }
            if (params.amountOut > outDest.balanceOf(address(autoPool))) {
                revert InsufficientAssets(params.tokenOut);
            }
        }
    }

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

    function getRebalanceValueStats(IStrategy.RebalanceParams memory params)
        internal
        returns (RebalanceValueStats memory)
    {
        uint8 tokenOutDecimals = IERC20Metadata(params.tokenOut).decimals();
        uint8 tokenInDecimals = IERC20Metadata(params.tokenIn).decimals();
        address autoPoolAddress = address(autoPool);

        // Prices are all in terms of the base asset, so when its a rebalance back to the vault
        // or out of the vault, We can just take things as 1:1

        // Get the price of one unit of the underlying lp token, the params.tokenOut/tokenIn
        // Prices are calculated using the spot of price of the constituent tokens
        // validated to be within a tolerance of the safe price of those tokens
        uint256 outPrice = params.destinationOut != autoPoolAddress
            ? IDestinationVault(params.destinationOut).getValidatedSpotPrice()
            : 10 ** tokenOutDecimals;

        uint256 inPrice = params.destinationIn != autoPoolAddress
            ? IDestinationVault(params.destinationIn).getValidatedSpotPrice()
            : 10 ** tokenInDecimals;

        // prices are 1e18 and we want values in 1e18, so divide by token decimals
        uint256 outEthValue = params.destinationOut != autoPoolAddress
            ? outPrice * params.amountOut / 10 ** tokenOutDecimals
            : params.amountOut;

        // amountIn is a minimum to receive, but it is OK if we receive more
        uint256 inEthValue = params.destinationIn != autoPoolAddress
            ? inPrice * params.amountIn / 10 ** tokenInDecimals
            : params.amountIn;

        uint256 swapCost = outEthValue.subSaturate(inEthValue);
        uint256 slippage = outEthValue == 0 ? 0 : swapCost * 1e18 / outEthValue;

        return RebalanceValueStats({
            inPrice: inPrice,
            outPrice: outPrice,
            inEthValue: inEthValue,
            outEthValue: outEthValue,
            swapCost: swapCost,
            slippage: slippage
        });
    }

    function verifyRebalanceToIdle(IStrategy.RebalanceParams memory params, uint256 slippage) internal {
        IDestinationVault outDest = IDestinationVault(params.destinationOut);

        // multiple scenarios can be active at a given time. We want to use the highest
        // slippage among the active scenarios.
        uint256 maxSlippage = 0;
        RebalanceToIdleReasonEnum reason = RebalanceToIdleReasonEnum.UnknownReason;
        // Scenario 1: the destination has been shutdown -- done when a fast exit is required
        if (outDest.isShutdown()) {
            reason = RebalanceToIdleReasonEnum.DestinationIsShutdown;
            maxSlippage = maxEmergencyOperationSlippage;
        }

        // Scenario 2: the AutopoolETH has been shutdown
        if (autoPool.isShutdown() && maxShutdownOperationSlippage > maxSlippage) {
            reason = RebalanceToIdleReasonEnum.AutopoolETHIsShutdown;
            maxSlippage = maxShutdownOperationSlippage;
        }

        // Scenario 3: Replenishing Idle requires trimming destination
        if (verifyIdleUpOperation(params) && maxNormalOperationSlippage > maxSlippage) {
            reason = RebalanceToIdleReasonEnum.ReplenishIdlePosition;
            maxSlippage = maxNormalOperationSlippage;
        }

        // Scenario 4: position is a dust position and should be trimmed
        if (verifyCleanUpOperation(params) && maxNormalOperationSlippage > maxSlippage) {
            reason = RebalanceToIdleReasonEnum.TrimDustPosition;
            maxSlippage = maxNormalOperationSlippage;
        }

        // Scenario 5: the destination has been moved out of the Autopools active destinations
        if (autoPool.isDestinationQueuedForRemoval(params.destinationOut) && maxNormalOperationSlippage > maxSlippage) {
            reason = RebalanceToIdleReasonEnum.DestinationIsQueuedForRemoval;
            maxSlippage = maxNormalOperationSlippage;
        }

        // Scenario 6: the destination needs to be trimmed because it violated a constraint
        if (maxTrimOperationSlippage > maxSlippage) {
            reason = RebalanceToIdleReasonEnum.DestinationViolatedConstraint;
            uint256 trimAmount = getDestinationTrimAmount(outDest); // this is expensive, can it be refactored?
            if (trimAmount < 1e18 && verifyTrimOperation(params, trimAmount)) {
                maxSlippage = maxTrimOperationSlippage;
            }
            // slither-disable-next-line reentrancy-events
            emit DestViolationMaxTrimAmount(trimAmount);
        }
        // slither-disable-next-line reentrancy-events
        emit RebalanceToIdleReason(reason, maxSlippage, slippage);

        // if none of the scenarios are active then this rebalance is invalid
        if (maxSlippage == 0) revert InvalidRebalanceToIdle();

        if (slippage > maxSlippage) revert MaxSlippageExceeded();
    }

    function verifyCleanUpOperation(IStrategy.RebalanceParams memory params) internal view returns (bool) {
        IDestinationVault outDest = IDestinationVault(params.destinationOut);

        AutopoolDebt.DestinationInfo memory destInfo = autoPool.getDestinationInfo(params.destinationOut);
        // revert if information is too old
        ensureNotStaleData("DestInfo", destInfo.lastReport);
        // shares of the destination currently held by the AutopoolETH
        uint256 currentShares = outDest.balanceOf(address(autoPool));
        // withdrawals reduce totalAssets, but do not update the destinationInfo
        // adjust the current debt based on the currently owned shares
        uint256 currentDebt =
            destInfo.ownedShares == 0 ? 0 : destInfo.cachedDebtValue * currentShares / destInfo.ownedShares;

        // If the current position is the minimum portion, trim to idle is allowed
        // slither-disable-next-line divide-before-multiply
        if ((currentDebt * 1e18) < ((autoPool.totalAssets() * 1e18) / dustPositionPortions)) {
            return true;
        }

        return false;
    }

    function verifyIdleUpOperation(IStrategy.RebalanceParams memory params) internal view returns (bool) {
        AutopoolDebt.DestinationInfo memory destInfo = autoPool.getDestinationInfo(params.destinationOut);
        // revert if information is too old
        ensureNotStaleData("DestInfo", destInfo.lastReport);
        uint256 currentIdle = autoPool.getAssetBreakdown().totalIdle;
        uint256 newIdle = currentIdle + params.amountIn;
        uint256 totalAssets = autoPool.totalAssets();
        // If idle is below low threshold, then allow replinishing Idle. New idle after rebalance should be above high
        // threshold. While totalAssets after this rebalance will be lower by swap loss, the ratio idle / total assets
        // as used is conservative.
        // Idle thresholds (both low & high) use 18 decimals
        // slither-disable-next-line divide-before-multiply
        if ((currentIdle * 1e18) / totalAssets < idleLowThreshold) {
            // Allow small margin to exceed high threshold to avoid precision issues & pricing differences
            if ((newIdle * 1e18) / totalAssets < idleHighThreshold + 1e16) {
                return true;
            }
        }

        return false;
    }

    function verifyTrimOperation(IStrategy.RebalanceParams memory params, uint256 trimAmount) internal returns (bool) {
        // if the position can be trimmed to zero, then no checks are necessary
        if (trimAmount == 0) {
            return true;
        }

        IDestinationVault outDest = IDestinationVault(params.destinationOut);

        AutopoolDebt.DestinationInfo memory destInfo = autoPool.getDestinationInfo(params.destinationOut);
        // revert if information is too old
        ensureNotStaleData("DestInfo", destInfo.lastReport);

        // shares of the destination currently held by the AutopoolETH
        uint256 currentShares = outDest.balanceOf(address(autoPool));

        // withdrawals reduce totalAssets, but do not update the destinationInfo
        // adjust the current debt based on the currently owned shares
        uint256 currentDebt =
            destInfo.ownedShares == 0 ? 0 : destInfo.cachedDebtValue * currentShares / destInfo.ownedShares;

        // prior validation ensures that currentShares >= amountOut
        uint256 sharesAfterRebalance = currentShares - params.amountOut;

        // current value of the destination shares, not cached from debt reporting
        uint256 destValueAfterRebalance = outDest.debtValue(sharesAfterRebalance);

        // calculate the total value of the autoPool after the rebalance is made
        // note that only the out destination value is adjusted to current
        // amountIn is a minimum to receive, but it is OK if we receive more
        uint256 autoPoolAssetsAfterRebalance =
            (autoPool.totalAssets() + params.amountIn + destValueAfterRebalance - currentDebt);

        // trimming may occur over multiple rebalances, so we only want to ensure we aren't removing too much
        if (autoPoolAssetsAfterRebalance > 0) {
            return destValueAfterRebalance * 1e18 / autoPoolAssetsAfterRebalance >= trimAmount;
        } else {
            // Autopool assets after rebalance are 0
            return true;
        }
    }

    function getDestinationTrimAmount(IDestinationVault dest) internal returns (uint256) {
        uint256 discountThresholdOne = 3e5; // 3% 1e7 precision, discount required to consider trimming
        uint256 discountDaysThreshold = 7; // number of last 10 days that it was >= discountThreshold
        uint256 discountThresholdTwo = 5e5; // 5% 1e7 precision, discount required to completely exit

        // this is always the out destination and guaranteed not to be the AutopoolETH idle asset
        IDexLSTStats.DexLSTStatsData memory stats = dest.getStats().current();

        ILSTStats.LSTStatsData[] memory lstStats = stats.lstStatsData;
        uint256 numLsts = lstStats.length;

        uint256 minTrim = 1e18; // 100% -- no trim required
        for (uint256 i = 0; i < numLsts; ++i) {
            ILSTStats.LSTStatsData memory targetLst = lstStats[i];
            (uint256 numViolationsOne, uint256 numViolationsTwo) =
                getDiscountAboveThreshold(targetLst.discountHistory, discountThresholdOne, discountThresholdTwo);

            // slither-disable-next-line reentrancy-events
            emit DestTrimRebalanceDetails(i, numViolationsOne, numViolationsTwo, targetLst.discount);

            if (targetLst.discount >= int256(discountThresholdTwo * 1e11) && numViolationsTwo >= discountDaysThreshold)
            {
                // this is the worst possible trim, so we can exit without checking other LSTs

                return 0;
            }

            // discountThreshold is 1e7 precision for the discount history, but here it is compared to a 1e18, so pad it
            if (targetLst.discount >= int256(discountThresholdOne * 1e11) && numViolationsOne >= discountDaysThreshold)
            {
                minTrim = minTrim.min(1e17); // 10%
            }
        }

        return minTrim;
    }

    function getDiscountAboveThreshold(
        uint24[10] memory discountHistory,
        uint256 threshold1,
        uint256 threshold2
    ) internal pure returns (uint256 count1, uint256 count2) {
        count1 = 0;
        count2 = 0;
        uint256 len = discountHistory.length;
        for (uint256 i = 0; i < len; ++i) {
            if (discountHistory[i] >= threshold1) {
                count1 += 1;
            }
            if (discountHistory[i] >= threshold2) {
                count2 += 1;
            }
        }
    }

    /// @inheritdoc IAutopoolStrategy
    function getRebalanceOutSummaryStats(IStrategy.RebalanceParams memory rebalanceParams)
        external
        returns (IStrategy.SummaryStats memory outSummary)
    {
        validateRebalanceParams(rebalanceParams);
        // Verify spot & safe price for the individual tokens in the pool are not far apart.
        // Call to verify before remove/add liquidity to the dest in the rebalance txn
        // if the in dest is not the Autopool i.e. this is not a rebalance to idle txn, verify price tolerance
        if (rebalanceParams.destinationIn != address(autoPool)) {
            if (!SummaryStats.verifyLSTPriceGap(autoPool, rebalanceParams, lstPriceGapTolerance)) {
                revert LSTPriceGapToleranceExceeded();
            }
        }
        outSummary = _getRebalanceOutSummaryStats(rebalanceParams);
    }

    function _getRebalanceOutSummaryStats(IStrategy.RebalanceParams memory rebalanceParams)
        internal
        virtual
        returns (IStrategy.SummaryStats memory outSummary)
    {
        // Use safe price
        uint256 outPrice = _getInOutTokenPriceInEth(rebalanceParams.tokenOut, rebalanceParams.destinationOut);
        outSummary = (
            SummaryStats.getDestinationSummaryStats(
                autoPool,
                systemRegistry.incentivePricing(),
                rebalanceParams.destinationOut,
                outPrice,
                RebalanceDirection.Out,
                rebalanceParams.amountOut
            )
        );
    }

    /// @dev Price the tokens from rebalance params with the appropriate method
    function _getInOutTokenPriceInEth(address token, address destination) private returns (uint256) {
        IRootPriceOracle pricer = systemRegistry.rootPriceOracle();
        if (destination == address(autoPool)) {
            // When the destination is the autoPool then the token is the underlying asset
            // which means its not an LP token so we use this pricing fn
            return pricer.getPriceInEth(token);
        } else {
            // Otherwise we know its a real destination and so we can get the price directly from there
            return IDestinationVault(destination).getValidatedSafePrice();
        }
    }

    // Summary stats for destination In
    function getRebalanceInSummaryStats(IStrategy.RebalanceParams memory rebalanceParams)
        internal
        virtual
        returns (IStrategy.SummaryStats memory inSummary)
    {
        // Use safe price
        uint256 inPrice = _getInOutTokenPriceInEth(rebalanceParams.tokenIn, rebalanceParams.destinationIn);
        inSummary = (
            SummaryStats.getDestinationSummaryStats(
                autoPool,
                systemRegistry.incentivePricing(),
                rebalanceParams.destinationIn,
                inPrice,
                RebalanceDirection.In,
                rebalanceParams.amountIn
            )
        );
    }

    /// @inheritdoc IAutopoolStrategy
    function navUpdate(uint256 navPerShare) external onlyAutopool {
        uint40 blockTime = uint40(block.timestamp);
        navTrackingState.insert(navPerShare, blockTime);

        clearExpiredPause();

        // check if the strategy needs to be paused due to NAV decay
        // the check only happens after there are enough data points
        // skip the check if the strategy is already paused
        // slither-disable-next-line timestamp
        if (navTrackingState.len > navLookback3InDays && !paused()) {
            uint256 nav1 = navTrackingState.getDaysAgo(navLookback1InDays);
            uint256 nav2 = navTrackingState.getDaysAgo(navLookback2InDays);
            uint256 nav3 = navTrackingState.getDaysAgo(navLookback3InDays);

            if (navPerShare < nav1 && navPerShare < nav2 && navPerShare < nav3) {
                lastPausedTimestamp = blockTime;
                emit PauseStart(navPerShare, nav1, nav2, nav3);
            }
        }
    }

    /// @inheritdoc IAutopoolStrategy
    function rebalanceSuccessfullyExecuted(IStrategy.RebalanceParams memory params) external onlyAutopool {
        // clearExpirePause sets _swapCostOffsetPeriod, so skip when possible to avoid double write
        if (!clearExpiredPause()) _swapCostOffsetPeriod = swapCostOffsetPeriodInDays();

        address autoPoolAddress = address(autoPool);

        // update the destination that had assets added
        // moves into idle are not tracked for violations
        if (params.destinationIn != autoPoolAddress) {
            // Update to lastRebalanceTimestamp excludes rebalances to idle as those skip swapCostOffset logic
            lastRebalanceTimestamp = uint40(block.timestamp);
            lastAddTimestampByDestination[params.destinationIn] = lastRebalanceTimestamp;
        }

        // violations are only tracked when moving between non-idle assets
        if (params.destinationOut != autoPoolAddress && params.destinationIn != autoPoolAddress) {
            uint40 lastAddForRemoveDestination = lastAddTimestampByDestination[params.destinationOut];
            uint40 swapCostOffsetPeriod = uint40(swapCostOffsetPeriodInDays());
            if (
                // slither-disable-start timestamp
                lastRebalanceTimestamp - lastAddForRemoveDestination < swapCostOffsetPeriod * 1 days
            ) {
                // slither-disable-end timestamp

                violationTrackingState.insert(true);
            } else {
                violationTrackingState.insert(false);
            }
            emit SuccessfulRebalanceBetweenDestinations(
                params.destinationOut, lastRebalanceTimestamp, lastAddForRemoveDestination, swapCostOffsetPeriod
            );
        }

        // tighten if X of the last 10 rebalances were violations
        if (
            violationTrackingState.len == 10
                && violationTrackingState.violationCount >= swapCostOffsetTightenThresholdInViolations
        ) {
            tightenSwapCostOffset();
            violationTrackingState.reset();
        }
    }

    function swapCostOffsetPeriodInDays() public view returns (uint16) {
        // if the system is in an expired pause state then ensure that swap cost offset
        // is set to the most conservative value (shortest number of days)
        if (expiredPauseState()) {
            return swapCostOffsetMinInDays;
        }

        // truncation is desirable because we only want the number of times it has exceeded the threshold
        // slither-disable-next-line divide-before-multiply
        uint40 numRelaxPeriods = swapCostOffsetRelaxThresholdInDays == 0
            ? 0
            : (uint40(block.timestamp) - lastRebalanceTimestamp) / 1 days / uint40(swapCostOffsetRelaxThresholdInDays);
        uint40 relaxDays = numRelaxPeriods * uint40(swapCostOffsetRelaxStepInDays);
        uint40 newSwapCostOffset = uint40(_swapCostOffsetPeriod) + relaxDays;

        // slither-disable-next-line timestamp
        if (newSwapCostOffset > swapCostOffsetMaxInDays) {
            return swapCostOffsetMaxInDays;
        }

        return uint16(newSwapCostOffset);
    }

    function tightenSwapCostOffset() internal {
        uint16 currentSwapOffset = swapCostOffsetPeriodInDays();
        uint16 newSwapCostOffset = 0;

        if (currentSwapOffset > swapCostOffsetTightenStepInDays) {
            newSwapCostOffset = currentSwapOffset - swapCostOffsetTightenStepInDays;
        }

        // slither-disable-next-line timestamp
        if (newSwapCostOffset < swapCostOffsetMinInDays) {
            _swapCostOffsetPeriod = swapCostOffsetMinInDays;
        } else {
            _swapCostOffsetPeriod = newSwapCostOffset;
        }
    }

    function paused() public view returns (bool) {
        // slither-disable-next-line incorrect-equality,timestamp
        if (lastPausedTimestamp == 0) return false;
        uint40 pauseRebalanceInSeconds = uint40(pauseRebalancePeriodInDays) * 1 days;

        // slither-disable-next-line timestamp
        return uint40(block.timestamp) - lastPausedTimestamp <= pauseRebalanceInSeconds;
    }

    function ensureNotPaused() internal view {
        if (paused()) revert StrategyPaused();
    }

    function expiredPauseState() internal view returns (bool) {
        // slither-disable-next-line timestamp
        return lastPausedTimestamp > 0 && !paused();
    }

    function clearExpiredPause() internal returns (bool) {
        if (!expiredPauseState()) return false;

        lastPausedTimestamp = 0;
        emit PauseStop();
        _swapCostOffsetPeriod = swapCostOffsetMinInDays;
        return true;
    }

    function ensureNotStaleData(string memory name, uint256 dataTimestamp) internal view {
        // slither-disable-next-line timestamp
        if (block.timestamp - dataTimestamp > staleDataToleranceInSeconds) revert StaleData(name);
    }
}

File 14 of 80 : LibAdapter.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 15 of 80 : IWETH9.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

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 16 of 80 : IAccToke.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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

    ///////////////////////////////////////////////////////////////////
    //                        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, uint256 amount);

    ///////////////////////////////////////////////////////////////////
    //
    //                        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 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 Preview the number of points that would be returned for the
     * given amount and duration.
     *
     * @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 Check if amount can be staked
    function isStakeableAmount(uint256 amount) external pure returns (bool);
}

File 17 of 80 : IAccessController.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 18 of 80 : ISwapRouter.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 19 of 80 : ICurveResolver.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity 0.8.17;

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 20 of 80 : IAutopilotRouter.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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

/**
 * @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 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 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 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 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 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 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 returns (uint256);
}

File 21 of 80 : ISystemSecurity.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 view returns (bool);
}

File 22 of 80 : IDestinationRegistry.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 23 of 80 : IRootPriceOracle.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity 0.8.17;

/// @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 24 of 80 : IDestinationVaultRegistry.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 25 of 80 : IStatsCalculatorRegistry.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 Register a new stats calculator
    /// @param calculator address of the calculator
    function register(address calculator) external;

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

File 26 of 80 : IAsyncSwapperRegistry.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 27 of 80 : 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 28 of 80 : IIncentivesPricingStats.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

/// @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 29 of 80 : IMessageProxy.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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

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

pragma solidity 0.8.17;

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 { IStrategy } from "src/interfaces/strategy/IStrategy.sol";
import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20Metadata as IERC20 } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IERC3156FlashBorrower } from "openzeppelin-contracts/interfaces/IERC3156FlashBorrower.sol";
import { IAutopoolStrategy } from "src/interfaces/strategy/IAutopoolStrategy.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";

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 RebalanceDestinationsMatch(address destinationVault);
    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);

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

    struct FlashRebalanceParams {
        uint256 totalIdle;
        uint256 totalDebt;
        IERC20 baseAsset;
        bool shutdown;
    }

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

    function flashRebalance(
        DestinationInfo storage destInfoOut,
        DestinationInfo storage destInfoIn,
        IERC3156FlashBorrower receiver,
        IStrategy.RebalanceParams memory params,
        IStrategy.SummaryStats memory destSummaryOut,
        IAutopoolStrategy autoPoolStrategy,
        FlashRebalanceParams memory flashParams,
        bytes calldata data
    ) external returns (IdleDebtUpdates memory result) {
        // 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(receiver),
                destinationOut: params.destinationOut,
                amountOut: params.amountOut,
                tokenOut: params.tokenOut,
                _baseAsset: flashParams.baseAsset,
                _shutdown: flashParams.shutdown
            }),
            destInfoOut
        );

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

        // Handle increase (shares coming "In", getting underlying from the swapper and trading for new shares)
        if (params.amountIn > 0) {
            FlashResultInfo memory flashResultInfo;
            // get "before" counts
            flashResultInfo.tokenInBalanceBefore = IERC20(params.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 = receiver.onFlashLoan(msg.sender, params.tokenIn, params.amountIn, 0, data);

            // We assume the solver will send us the assets
            flashResultInfo.tokenInBalanceAfter = IERC20(params.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 + params.amountIn
            ) {
                revert Errors.FlashLoanFailed(params.tokenIn, params.amountIn);
            }

            {
                // make sure we have a valid path
                (bool success, string memory message) = autoPoolStrategy.verifyRebalance(params, destSummaryOut);
                if (!success) {
                    revert RebalanceFailed(message);
                }
            }

            if (params.tokenIn != address(flashParams.baseAsset)) {
                IdleDebtUpdates memory inDebtResult = _handleRebalanceIn(
                    destInfoIn,
                    IDestinationVault(params.destinationIn),
                    params.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;
            }
        }
    }

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

    /**
     * @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.amountOut > 0) {
            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 {
                // If we are shutdown then the only operations we should be performing are those that get
                // the base asset back to the vault. We shouldn't be sending out more

                if (params._shutdown) {
                    revert VaultShutdown();
                }
                // 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) {
        // TODO: Trace the use of this fn and ensure that every is handling is pricesWereSafe

        // 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
        // TODO: Evaluate whether to round these up so we don't accumulate small amounts
        // 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(
        StructuredLinkedList.List storage debtReportQueue,
        mapping(address => AutopoolDebt.DestinationInfo) storage destinationInfo,
        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(
        StructuredLinkedList.List storage debtReportQueue,
        mapping(address => AutopoolDebt.DestinationInfo) storage destinationInfo,
        uint256 numToProcess
    ) external returns (IdleDebtUpdates memory result) {
        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), 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);
        }
    }

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

        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(
        uint256 assets,
        uint256 applicableTotalAssets,
        IAutopool.AssetBreakdown storage assetBreakdown,
        StructuredLinkedList.List storage withdrawalQueue,
        mapping(address => AutopoolDebt.DestinationInfo) storage destinationInfo
    ) 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());
            if (address(destVault) == address(0)) {
                // TODO: This may be some NULL value too, check the underlying library
                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;
                    // 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;
                }
            }

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

            info.assetsPulled += pulledAssets;

            // 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
            // The assets we calculated to pull are from the minDebt number we track so
            // we'll use that one to ensure we properly account for slippage (the `pulled` var below)
            // The other two debt numbers we just need to keep up to date.
            uint256 debtMinDecrease = destinationInfo[address(destVault)].cachedMinDebtValue.mulDiv(
                dvSharesToBurn, destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
            );
            info.debtMinDecrease += debtMinDecrease;

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

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

            info.assetsToPull -= pulledAssets;
        }

        // info.assetsToPull isn't safe to use past this point.
        // It may or may not be accurate from the previous loop

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

    /// @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(
        uint256 assets,
        uint256 applicableTotalAssets,
        IAutopool.AssetBreakdown storage assetBreakdown,
        StructuredLinkedList.List storage withdrawalQueue,
        mapping(address => AutopoolDebt.DestinationInfo) storage destinationInfo
    ) 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;
                }
            }

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

            info.assetsPulled += pulledAssets;

            // 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
            // The assets we calculated to pull are from the minDebt number we track so
            // we'll use that one to ensure we properly account for slippage (the `pulled` var below)
            // The other two debt numbers we just need to keep up to date.
            uint256 debtMinDecrease = destinationInfo[address(destVault)].cachedMinDebtValue.mulDiv(
                dvSharesToBurn, destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
            );
            info.debtMinDecrease += debtMinDecrease;

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

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

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

            // Any deficiency in the amount we received is slippage. debtDecrease is what we expected
            // to receive. If we received any extra, that's great we'll roll it forward so we burn
            // less on the next loop.
            uint256 pulled = Math.max(debtMinDecrease, pulledAssets);
            if (pulled >= info.assetsToPull) {
                // We either have enough assets, or we've burned the max debt we're allowed
                break;
            } else {
                info.assetsToPull -= pulled;
            }

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

        // info.assetsToPull isn't safe to use past this point.
        // It may or may not be accurate from the previous loop

        // We didn't get enough assets from the debt pull
        // See if we can get the rest from idle
        // Check the debt burned though to ensure that we don't try to make up
        // slippage incurred out of idle
        if (
            info.assetsPulled < assets && info.debtMinDecrease < assets && info.currentIdle > 0 && exhaustedDestinations
        ) {
            uint256 remaining = assets - Math.max(info.assetsPulled, info.debtMinDecrease);
            if (remaining < info.currentIdle) {
                info.assetsFromIdle = remaining;
            } else {
                info.assetsFromIdle = info.currentIdle;
            }
        }

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

        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 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.
     * @param assetBreakdown Asset breakdown for the Autopool.
     * @param tokenData Token data for the Autopool.
     */
    function completeWithdrawal(
        uint256 assets,
        uint256 shares,
        address owner,
        address receiver,
        IERC20 baseAsset,
        IAutopool.AssetBreakdown storage assetBreakdown,
        AutopoolToken.TokenData storage tokenData
    ) 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 {
                    tokenData.approve(owner, msg.sender, allowed - shares);
                }
            }
        }

        tokenData.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 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.
     * @param assetBreakdown Breakdown of vault assets from Autopool storage.
     * @param withdrawalQueue Destination vault withdrawal queue from Autopool storage.
     * @param destinationInfo Mapping of information for destinations.
     * @return assetsAmount Preview of amount of assets to send to receiver.
     * @return sharesAmount Preview of amount of assets to burn from owner.
     */
    function preview(
        bool previewWithdraw,
        uint256 assets,
        uint256 applicableTotalAssets,
        bytes memory functionCallEncoded,
        IAutopool.AssetBreakdown storage assetBreakdown,
        StructuredLinkedList.List storage withdrawalQueue,
        mapping(address => AutopoolDebt.DestinationInfo) storage destinationInfo
    ) 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, assetBreakdown, withdrawalQueue, destinationInfo);
            } else {
                (previewAssets, previewShares,) =
                    redeem(assets, applicableTotalAssets, assetBreakdown, withdrawalQueue, destinationInfo);
            }

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

File 31 of 80 : Pausable.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 view 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 view virtual {
        if (paused()) {
            revert IsPaused();
        }
    }

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

File 32 of 80 : VaultTypes.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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

File 33 of 80 : NonReentrant.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity 0.8.17;

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

    uint256 private _status;

    error ReentrancyGuardReentrantCall();

    constructor() {
        _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 34 of 80 : IAutopool.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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                      */
    /* ******************************** */
    event TokensPulled(address[] tokens, uint256[] amounts, address[] destinations);
    event TokensRecovered(address[] tokens, uint256[] amounts, address[] destinations);
    event Nav(uint256 idle, uint256 debt, uint256 totalSupply);
    event RewarderSet(address newRewarder, address oldRewarder);
    event DestinationDebtReporting(address destination, uint256 debtValue, uint256 claimed, uint256 claimGasUsed);
    event FeeCollected(uint256 fees, address feeSink, uint256 mintedShares, uint256 profit, uint256 idle, uint256 debt);
    event PeriodicFeeCollected(uint256 fees, address feeSink, uint256 mintedShares);
    event Shutdown(VaultShutdownStatus reason);

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

    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);
    error InvalidShutdownStatus(VaultShutdownStatus status);

    error WithdrawalFailed();
    error DepositFailed();
    error InsufficientFundsInDestinations(uint256 deficit);
    error WithdrawalIncomplete();
    error ValueSharesMismatch(uint256 value, uint256 shares);

    /// @notice A full unit of this pool
    // solhint-disable-next-line func-name-mixedcase
    function ONE() 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 all past rewarders.
    function getPastRewarders() external view returns (address[] memory _pastRewarders);

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

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

pragma solidity 0.8.17;

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

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 * 1 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();

    /// @notice Returns the amount of unlocked profit shares that will be burned
    function unlockedShares(
        IAutopool.ProfitUnlockSettings storage profitUnlockSettings,
        AutopoolToken.TokenData storage tokenData
    ) 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 = tokenData.balances[address(this)];
        }
    }

    function initializeFeeSettings(IAutopool.AutopoolFeeSettings storage settings) external {
        uint256 timestamp = block.timestamp;
        settings.lastPeriodicFeeTake = timestamp; // Stops fees from being able to be claimed before init timestamp.
        settings.navPerShareLastFeeMark = FEE_DIVISOR;
        settings.navPerShareLastFeeMarkTimestamp = timestamp;
        emit LastPeriodicFeeTakeSet(timestamp);
    }

    function burnUnlockedShares(
        IAutopool.ProfitUnlockSettings storage profitUnlockSettings,
        AutopoolToken.TokenData storage tokenData
    ) external {
        uint256 shares = unlockedShares(profitUnlockSettings, tokenData);
        if (shares == 0) {
            return;
        }
        if (profitUnlockSettings.fullProfitUnlockTime > block.timestamp) {
            profitUnlockSettings.lastProfitUnlockTime = uint48(block.timestamp);
        }
        tokenData.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(
        uint256 totalAssets,
        uint256 currentTotalSupply,
        IAutopool.AutopoolFeeSettings storage settings,
        AutopoolToken.TokenData storage tokenData,
        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;
        }

        // slither-disable-next-line incorrect-equality
        if (settings.totalAssetsHighMark == 0) {
            // Initialize our high water mark to the current assets
            settings.totalAssetsHighMark = totalAssets;
        }

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

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

                currentTotalSupply += periodicShares;
                tokenData.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.
            settings.lastPeriodicFeeTake = block.timestamp;
            emit LastPeriodicFeeTakeSet(block.timestamp);
        }

        // slither-disable-end timestamp
        uint256 currentNavPerShare = (totalAssets * 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(settings, 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) * currentTotalSupply;
            uint256 fees = profit.mulDiv(settings.streamingFeeBps, (FEE_DIVISOR ** 2), Math.Rounding.Up);

            if (fees > 0) {
                currentTotalSupply = _mintStreamingFee(
                    tokenData, fees, settings.streamingFeeBps, profit, currentTotalSupply, totalAssets, settings.feeSink
                );
                currentNavPerShare = (totalAssets * 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 || !settings.rebalanceFeeHighWaterMarkEnabled) {
            settings.navPerShareLastFeeMark = currentNavPerShare;
            settings.navPerShareLastFeeMarkTimestamp = block.timestamp;
            emit NewNavShareFeeMark(currentNavPerShare, block.timestamp);
        }

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

        return currentTotalSupply;
    }

    function _mintStreamingFee(
        AutopoolToken.TokenData storage tokenData,
        uint256 fees,
        uint256 streamingFeeBps,
        uint256 profit,
        uint256 currentTotalSupply,
        uint256 totalAssets,
        address sink
    ) private returns (uint256) {
        if (sink == address(0)) {
            return currentTotalSupply;
        }

        uint256 streamingFeeShares =
            _calculateSharesToMintFeeCollection(streamingFeeBps, totalAssets, currentTotalSupply);
        tokenData.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, 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 totalAssets,
        uint256 totalSupply
    ) private pure returns (uint256 toMint) {
        // Gas savings, this is used twice.
        uint256 feeTotalAssets = feeBps * totalAssets / FEE_DIVISOR;

        // Calculated separate from other mints as normal share mint is round down
        // Note: We use Lido's formula: from https://docs.lido.fi/guides/lido-tokens-integration-guide/#fees
        // suggested by: https://github.com/sherlock-audit/2023-06-tokemak-judging/blob/main/486-H/624-best.md
        // but we scale down `profit` by FEE_DIVISOR
        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(
        IAutopool.ProfitUnlockSettings storage settings,
        AutopoolToken.TokenData storage tokenData,
        uint48 newUnlockPeriodInSeconds
    ) external {
        settings.unlockPeriodInSeconds = newUnlockPeriodInSeconds;

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

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

        emit NewProfitUnlockTime(newUnlockPeriodInSeconds);
    }

    function calculateProfitLocking(
        IAutopool.ProfitUnlockSettings storage settings,
        AutopoolToken.TokenData storage tokenData,
        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;
            tokenData.mint(address(this), mintAmount);
            startTotalSupply += mintAmount;
        } else if (totalLockShares < previousLockShares) {
            uint256 burnAmount = previousLockShares - totalLockShares;
            tokenData.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;

        // 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 fee Percent. 100% == 10000
    function setStreamingFeeBps(IAutopool.AutopoolFeeSettings storage feeSettings, uint256 fee) external {
        if (fee >= FEE_DIVISOR) {
            revert InvalidFee(fee);
        }

        feeSettings.streamingFeeBps = fee;

        IAutopool vault = IAutopool(address(this));

        // 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 * 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 fee Fee to update periodic fee to.
    function setPeriodicFeeBps(IAutopool.AutopoolFeeSettings storage feeSettings, uint256 fee) external {
        if (fee > MAX_PERIODIC_FEE_BPS) {
            revert InvalidFee(fee);
        }

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

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

pragma solidity 0.8.17;

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 37 of 80 : Autopool4626.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity 0.8.17;

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

library Autopool4626 {
    using SafeERC20 for IERC20Metadata;
    using WithdrawalQueue for StructuredLinkedList.List;
    using AutopoolToken for AutopoolToken.TokenData;

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

    error InvalidTotalAssetPurpose();

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

    /// @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(
        AutopoolToken.TokenData storage tokenData,
        IAutopool.ProfitUnlockSettings storage profitUnlockSettings,
        address account
    ) public view returns (uint256) {
        if (account == address(this)) {
            return tokenData.balances[account] - AutopoolFees.unlockedShares(profitUnlockSettings, tokenData);
        }
        return tokenData.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();
        }
    }

    function maxMint(
        AutopoolToken.TokenData storage tokenData,
        IAutopool.ProfitUnlockSettings storage profitUnlockSettings,
        StructuredLinkedList.List storage debtReportQueue,
        mapping(address => AutopoolDebt.DestinationInfo) storage destinationInfo,
        address,
        bool paused,
        bool shutdown
    ) 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(tokenData, profitUnlockSettings);
        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(debtReportQueue, destinationInfo, IAutopool.TotalAssetPurpose.Deposit);
        if (ta == 0) {
            return 0;
        }

        if (ts > type(uint112).max) {
            return 0;
        }

        return type(uint112).max - ts;
    }

    /// @notice Returns the amount of tokens in existence.
    /// @dev Subtracts any unlocked profit shares that will be burned
    function totalSupply(
        AutopoolToken.TokenData storage tokenData,
        IAutopool.ProfitUnlockSettings storage profitUnlockSettings
    ) public view returns (uint256 shares) {
        shares = tokenData.totalSupply - AutopoolFees.unlockedShares(profitUnlockSettings, tokenData);
    }

    function transferAndMint(
        IERC20Metadata baseAsset,
        IAutopool.AssetBreakdown storage assetBreakdown,
        AutopoolToken.TokenData storage tokenData,
        IAutopool.ProfitUnlockSettings storage profitUnlockSettings,
        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

        baseAsset.safeTransferFrom(msg.sender, address(this), assets);

        assetBreakdown.totalIdle += assets;

        tokenData.mint(receiver, shares);

        emit Deposit(msg.sender, receiver, assets, shares);

        emit Nav(assetBreakdown.totalIdle, assetBreakdown.totalDebt, totalSupply(tokenData, profitUnlockSettings));
    }

    /// @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
                payable(destination).call{ value: amount };
            }
        }
    }
}

File 38 of 80 : IStrategy.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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
    /// @param slashingCost The loss due to slashing of the backing
    struct SummaryStats {
        address destination;
        uint256 baseApr;
        uint256 feeApr;
        uint256 incentiveApr;
        uint256 safeTotalSupply;
        int256 priceReturn;
        int256 maxDiscount;
        int256 maxPremium;
        uint256 ownedShares;
        int256 compositeReturn;
        uint256 pricePerShare;
        uint256 slashingCost;
    }

    /// @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 39 of 80 : 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 40 of 80 : WithdrawalQueue.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17; // 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 41 of 80 : AutopoolDestinations.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity 0.8.17;

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

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

    /// @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 removalQueue Destinations that queued for removal in the vault
    /// @param destinations Full list of destinations from the vault
    /// @param _destinations Destinations to remove
    function removeDestinations(
        EnumerableSet.AddressSet storage removalQueue,
        EnumerableSet.AddressSet storage destinations,
        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 removalQueue Destinations that queued for removal in the vault
    /// @param destinations Full list of destinations from the vault
    /// @param _destinations New destinations to add
    /// @param systemRegistry System registry reference for the vault
    function addDestinations(
        EnumerableSet.AddressSet storage removalQueue,
        EnumerableSet.AddressSet storage destinations,
        address[] calldata _destinations,
        ISystemRegistry systemRegistry
    ) external {
        IDestinationVaultRegistry destinationRegistry = systemRegistry.destinationVaultRegistry();

        uint256 numDestinations = _destinations.length;
        if (numDestinations == 0) {
            revert Errors.InvalidParams();
        }

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

            // 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(
        address destination,
        bool destinationIn,
        StructuredLinkedList.List storage withdrawalQueue,
        StructuredLinkedList.List storage debtReportQueue,
        EnumerableSet.AddressSet storage removalQueue
    ) 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
                debtReportQueue.popAddress(destination);
                withdrawalQueue.popAddress(destination);

                if (removalQueue.remove(destination)) {
                    emit RemovedFromRemovalQueue(destination);
                }
            }
        }
    }
}

File 42 of 80 : 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 43 of 80 : ISystemComponent.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

/// @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 44 of 80 : IAutopoolStrategy.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 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 used slashing costs
    function weightSlashing() 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 45 of 80 : IMainRewarder.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

import { IBaseRewarder } from "src/interfaces/rewarders/IBaseRewarder.sol";
import { IExtraRewarder } from "src/interfaces/rewarders/IExtraRewarder.sol";

interface IMainRewarder is IBaseRewarder {
    error ExtraRewardsNotAllowed();

    event ExtraRewardAdded(address reward);
    event ExtraRewardsCleared();
    event ExtraRewardRemoved(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 Removes a list of ExtraRewarder contract addresses from the extraRewards array.
     */
    function removeExtraRewards(address[] calldata _rewards) 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 Clears the extraRewards array.
     */
    function clearExtraRewards() 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 claimExtras If true, claims rewards from linked extra reward contracts.
     */
    function getReward(address account, 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 46 of 80 : StructuredLinkedList.sol
// SPDX-License-Identifier: MIT

pragma solidity =0.8.17;

/**
 * @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 47 of 80 : 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 48 of 80 : 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 49 of 80 : 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 50 of 80 : MainRewarder.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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

    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();
        }
        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 removeExtraRewards(address[] calldata _rewards) external hasRole(rewardRole) {
        uint256 length = _rewards.length;
        for (uint256 i = 0; i < length; ++i) {
            if (!_extraRewards.remove(_rewards[i])) {
                revert Errors.ItemNotFound();
            }
            emit ExtraRewardRemoved(_rewards[i]);
        }
    }

    /// @inheritdoc IMainRewarder
    function clearExtraRewards() external hasRole(rewardRole) {
        while (_extraRewards.length() > 0) {
            if (!_extraRewards.remove(_extraRewards.at(_extraRewards.length() - 1))) {
                revert Errors.ItemNotFound();
            }
        }

        emit ExtraRewardsCleared();
    }

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

    function _getReward(address account, bool claimExtras) internal nonReentrant {
        _updateReward(account);
        _processRewards(account, 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, bool claimExtras) internal {
        _getReward(account);
        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);
            }
        }
    }
}

File 51 of 80 : 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 52 of 80 : IDestinationVault.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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

interface IDestinationVault is 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 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 underlyering 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);

    /* ******************************** */
    /* 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
    function withdrawBaseAsset(uint256 shares, address to) external returns (uint256 amount);

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

File 53 of 80 : IDexLSTStats.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 54 of 80 : ViolationTracking.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

library ViolationTracking {
    uint16 internal constant TAIL_MASK = 1 << 9;
    uint16 internal constant HEAD_MASK = 1;

    struct State {
        uint8 violationCount;
        uint8 len;
        uint16 violations;
    }

    function insert(State storage self, bool isViolation) internal {
        bool tailValue = (self.violations & TAIL_MASK) > 0;

        // push new spot into the queue (default false)
        self.violations <<= 1;

        // flip the bit to true and increment counter if it is a violation
        if (isViolation) {
            self.violations |= HEAD_MASK;
            self.violationCount += 1;
        }

        // if we're dropping a violation then decrement the counter
        if (tailValue) {
            self.violationCount -= 1;
        }

        if (self.len < 10) {
            self.len += 1;
        }
    }

    function reset(State storage self) internal {
        self.violationCount = 0;
        self.violations = 0;
        self.len = 0;
    }
}

File 55 of 80 : NavTracking.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

library NavTracking {
    uint8 internal constant MAX_NAV_TRACKING = 91;

    error NavHistoryInsufficient();
    error InvalidNavTimestamp(uint40 current, uint40 provided);

    event NavHistoryInsert(uint256 navPerShare, uint40 timestamp);

    struct State {
        uint8 len;
        uint8 currentIndex;
        uint40 lastFinalizedTimestamp;
        uint256[MAX_NAV_TRACKING] history;
    }

    function insert(State storage self, uint256 navPerShare, uint40 timestamp) internal {
        if (timestamp < self.lastFinalizedTimestamp) revert InvalidNavTimestamp(self.lastFinalizedTimestamp, timestamp);

        // if it's been a day since the last finalized value, then finalize the current value
        // otherwise continue to overwrite the currentIndex
        if (timestamp - self.lastFinalizedTimestamp >= 1 days) {
            if (self.lastFinalizedTimestamp > 0) {
                self.currentIndex = (self.currentIndex + 1) % MAX_NAV_TRACKING;
            }
            self.lastFinalizedTimestamp = timestamp;
        }

        self.history[self.currentIndex] = navPerShare;
        if (self.len < MAX_NAV_TRACKING) {
            self.len += 1;
        }

        emit NavHistoryInsert(navPerShare, timestamp);
    }

    // the way this information is used, it is ok for it to not perfectly be daily
    // gaps of a few days are acceptable and do not materially degrade the NAV decay checks
    function getDaysAgo(State memory self, uint8 daysAgo) internal pure returns (uint256) {
        if (daysAgo >= self.len) revert NavHistoryInsufficient();

        uint8 targetIndex = (MAX_NAV_TRACKING + self.currentIndex - daysAgo) % MAX_NAV_TRACKING;
        return self.history[targetIndex];
    }
}

File 56 of 80 : ILSTStats.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

/// @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[5] discountTimestampByPercent; // each index is the timestamp that the token reached that discount
        uint256[] slashingCosts;
        uint256[] slashingTimestamps;
    }

    /// @notice Used to transfer LST snapshot data to other chain.
    struct LSTDestinationInfo {
        uint256 snapshotTimestamp;
        uint256 newBaseApr;
        uint256 currentEthPerToken;
    }

    /// @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 Get if the underlying LST token is rebasing
    /// @return rebasing is true if the lst is a rebasing token
    function isRebasing() external view returns (bool rebasing);
}

File 57 of 80 : AutopoolETHStrategyConfig.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

import { NavTracking } from "src/strategy/NavTracking.sol";

library AutopoolETHStrategyConfig {
    uint256 private constant WEIGHT_MAX = 1e6;
    int256 private constant WEIGHT_MAX_I = 1e6;

    error InvalidConfig(string paramName);

    // TODO: switch swapCostOffset from days to seconds; possibly pauseRebalance too
    struct StrategyConfig {
        SwapCostOffsetConfig swapCostOffset;
        NavLookbackConfig navLookback;
        SlippageConfig slippage;
        ModelWeights modelWeights;
        // number of days to pause rebalancing if a long-term nav decay is detected
        uint16 pauseRebalancePeriodInDays;
        // number of seconds before next rebalance can occur
        uint256 rebalanceTimeGapInSeconds;
        // destinations trading a premium above maxPremium will be blocked from new capital deployments
        int256 maxPremium; // 100% = 1e18
        // destinations trading a discount above maxDiscount will be blocked from new capital deployments
        int256 maxDiscount; // 100% = 1e18
        // if any stats data is older than this, rebalancing will revert
        uint40 staleDataToleranceInSeconds;
        // the maximum discount incorporated in price return
        int256 maxAllowedDiscount;
        // the maximum deviation between spot & safe price for individual LSTs
        uint256 lstPriceGapTolerance;
    }

    struct SwapCostOffsetConfig {
        // the swap cost offset period to initialize the strategy with
        uint16 initInDays;
        // the number of violations required to trigger a tightening of the swap cost offset period (1 to 10)
        uint16 tightenThresholdInViolations;
        // the number of days to decrease the swap offset period for each tightening step
        uint16 tightenStepInDays;
        // the number of days since a rebalance required to trigger a relaxing of the swap cost offset period
        uint16 relaxThresholdInDays;
        // the number of days to increase the swap offset period for each relaxing step
        uint16 relaxStepInDays;
        // the maximum the swap cost offset period can reach. This is the loosest the strategy will be
        uint16 maxInDays;
        // the minimum the swap cost offset period can reach. This is the most conservative the strategy will be
        uint16 minInDays;
    }

    struct NavLookbackConfig {
        // the number of days for the first NAV decay comparison (e.g., 30 days)
        uint8 lookback1InDays;
        // the number of days for the second NAV decay comparison (e.g., 60 days)
        uint8 lookback2InDays;
        // the number of days for the third NAV decay comparison (e.g., 90 days)
        uint8 lookback3InDays;
    }

    struct SlippageConfig {
        // the maximum slippage that is allowed for a normal rebalance
        // under normal circumstances this will not be triggered because the swap offset logic is the primary gate
        // but this ensures a sensible slippage level will never be exceeded
        uint256 maxNormalOperationSlippage; // 100% = 1e18
        // the maximum amount of slippage to allow when a destination is trimmed due to constraint violations
        // recommend setting this higher than maxNormalOperationSlippage
        uint256 maxTrimOperationSlippage; // 100% = 1e18
        // 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
        uint256 maxEmergencyOperationSlippage; // 100% = 1e18
        // the maximum amount of slippage to allow when the Autopool has been shutdown
        uint256 maxShutdownOperationSlippage; // 100% = 1e18
    }

    struct ModelWeights {
        uint256 baseYield;
        uint256 feeYield;
        uint256 incentiveYield;
        uint256 slashing;
        int256 priceDiscountExit;
        int256 priceDiscountEnter;
        int256 pricePremium;
    }

    // slither-disable-start cyclomatic-complexity

    function validate(StrategyConfig memory config) internal pure {
        // Swap Cost Offset Config

        if (
            config.swapCostOffset.initInDays < config.swapCostOffset.minInDays
                || config.swapCostOffset.initInDays > config.swapCostOffset.maxInDays
                || config.swapCostOffset.initInDays < 7 || config.swapCostOffset.initInDays > 90
        ) {
            revert InvalidConfig("swapCostOffset_initInDays");
        }

        if (
            config.swapCostOffset.tightenThresholdInViolations < 1
                || config.swapCostOffset.tightenThresholdInViolations > 10
        ) {
            revert InvalidConfig("swapCostOffset_tightenThresholdInViolations");
        }

        if (config.swapCostOffset.tightenStepInDays < 1 || config.swapCostOffset.tightenStepInDays > 7) {
            revert InvalidConfig("swapCostOffset_tightenStepInDays");
        }

        if (config.swapCostOffset.relaxThresholdInDays < 14 || config.swapCostOffset.relaxThresholdInDays > 90) {
            revert InvalidConfig("swapCostOffset_relaxThresholdInDays");
        }

        if (config.swapCostOffset.relaxStepInDays < 1 || config.swapCostOffset.relaxStepInDays > 7) {
            revert InvalidConfig("swapCostOffset_relaxStepInDays");
        }

        if (
            config.swapCostOffset.maxInDays <= config.swapCostOffset.minInDays || config.swapCostOffset.maxInDays < 8
                || config.swapCostOffset.maxInDays > 90
        ) {
            revert InvalidConfig("swapCostOffset_maxInDays");
        }

        if (config.swapCostOffset.minInDays < 7 || config.swapCostOffset.minInDays > 90) {
            revert InvalidConfig("swapCostOffset_minInDays");
        }

        // NavLookback

        _validateNotZero(config.navLookback.lookback1InDays, "navLookback_lookback1InDays");

        // the 91st spot holds current (0 days ago), so the farthest back that can be retrieved is 90 days ago
        if (
            config.navLookback.lookback1InDays >= NavTracking.MAX_NAV_TRACKING
                || config.navLookback.lookback2InDays >= NavTracking.MAX_NAV_TRACKING
                || config.navLookback.lookback3InDays > NavTracking.MAX_NAV_TRACKING
        ) {
            revert InvalidConfig("navLookback_max");
        }

        // lookback should be configured smallest to largest and should not be equal
        if (
            config.navLookback.lookback1InDays >= config.navLookback.lookback2InDays
                || config.navLookback.lookback2InDays >= config.navLookback.lookback3InDays
        ) {
            revert InvalidConfig("navLookback_steps");
        }

        // Slippage

        _ensureNotGt25PctE18(config.slippage.maxShutdownOperationSlippage, "slippage_maxShutdownOperationSlippage");
        _ensureNotGt25PctE18(config.slippage.maxEmergencyOperationSlippage, "slippage_maxEmergencyOperationSlippage");
        _ensureNotGt25PctE18(config.slippage.maxTrimOperationSlippage, "slippage_maxTrimOperationSlippage");
        _ensureNotGt25PctE18(config.slippage.maxNormalOperationSlippage, "slippage_maxNormalOperationSlippage");

        // Model Weights

        if (config.modelWeights.baseYield > WEIGHT_MAX) {
            revert InvalidConfig("modelWeights_baseYield");
        }

        if (config.modelWeights.feeYield > WEIGHT_MAX) {
            revert InvalidConfig("modelWeights_feeYield");
        }

        if (config.modelWeights.incentiveYield > WEIGHT_MAX) {
            revert InvalidConfig("modelWeights_incentiveYield");
        }

        if (config.modelWeights.slashing > WEIGHT_MAX) {
            revert InvalidConfig("modelWeights_slashing");
        }

        if (config.modelWeights.priceDiscountExit > WEIGHT_MAX_I) {
            revert InvalidConfig("modelWeights_priceDiscountExit");
        }

        if (config.modelWeights.priceDiscountEnter > WEIGHT_MAX_I) {
            revert InvalidConfig("modelWeights_priceDiscountEnter");
        }

        if (config.modelWeights.pricePremium > WEIGHT_MAX_I) {
            revert InvalidConfig("modelWeights_pricePremium");
        }

        // Top Level Config

        if (config.pauseRebalancePeriodInDays < 7 || config.pauseRebalancePeriodInDays > 90) {
            revert InvalidConfig("pauseRebalancePeriodInDays");
        }

        if (config.rebalanceTimeGapInSeconds < 1 hours || config.rebalanceTimeGapInSeconds > 30 days) {
            revert InvalidConfig("rebalanceTimeGapInSeconds");
        }

        _ensureNotGt25PctE18OrLtZero(config.maxPremium, "maxPremium");
        _ensureNotGt25PctE18OrLtZero(config.maxDiscount, "maxPremium");

        if (config.staleDataToleranceInSeconds < 1 hours || config.staleDataToleranceInSeconds > 7 days) {
            revert InvalidConfig("staleDataToleranceInSeconds");
        }

        if (config.maxAllowedDiscount < 0 || config.maxAllowedDiscount > 0.5e18) {
            revert InvalidConfig("maxAllowedDiscount");
        }

        if (config.lstPriceGapTolerance > 500) {
            revert InvalidConfig("lstPriceGapTolerance");
        }
    }

    // slither-disable-end cyclomatic-complexity

    function _validateNotZero(uint256 val, string memory err) private pure {
        if (val == 0) {
            revert InvalidConfig(err);
        }
    }

    function _ensureNotGt25PctE18(uint256 value, string memory err) private pure {
        if (value > 0.25e18) {
            revert InvalidConfig(err);
        }
    }

    function _ensureNotGt25PctE18OrLtZero(int256 value, string memory err) private pure {
        if (value > 0.25e18 || value < 0) {
            revert InvalidConfig(err);
        }
    }
}

File 58 of 80 : StrategyUtils.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity 0.8.17;

library StrategyUtils {
    error CannotConvertUintToInt();

    function convertUintToInt(uint256 value) internal pure returns (int256) {
        // slither-disable-next-line timestamp
        if (value > uint256(type(int256).max)) revert CannotConvertUintToInt();
        return int256(value);
    }
}

File 59 of 80 : SubSaturateMath.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity 0.8.17;

library SubSaturateMath {
    function subSaturate(uint256 self, uint256 other) internal pure returns (uint256) {
        if (other >= self) return 0;
        return self - other;
    }

    function subSaturate(int256 self, int256 other) internal pure returns (int256) {
        if (other >= self) return 0;
        return self - other;
    }
}

File 60 of 80 : SummaryStats.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity 0.8.17;

import { IDexLSTStats } from "src/interfaces/stats/IDexLSTStats.sol";
import { IAutopoolStrategy } from "src/interfaces/strategy/IAutopoolStrategy.sol";
import { IIncentivesPricingStats } from "src/interfaces/stats/IIncentivesPricingStats.sol";
import { IDestinationVault } from "src/interfaces/vault/IDestinationVault.sol";
import { IStrategy } from "src/interfaces/strategy/IStrategy.sol";
import { IAutopoolStrategy } from "src/interfaces/strategy/IAutopoolStrategy.sol";
import { StrategyUtils } from "src/strategy/libs/StrategyUtils.sol";
import { IDexLSTStats } from "src/interfaces/stats/IDexLSTStats.sol";
import { Incentives } from "src/strategy/libs/Incentives.sol";
import { PriceReturn } from "src/strategy/libs/PriceReturn.sol";
import { IAutopool } from "src/interfaces/vault/IAutopool.sol";
import { ISystemComponent } from "src/interfaces/ISystemComponent.sol";
import { IRootPriceOracle } from "src/interfaces/oracles/IRootPriceOracle.sol";
import { ISystemRegistry } from "src/interfaces/ISystemRegistry.sol";

library SummaryStats {
    error StaleData(string name);
    error LstStatsReservesMismatch();

    event InLSTPriceGap(address token, uint256 priceSafe, uint256 priceSpot);
    event OutLSTPriceGap(address token, uint256 priceSafe, uint256 priceSpot);

    struct InterimStats {
        uint256 baseApr;
        int256 priceReturn;
        int256 maxDiscount;
        int256 maxPremium;
        uint256 reservesTotal;
        int256[] priceReturns;
        uint256 numLstStats;
    }

    function getDestinationSummaryStats(
        IAutopool autoPool,
        IIncentivesPricingStats incentivePricing,
        address destAddress,
        uint256 price,
        IAutopoolStrategy.RebalanceDirection direction,
        uint256 amount
    ) external returns (IStrategy.SummaryStats memory) {
        // NOTE: creating this as empty to save on variables later
        // has the distinct downside that if you forget to update a value, you get the zero value
        // slither-disable-next-line uninitialized-local
        IStrategy.SummaryStats memory result;

        if (destAddress == address(autoPool)) {
            result.destination = destAddress;
            result.ownedShares = autoPool.getAssetBreakdown().totalIdle;
            result.pricePerShare = price;
            return result;
        }

        IDestinationVault dest = IDestinationVault(destAddress);
        IDexLSTStats.DexLSTStatsData memory stats = dest.getStats().current();

        ensureNotStaleData("DexStats", stats.lastSnapshotTimestamp);

        // temporary holder to reduce variables
        InterimStats memory interimStats;

        interimStats.numLstStats = stats.lstStatsData.length;
        if (interimStats.numLstStats != stats.reservesInEth.length) revert LstStatsReservesMismatch();

        interimStats.priceReturns = PriceReturn.calculatePriceReturns(stats);

        for (uint256 i = 0; i < interimStats.numLstStats; ++i) {
            uint256 reserveValue = stats.reservesInEth[i];
            interimStats.reservesTotal += reserveValue;

            if (interimStats.priceReturns[i] != 0) {
                interimStats.priceReturn +=
                    PriceReturn.calculateWeightedPriceReturn(interimStats.priceReturns[i], reserveValue, direction);
            }

            // For tokens like WETH/ETH who have no data, tokens we've configured as NO_OP's in the
            // destinations/calculators, we can just skip the rest of these calcs as they have no stats
            if (stats.lstStatsData[i].baseApr == 0 && stats.lstStatsData[i].lastSnapshotTimestamp == 0) {
                continue;
            }

            ensureNotStaleData("lstData", stats.lstStatsData[i].lastSnapshotTimestamp);

            interimStats.baseApr += stats.lstStatsData[i].baseApr * reserveValue;

            int256 discount = stats.lstStatsData[i].discount;
            // slither-disable-next-line timestamp
            if (discount < interimStats.maxPremium) {
                interimStats.maxPremium = discount;
            }
            // slither-disable-next-line timestamp
            if (discount > interimStats.maxDiscount) {
                interimStats.maxDiscount = discount;
            }
        }

        // if reserves are 0, then leave baseApr + priceReturn as 0
        if (interimStats.reservesTotal > 0) {
            result.baseApr = interimStats.baseApr / interimStats.reservesTotal;
            result.priceReturn = interimStats.priceReturn / StrategyUtils.convertUintToInt(interimStats.reservesTotal);
        }

        result.destination = destAddress;
        result.feeApr = stats.feeApr;
        result.incentiveApr = Incentives.calculateIncentiveApr(
            incentivePricing, stats.stakingIncentiveStats, direction, destAddress, amount, price
        );
        result.safeTotalSupply = stats.stakingIncentiveStats.safeTotalSupply;
        result.ownedShares = dest.balanceOf(address(autoPool));
        result.pricePerShare = price;
        result.maxPremium = interimStats.maxPremium;
        result.maxDiscount = interimStats.maxDiscount;

        uint256 returnExPrice = (
            result.baseApr * IAutopoolStrategy(address(this)).weightBase() / 1e6
                + result.feeApr * IAutopoolStrategy(address(this)).weightFee() / 1e6
                + result.incentiveApr * IAutopoolStrategy(address(this)).weightIncentive() / 1e6
        );

        // price already weighted
        result.compositeReturn = StrategyUtils.convertUintToInt(returnExPrice) + result.priceReturn;

        return result;
    }

    // Calculate the largest difference between spot & safe price for the underlying LST tokens.
    // This does not support Curve meta pools
    function verifyLSTPriceGap(
        IAutopool autoPool,
        IStrategy.RebalanceParams memory params,
        uint256 tolerance
    ) external returns (bool) {
        // Pricer
        ISystemRegistry registry = ISystemRegistry(ISystemComponent(address(this)).getSystemRegistry());
        IRootPriceOracle pricer = registry.rootPriceOracle();

        IDestinationVault dest;
        address[] memory lstTokens;
        uint256 numLsts;
        address dvPoolAddress;

        // Out Destination
        if (params.destinationOut != address(autoPool)) {
            dest = IDestinationVault(params.destinationOut);
            lstTokens = dest.underlyingTokens();
            numLsts = lstTokens.length;
            dvPoolAddress = dest.getPool();
            for (uint256 i = 0; i < numLsts; ++i) {
                uint256 priceSafe = pricer.getPriceInEth(lstTokens[i]);
                uint256 priceSpot = pricer.getSpotPriceInEth(lstTokens[i], dvPoolAddress);
                // slither-disable-next-line reentrancy-events
                emit OutLSTPriceGap(lstTokens[i], priceSafe, priceSpot);
                // For out destination, the pool tokens should not be lower than safe price by tolerance
                if ((priceSafe == 0) || (priceSpot == 0)) {
                    return false;
                } else if (priceSafe > priceSpot) {
                    if (((priceSafe * 1.0e18 / priceSpot - 1.0e18) * 10_000) / 1.0e18 > tolerance) {
                        return false;
                    }
                }
            }
        }

        // In Destination
        dest = IDestinationVault(params.destinationIn);
        lstTokens = dest.underlyingTokens();
        numLsts = lstTokens.length;
        dvPoolAddress = dest.getPool();
        for (uint256 i = 0; i < numLsts; ++i) {
            uint256 priceSafe = pricer.getPriceInEth(lstTokens[i]);
            uint256 priceSpot = pricer.getSpotPriceInEth(lstTokens[i], dvPoolAddress);
            // slither-disable-next-line reentrancy-events
            emit InLSTPriceGap(lstTokens[i], priceSafe, priceSpot);
            // For in destination, the pool tokens should not be higher than safe price by tolerance
            if ((priceSafe == 0) || (priceSpot == 0)) {
                return false;
            } else if (priceSpot > priceSafe) {
                if (((priceSpot * 1.0e18 / priceSafe - 1.0e18) * 10_000) / 1.0e18 > tolerance) {
                    return false;
                }
            }
        }

        return true;
    }

    function ensureNotStaleData(string memory name, uint256 dataTimestamp) internal view {
        // slither-disable-next-line timestamp
        if (block.timestamp - dataTimestamp > IAutopoolStrategy(address(this)).staleDataToleranceInSeconds()) {
            revert StaleData(name);
        }
    }
}

File 61 of 80 : 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 62 of 80 : ISyncSwapper.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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

interface ISyncSwapper {
    error DataMismatch(string element);
    error InvalidIndex();

    /**
     * @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 63 of 80 : IAutopilotRouterBase.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity >=0.8.7;

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 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 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 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.
    function claimAutopoolRewards(IAutopool vault, IMainRewarder rewarder) external;
}

File 64 of 80 : IRewards.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2024 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 vaultToken() 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 65 of 80 : IAsyncSwapper.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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

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 66 of 80 : IDestinationAdapter.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

/**
 * @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 80 : IDestinationVaultFactory.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 80 : IStatsCalculator.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

/// @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 80 : IERC4626.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

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 70 of 80 : 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 71 of 80 : 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 80 : IBaseRewarder.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

interface IBaseRewarder {
    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, uint256 reward);
    event QueuedRewardsUpdated(uint256 startingQueuedRewards, uint256 startingNewRewards, uint256 queuedRewards);
    event AddedToWhitelist(address indexed wallet);
    event RemovedFromWhitelist(address indexed wallet);

    event TokeLockDurationUpdated(uint256 newDuration);

    /**
     * @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 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 80 : IExtraRewarder.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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.
     */
    function getReward(address account) external;
}

File 74 of 80 : 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 75 of 80 : AbstractRewarder.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.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 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();

        // Do not update lastUpdateBlock if rewardPerTokenStored is 0, to prevent the loss of rewards when supply is 0
        if (rewardPerTokenStored > 0) {
            if (account != address(0)) {
                lastUpdateBlock = lastBlockRewardApplicable();
                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.
     */
    function _getReward(address account) internal {
        Errors.verifyNotZero(account, "account");

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

            IERC20(rewardToken).safeTransfer(account, reward);
        } else if (accToke.isStakeableAmount(reward)) {
            rewards[account] = 0;
            emit RewardPaid(account, reward);
            // authorize accToke to get our reward Toke
            LibAdapter._approve(IERC20(tokeAddress), address(accToke), reward);

            // stake Toke
            accToke.stake(reward, tokeLockDuration, account);
        }
    }

    /**
     * @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 balanceOf(address account) public view virtual returns (uint256);
}

File 76 of 80 : IBaseAssetVault.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;

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 77 of 80 : Incentives.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity 0.8.17;

import { IDexLSTStats } from "src/interfaces/stats/IDexLSTStats.sol";
import { IAutopoolStrategy } from "src/interfaces/strategy/IAutopoolStrategy.sol";
import { IIncentivesPricingStats } from "src/interfaces/stats/IIncentivesPricingStats.sol";
import { IDestinationVault } from "src/interfaces/vault/IDestinationVault.sol";
import { IERC20Metadata } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { Math } from "openzeppelin-contracts/utils/math/Math.sol";

library Incentives {
    using Math for uint256;

    // when removing liquidity, rewards can be expired by this amount if the pool as incentive credits
    uint256 private constant EXPIRED_REWARD_TOLERANCE = 2 days;

    function calculateIncentiveApr(
        IIncentivesPricingStats pricing,
        IDexLSTStats.StakingIncentiveStats memory stats,
        IAutopoolStrategy.RebalanceDirection direction,
        address destAddress,
        uint256 lpAmountToAddOrRemove,
        uint256 lpPrice
    ) external view returns (uint256) {
        uint40 staleDataToleranceInSeconds = IAutopoolStrategy(address(this)).staleDataToleranceInSeconds();

        bool hasCredits = stats.incentiveCredits > 0;
        uint256 totalRewards = 0;

        uint256 numRewards = stats.annualizedRewardAmounts.length;
        for (uint256 i = 0; i < numRewards; ++i) {
            address rewardToken = stats.rewardTokens[i];
            // Move ahead only if the rewardToken is not 0
            if (rewardToken != address(0)) {
                uint256 tokenPrice = getIncentivePrice(staleDataToleranceInSeconds, pricing, rewardToken);

                // skip processing if the token is worthless or unregistered
                if (tokenPrice == 0) continue;

                uint256 periodFinish = stats.periodFinishForRewards[i];
                uint256 rewardRate = stats.annualizedRewardAmounts[i];
                uint256 rewardDivisor = 10 ** IERC20Metadata(rewardToken).decimals();

                if (direction == IAutopoolStrategy.RebalanceDirection.Out) {
                    // if the destination has credits then extend the periodFinish by the expiredTolerance
                    // this allows destinations that consistently had rewards some leniency
                    if (hasCredits) {
                        periodFinish += EXPIRED_REWARD_TOLERANCE;
                    }

                    // slither-disable-next-line timestamp
                    if (periodFinish > block.timestamp) {
                        // tokenPrice is 1e18 and we want 1e18 out, so divide by the token decimals
                        totalRewards += rewardRate * tokenPrice / rewardDivisor;
                    }
                } else {
                    // when adding to a destination, count incentives only when either of the following conditions are
                    // met:
                    // 1) the incentive lasts at least 7 days
                    // 2) the incentive lasts >3 days and the destination has a positive incentive credit balance
                    if (
                        // slither-disable-next-line timestamp
                        periodFinish >= block.timestamp + 7 days
                            || (hasCredits && periodFinish > block.timestamp + 3 days)
                    ) {
                        // tokenPrice is 1e18 and we want 1e18 out, so divide by the token decimals
                        totalRewards += rewardRate * tokenPrice / rewardDivisor;
                    }
                }
            }
        }

        if (totalRewards == 0) {
            return 0;
        }

        uint256 lpTokenDivisor = 10 ** IDestinationVault(destAddress).decimals();
        uint256 totalSupplyInEth = 0;
        // When comparing in & out destinations, we want to consider the supply with our allocation
        // included to estimate the resulting incentive rate
        if (direction == IAutopoolStrategy.RebalanceDirection.Out) {
            totalSupplyInEth = stats.safeTotalSupply * lpPrice / lpTokenDivisor;
        } else {
            totalSupplyInEth = (stats.safeTotalSupply + lpAmountToAddOrRemove) * lpPrice / lpTokenDivisor;
        }

        // Adjust for totalSupplyInEth is 0
        if (totalSupplyInEth != 0) {
            return (totalRewards * 1e18) / totalSupplyInEth;
        } else {
            return (totalRewards);
        }
    }

    function getIncentivePrice(
        uint40 staleDataToleranceInSeconds,
        IIncentivesPricingStats pricing,
        address token
    ) public view returns (uint256) {
        (uint256 fastPrice, uint256 slowPrice) = pricing.getPriceOrZero(token, staleDataToleranceInSeconds);
        return fastPrice.min(slowPrice);
    }
}

File 78 of 80 : PriceReturn.sol
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.

pragma solidity 0.8.17;

import { IAutopoolStrategy } from "src/interfaces/strategy/IAutopoolStrategy.sol";
import { StrategyUtils } from "src/strategy/libs/StrategyUtils.sol";
import { IDexLSTStats } from "src/interfaces/stats/IDexLSTStats.sol";
import { ILSTStats } from "src/interfaces/stats/ILSTStats.sol";

library PriceReturn {
    function calculateWeightedPriceReturn(
        int256 priceReturn,
        uint256 reserveValue,
        IAutopoolStrategy.RebalanceDirection direction
    ) external view returns (int256) {
        IAutopoolStrategy strategy = IAutopoolStrategy(address(this));

        // slither-disable-next-line timestamp
        if (priceReturn > 0) {
            // LST trading at a discount
            if (direction == IAutopoolStrategy.RebalanceDirection.Out) {
                return priceReturn * StrategyUtils.convertUintToInt(reserveValue) * strategy.weightPriceDiscountExit()
                    / 1e6;
            } else {
                return priceReturn * StrategyUtils.convertUintToInt(reserveValue) * strategy.weightPriceDiscountEnter()
                    / 1e6;
            }
        } else {
            // LST trading at 0 or a premium
            return priceReturn * StrategyUtils.convertUintToInt(reserveValue) * strategy.weightPricePremium() / 1e6;
        }
    }

    function calculatePriceReturns(IDexLSTStats.DexLSTStatsData memory stats) external view returns (int256[] memory) {
        IAutopoolStrategy strategy = IAutopoolStrategy(address(this));

        ILSTStats.LSTStatsData[] memory lstStatsData = stats.lstStatsData;

        uint256 numLsts = lstStatsData.length;
        int256[] memory priceReturns = new int256[](numLsts);

        for (uint256 i = 0; i < numLsts; ++i) {
            ILSTStats.LSTStatsData memory data = lstStatsData[i];

            uint256 scalingFactor = 1e18; // default scalingFactor is 1

            int256 discount = data.discount;
            if (discount > strategy.maxAllowedDiscount()) {
                discount = strategy.maxAllowedDiscount();
            }

            // discount value that is negative indicates LST price premium
            // scalingFactor = 1e18 for premiums and discounts that are small
            // discountTimestampByPercent array holds the timestamp in position i for discount = (i+1)%
            uint40[5] memory discountTimestampByPercent = data.discountTimestampByPercent;

            // 1e16 means a 1% LST discount where full scale is 1e18.
            if (discount > 1e16) {
                // linear approximation for exponential function with approx. half life of 30 days
                uint256 halfLifeSec = 30 * 24 * 60 * 60;
                uint256 len = data.discountTimestampByPercent.length;
                for (uint256 j = 1; j < len; ++j) {
                    // slither-disable-next-line timestamp
                    if (discount <= StrategyUtils.convertUintToInt((j + 1) * 1e16)) {
                        // current timestamp should be strictly >= timestamp in discountTimestampByPercent
                        uint256 timeSinceDiscountSec =
                            uint256(uint40(block.timestamp) - discountTimestampByPercent[j - 1]);
                        scalingFactor >>= (timeSinceDiscountSec / halfLifeSec);
                        // slither-disable-next-line weak-prng
                        timeSinceDiscountSec %= halfLifeSec;
                        scalingFactor -= scalingFactor * timeSinceDiscountSec / halfLifeSec / 2;
                        break;
                    }
                }
            }
            priceReturns[i] = discount * StrategyUtils.convertUintToInt(scalingFactor) / 1e18;
        }

        return priceReturns;
    }
}

File 79 of 80 : 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 80 of 80 : 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);
    }
}

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/",
    "ERC4626/=lib/properties/lib/ERC4626/contracts/",
    "properties/=lib/properties/contracts/",
    "solmate/=lib/properties/lib/solmate/src/",
    "usingtellor/=lib/usingtellor/contracts/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs"
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "london",
  "viaIR": false,
  "libraries": {
    "src/destinations/adapters/BalancerBeethovenAdapter.sol": {
      "BalancerBeethovenAdapter": "0xeE9d4bDA0a124157D05756e48e12C67289833191"
    },
    "src/destinations/adapters/CurveV2FactoryCryptoAdapter.sol": {
      "CurveV2FactoryCryptoAdapter": "0xe46E7634d6e0BC954407078818fF62eDfF414184"
    },
    "src/destinations/adapters/rewards/AuraRewardsAdapter.sol": {
      "AuraRewards": "0x27401aca0EA294195187F6aE0673451AB01e5471"
    },
    "src/destinations/adapters/rewards/ConvexRewardsAdapter.sol": {
      "ConvexRewards": "0x08F295fD04B52Ab6a7f8A86bC9A0A1c0EA8aDE01"
    },
    "src/destinations/adapters/rewards/MaverickRewardsAdapter.sol": {
      "MaverickRewardsAdapter": "0x487F4bdf1201Ac0eD5D8c9A9b706A3b62De5249e"
    },
    "src/destinations/adapters/rewards/MaverickStakingAdapter.sol": {
      "MaverickStakingAdapter": "0x123d88094FEfB727Bef3906123485C2AF49810bC"
    },
    "src/destinations/adapters/staking/AuraAdapter.sol": {
      "AuraStaking": "0x9f121874565cC071Fd2E830DF2021987210DD853"
    },
    "src/destinations/adapters/staking/ConvexAdapter.sol": {
      "ConvexStaking": "0x6c2f91978004B0A769c622dfe352EdF331044d49"
    },
    "src/libs/BalancerUtilities.sol": {
      "BalancerUtilities": "0xa986Cc1bc9C8987E7b679F4edF8e065079BA461B"
    },
    "src/strategy/StructuredLinkedList.sol": {
      "StructuredLinkedList": "0x6C566a67b34CFed9821FC7433750A1391fa97989"
    },
    "src/strategy/WithdrawalQueue.sol": {
      "WithdrawalQueue": "0xC754773B0e8CaFbD17e978be3c31ef34869ba733"
    },
    "src/strategy/libs/Incentives.sol": {
      "Incentives": "0x451cE891E340A22bB6c2CE046827D4d69f571648"
    },
    "src/strategy/libs/PriceReturn.sol": {
      "PriceReturn": "0xF8e01E7120C0C4cF8Ba90d12DDdF0E3bE9e3DBaD"
    },
    "src/strategy/libs/SummaryStats.sol": {
      "SummaryStats": "0x88eA6A9B3d27Ee782f8c3e6bEE6392fabAD59796"
    },
    "src/vault/libs/Autopool4626.sol": {
      "Autopool4626": "0x1Dc315a9d153Dc516A007358C173df51C78C61e4"
    },
    "src/vault/libs/AutopoolDebt.sol": {
      "AutopoolDebt": "0xc2d3450E1b80136aE8fa3bC6EE8E4b702C7D4942"
    },
    "src/vault/libs/AutopoolDestinations.sol": {
      "AutopoolDestinations": "0x93B5d679C4FC5b5705cba9D8e505Ff66b962C7aD"
    },
    "src/vault/libs/AutopoolFees.sol": {
      "AutopoolFees": "0x35400b052d96ce4d9943AEeE9f36beB63eCB6b2b"
    },
    "src/vault/libs/AutopoolToken.sol": {
      "AutopoolToken": "0xb916038B047C637bD2be297A7480708Ba74E1A82"
    }
  }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"contract ISystemRegistry","name":"_systemRegistry","type":"address"},{"internalType":"address","name":"_template","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":"InvalidEthAmount","type":"error"},{"inputs":[{"internalType":"string","name":"paramName","type":"string"}],"name":"InvalidParam","type":"error"},{"inputs":[],"name":"InvalidStrategy","type":"error"},{"inputs":[],"name":"ItemExists","type":"error"},{"inputs":[],"name":"ItemNotFound","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":"blockDuration","type":"uint256"}],"name":"DefaultBlockDurationSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"rewardRatio","type":"uint256"}],"name":"DefaultRewardRatioSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"template","type":"address"}],"name":"StrategyTemplateAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"template","type":"address"}],"name":"StrategyTemplateRemoved","type":"event"},{"inputs":[],"name":"accessController","outputs":[{"internalType":"contract IAccessController","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"strategyTemplate","type":"address"}],"name":"addStrategyTemplate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"strategyTemplate","type":"address"},{"internalType":"string","name":"symbolSuffix","type":"string"},{"internalType":"string","name":"descPrefix","type":"string"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"bytes","name":"extraParams","type":"bytes"}],"name":"createVault","outputs":[{"internalType":"address","name":"newVaultAddress","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":"getStrategyTemplates","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSystemRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"isStrategyTemplate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"strategyTemplate","type":"address"}],"name":"removeStrategyTemplate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockDuration","type":"uint256"}],"name":"setDefaultRewardBlockDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"rewardRatio","type":"uint256"}],"name":"setDefaultRewardRatio","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"template","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vaultRegistry","outputs":[{"internalType":"contract IAutopoolRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"vaultTypeToPrototype","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]

6101006040523480156200001257600080fd5b506040516200519a3803806200519a833981016040819052620000359162000361565b836001600160a01b031663bc43cbaf6040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000074573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200009a9190620003ae565b84620000d9816040518060400160405280600f81526020016e5f73797374656d526567697374727960881b815250620002a660201b62000cdc1760201c565b6001600160a01b0390811660805281166200010757604051630cbe126f60e11b815260040160405180910390fd5b6001600160a01b031660a05260408051808201909152600881526774656d706c61746560c01b6020808301919091526200014c91859162000cdc620002a6821b17901c565b826001600160a01b031663f12baf5c6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200018b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001b19190620003ae565b6001600160a01b0316846001600160a01b031614620001f957604051632f6b3b6360e01b81523060048201526001600160a01b03841660248201526044015b60405180910390fd5b826001600160a01b031660e0816001600160a01b0316815250506080516001600160a01b031663047e51386040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000254573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200027a9190620003ae565b6001600160a01b031660c0526200029182620002d6565b6200029c8162000312565b5050505062000425565b6001600160a01b038216620002d2578060405163eac0d38960e01b8152600401620001f09190620003d5565b5050565b60038190556040518181527fc5bd932f8a6237639f4f95a2dfd064cda654f71b0e54ec3122fbc4e88f32734b906020015b60405180910390a150565b60048190556040518181527fd9798cd6c6dd9262c7ed2bbcb3156e1141a18884539118c3431475c553fc97eb9060200162000307565b6001600160a01b03811681146200035e57600080fd5b50565b600080600080608085870312156200037857600080fd5b8451620003858162000348565b6020860151909450620003988162000348565b6040860151606090960151949790965092505050565b600060208284031215620003c157600080fd5b8151620003ce8162000348565b9392505050565b600060208083528351808285015260005b818110156200040457858101830151858201604001528201620003e6565b506000604082860101526040601f19601f8301168501019250505092915050565b60805160a05160c05160e051614cee620004ac60003960008181610206015281816107f0015261095b0152600081816102be0152610c5a0152600081816102880152818161038a015281816104b40152818161058a015281816106480152610ece015260008181610307015281816108370152818161085801526109f10152614cee6000f3fe608060405260043610620000ef5760003560e01c8063b6d725201162000089578063db475d391162000060578063db475d3914620002e0578063f12baf5c14620002f7578063f5b7fe87146200032c578063f7581129146200034457600080fd5b8063b6d72520146200024d578063bc43cbaf1462000274578063cdd7b38a14620002aa57600080fd5b806362eda4cc11620000ca57806362eda4cc14620001975780636a306d6114620001cd5780636f2ddd9314620001f257806390a45519146200022857600080fd5b806346453be414620000f45780635c24feaf146200014b5780635e3119b61462000172575b600080fd5b3480156200010157600080fd5b506200012e6200011336600462001810565b6002602052600090815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156200015857600080fd5b50620001706200016a36600462001843565b6200036b565b005b3480156200017f57600080fd5b50620001706200019136600462001810565b62000495565b348015620001a457600080fd5b50620001bc620001b636600462001843565b62000557565b604051901515815260200162000142565b348015620001da57600080fd5b5062000170620001ec36600462001810565b6200056b565b348015620001ff57600080fd5b506200012e7f000000000000000000000000000000000000000000000000000000000000000081565b3480156200023557600080fd5b50620001706200024736600462001843565b62000629565b3480156200025a57600080fd5b506200026560035481565b60405190815260200162000142565b3480156200028157600080fd5b506200012e7f000000000000000000000000000000000000000000000000000000000000000081565b348015620002b757600080fd5b506200012e7f000000000000000000000000000000000000000000000000000000000000000081565b6200012e620002f13660046200190e565b62000741565b3480156200030457600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006200012e565b3480156200033957600080fd5b506200026560045481565b3480156200035157600080fd5b506200035c62000cc9565b604051620001429190620019f2565b604051632474521560e21b8152600060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015620003da573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000400919062001a41565b6200041e57604051634ca8886760e01b815260040160405180910390fd5b6200042a308362000d08565b6200043760008362000ddb565b6200045557604051633e04f87160e01b815260040160405180910390fd5b6040516001600160a01b03831681527ef3e3a0acda02c27a13452e372b704b521e4d6fd4e6ef61c644630191da2793906020015b60405180910390a15050565b604051632474521560e21b8152600060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa15801562000504573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200052a919062001a41565b6200054857604051634ca8886760e01b815260040160405180910390fd5b620005538262000df9565b5050565b600062000565818362000e35565b92915050565b604051632474521560e21b8152600060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015620005da573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000600919062001a41565b6200061e57604051634ca8886760e01b815260040160405180910390fd5b620005538262000e58565b604051632474521560e21b8152600060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa15801562000698573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620006be919062001a41565b620006dc57604051634ca8886760e01b815260040160405180910390fd5b620006e960008362000e8e565b620007075760405163d3ed043d60e01b815260040160405180910390fd5b6040516001600160a01b03831681527f518600c6ec6d3c5988449de003934cd665d2567f8100ade182775e3006a6114b9060200162000489565b60006200076f7f643a332a1a6de98a36f48a7596dfafd4ea1b69babd8e98b38d6b2a652b59d09b3362000ea5565b6200078d57604051634ca8886760e01b815260040160405180910390fd5b620007b584604051806040016040528060048152602001631cd85b1d60e21b81525062000f3e565b620007c260008862000e35565b620007e057604051632711b74d60e11b815260040160405180910390fd5b6000620008176001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168662000f61565b90506000620008306001600160a01b038a168762000f61565b90506000867f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663f543bb0e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620008b5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620008db919062001a65565b600354600454600188604051620008f29062001802565b6001600160a01b0396871681529486166020860152604085019390935260608401919091521515608083015290911660a082015260c0018190604051809103906000f59050801580156200094a573d6000803e3d6000fd5b509050620009826001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168862000fc2565b93506000846001600160a01b031663fc4680216040518163ffffffff1660e01b8152600401602060405180830381865afa158015620009c5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620009eb919062001a85565b905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316633fc8cef36040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000a4e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000a74919062001a65565b905081341462000a9e5760405163052da6dd60e31b81523460048201526024015b60405180910390fd5b806001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b15801562000ada57600080fd5b505af115801562000aef573d6000803e3d6000fd5b505050505062000b0181878462001061565b60405163267eb9ed60e01b81526001600160a01b0387169063267eb9ed9062000b379087908f908f908e908e9060040162001af3565b600060405180830381600087803b15801562000b5257600080fd5b505af115801562000b67573d6000803e3d6000fd5b5050604051630e9918b960e21b81526001600160a01b03868116600483015289169250633a6462e49150602401600060405180830381600087803b15801562000baf57600080fd5b505af115801562000bc4573d6000803e3d6000fd5b5062000bde925050506001600160a01b038d168a62000fc2565b60405163189acdbd60e31b81526001600160a01b038881166004830152919091169063c4d66de890602401600060405180830381600087803b15801562000c2457600080fd5b505af115801562000c39573d6000803e3d6000fd5b50506040516312b5ad0160e11b81526001600160a01b0389811660048301527f000000000000000000000000000000000000000000000000000000000000000016925063256b5a029150602401600060405180830381600087803b15801562000ca157600080fd5b505af115801562000cb6573d6000803e3d6000fd5b5050505050505050509695505050505050565b606062000cd7600062001113565b905090565b6001600160a01b03821662000553578060405163eac0d38960e01b815260040162000a95919062001b64565b6040805160048152602481019091526020810180516001600160e01b0316633c4aebd760e21b179052600062000d486001600160a01b0385168362001122565b80602001905181019062000d5d919062001a65565b9050600062000d766001600160a01b0385168462001122565b80602001905181019062000d8b919062001a65565b9050806001600160a01b0316826001600160a01b03161462000dd457604051632f6b3b6360e01b81526001600160a01b0380871660048301528516602482015260440162000a95565b5050505050565b600062000df2836001600160a01b0384166200114a565b9392505050565b60048190556040518181527fd9798cd6c6dd9262c7ed2bbcb3156e1141a18884539118c3431475c553fc97eb906020015b60405180910390a150565b6001600160a01b0381166000908152600183016020526040812054151562000df2565b60038190556040518181527fc5bd932f8a6237639f4f95a2dfd064cda654f71b0e54ec3122fbc4e88f32734b9060200162000e2a565b600062000df2836001600160a01b0384166200119c565b604051632474521560e21b8152600481018390526001600160a01b0382811660248301526000917f0000000000000000000000000000000000000000000000000000000000000000909116906391d1485490604401602060405180830381865afa15801562000f18573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000df2919062001a41565b81620005535780604051634389d5ab60e01b815260040162000a95919062001b64565b6040513060388201526f5af43d82803e903d91602b57fd5bf3ff602482015260148101839052733d602d80600a3d3981f3363d3d373d3d3d363d738152605881018290526037600c8201206078820152605560439091012060009062000df2565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b038116620005655760405162461bcd60e51b815260206004820152601760248201527f455243313136373a2063726561746532206661696c6564000000000000000000604482015260640162000a95565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301526000919085169063dd62ed3e90604401602060405180830381865afa158015620010b2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620010d8919062001a85565b90508015620010f757620010f76001600160a01b0385168483620012a0565b6200110d6001600160a01b0385168484620013e6565b50505050565b6060600062000df2836200149f565b606062000df2838360405180606001604052806025815260200162004c9460259139620014fd565b6000818152600183016020526040812054620011935750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915562000565565b50600062000565565b6000818152600183016020526040812054801562001295576000620011c360018362001b8f565b8554909150600090620011d99060019062001b8f565b905081811462001245576000866000018281548110620011fd57620011fd62001ba5565b906000526020600020015490508087600001848154811062001223576200122362001ba5565b6000918252602080832090910192909255918252600188019052604090208390555b855486908062001259576200125962001bbb565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505062000565565b600091505062000565565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301526000919085169063dd62ed3e90604401602060405180830381865afa158015620012f1573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062001317919062001a85565b9050818110156200137d5760405162461bcd60e51b815260206004820152602960248201527f5361666545524332303a2064656372656173656420616c6c6f77616e63652062604482015268656c6f77207a65726f60b81b606482015260840162000a95565b6040516001600160a01b0384166024820152828203604482018190529062000dd490869063095ea7b360e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526200157b565b604051636eb1769f60e11b81523060048201526001600160a01b038381166024830152600091839186169063dd62ed3e90604401602060405180830381865afa15801562001438573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200145e919062001a85565b6200146a919062001bd1565b6040516001600160a01b0385166024820152604481018290529091506200110d90859063095ea7b360e01b90606401620013ae565b606081600001805480602002602001604051908101604052809291908181526020018280548015620014f157602002820191906000526020600020905b815481526020019060010190808311620014dc575b50505050509050919050565b6060600080856001600160a01b0316856040516200151c919062001be7565b600060405180830381855afa9150503d806000811462001559576040519150601f19603f3d011682016040523d82523d6000602084013e6200155e565b606091505b5091509150620015718683838762001659565b9695505050505050565b6000620015d2826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316620016e19092919063ffffffff16565b805190915015620016545780806020019051810190620015f3919062001a41565b620016545760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840162000a95565b505050565b60608315620016cd578251600003620016c5576001600160a01b0385163b620016c55760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640162000a95565b5081620016d9565b620016d98383620016f2565b949350505050565b6060620016d984846000856200171f565b815115620017035781518083602001fd5b8060405162461bcd60e51b815260040162000a95919062001b64565b606082471015620017825760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840162000a95565b600080866001600160a01b03168587604051620017a0919062001be7565b60006040518083038185875af1925050503d8060008114620017df576040519150601f19603f3d011682016040523d82523d6000602084013e620017e4565b606091505b5091509150620017f78783838762001659565b979650505050505050565b61308e8062001c0683390190565b6000602082840312156200182357600080fd5b5035919050565b6001600160a01b03811681146200184057600080fd5b50565b6000602082840312156200185657600080fd5b813562000df2816200182a565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200188b57600080fd5b813567ffffffffffffffff80821115620018a957620018a962001863565b604051601f8301601f19908116603f01168101908282118183101715620018d457620018d462001863565b81604052838152866020858801011115620018ee57600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060008060008060a087890312156200192857600080fd5b863562001935816200182a565b9550602087013567ffffffffffffffff808211156200195357600080fd5b620019618a838b0162001879565b965060408901359150808211156200197857600080fd5b620019868a838b0162001879565b9550606089013594506080890135915080821115620019a457600080fd5b818901915089601f830112620019b957600080fd5b813581811115620019c957600080fd5b8a6020828501011115620019dc57600080fd5b6020830194508093505050509295509295509295565b6020808252825182820181905260009190848201906040850190845b8181101562001a355783516001600160a01b03168352928401929184019160010162001a0e565b50909695505050505050565b60006020828403121562001a5457600080fd5b8151801515811462000df257600080fd5b60006020828403121562001a7857600080fd5b815162000df2816200182a565b60006020828403121562001a9857600080fd5b5051919050565b60005b8381101562001abc57818101518382015260200162001aa2565b50506000910152565b6000815180845262001adf81602086016020860162001a9f565b601f01601f19169290920160200192915050565b6001600160a01b038616815260806020820181905260009062001b199083018762001ac5565b828103604084015262001b2d818762001ac5565b90508281036060840152838152838560208301376000602085830101526020601f19601f8601168201019150509695505050505050565b60208152600062000df2602083018462001ac5565b634e487b7160e01b600052601160045260246000fd5b8181038181111562000565576200056562001b79565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052603160045260246000fd5b8082018082111562000565576200056562001b79565b6000825162001bfb81846020870162001a9f565b919091019291505056fe6101806040523480156200001257600080fd5b506040516200308e3803806200308e8339810160408190526200003591620003d0565b858585857ffde2f69a846a71295e563d91ade82dc70e9eda278403d1aece24d0ded949403a868585858585846001600160a01b031663bc43cbaf6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200009f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620000c5919062000449565b6001600160a01b038116620000ed57604051630cbe126f60e11b815260040160405180910390fd5b6001600160a01b031660805260408051808201909152600c81526b2fb932bbb0b9322a37b5b2b760a11b60208083019190915262000136918691620014ac62000328821b17901c565b62000175826040518060400160405280601081526020016f5f6475726174696f6e496e426c6f636b60801b8152506200035860201b620014de1760201c565b620001b3836040518060400160405280600f81526020016e5f6e6577526577617264526174696f60881b8152506200035860201b620014de1760201c565b620001ed816040518060400160405280600b81526020016a5f726577617264526f6c6560a81b8152506200037e60201b620015011760201c565b6001600160a01b0385811660e08190526040516316bfae7f60e31b815291861660048301529063b5fd73f890602401602060405180830381865afa1580156200023a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000260919062000470565b620002a257604051634389d5ab60e01b815260206004820152600c60248201526b2fb932bbb0b9322a37b5b2b760a11b60448201526064015b60405180910390fd5b6001600160a01b039093166101005260c09190915260a05261012052506001600b55151561014052505060408051808201909152600d81526c2fb9ba30b5b4b733aa37b5b2b760991b6020828101919091526200030f9450859350909150620014ac62000328821b17901c565b6001600160a01b03166101605250620004de9350505050565b6001600160a01b03821662000354578060405163eac0d38960e01b81526004016200029991906200048e565b5050565b81600003620003545780604051634389d5ab60e01b81526004016200029991906200048e565b81620003545780604051634389d5ab60e01b81526004016200029991906200048e565b6001600160a01b0381168114620003b757600080fd5b50565b80518015158114620003cb57600080fd5b919050565b60008060008060008060c08789031215620003ea57600080fd5b8651620003f781620003a1565b60208801519096506200040a81620003a1565b60408801516060890151919650945092506200042960808801620003ba565b915060a08701516200043b81620003a1565b809150509295509295509295565b6000602082840312156200045c57600080fd5b81516200046981620003a1565b9392505050565b6000602082840312156200048357600080fd5b6200046982620003ba565b600060208083528351808285015260005b81811015620004bd578581018301518582016040015282016200049f565b506000604082860101526040601f19601f8301168501019250505092915050565b60805160a05160c05160e05161010051610120516101405161016051612a8d62000601600039600081816103cd01528181610fce01526114850152600081816104880152610cb10152600081816105f40152818161074e01528181610bf001528181610e5d01528181611005015261122801526000818161054601528181610bb401528181611e820152611f1e015260008181610818015281816108bf01528181610d8c015281816113b901528181611d620152611de40152600081816103610152610b210152600081816102ce01528181610aba0152818161184a01526118a20152600081816104af015281816106230152818161077d01528181610c1f01528181610e8c015281816110340152818161125701526117350152612a8d6000f3fe608060405234801561001057600080fd5b50600436106102265760003560e01c80637b0a47ee11610130578063bc43cbaf116100b8578063e21c81d31161007c578063e21c81d3146104f3578063e43252d714610508578063e665b4141461051b578063ead5d3591461052e578063f7c618c11461054157600080fd5b8063bc43cbaf146104aa578063cd3daf9d146104d1578063cd8e33d4146104d9578063d55a23f4146104e2578063df136d65146104ea57600080fd5b8063a218141b116100ff578063a218141b1461044c578063abe0429c14610455578063adc9772e1461045d578063aec9814c14610470578063b263487f1461048357600080fd5b80637b0a47ee146104075780638ab1d681146104105780638b87634714610423578063901a7d531461044357600080fd5b80633d18b912116101b35780636c8bcee8116101825780636c8bcee81461035c5780636f73a38f146103835780637050ccd91461038c57806370a082311461039f57806372f702f3146103c857600080fd5b80633d18b91214610325578063590a41f51461032d5780635e43c47b1461034057806363d38c3b1461035357600080fd5b806314d09249116101fa57806314d09249146102ae57806318160ddd146102c15780631fc93059146102c9578063262d3d6d146102f05780633af32abf146102f957600080fd5b80628cc2621461022b5780630569d3881461025157806306c933d81461025b5780630700037d1461028e575b600080fd5b61023e61023936600461272a565b610568565b6040519081526020015b60405180910390f35b6102596105e5565b005b61027e61026936600461272a565b600a6020526000908152604090205460ff1681565b6040519015158152602001610248565b61023e61029c36600461272a565b60086020526000908152604090205481565b6102596102bc366004612747565b61073f565b600e5461023e565b61023e7f000000000000000000000000000000000000000000000000000000000000000081565b61023e60065481565b61027e61030736600461272a565b6001600160a01b03166000908152600a602052604090205460ff1690565b6102596109fd565b61025961033b366004612747565b610a25565b61025961034e36600461272a565b610be1565b61023e60045481565b61023e7f000000000000000000000000000000000000000000000000000000000000000081565b61023e60095481565b61025961039a36600461276e565b610d76565b61023e6103ad36600461272a565b6001600160a01b03166000908152600f602052604090205490565b6103ef7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610248565b61023e60015481565b61025961041e36600461272a565b610e4e565b61023e61043136600461272a565b60076020526000908152604090205481565b61023e60055481565b61023e60025481565b61023e610f9f565b61025961046b3660046127a7565b610fb7565b61025961047e3660046127d3565b610ff6565b61027e7f000000000000000000000000000000000000000000000000000000000000000081565b6103ef7f000000000000000000000000000000000000000000000000000000000000000081565b61023e611191565b61023e60005481565b61023e611201565b61023e60035481565b6104fb61120d565b6040516102489190612848565b61025961051636600461272a565b611219565b6103ef610529366004612747565b611396565b61025961053c366004612895565b6113a3565b6103ef7f000000000000000000000000000000000000000000000000000000000000000081565b6001600160a01b0381166000908152600860209081526040808320546007909252822054670de0b6b3a76400009061059e611191565b6105a891906128ed565b6001600160a01b0385166000908152600f60205260409020546105cb9190612900565b6105d59190612917565b6105df9190612939565b92915050565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610672573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610696919061294c565b6106b357604051634ca8886760e01b815260040160405180910390fd5b60006106bf600c611521565b1115610713576106f16106e960016106d7600c611521565b6106e191906128ed565b600c9061152b565b600c9061153e565b61070e5760405163d3ed043d60e01b815260040160405180910390fd5b6106b3565b6040517f1a53a10dfbef82dfcf34ce946c284b478164f6e7496a07fc0cbe69d01ff44f0990600090a150565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa1580156107cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107f0919061294c565b61080d57604051634ca8886760e01b815260040160405180910390fd5b81156109c0576108bd7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663024d381b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610874573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108989190612969565b60405180604001604052806007815260200166616363546f6b6560c81b8152506114ac565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663024d381b6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561091b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061093f9190612969565b6001600160a01b0316635fec5c646040518163ffffffff1660e01b8152600401602060405180830381865afa15801561097c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109a09190612986565b8210156109c05760405163bcec4c5360e01b815260040160405180910390fd5b60098290556040518281527f62ff17080925adbeb90d914efe5f615d059f19c2e728740b500d1e84ba65989a906020015b60405180910390a15050565b610a05611553565b610a0e336115ac565b610a19336001611665565b610a236001600b55565b565b336000908152600a602052604090205460ff16158015610a6c5750610a6a7f5e17fc5225d4a099df75359ce1f405503ca79498a8dc46a7d583235a0ee45c163361170c565b155b15610a8a57604051634ca8886760e01b815260040160405180910390fd5b60045481610a988282612939565b92506000544310610ab657610aac836117a2565b6000600455610b64565b60007f0000000000000000000000000000000000000000000000000000000000000000600054610ae691906128ed565b610af090436128ed565b9050600081600154610b029190612900565b9050600085610b13836103e8612900565b610b1d9190612917565b90507f0000000000000000000000000000000000000000000000000000000000000000811015610b5a57610b50866117a2565b6000600455610b60565b60048690555b5050505b600454604080518481526020810184905280820192909252517fe4a19739e7048ef5e90c7a157e8fb37a6e90cb8de298625227540d2443b9769c9181900360600190a1610bdc6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633308461192a565b505050565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610c6e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c92919061294c565b610caf57604051634ca8886760e01b815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000000610ced57604051639fe610b960e01b815260040160405180910390fd5b610d1582604051806040016040528060068152602001651c995dd85c9960d21b8152506114ac565b610d20600c83611995565b610d3d57604051633e04f87160e01b815260040160405180910390fd5b6040516001600160a01b03831681527fd432e6f46dbf91c120fdfa95a1f4bf5c43f04d957fbc3a32e693be0d29bf17b0906020016109f1565b336001600160a01b03831614801590610e2257507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166330d960af6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610de8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e0c9190612969565b6001600160a01b0316336001600160a01b031614155b15610e4057604051634ca8886760e01b815260040160405180910390fd5b610e4a82826119aa565b5050565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610edb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610eff919061294c565b610f1c57604051634ca8886760e01b815260040160405180910390fd5b6001600160a01b0382166000908152600a602052604090205460ff16610f555760405163d3ed043d60e01b815260040160405180910390fd5b6001600160a01b0382166000818152600a6020526040808220805460ff19169055517fcdd2e9b91a56913d370075169cefa1602ba36be5301664f752192bb1709df7579190a25050565b600080544310610fb0575060005490565b435b905090565b610fc182826119cf565b610e4a6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633308461192a565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015611083573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110a7919061294c565b6110c457604051634ca8886760e01b815260040160405180910390fd5b8160005b8181101561118a576110fa8585838181106110e5576110e561299f565b90506020020160208101906106e9919061272a565b6111175760405163d3ed043d60e01b815260040160405180910390fd5b7fe0f3b1406ab4dd940a92a081b1bcef51da212a57978cc8f6dde5c89ab41a5aa785858381811061114a5761114a61299f565b905060200201602081019061115f919061272a565b6040516001600160a01b03909116815260200160405180910390a1611183816129b5565b90506110c8565b5050505050565b60008061119d600e5490565b9050806000036111af57505060035490565b806001546002546111be610f9f565b6111c891906128ed565b6111d29190612900565b6111e490670de0b6b3a7640000612900565b6111ee9190612917565b6003546111fb9190612939565b91505090565b6000610fb2600c611521565b6060610fb2600c611ac5565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa1580156112a6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112ca919061294c565b6112e757604051634ca8886760e01b815260040160405180910390fd5b61130f82604051806040016040528060068152602001651dd85b1b195d60d21b8152506114ac565b6001600160a01b0382166000908152600a602052604090205460ff161561134957604051633e04f87160e01b815260040160405180910390fd5b6001600160a01b0382166000818152600a6020526040808220805460ff19166001179055517fa850ae9193f515cbae8d35e8925bd2be26627fc91bce650b8652ed254e9cab039190a25050565b60006105df600c8361152b565b336001600160a01b0384161480159061144f57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166330d960af6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611415573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114399190612969565b6001600160a01b0316336001600160a01b031614155b1561146d57604051634ca8886760e01b815260040160405180910390fd5b611478838383611ad2565b610bdc6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168484611bda565b6001600160a01b038216610e4a578060405163eac0d38960e01b81526004016114d591906129f2565b60405180910390fd5b81600003610e4a5780604051634389d5ab60e01b81526004016114d591906129f2565b81610e4a5780604051634389d5ab60e01b81526004016114d591906129f2565b60006105df825490565b60006115378383611c0a565b9392505050565b6000611537836001600160a01b038416611c34565b6002600b54036115a55760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064016114d5565b6002600b55565b60006115b6611191565b600381905515611610576001600160a01b03821615611610576115d7610f9f565b6002556115e382610568565b6001600160a01b038316600090815260086020908152604080832084905560035460079092529091205590505b6003546002546040805184815260208101939093528201526001600160a01b038316907f469d38647ec007a9c93421468c92550d50fccc01ae12e149b1216aa9b0136fc7906060015b60405180910390a25050565b61166e82611d27565b600061167a600c611521565b90508115610bdc5760005b8181101561170657611698600c8261152b565b604051630c00007b60e41b81526001600160a01b038681166004830152919091169063c00007b090602401600060405180830381600087803b1580156116dd57600080fd5b505af11580156116f1573d6000803e3d6000fd5b50505050806116ff906129b5565b9050611685565b50505050565b604051632474521560e21b8152600481018390526001600160a01b0382811660248301526000917f0000000000000000000000000000000000000000000000000000000000000000909116906391d1485490604401602060405180830381865afa15801561177e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611537919061294c565b80600660008282546117b49190612939565b9091555050600e546000036117fe5760005460025410156117f9576001546002546000546117e291906128ed565b6117ec9190612900565b6117f69082612939565b90505b61183b565b60005443101561183b5760004360005461181891906128ed565b905060006001548261182a9190612900565b90506118368184612939565b925050505b61184560006115ac565b61186f7f000000000000000000000000000000000000000000000000000000000000000082612917565b600181905561189157604051631f2a200560e01b815260040160405180910390fd5b60058190554360028190556118c7907f000000000000000000000000000000000000000000000000000000000000000090612939565b6000819055600154600254600654604080518681526020810194909452830191909152606082019290925260808101919091527f8ce8cbe5f803930b0c6afe4640018bbfb02cbb5b0bfbe051b25a155201e80dac9060a00160405180910390a150565b6040516001600160a01b03808516602483015283166044820152606481018290526117069085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b03199093169290921790915261208b565b6000611537836001600160a01b03841661215d565b6119b2611553565b6119bb826115ac565b6119c58282611665565b610e4a6001600b55565b6119d8826115ac565b6119e282826121ac565b60006119ee600c611521565b905060005b81811015611a7b57611a06600c8261152b565b6040516356e4bb9760e11b81526001600160a01b03868116600483015260248201869052919091169063adc9772e90604401600060405180830381600087803b158015611a5257600080fd5b505af1158015611a66573d6000803e3d6000fd5b5050505080611a74906129b5565b90506119f3565b5081600e6000828254611a8e9190612939565b90915550506001600160a01b0383166000908152600f602052604081208054849290611abb908490612939565b9091555050505050565b6060600061153783612238565b611adb836115ac565b611ae58383612294565b6000611af1600c611521565b905060005b81811015611b7e57611b09600c8261152b565b60405163f3fef3a360e01b81526001600160a01b03878116600483015260248201879052919091169063f3fef3a390604401600060405180830381600087803b158015611b5557600080fd5b505af1158015611b69573d6000803e3d6000fd5b5050505080611b77906129b5565b9050611af6565b508115611b9057611b90846001611665565b82600e6000828254611ba291906128ed565b90915550506001600160a01b0384166000908152600f602052604081208054859290611bcf9084906128ed565b909155505050505050565b6040516001600160a01b038316602482015260448101829052610bdc90849063a9059cbb60e01b9060640161195e565b6000826000018281548110611c2157611c2161299f565b9060005260206000200154905092915050565b60008181526001830160205260408120548015611d1d576000611c586001836128ed565b8554909150600090611c6c906001906128ed565b9050818114611cd1576000866000018281548110611c8c57611c8c61299f565b9060005260206000200154905080876000018481548110611caf57611caf61299f565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080611ce257611ce2612a25565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506105df565b60009150506105df565b611d5081604051806040016040528060078152602001661858d8dbdd5b9d60ca1b8152506114ac565b6000611d5b82610568565b90506000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663024d381b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611dbe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611de29190612969565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663f543bb0e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e649190612969565b9150915082600003611e765750505050565b806001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316141580611eb75750600954155b15611f4a576001600160a01b03841660008181526008602052604080822091909155517fe2403640ba68fed3a2f88b7557551d1993f84b99bb10ff833f0cf8db0c5e048690611f099086815260200190565b60405180910390a2611f456001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168585611bda565b611706565b604051633f6e925b60e01b8152600481018490526001600160a01b03831690633f6e925b90602401602060405180830381865afa158015611f8f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611fb3919061294c565b15611706576001600160a01b03841660008181526008602052604080822091909155517fe2403640ba68fed3a2f88b7557551d1993f84b99bb10ff833f0cf8db0c5e0486906120059086815260200190565b60405180910390a2612018818385612320565b600954604051637628a37d60e01b81526004810185905260248101919091526001600160a01b038581166044830152831690637628a37d90606401600060405180830381600087803b15801561206d57600080fd5b505af1158015612081573d6000803e3d6000fd5b5050505050505050565b60006120e0826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166123c49092919063ffffffff16565b805190915015610bdc57808060200190518101906120fe919061294c565b610bdc5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016114d5565b60008181526001830160205260408120546121a4575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556105df565b5060006105df565b6121d582604051806040016040528060078152602001661858d8dbdd5b9d60ca1b8152506114ac565b6121fd8160405180604001604052806006815260200165185b5bdd5b9d60d21b8152506114de565b816001600160a01b03167f9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d8260405161165991815260200190565b60608160000180548060200260200160405190810160405280929190818152602001828054801561228857602002820191906000526020600020905b815481526020019060010190808311612274575b50505050509050919050565b6122bd82604051806040016040528060078152602001661858d8dbdd5b9d60ca1b8152506114ac565b6122e58160405180604001604052806006815260200165185b5bdd5b9d60d21b8152506114de565b816001600160a01b03167f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d58260405161165991815260200190565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301526000919085169063dd62ed3e90604401602060405180830381865afa158015612370573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123949190612986565b905080156123b0576123b06001600160a01b03851684836123db565b6117066001600160a01b03851684846124e7565b60606123d38484600085612599565b949350505050565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301526000919085169063dd62ed3e90604401602060405180830381865afa15801561242b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061244f9190612986565b9050818110156124b35760405162461bcd60e51b815260206004820152602960248201527f5361666545524332303a2064656372656173656420616c6c6f77616e63652062604482015268656c6f77207a65726f60b81b60648201526084016114d5565b6040516001600160a01b0384166024820152828203604482018190529061118a90869063095ea7b360e01b9060640161195e565b604051636eb1769f60e11b81523060048201526001600160a01b038381166024830152600091839186169063dd62ed3e90604401602060405180830381865afa158015612538573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061255c9190612986565b6125669190612939565b6040516001600160a01b03851660248201526044810182905290915061170690859063095ea7b360e01b9060640161195e565b6060824710156125fa5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016114d5565b600080866001600160a01b031685876040516126169190612a3b565b60006040518083038185875af1925050503d8060008114612653576040519150601f19603f3d011682016040523d82523d6000602084013e612658565b606091505b509150915061266987838387612674565b979650505050505050565b606083156126e35782516000036126dc576001600160a01b0385163b6126dc5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016114d5565b50816123d3565b6123d383838151156126f85781518083602001fd5b8060405162461bcd60e51b81526004016114d591906129f2565b6001600160a01b038116811461272757600080fd5b50565b60006020828403121561273c57600080fd5b813561153781612712565b60006020828403121561275957600080fd5b5035919050565b801515811461272757600080fd5b6000806040838503121561278157600080fd5b823561278c81612712565b9150602083013561279c81612760565b809150509250929050565b600080604083850312156127ba57600080fd5b82356127c581612712565b946020939093013593505050565b600080602083850312156127e657600080fd5b823567ffffffffffffffff808211156127fe57600080fd5b818501915085601f83011261281257600080fd5b81358181111561282157600080fd5b8660208260051b850101111561283657600080fd5b60209290920196919550909350505050565b6020808252825182820181905260009190848201906040850190845b818110156128895783516001600160a01b031683529284019291840191600101612864565b50909695505050505050565b6000806000606084860312156128aa57600080fd5b83356128b581612712565b92506020840135915060408401356128cc81612760565b809150509250925092565b634e487b7160e01b600052601160045260246000fd5b818103818111156105df576105df6128d7565b80820281158282048414176105df576105df6128d7565b60008261293457634e487b7160e01b600052601260045260246000fd5b500490565b808201808211156105df576105df6128d7565b60006020828403121561295e57600080fd5b815161153781612760565b60006020828403121561297b57600080fd5b815161153781612712565b60006020828403121561299857600080fd5b5051919050565b634e487b7160e01b600052603260045260246000fd5b6000600182016129c7576129c76128d7565b5060010190565b60005b838110156129e95781810151838201526020016129d1565b50506000910152565b6020815260008251806020840152612a118160408501602087016129ce565b601f01601f19169190910160400192915050565b634e487b7160e01b600052603160045260246000fd5b60008251612a4d8184602087016129ce565b919091019291505056fea2646970667358221220daa62ee8e068827eea5e5aa0e855ce0a9b17ee160a6870c6be36c6a73abdb77d64736f6c63430008110033416464726573733a206c6f772d6c6576656c207374617469632063616c6c206661696c6564a264697066735822122008d873a1af0917639a576cfd20b62ba3577963de02633ae84da0d902622855bc64736f6c63430008110033000000000000000000000000b20193f43c9a7184f3cbed9bad59154da01488b4000000000000000000000000a75c736478458b8d5871e00d2465173f565d819700000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000000000000000064

Deployed Bytecode

0x608060405260043610620000ef5760003560e01c8063b6d725201162000089578063db475d391162000060578063db475d3914620002e0578063f12baf5c14620002f7578063f5b7fe87146200032c578063f7581129146200034457600080fd5b8063b6d72520146200024d578063bc43cbaf1462000274578063cdd7b38a14620002aa57600080fd5b806362eda4cc11620000ca57806362eda4cc14620001975780636a306d6114620001cd5780636f2ddd9314620001f257806390a45519146200022857600080fd5b806346453be414620000f45780635c24feaf146200014b5780635e3119b61462000172575b600080fd5b3480156200010157600080fd5b506200012e6200011336600462001810565b6002602052600090815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156200015857600080fd5b50620001706200016a36600462001843565b6200036b565b005b3480156200017f57600080fd5b50620001706200019136600462001810565b62000495565b348015620001a457600080fd5b50620001bc620001b636600462001843565b62000557565b604051901515815260200162000142565b348015620001da57600080fd5b5062000170620001ec36600462001810565b6200056b565b348015620001ff57600080fd5b506200012e7f000000000000000000000000a75c736478458b8d5871e00d2465173f565d819781565b3480156200023557600080fd5b50620001706200024736600462001843565b62000629565b3480156200025a57600080fd5b506200026560035481565b60405190815260200162000142565b3480156200028157600080fd5b506200012e7f0000000000000000000000006d7c75d36931535c8cdbd2e5dbdc4644a61e1ee681565b348015620002b757600080fd5b506200012e7f000000000000000000000000d1ff8fd78f067ea2719730f5ca38d6da0bbd46c281565b6200012e620002f13660046200190e565b62000741565b3480156200030457600080fd5b507f000000000000000000000000b20193f43c9a7184f3cbed9bad59154da01488b46200012e565b3480156200033957600080fd5b506200026560045481565b3480156200035157600080fd5b506200035c62000cc9565b604051620001429190620019f2565b604051632474521560e21b8152600060048201819052336024830152907f0000000000000000000000006d7c75d36931535c8cdbd2e5dbdc4644a61e1ee66001600160a01b0316906391d1485490604401602060405180830381865afa158015620003da573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000400919062001a41565b6200041e57604051634ca8886760e01b815260040160405180910390fd5b6200042a308362000d08565b6200043760008362000ddb565b6200045557604051633e04f87160e01b815260040160405180910390fd5b6040516001600160a01b03831681527ef3e3a0acda02c27a13452e372b704b521e4d6fd4e6ef61c644630191da2793906020015b60405180910390a15050565b604051632474521560e21b8152600060048201819052336024830152907f0000000000000000000000006d7c75d36931535c8cdbd2e5dbdc4644a61e1ee66001600160a01b0316906391d1485490604401602060405180830381865afa15801562000504573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200052a919062001a41565b6200054857604051634ca8886760e01b815260040160405180910390fd5b620005538262000df9565b5050565b600062000565818362000e35565b92915050565b604051632474521560e21b8152600060048201819052336024830152907f0000000000000000000000006d7c75d36931535c8cdbd2e5dbdc4644a61e1ee66001600160a01b0316906391d1485490604401602060405180830381865afa158015620005da573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000600919062001a41565b6200061e57604051634ca8886760e01b815260040160405180910390fd5b620005538262000e58565b604051632474521560e21b8152600060048201819052336024830152907f0000000000000000000000006d7c75d36931535c8cdbd2e5dbdc4644a61e1ee66001600160a01b0316906391d1485490604401602060405180830381865afa15801562000698573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620006be919062001a41565b620006dc57604051634ca8886760e01b815260040160405180910390fd5b620006e960008362000e8e565b620007075760405163d3ed043d60e01b815260040160405180910390fd5b6040516001600160a01b03831681527f518600c6ec6d3c5988449de003934cd665d2567f8100ade182775e3006a6114b9060200162000489565b60006200076f7f643a332a1a6de98a36f48a7596dfafd4ea1b69babd8e98b38d6b2a652b59d09b3362000ea5565b6200078d57604051634ca8886760e01b815260040160405180910390fd5b620007b584604051806040016040528060048152602001631cd85b1d60e21b81525062000f3e565b620007c260008862000e35565b620007e057604051632711b74d60e11b815260040160405180910390fd5b6000620008176001600160a01b037f000000000000000000000000a75c736478458b8d5871e00d2465173f565d8197168662000f61565b90506000620008306001600160a01b038a168762000f61565b90506000867f000000000000000000000000b20193f43c9a7184f3cbed9bad59154da01488b47f000000000000000000000000b20193f43c9a7184f3cbed9bad59154da01488b46001600160a01b031663f543bb0e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620008b5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620008db919062001a65565b600354600454600188604051620008f29062001802565b6001600160a01b0396871681529486166020860152604085019390935260608401919091521515608083015290911660a082015260c0018190604051809103906000f59050801580156200094a573d6000803e3d6000fd5b509050620009826001600160a01b037f000000000000000000000000a75c736478458b8d5871e00d2465173f565d8197168862000fc2565b93506000846001600160a01b031663fc4680216040518163ffffffff1660e01b8152600401602060405180830381865afa158015620009c5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620009eb919062001a85565b905060007f000000000000000000000000b20193f43c9a7184f3cbed9bad59154da01488b46001600160a01b0316633fc8cef36040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000a4e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000a74919062001a65565b905081341462000a9e5760405163052da6dd60e31b81523460048201526024015b60405180910390fd5b806001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b15801562000ada57600080fd5b505af115801562000aef573d6000803e3d6000fd5b505050505062000b0181878462001061565b60405163267eb9ed60e01b81526001600160a01b0387169063267eb9ed9062000b379087908f908f908e908e9060040162001af3565b600060405180830381600087803b15801562000b5257600080fd5b505af115801562000b67573d6000803e3d6000fd5b5050604051630e9918b960e21b81526001600160a01b03868116600483015289169250633a6462e49150602401600060405180830381600087803b15801562000baf57600080fd5b505af115801562000bc4573d6000803e3d6000fd5b5062000bde925050506001600160a01b038d168a62000fc2565b60405163189acdbd60e31b81526001600160a01b038881166004830152919091169063c4d66de890602401600060405180830381600087803b15801562000c2457600080fd5b505af115801562000c39573d6000803e3d6000fd5b50506040516312b5ad0160e11b81526001600160a01b0389811660048301527f000000000000000000000000d1ff8fd78f067ea2719730f5ca38d6da0bbd46c216925063256b5a029150602401600060405180830381600087803b15801562000ca157600080fd5b505af115801562000cb6573d6000803e3d6000fd5b5050505050505050509695505050505050565b606062000cd7600062001113565b905090565b6001600160a01b03821662000553578060405163eac0d38960e01b815260040162000a95919062001b64565b6040805160048152602481019091526020810180516001600160e01b0316633c4aebd760e21b179052600062000d486001600160a01b0385168362001122565b80602001905181019062000d5d919062001a65565b9050600062000d766001600160a01b0385168462001122565b80602001905181019062000d8b919062001a65565b9050806001600160a01b0316826001600160a01b03161462000dd457604051632f6b3b6360e01b81526001600160a01b0380871660048301528516602482015260440162000a95565b5050505050565b600062000df2836001600160a01b0384166200114a565b9392505050565b60048190556040518181527fd9798cd6c6dd9262c7ed2bbcb3156e1141a18884539118c3431475c553fc97eb906020015b60405180910390a150565b6001600160a01b0381166000908152600183016020526040812054151562000df2565b60038190556040518181527fc5bd932f8a6237639f4f95a2dfd064cda654f71b0e54ec3122fbc4e88f32734b9060200162000e2a565b600062000df2836001600160a01b0384166200119c565b604051632474521560e21b8152600481018390526001600160a01b0382811660248301526000917f0000000000000000000000006d7c75d36931535c8cdbd2e5dbdc4644a61e1ee6909116906391d1485490604401602060405180830381865afa15801562000f18573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000df2919062001a41565b81620005535780604051634389d5ab60e01b815260040162000a95919062001b64565b6040513060388201526f5af43d82803e903d91602b57fd5bf3ff602482015260148101839052733d602d80600a3d3981f3363d3d373d3d3d363d738152605881018290526037600c8201206078820152605560439091012060009062000df2565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b038116620005655760405162461bcd60e51b815260206004820152601760248201527f455243313136373a2063726561746532206661696c6564000000000000000000604482015260640162000a95565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301526000919085169063dd62ed3e90604401602060405180830381865afa158015620010b2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620010d8919062001a85565b90508015620010f757620010f76001600160a01b0385168483620012a0565b6200110d6001600160a01b0385168484620013e6565b50505050565b6060600062000df2836200149f565b606062000df2838360405180606001604052806025815260200162004c9460259139620014fd565b6000818152600183016020526040812054620011935750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915562000565565b50600062000565565b6000818152600183016020526040812054801562001295576000620011c360018362001b8f565b8554909150600090620011d99060019062001b8f565b905081811462001245576000866000018281548110620011fd57620011fd62001ba5565b906000526020600020015490508087600001848154811062001223576200122362001ba5565b6000918252602080832090910192909255918252600188019052604090208390555b855486908062001259576200125962001bbb565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505062000565565b600091505062000565565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301526000919085169063dd62ed3e90604401602060405180830381865afa158015620012f1573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062001317919062001a85565b9050818110156200137d5760405162461bcd60e51b815260206004820152602960248201527f5361666545524332303a2064656372656173656420616c6c6f77616e63652062604482015268656c6f77207a65726f60b81b606482015260840162000a95565b6040516001600160a01b0384166024820152828203604482018190529062000dd490869063095ea7b360e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526200157b565b604051636eb1769f60e11b81523060048201526001600160a01b038381166024830152600091839186169063dd62ed3e90604401602060405180830381865afa15801562001438573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200145e919062001a85565b6200146a919062001bd1565b6040516001600160a01b0385166024820152604481018290529091506200110d90859063095ea7b360e01b90606401620013ae565b606081600001805480602002602001604051908101604052809291908181526020018280548015620014f157602002820191906000526020600020905b815481526020019060010190808311620014dc575b50505050509050919050565b6060600080856001600160a01b0316856040516200151c919062001be7565b600060405180830381855afa9150503d806000811462001559576040519150601f19603f3d011682016040523d82523d6000602084013e6200155e565b606091505b5091509150620015718683838762001659565b9695505050505050565b6000620015d2826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316620016e19092919063ffffffff16565b805190915015620016545780806020019051810190620015f3919062001a41565b620016545760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840162000a95565b505050565b60608315620016cd578251600003620016c5576001600160a01b0385163b620016c55760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640162000a95565b5081620016d9565b620016d98383620016f2565b949350505050565b6060620016d984846000856200171f565b815115620017035781518083602001fd5b8060405162461bcd60e51b815260040162000a95919062001b64565b606082471015620017825760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840162000a95565b600080866001600160a01b03168587604051620017a0919062001be7565b60006040518083038185875af1925050503d8060008114620017df576040519150601f19603f3d011682016040523d82523d6000602084013e620017e4565b606091505b5091509150620017f78783838762001659565b979650505050505050565b61308e8062001c0683390190565b6000602082840312156200182357600080fd5b5035919050565b6001600160a01b03811681146200184057600080fd5b50565b6000602082840312156200185657600080fd5b813562000df2816200182a565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200188b57600080fd5b813567ffffffffffffffff80821115620018a957620018a962001863565b604051601f8301601f19908116603f01168101908282118183101715620018d457620018d462001863565b81604052838152866020858801011115620018ee57600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060008060008060a087890312156200192857600080fd5b863562001935816200182a565b9550602087013567ffffffffffffffff808211156200195357600080fd5b620019618a838b0162001879565b965060408901359150808211156200197857600080fd5b620019868a838b0162001879565b9550606089013594506080890135915080821115620019a457600080fd5b818901915089601f830112620019b957600080fd5b813581811115620019c957600080fd5b8a6020828501011115620019dc57600080fd5b6020830194508093505050509295509295509295565b6020808252825182820181905260009190848201906040850190845b8181101562001a355783516001600160a01b03168352928401929184019160010162001a0e565b50909695505050505050565b60006020828403121562001a5457600080fd5b8151801515811462000df257600080fd5b60006020828403121562001a7857600080fd5b815162000df2816200182a565b60006020828403121562001a9857600080fd5b5051919050565b60005b8381101562001abc57818101518382015260200162001aa2565b50506000910152565b6000815180845262001adf81602086016020860162001a9f565b601f01601f19169290920160200192915050565b6001600160a01b038616815260806020820181905260009062001b199083018762001ac5565b828103604084015262001b2d818762001ac5565b90508281036060840152838152838560208301376000602085830101526020601f19601f8601168201019150509695505050505050565b60208152600062000df2602083018462001ac5565b634e487b7160e01b600052601160045260246000fd5b8181038181111562000565576200056562001b79565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052603160045260246000fd5b8082018082111562000565576200056562001b79565b6000825162001bfb81846020870162001a9f565b919091019291505056fe6101806040523480156200001257600080fd5b506040516200308e3803806200308e8339810160408190526200003591620003d0565b858585857ffde2f69a846a71295e563d91ade82dc70e9eda278403d1aece24d0ded949403a868585858585846001600160a01b031663bc43cbaf6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200009f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620000c5919062000449565b6001600160a01b038116620000ed57604051630cbe126f60e11b815260040160405180910390fd5b6001600160a01b031660805260408051808201909152600c81526b2fb932bbb0b9322a37b5b2b760a11b60208083019190915262000136918691620014ac62000328821b17901c565b62000175826040518060400160405280601081526020016f5f6475726174696f6e496e426c6f636b60801b8152506200035860201b620014de1760201c565b620001b3836040518060400160405280600f81526020016e5f6e6577526577617264526174696f60881b8152506200035860201b620014de1760201c565b620001ed816040518060400160405280600b81526020016a5f726577617264526f6c6560a81b8152506200037e60201b620015011760201c565b6001600160a01b0385811660e08190526040516316bfae7f60e31b815291861660048301529063b5fd73f890602401602060405180830381865afa1580156200023a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000260919062000470565b620002a257604051634389d5ab60e01b815260206004820152600c60248201526b2fb932bbb0b9322a37b5b2b760a11b60448201526064015b60405180910390fd5b6001600160a01b039093166101005260c09190915260a05261012052506001600b55151561014052505060408051808201909152600d81526c2fb9ba30b5b4b733aa37b5b2b760991b6020828101919091526200030f9450859350909150620014ac62000328821b17901c565b6001600160a01b03166101605250620004de9350505050565b6001600160a01b03821662000354578060405163eac0d38960e01b81526004016200029991906200048e565b5050565b81600003620003545780604051634389d5ab60e01b81526004016200029991906200048e565b81620003545780604051634389d5ab60e01b81526004016200029991906200048e565b6001600160a01b0381168114620003b757600080fd5b50565b80518015158114620003cb57600080fd5b919050565b60008060008060008060c08789031215620003ea57600080fd5b8651620003f781620003a1565b60208801519096506200040a81620003a1565b60408801516060890151919650945092506200042960808801620003ba565b915060a08701516200043b81620003a1565b809150509295509295509295565b6000602082840312156200045c57600080fd5b81516200046981620003a1565b9392505050565b6000602082840312156200048357600080fd5b6200046982620003ba565b600060208083528351808285015260005b81811015620004bd578581018301518582016040015282016200049f565b506000604082860101526040601f19601f8301168501019250505092915050565b60805160a05160c05160e05161010051610120516101405161016051612a8d62000601600039600081816103cd01528181610fce01526114850152600081816104880152610cb10152600081816105f40152818161074e01528181610bf001528181610e5d01528181611005015261122801526000818161054601528181610bb401528181611e820152611f1e015260008181610818015281816108bf01528181610d8c015281816113b901528181611d620152611de40152600081816103610152610b210152600081816102ce01528181610aba0152818161184a01526118a20152600081816104af015281816106230152818161077d01528181610c1f01528181610e8c015281816110340152818161125701526117350152612a8d6000f3fe608060405234801561001057600080fd5b50600436106102265760003560e01c80637b0a47ee11610130578063bc43cbaf116100b8578063e21c81d31161007c578063e21c81d3146104f3578063e43252d714610508578063e665b4141461051b578063ead5d3591461052e578063f7c618c11461054157600080fd5b8063bc43cbaf146104aa578063cd3daf9d146104d1578063cd8e33d4146104d9578063d55a23f4146104e2578063df136d65146104ea57600080fd5b8063a218141b116100ff578063a218141b1461044c578063abe0429c14610455578063adc9772e1461045d578063aec9814c14610470578063b263487f1461048357600080fd5b80637b0a47ee146104075780638ab1d681146104105780638b87634714610423578063901a7d531461044357600080fd5b80633d18b912116101b35780636c8bcee8116101825780636c8bcee81461035c5780636f73a38f146103835780637050ccd91461038c57806370a082311461039f57806372f702f3146103c857600080fd5b80633d18b91214610325578063590a41f51461032d5780635e43c47b1461034057806363d38c3b1461035357600080fd5b806314d09249116101fa57806314d09249146102ae57806318160ddd146102c15780631fc93059146102c9578063262d3d6d146102f05780633af32abf146102f957600080fd5b80628cc2621461022b5780630569d3881461025157806306c933d81461025b5780630700037d1461028e575b600080fd5b61023e61023936600461272a565b610568565b6040519081526020015b60405180910390f35b6102596105e5565b005b61027e61026936600461272a565b600a6020526000908152604090205460ff1681565b6040519015158152602001610248565b61023e61029c36600461272a565b60086020526000908152604090205481565b6102596102bc366004612747565b61073f565b600e5461023e565b61023e7f000000000000000000000000000000000000000000000000000000000000000081565b61023e60065481565b61027e61030736600461272a565b6001600160a01b03166000908152600a602052604090205460ff1690565b6102596109fd565b61025961033b366004612747565b610a25565b61025961034e36600461272a565b610be1565b61023e60045481565b61023e7f000000000000000000000000000000000000000000000000000000000000000081565b61023e60095481565b61025961039a36600461276e565b610d76565b61023e6103ad36600461272a565b6001600160a01b03166000908152600f602052604090205490565b6103ef7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610248565b61023e60015481565b61025961041e36600461272a565b610e4e565b61023e61043136600461272a565b60076020526000908152604090205481565b61023e60055481565b61023e60025481565b61023e610f9f565b61025961046b3660046127a7565b610fb7565b61025961047e3660046127d3565b610ff6565b61027e7f000000000000000000000000000000000000000000000000000000000000000081565b6103ef7f000000000000000000000000000000000000000000000000000000000000000081565b61023e611191565b61023e60005481565b61023e611201565b61023e60035481565b6104fb61120d565b6040516102489190612848565b61025961051636600461272a565b611219565b6103ef610529366004612747565b611396565b61025961053c366004612895565b6113a3565b6103ef7f000000000000000000000000000000000000000000000000000000000000000081565b6001600160a01b0381166000908152600860209081526040808320546007909252822054670de0b6b3a76400009061059e611191565b6105a891906128ed565b6001600160a01b0385166000908152600f60205260409020546105cb9190612900565b6105d59190612917565b6105df9190612939565b92915050565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610672573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610696919061294c565b6106b357604051634ca8886760e01b815260040160405180910390fd5b60006106bf600c611521565b1115610713576106f16106e960016106d7600c611521565b6106e191906128ed565b600c9061152b565b600c9061153e565b61070e5760405163d3ed043d60e01b815260040160405180910390fd5b6106b3565b6040517f1a53a10dfbef82dfcf34ce946c284b478164f6e7496a07fc0cbe69d01ff44f0990600090a150565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa1580156107cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107f0919061294c565b61080d57604051634ca8886760e01b815260040160405180910390fd5b81156109c0576108bd7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663024d381b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610874573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108989190612969565b60405180604001604052806007815260200166616363546f6b6560c81b8152506114ac565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663024d381b6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561091b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061093f9190612969565b6001600160a01b0316635fec5c646040518163ffffffff1660e01b8152600401602060405180830381865afa15801561097c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109a09190612986565b8210156109c05760405163bcec4c5360e01b815260040160405180910390fd5b60098290556040518281527f62ff17080925adbeb90d914efe5f615d059f19c2e728740b500d1e84ba65989a906020015b60405180910390a15050565b610a05611553565b610a0e336115ac565b610a19336001611665565b610a236001600b55565b565b336000908152600a602052604090205460ff16158015610a6c5750610a6a7f5e17fc5225d4a099df75359ce1f405503ca79498a8dc46a7d583235a0ee45c163361170c565b155b15610a8a57604051634ca8886760e01b815260040160405180910390fd5b60045481610a988282612939565b92506000544310610ab657610aac836117a2565b6000600455610b64565b60007f0000000000000000000000000000000000000000000000000000000000000000600054610ae691906128ed565b610af090436128ed565b9050600081600154610b029190612900565b9050600085610b13836103e8612900565b610b1d9190612917565b90507f0000000000000000000000000000000000000000000000000000000000000000811015610b5a57610b50866117a2565b6000600455610b60565b60048690555b5050505b600454604080518481526020810184905280820192909252517fe4a19739e7048ef5e90c7a157e8fb37a6e90cb8de298625227540d2443b9769c9181900360600190a1610bdc6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633308461192a565b505050565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610c6e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c92919061294c565b610caf57604051634ca8886760e01b815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000000610ced57604051639fe610b960e01b815260040160405180910390fd5b610d1582604051806040016040528060068152602001651c995dd85c9960d21b8152506114ac565b610d20600c83611995565b610d3d57604051633e04f87160e01b815260040160405180910390fd5b6040516001600160a01b03831681527fd432e6f46dbf91c120fdfa95a1f4bf5c43f04d957fbc3a32e693be0d29bf17b0906020016109f1565b336001600160a01b03831614801590610e2257507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166330d960af6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610de8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e0c9190612969565b6001600160a01b0316336001600160a01b031614155b15610e4057604051634ca8886760e01b815260040160405180910390fd5b610e4a82826119aa565b5050565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015610edb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610eff919061294c565b610f1c57604051634ca8886760e01b815260040160405180910390fd5b6001600160a01b0382166000908152600a602052604090205460ff16610f555760405163d3ed043d60e01b815260040160405180910390fd5b6001600160a01b0382166000818152600a6020526040808220805460ff19169055517fcdd2e9b91a56913d370075169cefa1602ba36be5301664f752192bb1709df7579190a25050565b600080544310610fb0575060005490565b435b905090565b610fc182826119cf565b610e4a6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633308461192a565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa158015611083573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110a7919061294c565b6110c457604051634ca8886760e01b815260040160405180910390fd5b8160005b8181101561118a576110fa8585838181106110e5576110e561299f565b90506020020160208101906106e9919061272a565b6111175760405163d3ed043d60e01b815260040160405180910390fd5b7fe0f3b1406ab4dd940a92a081b1bcef51da212a57978cc8f6dde5c89ab41a5aa785858381811061114a5761114a61299f565b905060200201602081019061115f919061272a565b6040516001600160a01b03909116815260200160405180910390a1611183816129b5565b90506110c8565b5050505050565b60008061119d600e5490565b9050806000036111af57505060035490565b806001546002546111be610f9f565b6111c891906128ed565b6111d29190612900565b6111e490670de0b6b3a7640000612900565b6111ee9190612917565b6003546111fb9190612939565b91505090565b6000610fb2600c611521565b6060610fb2600c611ac5565b604051632474521560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201819052336024830152907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906391d1485490604401602060405180830381865afa1580156112a6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112ca919061294c565b6112e757604051634ca8886760e01b815260040160405180910390fd5b61130f82604051806040016040528060068152602001651dd85b1b195d60d21b8152506114ac565b6001600160a01b0382166000908152600a602052604090205460ff161561134957604051633e04f87160e01b815260040160405180910390fd5b6001600160a01b0382166000818152600a6020526040808220805460ff19166001179055517fa850ae9193f515cbae8d35e8925bd2be26627fc91bce650b8652ed254e9cab039190a25050565b60006105df600c8361152b565b336001600160a01b0384161480159061144f57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166330d960af6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611415573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114399190612969565b6001600160a01b0316336001600160a01b031614155b1561146d57604051634ca8886760e01b815260040160405180910390fd5b611478838383611ad2565b610bdc6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168484611bda565b6001600160a01b038216610e4a578060405163eac0d38960e01b81526004016114d591906129f2565b60405180910390fd5b81600003610e4a5780604051634389d5ab60e01b81526004016114d591906129f2565b81610e4a5780604051634389d5ab60e01b81526004016114d591906129f2565b60006105df825490565b60006115378383611c0a565b9392505050565b6000611537836001600160a01b038416611c34565b6002600b54036115a55760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064016114d5565b6002600b55565b60006115b6611191565b600381905515611610576001600160a01b03821615611610576115d7610f9f565b6002556115e382610568565b6001600160a01b038316600090815260086020908152604080832084905560035460079092529091205590505b6003546002546040805184815260208101939093528201526001600160a01b038316907f469d38647ec007a9c93421468c92550d50fccc01ae12e149b1216aa9b0136fc7906060015b60405180910390a25050565b61166e82611d27565b600061167a600c611521565b90508115610bdc5760005b8181101561170657611698600c8261152b565b604051630c00007b60e41b81526001600160a01b038681166004830152919091169063c00007b090602401600060405180830381600087803b1580156116dd57600080fd5b505af11580156116f1573d6000803e3d6000fd5b50505050806116ff906129b5565b9050611685565b50505050565b604051632474521560e21b8152600481018390526001600160a01b0382811660248301526000917f0000000000000000000000000000000000000000000000000000000000000000909116906391d1485490604401602060405180830381865afa15801561177e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611537919061294c565b80600660008282546117b49190612939565b9091555050600e546000036117fe5760005460025410156117f9576001546002546000546117e291906128ed565b6117ec9190612900565b6117f69082612939565b90505b61183b565b60005443101561183b5760004360005461181891906128ed565b905060006001548261182a9190612900565b90506118368184612939565b925050505b61184560006115ac565b61186f7f000000000000000000000000000000000000000000000000000000000000000082612917565b600181905561189157604051631f2a200560e01b815260040160405180910390fd5b60058190554360028190556118c7907f000000000000000000000000000000000000000000000000000000000000000090612939565b6000819055600154600254600654604080518681526020810194909452830191909152606082019290925260808101919091527f8ce8cbe5f803930b0c6afe4640018bbfb02cbb5b0bfbe051b25a155201e80dac9060a00160405180910390a150565b6040516001600160a01b03808516602483015283166044820152606481018290526117069085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b03199093169290921790915261208b565b6000611537836001600160a01b03841661215d565b6119b2611553565b6119bb826115ac565b6119c58282611665565b610e4a6001600b55565b6119d8826115ac565b6119e282826121ac565b60006119ee600c611521565b905060005b81811015611a7b57611a06600c8261152b565b6040516356e4bb9760e11b81526001600160a01b03868116600483015260248201869052919091169063adc9772e90604401600060405180830381600087803b158015611a5257600080fd5b505af1158015611a66573d6000803e3d6000fd5b5050505080611a74906129b5565b90506119f3565b5081600e6000828254611a8e9190612939565b90915550506001600160a01b0383166000908152600f602052604081208054849290611abb908490612939565b9091555050505050565b6060600061153783612238565b611adb836115ac565b611ae58383612294565b6000611af1600c611521565b905060005b81811015611b7e57611b09600c8261152b565b60405163f3fef3a360e01b81526001600160a01b03878116600483015260248201879052919091169063f3fef3a390604401600060405180830381600087803b158015611b5557600080fd5b505af1158015611b69573d6000803e3d6000fd5b5050505080611b77906129b5565b9050611af6565b508115611b9057611b90846001611665565b82600e6000828254611ba291906128ed565b90915550506001600160a01b0384166000908152600f602052604081208054859290611bcf9084906128ed565b909155505050505050565b6040516001600160a01b038316602482015260448101829052610bdc90849063a9059cbb60e01b9060640161195e565b6000826000018281548110611c2157611c2161299f565b9060005260206000200154905092915050565b60008181526001830160205260408120548015611d1d576000611c586001836128ed565b8554909150600090611c6c906001906128ed565b9050818114611cd1576000866000018281548110611c8c57611c8c61299f565b9060005260206000200154905080876000018481548110611caf57611caf61299f565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080611ce257611ce2612a25565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506105df565b60009150506105df565b611d5081604051806040016040528060078152602001661858d8dbdd5b9d60ca1b8152506114ac565b6000611d5b82610568565b90506000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663024d381b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611dbe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611de29190612969565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663f543bb0e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e649190612969565b9150915082600003611e765750505050565b806001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316141580611eb75750600954155b15611f4a576001600160a01b03841660008181526008602052604080822091909155517fe2403640ba68fed3a2f88b7557551d1993f84b99bb10ff833f0cf8db0c5e048690611f099086815260200190565b60405180910390a2611f456001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168585611bda565b611706565b604051633f6e925b60e01b8152600481018490526001600160a01b03831690633f6e925b90602401602060405180830381865afa158015611f8f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611fb3919061294c565b15611706576001600160a01b03841660008181526008602052604080822091909155517fe2403640ba68fed3a2f88b7557551d1993f84b99bb10ff833f0cf8db0c5e0486906120059086815260200190565b60405180910390a2612018818385612320565b600954604051637628a37d60e01b81526004810185905260248101919091526001600160a01b038581166044830152831690637628a37d90606401600060405180830381600087803b15801561206d57600080fd5b505af1158015612081573d6000803e3d6000fd5b5050505050505050565b60006120e0826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166123c49092919063ffffffff16565b805190915015610bdc57808060200190518101906120fe919061294c565b610bdc5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016114d5565b60008181526001830160205260408120546121a4575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556105df565b5060006105df565b6121d582604051806040016040528060078152602001661858d8dbdd5b9d60ca1b8152506114ac565b6121fd8160405180604001604052806006815260200165185b5bdd5b9d60d21b8152506114de565b816001600160a01b03167f9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d8260405161165991815260200190565b60608160000180548060200260200160405190810160405280929190818152602001828054801561228857602002820191906000526020600020905b815481526020019060010190808311612274575b50505050509050919050565b6122bd82604051806040016040528060078152602001661858d8dbdd5b9d60ca1b8152506114ac565b6122e58160405180604001604052806006815260200165185b5bdd5b9d60d21b8152506114de565b816001600160a01b03167f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d58260405161165991815260200190565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301526000919085169063dd62ed3e90604401602060405180830381865afa158015612370573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123949190612986565b905080156123b0576123b06001600160a01b03851684836123db565b6117066001600160a01b03851684846124e7565b60606123d38484600085612599565b949350505050565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301526000919085169063dd62ed3e90604401602060405180830381865afa15801561242b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061244f9190612986565b9050818110156124b35760405162461bcd60e51b815260206004820152602960248201527f5361666545524332303a2064656372656173656420616c6c6f77616e63652062604482015268656c6f77207a65726f60b81b60648201526084016114d5565b6040516001600160a01b0384166024820152828203604482018190529061118a90869063095ea7b360e01b9060640161195e565b604051636eb1769f60e11b81523060048201526001600160a01b038381166024830152600091839186169063dd62ed3e90604401602060405180830381865afa158015612538573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061255c9190612986565b6125669190612939565b6040516001600160a01b03851660248201526044810182905290915061170690859063095ea7b360e01b9060640161195e565b6060824710156125fa5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016114d5565b600080866001600160a01b031685876040516126169190612a3b565b60006040518083038185875af1925050503d8060008114612653576040519150601f19603f3d011682016040523d82523d6000602084013e612658565b606091505b509150915061266987838387612674565b979650505050505050565b606083156126e35782516000036126dc576001600160a01b0385163b6126dc5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016114d5565b50816123d3565b6123d383838151156126f85781518083602001fd5b8060405162461bcd60e51b81526004016114d591906129f2565b6001600160a01b038116811461272757600080fd5b50565b60006020828403121561273c57600080fd5b813561153781612712565b60006020828403121561275957600080fd5b5035919050565b801515811461272757600080fd5b6000806040838503121561278157600080fd5b823561278c81612712565b9150602083013561279c81612760565b809150509250929050565b600080604083850312156127ba57600080fd5b82356127c581612712565b946020939093013593505050565b600080602083850312156127e657600080fd5b823567ffffffffffffffff808211156127fe57600080fd5b818501915085601f83011261281257600080fd5b81358181111561282157600080fd5b8660208260051b850101111561283657600080fd5b60209290920196919550909350505050565b6020808252825182820181905260009190848201906040850190845b818110156128895783516001600160a01b031683529284019291840191600101612864565b50909695505050505050565b6000806000606084860312156128aa57600080fd5b83356128b581612712565b92506020840135915060408401356128cc81612760565b809150509250925092565b634e487b7160e01b600052601160045260246000fd5b818103818111156105df576105df6128d7565b80820281158282048414176105df576105df6128d7565b60008261293457634e487b7160e01b600052601260045260246000fd5b500490565b808201808211156105df576105df6128d7565b60006020828403121561295e57600080fd5b815161153781612760565b60006020828403121561297b57600080fd5b815161153781612712565b60006020828403121561299857600080fd5b5051919050565b634e487b7160e01b600052603260045260246000fd5b6000600182016129c7576129c76128d7565b5060010190565b60005b838110156129e95781810151838201526020016129d1565b50506000910152565b6020815260008251806020840152612a118160408501602087016129ce565b601f01601f19169190910160400192915050565b634e487b7160e01b600052603160045260246000fd5b60008251612a4d8184602087016129ce565b919091019291505056fea2646970667358221220daa62ee8e068827eea5e5aa0e855ce0a9b17ee160a6870c6be36c6a73abdb77d64736f6c63430008110033416464726573733a206c6f772d6c6576656c207374617469632063616c6c206661696c6564a264697066735822122008d873a1af0917639a576cfd20b62ba3577963de02633ae84da0d902622855bc64736f6c63430008110033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000b20193f43c9a7184f3cbed9bad59154da01488b4000000000000000000000000a75c736478458b8d5871e00d2465173f565d819700000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000000000000000064

-----Decoded View---------------
Arg [0] : _systemRegistry (address): 0xB20193f43C9a7184F3cbeD9bAD59154da01488b4
Arg [1] : _template (address): 0xa75C736478458b8D5871E00D2465173F565D8197
Arg [2] : _defaultRewardRatio (uint256): 800
Arg [3] : _defaultRewardBlockDuration (uint256): 100

-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 000000000000000000000000b20193f43c9a7184f3cbed9bad59154da01488b4
Arg [1] : 000000000000000000000000a75c736478458b8d5871e00d2465173f565d8197
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000320
Arg [3] : 0000000000000000000000000000000000000000000000000000000000000064


Block Transaction Difficulty 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
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.