ETH Price: $3,441.91 (+1.27%)

Contract Diff Checker

Contract Name:
EigenLSTRestaking

Contract Source Code:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import {EigenStrategy} from "../EigenStrategy.sol";
import {SwappingAggregator} from "../SwappingAggregator.sol";

import {IStrategyManager} from "../../interfaces/IStrategyManager.sol";
import {IDelegationManager} from "../../interfaces/IDelegationManager.sol";
import {IEigenStrategy} from "../../interfaces/IEigenStrategy.sol";
import {ILido} from "../../interfaces/ILido.sol";
import {ILidoWithdrawalQueue} from "../../interfaces/ILidoWithdrawalQueue.sol";

contract EigenLSTRestaking is EigenStrategy {
    using EnumerableSet for EnumerableSet.Bytes32Set;

    address public immutable tokenAddr;
    address public immutable strategyManager;
    address public immutable delegationManager;
    address public immutable eigenStrategy;

    address public immutable LidoWithdrawalQueue;
    address payable public immutable SWAPPING;

    address public referral;

    address public eigenOperator;

    bool public buyOnDex;
    bool public sellOnDex;

    bytes32[] public withdrawals;

    mapping(bytes32 => uint256) public withdrawingShares;

    EnumerableSet.Bytes32Set private withdrawalRootsSet;

    event Swap(address from, address to, uint256 sent, uint256 received);
    event DepositIntoStrategy(address strategy, address token, uint256 amount);
    event DelegateTo(address operator);
    event WithdrawalQueued(bytes32 withdrawalRoot);
    event WithdrawalCompleted(
        IDelegationManager.Withdrawal withdrawal,
        bytes32 root
    );
    event SetEigenOperator(address oldOperator, address newOperator);
    event SetWithdrawQueueParams(uint256 length, uint256 amount);
    event SetRouter(bool buyOnDex, bool sellOnDex);
    event SetReferral(address oldAddr, address newAddr);
    event Invoked(address indexed targetAddress, uint256 value, bytes data);

    constructor(
        address payable _controller,
        address _tokenAddr,
        address _lidoWithdrawalQueue,
        address _strategyManager,
        address _delegationManager,
        address _eigenStrategy,
        address payable _swap,
        string memory _name
    ) EigenStrategy(_controller, _name) {
        require(
            _tokenAddr != address(0) &&
                _lidoWithdrawalQueue != address(0) &&
                _strategyManager != address(0) &&
                _delegationManager != address(0) &&
                _eigenStrategy != address(0) &&
                _swap != address(0),
            "ZERO ADDRESS"
        );

        tokenAddr = _tokenAddr;
        LidoWithdrawalQueue = _lidoWithdrawalQueue;

        strategyManager = _strategyManager;
        delegationManager = _delegationManager;
        eigenStrategy = _eigenStrategy;

        SWAPPING = _swap;
    }

    function deposit() public payable override onlyController notAtSameBlock {
        require(msg.value != 0, "zero value");

        latestUpdateTime = block.timestamp;
    }

    function withdraw(
        uint256 _amount
    )
        public
        override
        onlyController
        notAtSameBlock
        returns (uint256 actualAmount)
    {
        actualAmount = _withdraw(_amount);
    }

    function instantWithdraw(
        uint256 _amount
    ) public override onlyController returns (uint256 actualAmount) {
        actualAmount = _withdraw(_amount);
    }

    function _withdraw(
        uint256 _amount
    ) internal returns (uint256 actualAmount) {
        require(_amount != 0, "zero value");
        require(_amount <= address(this).balance, "not enough");

        actualAmount = _amount;

        TransferHelper.safeTransferETH(controller, actualAmount);

        latestUpdateTime = block.timestamp;
    }

    function clear() public override onlyController returns (uint256 amount) {
        uint256 balance = address(this).balance;

        if (balance != 0) {
            TransferHelper.safeTransferETH(controller, balance);
            amount = balance;
        }
    }

    function getAllValue() public override returns (uint256 value) {
        value = getInvestedValue();
    }

    // TODO need testing
    function getInvestedValue() public override returns (uint256 value) {
        uint256 etherValue = address(this).balance;
        uint256 tokenValue = IERC20(tokenAddr).balanceOf(address(this));
        (, uint256 claimableValue, uint256 pendingValue) = checkPendingAssets();
        uint256 eigenValue = getRestakingValue();
        uint256 unstakingValue = getUnstakingValue();

        value =
            etherValue +
            tokenValue +
            claimableValue +
            pendingValue +
            eigenValue +
            unstakingValue;
    }

    function depositIntoStrategy(
        uint256 _amount
    ) external onlyOwner returns (uint256 shares) {
        TransferHelper.safeApprove(tokenAddr, strategyManager, _amount);

        shares = IStrategyManager(strategyManager).depositIntoStrategy(
            eigenStrategy,
            tokenAddr,
            _amount
        );

        emit DepositIntoStrategy(eigenStrategy, tokenAddr, _amount);
    }

    function delegateTo(
        IDelegationManager.SignatureWithExpiry
            memory _approverSignatureAndExpiry,
        bytes32 _approverSalt
    ) external onlyOwner {
        IDelegationManager(delegationManager).delegateTo(
            eigenOperator,
            _approverSignatureAndExpiry,
            _approverSalt
        );

        emit DelegateTo(eigenOperator);
    }

    function undelegate()
        external
        onlyOwner
        returns (bytes32[] memory withdrawalRoots)
    {
        require(
            IEigenStrategy(eigenStrategy).shares(address(this)) == 0,
            "active shares"
        );

        withdrawalRoots = IDelegationManager(delegationManager).undelegate(
            address(this)
        );
    }

    function queueWithdrawals(
        IDelegationManager.QueuedWithdrawalParams[]
            calldata _queuedWithdrawalParams
    ) external onlyOwner returns (bytes32[] memory withdrawalRoots) {
        require(
            _queuedWithdrawalParams.length == 1 &&
                _queuedWithdrawalParams[0].shares.length == 1 &&
                _queuedWithdrawalParams[0].strategies.length == 1,
            "invalid length"
        );
        withdrawalRoots = IDelegationManager(delegationManager)
            .queueWithdrawals(_queuedWithdrawalParams);

        uint256 length = withdrawalRoots.length;

        require(length == 1, "invalid root length");

        bytes32 root = withdrawalRoots[0];
        withdrawalRootsSet.add(root);
        withdrawingShares[root] = _queuedWithdrawalParams[0].shares[0];

        emit WithdrawalQueued(root);
    }

    function completeQueuedWithdrawal(
        IDelegationManager.Withdrawal calldata _withdrawal,
        IERC20[] calldata _tokens,
        uint256 _middlewareTimesIndex,
        bool _receiveAsTokens
    ) external onlyOwner {
        IDelegationManager(delegationManager).completeQueuedWithdrawal(
            _withdrawal,
            _tokens,
            _middlewareTimesIndex,
            _receiveAsTokens
        );

        bytes32 root = calculateWithdrawalRoot(_withdrawal);
        withdrawingShares[root] = 0;
        withdrawalRootsSet.remove(root);

        emit WithdrawalCompleted(_withdrawal, root);
    }

    function swapToToken(
        uint256 _amount
    ) external onlyOwner returns (uint256 tokenAmount) {
        require(_amount != 0, "zero");
        require(_amount <= address(this).balance, "exceed balance");

        if (!buyOnDex) {
            tokenAmount = ILido(tokenAddr).submit{value: _amount}(referral);
        } else {
            tokenAmount = SwappingAggregator(SWAPPING).swap{value: _amount}(
                tokenAddr,
                _amount,
                false
            );
        }

        emit Swap(address(0), tokenAddr, _amount, tokenAmount);
    }

    function swapToEther(
        uint256 _amount
    ) external onlyOwner returns (uint256 etherAmount) {
        IERC20 token = IERC20(tokenAddr);

        require(_amount != 0, "zero");
        require(_amount <= token.balanceOf(address(this)), "exceed balance");

        if (!sellOnDex) {
            token.approve(LidoWithdrawalQueue, _amount);

            ILidoWithdrawalQueue withdrawalQueue = ILidoWithdrawalQueue(
                LidoWithdrawalQueue
            );
            uint256 maxAmountPerRequest = withdrawalQueue
                .MAX_STETH_WITHDRAWAL_AMOUNT();
            uint256 minAmountPerRequest = withdrawalQueue
                .MIN_STETH_WITHDRAWAL_AMOUNT();

            uint256[] memory amounts;
            if (_amount <= maxAmountPerRequest) {
                amounts = new uint256[](1);
                amounts[0] = _amount;
            } else {
                uint256 length = _amount / maxAmountPerRequest + 1;
                uint256 remainder = _amount % maxAmountPerRequest;

                if (remainder >= minAmountPerRequest) {
                    amounts = new uint256[](length);
                    amounts[length - 1] = remainder;
                } else {
                    amounts = new uint256[](length - 1);
                }

                uint256 i;
                for (i; i < length - 1; i++) {
                    amounts[i] = maxAmountPerRequest;
                }
            }

            uint256[] memory ids = withdrawalQueue.requestWithdrawals(
                amounts,
                address(this)
            );
            require(ids.length != 0, "Lido request withdrawal error");

            etherAmount = _amount;
        } else {
            TransferHelper.safeApprove(tokenAddr, SWAPPING, _amount);
            etherAmount = SwappingAggregator(SWAPPING).swap(
                tokenAddr,
                _amount,
                true
            );
        }

        emit Swap(tokenAddr, address(0), _amount, etherAmount);
    }

    function setRouter(bool _buyOnDex, bool _sellOnDex) external onlyOwner {
        buyOnDex = _buyOnDex;
        sellOnDex = _sellOnDex;

        emit SetRouter(_buyOnDex, _sellOnDex);
    }

    function setEigenOperator(address _operator) external onlyOwner {
        require(_operator != address(0), "ZERO ADDR");
        require(
            IDelegationManager(delegationManager).isOperator(_operator),
            "not operator"
        );

        emit SetEigenOperator(eigenOperator, _operator);
        eigenOperator = _operator;
    }

    function setReferral(address _referral) external onlyOwner {
        require(_referral != address(0), "ZERO ADDR");

        emit SetReferral(referral, _referral);
        referral = _referral;
    }

    function claimPendingAssets(uint256[] memory _ids) external onlyOwner {
        uint256 length = _ids.length;
        require(length != 0, "invalid length");

        for (uint256 i; i < length; i++) {
            if (_ids[i] == 0) continue;
            ILidoWithdrawalQueue(LidoWithdrawalQueue).claimWithdrawal(_ids[i]);
        }
    }

    function claimAllPendingAssets() external onlyOwner {
        (uint256[] memory ids, , ) = checkPendingAssets();

        uint256 length = ids.length;
        for (uint256 i; i < length; i++) {
            if (ids[i] == 0) continue;
            ILidoWithdrawalQueue(LidoWithdrawalQueue).claimWithdrawal(ids[i]);
        }
    }

    // TODO Gas consumption
    function checkPendingAssets()
        public
        returns (
            uint256[] memory ids,
            uint256 totalClaimable,
            uint256 totalPending
        )
    {
        ILidoWithdrawalQueue queue = ILidoWithdrawalQueue(LidoWithdrawalQueue);

        uint256[] memory allIds = queue.getWithdrawalRequests(address(this));

        if (allIds.length == 0) {
            return (new uint256[](0), 0, 0);
        }

        ids = new uint256[](allIds.length);

        ILidoWithdrawalQueue.WithdrawalRequestStatus[] memory statuses = queue
            .getWithdrawalStatus(allIds);

        uint256 j;
        uint256 length = statuses.length;
        for (uint256 i; i < length; i++) {
            ILidoWithdrawalQueue.WithdrawalRequestStatus
                memory status = statuses[i];
            if (status.isClaimed) {
                continue;
            }
            if (status.isFinalized) {
                ids[j++] = allIds[i];
                totalClaimable = totalClaimable + status.amountOfStETH;
            } else {
                totalPending = totalPending + status.amountOfStETH;
            }
        }

        assembly {
            mstore(ids, j)
        }
    }

    function getRestakingValue() public view returns (uint256 value) {
        value = IEigenStrategy(eigenStrategy).userUnderlyingView(address(this));
    }

    function getUnstakingValue() public view returns (uint256 value) {
        uint256 length = withdrawalRootsSet.length();

        uint256 i;
        for (i; i < length; i++) {
            value += IEigenStrategy(eigenStrategy).sharesToUnderlyingView(
                withdrawingShares[withdrawalRootsSet.at(i)]
            );
        }
    }

    function getWithdrawalRoots() public view returns (bytes32[] memory roots) {
        uint256 length = withdrawalRootsSet.length();

        roots = new bytes32[](length);

        for (uint256 i; i < length; i++) {
            roots[i] = withdrawalRootsSet.at(i);
        }
    }

    function calculateWithdrawalRoot(
        IDelegationManager.Withdrawal memory withdrawal
    ) public pure returns (bytes32) {
        return keccak256(abi.encode(withdrawal));
    }

    function invoke(
        address target,
        bytes memory data
    ) external onlyOwner returns (bytes memory result) {
        require(target != tokenAddr, "not permit");

        bool success;
        (success, result) = target.call{value: 0}(data);
        if (!success) {
            // solhint-disable-next-line no-inline-assembly
            assembly {
                returndatacopy(0, 0, returndatasize())
                revert(0, returndatasize())
            }
        }
        emit Invoked(target, 0, data);
    }

    receive() external payable {}
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
import {IQuoter} from "@uniswap/v3-periphery/contracts/interfaces/IQuoter.sol";

import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {IStableSwap} from "../interfaces/IStableSwap.sol";
import {IWETH9} from "../interfaces/IWETH9.sol";

contract SwappingAggregator is ReentrancyGuard {
    uint256 internal constant MULTIPLIER = 1e18;
    uint256 internal constant ONE_HUNDRED_PERCENT = 1e6;

    address internal immutable QUOTER =
        0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6;
    address internal immutable ROUTER =
        0xE592427A0AEce92De3Edee1F18E0157C05861564;

    address internal immutable WETH9;

    address internal immutable CURVE_ETH =
        0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    mapping(address => address) public uniV3Pools; // token => pool
    mapping(address => address) public curvePools;
    mapping(address => uint8) public curvePoolType; // 0 - stableswap; 1 - two crypto swap
    mapping(address => uint256) public slippage;
    mapping(address => uint24) public fees;

    address public governance;

    event SetSlippage(
        address indexed token,
        uint256 oldSlippage,
        uint256 newSlippage
    );
    event TransferGovernance(address oldAddr, address newAddr);

    modifier onlyGovernance() {
        require(governance == msg.sender, "not governace");
        _;
    }

    enum DEX_TYPE {
        UNISWAPV3,
        CURVE
    }

    constructor(
        address _wETH,
        address[] memory _tokens,
        address[] memory _uniPools,
        address[] memory _curvePools,
        uint8[] memory _curvePoolTypes,
        uint256[] memory _slippages,
        uint24[] memory _fees
    ) {
        uint256 length = _tokens.length;
        require(
            length == _uniPools.length &&
                length == _curvePools.length &&
                length == _curvePoolTypes.length,
            "invalid length"
        );

        require(_wETH != address(0), "ZERO ADDRESS");

        for (uint256 i; i < length; i++) {
            require(
                _tokens[i] != address(0) &&
                    _uniPools[i] != address(0) &&
                    _curvePools[i] != address(0),
                "ZERO ADDRESS"
            );

            uniV3Pools[_tokens[i]] = _uniPools[i];
            curvePools[_tokens[i]] = _curvePools[i];
            curvePoolType[_curvePools[i]] = _curvePoolTypes[i];
            slippage[_tokens[i]] = _slippages[i];
            fees[_tokens[i]] = _fees[i];
        }

        governance = msg.sender;

        WETH9 = _wETH;
    }

    function swap(
        address _token,
        uint256 _amount,
        bool _isSell
    ) external payable returns (uint256 amount) {
        if (
            uniV3Pools[_token] == address(0) && curvePools[_token] == address(0)
        ) {
            return 0;
        }

        (DEX_TYPE dex, uint256 expected) = getBestRouter(
            _token,
            _amount,
            _isSell
        );
        if (!_isSell) {
            require(_amount == msg.value, "wrong value");
        }
        if (expected == 0) {
            return 0;
        }
        if (dex == DEX_TYPE.UNISWAPV3) {
            amount = swapOnUniV3(_token, _amount, _isSell);
        } else {
            amount = swapOnCurve(_token, _amount, _isSell);
        }
    }

    function swapOnUniV3(
        address _token,
        uint256 _amount,
        bool _isSell
    ) internal nonReentrant returns (uint256 amount) {
        uint256 minReceived = calMinimumReceivedAmount(
            _amount,
            slippage[_token]
        );

        address pool = uniV3Pools[_token];
        if (_isSell) {
            TransferHelper.safeTransferFrom(
                _token,
                msg.sender,
                address(this),
                _amount
            );
            TransferHelper.safeApprove(_token, ROUTER, _amount);

            ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
                .ExactInputSingleParams({
                    tokenIn: _token,
                    tokenOut: WETH9,
                    fee: fees[_token],
                    recipient: address(this),
                    deadline: block.timestamp,
                    amountIn: _amount,
                    amountOutMinimum: minReceived,
                    sqrtPriceLimitX96: 0
                });

            amount = ISwapRouter(ROUTER).exactInputSingle(params);

            IWETH9(WETH9).withdraw(amount);

            TransferHelper.safeTransferETH(msg.sender, address(this).balance);
        } else {
            IWETH9(WETH9).deposit{value: msg.value}();

            TransferHelper.safeApprove(
                WETH9,
                ROUTER,
                IWETH9(WETH9).balanceOf(address(this))
            );

            ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
                .ExactInputSingleParams({
                    tokenIn: WETH9,
                    tokenOut: _token,
                    fee: fees[_token],
                    recipient: address(this),
                    deadline: block.timestamp,
                    amountIn: _amount,
                    amountOutMinimum: minReceived,
                    sqrtPriceLimitX96: 0
                });

            amount = ISwapRouter(ROUTER).exactInputSingle(params);

            TransferHelper.safeTransfer(
                _token,
                msg.sender,
                IERC20(_token).balanceOf(address(this))
            );
        }
    }

    function swapOnCurve(
        address _token,
        uint256 _amount,
        bool _isSell
    ) internal nonReentrant returns (uint256 amount) {
        uint256 minReceived = calMinimumReceivedAmount(
            _amount,
            slippage[_token]
        );

        address pool = curvePools[_token];
        (uint256 e, uint256 t, bool wrapped) = getCurveCoinIndex(_token);

        uint8 poolType = curvePoolType[pool];

        if (_isSell) {
            TransferHelper.safeTransferFrom(
                _token,
                msg.sender,
                address(this),
                _amount
            );
            TransferHelper.safeApprove(_token, pool, _amount);

            if (poolType == 0) {
                amount = IStableSwap(pool).exchange(
                    int128(int256(t)),
                    int128(int256(e)),
                    _amount,
                    minReceived
                );
            } else {
                amount = IStableSwap(pool).exchange(t, e, _amount, minReceived);
            }

            if (wrapped) {
                IWETH9 weth = IWETH9(WETH9);
                weth.withdraw(weth.balanceOf(address(this)));
            }

            TransferHelper.safeTransferETH(msg.sender, address(this).balance);
        } else {
            if (poolType == 0) {
                if (!wrapped) {
                    amount = IStableSwap(pool).exchange{value: _amount}(
                        int128(int256(e)),
                        int128(int256(t)),
                        _amount,
                        minReceived
                    );
                } else {
                    IWETH9 weth = IWETH9(WETH9);
                    weth.deposit{value: _amount}();
                    weth.approve(pool, _amount);

                    amount = IStableSwap(pool).exchange(
                        int128(int256(e)),
                        int128(int256(t)),
                        _amount,
                        minReceived
                    );
                }
            } else {
                if (!wrapped) {
                    amount = IStableSwap(pool).exchange{value: _amount}(
                        e,
                        t,
                        _amount,
                        minReceived
                    );
                } else {
                    IWETH9 weth = IWETH9(WETH9);
                    weth.deposit{value: _amount}();
                    weth.approve(pool, _amount);

                    amount = IStableSwap(pool).exchange(
                        e,
                        t,
                        _amount,
                        minReceived
                    );
                }
            }

            TransferHelper.safeTransfer(
                _token,
                msg.sender,
                IERC20(_token).balanceOf(address(this))
            );
        }
    }

    function getBestRouter(
        address _token,
        uint256 _amount,
        bool _isSell
    ) public returns (DEX_TYPE dex, uint256 out) {
        uint256 uniV3Out = getUniV3Out(_token, _amount, _isSell);
        uint256 curveOut = getCurveOut(_token, _amount, _isSell);

        return
            uniV3Out > curveOut
                ? (DEX_TYPE.UNISWAPV3, uniV3Out)
                : (DEX_TYPE.CURVE, curveOut);
    }

    function getUniV3Out(
        address _token,
        uint256 _amount,
        bool _isSell
    ) public returns (uint256 out) {
        if (uniV3Pools[_token] == address(0)) {
            return 0;
        }

        if (_isSell) {
            out = IQuoter(QUOTER).quoteExactInputSingle(
                _token,
                WETH9,
                fees[_token],
                _amount,
                0
            );
        } else {
            out = IQuoter(QUOTER).quoteExactInputSingle(
                WETH9,
                _token,
                fees[_token],
                _amount,
                0
            );
        }
    }

    function getCurveOut(
        address _token,
        uint256 _amount,
        bool _isSell
    ) public returns (uint256 out) {
        address poolAddr = curvePools[_token];

        if (poolAddr == address(0)) {
            return 0;
        }

        (uint256 e, uint256 t, ) = getCurveCoinIndex(_token);

        IStableSwap pool = IStableSwap(poolAddr);
        uint8 poolType = curvePoolType[poolAddr];
        if (_isSell) {
            if (poolType == 0) {
                out = pool.get_dy(
                    int128(int256(t)),
                    int128(int256(e)),
                    _amount
                );
            } else {
                out = pool.get_dy(t, e, _amount);
            }
        } else {
            if (poolType == 0) {
                out = pool.get_dy(
                    int128(int256(e)),
                    int128(int256(t)),
                    _amount
                );
            } else {
                out = pool.get_dy(e, t, _amount);
            }
        }
    }

    function getCurveCoinIndex(
        address _token
    ) public view returns (uint256 i, uint256 j, bool wrapped) {
        // i for Ether, j for another token
        IStableSwap pool = IStableSwap(curvePools[_token]);

        if (pool.coins(0) == WETH9 || pool.coins(1) == WETH9) {
            wrapped = true;
        }

        if (pool.coins(0) == CURVE_ETH || pool.coins(0) == WETH9) {
            i = 0;
            j = 1;
        } else {
            i = 1;
            j = 0;
        }
    }

    function calMinimumReceivedAmount(
        uint256 _amount,
        uint256 _slippage
    ) internal view returns (uint256 amount) {
        amount = (_amount * _slippage) / ONE_HUNDRED_PERCENT;
    }

    function setSlippage(
        address _token,
        uint256 _slippage
    ) external onlyGovernance {
        emit SetSlippage(_token, slippage[_token], _slippage);

        slippage[_token] = _slippage;
    }

    function setUniRouter(
        address _token,
        address _uniPool,
        uint256 _slippage,
        uint24 _fee
    ) external onlyGovernance {
        require(_token != address(0), "ZERO ADDRESS");

        uniV3Pools[_token] = _uniPool;
        slippage[_token] = _slippage;
        fees[_token] = _fee;
    }

    function setCurveRouter(
        address _token,
        address _curvePool,
        uint8 _curvePoolType,
        uint256 _slippage
    ) external onlyGovernance {
        require(_token != address(0), "ZERO ADDRESS");

        curvePools[_token] = _curvePool;
        curvePoolType[_curvePool] = _curvePoolType;
        slippage[_token] = _slippage;
    }

    function setNewGovernance(address _governance) external onlyGovernance {
        emit TransferGovernance(governance, _governance);

        governance = _governance;
    }

    receive() external payable {}
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";

import {Strategy} from "./Strategy.sol";
import {AssetsVault} from "../AssetsVault.sol";

contract StrategyController {
    using EnumerableSet for EnumerableSet.AddressSet;

    uint256 internal constant ONE_HUNDRED_PERCENT = 1e6;

    address public stoneVault;
    address payable public immutable assetsVault;

    EnumerableSet.AddressSet private strategies;

    mapping(address => uint256) public ratios;

    struct StrategyDiff {
        address strategy;
        bool isDeposit;
        uint256 amount;
    }

    modifier onlyVault() {
        require(stoneVault == msg.sender, "not vault");
        _;
    }

    constructor(
        address payable _assetsVault,
        address[] memory _strategies,
        uint256[] memory _ratios
    ) {
        require(_assetsVault != address(0), "ZERO ADDRESS");

        uint256 length = _strategies.length;
        for (uint256 i; i < length; i++) {
            require(_strategies[i] != address(0), "ZERO ADDRESS");
        }

        stoneVault = msg.sender;
        assetsVault = _assetsVault;

        _initStrategies(_strategies, _ratios);
    }

    function forceWithdraw(
        uint256 _amount
    ) external onlyVault returns (uint256 actualAmount) {
        uint256 balanceBeforeRepay = address(this).balance;

        if (balanceBeforeRepay >= _amount) {
            _repayToVault();

            actualAmount = _amount;
        } else {
            actualAmount =
                _forceWithdraw(_amount - balanceBeforeRepay) +
                balanceBeforeRepay;
        }
    }

    function setStrategies(
        address[] memory _strategies,
        uint256[] memory _ratios
    ) external onlyVault {
        _setStrategies(_strategies, _ratios);
    }

    function addStrategy(address _strategy) external onlyVault {
        require(!strategies.contains(_strategy), "already exist");

        strategies.add(_strategy);
    }

    function rebaseStrategies(
        uint256 _in,
        uint256 _out
    ) external payable onlyVault {
        _rebase(_in, _out);
    }

    function destroyStrategy(address _strategy) external onlyVault {
        _destoryStrategy(_strategy);
    }

    function _rebase(uint256 _in, uint256 _out) internal {
        require(_in == 0 || _out == 0, "only deposit or withdraw");

        if (_in != 0) {
            AssetsVault(assetsVault).withdraw(address(this), _in);
        }
        uint256 total = getAllStrategyValidValue();
        if (total < _out) {
            total = 0;
        } else {
            total = total + _in - _out;
        }

        uint256 length = strategies.length();
        StrategyDiff[] memory diffs = new StrategyDiff[](length);
        uint256 head;
        uint256 tail = length - 1;
        for (uint i; i < length; i++) {
            address strategy = strategies.at(i);
            if (ratios[strategy] == 0) {
                _clearStrategy(strategy, true);
                continue;
            }
            uint256 newPosition = (total * ratios[strategy]) /
                ONE_HUNDRED_PERCENT;
            uint256 position = getStrategyValidValue(strategy);

            if (newPosition < position) {
                diffs[head] = StrategyDiff(
                    strategy,
                    false,
                    position - newPosition
                );
                head++;
            } else if (newPosition > position) {
                diffs[tail] = StrategyDiff(
                    strategy,
                    true,
                    newPosition - position
                );
                if (tail != 0) {
                    tail--;
                }
            }
        }

        length = diffs.length;
        for (uint256 i; i < length; i++) {
            StrategyDiff memory diff = diffs[i];

            if (diff.amount == 0) {
                continue;
            }

            if (diff.isDeposit) {
                if (address(this).balance < diff.amount) {
                    diff.amount = address(this).balance;
                }
                _depositToStrategy(diff.strategy, diff.amount);
            } else {
                _withdrawFromStrategy(diff.strategy, diff.amount);
            }
        }

        _repayToVault();
    }

    function _repayToVault() internal {
        if (address(this).balance != 0) {
            TransferHelper.safeTransferETH(assetsVault, address(this).balance);
        }
    }

    function _depositToStrategy(address _strategy, uint256 _amount) internal {
        Strategy(_strategy).deposit{value: _amount}();
    }

    function _withdrawFromStrategy(
        address _strategy,
        uint256 _amount
    ) internal {
        Strategy(_strategy).withdraw(_amount);
    }

    function _forceWithdraw(
        uint256 _amount
    ) internal returns (uint256 actualAmount) {
        uint256 length = strategies.length();

        uint256 allRatios;
        for (uint i; i < length; i++) {
            address strategy = strategies.at(i);
            allRatios += ratios[strategy];
        }

        for (uint i; i < length; i++) {
            address strategy = strategies.at(i);

            uint256 withAmount = (_amount * ratios[strategy]) / allRatios;

            if (withAmount != 0) {
                actualAmount =
                    Strategy(strategy).instantWithdraw(withAmount) +
                    actualAmount;
            }
        }

        _repayToVault();
    }

    function getStrategyValue(
        address _strategy
    ) public returns (uint256 _value) {
        return Strategy(_strategy).getAllValue();
    }

    function getStrategyValidValue(
        address _strategy
    ) public returns (uint256 _value) {
        return Strategy(_strategy).getInvestedValue();
    }

    function getStrategyPendingValue(
        address _strategy
    ) public returns (uint256 _value) {
        return Strategy(_strategy).getPendingValue();
    }

    function getAllStrategiesValue() public returns (uint256 _value) {
        uint256 length = strategies.length();
        for (uint i; i < length; i++) {
            _value = _value + getStrategyValue(strategies.at(i));
        }
    }

    function getAllStrategyValidValue() public returns (uint256 _value) {
        uint256 length = strategies.length();
        for (uint i; i < length; i++) {
            _value = _value + getStrategyValidValue(strategies.at(i));
        }
    }

    function getAllStrategyPendingValue() public returns (uint256 _value) {
        uint256 length = strategies.length();
        for (uint i; i < length; i++) {
            _value = _value + getStrategyPendingValue(strategies.at(i));
        }
    }

    function getStrategies()
        public
        view
        returns (address[] memory addrs, uint256[] memory portions)
    {
        uint256 length = strategies.length();

        addrs = new address[](length);
        portions = new uint256[](length);

        for (uint256 i; i < length; i++) {
            address addr = strategies.at(i);
            addrs[i] = addr;
            portions[i] = ratios[addr];
        }
    }

    function _initStrategies(
        address[] memory _strategies,
        uint256[] memory _ratios
    ) internal {
        require(_strategies.length == _ratios.length, "invalid length");

        uint256 totalRatio;
        uint256 length = _strategies.length;
        for (uint i; i < length; i++) {
            strategies.add(_strategies[i]);
            ratios[_strategies[i]] = _ratios[i];
            totalRatio = totalRatio + _ratios[i];
        }
        require(totalRatio <= ONE_HUNDRED_PERCENT, "exceed 100%");
    }

    function _setStrategies(
        address[] memory _strategies,
        uint256[] memory _ratios
    ) internal {
        uint256 length = _strategies.length;
        require(length == _ratios.length, "invalid length");

        uint256 oldLength = strategies.length();
        for (uint i; i < oldLength; i++) {
            ratios[strategies.at(i)] = 0;
        }
        uint256 totalRatio;
        for (uint i; i < length; i++) {
            require(
                Strategy(_strategies[i]).controller() == address(this),
                "controller mismatch"
            );
            strategies.add(_strategies[i]);
            ratios[_strategies[i]] = _ratios[i];
            totalRatio = totalRatio + _ratios[i];
        }
        require(totalRatio <= ONE_HUNDRED_PERCENT, "exceed 100%");
    }

    function clearStrategy(address _strategy) public onlyVault {
        _clearStrategy(_strategy, false);
    }

    function _clearStrategy(address _strategy, bool _isRebase) internal {
        Strategy(_strategy).clear();

        if (!_isRebase) {
            _repayToVault();
        }
    }

    function _destoryStrategy(address _strategy) internal {
        require(_couldDestroyStrategy(_strategy), "still active");

        strategies.remove(_strategy);

        _repayToVault();
    }

    function _couldDestroyStrategy(
        address _strategy
    ) internal returns (bool status) {
        return
            ratios[_strategy] == 0 && Strategy(_strategy).getAllValue() < 1e4;
    }

    function setNewVault(address _vault) external onlyVault {
        stoneVault = _vault;
    }

    receive() external payable {}
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import {StrategyController} from "../strategies/StrategyController.sol";

abstract contract Strategy {
    address payable public immutable controller;

    address public governance;

    uint256 public latestUpdateTime;
    uint256 public bufferTime = 12;

    string public name;

    modifier onlyGovernance() {
        require(governance == msg.sender, "not governace");
        _;
    }

    modifier notAtSameBlock() {
        require(
            latestUpdateTime + bufferTime <= block.timestamp,
            "at the same block"
        );
        _;
    }

    event TransferGovernance(address oldOwner, address newOwner);

    constructor(address payable _controller, string memory _name) {
        require(_controller != address(0), "ZERO ADDRESS");

        governance = msg.sender;
        controller = _controller;
        name = _name;
    }

    modifier onlyController() {
        require(controller == msg.sender, "not controller");
        _;
    }

    function deposit() public payable virtual onlyController notAtSameBlock {}

    function withdraw(
        uint256 _amount
    )
        public
        virtual
        onlyController
        notAtSameBlock
        returns (uint256 actualAmount)
    {}

    function instantWithdraw(
        uint256 _amount
    )
        public
        virtual
        onlyController
        notAtSameBlock
        returns (uint256 actualAmount)
    {}

    function clear() public virtual onlyController returns (uint256 amount) {}

    function execPendingRequest(
        uint256 _amount
    ) public virtual returns (uint256 amount) {}

    function getAllValue() public virtual returns (uint256 value) {}

    function getPendingValue() public virtual returns (uint256 value) {}

    function getInvestedValue() public virtual returns (uint256 value) {}

    function checkPendingStatus()
        public
        virtual
        returns (uint256 pending, uint256 executable)
    {}

    function setGovernance(address governance_) external onlyGovernance {
        emit TransferGovernance(governance, governance_);
        governance = governance_;
    }

    function setBufferTime(uint256 _time) external onlyGovernance {
        bufferTime = _time;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";

import {StrategyController} from "../strategies/StrategyController.sol";

abstract contract EigenStrategy is Ownable2Step {
    address payable public immutable controller;

    uint256 public latestUpdateTime;
    uint256 public bufferTime = 12;

    string public name;

    modifier notAtSameBlock() {
        require(
            latestUpdateTime + bufferTime <= block.timestamp,
            "at the same block"
        );
        _;
    }

    event TransferGovernance(address oldOwner, address newOwner);

    constructor(address payable _controller, string memory _name) {
        require(_controller != address(0), "ZERO ADDRESS");

        controller = _controller;
        name = _name;
    }

    modifier onlyController() {
        require(controller == msg.sender, "not controller");
        _;
    }

    function deposit() public payable virtual onlyController notAtSameBlock {}

    function withdraw(
        uint256 _amount
    )
        public
        virtual
        onlyController
        notAtSameBlock
        returns (uint256 actualAmount)
    {}

    function instantWithdraw(
        uint256 _amount
    )
        public
        virtual
        onlyController
        notAtSameBlock
        returns (uint256 actualAmount)
    {}

    function clear() public virtual onlyController returns (uint256 amount) {}

    function execPendingRequest(
        uint256 _amount
    ) public virtual returns (uint256 amount) {}

    function getAllValue() public virtual returns (uint256 value) {}

    function getPendingValue() public virtual returns (uint256 value) {}

    function getInvestedValue() public virtual returns (uint256 value) {}

    function checkPendingStatus()
        public
        virtual
        returns (uint256 pending, uint256 executable)
    {}

    function setBufferTime(uint256 _time) external onlyOwner {
        bufferTime = _time;
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.21;

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

/// @title Interface for WETH9
interface IWETH9 is IERC20 {
    /// @notice Deposit ether to get wrapped ether
    function deposit() external payable;

    /// @notice Withdraw wrapped ether to get ether
    function withdraw(uint256) external;
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

interface IStrategyManager {
    function depositIntoStrategy(
        address strategy,
        address token,
        uint256 amount
    ) external returns (uint256 shares);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

interface IStableSwap {
    function lp_token() external view returns (address);

    function coins(uint256 i) external view returns (address);

    function get_dy(
        int128 i,
        int128 j,
        uint256 dx
    ) external view returns (uint256);

    function get_dy(
        uint256 i,
        uint256 j,
        uint256 dx
    ) external view returns (uint256);

    function fee() external view returns (uint256);

    function admin_fee() external view returns (uint256);

    function calc_withdraw_one_coin(
        uint256 _token_amount,
        int128 i
    ) external view returns (uint256);

    function calc_token_amount(
        uint256[2] memory,
        bool
    ) external view returns (uint256);

    function remove_liquidity_imbalance(
        uint256[2] memory _amounts,
        uint256 _max_burn_amount
    ) external returns (uint256);

    function remove_liquidity_one_coin(
        uint256 _token_amount,
        int128 i,
        uint256 _min_amount
    ) external returns (uint256);

    function add_liquidity(
        uint256[2] memory amounts,
        uint256 min_mint_amount
    ) external payable returns (uint256);

    function exchange(
        int128 i,
        int128 j,
        uint256 dx,
        uint256 min_dy
    ) external payable returns (uint256);

    function exchange(
        uint256 i,
        uint256 j,
        uint256 dx,
        uint256 min_dy
    ) external payable returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

interface ILidoWithdrawalQueue {
    struct WithdrawalRequestStatus {
        /// @notice stETH token amount that was locked on withdrawal queue for this request
        uint256 amountOfStETH;
        /// @notice amount of stETH shares locked on withdrawal queue for this request
        uint256 amountOfShares;
        /// @notice address that can claim or transfer this request
        address owner;
        /// @notice timestamp of when the request was created, in seconds
        uint256 timestamp;
        /// @notice true, if request is finalized
        bool isFinalized;
        /// @notice true, if request is claimed. Request is claimable if (isFinalized && !isClaimed)
        bool isClaimed;
    }

    function MAX_STETH_WITHDRAWAL_AMOUNT() external view returns (uint256);

    function MIN_STETH_WITHDRAWAL_AMOUNT() external view returns (uint256);

    function getWithdrawalRequests(
        address _owner
    ) external view returns (uint256[] memory requestsIds);

    function getWithdrawalStatus(
        uint256[] calldata _requestIds
    ) external view returns (WithdrawalRequestStatus[] memory statuses);

    function requestWithdrawals(
        uint256[] calldata _amounts,
        address _owner
    ) external returns (uint256[] memory requestIds);

    function claimWithdrawals(
        uint256[] calldata _requestIds,
        uint256[] calldata _hints
    ) external;

    function claimWithdrawalsTo(
        uint256[] calldata _requestIds,
        uint256[] calldata _hints,
        address _recipient
    ) external;

    function claimWithdrawal(uint256 _requestId) external;
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

interface ILido {
    function submit(address _referral) external payable returns (uint256);

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

    function approve(address spender, uint256 amount) external returns (bool);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

interface IEigenStrategy {
    function userUnderlyingView(address user) external view returns (uint256);

    function shares(address user) external view returns (uint256);

    function sharesToUnderlyingView(
        uint256 amountShares
    ) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

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

interface IDelegationManager {
    struct SignatureWithExpiry {
        bytes signature;
        uint256 expiry;
    }

    struct QueuedWithdrawalParams {
        address[] strategies;
        uint256[] shares;
        address withdrawer;
    }

    struct Withdrawal {
        address staker;
        address delegatedTo;
        address withdrawer;
        uint256 nonce;
        uint32 startBlock;
        address[] strategies;
        uint256[] shares;
    }

    event WithdrawalQueued(bytes32 withdrawalRoot, Withdrawal withdrawal);

    function delegateTo(
        address operator,
        SignatureWithExpiry memory approverSignatureAndExpiry,
        bytes32 approverSalt
    ) external;

    function undelegate(
        address staker
    ) external returns (bytes32[] memory withdrawalRoots);

    function queueWithdrawals(
        QueuedWithdrawalParams[] calldata queuedWithdrawalParams
    ) external returns (bytes32[] memory);

    function completeQueuedWithdrawal(
        Withdrawal calldata withdrawal,
        IERC20[] calldata tokens,
        uint256 middlewareTimesIndex,
        bool receiveAsTokens
    ) external;

    function isOperator(address operator) external view returns (bool);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";

contract AssetsVault {
    address public stoneVault;
    address public strategyController;

    modifier onlyPermit() {
        require(
            stoneVault == msg.sender || strategyController == msg.sender,
            "not permit"
        );
        _;
    }

    constructor(address _stoneVault, address _strategyController) {
        require(
            _stoneVault != address(0) && _strategyController != address(0),
            "ZERO ADDRESS"
        );
        stoneVault = _stoneVault;
        strategyController = _strategyController;
    }

    function deposit() external payable {
        require(msg.value != 0, "too small");
    }

    function withdraw(address _to, uint256 _amount) external onlyPermit {
        TransferHelper.safeTransferETH(_to, _amount);
    }

    function setNewVault(address _vault) external onlyPermit {
        stoneVault = _vault;
    }

    function getBalance() external view returns (uint256 amount) {
        amount = address(this).balance;
    }

    receive() external payable {}
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.6.0;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

library TransferHelper {
    /// @notice Transfers tokens from the targeted address to the given destination
    /// @notice Errors with 'STF' if transfer fails
    /// @param token The contract address of the token to be transferred
    /// @param from The originating address from which the tokens will be transferred
    /// @param to The destination address of the transfer
    /// @param value The amount to be transferred
    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) =
            token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'STF');
    }

    /// @notice Transfers tokens from msg.sender to a recipient
    /// @dev Errors with ST if transfer fails
    /// @param token The contract address of the token which will be transferred
    /// @param to The recipient of the transfer
    /// @param value The value of the transfer
    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'ST');
    }

    /// @notice Approves the stipulated contract to spend the given allowance in the given token
    /// @dev Errors with 'SA' if transfer fails
    /// @param token The contract address of the token to be approved
    /// @param to The target of the approval
    /// @param value The amount of the given token the target will be allowed to spend
    function safeApprove(
        address token,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'SA');
    }

    /// @notice Transfers ETH to the recipient address
    /// @dev Fails with `STE`
    /// @param to The destination of the transfer
    /// @param value The value to be transferred
    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, 'STE');
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.5;
pragma abicoder v2;

import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol';

/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface ISwapRouter is IUniswapV3SwapCallback {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);

    struct ExactInputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);

    struct ExactOutputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);

    struct ExactOutputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.5;
pragma abicoder v2;

/// @title Quoter Interface
/// @notice Supports quoting the calculated amounts from exact input or exact output swaps
/// @dev These functions are not marked view because they rely on calling non-view functions and reverting
/// to compute the result. They are also not gas efficient and should not be called on-chain.
interface IQuoter {
    /// @notice Returns the amount out received for a given exact input swap without executing the swap
    /// @param path The path of the swap, i.e. each token pair and the pool fee
    /// @param amountIn The amount of the first token to swap
    /// @return amountOut The amount of the last token that would be received
    function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut);

    /// @notice Returns the amount out received for a given exact input but for a swap of a single pool
    /// @param tokenIn The token being swapped in
    /// @param tokenOut The token being swapped out
    /// @param fee The fee of the token pool to consider for the pair
    /// @param amountIn The desired input amount
    /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
    /// @return amountOut The amount of `tokenOut` that would be received
    function quoteExactInputSingle(
        address tokenIn,
        address tokenOut,
        uint24 fee,
        uint256 amountIn,
        uint160 sqrtPriceLimitX96
    ) external returns (uint256 amountOut);

    /// @notice Returns the amount in required for a given exact output swap without executing the swap
    /// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
    /// @param amountOut The amount of the last token to receive
    /// @return amountIn The amount of first token required to be paid
    function quoteExactOutput(bytes memory path, uint256 amountOut) external returns (uint256 amountIn);

    /// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool
    /// @param tokenIn The token being swapped in
    /// @param tokenOut The token being swapped out
    /// @param fee The fee of the token pool to consider for the pair
    /// @param amountOut The desired output amount
    /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
    /// @return amountIn The amount required as the input for the swap in order to receive `amountOut`
    function quoteExactOutputSingle(
        address tokenIn,
        address tokenOut,
        uint24 fee,
        uint256 amountOut,
        uint160 sqrtPriceLimitX96
    ) external returns (uint256 amountIn);
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
    /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
    /// @dev In the implementation you must pay the pool tokens owed for the swap.
    /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
    /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
    /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
    /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
    /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
    /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
    /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
    function uniswapV3SwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata data
    ) external;
}

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

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastValue;
                // Update the index for the moved value
                set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
            }

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

            // Delete the index for the deleted slot
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }

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

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

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

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

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

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

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

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

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

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

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

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

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

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

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

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

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

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

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

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

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

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

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

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

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

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

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

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

        return result;
    }
}

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

pragma solidity ^0.8.0;

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

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

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

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

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

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

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

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

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

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.0;

import "./Ownable.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

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

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

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
        _transferOwnership(sender);
    }
}

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

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

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

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

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

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

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

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

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

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

Please enter a contract address above to load the contract details and source code.

Context size (optional):