ETH Price: $3,118.49 (+1.74%)

Contract

0x56a20C5D76d41338fBd5D7cB7Db37e937143f910
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
0x61014060200898462024-06-14 11:48:23154 days ago1718365703IN
 Create: REthHoldingStrategy
0 ETH0.0588390711.30115499

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
REthHoldingStrategy

Compiler Version
v0.8.17+commit.8df45f5f

Optimization Enabled:
Yes with 99999 runs

Other Settings:
london EvmVersion
File 1 of 32 : REthHoldingStrategy.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

import "@openzeppelin/token/ERC20/IERC20.sol";
import "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import "../external/interfaces/strategies/rEth/IREthToken.sol";
import "../external/interfaces/strategies/rEth/IRocketSwapRouter.sol";
import "../libraries/PackedRange.sol";
import "./Strategy.sol";
import "./helpers/WethHelper.sol";

error REthHoldingBeforeDepositCheckFailed();
error REthHoldingBeforeRedeemalCheckFailed();
error REthHoldingDepositSlippagesFailed();
error REthHoldingRedeemSlippagesFailed();

// one asset
// no rewards
// slippages
// - mode selection: slippages[0]
// - DHW with deposit: slippages[0] == 0
//   - beforeDepositCheck: slippages[1]
//   - beforeRedeemalCheck: slippages[2]
//   - _depositToProtocol: slippages[3..6]
// - DHW with withdrawal: slippages[0] == 1
//   - beforeDepositCheck: slippages[1]
//   - beforeRedeemalCheck: slippages[2]
//   - _redeemFromProtocol: slippages[3..6]
// - reallocate: slippages[0] == 2
//   - beforeDepositCheck: depositSlippages[1]
//   - _depositToProtocol: depositSlippages[2..5]
//   - beforeRedeemalCheck: withdrawalSlippages[1]
//   - _redeemFromProtocol: withdrawalSlippages[2..5]
// - redeemFast or emergencyWithdraw: slippages[0] == 3
//   - _redeemFromProtocol or _emergencyWithdrawImpl: slippages[1..4]
// - deposit and withdrawals from protocol require four slippages:
//   - 1: portion to swap on uniswap
//   - 2: portion to swap on balancer
//   - 3: min out
//   - 4: ideal out
// Description:
// This is a liquid staking derivative strategy where eth is staked with Rocket
// pool to be used for spinning up validators. Users staking share is
// represented by rETH. The value of rETH compared to eth is growing with
// validation rewards collected by validators spinned up using the staked eth.
// The strategy uses the Rocket swap router to buy and sell rETH for eth. The
// swap calls have four parameters that control how exactly the swap takes
// place; portion to swap on Uniswap, portion to swap on Balancer, minimal out
// and ideal out. If ideal out is less then or equal to the internal rETH/eth
// price, the router will swap as much as possible internally. What cannot be
// swapped internally, will be swapped on Uniswap and Balancer, based on
// portions specified. Note that the portions are arbitrary numbers, what
// matters is just their ratio. At the end, the swapper checks if final swapped
// amount is at least min out, otherwise the transaction reverts. These
// parameters are passed in as slippages (see above for details).
contract REthHoldingStrategy is Strategy, WethHelper {
    using SafeERC20 for IERC20;

    IRocketSwapRouter public immutable rocketSwapRouter;
    IREthToken public immutable rEthToken;

    uint256 private _lastSharePrice;

    constructor(
        IAssetGroupRegistry assetGroupRegistry_,
        ISpoolAccessControl accessControl_,
        uint256 assetGroupId_,
        IRocketSwapRouter rocketSwapRouter_,
        address weth_
    ) Strategy(assetGroupRegistry_, accessControl_, assetGroupId_) WethHelper(weth_) {
        if (address(rocketSwapRouter_) == address(0)) {
            revert ConfigurationAddressZero();
        }

        rocketSwapRouter = rocketSwapRouter_;

        rEthToken = IREthToken(rocketSwapRouter_.rETH());
    }

    function initialize(string memory strategyName_) external initializer {
        __Strategy_init(strategyName_, NULL_ASSET_GROUP_ID);

        address[] memory tokens = _assetGroupRegistry.listAssetGroup(assetGroupId());

        if (tokens.length != 1 || tokens[0] != weth) {
            revert InvalidAssetGroup(assetGroupId());
        }

        _lastSharePrice = _getSharePrice();
    }

    function assetRatio() external pure override returns (uint256[] memory) {
        uint256[] memory _assetRatio = new uint256[](1);
        _assetRatio[0] = 1;
        return _assetRatio;
    }

    function getUnderlyingAssetAmounts() external view returns (uint256[] memory amounts) {
        amounts = new uint256[](1);
        amounts[0] = rEthToken.getEthValue(rEthToken.balanceOf(address(this)));
    }

    function beforeDepositCheck(uint256[] memory amounts, uint256[] calldata slippages) public override {
        if (_isViewExecution()) {
            uint256[] memory beforeDepositCheckSlippageAmounts = new uint256[](1);
            beforeDepositCheckSlippageAmounts[0] = amounts[0];

            emit BeforeDepositCheckSlippages(beforeDepositCheckSlippageAmounts);
            return;
        }

        if (slippages[0] > 2) {
            revert REthHoldingBeforeDepositCheckFailed();
        }

        if (!PackedRange.isWithinRange(slippages[1], amounts[0])) {
            revert REthHoldingBeforeDepositCheckFailed();
        }
    }

    function beforeRedeemalCheck(uint256 ssts, uint256[] calldata slippages) public override {
        if (_isViewExecution()) {
            emit BeforeRedeemalCheckSlippages(ssts);
            return;
        }

        uint256 slippage;
        if (slippages[0] < 2) {
            slippage = slippages[2];
        } else if (slippages[0] == 2) {
            slippage = slippages[1];
        } else {
            revert REthHoldingBeforeRedeemalCheckFailed();
        }

        if (!PackedRange.isWithinRange(slippage, ssts)) {
            revert REthHoldingBeforeRedeemalCheckFailed();
        }
    }

    function _depositToProtocol(address[] calldata, uint256[] memory amounts, uint256[] calldata slippages)
        internal
        override
    {
        uint256 slippageOffset;
        if (slippages[0] == 0) {
            slippageOffset = 3;
        } else if (slippages[0] == 2) {
            slippageOffset = 2;
        } else {
            revert REthHoldingDepositSlippagesFailed();
        }

        _depositInternal(amounts[0], slippages, slippageOffset);
    }

    function _redeemFromProtocol(address[] calldata, uint256 ssts, uint256[] calldata slippages) internal override {
        uint256 slippageOffset;
        if (slippages[0] == 1) {
            slippageOffset = 3;
        } else if (slippages[0] == 2) {
            slippageOffset = 2;
        } else if (slippages[0] == 3) {
            slippageOffset = 1;
        } else if (slippages[0] == 0 && _isViewExecution()) {
            slippageOffset = 3;
        } else {
            revert REthHoldingRedeemSlippagesFailed();
        }

        uint256 sharesToRedeem = rEthToken.balanceOf(address(this)) * ssts / totalSupply();
        _redeemInternal(sharesToRedeem, slippages, slippageOffset);
    }

    function _emergencyWithdrawImpl(uint256[] calldata slippages, address recipient) internal override {
        if (slippages[0] != 3) {
            revert REthHoldingRedeemSlippagesFailed();
        }

        uint256 bought = _redeemInternal(rEthToken.balanceOf(address(this)), slippages, 1);

        IERC20(weth).safeTransfer(recipient, bought);
    }

    function _compound(address[] calldata, SwapInfo[] calldata, uint256[] calldata)
        internal
        pure
        override
        returns (int256)
    {}

    function _getYieldPercentage(int256) internal override returns (int256 baseYieldPercentage) {
        uint256 currentSharePrice = _getSharePrice();

        baseYieldPercentage = _calculateYieldPercentage(_lastSharePrice, currentSharePrice);

        _lastSharePrice = currentSharePrice;
    }

    function _swapAssets(address[] memory, uint256[] memory, SwapInfo[] calldata) internal override {}

    function _getUsdWorth(uint256[] memory exchangeRates, IUsdPriceFeedManager priceFeedManager)
        internal
        view
        override
        returns (uint256)
    {
        uint256 assets = rEthToken.getEthValue(rEthToken.balanceOf(address(this)));

        return priceFeedManager.assetToUsdCustomPrice(weth, assets, exchangeRates[0]);
    }

    function _depositInternal(uint256 amount, uint256[] memory slippages, uint256 startingSlippage)
        private
        returns (uint256 bought)
    {
        if (_isViewExecution()) {
            // optimize first
            (uint256[2] memory portions, uint256 amountOut) = rocketSwapRouter.optimiseSwapTo(amount, 10);

            slippages[startingSlippage] = portions[0];
            slippages[startingSlippage + 1] = portions[1];
            slippages[startingSlippage + 2] = amountOut;
            slippages[startingSlippage + 3] = amountOut;

            uint256[] memory depositSlippages = new uint256[](4);
            depositSlippages[0] = portions[0];
            depositSlippages[1] = portions[1];
            depositSlippages[2] = amountOut;
            depositSlippages[3] = amountOut;

            emit Slippages(true, 0, abi.encode(depositSlippages));
        }

        unwrapEth(amount);

        bought = rEthToken.balanceOf(address(this));

        rocketSwapRouter.swapTo{value: amount}(
            slippages[startingSlippage],
            slippages[startingSlippage + 1],
            slippages[startingSlippage + 2],
            slippages[startingSlippage + 3]
        );

        bought = rEthToken.balanceOf(address(this)) - bought;
    }

    function _redeemInternal(uint256 amount, uint256[] memory slippages, uint256 startingSlippage)
        private
        returns (uint256 bought)
    {
        if (_isViewExecution()) {
            // optimize first
            (uint256[2] memory portions, uint256 amountOut) = rocketSwapRouter.optimiseSwapFrom(amount, 10);

            slippages[startingSlippage] = portions[0];
            slippages[startingSlippage + 1] = portions[1];
            slippages[startingSlippage + 2] = amountOut;
            slippages[startingSlippage + 3] = amountOut;

            uint256[] memory withdrawalSlippages = new uint256[](4);
            withdrawalSlippages[0] = portions[0];
            withdrawalSlippages[1] = portions[1];
            withdrawalSlippages[2] = amountOut;
            withdrawalSlippages[3] = amountOut;

            emit Slippages(false, 0, abi.encode(withdrawalSlippages));
        }

        _resetAndApprove(IERC20(address(rEthToken)), address(rocketSwapRouter), amount);
        rocketSwapRouter.swapFrom(
            slippages[startingSlippage],
            slippages[startingSlippage + 1],
            slippages[startingSlippage + 2],
            slippages[startingSlippage + 3],
            amount
        );

        bought = address(this).balance;
        wrapEth(bought);
    }

    function _getSharePrice() private view returns (uint256) {
        return rEthToken.getEthValue(1 ether);
    }

    function _getProtocolRewardsInternal()
        internal
        virtual
        override
        returns (address[] memory tokens, uint256[] memory amounts)
    {}
}

File 2 of 32 : 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 3 of 32 : 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/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 4 of 32 : IREthToken.sol
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.17;

interface IREthToken {
    function totalSupply() external view returns (uint256);

    function balanceOf(address account) external view returns (uint256);

    function getEthValue(uint256 _rethAmount) external view returns (uint256);

    function getRethValue(uint256 _ethAmount) external view returns (uint256);

    function getTotalCollateral() external view returns (uint256);
}

File 5 of 32 : IRocketSwapRouter.sol
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.17;

interface IRocketSwapRouter {
    function rETH() external view returns (address);

    function swapTo(uint256 _uniswapPortion, uint256 _balancerPortion, uint256 _minTokensOut, uint256 _idealTokensOut)
        external
        payable;

    function swapFrom(
        uint256 _uniswapPortion,
        uint256 _balancerPortion,
        uint256 _minTokensOut,
        uint256 _idealTokensOut,
        uint256 _tokensIn
    ) external;

    function optimiseSwapTo(uint256 _amount, uint256 _steps)
        external
        returns (uint256[2] memory portions, uint256 amountOut);

    function optimiseSwapFrom(uint256 _amount, uint256 _steps)
        external
        returns (uint256[2] memory portions, uint256 amountOut);
}

File 6 of 32 : PackedRange.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

library PackedRange {
    uint256 constant BITS = 128;
    uint256 constant MAX = (1 << BITS) - 1;

    function isWithinRange(uint256 range, uint256 value) internal pure returns (bool) {
        return !((value < (range & MAX)) || (value > (range >> BITS)));
    }
}

File 7 of 32 : Strategy.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

import "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/utils/math/Math.sol";
import "@openzeppelin-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "../interfaces/Constants.sol";
import "../interfaces/IAssetGroupRegistry.sol";
import "../interfaces/IMasterWallet.sol";
import "../interfaces/IStrategy.sol";
import "../interfaces/IStrategyRegistry.sol";
import "../interfaces/CommonErrors.sol";
import "../interfaces/Constants.sol";
import "../access/SpoolAccessControllable.sol";

/**
 * @notice Used when initial locked strategy shares are already minted and strategy usd value is zero.
 */
error StrategyWorthIsZero();

abstract contract Strategy is ERC20Upgradeable, SpoolAccessControllable, IStrategy {
    using SafeERC20 for IERC20;

    /* ========== STATE VARIABLES ========== */

    IAssetGroupRegistry internal immutable _assetGroupRegistry;

    /// @notice Name of the strategy
    string private _strategyName;

    /// @dev ID of the asset group used by the strategy.
    uint256 private immutable _assetGroupId;
    /// @dev ID of the asset group used by the strategy.
    uint256 private _assetGroupIdStorage;
    // Only one of the above can be set. Use the `assetGroupId` function to read
    // the correct one.

    constructor(IAssetGroupRegistry assetGroupRegistry_, ISpoolAccessControl accessControl_, uint256 assetGroupId_)
        SpoolAccessControllable(accessControl_)
    {
        if (address(assetGroupRegistry_) == address(0)) {
            revert ConfigurationAddressZero();
        }

        _assetGroupRegistry = assetGroupRegistry_;
        _assetGroupId = assetGroupId_;
    }

    function __Strategy_init(string memory strategyName_, uint256 assetGroupId_) internal onlyInitializing {
        if (bytes(strategyName_).length == 0) revert InvalidConfiguration();

        // asset group ID needs to be set exactly once,
        // either in constructor or initializer
        if (_assetGroupId == NULL_ASSET_GROUP_ID) {
            if (assetGroupId_ == NULL_ASSET_GROUP_ID) {
                revert InvalidAssetGroupIdInitialization();
            }
            _assetGroupIdStorage = assetGroupId_;
        } else {
            if (assetGroupId_ != NULL_ASSET_GROUP_ID) {
                revert InvalidAssetGroupIdInitialization();
            }
        }
        _assetGroupRegistry.validateAssetGroup(assetGroupId());

        _strategyName = strategyName_;

        __ERC20_init("Strategy Share Token", "SST");
    }

    /* ========== EXTERNAL VIEW FUNCTIONS ========== */

    function assetGroupId() public view returns (uint256) {
        return _assetGroupId > 0 ? _assetGroupId : _assetGroupIdStorage;
    }

    function assets() public view returns (address[] memory) {
        return _assetGroupRegistry.listAssetGroup(assetGroupId());
    }

    function assetRatio() external view virtual returns (uint256[] memory);

    function strategyName() external view returns (string memory) {
        return _strategyName;
    }

    function beforeDepositCheck(uint256[] memory amounts, uint256[] calldata slippages) public virtual;

    function beforeRedeemalCheck(uint256 ssts, uint256[] calldata slippages) public virtual;

    /* ========== EXTERNAL MUTATIVE FUNCTIONS ========== */

    function doHardWork(StrategyDhwParameterBag calldata dhwParams) external returns (DhwInfo memory dhwInfo) {
        _checkRole(ROLE_STRATEGY_REGISTRY, msg.sender);

        bool depositNeeded;
        uint256[] memory assetsToDeposit = new uint256[](dhwParams.assetGroup.length);
        unchecked {
            for (uint256 i; i < dhwParams.assetGroup.length; ++i) {
                assetsToDeposit[i] = IERC20(dhwParams.assetGroup[i]).balanceOf(address(this));

                if (assetsToDeposit[i] > 0) {
                    depositNeeded = true;
                }
            }
        }

        beforeDepositCheck(assetsToDeposit, dhwParams.slippages);
        beforeRedeemalCheck(dhwParams.withdrawnShares, dhwParams.slippages);

        // usdWorth[0]: usd worth before deposit / withdrawal
        // usdWorth[1]: usd worth after deposit / withdrawal
        uint256[2] memory usdWorth;

        // Compound and get USD value.
        {
            dhwInfo.yieldPercentage = _getYieldPercentage(dhwParams.baseYield);
            int256 compoundYield = _compound(dhwParams.assetGroup, dhwParams.compoundSwapInfo, dhwParams.slippages);
            dhwInfo.yieldPercentage += compoundYield + compoundYield * dhwInfo.yieldPercentage / YIELD_FULL_PERCENT_INT;
        }

        // collect fees, mint SVTs relative to the yield generated
        _collectPlatformFees(dhwInfo.yieldPercentage, dhwParams.platformFees);

        usdWorth[0] = _getUsdWorth(dhwParams.exchangeRates, dhwParams.priceFeedManager);

        uint256 matchedShares;
        uint256 depositShareEquivalent;
        uint256 mintedShares;
        uint256 withdrawnShares = dhwParams.withdrawnShares;

        // Calculate deposit share equivalent.
        if (depositNeeded) {
            uint256 valueToDeposit = dhwParams.priceFeedManager.assetToUsdCustomPriceBulk(
                dhwParams.assetGroup, assetsToDeposit, dhwParams.exchangeRates
            );

            if (totalSupply() < INITIAL_LOCKED_SHARES) {
                depositShareEquivalent = INITIAL_SHARE_MULTIPLIER * valueToDeposit;
            } else if (usdWorth[0] > 0) {
                depositShareEquivalent = totalSupply() * valueToDeposit / usdWorth[0];
            } else {
                revert StrategyWorthIsZero();
            }

            // Match withdrawals and deposits by taking smaller value as matched shares.
            if (depositShareEquivalent < withdrawnShares) {
                matchedShares = depositShareEquivalent;
            } else {
                matchedShares = withdrawnShares;
            }
        }

        uint256[] memory withdrawnAssets = new uint256[](dhwParams.assetGroup.length);
        bool withdrawn;
        if (depositShareEquivalent > withdrawnShares) {
            // Deposit is needed.

            // - match if needed
            if (matchedShares > 0) {
                unchecked {
                    for (uint256 i; i < dhwParams.assetGroup.length; ++i) {
                        withdrawnAssets[i] = assetsToDeposit[i] * matchedShares / depositShareEquivalent;
                        assetsToDeposit[i] -= withdrawnAssets[i];
                    }
                }
                withdrawn = true;
            }

            // - swap assets
            uint256[] memory assetsIn = new uint256[](assetsToDeposit.length);
            if (dhwParams.swapInfo.length > 0) {
                _swapAssets(dhwParams.assetGroup, assetsToDeposit, dhwParams.swapInfo);
                for (uint256 i; i < dhwParams.assetGroup.length; ++i) {
                    assetsIn[i] = assetsToDeposit[i];
                    assetsToDeposit[i] = IERC20(dhwParams.assetGroup[i]).balanceOf(address(this)) - withdrawnAssets[i];
                }
            } else {
                for (uint256 i; i < dhwParams.assetGroup.length; ++i) {
                    assetsIn[i] = assetsToDeposit[i];
                }
            }

            // - deposit assets into the protocol
            _depositToProtocol(dhwParams.assetGroup, assetsToDeposit, dhwParams.slippages);
            usdWorth[1] = _getUsdWorth(dhwParams.exchangeRates, dhwParams.priceFeedManager);

            // - mint SSTs
            mintedShares = _mintStrategyShares(usdWorth[0], usdWorth[1]);

            emit Deposited(mintedShares, usdWorth[1] - usdWorth[0], assetsIn, assetsToDeposit);

            mintedShares += matchedShares;
        } else if (withdrawnShares > depositShareEquivalent) {
            // Withdrawal is needed.

            // - match if needed
            if (matchedShares > 0) {
                unchecked {
                    withdrawnShares -= matchedShares;
                    mintedShares = matchedShares;
                }
            }

            // - redeem shares from protocol
            _redeemFromProtocol(dhwParams.assetGroup, withdrawnShares, dhwParams.slippages);
            _burn(address(this), withdrawnShares);
            withdrawn = true;

            // - figure out how much was withdrawn
            usdWorth[1] = _getUsdWorth(dhwParams.exchangeRates, dhwParams.priceFeedManager);
            unchecked {
                for (uint256 i; i < dhwParams.assetGroup.length; ++i) {
                    withdrawnAssets[i] = IERC20(dhwParams.assetGroup[i]).balanceOf(address(this));
                }
            }

            emit Withdrawn(withdrawnShares, usdWorth[1], withdrawnAssets);
        } else {
            // Neither withdrawal nor deposit is needed.

            // - match if needed
            if (matchedShares > 0) {
                mintedShares = withdrawnShares;
                unchecked {
                    for (uint256 i; i < dhwParams.assetGroup.length; ++i) {
                        withdrawnAssets[i] = assetsToDeposit[i];
                    }
                }
                withdrawn = true;
            }

            usdWorth[1] = usdWorth[0];
        }

        // Transfer withdrawn assets to master wallet if needed.
        if (withdrawn) {
            unchecked {
                for (uint256 i; i < dhwParams.assetGroup.length; ++i) {
                    IERC20(dhwParams.assetGroup[i]).safeTransfer(dhwParams.masterWallet, withdrawnAssets[i]);
                }
            }
        }

        dhwInfo.sharesMinted = mintedShares;
        dhwInfo.assetsWithdrawn = withdrawnAssets;
        dhwInfo.valueAtDhw = usdWorth[1];
        dhwInfo.totalSstsAtDhw = totalSupply();
    }

    function redeemFast(
        uint256 shares,
        address masterWallet,
        address[] calldata assetGroup,
        uint256[] calldata slippages
    ) external returns (uint256[] memory) {
        if (
            !_accessControl.hasRole(ROLE_SMART_VAULT_MANAGER, msg.sender)
                && !_accessControl.hasRole(ROLE_STRATEGY_REGISTRY, msg.sender)
        ) {
            revert NotFastRedeemer(msg.sender);
        }

        return _redeemShares(shares, address(this), masterWallet, assetGroup, slippages);
    }

    function redeemShares(uint256 shares, address redeemer, address[] calldata assetGroup, uint256[] calldata slippages)
        external
        returns (uint256[] memory)
    {
        _checkRole(ROLE_STRATEGY_REGISTRY, msg.sender);

        return _redeemShares(shares, redeemer, redeemer, assetGroup, slippages);
    }

    /// @dev is only called when reallocating
    function depositFast(
        address[] calldata assetGroup,
        uint256[] calldata exchangeRates,
        IUsdPriceFeedManager priceFeedManager,
        uint256[] calldata slippages,
        SwapInfo[] calldata swapInfo
    ) external onlyRole(ROLE_SMART_VAULT_MANAGER, msg.sender) returns (uint256) {
        // get amount of assets available to deposit
        uint256[] memory assetsToDeposit = new uint256[](assetGroup.length);
        for (uint256 i; i < assetGroup.length; ++i) {
            assetsToDeposit[i] = IERC20(assetGroup[i]).balanceOf(address(this));
        }

        // swap assets
        _swapAssets(assetGroup, assetsToDeposit, swapInfo);
        uint256[] memory assetsDeposited = new uint256[](assetGroup.length);
        for (uint256 i; i < assetGroup.length; ++i) {
            assetsDeposited[i] = IERC20(assetGroup[i]).balanceOf(address(this));
        }

        // deposit assets
        uint256 usdWorth0 = _getUsdWorth(exchangeRates, priceFeedManager);
        _depositToProtocol(assetGroup, assetsDeposited, slippages);
        uint256 usdWorth1 = _getUsdWorth(exchangeRates, priceFeedManager);

        // mint SSTs
        uint256 sstsToMint = _mintStrategyShares(usdWorth0, usdWorth1);

        emit Deposited(sstsToMint, usdWorth1 - usdWorth0, assetsToDeposit, assetsDeposited);

        return sstsToMint;
    }

    function claimShares(address smartVault, uint256 amount) external onlyRole(ROLE_SMART_VAULT_MANAGER, msg.sender) {
        _transfer(address(this), smartVault, amount);
    }

    function releaseShares(address smartVault, uint256 amount)
        external
        onlyRole(ROLE_SMART_VAULT_MANAGER, msg.sender)
    {
        _transfer(smartVault, address(this), amount);
    }

    function emergencyWithdraw(uint256[] calldata slippages, address recipient)
        external
        onlyRole(ROLE_STRATEGY_REGISTRY, msg.sender)
    {
        _emergencyWithdrawImpl(slippages, recipient);
    }

    function getProtocolRewards() external onlyViewExecution returns (address[] memory, uint256[] memory) {
        return _getProtocolRewardsInternal();
    }

    function getUsdWorth(uint256[] memory exchangeRates, IUsdPriceFeedManager priceFeedManager)
        external
        onlyRole(ROLE_SMART_VAULT_MANAGER, msg.sender)
        returns (uint256)
    {
        return _getUsdWorth(exchangeRates, priceFeedManager);
    }

    /* ========== PRIVATE/INTERNAL FUNCTIONS ========== */

    function _mintStrategyShares(uint256 usdWorthBefore, uint256 usdWorthAfter)
        private
        returns (uint256 mintedShares)
    {
        uint256 totalSupply_ = totalSupply();

        if (totalSupply_ < INITIAL_LOCKED_SHARES) {
            // multiply with usd worth after deposit as there are no other owned shares
            mintedShares = usdWorthAfter * INITIAL_SHARE_MULTIPLIER;

            unchecked {
                uint256 lockedSharesLeftToMint = INITIAL_LOCKED_SHARES - totalSupply_;

                if (mintedShares < lockedSharesLeftToMint) {
                    lockedSharesLeftToMint = mintedShares;
                }

                mintedShares -= lockedSharesLeftToMint;

                _mint(INITIAL_LOCKED_SHARES_ADDRESS, lockedSharesLeftToMint);
            }
        } else if (usdWorthBefore > 0) {
            mintedShares = (usdWorthAfter - usdWorthBefore) * totalSupply_ / usdWorthBefore;
        } else {
            revert StrategyWorthIsZero();
        }

        _mint(address(this), mintedShares);
    }

    function _redeemShares(
        uint256 shares,
        address shareOwner,
        address recipient,
        address[] calldata assetGroup,
        uint256[] calldata slippages
    ) internal virtual returns (uint256[] memory) {
        // redeem shares from protocol
        uint256[] memory assetsWithdrawn = _redeemFromProtocolAndReturnAssets(assetGroup, shares, slippages);
        _burn(shareOwner, shares);

        // transfer assets to recipient (master wallet in case of redeemFast)
        unchecked {
            for (uint256 i; i < assetGroup.length; ++i) {
                IERC20(assetGroup[i]).safeTransfer(recipient, assetsWithdrawn[i]);
            }
        }

        return assetsWithdrawn;
    }

    /**
     * @notice Calculate and mint platform performance fees based on the yield generated.
     * @param yieldPct Yield generated since previous DHW. Full percent is `YIELD_FULL_PERCENT`.
     * @param platformFees Platform fees info, containing information of the sice and recipient of the fees (SSTs).
     * @return sharesMinted Returns newly minted shares representing the platform performance fees.
     */
    function _collectPlatformFees(int256 yieldPct, PlatformFees calldata platformFees)
        internal
        virtual
        returns (uint256 sharesMinted)
    {
        if (yieldPct > 0) {
            uint256 uint256YieldPct = uint256(yieldPct);

            uint256 yieldPctUsersPlusOne = uint256YieldPct
                * (FULL_PERCENT - platformFees.ecosystemFeePct - platformFees.treasuryFeePct)
                + FULL_PERCENT * YIELD_FULL_PERCENT;
            uint256 totalSupplyTimesYieldPct = totalSupply() * uint256YieldPct;

            // mint new ecosystem fee SSTs
            uint256 newEcosystemFeeSsts = totalSupplyTimesYieldPct * platformFees.ecosystemFeePct / yieldPctUsersPlusOne;
            _mint(platformFees.ecosystemFeeReceiver, newEcosystemFeeSsts);

            // mint new treasury fee SSTs
            uint256 newTreasuryFeeSsts = totalSupplyTimesYieldPct * platformFees.treasuryFeePct / yieldPctUsersPlusOne;
            _mint(platformFees.treasuryFeeReceiver, newTreasuryFeeSsts);

            unchecked {
                sharesMinted = newEcosystemFeeSsts + newTreasuryFeeSsts;
            }

            emit PlatformFeesCollected(address(this), sharesMinted);
        }
    }

    function _redeemFromProtocolAndReturnAssets(address[] calldata tokens, uint256 ssts, uint256[] calldata slippages)
        internal
        virtual
        returns (uint256[] memory withdrawnAssets)
    {
        withdrawnAssets = new uint256[](tokens.length);
        for (uint256 i; i < tokens.length; ++i) {
            withdrawnAssets[i] = IERC20(tokens[i]).balanceOf(address(this));
        }

        _redeemFromProtocol(tokens, ssts, slippages);

        for (uint256 i; i < tokens.length; ++i) {
            withdrawnAssets[i] = IERC20(tokens[i]).balanceOf(address(this)) - withdrawnAssets[i];
        }
    }

    function _calculateYieldPercentage(uint256 previousValue, uint256 currentValue)
        internal
        pure
        returns (int256 yieldPercentage)
    {
        if (currentValue > previousValue) {
            yieldPercentage = int256((currentValue - previousValue) * YIELD_FULL_PERCENT / previousValue);
        } else if (previousValue > currentValue) {
            yieldPercentage = -int256((previousValue - currentValue) * YIELD_FULL_PERCENT / previousValue);
        }
    }

    function _resetAndApprove(IERC20 token, address spender, uint256 amount) internal {
        _resetAllowance(token, spender);
        token.safeApprove(spender, amount);
    }

    function _resetAllowance(IERC20 token, address spender) internal {
        if (token.allowance(address(this), spender) > 0) {
            token.safeApprove(spender, 0);
        }
    }

    function _isViewExecution() internal view returns (bool) {
        return tx.origin == address(0);
    }

    function _compound(address[] calldata tokens, SwapInfo[] calldata compoundSwapInfo, uint256[] calldata slippages)
        internal
        virtual
        returns (int256 compoundYield);

    function _getYieldPercentage(int256 manualYield) internal virtual returns (int256);

    /**
     * @dev Swaps assets.
     * @param tokens Addresses of tokens to swap.
     * @param toSwap Available amounts to swap.
     * @param swapInfo Information on how to swap.
     */
    function _swapAssets(address[] memory tokens, uint256[] memory toSwap, SwapInfo[] calldata swapInfo)
        internal
        virtual;

    /**
     * @dev Deposits assets into the underlying protocol.
     * @param tokens Addresses of asset tokens.
     * @param amounts Amounts to deposit.
     * @param slippages Slippages to guard depositing.
     */
    function _depositToProtocol(address[] calldata tokens, uint256[] memory amounts, uint256[] calldata slippages)
        internal
        virtual;

    /**
     * @dev Redeems shares from the undelying protocol.
     * @param tokens Addresses of asset tokens.
     * @param ssts Amount of strategy tokens to redeem.
     * @param slippages Slippages to guard redeemal.
     */
    function _redeemFromProtocol(address[] calldata tokens, uint256 ssts, uint256[] calldata slippages)
        internal
        virtual;

    function _emergencyWithdrawImpl(uint256[] calldata slippages, address recipient) internal virtual;

    function _getUsdWorth(uint256[] memory exchangeRates, IUsdPriceFeedManager priceFeedManager)
        internal
        virtual
        returns (uint256);

    /**
     * @dev Gets protocol rewards.
     * @return tokens Addresses of reward tokens.
     * @return amounts Amount of each reward token.
     */
    function _getProtocolRewardsInternal()
        internal
        virtual
        returns (address[] memory tokens, uint256[] memory amounts);

    /* ========== MODIFIERS ========== */

    modifier onlyViewExecution() {
        require(_isViewExecution());
        _;
    }
}

File 8 of 32 : WethHelper.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

import "@openzeppelin/token/ERC20/IERC20.sol";
import "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import "../../external/interfaces/weth/IWETH9.sol";
import "../../interfaces/CommonErrors.sol";

abstract contract WethHelper {
    using SafeERC20 for IERC20;

    /**
     * @notice Address of wrapped eth contract.
     */
    address public immutable weth;

    /**
     * @param weth_ Address of wrapped eth contract.
     */
    constructor(address weth_) {
        if (address(weth_) == address(0)) {
            revert ConfigurationAddressZero();
        }

        weth = weth_;
    }

    /**
     * @notice Wraps eth.
     * @param amount Amount of eth to wrap.
     */
    function wrapEth(uint256 amount) internal {
        IWETH9(weth).deposit{value: amount}();
    }

    /**
     * @notice Unwraps eth.
     * @param amount Amount of eth to unwrap.
     */
    function unwrapEth(uint256 amount) internal {
        IWETH9(weth).withdraw(amount);
    }

    receive() external payable {}
}

File 9 of 32 : IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/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 10 of 32 : 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
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.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 11 of 32 : 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 12 of 32 : 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, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

File 13 of 32 : ERC20Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.0;

import "./IERC20Upgradeable.sol";
import "./extensions/IERC20MetadataUpgradeable.sol";
import "../../utils/ContextUpgradeable.sol";
import "../../proxy/utils/Initializable.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * The default value of {decimals} is 18. To select a different value for
     * {decimals} you should overload it.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {
        __ERC20_init_unchained(name_, symbol_);
    }

    function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
        _name = name_;
        _symbol = symbol_;
    }

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

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the value {ERC20} uses, unless this function is
     * overridden;
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _balances[account] += amount;
        }
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
            // Overflow not possible: amount <= accountBalance <= totalSupply.
            _totalSupply -= amount;
        }

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance amount in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[45] private __gap;
}

File 14 of 32 : Constants.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

/// @dev Number of seconds in an average year.
uint256 constant SECONDS_IN_YEAR = 31_556_926;

/// @dev Number of seconds in an average year.
int256 constant SECONDS_IN_YEAR_INT = 31_556_926;

/// @dev Represents 100%.
uint256 constant FULL_PERCENT = 100_00;

/// @dev Represents 100%.
int256 constant FULL_PERCENT_INT = 100_00;

/// @dev Represents 100% for yield.
int256 constant YIELD_FULL_PERCENT_INT = 10 ** 12;

/// @dev Represents 100% for yield.
uint256 constant YIELD_FULL_PERCENT = uint256(YIELD_FULL_PERCENT_INT);

/// @dev Maximal management fee that can be set on a smart vault. Expressed in terms of FULL_PERCENT.
uint256 constant MANAGEMENT_FEE_MAX = 5_00;

/// @dev Maximal deposit fee that can be set on a smart vault. Expressed in terms of FULL_PERCENT.
uint256 constant DEPOSIT_FEE_MAX = 5_00;

/// @dev Maximal smart vault performance fee that can be set on a smart vault. Expressed in terms of FULL_PERCENT.
uint256 constant SV_PERFORMANCE_FEE_MAX = 20_00;

/// @dev Maximal ecosystem fee that can be set on the system. Expressed in terms of FULL_PERCENT.
uint256 constant ECOSYSTEM_FEE_MAX = 20_00;

/// @dev Maximal treasury fee that can be set on the system. Expressed in terms of FULL_PERCENT.
uint256 constant TREASURY_FEE_MAX = 10_00;

/// @dev Maximal risk score a strategy can be assigned.
uint8 constant MAX_RISK_SCORE = 10_0;

/// @dev Minimal risk score a strategy can be assigned.
uint8 constant MIN_RISK_SCORE = 1;

/// @dev Maximal value for risk tolerance a smart vautl can have.
int8 constant MAX_RISK_TOLERANCE = 10;

/// @dev Minimal value for risk tolerance a smart vault can have.
int8 constant MIN_RISK_TOLERANCE = -10;

/// @dev If set as risk provider, system will return fixed risk score values
address constant STATIC_RISK_PROVIDER = address(0xaaa);

/// @dev Fixed values to use if risk provider is set to STATIC_RISK_PROVIDER
uint8 constant STATIC_RISK_SCORE = 1;

/// @dev Maximal value of deposit NFT ID.
uint256 constant MAXIMAL_DEPOSIT_ID = 2 ** 255;

/// @dev Maximal value of withdrawal NFT ID.
uint256 constant MAXIMAL_WITHDRAWAL_ID = 2 ** 256 - 1;

/// @dev How many shares will be minted with a NFT
uint256 constant NFT_MINTED_SHARES = 10 ** 6;

/// @dev Each smart vault can have up to STRATEGY_COUNT_CAP strategies.
uint256 constant STRATEGY_COUNT_CAP = 16;

/// @dev Maximal DHW base yield. Expressed in terms of FULL_PERCENT.
uint256 constant MAX_DHW_BASE_YIELD_LIMIT = 10_00;

/// @dev Smart vault and strategy share multiplier at first deposit.
uint256 constant INITIAL_SHARE_MULTIPLIER = 1000;

/// @dev Strategy initial locked shares. These shares will never be unlocked.
uint256 constant INITIAL_LOCKED_SHARES = 10 ** 12;

/// @dev Strategy initial locked shares address.
address constant INITIAL_LOCKED_SHARES_ADDRESS = address(0xdead);

/// @dev Maximum number of guards a smart vault can be configured with
uint256 constant MAX_GUARD_COUNT = 10;

/// @dev Maximum number of actions a smart vault can be configured with
uint256 constant MAX_ACTION_COUNT = 10;

/// @dev ID of null asset group. Should not be used by any strategy or smart vault.
uint256 constant NULL_ASSET_GROUP_ID = 0;

File 15 of 32 : IAssetGroupRegistry.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

import "@openzeppelin/token/ERC20/IERC20.sol";

/* ========== ERRORS ========== */

/**
 * @notice Used when invalid ID for asset group is provided.
 * @param assetGroupId Invalid ID for asset group.
 */
error InvalidAssetGroup(uint256 assetGroupId);

/**
 * @notice Used when no assets are provided for an asset group.
 */
error NoAssetsProvided();

/**
 * @notice Used when token is not allowed to be used as an asset.
 * @param token Address of the token that is not allowed.
 */
error TokenNotAllowed(address token);

/**
 * @notice Used when asset group already exists.
 * @param assetGroupId ID of the already existing asset group.
 */
error AssetGroupAlreadyExists(uint256 assetGroupId);

/**
 * @notice Used when given array is unsorted.
 */
error UnsortedArray();

/* ========== INTERFACES ========== */

interface IAssetGroupRegistry {
    /* ========== EVENTS ========== */

    /**
     * @notice Emitted when token is allowed to be used as an asset.
     * @param token Address of newly allowed token.
     */
    event TokenAllowed(address indexed token);

    /**
     * @notice Emitted when asset group is registered.
     * @param assetGroupId ID of the newly registered asset group.
     */
    event AssetGroupRegistered(uint256 indexed assetGroupId);

    /* ========== VIEW FUNCTIONS ========== */

    /**
     * @notice Checks if token is allowed to be used as an asset.
     * @param token Address of token to check.
     * @return isAllowed True if token is allowed, false otherwise.
     */
    function isTokenAllowed(address token) external view returns (bool isAllowed);

    /**
     * @notice Gets number of registered asset groups.
     * @return count Number of registered asset groups.
     */
    function numberOfAssetGroups() external view returns (uint256 count);

    /**
     * @notice Gets asset group by its ID.
     * @dev Requirements:
     * - must provide a valid ID for the asset group
     * @return assets Array of assets in the asset group.
     */
    function listAssetGroup(uint256 assetGroupId) external view returns (address[] memory assets);

    /**
     * @notice Gets asset group length.
     * @dev Requirements:
     * - must provide a valid ID for the asset group
     * @return length
     */
    function assetGroupLength(uint256 assetGroupId) external view returns (uint256 length);

    /**
     * @notice Validates that provided ID represents an asset group.
     * @dev Function reverts when ID does not represent an asset group.
     * @param assetGroupId ID to validate.
     */
    function validateAssetGroup(uint256 assetGroupId) external view;

    /**
     * @notice Checks if asset group composed of assets already exists.
     * Will revert if provided assets cannot form an asset group.
     * @param assets Assets composing the asset group.
     * @return Asset group ID if such asset group exists, 0 otherwise.
     */
    function checkAssetGroupExists(address[] calldata assets) external view returns (uint256);

    /* ========== MUTATIVE FUNCTIONS ========== */

    /**
     * @notice Allows a token to be used as an asset.
     * @dev Requirements:
     * - can only be called by the ROLE_SPOOL_ADMIN
     * @param token Address of token to be allowed.
     */
    function allowToken(address token) external;

    /**
     * @notice Allows tokens to be used as assets.
     * @dev Requirements:
     * - can only be called by the ROLE_SPOOL_ADMIN
     * @param tokens Addresses of tokens to be allowed.
     */
    function allowTokenBatch(address[] calldata tokens) external;

    /**
     * @notice Registers a new asset group.
     * @dev Requirements:
     * - must provide at least one asset
     * - all assets must be allowed
     * - assets must be sorted
     * - such asset group should not exist yet
     * - can only be called by the ROLE_SPOOL_ADMIN
     * @param assets Array of assets in the asset group.
     * @return id Sequential ID assigned to the asset group.
     */
    function registerAssetGroup(address[] calldata assets) external returns (uint256 id);
}

File 16 of 32 : IMasterWallet.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

import "@openzeppelin/token/ERC20/IERC20.sol";

interface IMasterWallet {
    /**
     * @notice Transfers amount of token to the recipient.
     * @dev Requirements:
     * - caller must have role ROLE_MASTER_WALLET_MANAGER
     * @param token Token to transfer.
     * @param recipient Target of the transfer.
     * @param amount Amount to transfer.
     */
    function transfer(IERC20 token, address recipient, uint256 amount) external;
}

File 17 of 32 : IStrategy.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

import "@openzeppelin/token/ERC20/IERC20.sol";
import "@openzeppelin-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {PlatformFees} from "./IStrategyRegistry.sol";
import "./ISwapper.sol";
import "./IUsdPriceFeedManager.sol";

/**
 * @notice Struct holding information how to swap the assets.
 * @custom:member slippage minumum output amount
 * @custom:member path swap path, first byte represents an action (e.g. Uniswap V2 custom swap), rest is swap specific path
 */
struct SwapData {
    uint256 slippage; // min amount out
    bytes path; // 1st byte is action, then path
}

/**
 * @notice Parameters for calling do hard work on strategy.
 * @custom:member swapInfo Information for swapping assets before depositing into the protocol.
 * @custom:member swapInfo Information for swapping rewards before depositing them back into the protocol.
 * @custom:member slippages Slippages used to constrain depositing and withdrawing from the protocol.
 * @custom:member assetGroup Asset group of the strategy.
 * @custom:member exchangeRates Exchange rates for assets.
 * @custom:member withdrawnShares Strategy shares withdrawn by smart vault.
 * @custom:member masterWallet Master wallet.
 * @custom:member priceFeedManager Price feed manager.
 * @custom:member baseYield Base yield value, manual input for specific strategies.
 * @custom:member platformFees Platform fees info.
 */
struct StrategyDhwParameterBag {
    SwapInfo[] swapInfo;
    SwapInfo[] compoundSwapInfo;
    uint256[] slippages;
    address[] assetGroup;
    uint256[] exchangeRates;
    uint256 withdrawnShares;
    address masterWallet;
    IUsdPriceFeedManager priceFeedManager;
    int256 baseYield;
    PlatformFees platformFees;
}

/**
 * @notice Information about results of the do hard work.
 * @custom:member sharesMinted Amount of strategy shares minted.
 * @custom:member assetsWithdrawn Amount of assets withdrawn.
 * @custom:member yieldPercentage Yield percentage from the previous DHW.
 * @custom:member valueAtDhw Value of the strategy at the end of DHW.
 * @custom:member totalSstsAtDhw Total SSTs at the end of DHW.
 */
struct DhwInfo {
    uint256 sharesMinted;
    uint256[] assetsWithdrawn;
    int256 yieldPercentage;
    uint256 valueAtDhw;
    uint256 totalSstsAtDhw;
}

/**
 * @notice Used when ghost strategy is called.
 */
error IsGhostStrategy();

/**
 * @notice Used when user is not allowed to redeem fast.
 * @param user User that tried to redeem fast.
 */
error NotFastRedeemer(address user);

/**
 * @notice Used when asset group ID is not correctly initialized.
 */
error InvalidAssetGroupIdInitialization();

interface IStrategy is IERC20Upgradeable {
    /* ========== EVENTS ========== */

    event Deposited(
        uint256 mintedShares, uint256 usdWorthDeposited, uint256[] assetsBeforeSwap, uint256[] assetsDeposited
    );

    event Withdrawn(uint256 withdrawnShares, uint256 usdWorthWithdrawn, uint256[] withdrawnAssets);

    event PlatformFeesCollected(address indexed strategy, uint256 sharesMinted);

    event Slippages(bool isDeposit, uint256 slippage, bytes data);

    event BeforeDepositCheckSlippages(uint256[] amounts);

    event BeforeRedeemalCheckSlippages(uint256 ssts);

    /* ========== VIEW FUNCTIONS ========== */

    /**
     * @notice Gets strategy name.
     * @return name Name of the strategy.
     */
    function strategyName() external view returns (string memory name);

    /**
     * @notice Gets required ratio between underlying assets.
     * @return ratio Required asset ratio for the strategy.
     */
    function assetRatio() external view returns (uint256[] memory ratio);

    /**
     * @notice Gets asset group used by the strategy.
     * @return id ID of the asset group.
     */
    function assetGroupId() external view returns (uint256 id);

    /**
     * @notice Gets underlying assets for the strategy.
     * @return assets Addresses of the underlying assets.
     */
    function assets() external view returns (address[] memory assets);

    /**
     * @notice Gets underlying asset amounts for the strategy.
     * @return amounts Amounts of the underlying assets.
     */
    function getUnderlyingAssetAmounts() external view returns (uint256[] memory amounts);

    /* ========== MUTATIVE FUNCTIONS ========== */

    /**
     * @dev Performs slippages check before depositing.
     * @param amounts Amounts to be deposited.
     * @param slippages Slippages to check against.
     */
    function beforeDepositCheck(uint256[] memory amounts, uint256[] calldata slippages) external;

    /**
     * @dev Performs slippages check before redeemal.
     * @param ssts Amount of strategy tokens to be redeemed.
     * @param slippages Slippages to check against.
     */
    function beforeRedeemalCheck(uint256 ssts, uint256[] calldata slippages) external;

    /**
     * @notice Does hard work:
     * - compounds rewards
     * - deposits into the protocol
     * - withdraws from the protocol
     * @dev Requirements:
     * - caller must have role ROLE_STRATEGY_REGISTRY
     * @param dhwParams Parameters for the do hard work.
     * @return info Information about do the performed hard work.
     */
    function doHardWork(StrategyDhwParameterBag calldata dhwParams) external returns (DhwInfo memory info);

    /**
     * @notice Claims strategy shares after do-hard-work.
     * @dev Requirements:
     * - caller must have role ROLE_SMART_VAULT_MANAGER
     * @param smartVault Smart vault claiming shares.
     * @param amount Amount of strategy shares to claim.
     */
    function claimShares(address smartVault, uint256 amount) external;

    /**
     * @notice Releases shares back to strategy.
     * @dev Requirements:
     * - caller must have role ROLE_SMART_VAULT_MANAGER
     * @param smartVault Smart vault releasing shares.
     * @param amount Amount of strategy shares to release.
     */
    function releaseShares(address smartVault, uint256 amount) external;

    /**
     * @notice Instantly redeems strategy shares for assets.
     * @dev Requirements:
     * - caller must have either role ROLE_SMART_VAULT_MANAGER or role ROLE_STRATEGY_REGISTRY
     * @param shares Amount of shares to redeem.
     * @param masterWallet Address of the master wallet.
     * @param assetGroup Asset group of the strategy.
     * @param slippages Slippages to guard redeeming.
     * @return assetsWithdrawn Amount of assets withdrawn.
     */
    function redeemFast(
        uint256 shares,
        address masterWallet,
        address[] calldata assetGroup,
        uint256[] calldata slippages
    ) external returns (uint256[] memory assetsWithdrawn);

    /**
     * @notice Instantly redeems strategy shares for assets.
     * @param shares Amount of shares to redeem.
     * @param redeemer Address of he redeemer, owner of SSTs.
     * @param assetGroup Asset group of the strategy.
     * @param slippages Slippages to guard redeeming.
     * @return assetsWithdrawn Amount of assets withdrawn.
     */
    function redeemShares(uint256 shares, address redeemer, address[] calldata assetGroup, uint256[] calldata slippages)
        external
        returns (uint256[] memory assetsWithdrawn);

    /**
     * @notice Instantly deposits into the protocol.
     * @dev Requirements:
     * - caller must have role ROLE_SMART_VAULT_MANAGER
     * @param assetGroup Asset group of the strategy.
     * @param exchangeRates Asset to USD exchange rates.
     * @param priceFeedManager Price feed manager contract.
     * @param slippages Slippages to guard depositing.
     * @param swapInfo Information for swapping assets before depositing into the protocol.
     * @return sstsMinted Amount of SSTs minted.
     */
    function depositFast(
        address[] calldata assetGroup,
        uint256[] calldata exchangeRates,
        IUsdPriceFeedManager priceFeedManager,
        uint256[] calldata slippages,
        SwapInfo[] calldata swapInfo
    ) external returns (uint256 sstsMinted);

    /**
     * @notice Instantly withdraws assets, bypassing shares mechanism.
     * Transfers withdrawn assets to the emergency withdrawal wallet.
     * @dev Requirements:
     * - caller must have role ROLE_STRATEGY_REGISTRY
     * @param slippages Slippages to guard redeeming.
     * @param recipient Recipient address
     */
    function emergencyWithdraw(uint256[] calldata slippages, address recipient) external;

    /**
     * @notice Gets USD worth of the strategy.
     * @dev Requirements:
     * - caller must have role ROLE_SMART_VAULT_MANAGER
     * @param exchangeRates Asset to USD exchange rates.
     * @param priceFeedManager Price feed manager contract.
     */
    function getUsdWorth(uint256[] memory exchangeRates, IUsdPriceFeedManager priceFeedManager)
        external
        returns (uint256 usdWorth);

    /**
     * @notice Gets protocol rewards.
     * @dev Requirements:
     * - can only be called in view-execution mode.
     * @return tokens Addresses of reward tokens.
     * @return amounts Amount of reward tokens available.
     */
    function getProtocolRewards() external returns (address[] memory tokens, uint256[] memory amounts);
}

File 18 of 32 : IStrategyRegistry.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

import "./ISwapper.sol";
import {DhwInfo} from "./IStrategy.sol";
import "../libraries/uint16a16Lib.sol";

/* ========== ERRORS ========== */

/**
 * @notice Used when trying to register an already registered strategy.
 * @param address_ Address of already registered strategy.
 */
error StrategyAlreadyRegistered(address address_);

/**
 * @notice Used when DHW was not run yet for a strategy index.
 * @param strategy Address of the strategy.
 * @param strategyIndex Index of the strategy.
 */
error DhwNotRunYetForIndex(address strategy, uint256 strategyIndex);

/**
 * @notice Used when provided token list is invalid.
 */
error InvalidTokenList();

/**
 * @notice Used when ghost strategy is used.
 */
error GhostStrategyUsed();

/**
 * @notice Used when syncing vault that is already fully synced.
 */
error NothingToSync();

/**
 * @notice Used when system tries to configure a too large ecosystem fee.
 * @param ecosystemFeePct Requested ecosystem fee.
 */
error EcosystemFeeTooLarge(uint256 ecosystemFeePct);

/**
 * @notice Used when system tries to configure a too large treasury fee.
 * @param treasuryFeePct Requested treasury fee.
 */
error TreasuryFeeTooLarge(uint256 treasuryFeePct);

/**
 * @notice Used when user tries to re-add a strategy that was previously removed from the system.
 * @param strategy Strategy address
 */
error StrategyPreviouslyRemoved(address strategy);

/**
 * @notice Represents change of state for a strategy during a DHW.
 * @custom:member exchangeRates Exchange rates between assets and USD.
 * @custom:member assetsDeposited Amount of assets deposited into the strategy.
 * @custom:member sharesMinted Amount of strategy shares minted.
 * @custom:member totalSSTs Amount of strategy shares at the end of the DHW.
 * @custom:member totalStrategyValue Total strategy value at the end of the DHW.
 * @custom:member dhwYields DHW yield percentage from the previous DHW.
 */
struct StrategyAtIndex {
    uint256[] exchangeRates;
    uint256[] assetsDeposited;
    uint256 sharesMinted;
    uint256 totalSSTs;
    uint256 totalStrategyValue;
    int256 dhwYields;
}

/**
 * @notice Parameters for calling do hard work.
 * @custom:member strategies Strategies to do-hard-worked upon, grouped by their asset group.
 * @custom:member swapInfo Information for swapping assets before depositing into protocol. SwapInfo[] per each strategy.
 * @custom:member compoundSwapInfo Information for swapping rewards before depositing them back into the protocol. SwapInfo[] per each strategy.
 * @custom:member strategySlippages Slippages used to constrain depositing into and withdrawing from the protocol. uint256[] per strategy.
 * @custom:member baseYields Base yield percentage the strategy created in the DHW period (applicable only for some strategies).
 * @custom:member tokens List of all asset tokens involved in the do hard work.
 * @custom:member exchangeRateSlippages Slippages used to constrain exchange rates for asset tokens. uint256[2] for each token.
 * @custom:member validUntil Sets the maximum timestamp the user is willing to wait to start executing 'do hard work'.
 */
struct DoHardWorkParameterBag {
    address[][] strategies;
    SwapInfo[][][] swapInfo;
    SwapInfo[][][] compoundSwapInfo;
    uint256[][][] strategySlippages;
    int256[][] baseYields;
    address[] tokens;
    uint256[2][] exchangeRateSlippages;
    uint256 validUntil;
}

/**
 * @notice Parameters for calling redeem fast.
 * @custom:member strategies Addresses of strategies.
 * @custom:member strategyShares Amount of shares to redeem.
 * @custom:member assetGroup Asset group of the smart vault.
 * @custom:member slippages Slippages to guard withdrawal.
 */
struct RedeemFastParameterBag {
    address[] strategies;
    uint256[] strategyShares;
    address[] assetGroup;
    uint256[][] withdrawalSlippages;
}

/**
 * @notice Group of platform fees.
 * @custom:member ecosystemFeeReciever Receiver of the ecosystem fees.
 * @custom:member ecosystemFeePct Ecosystem fees. Expressed in FULL_PERCENT.
 * @custom:member treasuryFeeReciever Receiver of the treasury fees.
 * @custom:member treasuryFeePct Treasury fees. Expressed in FULL_PERCENT.
 */
struct PlatformFees {
    address ecosystemFeeReceiver;
    uint96 ecosystemFeePct;
    address treasuryFeeReceiver;
    uint96 treasuryFeePct;
}

/* ========== INTERFACES ========== */

interface IStrategyRegistry {
    /* ========== EXTERNAL VIEW FUNCTIONS ========== */

    /**
     * @notice Returns address of emergency withdrawal wallet.
     * @return emergencyWithdrawalWallet Address of the emergency withdrawal wallet.
     */
    function emergencyWithdrawalWallet() external view returns (address emergencyWithdrawalWallet);

    /**
     * @notice Returns current do-hard-work indexes for strategies.
     * @param strategies Strategies.
     * @return dhwIndexes Current do-hard-work indexes for strategies.
     */
    function currentIndex(address[] calldata strategies) external view returns (uint256[] memory dhwIndexes);

    /**
     * @notice Returns current strategy APYs.
     * @param strategies Strategies.
     */
    function strategyAPYs(address[] calldata strategies) external view returns (int256[] memory apys);

    /**
     * @notice Returns assets deposited into a do-hard-work index for a strategy.
     * @param strategy Strategy.
     * @param dhwIndex Do-hard-work index.
     * @return assets Assets deposited into the do-hard-work index for the strategy.
     */
    function depositedAssets(address strategy, uint256 dhwIndex) external view returns (uint256[] memory assets);

    /**
     * @notice Returns shares redeemed in a do-hard-work index for a strategy.
     * @param strategy Strategy.
     * @param dhwIndex Do-hard-work index.
     * @return shares Shares redeemed in a do-hard-work index for the strategy.
     */
    function sharesRedeemed(address strategy, uint256 dhwIndex) external view returns (uint256 shares);

    /**
     * @notice Gets timestamps when do-hard-works were performed.
     * @param strategies Strategies.
     * @param dhwIndexes Do-hard-work indexes.
     * @return timestamps Timestamp for each pair of strategies and do-hard-work indexes.
     */
    function dhwTimestamps(address[] calldata strategies, uint16a16 dhwIndexes)
        external
        view
        returns (uint256[] memory timestamps);

    function getDhwYield(address[] calldata strategies, uint16a16 dhwIndexes)
        external
        view
        returns (int256[] memory yields);

    /**
     * @notice Returns state of strategies at do-hard-work indexes.
     * @param strategies Strategies.
     * @param dhwIndexes Do-hard-work indexes.
     * @return states State of each strategy at corresponding do-hard-work index.
     */
    function strategyAtIndexBatch(address[] calldata strategies, uint16a16 dhwIndexes, uint256 assetGroupLength)
        external
        view
        returns (StrategyAtIndex[] memory states);

    /**
     * @notice Gets required asset ratio for strategy at last DHW.
     * @param strategy Address of the strategy.
     * @return assetRatio Asset ratio.
     */
    function assetRatioAtLastDhw(address strategy) external view returns (uint256[] memory assetRatio);

    /**
     * @notice Gets set platform fees.
     * @return fees Set platform fees.
     */
    function platformFees() external view returns (PlatformFees memory fees);

    /* ========== EXTERNAL MUTATIVE FUNCTIONS ========== */

    /**
     * @notice Registers a strategy into the system.
     * @dev Requirements:
     * - caller must have role ROLE_SPOOL_ADMIN
     * @param strategy Address of strategy to register.
     * @param apy Apy of the strategy at the time of the registration.
     */
    function registerStrategy(address strategy, int256 apy) external;

    /**
     * @notice Removes strategy from the system.
     * @dev Requirements:
     * - caller must have role ROLE_SMART_VAULT_MANAGER
     * @param strategy Strategy to remove.
     */
    function removeStrategy(address strategy) external;

    /**
     * @notice Sets ecosystem fee.
     * @dev Requirements:
     * - caller must have role ROLE_SPOOL_ADMIN
     * @param ecosystemFeePct Ecosystem fee to set. Expressed in terms of FULL_PERCENT.
     */
    function setEcosystemFee(uint96 ecosystemFeePct) external;

    /**
     * @notice Sets receiver of the ecosystem fees.
     * @dev Requirements:
     * - caller must have role ROLE_SPOOL_ADMIN
     * @param ecosystemFeeReceiver Receiver to set.
     */
    function setEcosystemFeeReceiver(address ecosystemFeeReceiver) external;

    /**
     * @notice Sets treasury fee.
     * @dev Requirements:
     * - caller must have role ROLE_SPOOL_ADMIN
     * @param treasuryFeePct Treasury fee to set. Expressed in terms of FULL_PERCENT.
     */
    function setTreasuryFee(uint96 treasuryFeePct) external;

    /**
     * @notice Sets treasury fee receiver.
     * @dev Requirements:
     * - caller must have role ROLE_SPOOL_ADMIN
     * @param treasuryFeeReceiver Receiver to set.
     */
    function setTreasuryFeeReceiver(address treasuryFeeReceiver) external;

    /**
     * @notice Does hard work on multiple strategies.
     * @dev Requirements:
     * - caller must have role ROLE_DO_HARD_WORKER
     * @param dhwParams Parameters for do hard work.
     */
    function doHardWork(DoHardWorkParameterBag calldata dhwParams) external;

    /**
     * @notice Adds deposits to strategies to be processed at next do-hard-work.
     * @dev Requirements:
     * - caller must have role ROLE_SMART_VAULT_MANAGER
     * @param strategies Strategies to which to add deposit.
     * @param amounts Amounts of assets to add to each strategy.
     * @return strategyIndexes Current do-hard-work indexes for the strategies.
     */
    function addDeposits(address[] calldata strategies, uint256[][] calldata amounts)
        external
        returns (uint16a16 strategyIndexes);

    /**
     * @notice Adds withdrawals to strategies to be processed at next do-hard-work.
     * @dev Requirements:
     * - caller must have role ROLE_SMART_VAULT_MANAGER
     * @param strategies Strategies to which to add withdrawal.
     * @param strategyShares Amounts of strategy shares to add to each strategy.
     * @return strategyIndexes Current do-hard-work indexes for the strategies.
     */
    function addWithdrawals(address[] calldata strategies, uint256[] calldata strategyShares)
        external
        returns (uint16a16 strategyIndexes);

    /**
     * @notice Instantly redeems strategy shares for assets.
     * @dev Requirements:
     * - caller must have role ROLE_SMART_VAULT_MANAGER
     * @param redeemFastParams Parameters for fast redeem.
     * @return withdrawnAssets Amount of assets withdrawn.
     */
    function redeemFast(RedeemFastParameterBag calldata redeemFastParams)
        external
        returns (uint256[] memory withdrawnAssets);

    /**
     * @notice Claims withdrawals from the strategies.
     * @dev Requirements:
     * - caller must have role ROLE_SMART_VAULT_MANAGER
     * - DHWs must be run for withdrawal indexes.
     * @param strategies Addresses if strategies from which to claim withdrawal.
     * @param dhwIndexes Indexes of strategies when withdrawal was made.
     * @param strategyShares Amount of strategy shares that was withdrawn.
     * @return assetsWithdrawn Amount of assets withdrawn from strategies.
     */
    function claimWithdrawals(address[] calldata strategies, uint16a16 dhwIndexes, uint256[] calldata strategyShares)
        external
        returns (uint256[] memory assetsWithdrawn);

    /**
     * @notice Redeems strategy shares.
     * Used by recipients of platform fees.
     * @param strategies Strategies from which to redeem.
     * @param shares Amount of shares to redeem from each strategy.
     * @param withdrawalSlippages Slippages to guard redeemal process.
     */
    function redeemStrategyShares(
        address[] calldata strategies,
        uint256[] calldata shares,
        uint256[][] calldata withdrawalSlippages
    ) external;

    /**
     * @notice Strategy was registered
     * @param strategy Strategy address
     */
    event StrategyRegistered(address indexed strategy);

    /**
     * @notice Strategy was removed
     * @param strategy Strategy address
     */
    event StrategyRemoved(address indexed strategy);

    /**
     * @notice Strategy DHW was executed
     * @param strategy Strategy address
     * @param dhwIndex DHW index
     * @param dhwInfo DHW info
     */
    event StrategyDhw(address indexed strategy, uint256 dhwIndex, DhwInfo dhwInfo);

    /**
     * @notice Ecosystem fee configuration was changed
     * @param feePct Fee percentage value
     */
    event EcosystemFeeSet(uint256 feePct);

    /**
     * @notice Ecosystem fee receiver was changed
     * @param ecosystemFeeReceiver Receiver address
     */
    event EcosystemFeeReceiverSet(address indexed ecosystemFeeReceiver);

    /**
     * @notice Treasury fee configuration was changed
     * @param feePct Fee percentage value
     */
    event TreasuryFeeSet(uint256 feePct);

    /**
     * @notice Treasury fee receiver was changed
     * @param treasuryFeeReceiver Receiver address
     */
    event TreasuryFeeReceiverSet(address indexed treasuryFeeReceiver);

    /**
     * @notice Emergency withdrawal wallet changed
     * @param wallet Emergency withdrawal wallet address
     */
    event EmergencyWithdrawalWalletSet(address indexed wallet);

    /**
     * @notice Strategy shares have been redeemed
     * @param strategy Strategy address
     * @param owner Address that owns the shares
     * @param recipient Address that received the withdrawn funds
     * @param shares Amount of shares that were redeemed
     * @param assetsWithdrawn Amounts of withdrawn assets
     */
    event StrategySharesRedeemed(
        address indexed strategy,
        address indexed owner,
        address indexed recipient,
        uint256 shares,
        uint256[] assetsWithdrawn
    );

    /**
     * @notice Strategy shares were fast redeemed
     * @param strategy Strategy address
     * @param shares Amount of shares redeemed
     * @param assetsWithdrawn Amounts of withdrawn assets
     */
    event StrategySharesFastRedeemed(address indexed strategy, uint256 shares, uint256[] assetsWithdrawn);

    /**
     * @notice Strategy APY value was updated
     * @param strategy Strategy address
     * @param apy New APY value
     */
    event StrategyApyUpdated(address indexed strategy, int256 apy);
}

interface IEmergencyWithdrawal {
    /**
     * @notice Emitted when a strategy is emergency withdrawn from.
     * @param strategy Strategy that was emergency withdrawn from.
     */
    event StrategyEmergencyWithdrawn(address indexed strategy);

    /**
     * @notice Set a new address that will receive assets withdrawn if emergency withdrawal is executed.
     * @dev Requirements:
     * - caller must have role ROLE_SPOOL_ADMIN
     * @param wallet Address to set as the emergency withdrawal wallet.
     */
    function setEmergencyWithdrawalWallet(address wallet) external;

    /**
     * @notice Instantly withdraws assets from a strategy, bypassing shares mechanism.
     * @dev Requirements:
     * - caller must have role ROLE_EMERGENCY_WITHDRAWAL_EXECUTOR
     * @param strategies Addresses of strategies.
     * @param withdrawalSlippages Slippages to guard withdrawal.
     * @param removeStrategies Whether to remove strategies from the system after withdrawal.
     */
    function emergencyWithdraw(
        address[] calldata strategies,
        uint256[][] calldata withdrawalSlippages,
        bool removeStrategies
    ) external;
}

File 19 of 32 : CommonErrors.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

/**
 * @notice Used when an array has invalid length.
 */
error InvalidArrayLength();

/**
 * @notice Used when group of smart vaults or strategies do not have same asset group.
 */
error NotSameAssetGroup();

/**
 * @notice Used when configuring an address with a zero address.
 */
error ConfigurationAddressZero();

/**
 * @notice Used when constructor or intializer parameters are invalid.
 */
error InvalidConfiguration();

/**
 * @notice Used when fetched exchange rate is out of slippage range.
 */
error ExchangeRateOutOfSlippages();

/**
 * @notice Used when an invalid strategy is provided.
 * @param address_ Address of the invalid strategy.
 */
error InvalidStrategy(address address_);

/**
 * @notice Used when doing low-level call on an address that is not a contract.
 * @param address_ Address of the contract
 */
error AddressNotContract(address address_);

/**
 * @notice Used when invoking an only view execution and tx.origin is not address zero.
 * @param address_ Address of the tx.origin
 */
error OnlyViewExecution(address address_);

File 20 of 32 : SpoolAccessControllable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

import "../interfaces/ISpoolAccessControl.sol";
import "../interfaces/CommonErrors.sol";
import "./Roles.sol";

/**
 * @notice Account access role verification middleware
 */
abstract contract SpoolAccessControllable {
    /* ========== CONSTANTS ========== */

    /**
     * @dev Spool access control manager.
     */
    ISpoolAccessControl internal immutable _accessControl;

    /* ========== CONSTRUCTOR ========== */

    /**
     * @param accessControl_ Spool access control manager.
     */
    constructor(ISpoolAccessControl accessControl_) {
        if (address(accessControl_) == address(0)) revert ConfigurationAddressZero();

        _accessControl = accessControl_;
    }

    /* ========== INTERNAL FUNCTIONS ========== */

    /**
     * @dev Reverts if an account is missing a role.\
     * @param role Role to check for.
     * @param account Account to check.
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!_accessControl.hasRole(role, account)) {
            revert MissingRole(role, account);
        }
    }

    /**
     * @dev Revert if an account is missing a role for a smartVault.
     * @param smartVault Address of the smart vault.
     * @param role Role to check for.
     * @param account Account to check.
     */
    function _checkSmartVaultRole(address smartVault, bytes32 role, address account) internal view {
        if (!_accessControl.hasSmartVaultRole(smartVault, role, account)) {
            revert MissingRole(role, account);
        }
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        if (_accessControl.paused()) {
            revert SystemPaused();
        }
    }

    function _checkNonReentrant() internal view {
        _accessControl.checkNonReentrant();
    }

    function _nonReentrantBefore() internal {
        _accessControl.nonReentrantBefore();
    }

    function _nonReentrantAfter() internal {
        _accessControl.nonReentrantAfter();
    }

    /* ========== MODIFIERS ========== */

    /**
     * @notice Only allows accounts with granted role.
     * @dev Reverts when the account fails check.
     * @param role Role to check for.
     * @param account Account to check.
     */
    modifier onlyRole(bytes32 role, address account) {
        _checkRole(role, account);
        _;
    }

    /**
     * @notice Only allows accounts with granted role for a smart vault.
     * @dev Reverts when the account fails check.
     * @param smartVault Address of the smart vault.
     * @param role Role to check for.
     * @param account Account to check.
     */
    modifier onlySmartVaultRole(address smartVault, bytes32 role, address account) {
        _checkSmartVaultRole(smartVault, role, account);
        _;
    }

    /**
     * @notice Only allows accounts that are Spool admins or admins of a smart vault.
     * @dev Reverts when the account fails check.
     * @param smartVault Address of the smart vault.
     * @param account Account to check.
     */
    modifier onlyAdminOrVaultAdmin(address smartVault, address account) {
        _accessControl.checkIsAdminOrVaultAdmin(smartVault, account);
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Prevents a contract from calling itself, or other contracts using this modifier.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    /**
     * @dev Check if a system has already entered in the non-reentrant state.
     */
    modifier checkNonReentrant() {
        _checkNonReentrant();
        _;
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}

File 21 of 32 : IWETH9.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

interface IWETH9 {
    function deposit() external payable;

    function withdraw(uint256 wad) external;

    function transfer(address dst, uint256 wad) external returns (bool);
}

File 22 of 32 : IERC20Upgradeable.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 IERC20Upgradeable {
    /**
     * @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 23 of 32 : IERC20MetadataUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20Upgradeable.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20MetadataUpgradeable is IERC20Upgradeable {
    /**
     * @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 24 of 32 : ContextUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

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

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract ContextUpgradeable is Initializable {
    function __Context_init() internal onlyInitializing {
    }

    function __Context_init_unchained() internal onlyInitializing {
    }
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[50] private __gap;
}

File 25 of 32 : Initializable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.2;

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

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     * @custom:oz-retyped-from bool
     */
    uint8 private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint8 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
     * constructor.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        bool isTopLevelCall = !_initializing;
        require(
            (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
            "Initializable: contract is already initialized"
        );
        _initialized = 1;
        if (isTopLevelCall) {
            _initializing = true;
        }
        _;
        if (isTopLevelCall) {
            _initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: setting the version to 255 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint8 version) {
        require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
        _initialized = version;
        _initializing = true;
        _;
        _initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        require(_initializing, "Initializable: contract is not initializing");
        _;
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        require(!_initializing, "Initializable: contract is initializing");
        if (_initialized != type(uint8).max) {
            _initialized = type(uint8).max;
            emit Initialized(type(uint8).max);
        }
    }

    /**
     * @dev Internal function that returns the initialized version. Returns `_initialized`
     */
    function _getInitializedVersion() internal view returns (uint8) {
        return _initialized;
    }

    /**
     * @dev Internal function that returns the initialized version. Returns `_initializing`
     */
    function _isInitializing() internal view returns (bool) {
        return _initializing;
    }
}

File 26 of 32 : ISwapper.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

/* ========== STRUCTS ========== */

/**
 * @notice Information needed to make a swap of assets.
 * @custom:member swapTarget Contract executing the swap.
 * @custom:member token Token to be swapped.
 * @custom:member swapCallData Calldata describing the swap itself.
 */
struct SwapInfo {
    address swapTarget;
    address token;
    bytes swapCallData;
}

/* ========== ERRORS ========== */

/**
 * @notice Used when trying to do a swap via an exchange that is not allowed to execute a swap.
 * @param exchange Exchange used.
 */
error ExchangeNotAllowed(address exchange);

/**
 * @notice Used when trying to execute a swap but are not authorized.
 * @param caller Caller of the swap method.
 */
error NotSwapper(address caller);

/* ========== INTERFACES ========== */

interface ISwapper {
    /* ========== EVENTS ========== */

    /**
     * @notice Emitted when the exchange allowlist is updated.
     * @param exchange Exchange that was updated.
     * @param isAllowed Whether the exchange is allowed to be used in a swap or not after the update.
     */
    event ExchangeAllowlistUpdated(address indexed exchange, bool isAllowed);

    event Swapped(
        address indexed receiver, address[] tokensIn, address[] tokensOut, uint256[] amountsIn, uint256[] amountsOut
    );

    /* ========== EXTERNAL MUTATIVE FUNCTIONS ========== */

    /**
     * @notice Performs a swap of tokens with external contracts.
     * - deposit tokens into the swapper contract
     * - swapper will swap tokens based on swap info provided
     * - swapper will return unswapped tokens to the receiver
     * @param tokensIn Addresses of tokens available for the swap.
     * @param swapInfo Information needed to perform the swap.
     * @param tokensOut Addresses of tokens to swap to.
     * @param receiver Receiver of unswapped tokens.
     * @return amountsOut Amounts of `tokensOut` sent from the swapper to the receiver.
     */
    function swap(
        address[] calldata tokensIn,
        SwapInfo[] calldata swapInfo,
        address[] calldata tokensOut,
        address receiver
    ) external returns (uint256[] memory amountsOut);

    /**
     * @notice Updates list of exchanges that can be used in a swap.
     * @dev Requirements:
     *   - can only be called by user granted ROLE_SPOOL_ADMIN
     *   - exchanges and allowed arrays need to be of same length
     * @param exchanges Addresses of exchanges.
     * @param allowed Whether an exchange is allowed to be used in a swap.
     */
    function updateExchangeAllowlist(address[] calldata exchanges, bool[] calldata allowed) external;

    /* ========== EXTERNAL VIEW FUNCTIONS ========== */

    /**
     * @notice Checks if an exchange is allowed to be used in a swap.
     * @param exchange Exchange to check.
     * @return isAllowed True if the exchange is allowed to be used in a swap, false otherwise.
     */
    function isExchangeAllowed(address exchange) external view returns (bool isAllowed);
}

File 27 of 32 : IUsdPriceFeedManager.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

/// @dev Number of decimals used for USD values.
uint256 constant USD_DECIMALS = 18;

/**
 * @notice Emitted when asset is invalid.
 * @param asset Invalid asset.
 */
error InvalidAsset(address asset);

/**
 * @notice Emitted when price returned by price aggregator is negative or zero.
 * @param price Actual price returned by price aggregator.
 */
error NonPositivePrice(int256 price);

/**
 * @notice Emitted when pricing data returned by price aggregator is not from the current
 * round or the round hasn't finished.
 */
error StalePriceData();

interface IUsdPriceFeedManager {
    /**
     * @notice Gets number of decimals for an asset.
     * @param asset Address of the asset.
     * @return assetDecimals Number of decimals for the asset.
     */
    function assetDecimals(address asset) external view returns (uint256 assetDecimals);

    /**
     * @notice Gets number of decimals for USD.
     * @return usdDecimals Number of decimals for USD.
     */
    function usdDecimals() external view returns (uint256 usdDecimals);

    /**
     * @notice Calculates asset value in USD using current price.
     * @param asset Address of asset.
     * @param assetAmount Amount of asset in asset decimals.
     * @return usdValue Value in USD in USD decimals.
     */
    function assetToUsd(address asset, uint256 assetAmount) external view returns (uint256 usdValue);

    /**
     * @notice Calculates USD value in asset using current price.
     * @param asset Address of asset.
     * @param usdAmount Amount of USD in USD decimals.
     * @return assetValue Value in asset in asset decimals.
     */
    function usdToAsset(address asset, uint256 usdAmount) external view returns (uint256 assetValue);

    /**
     * @notice Calculates asset value in USD using provided price.
     * @param asset Address of asset.
     * @param assetAmount Amount of asset in asset decimals.
     * @param price Price of asset in USD.
     * @return usdValue Value in USD in USD decimals.
     */
    function assetToUsdCustomPrice(address asset, uint256 assetAmount, uint256 price)
        external
        view
        returns (uint256 usdValue);

    /**
     * @notice Calculates assets value in USD using provided prices.
     * @param assets Addresses of assets.
     * @param assetAmounts Amounts of assets in asset decimals.
     * @param prices Prices of asset in USD.
     * @return usdValue Value in USD in USD decimals.
     */
    function assetToUsdCustomPriceBulk(
        address[] calldata assets,
        uint256[] calldata assetAmounts,
        uint256[] calldata prices
    ) external view returns (uint256 usdValue);

    /**
     * @notice Calculates USD value in asset using provided price.
     * @param asset Address of asset.
     * @param usdAmount Amount of USD in USD decimals.
     * @param price Price of asset in USD.
     * @return assetValue Value in asset in asset decimals.
     */
    function usdToAssetCustomPrice(address asset, uint256 usdAmount, uint256 price)
        external
        view
        returns (uint256 assetValue);
}

File 28 of 32 : uint16a16Lib.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

type uint16a16 is uint256;

/**
 * @notice This library enables packing of sixteen uint16 elements into one uint256 word.
 */
library uint16a16Lib {
    /// @notice Number of bits per stored element.
    uint256 constant bits = 16;

    /// @notice Maximal number of elements stored.
    uint256 constant elements = 16;

    // must ensure that bits * elements <= 256

    /// @notice Range covered by stored element.
    uint256 constant range = 1 << bits;

    /// @notice Maximal value of stored element.
    uint256 constant max = range - 1;

    /**
     * @notice Gets element from packed array.
     * @param va Packed array.
     * @param index Index of element to get.
     * @return element Element of va stored in index index.
     */
    function get(uint16a16 va, uint256 index) internal pure returns (uint256) {
        require(index < elements);
        return (uint16a16.unwrap(va) >> (bits * index)) & max;
    }

    /**
     * @notice Sets element to packed array.
     * @param va Packed array.
     * @param index Index under which to store the element
     * @param ev Element to store.
     * @return va Packed array with stored element.
     */
    function set(uint16a16 va, uint256 index, uint256 ev) internal pure returns (uint16a16) {
        require(index < elements);
        require(ev < range);
        index *= bits;
        return uint16a16.wrap((uint16a16.unwrap(va) & ~(max << index)) | (ev << index));
    }

    /**
     * @notice Sets elements to packed array.
     * Elements are stored continuously from index 0 onwards.
     * @param va Packed array.
     * @param ev Elements to store.
     * @return va Packed array with stored elements.
     */
    function set(uint16a16 va, uint256[] memory ev) internal pure returns (uint16a16) {
        for (uint256 i; i < ev.length; ++i) {
            va = set(va, i, ev[i]);
        }

        return va;
    }
}

File 29 of 32 : ISpoolAccessControl.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

import "@openzeppelin-upgradeable/access/IAccessControlUpgradeable.sol";

/**
 * @notice Used when an account is missing a required role.
 * @param role Required role.
 * @param account Account missing the required role.
 */
error MissingRole(bytes32 role, address account);

/**
 * @notice Used when interacting with Spool when the system is paused.
 */
error SystemPaused();

/**
 * @notice Used when setting smart vault owner
 */
error SmartVaultOwnerAlreadySet(address smartVault);

/**
 * @notice Used when a contract tries to enter in a non-reentrant state.
 */
error ReentrantCall();

/**
 * @notice Used when a contract tries to call in a non-reentrant function and doesn't have the correct role.
 */
error NoReentrantRole();

/**
 * @notice thrown if unauthorized account tries to perform ownership transfer
 */
error OwnableUnauthorizedAccount(address account);

interface ISpoolAccessControl is IAccessControlUpgradeable {
    /* ========== VIEW FUNCTIONS ========== */

    /**
     * @notice Gets owner of a smart vault.
     * @param smartVault Smart vault.
     * @return owner Owner of the smart vault.
     */
    function smartVaultOwner(address smartVault) external view returns (address owner);

    /**
     * @notice Looks if an account has a role for a smart vault.
     * @param smartVault Address of the smart vault.
     * @param role Role to look for.
     * @param account Account to check.
     * @return hasRole True if account has the role for the smart vault, false otherwise.
     */
    function hasSmartVaultRole(address smartVault, bytes32 role, address account)
        external
        view
        returns (bool hasRole);

    /**
     * @notice Checks if an account is either Spool admin or admin for a smart vault.
     * @dev The function reverts if account is neither.
     * @param smartVault Address of the smart vault.
     * @param account to check.
     */
    function checkIsAdminOrVaultAdmin(address smartVault, address account) external view;

    /**
     * @notice Checks if system is paused or not.
     * @return isPaused True if system is paused, false otherwise.
     */
    function paused() external view returns (bool isPaused);

    /* ========== MUTATIVE FUNCTIONS ========== */

    /**
     * @notice Pauses the whole system.
     * @dev Requirements:
     * - caller must have role ROLE_PAUSER
     */
    function pause() external;

    /**
     * @notice Unpauses the whole system.
     * @dev Requirements:
     * - caller must have role ROLE_UNPAUSER
     */
    function unpause() external;

    /**
     * @notice Grants role to an account for a smart vault.
     * @dev Requirements:
     * - caller must have either role ROLE_SPOOL_ADMIN or role ROLE_SMART_VAULT_ADMIN for the smart vault
     * @param smartVault Address of the smart vault.
     * @param role Role to grant.
     * @param account Account to grant the role to.
     */
    function grantSmartVaultRole(address smartVault, bytes32 role, address account) external;

    /**
     * @notice Revokes role from an account for a smart vault.
     * @dev Requirements:
     * - caller must have either role ROLE_SPOOL_ADMIN or role ROLE_SMART_VAULT_ADMIN for the smart vault
     * @param smartVault Address of the smart vault.
     * @param role Role to revoke.
     * @param account Account to revoke the role from.
     */
    function revokeSmartVaultRole(address smartVault, bytes32 role, address account) external;

    /**
     * @notice Renounce role for a smart vault.
     * @param smartVault Address of the smart vault.
     * @param role Role to renounce.
     */
    function renounceSmartVaultRole(address smartVault, bytes32 role) external;

    /**
     * @notice Grant ownership to smart vault and assigns admin role.
     * @dev Ownership can only be granted once and it should be done at vault creation time.
     * @param smartVault Address of the smart vault.
     * @param owner address to which grant ownership to
     */
    function grantSmartVaultOwnership(address smartVault, address owner) external;

    /**
     * @notice Checks and reverts if a system has already entered in the non-reentrant state.
     */
    function checkNonReentrant() external view;

    /**
     * @notice Sets the entered flag to true when entering for the first time.
     * @dev Reverts if a system has already entered before.
     */
    function nonReentrantBefore() external;

    /**
     * @notice Resets the entered flag after the call is finished.
     */
    function nonReentrantAfter() external;

    /**
     * @notice Emitted when ownership of a smart vault is granted to an address
     * @param smartVault Smart vault address
     * @param address_ Address of the new smart vault owner
     */
    event SmartVaultOwnershipGranted(address indexed smartVault, address indexed address_);

    /**
     * @notice Smart vault specific role was granted
     * @param smartVault Smart vault address
     * @param role Role ID
     * @param account Account to which the role was granted
     */
    event SmartVaultRoleGranted(address indexed smartVault, bytes32 indexed role, address indexed account);

    /**
     * @notice Smart vault specific role was revoked
     * @param smartVault Smart vault address
     * @param role Role ID
     * @param account Account for which the role was revoked
     */
    event SmartVaultRoleRevoked(address indexed smartVault, bytes32 indexed role, address indexed account);

    /**
     * @notice Smart vault specific role was renounced
     * @param smartVault Smart vault address
     * @param role Role ID
     * @param account Account that renounced the role
     */
    event SmartVaultRoleRenounced(address indexed smartVault, bytes32 indexed role, address indexed account);

    /**
     * @notice SmartVault owner initiated transfer
     * @param previousOwner address
     * @param newOwner address
     */
    event SmartVaultOwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);

    /**
     * @notice Ownership transfer is finalized
     * @param previousOwner address
     * @param newOwner address
     */
    event SmartVaultOwnershipTransferred(address indexed previousOwner, address indexed newOwner);
}

File 30 of 32 : Roles.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

/**
 * @dev Grants permission to:
 * - acts as a default admin for other roles,
 * - can whitelist an action with action manager,
 * - can manage asset group registry.
 *
 * Is granted to the deployer of the SpoolAccessControl contract.
 *
 * Equals to the DEFAULT_ADMIN_ROLE of the OpenZeppelin AccessControl.
 */
bytes32 constant ROLE_SPOOL_ADMIN = 0x00;

/**
 * @dev Grants permission to integrate a new smart vault into the Spool ecosystem.
 *
 * Should be granted to smart vault factory contracts.
 */
bytes32 constant ROLE_SMART_VAULT_INTEGRATOR = keccak256("SMART_VAULT_INTEGRATOR");

/**
 * @dev Grants permission to
 * - manage rewards on smart vaults,
 * - manage roles on smart vaults,
 * - redeem for another user of a smart vault.
 */
bytes32 constant ROLE_SMART_VAULT_ADMIN = keccak256("SMART_VAULT_ADMIN");

/**
 * @dev Grants permission to manage allowlists with AllowlistGuard for a smart vault.
 *
 * Should be granted to whoever is in charge of maintaining allowlists with AllowlistGuard for a smart vault.
 */
bytes32 constant ROLE_GUARD_ALLOWLIST_MANAGER = keccak256("GUARD_ALLOWLIST_MANAGER");

/**
 * @dev Grants permission to manage assets on master wallet.
 *
 * Should be granted to:
 * - the SmartVaultManager contract,
 * - the StrategyRegistry contract,
 * - the DepositManager contract,
 * - the WithdrawalManager contract.
 */
bytes32 constant ROLE_MASTER_WALLET_MANAGER = keccak256("MASTER_WALLET_MANAGER");

/**
 * @dev Marks a contract as a smart vault manager.
 *
 * Should be granted to:
 * - the SmartVaultManager contract,
 * - the DepositManager contract.
 */
bytes32 constant ROLE_SMART_VAULT_MANAGER = keccak256("SMART_VAULT_MANAGER");

/**
 * @dev Marks a contract as a strategy registry.
 *
 * Should be granted to the StrategyRegistry contract.
 */
bytes32 constant ROLE_STRATEGY_REGISTRY = keccak256("STRATEGY_REGISTRY");

/**
 * @dev Grants permission to act as a risk provider.
 *
 * Should be granted to whoever is allowed to provide risk scores.
 */
bytes32 constant ROLE_RISK_PROVIDER = keccak256("RISK_PROVIDER");

/**
 * @dev Grants permission to act as an allocation provider.
 *
 * Should be granted to contracts that are allowed to calculate allocations.
 */
bytes32 constant ROLE_ALLOCATION_PROVIDER = keccak256("ALLOCATION_PROVIDER");

/**
 * @dev Grants permission to pause the system.
 */
bytes32 constant ROLE_PAUSER = keccak256("SYSTEM_PAUSER");

/**
 * @dev Grants permission to unpause the system.
 */
bytes32 constant ROLE_UNPAUSER = keccak256("SYSTEM_UNPAUSER");

/**
 * @dev Grants permission to manage rewards payment pool.
 */
bytes32 constant ROLE_REWARD_POOL_ADMIN = keccak256("REWARD_POOL_ADMIN");

/**
 * @dev Grants permission to reallocate smart vaults.
 */
bytes32 constant ROLE_REALLOCATOR = keccak256("REALLOCATOR");

/**
 * @dev Grants permission to be used as a strategy.
 */
bytes32 constant ROLE_STRATEGY = keccak256("STRATEGY");

/**
 * @dev Grants permission to manually set strategy apy.
 */
bytes32 constant ROLE_STRATEGY_APY_SETTER = keccak256("STRATEGY_APY_SETTER");

/**
 * @dev Grants permission to manage role ROLE_STRATEGY.
 */
bytes32 constant ADMIN_ROLE_STRATEGY = keccak256("ADMIN_STRATEGY");

/**
 * @dev Grants permission vault admins to allow redeem on behalf of other users.
 */
bytes32 constant ROLE_SMART_VAULT_ALLOW_REDEEM = keccak256("SMART_VAULT_ALLOW_REDEEM");

/**
 * @dev Grants permission to manage role ROLE_SMART_VAULT_ALLOW_REDEEM.
 */
bytes32 constant ADMIN_ROLE_SMART_VAULT_ALLOW_REDEEM = keccak256("ADMIN_SMART_VAULT_ALLOW_REDEEM");

/**
 * @dev Grants permission to run do hard work.
 */
bytes32 constant ROLE_DO_HARD_WORKER = keccak256("DO_HARD_WORKER");

/**
 * @dev Grants permission to immediately withdraw assets in case of emergency.
 */
bytes32 constant ROLE_EMERGENCY_WITHDRAWAL_EXECUTOR = keccak256("EMERGENCY_WITHDRAWAL_EXECUTOR");

/**
 * @dev Grants permission to swap with swapper.
 *
 * Should be granted to the DepositSwap contract.
 */
bytes32 constant ROLE_SWAPPER = keccak256("SWAPPER");

File 31 of 32 : AddressUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)

pragma solidity ^0.8.1;

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

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.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 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 32 of 32 : IAccessControlUpgradeable.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 IAccessControlUpgradeable {
    /**
     * @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;
}

Settings
{
  "remappings": [
    "ds-test/=lib/forge-std/lib/ds-test/src/",
    "forge-std/=lib/forge-std/src/",
    "@openzeppelin/=lib/openzeppelin-contracts/contracts/",
    "@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
    "@solmate/=lib/solmate/src/",
    "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
    "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/",
    "solmate/=lib/solmate/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 99999
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs"
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "london",
  "libraries": {
    "script/helper/ArraysHelper.sol": {
      "ArraysHelper": "0x7dF191D01252D2C0BABf77c725c63D69fe6EAA1a"
    },
    "src/libraries/ArrayMapping.sol": {
      "ArrayMappingUint256": "0xB6B08a9ed9282c695ddFbfA81d9ebF208b4c1De0"
    },
    "src/libraries/ReallocationLib.sol": {
      "ReallocationLib": "0x49CE6640EB2D89F80395C1a3695cc3a01b5C7bc1"
    },
    "src/libraries/SpoolUtils.sol": {
      "SpoolUtils": "0xCB3fECd1D5eabB1b18383604A28352c17Bdfef89"
    },
    "src/strategies/libraries/EthFrxEthAssetGroupAdapter.sol": {
      "EthFrxEthAssetGroupAdapter": "0xE23681bAA697EF5054E2D8C397ae59145027625D"
    },
    "src/strategies/libraries/EthStEthAssetGroupAdapter.sol": {
      "EthStEthAssetGroupAdapter": "0x1516eb3a54377168b6b184326200f4258C3529dF"
    }
  }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"contract IAssetGroupRegistry","name":"assetGroupRegistry_","type":"address"},{"internalType":"contract ISpoolAccessControl","name":"accessControl_","type":"address"},{"internalType":"uint256","name":"assetGroupId_","type":"uint256"},{"internalType":"contract IRocketSwapRouter","name":"rocketSwapRouter_","type":"address"},{"internalType":"address","name":"weth_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ConfigurationAddressZero","type":"error"},{"inputs":[{"internalType":"uint256","name":"assetGroupId","type":"uint256"}],"name":"InvalidAssetGroup","type":"error"},{"inputs":[],"name":"InvalidAssetGroupIdInitialization","type":"error"},{"inputs":[],"name":"InvalidConfiguration","type":"error"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"MissingRole","type":"error"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"NotFastRedeemer","type":"error"},{"inputs":[],"name":"REthHoldingBeforeDepositCheckFailed","type":"error"},{"inputs":[],"name":"REthHoldingBeforeRedeemalCheckFailed","type":"error"},{"inputs":[],"name":"REthHoldingDepositSlippagesFailed","type":"error"},{"inputs":[],"name":"REthHoldingRedeemSlippagesFailed","type":"error"},{"inputs":[],"name":"StrategyWorthIsZero","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"BeforeDepositCheckSlippages","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"ssts","type":"uint256"}],"name":"BeforeRedeemalCheckSlippages","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"mintedShares","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"usdWorthDeposited","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"assetsBeforeSwap","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"assetsDeposited","type":"uint256[]"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"strategy","type":"address"},{"indexed":false,"internalType":"uint256","name":"sharesMinted","type":"uint256"}],"name":"PlatformFeesCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"isDeposit","type":"bool"},{"indexed":false,"internalType":"uint256","name":"slippage","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"Slippages","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"withdrawnShares","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"usdWorthWithdrawn","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"withdrawnAssets","type":"uint256[]"}],"name":"Withdrawn","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"assetGroupId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"assetRatio","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"assets","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256[]","name":"slippages","type":"uint256[]"}],"name":"beforeDepositCheck","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"ssts","type":"uint256"},{"internalType":"uint256[]","name":"slippages","type":"uint256[]"}],"name":"beforeRedeemalCheck","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"smartVault","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimShares","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"assetGroup","type":"address[]"},{"internalType":"uint256[]","name":"exchangeRates","type":"uint256[]"},{"internalType":"contract IUsdPriceFeedManager","name":"priceFeedManager","type":"address"},{"internalType":"uint256[]","name":"slippages","type":"uint256[]"},{"components":[{"internalType":"address","name":"swapTarget","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"bytes","name":"swapCallData","type":"bytes"}],"internalType":"struct SwapInfo[]","name":"swapInfo","type":"tuple[]"}],"name":"depositFast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"swapTarget","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"bytes","name":"swapCallData","type":"bytes"}],"internalType":"struct SwapInfo[]","name":"swapInfo","type":"tuple[]"},{"components":[{"internalType":"address","name":"swapTarget","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"bytes","name":"swapCallData","type":"bytes"}],"internalType":"struct SwapInfo[]","name":"compoundSwapInfo","type":"tuple[]"},{"internalType":"uint256[]","name":"slippages","type":"uint256[]"},{"internalType":"address[]","name":"assetGroup","type":"address[]"},{"internalType":"uint256[]","name":"exchangeRates","type":"uint256[]"},{"internalType":"uint256","name":"withdrawnShares","type":"uint256"},{"internalType":"address","name":"masterWallet","type":"address"},{"internalType":"contract IUsdPriceFeedManager","name":"priceFeedManager","type":"address"},{"internalType":"int256","name":"baseYield","type":"int256"},{"components":[{"internalType":"address","name":"ecosystemFeeReceiver","type":"address"},{"internalType":"uint96","name":"ecosystemFeePct","type":"uint96"},{"internalType":"address","name":"treasuryFeeReceiver","type":"address"},{"internalType":"uint96","name":"treasuryFeePct","type":"uint96"}],"internalType":"struct PlatformFees","name":"platformFees","type":"tuple"}],"internalType":"struct StrategyDhwParameterBag","name":"dhwParams","type":"tuple"}],"name":"doHardWork","outputs":[{"components":[{"internalType":"uint256","name":"sharesMinted","type":"uint256"},{"internalType":"uint256[]","name":"assetsWithdrawn","type":"uint256[]"},{"internalType":"int256","name":"yieldPercentage","type":"int256"},{"internalType":"uint256","name":"valueAtDhw","type":"uint256"},{"internalType":"uint256","name":"totalSstsAtDhw","type":"uint256"}],"internalType":"struct DhwInfo","name":"dhwInfo","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"slippages","type":"uint256[]"},{"internalType":"address","name":"recipient","type":"address"}],"name":"emergencyWithdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getProtocolRewards","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getUnderlyingAssetAmounts","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"exchangeRates","type":"uint256[]"},{"internalType":"contract IUsdPriceFeedManager","name":"priceFeedManager","type":"address"}],"name":"getUsdWorth","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"strategyName_","type":"string"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rEthToken","outputs":[{"internalType":"contract IREthToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"masterWallet","type":"address"},{"internalType":"address[]","name":"assetGroup","type":"address[]"},{"internalType":"uint256[]","name":"slippages","type":"uint256[]"}],"name":"redeemFast","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"redeemer","type":"address"},{"internalType":"address[]","name":"assetGroup","type":"address[]"},{"internalType":"uint256[]","name":"slippages","type":"uint256[]"}],"name":"redeemShares","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"smartVault","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"releaseShares","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rocketSwapRouter","outputs":[{"internalType":"contract IRocketSwapRouter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"strategyName","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"weth","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]

6101406040523480156200001257600080fd5b50604051620060b0380380620060b0833981016040819052620000359162000194565b80858585816001600160a01b038116620000625760405163bb0e4c3560e01b815260040160405180910390fd5b6001600160a01b039081166080528316620000905760405163bb0e4c3560e01b815260040160405180910390fd5b6001600160a01b0392831660a05260c052508116620000c25760405163bb0e4c3560e01b815260040160405180910390fd5b6001600160a01b0390811660e0528216620000f05760405163bb0e4c3560e01b815260040160405180910390fd5b6001600160a01b038216610100819052604080516332a2a83960e21b8152905163ca8aa0e4916004808201926020929091908290030181865afa1580156200013c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000162919062000208565b6001600160a01b031661012052506200022f9350505050565b6001600160a01b03811681146200019157600080fd5b50565b600080600080600060a08688031215620001ad57600080fd5b8551620001ba816200017b565b6020870151909550620001cd816200017b565b604087015160608801519195509350620001e7816200017b565b6080870151909250620001fa816200017b565b809150509295509295909350565b6000602082840312156200021b57600080fd5b815162000228816200017b565b9392505050565b60805160a05160c05160e0516101005161012051615d89620003276000396000818161027701528181611c4c01528181612b4401528181612ce3015281816132e80152818161392001528181613c3a0152818161430701526144e7015260008181610595015281816139f701528181613c5b01528181613c8201528181614091015261438b01526000818161036b0152818161214e01528181612c0701528181612dd5015281816148ae015261495f015260008181610a5701528181610a8501526137350152600081816109440152818161207b01526137d30152600081816122c30152818161239f01526129e70152615d896000f3fe6080604052600436106101dc5760003560e01c806395d89b4111610102578063c1a7d80e11610095578063e173ad2511610064578063e173ad251461060a578063ebbd6bc71461061f578063f62d18881461063f578063fc195d8c1461065f57600080fd5b8063c1a7d80e14610543578063c8d31e7414610563578063d5ddd50314610583578063dd62ed3e146105b757600080fd5b8063a63e8c4b116100d1578063a63e8c4b146104cc578063a9059cbb146104e1578063beef8a7114610501578063c1a0ff4c1461052e57600080fd5b806395d89b41146104545780639b6aaa4714610469578063a3ea6c971461048c578063a457c2d7146104ac57600080fd5b8063313ce5671161017a57806370a082311161014957806370a08231146103ad57806371a97305146103f05780637817bf4a1461041257806384ba89e31461043f57600080fd5b8063313ce5671461031d57806339509351146103395780633fc8cef3146103595780636c60d9e71461038d57600080fd5b806317094411116101b6578063170944111461026557806318160ddd146102be57806323b872dd146102dd5780632ce5183f146102fd57600080fd5b806306fdde03146101e8578063095ea7b3146102135780631480fce31461024357600080fd5b366101e357005b600080fd5b3480156101f457600080fd5b506101fd61067f565b60405161020a9190614f3f565b60405180910390f35b34801561021f57600080fd5b5061023361022e366004614f77565b610711565b604051901515815260200161020a565b34801561024f57600080fd5b5061026361025e366004614fef565b61072b565b005b34801561027157600080fd5b506102997f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161020a565b3480156102ca57600080fd5b506035545b60405190815260200161020a565b3480156102e957600080fd5b506102336102f836600461503b565b610859565b34801561030957600080fd5b50610263610318366004614f77565b61087f565b34801561032957600080fd5b506040516012815260200161020a565b34801561034557600080fd5b50610233610354366004614f77565b6108b6565b34801561036557600080fd5b506102997f000000000000000000000000000000000000000000000000000000000000000081565b34801561039957600080fd5b506102636103a836600461507c565b610902565b3480156103b957600080fd5b506102cf6103c83660046150d3565b73ffffffffffffffffffffffffffffffffffffffff1660009081526033602052604090205490565b3480156103fc57600080fd5b50610405610940565b60405161020a9190615141565b34801561041e57600080fd5b5061043261042d366004615154565b610a0c565b60405161020a9190615210565b34801561044b57600080fd5b506102cf610a52565b34801561046057600080fd5b506101fd610aa7565b34801561047557600080fd5b5061047e610ab6565b60405161020a929190615223565b34801561049857600080fd5b506102cf6104a7366004615251565b610acd565b3480156104b857600080fd5b506102336104c7366004614f77565b610ea9565b3480156104d857600080fd5b50610432610f8a565b3480156104ed57600080fd5b506102336104fc366004614f77565b610fd5565b34801561050d57600080fd5b5061052161051c36600461532c565b610fe3565b60405161020a9190615368565b34801561053a57600080fd5b50610432611bfb565b34801561054f57600080fd5b5061026361055e3660046154c5565b611d55565b34801561056f57600080fd5b506102cf61057e366004615521565b611ebf565b34801561058f57600080fd5b506102997f000000000000000000000000000000000000000000000000000000000000000081565b3480156105c357600080fd5b506102cf6105d2366004615573565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260346020908152604080832093909416825291909152205490565b34801561061657600080fd5b506101fd611f02565b34801561062b57600080fd5b5061026361063a366004614f77565b611f11565b34801561064b57600080fd5b5061026361065a3660046155a1565b611f48565b34801561066b57600080fd5b5061043261067a366004615154565b61226c565b60606036805461068e90615654565b80601f01602080910402602001604051908101604052809291908181526020018280546106ba90615654565b80156107075780601f106106dc57610100808354040283529160200191610707565b820191906000526020600020905b8154815290600101906020018083116106ea57829003601f168201915b5050505050905090565b60003361071f818585612469565b60019150505b92915050565b32610768576040518381527f0d4fc85ef490d33e67589b22099d3954a460f3ab83b44caad5335d7f2f2c635a9060200160405180910390a1505050565b600060028383600081811061077f5761077f6156a7565b9050602002013510156107ad578282600281811061079f5761079f6156a7565b905060200201359050610813565b828260008181106107c0576107c06156a7565b905060200201356002036107e1578282600181811061079f5761079f6156a7565b6040517fb23013d500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61081d818561261c565b610853576040517fb23013d500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505050565b600033610867858285612654565b610872858585612725565b60019150505b9392505050565b7f72f6db45f6a510a9f71a52fed1cf587666e7205545d52ee38b978222d6abaa56336108ab828261299b565b610853308585612725565b33600081815260346020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716845290915281205490919061071f90829086906108fd908790615705565b612469565b7fd00dd30c0065f8d96fd9ccd8989cf005814e3a3d416d39c86ff1cd1f1ba7ded23361092e828261299b565b610939858585612aa7565b5050505050565b60607f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663dd0fe31a610986610a52565b6040518263ffffffff1660e01b81526004016109a491815260200190565b600060405180830381865afa1580156109c1573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610a079190810190615718565b905090565b6060610a387fd00dd30c0065f8d96fd9ccd8989cf005814e3a3d416d39c86ff1cd1f1ba7ded23361299b565b610a4787878888888888612c2e565b979650505050505050565b6000807f000000000000000000000000000000000000000000000000000000000000000011610a82575060975490565b507f000000000000000000000000000000000000000000000000000000000000000090565b60606037805461068e90615654565b6060803215610ac457600080fd5b50606091829150565b60007f72f6db45f6a510a9f71a52fed1cf587666e7205545d52ee38b978222d6abaa5633610afb828261299b565b60008b67ffffffffffffffff811115610b1657610b166153b8565b604051908082528060200260200182016040528015610b3f578160200160208202803683370190505b50905060005b8c811015610c31578d8d82818110610b5f57610b5f6156a7565b9050602002016020810190610b7491906150d3565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015610be0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c0491906157a7565b828281518110610c1657610c166156a7565b6020908102919091010152610c2a816157c0565b9050610b45565b50610c6b8d8d8080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050505050565b60008c67ffffffffffffffff811115610c8657610c866153b8565b604051908082528060200260200182016040528015610caf578160200160208202803683370190505b50905060005b8d811015610da1578e8e82818110610ccf57610ccf6156a7565b9050602002016020810190610ce491906150d3565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015610d50573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d7491906157a7565b828281518110610d8657610d866156a7565b6020908102919091010152610d9a816157c0565b9050610cb5565b506000610de28d8d808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508f9250612c9b915050565b9050610df18f8f848d8d612eb5565b6000610e3e8e8e80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050508d612c9b565b90506000610e4c8383612f9d565b90507fef3370e7b2b7c0f8f907344d48c31e679e6ff7ff055cad7846daf5ca2d5b30dc81610e7a85856157f8565b8787604051610e8c949392919061580b565b60405180910390a196505050505050509998505050505050505050565b33600081815260346020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716845290915281205490919083811015610f72576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f00000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b610f7f8286868403612469565b506001949350505050565b6040805160018082528183019092526060916000919060208083019080368337019050509050600181600081518110610fc557610fc56156a7565b6020908102919091010152919050565b60003361071f818585612725565b6110156040518060a0016040528060008152602001606081526020016000815260200160008152602001600081525090565b61103f7fd00dd30c0065f8d96fd9ccd8989cf005814e3a3d416d39c86ff1cd1f1ba7ded23361299b565b60008061104f606085018561583c565b905067ffffffffffffffff811115611069576110696153b8565b604051908082528060200260200182016040528015611092578160200160208202803683370190505b50905060005b6110a5606086018661583c565b90508110156111bd576110bb606086018661583c565b828181106110cb576110cb6156a7565b90506020020160208101906110e091906150d3565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa15801561114c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061117091906157a7565b828281518110611182576111826156a7565b60200260200101818152505060008282815181106111a2576111a26156a7565b602002602001015111156111b557600192505b600101611098565b506111cf8161055e604087018761583c565b6111e460a085013561025e604087018761583c565b6111ec614eb3565b6111fa85610100013561302b565b60408501526000611237611211606088018861583c565b61121e60208a018a61583c565b61122b60408c018c61583c565b60009695505050505050565b905064e8d4a5100085604001518261124f91906158a4565b611259919061591f565b6112639082615987565b856040018181516112749190615987565b90525050604084015161128b906101208701613050565b506112e461129c608087018761583c565b808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506112df92505050610100880160e089016150d3565b612c9b565b81526000808060a088013586156114355760006113086101008b0160e08c016150d3565b73ffffffffffffffffffffffffffffffffffffffff1663a2d2657f61133060608d018d61583c565b8a8e8060800190611341919061583c565b6040518663ffffffff1660e01b81526004016113619594939291906159a7565b602060405180830381865afa15801561137e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113a291906157a7565b905064e8d4a510006113b360355490565b10156113cc576113c5816103e8615a5f565b935061141f565b8551156113ed5785516035546113e3908390615a5f565b6113c59190615a76565b6040517f907d9a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8184101561142f57839450611433565b8194505b505b600061144460608b018b61583c565b905067ffffffffffffffff81111561145e5761145e6153b8565b604051908082528060200260200182016040528015611487578160200160208202803683370190505b5090506000828511156118d25785156115485760005b6114aa60608d018d61583c565b90508110156115425785878a83815181106114c7576114c76156a7565b602002602001015102816114dd576114dd6158f0565b048382815181106114f0576114f06156a7565b60200260200101818152505082818151811061150e5761150e6156a7565b6020026020010151898281518110611528576115286156a7565b60209081029190910101805191909103905260010161149d565b50600190505b6000885167ffffffffffffffff811115611564576115646153b8565b60405190808252806020026020018201604052801561158d578160200160208202803683370190505b509050600061159c8d8061583c565b90501115611772576116056115b460608e018e61583c565b80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050508a8e8060000190610853919061583c565b60005b61161560608e018e61583c565b905081101561176c57898181518110611630576116306156a7565b602002602001015182828151811061164a5761164a6156a7565b602002602001018181525050838181518110611668576116686156a7565b60200260200101518d8060600190611680919061583c565b83818110611690576116906156a7565b90506020020160208101906116a591906150d3565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015611711573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061173591906157a7565b61173f91906157f8565b8a8281518110611751576117516156a7565b6020908102919091010152611765816157c0565b9050611608565b506117d4565b60005b61178260608e018e61583c565b90508110156117d25789818151811061179d5761179d6156a7565b60200260200101518282815181106117b7576117b76156a7565b60209081029190910101526117cb816157c0565b9050611775565b505b6117fa6117e460608e018e61583c565b8b8f80604001906117f5919061583c565b612eb5565b61185d61180a60808e018e61583c565b80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050508d60e00160208101906112df91906150d3565b60208901819052885161186f91612f9d565b885160208a01519196507fef3370e7b2b7c0f8f907344d48c31e679e6ff7ff055cad7846daf5ca2d5b30dc9187916118a6916157f8565b838c6040516118b8949392919061580b565b60405180910390a16118ca8786615705565b945050611b29565b84831115611aba5785156118e95785830392508593505b61190f6118f960608d018d61583c565b858e806040019061190a919061583c565b6131ca565b61191930846133c7565b50600161196f61192c60808d018d61583c565b808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506112df925050506101008e0160e08f016150d3565b602088015260005b61198460608d018d61583c565b9050811015611a745761199a60608d018d61583c565b828181106119aa576119aa6156a7565b90506020020160208101906119bf91906150d3565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015611a2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a4f91906157a7565b838281518110611a6157611a616156a7565b6020908102919091010152600101611977565b5060208701516040517f8b1b306a5c3a19617717b1f37f2b4bea82978af8db2e859975f665fc40bc74b391611aad918691908690615a8a565b60405180910390a1611b29565b8515611b215782935060005b611ad360608d018d61583c565b9050811015611b1b57888181518110611aee57611aee6156a7565b6020026020010151838281518110611b0857611b086156a7565b6020908102919091010152600101611ac6565b50600190505b865160208801525b8015611bd15760005b611b3f60608d018d61583c565b9050811015611bcf57611bc7611b5b60e08e0160c08f016150d3565b848381518110611b6d57611b6d6156a7565b60200260200101518e8060600190611b85919061583c565b85818110611b9557611b956156a7565b9050602002016020810190611baa91906150d3565b73ffffffffffffffffffffffffffffffffffffffff16919061358d565b600101611b32565b505b838a526020808b0183905287015160608b015260355460808b015250979998505050505050505050565b60408051600180825281830190925260609160208083019080368337019050506040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529091507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1690638b32fa239082906370a0823190602401602060405180830381865afa158015611cb0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cd491906157a7565b6040518263ffffffff1660e01b8152600401611cf291815260200190565b602060405180830381865afa158015611d0f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d3391906157a7565b81600081518110611d4657611d466156a7565b60200260200101818152505090565b32611df3576040805160018082528183019092526000916020808301908036833701905050905083600081518110611d8f57611d8f6156a7565b602002602001015181600081518110611daa57611daa6156a7565b6020026020010181815250507f2da97ade1b5402d92aa4ecf88302c7c6a98498999fe040468284622731550b3681604051611de59190615210565b60405180910390a150505050565b600282826000818110611e0857611e086156a7565b905060200201351115611e47576040517fdfc8737800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611e8482826001818110611e5d57611e5d6156a7565b9050602002013584600081518110611e7757611e776156a7565b602002602001015161261c565b611eba576040517fdfc8737800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505050565b60007f72f6db45f6a510a9f71a52fed1cf587666e7205545d52ee38b978222d6abaa5633611eed828261299b565b611ef78585612c9b565b92505b505092915050565b60606096805461068e90615654565b7f72f6db45f6a510a9f71a52fed1cf587666e7205545d52ee38b978222d6abaa5633611f3d828261299b565b610853843085612725565b600054610100900460ff1615808015611f685750600054600160ff909116105b80611f825750303b158015611f82575060005460ff166001145b61200e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610f69565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561206c57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b612077826000613661565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663dd0fe31a6120bd610a52565b6040518263ffffffff1660e01b81526004016120db91815260200190565b600060405180830381865afa1580156120f8573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261213e9190810190615718565b9050805160011415806121b757507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1681600081518110612196576121966156a7565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1614155b156121f9576121c4610a52565b6040517f4b1f57ad000000000000000000000000000000000000000000000000000000008152600401610f6991815260200190565b6122016138e7565b60985550801561226857600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6040517f91d148540000000000000000000000000000000000000000000000000000000081527f72f6db45f6a510a9f71a52fed1cf587666e7205545d52ee38b978222d6abaa5660048201523360248201526060907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16906391d1485490604401602060405180830381865afa15801561231f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123439190615aa9565b15801561242157506040517f91d148540000000000000000000000000000000000000000000000000000000081527fd00dd30c0065f8d96fd9ccd8989cf005814e3a3d416d39c86ff1cd1f1ba7ded260048201523360248201527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16906391d1485490604401602060405180830381865afa1580156123fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061241f9190615aa9565b155b1561245a576040517f7ab612a3000000000000000000000000000000000000000000000000000000008152336004820152602401610f69565b610a4787308888888888612c2e565b73ffffffffffffffffffffffffffffffffffffffff831661250b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f72657373000000000000000000000000000000000000000000000000000000006064820152608401610f69565b73ffffffffffffffffffffffffffffffffffffffff82166125ae576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f73730000000000000000000000000000000000000000000000000000000000006064820152608401610f69565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526034602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b600061263a60017001000000000000000000000000000000006157f8565b831682108061264c5750608083901c82115b159392505050565b73ffffffffffffffffffffffffffffffffffffffff8381166000908152603460209081526040808320938616835292905220547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146108535781811015612718576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610f69565b6108538484848403612469565b73ffffffffffffffffffffffffffffffffffffffff83166127c8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f64726573730000000000000000000000000000000000000000000000000000006064820152608401610f69565b73ffffffffffffffffffffffffffffffffffffffff821661286b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f65737300000000000000000000000000000000000000000000000000000000006064820152608401610f69565b73ffffffffffffffffffffffffffffffffffffffff831660009081526033602052604090205481811015612921576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e742065786365656473206260448201527f616c616e636500000000000000000000000000000000000000000000000000006064820152608401610f69565b73ffffffffffffffffffffffffffffffffffffffff80851660008181526033602052604080822086860390559286168082529083902080548601905591517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9061298e9086815260200190565b60405180910390a3610853565b6040517f91d148540000000000000000000000000000000000000000000000000000000081526004810183905273ffffffffffffffffffffffffffffffffffffffff82811660248301527f000000000000000000000000000000000000000000000000000000000000000016906391d1485490604401602060405180830381865afa158015612a2e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a529190615aa9565b612268576040517f75000dc00000000000000000000000000000000000000000000000000000000081526004810183905273ffffffffffffffffffffffffffffffffffffffff82166024820152604401610f69565b82826000818110612aba57612aba6156a7565b90506020020135600314612afa576040517ffc5fbdf300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152600090612beb9073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa158015612b8b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612baf91906157a7565b858580806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250600192506139a0915050565b905061085373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016838361358d565b60606000612c3f86868b8787613dd9565b9050612c4b888a6133c7565b60005b85811015612c8e57612c8688838381518110612c6c57612c6c6156a7565b6020026020010151898985818110611b9557611b956156a7565b600101612c4e565b5098975050505050505050565b6040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152600090819073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690638b32fa239082906370a0823190602401602060405180830381865afa158015612d32573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d5691906157a7565b6040518263ffffffff1660e01b8152600401612d7491815260200190565b602060405180830381865afa158015612d91573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612db591906157a7565b90508273ffffffffffffffffffffffffffffffffffffffff16639115900c7f00000000000000000000000000000000000000000000000000000000000000008387600081518110612e0857612e086156a7565b60209081029190910101516040517fffffffff0000000000000000000000000000000000000000000000000000000060e086901b16815273ffffffffffffffffffffffffffffffffffffffff909316600484015260248301919091526044820152606401602060405180830381865afa158015612e89573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ead91906157a7565b949350505050565b600082826000818110612eca57612eca6156a7565b90506020020135600003612ee057506003612f3b565b82826000818110612ef357612ef36156a7565b90506020020135600203612f0957506002612f3b565b6040517f0947e0a500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612f9484600081518110612f5157612f516156a7565b602002602001015184848080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525086925061403a915050565b50505050505050565b600080612fa960355490565b905064e8d4a51000811015612ff157612fc46103e884615a5f565b915064e8d4a5100081900380831015612fda5750815b8083039250612feb61dead82614573565b5061301a565b83156113ed57838161300382866157f8565b61300d9190615a5f565b6130179190615a76565b91505b6130243083614573565b5092915050565b6000806130366138e7565b905061304460985482614668565b60989190915592915050565b6000808313156107255782600061306e64e8d4a51000612710615a5f565b61307e6080860160608701615acb565b6bffffffffffffffffffffffff1661309c6040870160208801615acb565b6130b6906bffffffffffffffffffffffff166127106157f8565b6130c091906157f8565b6130ca9084615a5f565b6130d49190615705565b90506000826130e260355490565b6130ec9190615a5f565b90506000826131016040880160208901615acb565b613119906bffffffffffffffffffffffff1684615a5f565b6131239190615a76565b905061313b61313560208801886150d3565b82614573565b60008361314e6080890160608a01615acb565b613166906bffffffffffffffffffffffff1685615a5f565b6131709190615a76565b90506131856131356060890160408a016150d3565b604051828201808252965030907fd02a13a1b75d55c8b9ced16f3558e9004a0827559f52715b34bcc685d904c4599060200160405180910390a2505050505092915050565b6000828260008181106131df576131df6156a7565b905060200201356001036131f5575060036132ad565b82826000818110613208576132086156a7565b9050602002013560020361321e575060026132ad565b82826000818110613231576132316156a7565b90506020020135600303613247575060016132ad565b8282600081811061325a5761325a6156a7565b90506020020135600014801561326e575032155b1561327b575060036132ad565b6040517ffc5fbdf300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006132b860355490565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015286907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16906370a0823190602401602060405180830381865afa158015613344573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061336891906157a7565b6133729190615a5f565b61337c9190615a76565b90506133bd818585808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508792506139a0915050565b5050505050505050565b73ffffffffffffffffffffffffffffffffffffffff821661346a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f2061646472657360448201527f73000000000000000000000000000000000000000000000000000000000000006064820152608401610f69565b73ffffffffffffffffffffffffffffffffffffffff821660009081526033602052604090205481811015613520576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e60448201527f63650000000000000000000000000000000000000000000000000000000000006064820152608401610f69565b73ffffffffffffffffffffffffffffffffffffffff831660008181526033602090815260408083208686039055603580548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3505050565b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052611eba9084907fa9059cbb00000000000000000000000000000000000000000000000000000000906064015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526146d4565b600054610100900460ff166136f8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610f69565b8151600003613733576040517fc52a9bd300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000000613799578061378f576040517f36307dff00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60978190556137d1565b80156137d1576040517f36307dff00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663e065ce6e613815610a52565b6040518263ffffffff1660e01b815260040161383391815260200190565b60006040518083038186803b15801561384b57600080fd5b505afa15801561385f573d6000803e3d6000fd5b5050505081609690816138729190615b3f565b506122686040518060400160405280601481526020017f537472617465677920536861726520546f6b656e0000000000000000000000008152506040518060400160405280600381526020017f53535400000000000000000000000000000000000000000000000000000000008152506147e0565b6040517f8b32fa23000000000000000000000000000000000000000000000000000000008152670de0b6b3a764000060048201526000907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1690638b32fa2390602401602060405180830381865afa15801561397c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a0791906157a7565b600032613c35576040517f2f18e2af00000000000000000000000000000000000000000000000000000000815260048101859052600a6024820152600090819073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690632f18e2af906044016060604051808303816000875af1158015613a40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613a649190615c59565b90925090508160006020020151858581518110613a8357613a836156a7565b6020908102919091010152816001602002015185613aa2866001615705565b81518110613ab257613ab26156a7565b60209081029190910101528085613aca866002615705565b81518110613ada57613ada6156a7565b60209081029190910101528085613af2866003615705565b81518110613b0257613b026156a7565b602090810291909101015260408051600480825260a0820190925260009181602001602082028036833750508451825192935091839150600090613b4857613b486156a7565b6020908102919091010152826001602002015181600181518110613b6e57613b6e6156a7565b6020026020010181815250508181600281518110613b8e57613b8e6156a7565b6020026020010181815250508181600381518110613bae57613bae6156a7565b6020026020010181815250507f872697b0ffc2830e88f47a2ddac8d40ec8898cb72bc036d3aaf8ce7960286a9060008083604051602001613bef9190615210565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815290829052613c29939291615cde565b60405180910390a15050505b613c807f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000086614881565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a824ae8b848481518110613cce57613cce6156a7565b602002602001015185856001613ce49190615705565b81518110613cf457613cf46156a7565b602002602001015186866002613d0a9190615705565b81518110613d1a57613d1a6156a7565b602002602001015187876003613d309190615705565b81518110613d4057613d406156a7565b60209081029190910101516040517fffffffff0000000000000000000000000000000000000000000000000000000060e087901b16815260048101949094526024840192909252604483015260648201526084810187905260a401600060405180830381600087803b158015613db557600080fd5b505af1158015613dc9573d6000803e3d6000fd5b50505050479050610878816148ac565b60608467ffffffffffffffff811115613df457613df46153b8565b604051908082528060200260200182016040528015613e1d578160200160208202803683370190505b50905060005b85811015613f0f57868682818110613e3d57613e3d6156a7565b9050602002016020810190613e5291906150d3565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015613ebe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613ee291906157a7565b828281518110613ef457613ef46156a7565b6020908102919091010152613f08816157c0565b9050613e23565b50613f1d86868686866131ca565b60005b8581101561403057818181518110613f3a57613f3a6156a7565b6020026020010151878783818110613f5457613f546156a7565b9050602002016020810190613f6991906150d3565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015613fd5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613ff991906157a7565b61400391906157f8565b828281518110614015576140156156a7565b6020908102919091010152614029816157c0565b9050613f20565b5095945050505050565b6000326142d0576040517f27e0416300000000000000000000000000000000000000000000000000000000815260048101859052600a6024820152600090819073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016906327e04163906044016060604051808303816000875af11580156140da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140fe9190615c59565b9092509050816000602002015185858151811061411d5761411d6156a7565b602090810291909101015281600160200201518561413c866001615705565b8151811061414c5761414c6156a7565b60209081029190910101528085614164866002615705565b81518110614174576141746156a7565b6020908102919091010152808561418c866003615705565b8151811061419c5761419c6156a7565b602090810291909101015260408051600480825260a08201909252600091816020016020820280368337505084518251929350918391506000906141e2576141e26156a7565b6020908102919091010152826001602002015181600181518110614208576142086156a7565b6020026020010181815250508181600281518110614228576142286156a7565b6020026020010181815250508181600381518110614248576142486156a7565b6020026020010181815250507f872697b0ffc2830e88f47a2ddac8d40ec8898cb72bc036d3aaf8ce7960286a90600160008360405160200161428a9190615210565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152908290526142c4939291615cde565b60405180910390a15050505b6142d984614930565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16906370a0823190602401602060405180830381865afa158015614363573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061438791906157a7565b90507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166355362f4d858585815181106143d8576143d86156a7565b6020026020010151868660016143ee9190615705565b815181106143fe576143fe6156a7565b6020026020010151878760026144149190615705565b81518110614424576144246156a7565b60200260200101518888600361443a9190615705565b8151811061444a5761444a6156a7565b60200260200101516040518663ffffffff1660e01b8152600401614487949392919093845260208401929092526040830152606082015260800190565b6000604051808303818588803b1580156144a057600080fd5b505af11580156144b4573d6000803e3d6000fd5b50506040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201528493507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1692506370a082319150602401602060405180830381865afa158015614545573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061456991906157a7565b612ead91906157f8565b73ffffffffffffffffffffffffffffffffffffffff82166145f0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610f69565b80603560008282546146029190615705565b909155505073ffffffffffffffffffffffffffffffffffffffff82166000818152603360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b60008282111561469e578264e8d4a5100061468382856157f8565b61468d9190615a5f565b6146979190615a76565b9050610725565b81831115610725578264e8d4a510006146b784836157f8565b6146c19190615a5f565b6146cb9190615a76565b61087890615cff565b6000614736826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff166149cc9092919063ffffffff16565b805190915015611eba57808060200190518101906147549190615aa9565b611eba576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152608401610f69565b600054610100900460ff16614877576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610f69565b61226882826149db565b61488b8383614a8b565b611eba73ffffffffffffffffffffffffffffffffffffffff84168383614b49565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561491457600080fd5b505af1158015614928573d6000803e3d6000fd5b505050505050565b6040517f2e1a7d4d000000000000000000000000000000000000000000000000000000008152600481018290527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1690632e1a7d4d90602401600060405180830381600087803b1580156149b857600080fd5b505af1158015610939573d6000803e3d6000fd5b6060612ead8484600085614ccb565b600054610100900460ff16614a72576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610f69565b6036614a7e8382615b3f565b506037611eba8282615b3f565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff82811660248301526000919084169063dd62ed3e90604401602060405180830381865afa158015614b01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614b2591906157a7565b11156122685761226873ffffffffffffffffffffffffffffffffffffffff83168260005b801580614be957506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff838116602483015284169063dd62ed3e90604401602060405180830381865afa158015614bc3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614be791906157a7565b155b614c75576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527f20746f206e6f6e2d7a65726f20616c6c6f77616e6365000000000000000000006064820152608401610f69565b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052611eba9084907f095ea7b300000000000000000000000000000000000000000000000000000000906064016135df565b606082471015614d5d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610f69565b6000808673ffffffffffffffffffffffffffffffffffffffff168587604051614d869190615d37565b60006040518083038185875af1925050503d8060008114614dc3576040519150601f19603f3d011682016040523d82523d6000602084013e614dc8565b606091505b5091509150610a478783838760608315614e6a578251600003614e635773ffffffffffffffffffffffffffffffffffffffff85163b614e63576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610f69565b5081612ead565b612ead8383815115614e7f5781518083602001fd5b806040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f699190614f3f565b60405180604001604052806002906020820280368337509192915050565b60005b83811015614eec578181015183820152602001614ed4565b50506000910152565b60008151808452614f0d816020860160208601614ed1565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006108786020830184614ef5565b73ffffffffffffffffffffffffffffffffffffffff81168114614f7457600080fd5b50565b60008060408385031215614f8a57600080fd5b8235614f9581614f52565b946020939093013593505050565b60008083601f840112614fb557600080fd5b50813567ffffffffffffffff811115614fcd57600080fd5b6020830191508360208260051b8501011115614fe857600080fd5b9250929050565b60008060006040848603121561500457600080fd5b83359250602084013567ffffffffffffffff81111561502257600080fd5b61502e86828701614fa3565b9497909650939450505050565b60008060006060848603121561505057600080fd5b833561505b81614f52565b9250602084013561506b81614f52565b929592945050506040919091013590565b60008060006040848603121561509157600080fd5b833567ffffffffffffffff8111156150a857600080fd5b6150b486828701614fa3565b90945092505060208401356150c881614f52565b809150509250925092565b6000602082840312156150e557600080fd5b813561087881614f52565b600081518084526020808501945080840160005b8381101561513657815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101615104565b509495945050505050565b60208152600061087860208301846150f0565b6000806000806000806080878903121561516d57600080fd5b86359550602087013561517f81614f52565b9450604087013567ffffffffffffffff8082111561519c57600080fd5b6151a88a838b01614fa3565b909650945060608901359150808211156151c157600080fd5b506151ce89828a01614fa3565b979a9699509497509295939492505050565b600081518084526020808501945080840160005b83811015615136578151875295820195908201906001016151f4565b60208152600061087860208301846151e0565b60408152600061523660408301856150f0565b828103602084015261524881856151e0565b95945050505050565b600080600080600080600080600060a08a8c03121561526f57600080fd5b893567ffffffffffffffff8082111561528757600080fd5b6152938d838e01614fa3565b909b50995060208c01359150808211156152ac57600080fd5b6152b88d838e01614fa3565b909950975060408c013591506152cd82614f52565b90955060608b013590808211156152e357600080fd5b6152ef8d838e01614fa3565b909650945060808c013591508082111561530857600080fd5b506153158c828d01614fa3565b915080935050809150509295985092959850929598565b60006020828403121561533e57600080fd5b813567ffffffffffffffff81111561535557600080fd5b82016101a0818503121561087857600080fd5b60208152815160208201526000602083015160a0604084015261538e60c08401826151e0565b90506040840151606084015260608401516080840152608084015160a08401528091505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561542e5761542e6153b8565b604052919050565b600067ffffffffffffffff821115615450576154506153b8565b5060051b60200190565b600082601f83011261546b57600080fd5b8135602061548061547b83615436565b6153e7565b82815260059290921b8401810191818101908684111561549f57600080fd5b8286015b848110156154ba57803583529183019183016154a3565b509695505050505050565b6000806000604084860312156154da57600080fd5b833567ffffffffffffffff808211156154f257600080fd5b6154fe8783880161545a565b9450602086013591508082111561551457600080fd5b5061502e86828701614fa3565b6000806040838503121561553457600080fd5b823567ffffffffffffffff81111561554b57600080fd5b6155578582860161545a565b925050602083013561556881614f52565b809150509250929050565b6000806040838503121561558657600080fd5b823561559181614f52565b9150602083013561556881614f52565b600060208083850312156155b457600080fd5b823567ffffffffffffffff808211156155cc57600080fd5b818501915085601f8301126155e057600080fd5b8135818111156155f2576155f26153b8565b615622847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116016153e7565b9150808252868482850101111561563857600080fd5b8084840185840137600090820190930192909252509392505050565b600181811c9082168061566857607f821691505b6020821081036156a1577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80820180821115610725576107256156d6565b6000602080838503121561572b57600080fd5b825167ffffffffffffffff81111561574257600080fd5b8301601f8101851361575357600080fd5b805161576161547b82615436565b81815260059190911b8201830190838101908783111561578057600080fd5b928401925b82841015610a4757835161579881614f52565b82529284019290840190615785565b6000602082840312156157b957600080fd5b5051919050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036157f1576157f16156d6565b5060010190565b81810381811115610725576107256156d6565b84815283602082015260806040820152600061582a60808301856151e0565b8281036060840152610a4781856151e0565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261587157600080fd5b83018035915067ffffffffffffffff82111561588c57600080fd5b6020019150600581901b3603821315614fe857600080fd5b808202600082127f8000000000000000000000000000000000000000000000000000000000000000841416156158dc576158dc6156d6565b8181058314821517610725576107256156d6565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60008261592e5761592e6158f0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f800000000000000000000000000000000000000000000000000000000000000083141615615982576159826156d6565b500590565b8082018281126000831280158216821582161715611efa57611efa6156d6565b6060808252810185905260008660808301825b888110156159f75782356159cd81614f52565b73ffffffffffffffffffffffffffffffffffffffff168252602092830192909101906001016159ba565b508381036020850152615a0a81886151e0565b91505082810360408401528381527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff841115615a4557600080fd5b8360051b8086602084013701602001979650505050505050565b8082028115828204841417610725576107256156d6565b600082615a8557615a856158f0565b500490565b83815282602082015260606040820152600061524860608301846151e0565b600060208284031215615abb57600080fd5b8151801515811461087857600080fd5b600060208284031215615add57600080fd5b81356bffffffffffffffffffffffff8116811461087857600080fd5b601f821115611eba57600081815260208120601f850160051c81016020861015615b205750805b601f850160051c820191505b8181101561492857828155600101615b2c565b815167ffffffffffffffff811115615b5957615b596153b8565b615b6d81615b678454615654565b84615af9565b602080601f831160018114615bc05760008415615b8a5750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555614928565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b82811015615c0d57888601518255948401946001909101908401615bee565b5085821015615c4957878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b60008060608385031215615c6c57600080fd5b83601f840112615c7b57600080fd5b6040516040810181811067ffffffffffffffff82111715615c9e57615c9e6153b8565b8060405250806040850186811115615cb557600080fd5b855b81811015615ccf578051835260209283019201615cb7565b50519196919550909350505050565b83151581528260208201526060604082015260006152486060830184614ef5565b60007f80000000000000000000000000000000000000000000000000000000000000008203615d3057615d306156d6565b5060000390565b60008251615d49818460208701614ed1565b919091019291505056fea26469706673582212205740574dd20ba806085e79746ceccc677ad2b2edf42947d6d5255ed62ba8944c64736f6c634300081100330000000000000000000000001aa2a802ba25669531ffd2b1ff8ae94f3d87f41a0000000000000000000000007b533e72e0cdc63aacd8cdb926ac402b846fbd13000000000000000000000000000000000000000000000000000000000000000100000000000000000000000016d5a408e807db8ef7c578279beeee6b228f1c1c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2

Deployed Bytecode

0x6080604052600436106101dc5760003560e01c806395d89b4111610102578063c1a7d80e11610095578063e173ad2511610064578063e173ad251461060a578063ebbd6bc71461061f578063f62d18881461063f578063fc195d8c1461065f57600080fd5b8063c1a7d80e14610543578063c8d31e7414610563578063d5ddd50314610583578063dd62ed3e146105b757600080fd5b8063a63e8c4b116100d1578063a63e8c4b146104cc578063a9059cbb146104e1578063beef8a7114610501578063c1a0ff4c1461052e57600080fd5b806395d89b41146104545780639b6aaa4714610469578063a3ea6c971461048c578063a457c2d7146104ac57600080fd5b8063313ce5671161017a57806370a082311161014957806370a08231146103ad57806371a97305146103f05780637817bf4a1461041257806384ba89e31461043f57600080fd5b8063313ce5671461031d57806339509351146103395780633fc8cef3146103595780636c60d9e71461038d57600080fd5b806317094411116101b6578063170944111461026557806318160ddd146102be57806323b872dd146102dd5780632ce5183f146102fd57600080fd5b806306fdde03146101e8578063095ea7b3146102135780631480fce31461024357600080fd5b366101e357005b600080fd5b3480156101f457600080fd5b506101fd61067f565b60405161020a9190614f3f565b60405180910390f35b34801561021f57600080fd5b5061023361022e366004614f77565b610711565b604051901515815260200161020a565b34801561024f57600080fd5b5061026361025e366004614fef565b61072b565b005b34801561027157600080fd5b506102997f000000000000000000000000ae78736cd615f374d3085123a210448e74fc639381565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161020a565b3480156102ca57600080fd5b506035545b60405190815260200161020a565b3480156102e957600080fd5b506102336102f836600461503b565b610859565b34801561030957600080fd5b50610263610318366004614f77565b61087f565b34801561032957600080fd5b506040516012815260200161020a565b34801561034557600080fd5b50610233610354366004614f77565b6108b6565b34801561036557600080fd5b506102997f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b34801561039957600080fd5b506102636103a836600461507c565b610902565b3480156103b957600080fd5b506102cf6103c83660046150d3565b73ffffffffffffffffffffffffffffffffffffffff1660009081526033602052604090205490565b3480156103fc57600080fd5b50610405610940565b60405161020a9190615141565b34801561041e57600080fd5b5061043261042d366004615154565b610a0c565b60405161020a9190615210565b34801561044b57600080fd5b506102cf610a52565b34801561046057600080fd5b506101fd610aa7565b34801561047557600080fd5b5061047e610ab6565b60405161020a929190615223565b34801561049857600080fd5b506102cf6104a7366004615251565b610acd565b3480156104b857600080fd5b506102336104c7366004614f77565b610ea9565b3480156104d857600080fd5b50610432610f8a565b3480156104ed57600080fd5b506102336104fc366004614f77565b610fd5565b34801561050d57600080fd5b5061052161051c36600461532c565b610fe3565b60405161020a9190615368565b34801561053a57600080fd5b50610432611bfb565b34801561054f57600080fd5b5061026361055e3660046154c5565b611d55565b34801561056f57600080fd5b506102cf61057e366004615521565b611ebf565b34801561058f57600080fd5b506102997f00000000000000000000000016d5a408e807db8ef7c578279beeee6b228f1c1c81565b3480156105c357600080fd5b506102cf6105d2366004615573565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260346020908152604080832093909416825291909152205490565b34801561061657600080fd5b506101fd611f02565b34801561062b57600080fd5b5061026361063a366004614f77565b611f11565b34801561064b57600080fd5b5061026361065a3660046155a1565b611f48565b34801561066b57600080fd5b5061043261067a366004615154565b61226c565b60606036805461068e90615654565b80601f01602080910402602001604051908101604052809291908181526020018280546106ba90615654565b80156107075780601f106106dc57610100808354040283529160200191610707565b820191906000526020600020905b8154815290600101906020018083116106ea57829003601f168201915b5050505050905090565b60003361071f818585612469565b60019150505b92915050565b32610768576040518381527f0d4fc85ef490d33e67589b22099d3954a460f3ab83b44caad5335d7f2f2c635a9060200160405180910390a1505050565b600060028383600081811061077f5761077f6156a7565b9050602002013510156107ad578282600281811061079f5761079f6156a7565b905060200201359050610813565b828260008181106107c0576107c06156a7565b905060200201356002036107e1578282600181811061079f5761079f6156a7565b6040517fb23013d500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61081d818561261c565b610853576040517fb23013d500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505050565b600033610867858285612654565b610872858585612725565b60019150505b9392505050565b7f72f6db45f6a510a9f71a52fed1cf587666e7205545d52ee38b978222d6abaa56336108ab828261299b565b610853308585612725565b33600081815260346020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716845290915281205490919061071f90829086906108fd908790615705565b612469565b7fd00dd30c0065f8d96fd9ccd8989cf005814e3a3d416d39c86ff1cd1f1ba7ded23361092e828261299b565b610939858585612aa7565b5050505050565b60607f0000000000000000000000001aa2a802ba25669531ffd2b1ff8ae94f3d87f41a73ffffffffffffffffffffffffffffffffffffffff1663dd0fe31a610986610a52565b6040518263ffffffff1660e01b81526004016109a491815260200190565b600060405180830381865afa1580156109c1573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610a079190810190615718565b905090565b6060610a387fd00dd30c0065f8d96fd9ccd8989cf005814e3a3d416d39c86ff1cd1f1ba7ded23361299b565b610a4787878888888888612c2e565b979650505050505050565b6000807f000000000000000000000000000000000000000000000000000000000000000111610a82575060975490565b507f000000000000000000000000000000000000000000000000000000000000000190565b60606037805461068e90615654565b6060803215610ac457600080fd5b50606091829150565b60007f72f6db45f6a510a9f71a52fed1cf587666e7205545d52ee38b978222d6abaa5633610afb828261299b565b60008b67ffffffffffffffff811115610b1657610b166153b8565b604051908082528060200260200182016040528015610b3f578160200160208202803683370190505b50905060005b8c811015610c31578d8d82818110610b5f57610b5f6156a7565b9050602002016020810190610b7491906150d3565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015610be0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c0491906157a7565b828281518110610c1657610c166156a7565b6020908102919091010152610c2a816157c0565b9050610b45565b50610c6b8d8d8080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050505050565b60008c67ffffffffffffffff811115610c8657610c866153b8565b604051908082528060200260200182016040528015610caf578160200160208202803683370190505b50905060005b8d811015610da1578e8e82818110610ccf57610ccf6156a7565b9050602002016020810190610ce491906150d3565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015610d50573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d7491906157a7565b828281518110610d8657610d866156a7565b6020908102919091010152610d9a816157c0565b9050610cb5565b506000610de28d8d808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508f9250612c9b915050565b9050610df18f8f848d8d612eb5565b6000610e3e8e8e80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050508d612c9b565b90506000610e4c8383612f9d565b90507fef3370e7b2b7c0f8f907344d48c31e679e6ff7ff055cad7846daf5ca2d5b30dc81610e7a85856157f8565b8787604051610e8c949392919061580b565b60405180910390a196505050505050509998505050505050505050565b33600081815260346020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716845290915281205490919083811015610f72576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f00000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b610f7f8286868403612469565b506001949350505050565b6040805160018082528183019092526060916000919060208083019080368337019050509050600181600081518110610fc557610fc56156a7565b6020908102919091010152919050565b60003361071f818585612725565b6110156040518060a0016040528060008152602001606081526020016000815260200160008152602001600081525090565b61103f7fd00dd30c0065f8d96fd9ccd8989cf005814e3a3d416d39c86ff1cd1f1ba7ded23361299b565b60008061104f606085018561583c565b905067ffffffffffffffff811115611069576110696153b8565b604051908082528060200260200182016040528015611092578160200160208202803683370190505b50905060005b6110a5606086018661583c565b90508110156111bd576110bb606086018661583c565b828181106110cb576110cb6156a7565b90506020020160208101906110e091906150d3565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa15801561114c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061117091906157a7565b828281518110611182576111826156a7565b60200260200101818152505060008282815181106111a2576111a26156a7565b602002602001015111156111b557600192505b600101611098565b506111cf8161055e604087018761583c565b6111e460a085013561025e604087018761583c565b6111ec614eb3565b6111fa85610100013561302b565b60408501526000611237611211606088018861583c565b61121e60208a018a61583c565b61122b60408c018c61583c565b60009695505050505050565b905064e8d4a5100085604001518261124f91906158a4565b611259919061591f565b6112639082615987565b856040018181516112749190615987565b90525050604084015161128b906101208701613050565b506112e461129c608087018761583c565b808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506112df92505050610100880160e089016150d3565b612c9b565b81526000808060a088013586156114355760006113086101008b0160e08c016150d3565b73ffffffffffffffffffffffffffffffffffffffff1663a2d2657f61133060608d018d61583c565b8a8e8060800190611341919061583c565b6040518663ffffffff1660e01b81526004016113619594939291906159a7565b602060405180830381865afa15801561137e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113a291906157a7565b905064e8d4a510006113b360355490565b10156113cc576113c5816103e8615a5f565b935061141f565b8551156113ed5785516035546113e3908390615a5f565b6113c59190615a76565b6040517f907d9a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8184101561142f57839450611433565b8194505b505b600061144460608b018b61583c565b905067ffffffffffffffff81111561145e5761145e6153b8565b604051908082528060200260200182016040528015611487578160200160208202803683370190505b5090506000828511156118d25785156115485760005b6114aa60608d018d61583c565b90508110156115425785878a83815181106114c7576114c76156a7565b602002602001015102816114dd576114dd6158f0565b048382815181106114f0576114f06156a7565b60200260200101818152505082818151811061150e5761150e6156a7565b6020026020010151898281518110611528576115286156a7565b60209081029190910101805191909103905260010161149d565b50600190505b6000885167ffffffffffffffff811115611564576115646153b8565b60405190808252806020026020018201604052801561158d578160200160208202803683370190505b509050600061159c8d8061583c565b90501115611772576116056115b460608e018e61583c565b80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050508a8e8060000190610853919061583c565b60005b61161560608e018e61583c565b905081101561176c57898181518110611630576116306156a7565b602002602001015182828151811061164a5761164a6156a7565b602002602001018181525050838181518110611668576116686156a7565b60200260200101518d8060600190611680919061583c565b83818110611690576116906156a7565b90506020020160208101906116a591906150d3565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015611711573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061173591906157a7565b61173f91906157f8565b8a8281518110611751576117516156a7565b6020908102919091010152611765816157c0565b9050611608565b506117d4565b60005b61178260608e018e61583c565b90508110156117d25789818151811061179d5761179d6156a7565b60200260200101518282815181106117b7576117b76156a7565b60209081029190910101526117cb816157c0565b9050611775565b505b6117fa6117e460608e018e61583c565b8b8f80604001906117f5919061583c565b612eb5565b61185d61180a60808e018e61583c565b80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050508d60e00160208101906112df91906150d3565b60208901819052885161186f91612f9d565b885160208a01519196507fef3370e7b2b7c0f8f907344d48c31e679e6ff7ff055cad7846daf5ca2d5b30dc9187916118a6916157f8565b838c6040516118b8949392919061580b565b60405180910390a16118ca8786615705565b945050611b29565b84831115611aba5785156118e95785830392508593505b61190f6118f960608d018d61583c565b858e806040019061190a919061583c565b6131ca565b61191930846133c7565b50600161196f61192c60808d018d61583c565b808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506112df925050506101008e0160e08f016150d3565b602088015260005b61198460608d018d61583c565b9050811015611a745761199a60608d018d61583c565b828181106119aa576119aa6156a7565b90506020020160208101906119bf91906150d3565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015611a2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a4f91906157a7565b838281518110611a6157611a616156a7565b6020908102919091010152600101611977565b5060208701516040517f8b1b306a5c3a19617717b1f37f2b4bea82978af8db2e859975f665fc40bc74b391611aad918691908690615a8a565b60405180910390a1611b29565b8515611b215782935060005b611ad360608d018d61583c565b9050811015611b1b57888181518110611aee57611aee6156a7565b6020026020010151838281518110611b0857611b086156a7565b6020908102919091010152600101611ac6565b50600190505b865160208801525b8015611bd15760005b611b3f60608d018d61583c565b9050811015611bcf57611bc7611b5b60e08e0160c08f016150d3565b848381518110611b6d57611b6d6156a7565b60200260200101518e8060600190611b85919061583c565b85818110611b9557611b956156a7565b9050602002016020810190611baa91906150d3565b73ffffffffffffffffffffffffffffffffffffffff16919061358d565b600101611b32565b505b838a526020808b0183905287015160608b015260355460808b015250979998505050505050505050565b60408051600180825281830190925260609160208083019080368337019050506040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529091507f000000000000000000000000ae78736cd615f374d3085123a210448e74fc639373ffffffffffffffffffffffffffffffffffffffff1690638b32fa239082906370a0823190602401602060405180830381865afa158015611cb0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cd491906157a7565b6040518263ffffffff1660e01b8152600401611cf291815260200190565b602060405180830381865afa158015611d0f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d3391906157a7565b81600081518110611d4657611d466156a7565b60200260200101818152505090565b32611df3576040805160018082528183019092526000916020808301908036833701905050905083600081518110611d8f57611d8f6156a7565b602002602001015181600081518110611daa57611daa6156a7565b6020026020010181815250507f2da97ade1b5402d92aa4ecf88302c7c6a98498999fe040468284622731550b3681604051611de59190615210565b60405180910390a150505050565b600282826000818110611e0857611e086156a7565b905060200201351115611e47576040517fdfc8737800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611e8482826001818110611e5d57611e5d6156a7565b9050602002013584600081518110611e7757611e776156a7565b602002602001015161261c565b611eba576040517fdfc8737800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505050565b60007f72f6db45f6a510a9f71a52fed1cf587666e7205545d52ee38b978222d6abaa5633611eed828261299b565b611ef78585612c9b565b92505b505092915050565b60606096805461068e90615654565b7f72f6db45f6a510a9f71a52fed1cf587666e7205545d52ee38b978222d6abaa5633611f3d828261299b565b610853843085612725565b600054610100900460ff1615808015611f685750600054600160ff909116105b80611f825750303b158015611f82575060005460ff166001145b61200e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610f69565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561206c57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b612077826000613661565b60007f0000000000000000000000001aa2a802ba25669531ffd2b1ff8ae94f3d87f41a73ffffffffffffffffffffffffffffffffffffffff1663dd0fe31a6120bd610a52565b6040518263ffffffff1660e01b81526004016120db91815260200190565b600060405180830381865afa1580156120f8573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261213e9190810190615718565b9050805160011415806121b757507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1681600081518110612196576121966156a7565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1614155b156121f9576121c4610a52565b6040517f4b1f57ad000000000000000000000000000000000000000000000000000000008152600401610f6991815260200190565b6122016138e7565b60985550801561226857600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6040517f91d148540000000000000000000000000000000000000000000000000000000081527f72f6db45f6a510a9f71a52fed1cf587666e7205545d52ee38b978222d6abaa5660048201523360248201526060907f0000000000000000000000007b533e72e0cdc63aacd8cdb926ac402b846fbd1373ffffffffffffffffffffffffffffffffffffffff16906391d1485490604401602060405180830381865afa15801561231f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123439190615aa9565b15801561242157506040517f91d148540000000000000000000000000000000000000000000000000000000081527fd00dd30c0065f8d96fd9ccd8989cf005814e3a3d416d39c86ff1cd1f1ba7ded260048201523360248201527f0000000000000000000000007b533e72e0cdc63aacd8cdb926ac402b846fbd1373ffffffffffffffffffffffffffffffffffffffff16906391d1485490604401602060405180830381865afa1580156123fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061241f9190615aa9565b155b1561245a576040517f7ab612a3000000000000000000000000000000000000000000000000000000008152336004820152602401610f69565b610a4787308888888888612c2e565b73ffffffffffffffffffffffffffffffffffffffff831661250b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f72657373000000000000000000000000000000000000000000000000000000006064820152608401610f69565b73ffffffffffffffffffffffffffffffffffffffff82166125ae576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f73730000000000000000000000000000000000000000000000000000000000006064820152608401610f69565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526034602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b600061263a60017001000000000000000000000000000000006157f8565b831682108061264c5750608083901c82115b159392505050565b73ffffffffffffffffffffffffffffffffffffffff8381166000908152603460209081526040808320938616835292905220547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146108535781811015612718576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610f69565b6108538484848403612469565b73ffffffffffffffffffffffffffffffffffffffff83166127c8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f64726573730000000000000000000000000000000000000000000000000000006064820152608401610f69565b73ffffffffffffffffffffffffffffffffffffffff821661286b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f65737300000000000000000000000000000000000000000000000000000000006064820152608401610f69565b73ffffffffffffffffffffffffffffffffffffffff831660009081526033602052604090205481811015612921576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e742065786365656473206260448201527f616c616e636500000000000000000000000000000000000000000000000000006064820152608401610f69565b73ffffffffffffffffffffffffffffffffffffffff80851660008181526033602052604080822086860390559286168082529083902080548601905591517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9061298e9086815260200190565b60405180910390a3610853565b6040517f91d148540000000000000000000000000000000000000000000000000000000081526004810183905273ffffffffffffffffffffffffffffffffffffffff82811660248301527f0000000000000000000000007b533e72e0cdc63aacd8cdb926ac402b846fbd1316906391d1485490604401602060405180830381865afa158015612a2e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a529190615aa9565b612268576040517f75000dc00000000000000000000000000000000000000000000000000000000081526004810183905273ffffffffffffffffffffffffffffffffffffffff82166024820152604401610f69565b82826000818110612aba57612aba6156a7565b90506020020135600314612afa576040517ffc5fbdf300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152600090612beb9073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ae78736cd615f374d3085123a210448e74fc639316906370a0823190602401602060405180830381865afa158015612b8b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612baf91906157a7565b858580806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250600192506139a0915050565b905061085373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216838361358d565b60606000612c3f86868b8787613dd9565b9050612c4b888a6133c7565b60005b85811015612c8e57612c8688838381518110612c6c57612c6c6156a7565b6020026020010151898985818110611b9557611b956156a7565b600101612c4e565b5098975050505050505050565b6040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152600090819073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ae78736cd615f374d3085123a210448e74fc63931690638b32fa239082906370a0823190602401602060405180830381865afa158015612d32573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d5691906157a7565b6040518263ffffffff1660e01b8152600401612d7491815260200190565b602060405180830381865afa158015612d91573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612db591906157a7565b90508273ffffffffffffffffffffffffffffffffffffffff16639115900c7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28387600081518110612e0857612e086156a7565b60209081029190910101516040517fffffffff0000000000000000000000000000000000000000000000000000000060e086901b16815273ffffffffffffffffffffffffffffffffffffffff909316600484015260248301919091526044820152606401602060405180830381865afa158015612e89573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ead91906157a7565b949350505050565b600082826000818110612eca57612eca6156a7565b90506020020135600003612ee057506003612f3b565b82826000818110612ef357612ef36156a7565b90506020020135600203612f0957506002612f3b565b6040517f0947e0a500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612f9484600081518110612f5157612f516156a7565b602002602001015184848080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525086925061403a915050565b50505050505050565b600080612fa960355490565b905064e8d4a51000811015612ff157612fc46103e884615a5f565b915064e8d4a5100081900380831015612fda5750815b8083039250612feb61dead82614573565b5061301a565b83156113ed57838161300382866157f8565b61300d9190615a5f565b6130179190615a76565b91505b6130243083614573565b5092915050565b6000806130366138e7565b905061304460985482614668565b60989190915592915050565b6000808313156107255782600061306e64e8d4a51000612710615a5f565b61307e6080860160608701615acb565b6bffffffffffffffffffffffff1661309c6040870160208801615acb565b6130b6906bffffffffffffffffffffffff166127106157f8565b6130c091906157f8565b6130ca9084615a5f565b6130d49190615705565b90506000826130e260355490565b6130ec9190615a5f565b90506000826131016040880160208901615acb565b613119906bffffffffffffffffffffffff1684615a5f565b6131239190615a76565b905061313b61313560208801886150d3565b82614573565b60008361314e6080890160608a01615acb565b613166906bffffffffffffffffffffffff1685615a5f565b6131709190615a76565b90506131856131356060890160408a016150d3565b604051828201808252965030907fd02a13a1b75d55c8b9ced16f3558e9004a0827559f52715b34bcc685d904c4599060200160405180910390a2505050505092915050565b6000828260008181106131df576131df6156a7565b905060200201356001036131f5575060036132ad565b82826000818110613208576132086156a7565b9050602002013560020361321e575060026132ad565b82826000818110613231576132316156a7565b90506020020135600303613247575060016132ad565b8282600081811061325a5761325a6156a7565b90506020020135600014801561326e575032155b1561327b575060036132ad565b6040517ffc5fbdf300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006132b860355490565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015286907f000000000000000000000000ae78736cd615f374d3085123a210448e74fc639373ffffffffffffffffffffffffffffffffffffffff16906370a0823190602401602060405180830381865afa158015613344573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061336891906157a7565b6133729190615a5f565b61337c9190615a76565b90506133bd818585808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508792506139a0915050565b5050505050505050565b73ffffffffffffffffffffffffffffffffffffffff821661346a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f2061646472657360448201527f73000000000000000000000000000000000000000000000000000000000000006064820152608401610f69565b73ffffffffffffffffffffffffffffffffffffffff821660009081526033602052604090205481811015613520576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e60448201527f63650000000000000000000000000000000000000000000000000000000000006064820152608401610f69565b73ffffffffffffffffffffffffffffffffffffffff831660008181526033602090815260408083208686039055603580548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3505050565b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052611eba9084907fa9059cbb00000000000000000000000000000000000000000000000000000000906064015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526146d4565b600054610100900460ff166136f8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610f69565b8151600003613733576040517fc52a9bd300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000001613799578061378f576040517f36307dff00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60978190556137d1565b80156137d1576040517f36307dff00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f0000000000000000000000001aa2a802ba25669531ffd2b1ff8ae94f3d87f41a73ffffffffffffffffffffffffffffffffffffffff1663e065ce6e613815610a52565b6040518263ffffffff1660e01b815260040161383391815260200190565b60006040518083038186803b15801561384b57600080fd5b505afa15801561385f573d6000803e3d6000fd5b5050505081609690816138729190615b3f565b506122686040518060400160405280601481526020017f537472617465677920536861726520546f6b656e0000000000000000000000008152506040518060400160405280600381526020017f53535400000000000000000000000000000000000000000000000000000000008152506147e0565b6040517f8b32fa23000000000000000000000000000000000000000000000000000000008152670de0b6b3a764000060048201526000907f000000000000000000000000ae78736cd615f374d3085123a210448e74fc639373ffffffffffffffffffffffffffffffffffffffff1690638b32fa2390602401602060405180830381865afa15801561397c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a0791906157a7565b600032613c35576040517f2f18e2af00000000000000000000000000000000000000000000000000000000815260048101859052600a6024820152600090819073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000016d5a408e807db8ef7c578279beeee6b228f1c1c1690632f18e2af906044016060604051808303816000875af1158015613a40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613a649190615c59565b90925090508160006020020151858581518110613a8357613a836156a7565b6020908102919091010152816001602002015185613aa2866001615705565b81518110613ab257613ab26156a7565b60209081029190910101528085613aca866002615705565b81518110613ada57613ada6156a7565b60209081029190910101528085613af2866003615705565b81518110613b0257613b026156a7565b602090810291909101015260408051600480825260a0820190925260009181602001602082028036833750508451825192935091839150600090613b4857613b486156a7565b6020908102919091010152826001602002015181600181518110613b6e57613b6e6156a7565b6020026020010181815250508181600281518110613b8e57613b8e6156a7565b6020026020010181815250508181600381518110613bae57613bae6156a7565b6020026020010181815250507f872697b0ffc2830e88f47a2ddac8d40ec8898cb72bc036d3aaf8ce7960286a9060008083604051602001613bef9190615210565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815290829052613c29939291615cde565b60405180910390a15050505b613c807f000000000000000000000000ae78736cd615f374d3085123a210448e74fc63937f00000000000000000000000016d5a408e807db8ef7c578279beeee6b228f1c1c86614881565b7f00000000000000000000000016d5a408e807db8ef7c578279beeee6b228f1c1c73ffffffffffffffffffffffffffffffffffffffff1663a824ae8b848481518110613cce57613cce6156a7565b602002602001015185856001613ce49190615705565b81518110613cf457613cf46156a7565b602002602001015186866002613d0a9190615705565b81518110613d1a57613d1a6156a7565b602002602001015187876003613d309190615705565b81518110613d4057613d406156a7565b60209081029190910101516040517fffffffff0000000000000000000000000000000000000000000000000000000060e087901b16815260048101949094526024840192909252604483015260648201526084810187905260a401600060405180830381600087803b158015613db557600080fd5b505af1158015613dc9573d6000803e3d6000fd5b50505050479050610878816148ac565b60608467ffffffffffffffff811115613df457613df46153b8565b604051908082528060200260200182016040528015613e1d578160200160208202803683370190505b50905060005b85811015613f0f57868682818110613e3d57613e3d6156a7565b9050602002016020810190613e5291906150d3565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015613ebe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613ee291906157a7565b828281518110613ef457613ef46156a7565b6020908102919091010152613f08816157c0565b9050613e23565b50613f1d86868686866131ca565b60005b8581101561403057818181518110613f3a57613f3a6156a7565b6020026020010151878783818110613f5457613f546156a7565b9050602002016020810190613f6991906150d3565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015613fd5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613ff991906157a7565b61400391906157f8565b828281518110614015576140156156a7565b6020908102919091010152614029816157c0565b9050613f20565b5095945050505050565b6000326142d0576040517f27e0416300000000000000000000000000000000000000000000000000000000815260048101859052600a6024820152600090819073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000016d5a408e807db8ef7c578279beeee6b228f1c1c16906327e04163906044016060604051808303816000875af11580156140da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140fe9190615c59565b9092509050816000602002015185858151811061411d5761411d6156a7565b602090810291909101015281600160200201518561413c866001615705565b8151811061414c5761414c6156a7565b60209081029190910101528085614164866002615705565b81518110614174576141746156a7565b6020908102919091010152808561418c866003615705565b8151811061419c5761419c6156a7565b602090810291909101015260408051600480825260a08201909252600091816020016020820280368337505084518251929350918391506000906141e2576141e26156a7565b6020908102919091010152826001602002015181600181518110614208576142086156a7565b6020026020010181815250508181600281518110614228576142286156a7565b6020026020010181815250508181600381518110614248576142486156a7565b6020026020010181815250507f872697b0ffc2830e88f47a2ddac8d40ec8898cb72bc036d3aaf8ce7960286a90600160008360405160200161428a9190615210565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152908290526142c4939291615cde565b60405180910390a15050505b6142d984614930565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201527f000000000000000000000000ae78736cd615f374d3085123a210448e74fc639373ffffffffffffffffffffffffffffffffffffffff16906370a0823190602401602060405180830381865afa158015614363573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061438791906157a7565b90507f00000000000000000000000016d5a408e807db8ef7c578279beeee6b228f1c1c73ffffffffffffffffffffffffffffffffffffffff166355362f4d858585815181106143d8576143d86156a7565b6020026020010151868660016143ee9190615705565b815181106143fe576143fe6156a7565b6020026020010151878760026144149190615705565b81518110614424576144246156a7565b60200260200101518888600361443a9190615705565b8151811061444a5761444a6156a7565b60200260200101516040518663ffffffff1660e01b8152600401614487949392919093845260208401929092526040830152606082015260800190565b6000604051808303818588803b1580156144a057600080fd5b505af11580156144b4573d6000803e3d6000fd5b50506040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201528493507f000000000000000000000000ae78736cd615f374d3085123a210448e74fc639373ffffffffffffffffffffffffffffffffffffffff1692506370a082319150602401602060405180830381865afa158015614545573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061456991906157a7565b612ead91906157f8565b73ffffffffffffffffffffffffffffffffffffffff82166145f0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610f69565b80603560008282546146029190615705565b909155505073ffffffffffffffffffffffffffffffffffffffff82166000818152603360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b60008282111561469e578264e8d4a5100061468382856157f8565b61468d9190615a5f565b6146979190615a76565b9050610725565b81831115610725578264e8d4a510006146b784836157f8565b6146c19190615a5f565b6146cb9190615a76565b61087890615cff565b6000614736826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff166149cc9092919063ffffffff16565b805190915015611eba57808060200190518101906147549190615aa9565b611eba576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152608401610f69565b600054610100900460ff16614877576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610f69565b61226882826149db565b61488b8383614a8b565b611eba73ffffffffffffffffffffffffffffffffffffffff84168383614b49565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561491457600080fd5b505af1158015614928573d6000803e3d6000fd5b505050505050565b6040517f2e1a7d4d000000000000000000000000000000000000000000000000000000008152600481018290527f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1690632e1a7d4d90602401600060405180830381600087803b1580156149b857600080fd5b505af1158015610939573d6000803e3d6000fd5b6060612ead8484600085614ccb565b600054610100900460ff16614a72576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610f69565b6036614a7e8382615b3f565b506037611eba8282615b3f565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff82811660248301526000919084169063dd62ed3e90604401602060405180830381865afa158015614b01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614b2591906157a7565b11156122685761226873ffffffffffffffffffffffffffffffffffffffff83168260005b801580614be957506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff838116602483015284169063dd62ed3e90604401602060405180830381865afa158015614bc3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614be791906157a7565b155b614c75576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527f20746f206e6f6e2d7a65726f20616c6c6f77616e6365000000000000000000006064820152608401610f69565b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052611eba9084907f095ea7b300000000000000000000000000000000000000000000000000000000906064016135df565b606082471015614d5d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610f69565b6000808673ffffffffffffffffffffffffffffffffffffffff168587604051614d869190615d37565b60006040518083038185875af1925050503d8060008114614dc3576040519150601f19603f3d011682016040523d82523d6000602084013e614dc8565b606091505b5091509150610a478783838760608315614e6a578251600003614e635773ffffffffffffffffffffffffffffffffffffffff85163b614e63576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610f69565b5081612ead565b612ead8383815115614e7f5781518083602001fd5b806040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f699190614f3f565b60405180604001604052806002906020820280368337509192915050565b60005b83811015614eec578181015183820152602001614ed4565b50506000910152565b60008151808452614f0d816020860160208601614ed1565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006108786020830184614ef5565b73ffffffffffffffffffffffffffffffffffffffff81168114614f7457600080fd5b50565b60008060408385031215614f8a57600080fd5b8235614f9581614f52565b946020939093013593505050565b60008083601f840112614fb557600080fd5b50813567ffffffffffffffff811115614fcd57600080fd5b6020830191508360208260051b8501011115614fe857600080fd5b9250929050565b60008060006040848603121561500457600080fd5b83359250602084013567ffffffffffffffff81111561502257600080fd5b61502e86828701614fa3565b9497909650939450505050565b60008060006060848603121561505057600080fd5b833561505b81614f52565b9250602084013561506b81614f52565b929592945050506040919091013590565b60008060006040848603121561509157600080fd5b833567ffffffffffffffff8111156150a857600080fd5b6150b486828701614fa3565b90945092505060208401356150c881614f52565b809150509250925092565b6000602082840312156150e557600080fd5b813561087881614f52565b600081518084526020808501945080840160005b8381101561513657815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101615104565b509495945050505050565b60208152600061087860208301846150f0565b6000806000806000806080878903121561516d57600080fd5b86359550602087013561517f81614f52565b9450604087013567ffffffffffffffff8082111561519c57600080fd5b6151a88a838b01614fa3565b909650945060608901359150808211156151c157600080fd5b506151ce89828a01614fa3565b979a9699509497509295939492505050565b600081518084526020808501945080840160005b83811015615136578151875295820195908201906001016151f4565b60208152600061087860208301846151e0565b60408152600061523660408301856150f0565b828103602084015261524881856151e0565b95945050505050565b600080600080600080600080600060a08a8c03121561526f57600080fd5b893567ffffffffffffffff8082111561528757600080fd5b6152938d838e01614fa3565b909b50995060208c01359150808211156152ac57600080fd5b6152b88d838e01614fa3565b909950975060408c013591506152cd82614f52565b90955060608b013590808211156152e357600080fd5b6152ef8d838e01614fa3565b909650945060808c013591508082111561530857600080fd5b506153158c828d01614fa3565b915080935050809150509295985092959850929598565b60006020828403121561533e57600080fd5b813567ffffffffffffffff81111561535557600080fd5b82016101a0818503121561087857600080fd5b60208152815160208201526000602083015160a0604084015261538e60c08401826151e0565b90506040840151606084015260608401516080840152608084015160a08401528091505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561542e5761542e6153b8565b604052919050565b600067ffffffffffffffff821115615450576154506153b8565b5060051b60200190565b600082601f83011261546b57600080fd5b8135602061548061547b83615436565b6153e7565b82815260059290921b8401810191818101908684111561549f57600080fd5b8286015b848110156154ba57803583529183019183016154a3565b509695505050505050565b6000806000604084860312156154da57600080fd5b833567ffffffffffffffff808211156154f257600080fd5b6154fe8783880161545a565b9450602086013591508082111561551457600080fd5b5061502e86828701614fa3565b6000806040838503121561553457600080fd5b823567ffffffffffffffff81111561554b57600080fd5b6155578582860161545a565b925050602083013561556881614f52565b809150509250929050565b6000806040838503121561558657600080fd5b823561559181614f52565b9150602083013561556881614f52565b600060208083850312156155b457600080fd5b823567ffffffffffffffff808211156155cc57600080fd5b818501915085601f8301126155e057600080fd5b8135818111156155f2576155f26153b8565b615622847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116016153e7565b9150808252868482850101111561563857600080fd5b8084840185840137600090820190930192909252509392505050565b600181811c9082168061566857607f821691505b6020821081036156a1577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80820180821115610725576107256156d6565b6000602080838503121561572b57600080fd5b825167ffffffffffffffff81111561574257600080fd5b8301601f8101851361575357600080fd5b805161576161547b82615436565b81815260059190911b8201830190838101908783111561578057600080fd5b928401925b82841015610a4757835161579881614f52565b82529284019290840190615785565b6000602082840312156157b957600080fd5b5051919050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036157f1576157f16156d6565b5060010190565b81810381811115610725576107256156d6565b84815283602082015260806040820152600061582a60808301856151e0565b8281036060840152610a4781856151e0565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261587157600080fd5b83018035915067ffffffffffffffff82111561588c57600080fd5b6020019150600581901b3603821315614fe857600080fd5b808202600082127f8000000000000000000000000000000000000000000000000000000000000000841416156158dc576158dc6156d6565b8181058314821517610725576107256156d6565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60008261592e5761592e6158f0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f800000000000000000000000000000000000000000000000000000000000000083141615615982576159826156d6565b500590565b8082018281126000831280158216821582161715611efa57611efa6156d6565b6060808252810185905260008660808301825b888110156159f75782356159cd81614f52565b73ffffffffffffffffffffffffffffffffffffffff168252602092830192909101906001016159ba565b508381036020850152615a0a81886151e0565b91505082810360408401528381527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff841115615a4557600080fd5b8360051b8086602084013701602001979650505050505050565b8082028115828204841417610725576107256156d6565b600082615a8557615a856158f0565b500490565b83815282602082015260606040820152600061524860608301846151e0565b600060208284031215615abb57600080fd5b8151801515811461087857600080fd5b600060208284031215615add57600080fd5b81356bffffffffffffffffffffffff8116811461087857600080fd5b601f821115611eba57600081815260208120601f850160051c81016020861015615b205750805b601f850160051c820191505b8181101561492857828155600101615b2c565b815167ffffffffffffffff811115615b5957615b596153b8565b615b6d81615b678454615654565b84615af9565b602080601f831160018114615bc05760008415615b8a5750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555614928565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b82811015615c0d57888601518255948401946001909101908401615bee565b5085821015615c4957878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b60008060608385031215615c6c57600080fd5b83601f840112615c7b57600080fd5b6040516040810181811067ffffffffffffffff82111715615c9e57615c9e6153b8565b8060405250806040850186811115615cb557600080fd5b855b81811015615ccf578051835260209283019201615cb7565b50519196919550909350505050565b83151581528260208201526060604082015260006152486060830184614ef5565b60007f80000000000000000000000000000000000000000000000000000000000000008203615d3057615d306156d6565b5060000390565b60008251615d49818460208701614ed1565b919091019291505056fea26469706673582212205740574dd20ba806085e79746ceccc677ad2b2edf42947d6d5255ed62ba8944c64736f6c63430008110033

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

0000000000000000000000001aa2a802ba25669531ffd2b1ff8ae94f3d87f41a0000000000000000000000007b533e72e0cdc63aacd8cdb926ac402b846fbd13000000000000000000000000000000000000000000000000000000000000000100000000000000000000000016d5a408e807db8ef7c578279beeee6b228f1c1c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2

-----Decoded View---------------
Arg [0] : assetGroupRegistry_ (address): 0x1Aa2a802BA25669531Ffd2b1fF8ae94f3D87f41A
Arg [1] : accessControl_ (address): 0x7b533e72E0cDC63AacD8cDB926AC402b846Fbd13
Arg [2] : assetGroupId_ (uint256): 1
Arg [3] : rocketSwapRouter_ (address): 0x16D5A408e807db8eF7c578279BEeEe6b228f1c1C
Arg [4] : weth_ (address): 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2

-----Encoded View---------------
5 Constructor Arguments found :
Arg [0] : 0000000000000000000000001aa2a802ba25669531ffd2b1ff8ae94f3d87f41a
Arg [1] : 0000000000000000000000007b533e72e0cdc63aacd8cdb926ac402b846fbd13
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000001
Arg [3] : 00000000000000000000000016d5a408e807db8ef7c578279beeee6b228f1c1c
Arg [4] : 000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2


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  ]

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.