Contract 0x215206661f153e06d44a7E6329B2874710B9CE7F

Txn Hash Method
Block
From
To
Value [Txn Fee]
0x3bf617a878f54370bf9358f259f16c34ec53ac7fd5d0199c75ff01b90b624af40x60806040175724832022-07-20 11:58:4270 days 22 hrs ago0xdbeb35889fa2677564ed765df2f7920d30078972 IN  Create: MinimaxMain0 AVAX0.1324305525
[ Download CSV Export 
Parent Txn Hash Block From To Value
Loading
This contract contains unverified libraries: PositionLib
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.

Contract Source Code Verified (Exact Match)

Contract Name:
MinimaxMain

Compiler Version
v0.8.13+commit.abaa5c0e

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
File 1 of 36 : MinimaxMain.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "./helpers/Math.sol";
import "./interfaces/IMinimaxStaking.sol";
import "./MinimaxStaking.sol";
import "./pool/IPoolAdapter.sol";
import "./interfaces/IERC20Decimals.sol";
import "./interfaces/IPriceOracle.sol";
import "./interfaces/IPancakeRouter.sol";
import "./interfaces/ISmartChef.sol";
import "./interfaces/IGelatoOps.sol";
import "./interfaces/IWrapped.sol";
import "./ProxyCaller.sol";
import "./ProxyCallerApi.sol";
import "./ProxyPool.sol";
import "./market/Market.sol";
import "./PositionInfo.sol";
import "./PositionExchangeLib.sol";
import "./PositionBalanceLib.sol";
import "./PositionLib.sol";
import "./interfaces/IMinimaxMain.sol";
import "./market/v2/IPairToken.sol";

/*
    MinimaxMain
*/
contract MinimaxMain is IMinimaxMain, OwnableUpgradeable, ReentrancyGuardUpgradeable {
    // -----------------------------------------------------------------------------------------------------------------
    // Using declarations.

    using SafeERC20Upgradeable for IERC20Upgradeable;

    using ProxyCallerApi for ProxyCaller;

    using ProxyPool for ProxyCaller[];

    // -----------------------------------------------------------------------------------------------------------------
    // Enums.

    enum ClosePositionReason {
        WithdrawnByOwner,
        LiquidatedByAutomation
    }

    // -----------------------------------------------------------------------------------------------------------------
    // Events.

    // NB: If `estimatedStakedTokenPrice` is equal to `0`, then the price is unavailable for some reason.

    event PositionWasCreated(uint indexed positionIndex);
    event PositionWasCreatedV2(
        uint indexed positionIndex,
        uint timestamp,
        uint stakedTokenPrice,
        uint8 stakedTokenPriceDecimals
    );

    event PositionWasModified(uint indexed positionIndex);

    event PositionWasClosed(uint indexed positionIndex);
    event PositionWasClosedV2(
        uint indexed positionIndex,
        uint timestamp,
        uint stakedTokenPrice,
        uint8 stakedTokenPriceDecimals
    );

    event PositionWasLiquidatedV2(
        uint indexed positionIndex,
        uint timestamp,
        uint stakedTokenPrice,
        uint8 stakedTokenPriceDecimals
    );

    // -----------------------------------------------------------------------------------------------------------------
    // Storage.

    uint public constant FEE_MULTIPLIER = 1e8;
    uint public constant SLIPPAGE_MULTIPLIER = 1e8;
    uint public constant POSITION_PRICE_LIMITS_MULTIPLIER = 1e8;

    address public cakeAddress; // TODO: remove when deploy clean version

    // BUSD for BSC, USDT for POLYGON
    address public busdAddress; // TODO: rename to stableToken when deploy clean version

    address public minimaxStaking;

    uint public lastPositionIndex;

    // Use mapping instead of array for upgradeability of PositionInfo struct
    mapping(uint => PositionInfo) public positions;

    mapping(address => bool) public isLiquidator;

    ProxyCaller[] public proxyPool;

    // Fee threshold
    struct FeeThreshold {
        uint fee;
        uint stakedAmountThreshold;
    }

    FeeThreshold[] public depositFees;

    /// @custom:oz-renamed-from poolAdapters
    mapping(address => IPoolAdapter) public poolAdaptersDeprecated;

    mapping(IERC20Upgradeable => IPriceOracle) public priceOracles;

    // TODO: deprecated
    mapping(address => address) public tokenExchanges;

    // gelato
    IGelatoOps public gelatoOps;

    address payable public gelatoPayee;

    mapping(address => uint256) public gelatoLiquidateFee; // TODO: remove when deploy clean version
    uint256 public stakeGelatoFee; // TODO: rename to stakeGelatoFee
    address public gelatoFeeToken; // TODO: remove when deploy clean version

    // TODO: deprecated
    address public defaultExchange;

    // poolAdapters by bytecode hash
    mapping(uint256 => IPoolAdapter) public poolAdapters;

    IMarket public market;

    address public wrappedNative;

    address public oneInchRouter;

    // -----------------------------------------------------------------------------------------------------------------
    // Methods.

    function setGasTankThreshold(uint256 value) external onlyOwner {
        stakeGelatoFee = value;
    }

    function setGelatoOps(address _gelatoOps) external onlyOwner {
        gelatoOps = IGelatoOps(_gelatoOps);
    }

    function setLastPositionIndex(uint newLastPositionIndex) external onlyOwner {
        require(newLastPositionIndex >= lastPositionIndex, "last position index may only be increased");
        lastPositionIndex = newLastPositionIndex;
    }

    function getPoolAdapterKey(address pool) public view returns (uint256) {
        return uint256(keccak256(pool.code));
    }

    function getPoolAdapter(address pool) public view returns (IPoolAdapter) {
        uint256 key = getPoolAdapterKey(pool);
        return poolAdapters[key];
    }

    function getPoolAdapterSafe(address pool) public view returns (IPoolAdapter) {
        IPoolAdapter adapter = getPoolAdapter(pool);
        require(address(adapter) != address(0), "pool adapter not found");
        return adapter;
    }

    function getPoolAdapters(address[] calldata pools)
        public
        view
        returns (IPoolAdapter[] memory adapters, uint256[] memory keys)
    {
        adapters = new IPoolAdapter[](pools.length);
        keys = new uint256[](pools.length);
        for (uint i = 0; i < pools.length; i++) {
            uint256 key = getPoolAdapterKey(pools[i]);
            keys[i] = key;
            adapters[i] = poolAdapters[key];
        }
    }

    // Staking pool adapters
    function setPoolAdapters(address[] calldata pools, IPoolAdapter[] calldata adapters) external onlyOwner {
        require(pools.length == adapters.length, "pools and adapters parameters should have the same length");
        for (uint32 i = 0; i < pools.length; i++) {
            uint256 key = getPoolAdapterKey(pools[i]);
            poolAdapters[key] = adapters[i];
        }
    }

    // Price oracles
    function setPriceOracles(IERC20Upgradeable[] calldata tokens, IPriceOracle[] calldata oracles) external onlyOwner {
        require(tokens.length == oracles.length, "tokens and oracles parameters should have the same length");
        for (uint32 i = 0; i < tokens.length; i++) {
            priceOracles[tokens[i]] = oracles[i];
        }
    }

    function getPriceOracleSafe(IERC20Upgradeable token) public view returns (IPriceOracle) {
        IPriceOracle oracle = priceOracles[token];
        require(address(oracle) != address(0), "price oracle not found");
        return oracle;
    }

    function setMarket(IMarket _market) external onlyOwner {
        market = _market;
    }

    function setWrappedNative(address _native) external onlyOwner {
        wrappedNative = _native;
    }

    function setOneInchRouter(address _router) external onlyOwner {
        oneInchRouter = _router;
    }

    modifier onlyAutomator() {
        require(msg.sender == address(gelatoOps) || isLiquidator[address(msg.sender)], "onlyAutomator");
        _;
    }

    function initialize(
        address _minimaxStaking,
        address _busdAddress,
        address _gelatoOps
    ) external initializer {
        minimaxStaking = _minimaxStaking;
        busdAddress = _busdAddress;
        gelatoOps = IGelatoOps(_gelatoOps);

        __Ownable_init();
        __ReentrancyGuard_init();

        // staking pool
        depositFees.push(
            FeeThreshold({
                fee: 100000, // 0.1%
                stakedAmountThreshold: 1000 * 1e18 // all stakers <= 1000 MMX would have 0.1% fee for deposit
            })
        );

        depositFees.push(
            FeeThreshold({
                fee: 90000, // 0.09%
                stakedAmountThreshold: 5000 * 1e18
            })
        );

        depositFees.push(
            FeeThreshold({
                fee: 80000, // 0.08%
                stakedAmountThreshold: 10000 * 1e18
            })
        );

        depositFees.push(
            FeeThreshold({
                fee: 70000, // 0.07%
                stakedAmountThreshold: 50000 * 1e18
            })
        );
        depositFees.push(
            FeeThreshold({
                fee: 50000, // 0.05%
                stakedAmountThreshold: 10000000 * 1e18 // this level doesn't matter
            })
        );
    }

    receive() external payable {}

    function getSlippageMultiplier() public pure returns (uint) {
        return SLIPPAGE_MULTIPLIER;
    }

    function getUserFee(address user) public view returns (uint) {
        IMinimaxStaking staking = IMinimaxStaking(minimaxStaking);

        uint amountPool2 = staking.getUserAmount(2, user);
        uint amountPool3 = staking.getUserAmount(3, user);
        uint totalStakedAmount = amountPool2 + amountPool3;

        uint length = depositFees.length;

        for (uint bucketId = 0; bucketId < length; ++bucketId) {
            uint threshold = depositFees[bucketId].stakedAmountThreshold;
            if (totalStakedAmount <= threshold) {
                return depositFees[bucketId].fee;
            }
        }

        return depositFees[length - 1].fee;
    }

    function getUserFeeAmount(address user, uint amount) public view returns (uint) {
        uint userFeeShare = getUserFee(user);
        return (amount * userFeeShare) / FEE_MULTIPLIER;
    }

    function getPositionInfo(uint positionIndex) external view returns (PositionInfo memory) {
        return positions[positionIndex];
    }

    function fillProxyPool(uint amount) external onlyOwner {
        proxyPool.add(amount);
    }

    function cleanProxyPool() external onlyOwner {
        delete proxyPool;
    }

    function transferTo(
        address token,
        address to,
        uint amount
    ) external onlyOwner {
        address nativeToken = address(0);
        if (token == nativeToken) {
            (bool success, ) = to.call{value: amount}("");
            require(success, "transferTo: BNB transfer failed");
        } else {
            SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(token), to, amount);
        }
    }

    function setDepositFee(uint poolIdx, uint feeShare) external onlyOwner {
        require(poolIdx < depositFees.length, "wrong pool index");
        depositFees[poolIdx].fee = feeShare;
    }

    function setMinimaxStakingAddress(address stakingAddress) external onlyOwner {
        minimaxStaking = stakingAddress;
    }

    function getPositionBalances(uint[] calldata positionIndexes)
        public
        returns (PositionBalanceLib.PositionBalance[] memory)
    {
        return PositionBalanceLib.getMany(this, positions, positionIndexes);
    }

    function _stakeToken(
        PositionLib.StakeParams memory stakeParams,
        uint swapKind,
        bytes memory swapParams
    ) private returns (uint) {
        require(msg.value >= stakeGelatoFee, "gasTankThreshold");

        uint positionIndex = lastPositionIndex;
        lastPositionIndex += 1;

        PositionInfo memory position = PositionLib.stake(
            this,
            proxyPool.acquire(),
            positionIndex,
            stakeParams,
            swapKind,
            swapParams
        );

        if (address(gelatoOps) != address(0)) {
            position.gelatoLiquidateTaskId = _gelatoCreateTask(positionIndex);
            depositGasTank(position.callerAddress);
        }

        positions[positionIndex] = position;
        emitPositionWasCreated(positionIndex, position.stakedToken);
        return positionIndex;
    }

    function stake(
        uint inputAmount,
        IERC20Upgradeable inputToken,
        uint stakingAmountMin,
        IERC20Upgradeable stakingToken,
        address stakingPool,
        uint maxSlippage,
        uint stopLossPrice,
        uint takeProfitPrice,
        uint swapKind,
        bytes calldata swapParams
    ) public payable nonReentrant returns (uint) {
        return
            _stakeToken(
                PositionLib.StakeParams(
                    inputAmount,
                    inputToken,
                    stakingAmountMin,
                    stakingToken,
                    stakingPool,
                    maxSlippage,
                    stopLossPrice,
                    takeProfitPrice
                ),
                swapKind,
                swapParams
            );
    }

    function stakeToken(
        IERC20Upgradeable stakingToken,
        address stakingPool,
        uint tokenAmount,
        uint maxSlippage,
        uint stopLossPrice,
        uint takeProfitPrice
    ) public payable nonReentrant returns (uint) {
        return
            _stakeToken(
                PositionLib.StakeParams(
                    tokenAmount,
                    stakingToken,
                    tokenAmount,
                    stakingToken,
                    stakingPool,
                    maxSlippage,
                    stopLossPrice,
                    takeProfitPrice
                ),
                PositionLib.StakeSimpleKind,
                ""
            );
    }

    function swapStakeToken(
        IERC20Upgradeable inputToken,
        IERC20Upgradeable stakingToken,
        address stakingPool,
        uint inputTokenAmount,
        uint stakingTokenAmountMin,
        uint maxSlippage,
        uint stopLossPrice,
        uint takeProfitPrice,
        bytes memory hints
    ) public payable nonReentrant returns (uint) {
        return
            _stakeToken(
                PositionLib.StakeParams(
                    inputTokenAmount,
                    inputToken,
                    stakingTokenAmountMin,
                    stakingToken,
                    stakingPool,
                    maxSlippage,
                    stopLossPrice,
                    takeProfitPrice
                ),
                PositionLib.StakeSwapMarketKind,
                abi.encode(PositionLib.StakeSwapMarket(hints))
            );
    }

    function swapStakeTokenOneInch(
        IERC20Upgradeable inputToken,
        IERC20Upgradeable stakingToken,
        address stakingPool,
        uint inputTokenAmount,
        uint maxSlippage,
        uint stopLossPrice,
        uint takeProfitPrice,
        bytes memory oneInchCallData
    ) public payable nonReentrant returns (uint) {
        return
            _stakeToken(
                PositionLib.StakeParams(
                    inputTokenAmount,
                    inputToken,
                    0,
                    stakingToken,
                    stakingPool,
                    maxSlippage,
                    stopLossPrice,
                    takeProfitPrice
                ),
                PositionLib.StakeSwapOneInchKind,
                abi.encode(PositionLib.StakeSwapOneInch(oneInchCallData))
            );
    }

    function swapStakeTokenEstimate(
        address inputToken,
        address stakingToken,
        uint inputTokenAmount,
        bool tokenInPair,
        bool tokenOutPair
    ) public view returns (uint amountOut, bytes memory hints) {
        require(address(market) != address(0), "no market");
        return market.estimateOut(inputToken, stakingToken, inputTokenAmount);
    }

    function swapEstimate(
        address inputToken,
        address stakingToken,
        uint inputTokenAmount
    ) public view returns (uint amountOut, bytes memory hints) {
        require(address(market) != address(0), "no market");
        return market.estimateOut(inputToken, stakingToken, inputTokenAmount);
    }

    function deposit(uint positionIndex, uint amount) external nonReentrant {
        PositionInfo storage position = positions[positionIndex];

        PositionLib.deposit(this, position, positionIndex, amount);
        emit PositionWasModified(positionIndex);
    }

    function setLiquidator(address user, bool value) external onlyOwner {
        isLiquidator[user] = value;
    }

    function alterPositionParams(
        uint positionIndex,
        uint newAmount,
        uint newStopLossPrice,
        uint newTakeProfitPrice,
        uint newSlippage
    ) external nonReentrant {
        PositionInfo storage position = positions[positionIndex];
        bool shouldClose = PositionLib.alterPositionParams(
            this,
            position,
            positionIndex,
            newAmount,
            newStopLossPrice,
            newTakeProfitPrice,
            newSlippage
        );
        if (shouldClose) {
            closePosition(positionIndex, ClosePositionReason.WithdrawnByOwner);
        } else {
            emit PositionWasModified(positionIndex);
        }
    }

    function withdrawImpl(
        uint positionIndex,
        uint amount,
        bool withdrawAll
    ) private {
        PositionInfo storage position = positions[positionIndex];
        bool shouldClose = PositionLib.withdraw(this, position, positionIndex, amount, withdrawAll);
        if (shouldClose) {
            closePosition(positionIndex, ClosePositionReason.WithdrawnByOwner);
        } else {
            emit PositionWasModified(positionIndex);
        }
    }

    function withdrawAll(uint positionIndex) external nonReentrant {
        withdrawImpl(
            positionIndex,
            0, /* amount */
            true /* withdrawAll */
        );

        PositionInfo storage position = positions[positionIndex];

        position.callerAddress.transferAll(position.stakedToken, position.owner);
        position.callerAddress.transferAll(position.rewardToken, position.owner);
    }

    function withdraw(uint positionIndex, uint amount) external nonReentrant {
        withdrawImpl(
            positionIndex,
            amount, /* amount */
            false /* withdrawAll */
        );

        PositionInfo storage position = positions[positionIndex];

        position.callerAddress.transferAll(position.stakedToken, position.owner);
        position.callerAddress.transferAll(position.rewardToken, position.owner);
    }

    function estimateLpPartsForPosition(uint positionIndex) external returns (uint, uint) {
        PositionInfo storage position = positions[positionIndex];

        withdrawImpl(
            positionIndex,
            0, /* amount */
            true /* withdrawAll */
        );

        return PositionLib.estimateLpPartsForPosition(this, position);
    }

    function estimateWithdrawalAmountForPosition(uint positionIndex) external returns (uint) {
        PositionInfo storage position = positions[positionIndex];
        withdrawImpl(
            positionIndex,
            0, /* amount */
            true /* withdrawAll */
        );
        return position.stakedToken.balanceOf(address(position.callerAddress));
    }

    struct SlotInfo {
        uint withdrawnBalance;
        address lpToken;
        uint amount0;
        uint amount1;
        address token0;
        address token1;
        uint amountFirstSwapOut;
        uint amountSecondSwapOut;
    }

    function withdrawAllWithSwap(
        uint positionIndex,
        address withdrawalToken,
        bytes memory oneInchCallData
    ) external nonReentrant {
        PositionInfo storage position = positions[positionIndex];
        require(position.stakedToken == position.rewardToken, "withdraw all only for APY");
        withdrawImpl(
            positionIndex,
            0, /* amount */
            true /* withdrawAll */
        );

        uint withdrawnBalance = position.stakedToken.balanceOf(address(position.callerAddress));
        position.callerAddress.transferAll(position.stakedToken, address(this));

        uint amountOut = PositionLib.makeSwapOneInch(
            withdrawnBalance,
            address(position.stakedToken),
            oneInchRouter,
            PositionLib.StakeSwapOneInch(oneInchCallData)
        );

        SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(withdrawalToken), msg.sender, amountOut);
    }

    // TODO: add slippage for swaps
    function withdrawAllWithSwapLp(
        uint positionIndex,
        address withdrawalToken,
        bytes memory oneInchCallDataToken0,
        bytes memory oneInchCallDataToken1
    ) external nonReentrant {
        SlotInfo memory slot;
        PositionInfo storage position = positions[positionIndex];
        require(position.stakedToken == position.rewardToken, "withdraw all only for APY");
        withdrawImpl(
            positionIndex,
            0, /* amount */
            true /* withdrawAll */
        );

        slot.withdrawnBalance = position.stakedToken.balanceOf(address(position.callerAddress));
        position.callerAddress.transferAll(position.stakedToken, address(this));

        // TODO: when fee of contract is non-zero, then ensure fees from LP-tokens are not burned here
        slot.lpToken = address(position.stakedToken);
        IERC20Upgradeable(slot.lpToken).transfer(address(slot.lpToken), slot.withdrawnBalance);

        (slot.amount0, slot.amount1) = IPairToken(slot.lpToken).burn(address(this));

        slot.token0 = IPairToken(slot.lpToken).token0();
        slot.token1 = IPairToken(slot.lpToken).token1();

        slot.amountFirstSwapOut = PositionLib.makeSwapOneInch(
            slot.amount0,
            slot.token0,
            oneInchRouter,
            PositionLib.StakeSwapOneInch(oneInchCallDataToken0)
        );

        slot.amountSecondSwapOut = PositionLib.makeSwapOneInch(
            slot.amount1,
            slot.token1,
            oneInchRouter,
            PositionLib.StakeSwapOneInch(oneInchCallDataToken1)
        );

        SafeERC20Upgradeable.safeTransfer(
            IERC20Upgradeable(withdrawalToken),
            msg.sender,
            slot.amountFirstSwapOut + slot.amountSecondSwapOut
        );
    }

    // Always emits `PositionWasClosed`
    function liquidateByIndexImpl(
        uint positionIndex,
        uint amountOutMin,
        bytes memory marketHints
    ) private {
        PositionInfo storage position = positions[positionIndex];
        require(isOpen(position), "isOpen");

        position.callerAddress.withdrawAll(
            getPoolAdapterSafe(position.poolAddress),
            position.poolAddress,
            abi.encode(position.stakedToken) // pass stakedToken for aave pools
        );

        uint stakedAmount = IERC20Upgradeable(position.stakedToken).balanceOf(address(position.callerAddress));

        position.callerAddress.approve(position.stakedToken, address(market), stakedAmount);
        position.callerAddress.swap(
            market, // adapter
            address(position.stakedToken), // tokenIn
            busdAddress, // tokenOut
            stakedAmount, // amountIn
            amountOutMin, // amountOutMin
            positions[positionIndex].owner, // to
            marketHints // hints
        );

        // Firstly, 'transfer', then 'dumpRewards': order is important here when (rewardToken == CAKE)
        position.callerAddress.transferAll(position.rewardToken, position.owner);

        closePosition(positionIndex, ClosePositionReason.LiquidatedByAutomation);
    }

    function closePosition(uint positionIndex, ClosePositionReason reason) private {
        PositionInfo storage position = positions[positionIndex];

        position.closed = true;

        if (isModernProxy(position.callerAddress)) {
            withdrawGasTank(position.callerAddress, position.owner);
            proxyPool.release(position.callerAddress);
        }

        _gelatoCancelTask(position.gelatoLiquidateTaskId);

        if (reason == ClosePositionReason.WithdrawnByOwner) {
            emitPositionWasClosed(positionIndex, position.stakedToken);
        }
        if (reason == ClosePositionReason.LiquidatedByAutomation) {
            emitPositionWasLiquidated(positionIndex, position.stakedToken);
        }
    }

    function depositGasTank(ProxyCaller proxy) private {
        address(proxy).call{value: msg.value}("");
    }

    function withdrawGasTank(ProxyCaller proxy, address owner) private {
        proxy.transferNativeAll(owner);
    }

    function isModernProxy(ProxyCaller proxy) public returns (bool) {
        return address(proxy).code.length == 945;
    }

    // -----------------------------------------------------------------------------------------------------------------
    // Position events.

    function emitPositionWasCreated(uint positionIndex, IERC20Upgradeable positionStakedToken) private {
        // TODO(TmLev): Remove once `PositionWasCreatedV2` is stable.
        emit PositionWasCreated(positionIndex);

        (uint price, uint8 priceDecimals) = PositionLib.estimatePositionStakedTokenPrice(this, positionStakedToken);
        emit PositionWasCreatedV2(positionIndex, block.timestamp, price, priceDecimals);
    }

    function emitPositionWasClosed(uint positionIndex, IERC20Upgradeable positionStakedToken) private {
        // TODO(TmLev): Remove once `PositionWasClosedV2` is stable.
        emit PositionWasClosed(positionIndex);

        (uint price, uint8 priceDecimals) = PositionLib.estimatePositionStakedTokenPrice(this, positionStakedToken);
        emit PositionWasClosedV2(positionIndex, block.timestamp, price, priceDecimals);
    }

    function emitPositionWasLiquidated(uint positionIndex, IERC20Upgradeable positionStakedToken) private {
        // TODO(TmLev): Remove once `PositionWasLiquidatedV2` is stable.
        emit PositionWasClosed(positionIndex);

        (uint price, uint8 priceDecimals) = PositionLib.estimatePositionStakedTokenPrice(this, positionStakedToken);
        emit PositionWasLiquidatedV2(positionIndex, block.timestamp, price, priceDecimals);
    }

    // -----------------------------------------------------------------------------------------------------------------
    // Gelato

    struct AutomationParams {
        uint256 positionIndex;
        uint256 minAmountOut;
        bytes marketHints;
    }

    function isOpen(PositionInfo storage position) private view returns (bool) {
        return !position.closed && position.owner != address(0);
    }

    function automationResolve(uint positionIndex) public returns (bool canExec, bytes memory execPayload) {
        PositionInfo storage position = positions[positionIndex];
        uint256 amountOut;
        bytes memory hints;
        (canExec, amountOut, hints) = PositionLib.isOutsideRange(this, position);
        if (canExec) {
            uint minAmountOut = amountOut - (amountOut * position.maxSlippage) / SLIPPAGE_MULTIPLIER;
            AutomationParams memory params = AutomationParams(positionIndex, minAmountOut, hints);
            execPayload = abi.encodeWithSelector(this.automationExec.selector, abi.encode(params));
        }
    }

    function automationExec(bytes calldata raw) public onlyAutomator {
        AutomationParams memory params = abi.decode(raw, (AutomationParams));
        gelatoPayFee(params.positionIndex);
        liquidateByIndexImpl(params.positionIndex, params.minAmountOut, params.marketHints);
    }

    function gelatoPayFee(uint positionIndex) private {
        (uint feeAmount, address feeToken) = gelatoOps.getFeeDetails();
        if (feeAmount == 0) {
            return;
        }

        require(feeToken == GelatoNativeToken);

        address feeDestination = gelatoOps.gelato();
        ProxyCaller proxy = positions[positionIndex].callerAddress;
        proxy.transferNative(feeDestination, feeAmount);
    }

    function _gelatoCreateTask(uint positionIndex) private returns (bytes32) {
        return
            gelatoOps.createTaskNoPrepayment(
                address(this), /* execAddress */
                this.automationExec.selector, /* execSelector */
                address(this), /* resolverAddress */
                abi.encodeWithSelector(this.automationResolve.selector, positionIndex), /* resolverData */
                GelatoNativeToken
            );
    }

    function _gelatoCancelTask(bytes32 gelatoTaskId) private {
        if (address(gelatoOps) != address(0) && gelatoTaskId != "") {
            gelatoOps.cancelTask(gelatoTaskId);
        }
    }
}

File 2 of 36 : OwnableUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * 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 OwnableUpgradeable is Initializable, ContextUpgradeable {
    address private _owner;

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

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

    function __Ownable_init_unchained() internal onlyInitializing {
        _transferOwnership(_msgSender());
    }

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

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

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

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

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

    /**
     * @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 3 of 36 : ReentrancyGuardUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)

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

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

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

    uint256 private _status;

    function __ReentrancyGuard_init() internal onlyInitializing {
        __ReentrancyGuard_init_unchained();
    }

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        _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() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

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

        _;

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

    /**
     * @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 4 of 36 : IERC20Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20Upgradeable {
    /**
     * @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);

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

File 5 of 36 : SafeERC20Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20Upgradeable.sol";
import "../../../utils/AddressUpgradeable.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 SafeERC20Upgradeable {
    using AddressUpgradeable for address;

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

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

    /**
     * @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(IERC20Upgradeable 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 6 of 36 : Math.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

library Math {
    function max(int x, int y) internal pure returns (int z) {
        z = x > y ? x : y;
    }

    function min(uint x, uint y) internal pure returns (uint z) {
        z = x < y ? x : y;
    }
}

File 7 of 36 : IMinimaxStaking.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IMinimaxStaking {
    function getUserAmount(uint _pid, address _user) external view returns (uint);
}

File 8 of 36 : MinimaxStaking.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "./interfaces/IMinimaxToken.sol";

contract MinimaxStaking is OwnableUpgradeable, ReentrancyGuardUpgradeable {
    uint public constant SHARE_MULTIPLIER = 1e12;

    using SafeERC20Upgradeable for IERC20Upgradeable;

    struct UserPoolInfo {
        uint amount; // How many LP tokens the user has provided.
        uint rewardDebt; // Reward debt. See explanation below.
        uint timeDeposited; // timestamp when minimax was deposited
    }

    // Info of each pool.
    struct PoolInfo {
        IERC20Upgradeable token; // Address of LP token contract.
        uint totalSupply;
        uint allocPoint; // How many allocation points assigned to this pool. MINIMAXs to distribute per block.
        uint timeLocked; // How long stake must be locked for
        uint lastRewardBlock; // Last block number that MINIMAXs distribution occurs.
        uint accMinimaxPerShare; // Accumulated MINIMAXs per share, times SHARE_MULTIPLIER. See below.
    }

    // Info of each pool.
    PoolInfo[] public poolInfo;
    // Info of each user that stakes LP tokens.
    mapping(uint => mapping(address => UserPoolInfo)) public userPoolInfo;

    address public minimaxToken;
    uint public minimaxPerBlock;
    uint public startBlock;

    // Total allocation points. Must be the sum of all allocation points in all pools.
    uint public totalAllocPoint;

    event Deposit(address indexed user, uint indexed pid, uint amount);
    event Withdraw(address indexed user, uint indexed pid, uint amount);
    event EmergencyWithdraw(address indexed user, uint indexed pid, uint256 amount);
    event PoolAdded(uint allocPoint, uint timeLocked);
    event SetMinimaxPerBlock(uint minimaxPerBlock);
    event SetPool(uint pid, uint allocPoint);

    function initialize(
        address _minimaxToken,
        uint _minimaxPerBlock,
        uint _startBlock
    ) external initializer {
        __Ownable_init();
        __ReentrancyGuard_init();

        minimaxToken = _minimaxToken;
        minimaxPerBlock = _minimaxPerBlock;
        startBlock = _startBlock;

        // staking pool
        poolInfo.push(
            PoolInfo({
                token: IERC20Upgradeable(minimaxToken),
                totalSupply: 0,
                allocPoint: 800,
                timeLocked: 0 days,
                lastRewardBlock: startBlock,
                accMinimaxPerShare: 0
            })
        );
        poolInfo.push(
            PoolInfo({
                token: IERC20Upgradeable(minimaxToken),
                totalSupply: 0,
                allocPoint: 1400,
                timeLocked: 7 days,
                lastRewardBlock: startBlock,
                accMinimaxPerShare: 0
            })
        );
        poolInfo.push(
            PoolInfo({
                token: IERC20Upgradeable(minimaxToken),
                totalSupply: 0,
                allocPoint: 2000,
                timeLocked: 30 days,
                lastRewardBlock: startBlock,
                accMinimaxPerShare: 0
            })
        );
        poolInfo.push(
            PoolInfo({
                token: IERC20Upgradeable(minimaxToken),
                totalSupply: 0,
                allocPoint: 3000,
                timeLocked: 90 days,
                lastRewardBlock: startBlock,
                accMinimaxPerShare: 0
            })
        );
        totalAllocPoint = 7200;
    }

    /* ========== External Functions ========== */

    function getUserAmount(uint _pid, address _user) external view returns (uint) {
        UserPoolInfo storage user = userPoolInfo[_pid][_user];
        return user.amount;
    }

    // View function to see pending MINIMAXs from Pools on frontend.
    function pendingMinimax(uint _pid, address _user) external view returns (uint) {
        PoolInfo memory pool = poolInfo[_pid];
        UserPoolInfo memory user = userPoolInfo[_pid][_user];

        // Minting reward
        uint accMinimaxPerShare = pool.accMinimaxPerShare;
        if (block.number > pool.lastRewardBlock && pool.totalSupply != 0) {
            uint multiplier = block.number - pool.lastRewardBlock;
            uint minimaxReward = (multiplier * minimaxPerBlock * pool.allocPoint) / totalAllocPoint;
            accMinimaxPerShare = accMinimaxPerShare + (minimaxReward * SHARE_MULTIPLIER) / pool.totalSupply;
        }
        uint pendingUserMinimax = (user.amount * accMinimaxPerShare) / SHARE_MULTIPLIER - user.rewardDebt;
        return pendingUserMinimax;
    }

    // Update reward variables of the given pool to be up-to-date.
    function updatePool(uint _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        if (block.number <= pool.lastRewardBlock) {
            return;
        }
        if (pool.totalSupply == 0) {
            pool.lastRewardBlock = block.number;
            return;
        }
        // Minting reward
        uint multiplier = block.number - pool.lastRewardBlock;
        uint minimaxReward = (multiplier * minimaxPerBlock * pool.allocPoint) / totalAllocPoint;
        pool.accMinimaxPerShare = pool.accMinimaxPerShare + (minimaxReward * SHARE_MULTIPLIER) / pool.totalSupply;
        pool.lastRewardBlock = block.number;
    }

    // Deposit lp tokens for MINIMAX allocation.
    function deposit(uint _pid, uint _amount) external nonReentrant {
        require(_amount > 0, "deposit: amount is 0");
        PoolInfo storage pool = poolInfo[_pid];
        UserPoolInfo storage user = userPoolInfo[_pid][msg.sender];
        updatePool(_pid);
        if (user.amount > 0) {
            _claimPendingMintReward(_pid, msg.sender);
        }
        if (_amount > 0) {
            uint before = pool.token.balanceOf(address(this));
            pool.token.safeTransferFrom(address(msg.sender), address(this), _amount);
            uint post = pool.token.balanceOf(address(this));
            uint finalAmount = post - before;
            user.amount = user.amount + finalAmount;
            user.timeDeposited = block.timestamp;
            pool.totalSupply = pool.totalSupply + finalAmount;
            emit Deposit(msg.sender, _pid, finalAmount);
        }
        user.rewardDebt = (user.amount * pool.accMinimaxPerShare) / SHARE_MULTIPLIER;
    }

    // Withdraw LP tokens
    function withdraw(uint _pid, uint _amount) external nonReentrant {
        PoolInfo storage pool = poolInfo[_pid];
        UserPoolInfo storage user = userPoolInfo[_pid][msg.sender];
        require(user.amount >= _amount, "withdraw: requested amount is high");
        require(block.timestamp >= user.timeDeposited + pool.timeLocked, "can't withdraw before end of lock-up");

        updatePool(_pid);
        _claimPendingMintReward(_pid, msg.sender);

        if (_amount > 0) {
            user.amount = user.amount - _amount;
            pool.totalSupply = pool.totalSupply - _amount;
            pool.token.safeTransfer(address(msg.sender), _amount);
        }
        user.rewardDebt = (user.amount * pool.accMinimaxPerShare) / SHARE_MULTIPLIER;
        emit Withdraw(msg.sender, _pid, _amount);
    }

    // Withdraw without caring about rewards. EMERGENCY ONLY.
    function emergencyWithdraw(uint _pid) external nonReentrant {
        PoolInfo storage pool = poolInfo[_pid];
        UserPoolInfo storage user = userPoolInfo[_pid][msg.sender];
        require(block.timestamp >= user.timeDeposited + pool.timeLocked, "time locked");

        uint amount = user.amount;

        pool.totalSupply = pool.totalSupply - user.amount;
        user.amount = 0;
        user.rewardDebt = 0;
        pool.token.safeTransfer(address(msg.sender), amount);
        emit EmergencyWithdraw(msg.sender, _pid, amount);
    }

    // Update reward variables for all pools. Be careful of gas spending!
    function massUpdatePools() public {
        uint length = poolInfo.length;
        for (uint pid = 0; pid < length; ++pid) {
            updatePool(pid);
        }
    }

    // Add a new lp to the pool. Can only be called by the owner.
    // XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do.
    function add(
        uint _allocPoint,
        address _poolToken,
        uint _timeLocked
    ) external onlyOwner {
        massUpdatePools();
        uint lastRewardBlock = block.number > startBlock ? block.number : startBlock;
        totalAllocPoint = totalAllocPoint + _allocPoint;
        poolInfo.push(
            PoolInfo({
                token: IERC20Upgradeable(_poolToken),
                totalSupply: 0,
                allocPoint: _allocPoint,
                timeLocked: _timeLocked,
                lastRewardBlock: lastRewardBlock,
                accMinimaxPerShare: 0
            })
        );
        emit PoolAdded(_allocPoint, _timeLocked);
    }

    // Update the given pool's MINIMAX allocation point. Can only be called by the owner.
    function set(uint _pid, uint _allocPoint) external onlyOwner {
        massUpdatePools();
        uint prevAllocPoint = poolInfo[_pid].allocPoint;
        poolInfo[_pid].allocPoint = _allocPoint;
        if (prevAllocPoint != _allocPoint) {
            totalAllocPoint = totalAllocPoint - prevAllocPoint + _allocPoint;
        }
        emit SetPool(_pid, _allocPoint);
    }

    function setMinimaxPerBlock(uint _minimaxPerBlock) external onlyOwner {
        minimaxPerBlock = _minimaxPerBlock;
        emit SetMinimaxPerBlock(_minimaxPerBlock);
    }

    function _claimPendingMintReward(uint _pid, address _user) private {
        PoolInfo storage pool = poolInfo[_pid];
        UserPoolInfo storage user = userPoolInfo[_pid][_user];

        uint pendingMintReward = (user.amount * pool.accMinimaxPerShare) / SHARE_MULTIPLIER - user.rewardDebt;
        if (pendingMintReward > 0) {
            IMinimaxToken(minimaxToken).mint(_user, pendingMintReward);
        }
    }
}

File 9 of 36 : IPoolAdapter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IPoolAdapter {
    function stakingBalance(address pool, bytes memory) external returns (uint256);

    function rewardBalance(address pool, bytes memory) external returns (uint256);

    function deposit(
        address pool,
        uint256 amount,
        bytes memory args
    ) external;

    function withdraw(
        address pool,
        uint256 amount,
        bytes memory args
    ) external;

    function withdrawAll(address pool, bytes memory args) external;

    function stakedToken(address pool, bytes memory args) external returns (address);

    function rewardToken(address pool, bytes memory args) external returns (address);
}

File 10 of 36 : IERC20Decimals.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";

interface IERC20Decimals is IERC20Upgradeable {
    function decimals() external view returns (uint8);
}

File 11 of 36 : IPriceOracle.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IPriceOracle {
    function decimals() external view returns (uint8);

    function latestAnswer() external view returns (int256);
}

File 12 of 36 : IPancakeRouter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IPancakeRouter {
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function getAmountIn(
        uint amountOut,
        uint reserveIn,
        uint reserveOut
    ) external pure returns (uint amountIn);

    function getAmountOut(
        uint amountIn,
        uint reserveIn,
        uint reserveOut
    ) external pure returns (uint amountOut);

    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);

    function factory() external view returns (address);
}

File 13 of 36 : ISmartChef.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";

interface ISmartChef {
    function stakedToken() external view returns (IERC20Upgradeable);

    function rewardToken() external view returns (IERC20Upgradeable);

    // Deposit '_amount' of stakedToken tokens
    function deposit(uint256 _amount) external;

    // Withdraw '_amount' of stakedToken and all pending rewardToken tokens
    function withdraw(uint256 _amount) external;
}

File 14 of 36 : IGelatoOps.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

address constant GelatoNativeToken = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

interface IGelatoOps {
    function createTaskNoPrepayment(
        address execAddress,
        bytes4 execSelector,
        address resolverAddress,
        bytes calldata resolverData,
        address feeToken
    ) external returns (bytes32 task);

    function cancelTask(bytes32 taskId) external;

    function getFeeDetails() external view returns (uint256, address);

    function gelato() external view returns (address payable);
}

File 15 of 36 : IWrapped.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";

interface IWrapped is IERC20Upgradeable {
    function deposit() external payable;

    function withdraw(uint wad) external;
}

File 16 of 36 : ProxyCaller.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// ProxyCaller contract is deployed frequently, and in order to reduce gas
// it has to be as small as possible
contract ProxyCaller {
    address immutable _owner;

    constructor() {
        _owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == _owner);
        _;
    }

    function exec(
        bool delegate,
        address target,
        bytes calldata data
    ) external onlyOwner returns (bool success, bytes memory) {
        if (delegate) {
            return target.delegatecall(data);
        }
        return target.call(data);
    }

    function transfer(address target, uint256 amount) external onlyOwner returns (bool success, bytes memory) {
        return target.call{value: amount}("");
    }

    receive() external payable {}
}

File 17 of 36 : ProxyCallerApi.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "./ProxyCaller.sol";
import "./market/IMarket.sol";
import "./pool/IPoolAdapter.sol";

library ProxyCallerApi {
    function propagateError(
        bool success,
        bytes memory data,
        string memory errorMessage
    ) public {
        // Forward error message from call/delegatecall
        if (!success) {
            if (data.length == 0) revert(errorMessage);
            assembly {
                revert(add(32, data), mload(data))
            }
        }
    }

    function deposit(
        ProxyCaller proxy,
        IPoolAdapter adapter,
        address pool,
        uint256 amount,
        bytes memory args // used for passing stakedToken for Aave pools
    ) external {
        (bool success, bytes memory data) = proxy.exec(
            true, /* delegate */
            address(adapter), /* target */
            abi.encodeWithSignature("deposit(address,uint256,bytes)", pool, amount, args) /* data */
        );

        propagateError(success, data, "deposit failed");
    }

    function stakingBalance(
        ProxyCaller proxy,
        IPoolAdapter adapter,
        address pool,
        bytes memory args // used for passing stakedToken for Aave pools
    ) external returns (uint256) {
        (bool success, bytes memory data) = proxy.exec(
            true, /* delegate */
            address(adapter), /* target */
            abi.encodeWithSignature("stakingBalance(address,bytes)", pool, args) /* data */
        );

        propagateError(success, data, "staking balance failed");

        return abi.decode(data, (uint256));
    }

    function rewardBalance(
        ProxyCaller proxy,
        IPoolAdapter adapter,
        address pool,
        bytes memory args
    ) external returns (uint256) {
        (bool success, bytes memory data) = proxy.exec(
            true, /* delegate */
            address(adapter), /* target */
            abi.encodeWithSignature("rewardBalance(address,bytes)", pool, args) /* data */
        );

        propagateError(success, data, "reward balance failed");

        return abi.decode(data, (uint256));
    }

    function withdraw(
        ProxyCaller proxy,
        IPoolAdapter adapter,
        address pool,
        uint256 amount,
        bytes memory args // used for passing stakedToken for Aave pools
    ) external {
        (bool success, bytes memory data) = proxy.exec(
            true, /* delegate */
            address(adapter), /* target */
            abi.encodeWithSignature("withdraw(address,uint256,bytes)", pool, amount, args) /* data */
        );

        propagateError(success, data, "withdraw failed");
    }

    function withdrawAll(
        ProxyCaller proxy,
        IPoolAdapter adapter,
        address pool,
        bytes memory args // used for passing stakedToken for Aave pools
    ) external {
        (bool success, bytes memory data) = proxy.exec(
            true, /* delegate */
            address(adapter), /* target */
            abi.encodeWithSignature("withdrawAll(address,bytes)", pool, args) /* data */
        );

        propagateError(success, data, "withdraw all failed");
    }

    function transfer(
        ProxyCaller proxy,
        IERC20Upgradeable token,
        address beneficiary,
        uint256 amount
    ) public {
        (bool success, bytes memory data) = proxy.exec(
            false, /* delegate */
            address(token), /* target */
            abi.encodeWithSignature("transfer(address,uint256)", beneficiary, amount) /* data */
        );
        propagateError(success, data, "transfer failed");
    }

    function transferAll(
        ProxyCaller proxy,
        IERC20Upgradeable token,
        address beneficiary
    ) external returns (uint256) {
        uint256 amount = token.balanceOf(address(proxy));
        if (amount > 0) {
            transfer(proxy, token, beneficiary, amount);
        }
        return amount;
    }

    function transferNative(
        ProxyCaller proxy,
        address beneficiary,
        uint256 amount
    ) external {
        (bool success, bytes memory data) = proxy.transfer(
            address(beneficiary), /* target */
            amount /* amount */
        );
        propagateError(success, data, "transfer native failed");
    }

    function transferNativeAll(ProxyCaller proxy, address beneficiary) external {
        (bool success, bytes memory data) = proxy.transfer(
            address(beneficiary), /* target */
            address(proxy).balance /* amount */
        );
        propagateError(success, data, "transfer native all failed");
    }

    function approve(
        ProxyCaller proxy,
        IERC20Upgradeable token,
        address beneficiary,
        uint amount
    ) external {
        (bool success, bytes memory data) = proxy.exec(
            false, /* delegate */
            address(token), /* target */
            abi.encodeWithSignature("approve(address,uint256)", beneficiary, amount) /* data */
        );
        require(success && (data.length == 0 || abi.decode(data, (bool))), "approve failed");
    }

    function swap(
        ProxyCaller proxy,
        IMarket market,
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 amountOutMin,
        address destination,
        bytes memory hints
    ) external returns (uint256) {
        (bool success, bytes memory data) = proxy.exec(
            false, /* delegate */
            address(market), /* target */
            abi.encodeWithSelector(market.swap.selector, tokenIn, tokenOut, amountIn, amountOutMin, destination, hints) /* data */
        );
        propagateError(success, data, "swap exact tokens failed");
        return abi.decode(data, (uint256));
    }
}

File 18 of 36 : ProxyPool.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./ProxyCaller.sol";

library ProxyPool {
    function release(ProxyCaller[] storage self, ProxyCaller proxy) internal {
        self.push(proxy);
    }

    function acquire(ProxyCaller[] storage self) internal returns (ProxyCaller) {
        if (self.length == 0) {
            return new ProxyCaller();
        }
        ProxyCaller proxy = self[self.length - 1];
        self.pop();
        return proxy;
    }

    function add(ProxyCaller[] storage self, uint amount) internal {
        for (uint i = 0; i < amount; i++) {
            self.push(new ProxyCaller());
        }
    }
}

File 19 of 36 : Market.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

import "./Hints.sol";
import "./v2/PancakeLpMarket.sol";
import "./v2/PairTokenDetector.sol";
import "./IMarket.sol";

contract Market is IMarket, OwnableUpgradeable {
    PancakeLpMarket public pancakeLpMarket;
    SingleMarket public singleMarket;
    PairTokenDetector public pairTokenDetector;

    constructor() initializer {
        __Ownable_init();
    }

    function setPancakeLpMarket(PancakeLpMarket _pancakeLpMarket) external onlyOwner {
        pancakeLpMarket = _pancakeLpMarket;
    }

    function setSingleMarket(SingleMarket _singleMarket) external onlyOwner {
        singleMarket = _singleMarket;
    }

    function setPairTokenDetector(PairTokenDetector _pairTokenDetector) external onlyOwner {
        pairTokenDetector = _pairTokenDetector;
    }

    function swap(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 amountOutMin,
        address destination,
        bytes memory hints
    ) external returns (uint256) {
        IERC20Upgradeable(tokenIn).transferFrom(address(msg.sender), address(this), amountIn);

        if (Hints.getIsPair(hints, tokenIn) || Hints.getIsPair(hints, tokenOut)) {
            IERC20Upgradeable(tokenIn).approve(address(pancakeLpMarket), amountIn);
            return pancakeLpMarket.swap(tokenIn, tokenOut, amountIn, amountOutMin, destination, hints);
        }

        IERC20Upgradeable(tokenIn).approve(address(singleMarket), amountIn);
        return singleMarket.swap(tokenIn, tokenOut, amountIn, amountOutMin, destination, hints);
    }

    function estimateBurn(address lpToken, uint amountIn) external view returns (uint, uint) {
        return pancakeLpMarket.estimateBurn(lpToken, amountIn);
    }

    function estimateOut(
        address tokenIn,
        address tokenOut,
        uint256 amountIn
    ) external view returns (uint256 amountOut, bytes memory hints) {
        bool tokenInPair = pairTokenDetector.isPairToken{gas: 50000}(tokenIn);
        bool tokenOutPair = pairTokenDetector.isPairToken{gas: 50000}(tokenOut);

        if (tokenInPair || tokenOutPair) {
            (uint256 amountOut, bytes memory hints) = pancakeLpMarket.estimateOut(
                tokenIn,
                tokenOut,
                amountIn,
                tokenInPair,
                tokenOutPair
            );

            if (tokenInPair) {
                hints = Hints.merge2(hints, Hints.setIsPair(tokenIn));
            }

            if (tokenOutPair) {
                hints = Hints.merge2(hints, Hints.setIsPair(tokenOut));
            }

            return (amountOut, hints);
        }

        return singleMarket.estimateOut(tokenIn, tokenOut, amountIn);
    }
}

File 20 of 36 : PositionInfo.sol
import "./ProxyCaller.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";

struct PositionInfo {
    uint stakedAmount; // wei
    uint feeAmount; // FEE_MULTIPLIER
    uint stopLossPrice; // POSITION_PRICE_LIMITS_MULTIPLIER
    uint maxSlippage; // SLIPPAGE_MULTIPLIER
    address poolAddress;
    address owner;
    ProxyCaller callerAddress;
    bool closed;
    uint takeProfitPrice; // POSITION_PRICE_LIMITS_MULTIPLIER
    IERC20Upgradeable stakedToken;
    IERC20Upgradeable rewardToken;
    bytes32 gelatoLiquidateTaskId; // TODO: rename to gelatoTaskId when deploy clean version
}

File 21 of 36 : PositionExchangeLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "./PositionInfo.sol";
import "./interfaces/IPriceOracle.sol";
import "./interfaces/IERC20Decimals.sol";

library PositionExchangeLib {
    uint public constant POSITION_PRICE_LIMITS_MULTIPLIER = 1e8;
    uint public constant SLIPPAGE_MULTIPLIER = 1e8;

    function isPriceOutsideRange(
        PositionInfo memory position,
        uint priceNumerator,
        uint priceDenominator,
        uint8 numeratorDecimals,
        uint8 denominatorDecimals
    ) public view returns (bool) {
        if (denominatorDecimals > numeratorDecimals) {
            priceNumerator *= 10**(denominatorDecimals - numeratorDecimals);
        } else if (numeratorDecimals > denominatorDecimals) {
            priceDenominator *= 10**(numeratorDecimals - denominatorDecimals);
        }

        // priceFloat = priceNumerator / priceDenominator
        // stopLossPriceFloat = position.stopLossPrice / POSITION_PRICE_LIMITS_MULTIPLIER
        // if
        // priceNumerator / priceDenominator > position.stopLossPrice / POSITION_PRICE_LIMITS_MULTIPLIER
        // then
        // priceNumerator * POSITION_PRICE_LIMITS_MULTIPLIER > position.stopLossPrice * priceDenominator

        if (
            position.stopLossPrice != 0 &&
            priceNumerator * POSITION_PRICE_LIMITS_MULTIPLIER < position.stopLossPrice * priceDenominator
        ) return true;

        if (
            position.takeProfitPrice != 0 &&
            priceNumerator * POSITION_PRICE_LIMITS_MULTIPLIER > position.takeProfitPrice * priceDenominator
        ) return true;

        return false;
    }
}

File 22 of 36 : PositionBalanceLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./ProxyCallerApi.sol";
import "./PositionInfo.sol";
import "./pool/IPoolAdapter.sol";
import "./interfaces/IMinimaxMain.sol";

library PositionBalanceLib {
    using ProxyCallerApi for ProxyCaller;

    struct PositionBalance {
        uint total;
        uint reward;
        uint gasTank;
    }

    function getMany(
        IMinimaxMain main,
        mapping(uint => PositionInfo) storage positions,
        uint[] calldata positionIndexes
    ) public returns (PositionBalance[] memory) {
        PositionBalance[] memory balances = new PositionBalance[](positionIndexes.length);
        for (uint i = 0; i < positionIndexes.length; ++i) {
            balances[i] = get(main, positions[positionIndexes[i]]);
        }
        return balances;
    }

    function get(IMinimaxMain main, PositionInfo storage position) public returns (PositionBalance memory) {
        if (position.closed) {
            return PositionBalance({total: 0, reward: 0, gasTank: 0});
        }

        IPoolAdapter adapter = main.poolAdapters(uint256(keccak256(position.poolAddress.code)));

        uint gasTank = address(position.callerAddress).balance;
        uint stakingBalance = position.callerAddress.stakingBalance(
            adapter,
            position.poolAddress,
            abi.encode(position.stakedToken)
        );
        uint rewardBalance = position.callerAddress.rewardBalance(adapter, position.poolAddress, "");

        if (position.stakedToken != position.rewardToken) {
            return PositionBalance({total: position.stakedAmount, reward: rewardBalance, gasTank: gasTank});
        }

        uint totalBalance = rewardBalance + stakingBalance;

        if (totalBalance < position.stakedAmount) {
            return PositionBalance({total: totalBalance, reward: 0, gasTank: gasTank});
        }

        return PositionBalance({total: totalBalance, reward: totalBalance - position.stakedAmount, gasTank: gasTank});
    }
}

File 23 of 36 : PositionLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";

import "./PositionInfo.sol";
import "./pool/IPoolAdapter.sol";
import "./ProxyCaller.sol";
import "./ProxyCallerApi.sol";
import "./interfaces/IPriceOracle.sol";
import "./interfaces/IMinimaxMain.sol";
import "./interfaces/IERC20Decimals.sol";
import "./market/IMarket.sol";
import "./PositionBalanceLib.sol";
import "./PositionExchangeLib.sol";

library PositionLib {
    using SafeERC20Upgradeable for IERC20Upgradeable;
    using ProxyCallerApi for ProxyCaller;

    uint public constant StakeSimpleKind = 1;

    uint public constant StakeSwapMarketKind = 2;

    struct StakeSwapMarket {
        bytes hints;
    }

    uint public constant StakeSwapOneInchKind = 3;

    struct StakeSwapOneInch {
        bytes oneInchCallData;
    }

    struct StakeParams {
        uint inputAmount;
        IERC20Upgradeable inputToken;
        uint stakingAmountMin;
        IERC20Upgradeable stakingToken;
        address stakingPool;
        uint maxSlippage;
        uint stopLossPrice;
        uint takeProfitPrice;
    }

    function stake(
        IMinimaxMain main,
        ProxyCaller proxy,
        uint positionIndex,
        StakeParams memory genericParams,
        uint swapKind,
        bytes memory swapParams
    ) external returns (PositionInfo memory) {
        uint tokenAmount;
        if (swapKind == StakeSimpleKind) {
            tokenAmount = stakeSimple(genericParams);
        } else if (swapKind == StakeSwapMarketKind) {
            StakeSwapMarket memory decoded = abi.decode(swapParams, (StakeSwapMarket));
            tokenAmount = stakeSwapMarket(main, genericParams, decoded);
        } else if (swapKind == StakeSwapOneInchKind) {
            StakeSwapOneInch memory decoded = abi.decode(swapParams, (StakeSwapOneInch));
            tokenAmount = stakeSwapOneInch(main, genericParams, decoded);
        } else {
            revert("invalid stake kind param");
        }
        return createPosition(main, genericParams, tokenAmount, positionIndex, proxy);
    }

    function stakeSimple(StakeParams memory params) private returns (uint) {
        params.stakingToken.safeTransferFrom(address(msg.sender), address(this), params.inputAmount);
        return params.inputAmount;
    }

    function stakeSwapMarket(
        IMinimaxMain main,
        StakeParams memory genericParams,
        StakeSwapMarket memory params
    ) private returns (uint) {
        IMarket market = main.market();
        require(address(market) != address(0), "no market");
        genericParams.inputToken.safeTransferFrom(address(msg.sender), address(this), genericParams.inputAmount);
        genericParams.inputToken.approve(address(market), genericParams.inputAmount);

        return
            market.swap(
                address(genericParams.inputToken),
                address(genericParams.stakingToken),
                genericParams.inputAmount,
                genericParams.stakingAmountMin,
                address(this),
                params.hints
            );
    }

    function makeSwapOneInchImpl(
        uint amount,
        IERC20Upgradeable inputToken,
        address router,
        StakeSwapOneInch memory params
    ) private returns (uint) {
        require(router != address(0), "no 1inch router set");
        // Approve twice more in case of amount fluctuation between estimate and transaction
        inputToken.approve(router, amount * 2);

        (bool success, bytes memory retData) = router.call(params.oneInchCallData);

        ProxyCallerApi.propagateError(success, retData, "1inch");

        require(success == true, "calling 1inch got an error");
        (uint actualAmount, ) = abi.decode(retData, (uint, uint));
        return actualAmount;
    }

    function makeSwapOneInch(
        uint amount,
        address inputToken,
        address router,
        StakeSwapOneInch memory params
    ) external returns (uint) {
        return makeSwapOneInchImpl(amount, IERC20Upgradeable(inputToken), router, params);
    }

    function stakeSwapOneInch(
        IMinimaxMain main,
        StakeParams memory genericParams,
        StakeSwapOneInch memory params
    ) private returns (uint) {
        genericParams.inputToken.safeTransferFrom(address(msg.sender), address(this), genericParams.inputAmount);
        address oneInchRouter = main.oneInchRouter();
        return makeSwapOneInchImpl(genericParams.inputAmount, genericParams.inputToken, oneInchRouter, params);
    }

    function createPosition(
        IMinimaxMain main,
        StakeParams memory genericParams,
        uint tokenAmount,
        uint positionIndex,
        ProxyCaller proxy
    ) private returns (PositionInfo memory) {
        IPoolAdapter adapter = main.getPoolAdapterSafe(genericParams.stakingPool);

        require(
            adapter.stakedToken(genericParams.stakingPool, abi.encode(genericParams.stakingToken)) ==
                address(genericParams.stakingToken),
            "stakeToken: invalid staking token."
        );

        address rewardToken = adapter.rewardToken(genericParams.stakingPool, abi.encode(genericParams.stakingToken));

        uint userFeeAmount = main.getUserFeeAmount(address(msg.sender), tokenAmount);
        uint amountToStake = tokenAmount - userFeeAmount;

        PositionInfo memory position = PositionInfo({
            stakedAmount: amountToStake,
            feeAmount: userFeeAmount,
            stopLossPrice: genericParams.stopLossPrice,
            maxSlippage: genericParams.maxSlippage,
            poolAddress: genericParams.stakingPool,
            owner: address(msg.sender),
            callerAddress: proxy,
            closed: false,
            takeProfitPrice: genericParams.takeProfitPrice,
            stakedToken: genericParams.stakingToken,
            rewardToken: IERC20Upgradeable(rewardToken),
            gelatoLiquidateTaskId: 0
        });

        proxyDeposit(position, adapter, amountToStake);

        return position;
    }

    function proxyDeposit(
        PositionInfo memory position,
        IPoolAdapter adapter,
        uint amount
    ) private {
        position.stakedToken.safeTransfer(address(position.callerAddress), amount);
        position.callerAddress.approve(position.stakedToken, position.poolAddress, amount);
        position.callerAddress.deposit(
            adapter,
            position.poolAddress,
            amount,
            abi.encode(position.stakedToken) // pass stakedToken for aave pools
        );
    }

    function alterPositionParams(
        IMinimaxMain main,
        PositionInfo storage position,
        uint positionIndex,
        uint newAmount,
        uint newStopLossPrice,
        uint newTakeProfitPrice,
        uint newSlippage
    ) external returns (bool shouldClose) {
        require(position.owner == address(msg.sender), "stop loss may be changed only by position owner");

        position.stopLossPrice = newStopLossPrice;
        position.takeProfitPrice = newTakeProfitPrice;
        position.maxSlippage = newSlippage;

        if (newAmount < position.stakedAmount) {
            uint withdrawAmount = position.stakedAmount - newAmount;
            return withdraw(main, position, positionIndex, withdrawAmount, false);
        } else if (newAmount > position.stakedAmount) {
            uint depositAmount = newAmount - position.stakedAmount;
            deposit(main, position, positionIndex, depositAmount);
            return false;
        }
    }

    // Withdraws `amount` tokens from position on underlying proxyCaller address
    function withdraw(
        IMinimaxMain main,
        PositionInfo storage position,
        uint positionIndex,
        uint amount,
        bool amountAll
    ) public returns (bool shouldClose) {
        require(position.owner == address(msg.sender), "withdraw: only position owner allowed");

        IPoolAdapter adapter = main.getPoolAdapterSafe(position.poolAddress);
        require(position.closed == false, "withdraw: position is closed");

        if (amountAll) {
            position.callerAddress.withdrawAll(
                adapter,
                position.poolAddress,
                abi.encode(position.stakedToken) // pass stakedToken for aave pools
            );
        } else {
            position.callerAddress.withdraw(
                adapter,
                position.poolAddress,
                amount,
                abi.encode(position.stakedToken) // pass stakedToken for aave pools
            );
        }

        uint poolBalance = position.callerAddress.stakingBalance(
            adapter,
            position.poolAddress,
            abi.encode(position.stakedToken)
        );
        if (poolBalance == 0 || amountAll) {
            return true;
        }

        position.stakedAmount = poolBalance;
        return false;
    }

    // Emits `PositionsWasModified` always.
    function deposit(
        IMinimaxMain main,
        PositionInfo storage position,
        uint positionIndex,
        uint amount
    ) public {
        IPoolAdapter adapter = main.getPoolAdapterSafe(position.poolAddress);

        require(position.owner == address(msg.sender), "deposit: only position owner allowed");
        require(position.closed == false, "deposit: position is closed");

        position.stakedToken.safeTransferFrom(address(msg.sender), address(this), amount);

        uint userFeeAmount = main.getUserFeeAmount(msg.sender, amount);
        uint amountToDeposit = amount - userFeeAmount;

        position.stakedAmount = position.stakedAmount + amountToDeposit;
        position.feeAmount = position.feeAmount + userFeeAmount;

        proxyDeposit(position, adapter, amountToDeposit);
        position.callerAddress.transferAll(position.rewardToken, position.owner);
    }

    function estimatePositionStakedTokenPrice(IMinimaxMain minimaxMain, IERC20Upgradeable positionStakedToken)
        public
        returns (uint price, uint8 priceDecimals)
    {
        // Try price oracle first.

        IPriceOracle priceOracle = minimaxMain.priceOracles(positionStakedToken);
        if (address(priceOracle) != address(0)) {
            int price = Math.max(0, priceOracle.latestAnswer());
            return (uint(price), priceOracle.decimals());
        }

        // We don't have price oracles for `positionStakedToken` -- try to estimate via the Market.

        IMarket market = minimaxMain.market();

        // Market is unavailable, nothing we can do here.
        if (address(market) == address(0)) {
            return (0, 0);
        }

        uint8 positionStakedTokenDecimals = IERC20Decimals(address(positionStakedToken)).decimals();

        (bool success, bytes memory encodedEstimateOutResult) = address(market).call(
            abi.encodeCall(
                market.estimateOut,
                (address(positionStakedToken), minimaxMain.busdAddress(), 10**positionStakedTokenDecimals)
            )
        );
        if (!success) {
            return (0, 0);
        }

        (uint estimatedOut, ) = abi.decode(encodedEstimateOutResult, (uint256, bytes));
        uint8 stablecoinDecimals = IERC20Decimals(minimaxMain.busdAddress()).decimals();
        return (estimatedOut, stablecoinDecimals);
    }

    function estimateLpPartsForPosition(IMinimaxMain minimaxMain, PositionInfo memory position)
        internal
        returns (uint, uint)
    {
        uint withdrawnBalance = position.stakedToken.balanceOf(address(position.callerAddress));
        position.callerAddress.transferAll(position.stakedToken, address(minimaxMain));

        IERC20Upgradeable(position.stakedToken).transfer(address(position.stakedToken), withdrawnBalance);

        (uint amount0, uint amount1) = IPairToken(address(position.stakedToken)).burn(address(minimaxMain));
        return (amount0, amount1);
    }

    function isOutsideRange(IMinimaxMain minimaxMain, PositionInfo storage position)
        external
        returns (
            bool isOutsideRange,
            uint256 amountOut,
            bytes memory hints
        )
    {
        bool isOutsideRange;
        isOutsideRange = isOpen(position);
        if (!isOutsideRange) {
            return (isOutsideRange, amountOut, hints);
        }

        PositionBalanceLib.PositionBalance memory balance = PositionBalanceLib.get(minimaxMain, position);

        uint amountIn = balance.total;
        (amountOut, hints) = minimaxMain.market().estimateOut(
            address(position.stakedToken),
            minimaxMain.busdAddress(),
            amountIn
        );

        uint8 outDecimals = IERC20Decimals(minimaxMain.busdAddress()).decimals();
        uint8 inDecimals = IERC20Decimals(address(position.stakedToken)).decimals();
        isOutsideRange = PositionExchangeLib.isPriceOutsideRange(
            position,
            amountOut,
            amountIn,
            outDecimals,
            inDecimals
        );
        if (!isOutsideRange) {
            return (isOutsideRange, amountOut, hints);
        }

        // if price oracle exists then double check
        // that price is outside range
        IPriceOracle oracle = minimaxMain.priceOracles(position.stakedToken);
        if (address(oracle) != address(0)) {
            uint oracleMultiplier = 10**oracle.decimals();
            uint oraclePrice = uint(oracle.latestAnswer());
            isOutsideRange = PositionExchangeLib.isPriceOutsideRange(position, oraclePrice, oracleMultiplier, 0, 0);
            if (!isOutsideRange) {
                return (isOutsideRange, amountOut, hints);
            }
        }

        return (isOutsideRange, amountOut, hints);
    }

    function isOpen(PositionInfo storage position) private view returns (bool) {
        return !position.closed && position.owner != address(0);
    }
}

File 24 of 36 : IMinimaxMain.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";

import "../pool/IPoolAdapter.sol";
import "../interfaces/IPriceOracle.sol";
import "../market/IMarket.sol";

interface IMinimaxMain {
    function getUserFeeAmount(address user, uint stakeAmount) external view returns (uint);

    function oneInchRouter() external view returns (address);

    function market() external view returns (IMarket);

    function priceOracles(IERC20Upgradeable) external view returns (IPriceOracle);

    function getPoolAdapterSafe(address pool) external view returns (IPoolAdapter);

    function poolAdapters(uint256 pool) external view returns (IPoolAdapter);

    function busdAddress() external view returns (address);
}

File 25 of 36 : IPairToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";

interface IPairToken is IERC20Upgradeable {
    function token0() external view returns (address);

    function token1() external view returns (address);

    function totalSupply() external view returns (uint);

    function getReserves()
        external
        view
        returns (
            uint112 reserve0,
            uint112 reserve1,
            uint32 blockTimestampLast
        );

    function burn(address to) external returns (uint amount0, uint amount1);

    function mint(address to) external returns (uint liquidity);
}

File 26 of 36 : 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 27 of 36 : Initializable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.0;

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.
 *
 * 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 initialize the implementation contract, you can either invoke the
 * initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() initializer {}
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     */
    bool private _initialized;

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

    /**
     * @dev Modifier to protect an initializer function from being invoked twice.
     */
    modifier initializer() {
        // If the contract is initializing we ignore whether _initialized is set in order to support multiple
        // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
        // contract may have been reentered.
        require(_initializing ? _isConstructor() : !_initialized, "Initializable: contract is already initialized");

        bool isTopLevelCall = !_initializing;
        if (isTopLevelCall) {
            _initializing = true;
            _initialized = true;
        }

        _;

        if (isTopLevelCall) {
            _initializing = false;
        }
    }

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

    function _isConstructor() private view returns (bool) {
        return !AddressUpgradeable.isContract(address(this));
    }
}

File 28 of 36 : AddressUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.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://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

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

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

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(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) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason 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 {
            // 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

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

File 29 of 36 : IMinimaxToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IMinimaxToken {
    function mint(address _to, uint256 _amount) external;

    function burn(address _from, uint256 _amount) external;

    function owner() external returns (address);
}

File 30 of 36 : IMarket.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

import "./Hints.sol";
import "./v2/PancakeLpMarket.sol";

interface IMarket {
    function swap(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 amountOutMin,
        address destination,
        bytes memory hints
    ) external returns (uint256);

    function estimateOut(
        address tokenIn,
        address tokenOut,
        uint256 amountIn
    ) external view returns (uint256 amountOut, bytes memory hints);

    function estimateBurn(address lpToken, uint amountIn) external view returns (uint, uint);
}

File 31 of 36 : Hints.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./ArrayHelper.sol";

library Hints {
    uint8 private constant IS_PAIR = 0;
    uint8 private constant PAIR_INPUT = 1;
    uint8 private constant RELAY = 2;
    uint8 private constant ROUTER = 3;

    function setIsPair(address key) internal pure returns (bytes memory) {
        return _encode(IS_PAIR, uint160(key), 1);
    }

    function getIsPair(bytes memory hints, address key) internal pure returns (bool isPairToken) {
        return _decode(hints, IS_PAIR, uint160(key)) == 1;
    }

    function setPairInput(address key, uint value) internal pure returns (bytes memory) {
        return _encode(PAIR_INPUT, uint160(key), value);
    }

    function getPairInput(bytes memory hints, address key) internal pure returns (uint value) {
        value = _decode(hints, PAIR_INPUT, uint160(key));
    }

    function setRouter(
        address tokenIn,
        address tokenOut,
        address router
    ) internal pure returns (bytes memory) {
        return _encodeAddress(ROUTER, _hashTuple(tokenIn, tokenOut), router);
    }

    function getRouter(
        bytes memory hints,
        address tokenIn,
        address tokenOut
    ) internal pure returns (address router) {
        return _decodeAddress(hints, ROUTER, _hashTuple(tokenIn, tokenOut));
    }

    function setRelay(
        address tokenIn,
        address tokenOut,
        address relay
    ) internal pure returns (bytes memory) {
        return _encodeAddress(RELAY, _hashTuple(tokenIn, tokenOut), relay);
    }

    function getRelay(
        bytes memory hints,
        address tokenIn,
        address tokenOut
    ) internal pure returns (address) {
        return _decodeAddress(hints, RELAY, _hashTuple(tokenIn, tokenOut));
    }

    function merge2(bytes memory h0, bytes memory h1) internal pure returns (bytes memory) {
        return abi.encodePacked(h0, h1);
    }

    function merge3(
        bytes memory h0,
        bytes memory h1,
        bytes memory h2
    ) internal pure returns (bytes memory) {
        return abi.encodePacked(h0, h1, h2);
    }

    function empty() internal pure returns (bytes memory) {
        return "";
    }

    function _encode(
        uint8 kind,
        uint key,
        uint value
    ) private pure returns (bytes memory) {
        return abi.encodePacked(kind, key, value);
    }

    function _encodeAddress(
        uint8 kind,
        uint key,
        address value
    ) private pure returns (bytes memory) {
        return _encode(kind, key, uint160(value));
    }

    function _decode(
        bytes memory hints,
        uint8 kind,
        uint key
    ) private pure returns (uint value) {
        // each hint takes 65 bytes (1+32+32). 1 byte for kind, 32 bytes for key, 32 bytes for value
        for (uint i = 0; i < hints.length; i += 65) {
            // kind is at offset 0
            if (uint8(hints[i]) != kind) {
                continue;
            }
            // key is at offset 1
            if (ArrayHelper.sliceUint(hints, i + 1) != key) {
                continue;
            }
            // value is at offset 33 (1+32)
            return ArrayHelper.sliceUint(hints, i + 33);
        }
    }

    function _decodeAddress(
        bytes memory hints,
        uint8 kind,
        uint key
    ) private pure returns (address) {
        return address(uint160(_decode(hints, kind, key)));
    }

    function _hashTuple(address a1, address a2) private pure returns (uint256) {
        uint256 u1 = uint160(a1);
        uint256 u2 = uint160(a2);
        u2 = u2 << 96;
        return u1 ^ u2;
    }
}

File 32 of 36 : PancakeLpMarket.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";

import "./IPairToken.sol";
import "../ArrayHelper.sol";
import "../Hints.sol";
import "../SingleMarket.sol";
import "../../interfaces/IPancakeFactory.sol";
import "../../interfaces/IPancakeRouter.sol";
import "../../helpers/Math.sol";

contract PancakeLpMarket is OwnableUpgradeable {
    using ArrayHelper for uint[];

    address public relayToken;
    SingleMarket public market;

    constructor() initializer {
        __Ownable_init();
    }

    function setRelayToken(address _relayToken) external onlyOwner {
        relayToken = _relayToken;
    }

    function setMarket(SingleMarket _market) external onlyOwner {
        market = _market;
    }

    function swap(
        address tokenIn,
        address tokenOut,
        uint amountIn,
        uint amountOutMin,
        address destination,
        bytes memory hints
    ) external returns (uint) {
        IERC20Upgradeable(tokenIn).transferFrom(address(msg.sender), address(this), amountIn);
        if (tokenIn == tokenOut) {
            require(amountIn >= amountOutMin, "amountOutMin");
            IERC20Upgradeable(tokenIn).transfer(destination, amountIn);
            return amountIn;
        }

        bool tokenInPair = Hints.getIsPair(hints, tokenIn);
        bool tokenOutPair = Hints.getIsPair(hints, tokenOut);
        uint amountOut;
        if (tokenInPair && tokenOutPair) {
            uint amountRelay = _swapPairToSingle(tokenIn, relayToken, amountIn, hints);
            amountOut = _swapSingleToPair(tokenIn, tokenOut, amountIn, hints);
        }

        if (tokenInPair && !tokenOutPair) {
            amountOut = _swapPairToSingle(tokenIn, tokenOut, amountIn, hints);
        }

        if (!tokenInPair && tokenOutPair) {
            amountOut = _swapSingleToPair(tokenIn, tokenOut, amountIn, hints);
        }

        if (!tokenInPair && !tokenOutPair) {
            amountOut = _swapSingles(tokenIn, tokenOut, amountIn, hints);
        }

        IERC20Upgradeable(tokenOut).transfer(destination, amountOut);
        require(amountOut >= amountOutMin, "amountOutMin");
        return amountOut;
    }

    function _swapPairToSingle(
        address tokenIn,
        address tokenOut,
        uint amountIn,
        bytes memory hints
    ) private returns (uint) {
        IPairToken pairIn = IPairToken(tokenIn);
        address token0 = pairIn.token0();
        address token1 = pairIn.token1();
        IERC20Upgradeable(tokenIn).transfer(tokenIn, amountIn);
        (uint amount0, uint amount1) = pairIn.burn(address(this));
        return _swapSingles(token0, tokenOut, amount0, hints) + _swapSingles(token1, tokenOut, amount1, hints);
    }

    function _swapSingleToPair(
        address tokenIn,
        address tokenOut,
        uint amountIn,
        bytes memory hints
    ) private returns (uint) {
        IPairToken pair = IPairToken(tokenOut);

        uint amountIn0 = Hints.getPairInput(hints, tokenOut);
        require(amountIn0 > 0, "swapSingleToPair: no hint");

        uint amountIn1 = amountIn - amountIn0;

        uint amount0 = _swapSingles(tokenIn, pair.token0(), amountIn0, hints);
        uint amount1 = _swapSingles(tokenIn, pair.token1(), amountIn1, hints);

        (uint liquidity, uint effective0, uint effective1) = _calculateEffective(pair, amount0, amount1);
        IERC20Upgradeable(pair.token0()).transfer(address(pair), effective0);
        IERC20Upgradeable(pair.token1()).transfer(address(pair), effective1);
        return pair.mint(address(this));
    }

    function _swapSingles(
        address tokenIn,
        address tokenOut,
        uint amountIn,
        bytes memory hints
    ) private returns (uint) {
        if (tokenIn == tokenOut) {
            return amountIn;
        }

        IERC20Upgradeable(tokenIn).approve(address(market), amountIn);
        return market.swap(tokenIn, tokenOut, amountIn, 0, address(this), hints);
    }

    function estimateOut(
        address tokenIn,
        address tokenOut,
        uint amountIn,
        bool tokenInPair,
        bool tokenOutPair
    ) external view returns (uint amountOut, bytes memory hints) {
        if (tokenIn == tokenOut) {
            return (amountIn, Hints.empty());
        }

        uint amountRelay;
        uint amountOut;
        bytes memory hints0;
        bytes memory hints1;

        if (tokenInPair && tokenOutPair) {
            (amountRelay, hints0) = _estimatePairToSingle(tokenIn, relayToken, amountIn);
            (amountOut, hints1) = _estimateSingleToPair(relayToken, tokenOut, amountRelay);
        }

        if (tokenInPair && !tokenOutPair) {
            (amountOut, hints0) = _estimatePairToSingle(tokenIn, tokenOut, amountIn);
        }

        if (!tokenInPair && tokenOutPair) {
            (amountOut, hints0) = _estimateSingleToPair(tokenIn, tokenOut, amountIn);
        }

        if (!tokenInPair && !tokenOutPair) {
            (amountOut, hints0) = market.estimateOut(tokenIn, tokenOut, amountIn);
        }

        return (amountOut, Hints.merge2(hints0, hints1));
    }

    struct reservesState {
        address token0;
        address token1;
        uint reserve0;
        uint reserve1;
    }

    function estimateBurn(address lpToken, uint amountIn) public view returns (uint, uint) {
        IPairToken pair = IPairToken(lpToken);

        reservesState memory state;
        state.token0 = pair.token0();
        state.token1 = pair.token1();

        state.reserve0 = IERC20Upgradeable(state.token0).balanceOf(address(lpToken));
        state.reserve1 = IERC20Upgradeable(state.token1).balanceOf(address(lpToken));
        uint totalSupply = pair.totalSupply();

        uint amount0 = (amountIn * state.reserve0) / totalSupply;
        uint amount1 = (amountIn * state.reserve1) / totalSupply;

        return (amount0, amount1);
    }

    function _estimatePairToSingle(
        address tokenIn,
        address tokenOut,
        uint amountIn
    ) private view returns (uint amountOut, bytes memory hints) {
        (uint amount0, uint amount1) = estimateBurn(tokenIn, amountIn);

        (uint amountOut0, bytes memory hint0) = market.estimateOut(IPairToken(tokenIn).token0(), tokenOut, amount0);
        (uint amountOut1, bytes memory hint1) = market.estimateOut(IPairToken(tokenIn).token1(), tokenOut, amount1);
        amountOut = amountOut0 + amountOut1;
        hints = Hints.merge2(hint0, hint1);
    }

    function _estimateSingleToPair(
        address tokenIn,
        address tokenOut,
        uint amountIn
    ) private view returns (uint amountOut, bytes memory hints) {
        IPairToken pair = IPairToken(tokenOut);
        uint amountIn0 = _calculatePairInput0(tokenIn, pair, amountIn);
        uint amountIn1 = amountIn - amountIn0;
        (uint amountOut0, bytes memory hints0) = market.estimateOut(tokenIn, pair.token0(), amountIn0);
        (uint amountOut1, bytes memory hints1) = market.estimateOut(tokenIn, pair.token1(), amountIn1);

        (uint liquidity, , ) = _calculateEffective(pair, amountOut0, amountOut1);
        amountOut = liquidity;
        hints = Hints.merge3(hints0, hints1, Hints.setPairInput(tokenOut, amountIn0));
    }

    // assume that pair consists of token0 and token1
    // _calculatePairInput0 returns the amount of tokenIn that
    // should be exchanged on token0,
    // so that token0 and token1 proportion match reserves proportions
    function _calculatePairInput0(
        address tokenIn,
        IPairToken pair,
        uint amountIn
    ) private view returns (uint) {
        reservesState memory state;
        state.token0 = pair.token0();
        state.token1 = pair.token1();
        (state.reserve0, state.reserve1, ) = pair.getReserves();

        (, bytes memory hints0) = market.estimateOut(tokenIn, state.token0, amountIn / 2);
        (, bytes memory hints1) = market.estimateOut(tokenIn, state.token1, amountIn / 2);

        uint left = 0;
        uint right = amountIn;
        uint eps = amountIn / 1000;

        while (right - left >= eps) {
            uint left_third = left + (right - left) / 3;
            uint right_third = right - (right - left) / 3;
            uint f_left = _targetFunction(state, tokenIn, amountIn, left_third, hints0, hints1);
            uint f_right = _targetFunction(state, tokenIn, amountIn, right_third, hints0, hints1);
            if (f_left < f_right) {
                left = left_third;
            } else {
                right = right_third;
            }
        }

        return (left + right) / 2;
    }

    function _targetFunction(
        reservesState memory state,
        address tokenIn,
        uint amountIn,
        uint amount0,
        bytes memory hints0,
        bytes memory hints1
    ) private view returns (uint) {
        uint amountOut0 = market.estimateOutWithHints(tokenIn, state.token0, amount0, hints0) * state.reserve1;
        uint amountOut1 = market.estimateOutWithHints(tokenIn, state.token1, amountIn - amount0, hints1) *
            state.reserve0;
        return Math.min(amountOut0, amountOut1);
    }

    function _calculateEffective(
        IPairToken pair,
        uint amountIn0,
        uint amountIn1
    )
        private
        view
        returns (
            uint liquidity,
            uint effective0,
            uint effective1
        )
    {
        (uint r0, uint r1, ) = pair.getReserves();
        uint totalSupply = pair.totalSupply();
        liquidity = Math.min((amountIn0 * totalSupply) / r0, (amountIn1 * totalSupply) / r1);
        effective0 = (liquidity * r0) / totalSupply;
        effective1 = (liquidity * r1) / totalSupply;
    }

    function transferTo(
        address token,
        address to,
        uint amount
    ) external onlyOwner {
        address nativeToken = address(0);
        if (token == nativeToken) {
            (bool success, ) = to.call{value: amount}("");
            require(success, "transferTo failed");
        } else {
            SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(token), to, amount);
        }
    }
}

File 33 of 36 : ArrayHelper.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

library ArrayHelper {
    function first(uint256[] memory arr) internal pure returns (uint256) {
        return arr[0];
    }

    function last(uint256[] memory arr) internal pure returns (uint256) {
        return arr[arr.length - 1];
    }

    // assume that b is encoded uint[]
    function lastUint(bytes memory b) internal pure returns (uint res) {
        require(b.length >= 32, "lastUint: out of range");
        uint i = b.length - 32;
        assembly {
            res := mload(add(b, add(0x20, i)))
        }
    }

    function sliceUint(bytes memory b, uint i) internal pure returns (uint res) {
        require(b.length >= i + 32, "sliceUint: out of range");
        assembly {
            res := mload(add(b, add(0x20, i)))
        }
    }

    function new2(address a0, address a1) internal pure returns (address[] memory) {
        address[] memory p = new address[](2);
        p[0] = a0;
        p[1] = a1;
        return p;
    }

    function new3(
        address a0,
        address a1,
        address a2
    ) internal pure returns (address[] memory) {
        address[] memory p = new address[](3);
        p[0] = a0;
        p[1] = a1;
        p[2] = a2;
        return p;
    }
}

File 34 of 36 : SingleMarket.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

import "./ArrayHelper.sol";
import "./Hints.sol";

interface IRouter {
    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);

    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
}

contract SingleMarket is OwnableUpgradeable {
    using ArrayHelper for uint[];

    address[] public relayTokens;
    IRouter[] public routers;

    constructor() initializer {
        __Ownable_init();
    }

    function getRelayTokens() external view returns (address[] memory) {
        return relayTokens;
    }

    function setRelayTokens(address[] calldata _relayTokens) external onlyOwner {
        relayTokens = _relayTokens;
    }

    function getRouters() external view returns (IRouter[] memory) {
        return routers;
    }

    function setRouters(IRouter[] calldata _routers) external onlyOwner {
        routers = _routers;
    }

    function swap(
        address tokenIn,
        address tokenOut,
        uint amountIn,
        uint amountOutMin,
        address destination,
        bytes memory hints
    ) external returns (uint) {
        IERC20Upgradeable(tokenIn).transferFrom(address(msg.sender), address(this), amountIn);
        if (tokenIn == tokenOut) {
            require(amountIn >= amountOutMin, "amountOutMin");
            IERC20Upgradeable(tokenIn).transfer(destination, amountIn);
            return amountIn;
        }

        address tokenRelay = Hints.getRelay(hints, tokenIn, tokenOut);
        if (tokenRelay == address(0)) {
            address router = Hints.getRouter(hints, tokenIn, tokenOut);
            return _swapDirect(router, tokenIn, tokenOut, amountIn, amountOutMin, destination);
        }

        address routerIn = Hints.getRouter(hints, tokenIn, tokenRelay);
        address routerOut = Hints.getRouter(hints, tokenRelay, tokenOut);
        return _swapRelay(routerIn, routerOut, tokenIn, tokenRelay, tokenOut, amountIn, amountOutMin, destination);
    }

    function _swapDirect(
        address router,
        address tokenIn,
        address tokenOut,
        uint amountIn,
        uint amountOutMin,
        address destination
    ) private returns (uint) {
        IERC20Upgradeable(tokenIn).approve(router, amountIn);
        return
            IRouter(router)
                .swapExactTokensForTokens({
                    amountIn: amountIn,
                    amountOutMin: amountOutMin,
                    path: ArrayHelper.new2(tokenIn, tokenOut),
                    to: destination,
                    deadline: block.timestamp
                })
                .last();
    }

    function _swapRelay(
        address routerIn,
        address routerOut,
        address tokenIn,
        address tokenRelay,
        address tokenOut,
        uint amountIn,
        uint amountOutMin,
        address destination
    ) private returns (uint) {
        if (routerIn == routerOut) {
            IERC20Upgradeable(tokenIn).approve(routerIn, amountIn);
            return
                IRouter(routerIn)
                    .swapExactTokensForTokens({
                        amountIn: amountIn,
                        amountOutMin: amountOutMin,
                        path: ArrayHelper.new3(tokenIn, tokenRelay, tokenOut),
                        to: destination,
                        deadline: block.timestamp
                    })
                    .last();
        }

        IERC20Upgradeable(tokenIn).approve(routerIn, amountIn);
        uint amountRelay = IRouter(routerIn)
            .swapExactTokensForTokens({
                amountIn: amountIn,
                amountOutMin: 0,
                path: ArrayHelper.new2(tokenIn, tokenRelay),
                to: address(this),
                deadline: block.timestamp
            })
            .last();

        IERC20Upgradeable(tokenRelay).approve(routerOut, amountRelay);
        return
            IRouter(routerOut)
                .swapExactTokensForTokens({
                    amountIn: amountRelay,
                    amountOutMin: amountOutMin,
                    path: ArrayHelper.new2(tokenRelay, tokenOut),
                    to: destination,
                    deadline: block.timestamp
                })
                .last();
    }

    function estimateOut(
        address tokenIn,
        address tokenOut,
        uint amountIn
    ) external view returns (uint amountOut, bytes memory hints) {
        if (tokenIn == tokenOut) {
            return (amountIn, Hints.empty());
        }

        (amountOut, hints) = _estimateOutDirect(tokenIn, tokenOut, amountIn);

        for (uint i = 0; i < relayTokens.length; i++) {
            (uint attemptOut, bytes memory attemptHints) = _estimateOutRelay(
                tokenIn,
                relayTokens[i],
                tokenOut,
                amountIn
            );
            if (attemptOut > amountOut) {
                amountOut = attemptOut;
                hints = attemptHints;
            }
        }

        require(amountOut > 0, "no estimation");
    }

    function estimateOutWithHints(
        address tokenIn,
        address tokenOut,
        uint amountIn,
        bytes memory hints
    ) external view returns (uint amountOut) {
        if (tokenIn == tokenOut) {
            return amountIn;
        }

        address relay = Hints.getRelay(hints, tokenIn, tokenOut);
        if (relay == address(0)) {
            address router = Hints.getRouter(hints, tokenIn, tokenOut);
            return _getAmountOut2(IRouter(router), tokenIn, tokenOut, amountIn);
        }

        address routerIn = Hints.getRouter(hints, tokenIn, relay);
        address routerOut = Hints.getRouter(hints, relay, tokenOut);
        if (routerIn == routerOut) {
            return _getAmountOut3(IRouter(routerIn), tokenIn, relay, tokenOut, amountIn);
        }

        uint amountRelay = _getAmountOut2(IRouter(routerIn), tokenIn, relay, amountIn);
        return _getAmountOut2(IRouter(routerOut), relay, tokenOut, amountRelay);
    }

    function _estimateOutDirect(
        address tokenIn,
        address tokenOut,
        uint amountIn
    ) private view returns (uint amountOut, bytes memory hints) {
        IRouter router;
        (router, amountOut) = _optimalAmount(tokenIn, tokenOut, amountIn);
        hints = Hints.setRouter(tokenIn, tokenOut, address(router));
    }

    function _estimateOutRelay(
        address tokenIn,
        address tokenRelay,
        address tokenOut,
        uint amountIn
    ) private view returns (uint amountOut, bytes memory hints) {
        (IRouter routerIn, uint amountRelay) = _optimalAmount(tokenIn, tokenRelay, amountIn);
        (IRouter routerOut, ) = _optimalAmount(tokenRelay, tokenOut, amountRelay);

        hints = Hints.setRelay(tokenIn, tokenOut, address(tokenRelay));
        hints = Hints.merge2(hints, Hints.setRouter(tokenIn, tokenRelay, address(routerIn)));
        hints = Hints.merge2(hints, Hints.setRouter(tokenRelay, tokenOut, address(routerOut)));

        if (routerIn == routerOut) {
            amountOut = _getAmountOut3(routerIn, tokenIn, tokenRelay, tokenOut, amountIn);
        } else {
            amountOut = _getAmountOut2(routerOut, tokenRelay, tokenOut, amountRelay);
        }
    }

    function _optimalAmount(
        address tokenIn,
        address tokenOut,
        uint amountIn
    ) private view returns (IRouter optimalRouter, uint optimalOut) {
        for (uint32 i = 0; i < routers.length; i++) {
            IRouter router = routers[i];
            uint amountOut = _getAmountOut2(router, tokenIn, tokenOut, amountIn);
            if (amountOut > optimalOut) {
                optimalRouter = routers[i];
                optimalOut = amountOut;
            }
        }
    }

    function _getAmountOut2(
        IRouter router,
        address tokenIn,
        address tokenOut,
        uint amountIn
    ) private view returns (uint) {
        return _getAmountSafe(router, ArrayHelper.new2(tokenIn, tokenOut), amountIn);
    }

    function _getAmountOut3(
        IRouter router,
        address tokenIn,
        address tokenMid,
        address tokenOut,
        uint amountIn
    ) private view returns (uint) {
        return _getAmountSafe(router, ArrayHelper.new3(tokenIn, tokenMid, tokenOut), amountIn);
    }

    function _getAmountSafe(
        IRouter router,
        address[] memory path,
        uint amountIn
    ) public view returns (uint output) {
        bytes memory payload = abi.encodeWithSelector(router.getAmountsOut.selector, amountIn, path);
        (bool success, bytes memory response) = address(router).staticcall(payload);
        if (success && response.length > 32) {
            return ArrayHelper.lastUint(response);
        }
        return 0;
    }
}

File 35 of 36 : IPancakeFactory.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IPancakeFactory {
    function getPair(address tokenA, address tokenB) external view returns (address);
}

File 36 of 36 : PairTokenDetector.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IPairToken.sol";

contract PairTokenDetector {
    function isPairToken(address a) external view returns (bool) {
        bool success;
        bytes memory response;

        (success, response) = a.staticcall(abi.encodeWithSelector(IPairToken.token0.selector));
        if (!(success && response.length == 32)) {
            return false;
        }

        (success, response) = a.staticcall(abi.encodeWithSelector(IPairToken.token1.selector));
        if (!(success && response.length == 32)) {
            return false;
        }

        (success, response) = a.staticcall(abi.encodeWithSelector(IPairToken.totalSupply.selector));
        if (!(success && response.length == 32)) {
            return false;
        }

        (success, response) = a.staticcall(abi.encodeWithSelector(IPairToken.getReserves.selector));
        if (!(success && response.length == 96)) {
            return false;
        }

        return true;
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {
    "contracts/PositionBalanceLib.sol": {
      "PositionBalanceLib": "0x06b619a9be1d87a620bfa39d51e3408f87a3d5f6"
    },
    "contracts/PositionLib.sol": {
      "PositionLib": "0xb7d988106099eb535dfebc1e9c133c510fc1d6ef"
    },
    "contracts/ProxyCallerApi.sol": {
      "ProxyCallerApi": "0xc7c1e3937502511f9f8c7eaebecfd5a492c56fd9"
    }
  }
}

Contract ABI

[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"positionIndex","type":"uint256"}],"name":"PositionWasClosed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"positionIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakedTokenPrice","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"stakedTokenPriceDecimals","type":"uint8"}],"name":"PositionWasClosedV2","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"positionIndex","type":"uint256"}],"name":"PositionWasCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"positionIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakedTokenPrice","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"stakedTokenPriceDecimals","type":"uint8"}],"name":"PositionWasCreatedV2","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"positionIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakedTokenPrice","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"stakedTokenPriceDecimals","type":"uint8"}],"name":"PositionWasLiquidatedV2","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"positionIndex","type":"uint256"}],"name":"PositionWasModified","type":"event"},{"inputs":[],"name":"FEE_MULTIPLIER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"POSITION_PRICE_LIMITS_MULTIPLIER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLIPPAGE_MULTIPLIER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionIndex","type":"uint256"},{"internalType":"uint256","name":"newAmount","type":"uint256"},{"internalType":"uint256","name":"newStopLossPrice","type":"uint256"},{"internalType":"uint256","name":"newTakeProfitPrice","type":"uint256"},{"internalType":"uint256","name":"newSlippage","type":"uint256"}],"name":"alterPositionParams","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"raw","type":"bytes"}],"name":"automationExec","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionIndex","type":"uint256"}],"name":"automationResolve","outputs":[{"internalType":"bool","name":"canExec","type":"bool"},{"internalType":"bytes","name":"execPayload","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"busdAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cakeAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cleanProxyPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"defaultExchange","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionIndex","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"depositFees","outputs":[{"internalType":"uint256","name":"fee","type":"uint256"},{"internalType":"uint256","name":"stakedAmountThreshold","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionIndex","type":"uint256"}],"name":"estimateLpPartsForPosition","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionIndex","type":"uint256"}],"name":"estimateWithdrawalAmountForPosition","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"fillProxyPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gelatoFeeToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"gelatoLiquidateFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gelatoOps","outputs":[{"internalType":"contract IGelatoOps","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gelatoPayee","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pool","type":"address"}],"name":"getPoolAdapter","outputs":[{"internalType":"contract IPoolAdapter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pool","type":"address"}],"name":"getPoolAdapterKey","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pool","type":"address"}],"name":"getPoolAdapterSafe","outputs":[{"internalType":"contract IPoolAdapter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"pools","type":"address[]"}],"name":"getPoolAdapters","outputs":[{"internalType":"contract IPoolAdapter[]","name":"adapters","type":"address[]"},{"internalType":"uint256[]","name":"keys","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"positionIndexes","type":"uint256[]"}],"name":"getPositionBalances","outputs":[{"components":[{"internalType":"uint256","name":"total","type":"uint256"},{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"gasTank","type":"uint256"}],"internalType":"struct PositionBalanceLib.PositionBalance[]","name":"","type":"tuple[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionIndex","type":"uint256"}],"name":"getPositionInfo","outputs":[{"components":[{"internalType":"uint256","name":"stakedAmount","type":"uint256"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"uint256","name":"stopLossPrice","type":"uint256"},{"internalType":"uint256","name":"maxSlippage","type":"uint256"},{"internalType":"address","name":"poolAddress","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"contract ProxyCaller","name":"callerAddress","type":"address"},{"internalType":"bool","name":"closed","type":"bool"},{"internalType":"uint256","name":"takeProfitPrice","type":"uint256"},{"internalType":"contract IERC20Upgradeable","name":"stakedToken","type":"address"},{"internalType":"contract IERC20Upgradeable","name":"rewardToken","type":"address"},{"internalType":"bytes32","name":"gelatoLiquidateTaskId","type":"bytes32"}],"internalType":"struct PositionInfo","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Upgradeable","name":"token","type":"address"}],"name":"getPriceOracleSafe","outputs":[{"internalType":"contract IPriceOracle","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSlippageMultiplier","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getUserFeeAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_minimaxStaking","type":"address"},{"internalType":"address","name":"_busdAddress","type":"address"},{"internalType":"address","name":"_gelatoOps","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isLiquidator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ProxyCaller","name":"proxy","type":"address"}],"name":"isModernProxy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lastPositionIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"market","outputs":[{"internalType":"contract IMarket","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minimaxStaking","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oneInchRouter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolAdapters","outputs":[{"internalType":"contract IPoolAdapter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"poolAdaptersDeprecated","outputs":[{"internalType":"contract IPoolAdapter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"positions","outputs":[{"internalType":"uint256","name":"stakedAmount","type":"uint256"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"uint256","name":"stopLossPrice","type":"uint256"},{"internalType":"uint256","name":"maxSlippage","type":"uint256"},{"internalType":"address","name":"poolAddress","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"contract ProxyCaller","name":"callerAddress","type":"address"},{"internalType":"bool","name":"closed","type":"bool"},{"internalType":"uint256","name":"takeProfitPrice","type":"uint256"},{"internalType":"contract IERC20Upgradeable","name":"stakedToken","type":"address"},{"internalType":"contract IERC20Upgradeable","name":"rewardToken","type":"address"},{"internalType":"bytes32","name":"gelatoLiquidateTaskId","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Upgradeable","name":"","type":"address"}],"name":"priceOracles","outputs":[{"internalType":"contract IPriceOracle","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"proxyPool","outputs":[{"internalType":"contract ProxyCaller","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolIdx","type":"uint256"},{"internalType":"uint256","name":"feeShare","type":"uint256"}],"name":"setDepositFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"setGasTankThreshold","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_gelatoOps","type":"address"}],"name":"setGelatoOps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newLastPositionIndex","type":"uint256"}],"name":"setLastPositionIndex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"bool","name":"value","type":"bool"}],"name":"setLiquidator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IMarket","name":"_market","type":"address"}],"name":"setMarket","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"stakingAddress","type":"address"}],"name":"setMinimaxStakingAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_router","type":"address"}],"name":"setOneInchRouter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"pools","type":"address[]"},{"internalType":"contract IPoolAdapter[]","name":"adapters","type":"address[]"}],"name":"setPoolAdapters","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20Upgradeable[]","name":"tokens","type":"address[]"},{"internalType":"contract IPriceOracle[]","name":"oracles","type":"address[]"}],"name":"setPriceOracles","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_native","type":"address"}],"name":"setWrappedNative","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"inputAmount","type":"uint256"},{"internalType":"contract IERC20Upgradeable","name":"inputToken","type":"address"},{"internalType":"uint256","name":"stakingAmountMin","type":"uint256"},{"internalType":"contract IERC20Upgradeable","name":"stakingToken","type":"address"},{"internalType":"address","name":"stakingPool","type":"address"},{"internalType":"uint256","name":"maxSlippage","type":"uint256"},{"internalType":"uint256","name":"stopLossPrice","type":"uint256"},{"internalType":"uint256","name":"takeProfitPrice","type":"uint256"},{"internalType":"uint256","name":"swapKind","type":"uint256"},{"internalType":"bytes","name":"swapParams","type":"bytes"}],"name":"stake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"stakeGelatoFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Upgradeable","name":"stakingToken","type":"address"},{"internalType":"address","name":"stakingPool","type":"address"},{"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"internalType":"uint256","name":"maxSlippage","type":"uint256"},{"internalType":"uint256","name":"stopLossPrice","type":"uint256"},{"internalType":"uint256","name":"takeProfitPrice","type":"uint256"}],"name":"stakeToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"inputToken","type":"address"},{"internalType":"address","name":"stakingToken","type":"address"},{"internalType":"uint256","name":"inputTokenAmount","type":"uint256"}],"name":"swapEstimate","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"bytes","name":"hints","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Upgradeable","name":"inputToken","type":"address"},{"internalType":"contract IERC20Upgradeable","name":"stakingToken","type":"address"},{"internalType":"address","name":"stakingPool","type":"address"},{"internalType":"uint256","name":"inputTokenAmount","type":"uint256"},{"internalType":"uint256","name":"stakingTokenAmountMin","type":"uint256"},{"internalType":"uint256","name":"maxSlippage","type":"uint256"},{"internalType":"uint256","name":"stopLossPrice","type":"uint256"},{"internalType":"uint256","name":"takeProfitPrice","type":"uint256"},{"internalType":"bytes","name":"hints","type":"bytes"}],"name":"swapStakeToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"inputToken","type":"address"},{"internalType":"address","name":"stakingToken","type":"address"},{"internalType":"uint256","name":"inputTokenAmount","type":"uint256"},{"internalType":"bool","name":"tokenInPair","type":"bool"},{"internalType":"bool","name":"tokenOutPair","type":"bool"}],"name":"swapStakeTokenEstimate","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"bytes","name":"hints","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Upgradeable","name":"inputToken","type":"address"},{"internalType":"contract IERC20Upgradeable","name":"stakingToken","type":"address"},{"internalType":"address","name":"stakingPool","type":"address"},{"internalType":"uint256","name":"inputTokenAmount","type":"uint256"},{"internalType":"uint256","name":"maxSlippage","type":"uint256"},{"internalType":"uint256","name":"stopLossPrice","type":"uint256"},{"internalType":"uint256","name":"takeProfitPrice","type":"uint256"},{"internalType":"bytes","name":"oneInchCallData","type":"bytes"}],"name":"swapStakeTokenOneInch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"tokenExchanges","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionIndex","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionIndex","type":"uint256"}],"name":"withdrawAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionIndex","type":"uint256"},{"internalType":"address","name":"withdrawalToken","type":"address"},{"internalType":"bytes","name":"oneInchCallData","type":"bytes"}],"name":"withdrawAllWithSwap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionIndex","type":"uint256"},{"internalType":"address","name":"withdrawalToken","type":"address"},{"internalType":"bytes","name":"oneInchCallDataToken0","type":"bytes"},{"internalType":"bytes","name":"oneInchCallDataToken1","type":"bytes"}],"name":"withdrawAllWithSwapLp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"wrappedNative","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]

608060405234801561001057600080fd5b50615ee080620000216000396000f3fe6080604052600436106103fe5760003560e01c806376a9023111610213578063b5c4b8eb11610123578063e2bbb158116100ab578063ebdac0901161007a578063ebdac09014610e44578063f2fde38b14610e79578063f31f090314610e99578063f645d0bb14610eb9578063f83c610a14610ed957600080fd5b8063e2bbb15814610dcf578063e8353c7814610def578063ea61aae214610e04578063eb6d3a1114610e2457600080fd5b8063c9a0015a116100f2578063c9a0015a14610d22578063cd9637ea14610d4f578063da03bfc314610d6f578063dd2038a614610d8f578063dfca286014610daf57600080fd5b8063b5c4b8eb14610cbc578063b866693f14610ccf578063c0c53b8b14610ce2578063c0d9399714610d0257600080fd5b80639b379c78116101a6578063ac3af20811610175578063ac3af20814610c39578063aff826a614610c59578063b209123114610c6f578063b504881d14610c8f578063b591b13c146108d257600080fd5b80639b379c7814610bd05780639d16d86c14610be3578063a5f2a15214610bf9578063a95dae1d14610c1957600080fd5b8063922c0042116101e2578063922c004214610a4b5780639353c23914610a81578063958e2d3114610ab757806399fbab8814610ad757600080fd5b806376a90231146109bf57806378df79f8146109df57806380f5560514610a0d5780638da5cb5b14610a2d57600080fd5b80634453a3741161030e5780635a62e0fd116102a1578063636a937d11610270578063636a937d146108d257806365df47db1461094a5780636c8b404b1461096a5780636dcea85f1461098a578063715018a6146109aa57600080fd5b80635a62e0fd146108d25780635cc3d29a146108ea5780635dd862411461090a578063610693a71461092a57600080fd5b80634df0c9fe116102dd5780634df0c9fe14610725578063519f509914610745578063529a356f1461088257806352cb60ca146108b257600080fd5b80634453a3741461068f57806347495b75146106af57806348da2275146106e55780634902627a1461070557600080fd5b80631d1814b511610391578063371744261161036057806337174426146105ef5780633d2e26cb1461060f57806342a67d671461062f578063438aacf21461064f578063441a3e701461066f57600080fd5b80631d1814b5146105535780631d2dc7c31461058f57806330062b03146105af57806336f532cf146105cf57600080fd5b80630fc4134e116103cd5780630fc4134e146104b557806311174702146104e35780631286312814610505578063178fdaee1461053357600080fd5b8063013745181461040a578063060f58c31461045d5780630890c2741461048b57806308ea7bea146104a257600080fd5b3661040557005b600080fd5b34801561041657600080fd5b506104406104253660046148a1565b60a0602052600090815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561046957600080fd5b5061047d6104783660046148a1565b610ef9565b604051908152602001610454565b34801561049757600080fd5b506305f5e10061047d565b61047d6104b03660046148be565b6110b9565b3480156104c157600080fd5b506104d56104d036600461495b565b61116d565b60405161045492919061499c565b3480156104ef57600080fd5b506105036104fe366004614a20565b6112b5565b005b34801561051157600080fd5b50610525610520366004614a39565b6112ed565b604051610454929190614ad2565b34801561053f57600080fd5b5061052561054e366004614af9565b6113c4565b34801561055f57600080fd5b5061057f61056e3660046148a1565b6001600160a01b03163b6103b11490565b6040519015158152602001610454565b34801561059b57600080fd5b506105036105aa3660046148a1565b61149d565b3480156105bb57600080fd5b506105036105ca366004614b61565b6114e9565b3480156105db57600080fd5b5060a754610440906001600160a01b031681565b3480156105fb57600080fd5b5060a254610440906001600160a01b031681565b34801561061b57600080fd5b5061047d61062a366004614bcc565b611626565b34801561063b57600080fd5b5061044061064a3660046148a1565b611655565b34801561065b57600080fd5b5061044061066a366004614a20565b6116be565b34801561067b57600080fd5b5061050361068a366004614bf8565b6116e8565b34801561069b57600080fd5b506105036106aa366004614c1a565b611867565b3480156106bb57600080fd5b506104406106ca3660046148a1565b60a1602052600090815260409020546001600160a01b031681565b3480156106f157600080fd5b50610503610700366004614a20565b6118bc565b34801561071157600080fd5b5060a654610440906001600160a01b031681565b34801561073157600080fd5b50610503610740366004614c94565b61194f565b34801561075157600080fd5b50610875610760366004614a20565b6040805161018081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081018290526101408101829052610160810191909152506000908152609b6020908152604091829020825161018081018452815481526001820154928101929092526002810154928201929092526003820154606082015260048201546001600160a01b0390811660808301526005830154811660a0830152600683015480821660c0840152600160a01b900460ff16151560e0830152600783015461010083015260088301548116610120830152600983015416610140820152600a9091015461016082015290565b6040516104549190614cc9565b34801561088e57600080fd5b5061057f61089d3660046148a1565b609c6020526000908152604090205460ff1681565b3480156108be57600080fd5b506105036108cd3660046148a1565b6119ec565b3480156108de57600080fd5b5061047d6305f5e10081565b3480156108f657600080fd5b50609754610440906001600160a01b031681565b34801561091657600080fd5b50610503610925366004614bf8565b611a38565b34801561093657600080fd5b506105036109453660046148a1565b611acf565b34801561095657600080fd5b506104406109653660046148a1565b611b1b565b34801561097657600080fd5b5061047d6109853660046148a1565b611b78565b34801561099657600080fd5b506105036109a53660046148a1565b611bae565b3480156109b657600080fd5b50610503611bfa565b3480156109cb57600080fd5b50609854610440906001600160a01b031681565b3480156109eb57600080fd5b506109ff6109fa366004614a20565b611c30565b604051610454929190614da1565b348015610a1957600080fd5b5060a954610440906001600160a01b031681565b348015610a3957600080fd5b506033546001600160a01b0316610440565b348015610a5757600080fd5b50610440610a663660046148a1565b609f602052600090815260409020546001600160a01b031681565b348015610a8d57600080fd5b50610440610a9c366004614a20565b60a8602052600090815260409020546001600160a01b031681565b348015610ac357600080fd5b50610503610ad2366004614a20565b611d8e565b348015610ae357600080fd5b50610b66610af2366004614a20565b609b60205260009081526040902080546001820154600283015460038401546004850154600586015460068701546007880154600889015460098a0154600a909a015498999798969795966001600160a01b03958616969486169580851695600160a01b90950460ff16949281169216908c565b604080519c8d5260208d019b909b52998b019890985260608a01969096526001600160a01b0394851660808a015292841660a089015290831660c0880152151560e08701526101008601529081166101208501521661014083015261016082015261018001610454565b61047d610bde366004614eca565b611f0d565b348015610bef57600080fd5b5061047d609a5481565b348015610c0557600080fd5b50610503610c14366004614a39565b611fd9565b348015610c2557600080fd5b50610503610c34366004614f65565b6120cd565b348015610c4557600080fd5b5060ab54610440906001600160a01b031681565b348015610c6557600080fd5b5061047d60a55481565b348015610c7b57600080fd5b50610440610c8a3660046148a1565b6121f4565b348015610c9b57600080fd5b5061047d610caa3660046148a1565b60a46020526000908152604090205481565b61047d610cca366004614fa0565b61221f565b61047d610cdd36600461505a565b6122f5565b348015610cee57600080fd5b50610503610cfd3660046150ff565b6123ad565b348015610d0e57600080fd5b5061047d610d1d366004614a20565b612623565b348015610d2e57600080fd5b50610d42610d3d36600461495b565b6126b8565b604051610454919061514a565b348015610d5b57600080fd5b5060a354610440906001600160a01b031681565b348015610d7b57600080fd5b50610503610d8a3660046151a3565b61273e565b348015610d9b57600080fd5b50610503610daa3660046151fb565b6129a6565b348015610dbb57600080fd5b50610503610dca366004614b61565b612ee6565b348015610ddb57600080fd5b50610503610dea366004614bf8565b613033565b348015610dfb57600080fd5b50610503613115565b348015610e1057600080fd5b50610503610e1f3660046148a1565b61314b565b348015610e3057600080fd5b5060aa54610440906001600160a01b031681565b348015610e5057600080fd5b50610e64610e5f366004614a20565b613197565b60408051928352602083019190915201610454565b348015610e8557600080fd5b50610503610e943660046148a1565b6131c5565b348015610ea557600080fd5b50610e64610eb4366004614a20565b61325d565b348015610ec557600080fd5b50609954610440906001600160a01b031681565b348015610ee557600080fd5b50610503610ef4366004614a20565b61332b565b609954604051633437586b60e01b8152600260048201526001600160a01b038381166024830152600092169082908290633437586b90604401602060405180830381865afa158015610f4f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f73919061527a565b604051633437586b60e01b8152600360048201526001600160a01b038681166024830152919250600091841690633437586b90604401602060405180830381865afa158015610fc6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fea919061527a565b90506000610ff882846152a9565b609e5490915060005b8181101561107d576000609e828154811061101e5761101e6152c1565b906000526020600020906002020160010154905080841161106c57609e828154811061104c5761104c6152c1565b906000526020600020906002020160000154975050505050505050919050565b50611076816152d7565b9050611001565b50609e61108b6001836152f0565b8154811061109b5761109b6152c1565b90600052602060002090600202016000015495505050505050919050565b60006002606554036110e65760405162461bcd60e51b81526004016110dd90615307565b60405180910390fd5b600260658190555061115d604051806101000160405280878152602001896001600160a01b03168152602001878152602001896001600160a01b03168152602001886001600160a01b031681526020018681526020018581526020018481525060016040518060200160405280600081525061335a565b6001606555979650505050505050565b606080826001600160401b0381111561118857611188614dbc565b6040519080825280602002602001820160405280156111b1578160200160208202803683370190505b509150826001600160401b038111156111cc576111cc614dbc565b6040519080825280602002602001820160405280156111f5578160200160208202803683370190505b50905060005b838110156112ad57600061122f86868481811061121a5761121a6152c1565b905060200201602081019061098591906148a1565b905080838381518110611244576112446152c1565b602090810291909101810191909152600082815260a8909152604090205484516001600160a01b0390911690859084908110611282576112826152c1565b6001600160a01b039092166020928302919091019091015250806112a5816152d7565b9150506111fb565b509250929050565b6033546001600160a01b031633146112df5760405162461bcd60e51b81526004016110dd9061533e565b6112ea609d82613573565b50565b60a9546000906060906001600160a01b03166113375760405162461bcd60e51b81526020600482015260096024820152681b9bc81b585c9ad95d60ba1b60448201526064016110dd565b60a954604051630e36569360e31b81526001600160a01b038781166004830152868116602483015260448201869052909116906371b2b49890606401600060405180830381865afa158015611390573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526113b891908101906153b8565b91509150935093915050565b60a9546000906060906001600160a01b031661140e5760405162461bcd60e51b81526020600482015260096024820152681b9bc81b585c9ad95d60ba1b60448201526064016110dd565b60a954604051630e36569360e31b81526001600160a01b038981166004830152888116602483015260448201889052909116906371b2b49890606401600060405180830381865afa158015611467573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261148f91908101906153b8565b915091509550959350505050565b6033546001600160a01b031633146114c75760405162461bcd60e51b81526004016110dd9061533e565b60ab80546001600160a01b0319166001600160a01b0392909216919091179055565b6033546001600160a01b031633146115135760405162461bcd60e51b81526004016110dd9061533e565b8281146115845760405162461bcd60e51b815260206004820152603960248201527f706f6f6c7320616e6420616461707465727320706172616d65746572732073686044820152780deead8c840d0c2ecca40e8d0ca40e6c2daca40d8cadccee8d603b1b60648201526084016110dd565b60005b63ffffffff811684111561161f5760006115b286868463ffffffff1681811061121a5761121a6152c1565b905083838363ffffffff168181106115cc576115cc6152c1565b90506020020160208101906115e191906148a1565b600091825260a8602052604090912080546001600160a01b0319166001600160a01b0390921691909117905580611617816153fe565b915050611587565b5050505050565b60008061163284610ef9565b90506305f5e1006116438285615421565b61164d9190615440565b949350505050565b6001600160a01b03808216600090815260a06020526040812054909116806116b85760405162461bcd60e51b81526020600482015260166024820152751c1c9a58d9481bdc9858db19481b9bdd08199bdd5b9960521b60448201526064016110dd565b92915050565b609d81815481106116ce57600080fd5b6000918252602090912001546001600160a01b0316905081565b60026065540361170a5760405162461bcd60e51b81526004016110dd90615307565b600260655561171b828260006135ec565b6000828152609b60205260409081902060068101546008820154600583015493516375a5c47160e01b8152929373c7c1e3937502511f9f8c7eaebecfd5a492c56fd9936375a5c47193611783936001600160a01b039182169390821692911690600401615462565b602060405180830381865af41580156117a0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117c4919061527a565b506006810154600982015460058301546040516375a5c47160e01b815273c7c1e3937502511f9f8c7eaebecfd5a492c56fd9936375a5c4719361181b936001600160a01b0392831693918316921690600401615462565b602060405180830381865af4158015611838573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061185c919061527a565b505060016065555050565b6033546001600160a01b031633146118915760405162461bcd60e51b81526004016110dd9061533e565b6001600160a01b03919091166000908152609c60205260409020805460ff1916911515919091179055565b6033546001600160a01b031633146118e65760405162461bcd60e51b81526004016110dd9061533e565b609a5481101561194a5760405162461bcd60e51b815260206004820152602960248201527f6c61737420706f736974696f6e20696e646578206d6179206f6e6c79206265206044820152681a5b98dc99585cd95960ba1b60648201526084016110dd565b609a55565b60a2546001600160a01b03163314806119775750336000908152609c602052604090205460ff165b6119b35760405162461bcd60e51b815260206004820152600d60248201526c37b7363ca0baba37b6b0ba37b960991b60448201526064016110dd565b60006119c182840184615485565b90506119d081600001516136d7565b6119e7816000015182602001518360400151613888565b505050565b6033546001600160a01b03163314611a165760405162461bcd60e51b81526004016110dd9061533e565b60aa80546001600160a01b0319166001600160a01b0392909216919091179055565b6033546001600160a01b03163314611a625760405162461bcd60e51b81526004016110dd9061533e565b609e548210611aa65760405162461bcd60e51b815260206004820152601060248201526f0eee4dedcce40e0deded840d2dcc8caf60831b60448201526064016110dd565b80609e8381548110611aba57611aba6152c1565b60009182526020909120600290910201555050565b6033546001600160a01b03163314611af95760405162461bcd60e51b81526004016110dd9061533e565b60a280546001600160a01b0319166001600160a01b0392909216919091179055565b600080611b27836121f4565b90506001600160a01b0381166116b85760405162461bcd60e51b81526020600482015260166024820152751c1bdbdb081859185c1d195c881b9bdd08199bdd5b9960521b60448201526064016110dd565b6000816001600160a01b0316803b806020016040519081016040528181526000908060200190933c805160209091012092915050565b6033546001600160a01b03163314611bd85760405162461bcd60e51b81526004016110dd9061533e565b60a980546001600160a01b0319166001600160a01b0392909216919091179055565b6033546001600160a01b03163314611c245760405162461bcd60e51b81526004016110dd9061533e565b611c2e6000613bfc565b565b6000818152609b6020526040808220905163c178219160e01b815230600482015260248101829052606091908390839073b7d988106099eb535dfebc1e9c133c510fc1d6ef9063c178219190604401600060405180830381865af4158015611c9c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611cc49190810190615515565b919650925090508415611d865760006305f5e100846003015484611ce89190615421565b611cf29190615440565b611cfc90846152f0565b6040805160608101825289815260208082018490528183018690529151929350916326f864ff60e11b91611d3291849101615564565b60408051601f1981840301815290829052611d4f91602401615593565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152955050505b505050915091565b600260655403611db05760405162461bcd60e51b81526004016110dd90615307565b6002606555611dc281600060016135ec565b6000818152609b60205260409081902060068101546008820154600583015493516375a5c47160e01b8152929373c7c1e3937502511f9f8c7eaebecfd5a492c56fd9936375a5c47193611e2a936001600160a01b039182169390821692911690600401615462565b602060405180830381865af4158015611e47573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e6b919061527a565b506006810154600982015460058301546040516375a5c47160e01b815273c7c1e3937502511f9f8c7eaebecfd5a492c56fd9936375a5c47193611ec2936001600160a01b0392831693918316921690600401615462565b602060405180830381865af4158015611edf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f03919061527a565b5050600160655550565b6000600260655403611f315760405162461bcd60e51b81526004016110dd90615307565b6002606581905550611fc76040518061010001604052808881526020018b6001600160a01b03168152602001600081526020018a6001600160a01b03168152602001896001600160a01b03168152602001878152602001868152602001858152506003604051806020016040528086815250604051602001611fb391906155a6565b60405160208183030381529060405261335a565b60016065559998505050505050505050565b6033546001600160a01b031633146120035760405162461bcd60e51b81526004016110dd9061533e565b60006001600160a01b0384166120bc576000836001600160a01b03168360405160006040518083038185875af1925050503d8060008114612060576040519150601f19603f3d011682016040523d82523d6000602084013e612065565b606091505b50509050806120b65760405162461bcd60e51b815260206004820152601f60248201527f7472616e73666572546f3a20424e42207472616e73666572206661696c65640060448201526064016110dd565b506120c7565b6120c7848484613c4e565b50505050565b6002606554036120ef5760405162461bcd60e51b81526004016110dd90615307565b60026065556000858152609b60205260408082209051630c0262dd60e21b81523060048201526024810182905260448101889052606481018790526084810186905260a4810185905260c4810184905290919073b7d988106099eb535dfebc1e9c133c510fc1d6ef906330098b749060e401602060405180830381865af415801561217e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121a291906155c1565b905080156121ba576121b5876000613ca0565b6121e6565b60405187907fcc90f0f50af7b46bc39b1d2aa58c061cbdf99448ffb57e668528b50d75de938490600090a25b505060016065555050505050565b60008061220083611b78565b600090815260a860205260409020546001600160a01b03169392505050565b60006002606554036122435760405162461bcd60e51b81526004016110dd90615307565b60026065819055506122e06040518061010001604052808e81526020018d6001600160a01b031681526020018c81526020018b6001600160a01b031681526020018a6001600160a01b03168152602001898152602001888152602001878152508585858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061335a92505050565b60016065559c9b505050505050505050505050565b60006002606554036123195760405162461bcd60e51b81526004016110dd90615307565b600260658190555061239a6040518061010001604052808981526020018c6001600160a01b031681526020018881526020018b6001600160a01b031681526020018a6001600160a01b03168152602001878152602001868152602001858152506002604051806020016040528086815250604051602001611fb391906155a6565b60016065559a9950505050505050505050565b600054610100900460ff166123c85760005460ff16156123cc565b303b155b61242f5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016110dd565b600054610100900460ff16158015612451576000805461ffff19166101011790555b609980546001600160a01b038087166001600160a01b0319928316179092556098805486841690831617905560a2805492851692909116919091179055612496613dbd565b61249e613dec565b604080518082018252620186a08152683635c9adc5dea000006020808301918252609e8054600181810183556000838152955160029283027fcfe2a20ff701a1f3e14f63bd70d6c6bc6fba8172ec6d5a505cdab3927c0a9de68181019290925595517fcfe2a20ff701a1f3e14f63bd70d6c6bc6fba8172ec6d5a505cdab3927c0a9de7968701558751808901895262015f90815269010f0cf064dd5920000081870190815285548085018755868a5291519185028084019290925551908701558751808901895262013880815269021e19e0c9bab240000081870190815285548085018755868a52915191850280840192909255519087015587518089018952620111708152690a968163f0a57b40000081870190815285548085018755868a529151918502808401929092555190870155875180890190985261c35088526a084595161401484a000000948801948552835491820184559290955294519390940293840192909255905191015580156120c7576000805461ff001916905550505050565b6000818152609b6020526040812061263d838360016135ec565b600881015460068201546040516370a0823160e01b81526001600160a01b0391821660048201529116906370a0823190602401602060405180830381865afa15801561268d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126b1919061527a565b9392505050565b60405163d5e8466760e01b81526060907306b619a9be1d87a620bfa39d51e3408f87a3d5f69063d5e84667906126f9903090609b90889088906004016155de565b600060405180830381865af4158015612716573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526126b19190810190615633565b6002606554036127605760405162461bcd60e51b81526004016110dd90615307565b60026065556000838152609b60205260409020600981015460088201546001600160a01b039081169116146127d35760405162461bcd60e51b8152602060048201526019602482015278776974686472617720616c6c206f6e6c7920666f722041505960381b60448201526064016110dd565b6127e084600060016135ec565b600881015460068201546040516370a0823160e01b81526001600160a01b03918216600482015260009291909116906370a0823190602401602060405180830381865afa158015612835573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612859919061527a565b600683015460088401546040516375a5c47160e01b815292935073c7c1e3937502511f9f8c7eaebecfd5a492c56fd9926375a5c471926128ac926001600160a01b03918216929116903090600401615462565b602060405180830381865af41580156128c9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128ed919061527a565b50600882015460ab54604080516020810182528681529051637ee1b49d60e11b815260009373b7d988106099eb535dfebc1e9c133c510fc1d6ef9363fdc3693a9361294b9388936001600160a01b0390811693169190600401615706565b602060405180830381865af4158015612968573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061298c919061527a565b9050612999853383613c4e565b5050600160655550505050565b6002606554036129c85760405162461bcd60e51b81526004016110dd90615307565b60026065556040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101919091526000858152609b60205260409020600981015460088201546001600160a01b03908116911614612a7c5760405162461bcd60e51b8152602060048201526019602482015278776974686472617720616c6c206f6e6c7920666f722041505960381b60448201526064016110dd565b612a8986600060016135ec565b600881015460068201546040516370a0823160e01b81526001600160a01b0391821660048201529116906370a0823190602401602060405180830381865afa158015612ad9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612afd919061527a565b8252600681015460088201546040516375a5c47160e01b815273c7c1e3937502511f9f8c7eaebecfd5a492c56fd9926375a5c47192612b50926001600160a01b0392831692909116903090600401615462565b602060405180830381865af4158015612b6d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b91919061527a565b5060088101546001600160a01b031660208301819052825160405163a9059cbb60e01b815260048101839052602481019190915263a9059cbb906044016020604051808303816000875af1158015612bed573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c1191906155c1565b50602082015160405163226bf2d160e21b81523060048201526001600160a01b03909116906389afcb449060240160408051808303816000875af1158015612c5d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c819190615740565b83604001846060018281525082815250505081602001516001600160a01b0316630dfe16816040518163ffffffff1660e01b8152600401602060405180830381865afa158015612cd5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cf9919061576f565b82608001906001600160a01b031690816001600160a01b03168152505081602001516001600160a01b031663d21220a76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612d58573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d7c919061576f565b6001600160a01b0390811660a0840152604080840151608085015160ab5483516020810185528981529351637ee1b49d60e11b815273b7d988106099eb535dfebc1e9c133c510fc1d6ef9563fdc3693a95612ddf95949390911691600401615706565b602060405180830381865af4158015612dfc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612e20919061527a565b60c0830152606082015160a083015160ab54604080516020810182528781529051637ee1b49d60e11b815273b7d988106099eb535dfebc1e9c133c510fc1d6ef9463fdc3693a94612e8394919390926001600160a01b0390921691600401615706565b602060405180830381865af4158015612ea0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ec4919061527a565b60e0830181905260c08301516129999187913391612ee1916152a9565b613c4e565b6033546001600160a01b03163314612f105760405162461bcd60e51b81526004016110dd9061533e565b828114612f815760405162461bcd60e51b815260206004820152603960248201527f746f6b656e7320616e64206f7261636c657320706172616d65746572732073686044820152780deead8c840d0c2ecca40e8d0ca40e6c2daca40d8cadccee8d603b1b60648201526084016110dd565b60005b63ffffffff811684111561161f5782828263ffffffff16818110612faa57612faa6152c1565b9050602002016020810190612fbf91906148a1565b60a0600087878563ffffffff16818110612fdb57612fdb6152c1565b9050602002016020810190612ff091906148a1565b6001600160a01b039081168252602082019290925260400160002080546001600160a01b031916929091169190911790558061302b816153fe565b915050612f84565b6002606554036130555760405162461bcd60e51b81526004016110dd90615307565b60026065556000828152609b60205260409081902090516334763ceb60e21b815230600482015260248101829052604481018490526064810183905273b7d988106099eb535dfebc1e9c133c510fc1d6ef9063d1d8f3ac9060840160006040518083038186803b1580156130c857600080fd5b505af41580156130dc573d6000803e3d6000fd5b50506040518592507fcc90f0f50af7b46bc39b1d2aa58c061cbdf99448ffb57e668528b50d75de93849150600090a25050600160655550565b6033546001600160a01b0316331461313f5760405162461bcd60e51b81526004016110dd9061533e565b611c2e609d6000614848565b6033546001600160a01b031633146131755760405162461bcd60e51b81526004016110dd9061533e565b609980546001600160a01b0319166001600160a01b0392909216919091179055565b609e81815481106131a757600080fd5b60009182526020909120600290910201805460019091015490915082565b6033546001600160a01b031633146131ef5760405162461bcd60e51b81526004016110dd9061533e565b6001600160a01b0381166132545760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016110dd565b6112ea81613bfc565b6000818152609b602052604081208190613279848360016135ec565b604080516101808101825282548152600183015460208201526002830154918101919091526003820154606082015260048201546001600160a01b0390811660808301526005830154811660a0830152600683015480821660c0840152600160a01b900460ff16151560e0830152600783015461010083015260088301548116610120830152600983015416610140820152600a820154610160820152613321903090613e1b565b9250925050915091565b6033546001600160a01b031633146133555760405162461bcd60e51b81526004016110dd9061533e565b60a555565b600060a5543410156133a15760405162461bcd60e51b815260206004820152601060248201526f19d85cd5185b9ad51a1c995cda1bdb1960821b60448201526064016110dd565b609a80549060019060006133b583856152a9565b909155506000905073b7d988106099eb535dfebc1e9c133c510fc1d6ef6372337b0e306133e2609d61402a565b858a8a8a6040518763ffffffff1660e01b81526004016134079695949392919061578c565b61018060405180830381865af4158015613425573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134499190615829565b60a2549091506001600160a01b03161561347a57613466826140d9565b61016082015260c081015161347a906141a4565b6000828152609b6020908152604091829020835181559083015160018201559082015160028201556060820151600382015560808201516004820180546001600160a01b03199081166001600160a01b039384161790915560a0840151600584018054831691841691909117905560c084015160068401805460e08701519285166001600160a81b031990911617600160a01b92151592909202919091179055610100840151600784015561012084015160088401805483168285161790556101408501516009850180549093169316929092179055610160830151600a9092019190915561356a9083906141ed565b50949350505050565b60005b818110156119e7578260405161358b90614866565b604051809103906000f0801580156135a7573d6000803e3d6000fd5b5081546001810183556000928352602090922090910180546001600160a01b0319166001600160a01b03909216919091179055806135e4816152d7565b915050613576565b6000838152609b60205260408082209051631a33d63360e11b8152306004820152602481018290526044810186905260648101859052831515608482015290919073b7d988106099eb535dfebc1e9c133c510fc1d6ef90633467ac669060a401602060405180830381865af4158015613669573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061368d91906155c1565b905080156136a5576136a0856000613ca0565b61161f565b60405185907fcc90f0f50af7b46bc39b1d2aa58c061cbdf99448ffb57e668528b50d75de938490600090a25050505050565b60a25460408051635c08631b60e11b8152815160009384936001600160a01b039091169263b810c63692600480830193928290030181865afa158015613721573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061374591906158ed565b915091508160000361375657505050565b6001600160a01b03811673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1461377f57600080fd5b60a2546040805163573ea57560e01b815290516000926001600160a01b03169163573ea5759160048083019260209291908290030181865afa1580156137c9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137ed919061576f565b6000858152609b602052604090819020600601549051633928468360e21b81526001600160a01b03918216600482018190529183166024820152604481018690529192509073c7c1e3937502511f9f8c7eaebecfd5a492c56fd99063e4a11a0c9060640160006040518083038186803b15801561386957600080fd5b505af415801561387d573d6000803e3d6000fd5b505050505050505050565b6000838152609b6020526040902061389f816142f2565b6138d45760405162461bcd60e51b815260206004820152600660248201526534b9a7b832b760d11b60448201526064016110dd565b6006810154600482015473c7c1e3937502511f9f8c7eaebecfd5a492c56fd99163d4f778db916001600160a01b03918216916139109116611b1b565b60048501546008860154604080516001600160a01b0392831660208201529190921691016040516020818303038152906040526040518563ffffffff1660e01b81526004016139629493929190615912565b60006040518083038186803b15801561397a57600080fd5b505af415801561398e573d6000803e3d6000fd5b50505050600881015460068201546040516370a0823160e01b81526001600160a01b03918216600482015260009291909116906370a0823190602401602060405180830381865afa1580156139e7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613a0b919061527a565b6006830154600884015460a95460405163806d4a6560e01b81526001600160a01b03938416600482015291831660248301529190911660448201526064810182905290915073c7c1e3937502511f9f8c7eaebecfd5a492c56fd99063806d4a659060840160006040518083038186803b158015613a8757600080fd5b505af4158015613a9b573d6000803e3d6000fd5b505050600683015460a954600885015460985460008a8152609b60205260409081902060050154905163038a83d760e61b815273c7c1e3937502511f9f8c7eaebecfd5a492c56fd9965063e2a0f5c095613b17956001600160a01b03918216959082169490821693908216928a928e9216908d90600401615950565b602060405180830381865af4158015613b34573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613b58919061527a565b506006820154600983015460058401546040516375a5c47160e01b815273c7c1e3937502511f9f8c7eaebecfd5a492c56fd9936375a5c47193613baf936001600160a01b0392831693918316921690600401615462565b602060405180830381865af4158015613bcc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613bf0919061527a565b5061161f856001613ca0565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526119e790849061431f565b6000828152609b60205260409020600681018054600160a01b60ff60a01b198216179091556001600160a01b03163b6103b103613d4c5760068101546005820154613cf7916001600160a01b0390811691166143f1565b6006810154609d80546001810182556000919091527fd26e832454299e9fabb89e0e5fffdc046d4e14431bc1bf607ffb2e8a1ddecf7b0180546001600160a01b0319166001600160a01b039092169190911790555b613d5981600a0154614468565b6000826001811115613d6d57613d6d6159b2565b03613d8b576008810154613d8b9084906001600160a01b03166144e0565b6001826001811115613d9f57613d9f6159b2565b036119e75760088101546119e79084906001600160a01b03166145db565b600054610100900460ff16613de45760405162461bcd60e51b81526004016110dd906159c8565b611c2e6146d6565b600054610100900460ff16613e135760405162461bcd60e51b81526004016110dd906159c8565b611c2e614706565b61012081015160c08201516040516370a0823160e01b81526001600160a01b039182166004820152600092839283929116906370a0823190602401602060405180830381865afa158015613e73573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613e97919061527a565b60c08501516101208601516040516375a5c47160e01b815292935073c7c1e3937502511f9f8c7eaebecfd5a492c56fd9926375a5c47192613ee8926001600160a01b03909116918a90600401615462565b602060405180830381865af4158015613f05573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613f29919061527a565b5061012084015160405163a9059cbb60e01b81526001600160a01b0390911660048201819052602482018390529063a9059cbb906044016020604051808303816000875af1158015613f7f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613fa391906155c1565b5061012084015160405163226bf2d160e21b81526001600160a01b03878116600483015260009283929116906389afcb449060240160408051808303816000875af1158015613ff6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061401a9190615740565b90955093505050505b9250929050565b805460009081036140645760405161404190614866565b604051809103906000f08015801561405d573d6000803e3d6000fd5b5092915050565b81546000908390614077906001906152f0565b81548110614087576140876152c1565b60009182526020909120015483546001600160a01b0390911691508390806140b1576140b1615a13565b600082815260209020810160001990810180546001600160a01b031916905501905592915050565b60a25460408051602480820185905282518083039091018152604490910182526020810180516001600160e01b0316630f1bef3f60e31b179052905163b9f45adb60e01b81526000926001600160a01b03169163b9f45adb916141619130916326f864ff60e11b91839173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee90600401615a29565b6020604051808303816000875af1158015614180573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116b8919061527a565b6040516001600160a01b038216903490600081818185875af1925050503d80600081146120c7576040519150601f19603f3d011682016040523d82523d6000602084013e6120c7565b60405182907f9202cfc9495fc0711f73c9f67aaecb6d71a0c079d364042750a1e52649c8bffd90600090a2604051630f711e5960e31b81523060048201526001600160a01b0382166024820152600090819073b7d988106099eb535dfebc1e9c133c510fc1d6ef90637b88f2c8906044016040805180830381865af415801561427a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061429e9190615a7c565b604080514281526020810184905260ff831691810191909152919350915084907f62f9bf33269004cdaa0d821e895de6093848d20e11ed1e17a1751055b8478b07906060015b60405180910390a250505050565b6006810154600090600160a01b900460ff161580156116b8575050600501546001600160a01b0316151590565b6000614374826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166147349092919063ffffffff16565b8051909150156119e7578080602001905181019061439291906155c1565b6119e75760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016110dd565b6040516326c9eb9b60e01b81526001600160a01b0383811660048301528216602482015273c7c1e3937502511f9f8c7eaebecfd5a492c56fd9906326c9eb9b9060440160006040518083038186803b15801561444c57600080fd5b505af4158015614460573d6000803e3d6000fd5b505050505050565b60a2546001600160a01b03161580159061448157508015155b156112ea5760a25460405163ee8ca3b560e01b8152600481018390526001600160a01b039091169063ee8ca3b590602401600060405180830381600087803b1580156144cc57600080fd5b505af115801561161f573d6000803e3d6000fd5b60405182907fc355243915beadf3078705397474fc7917bc9eeb17c5a863d8d2c9e139c7d2ab90600090a2604051630f711e5960e31b81523060048201526001600160a01b0382166024820152600090819073b7d988106099eb535dfebc1e9c133c510fc1d6ef90637b88f2c8906044016040805180830381865af415801561456d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906145919190615a7c565b604080514281526020810184905260ff831691810191909152919350915084907ff4f7bb1dfcbb667f0e89fb72a12238c23b52d73754aea82a7c17e6db85888e73906060016142e4565b60405182907fc355243915beadf3078705397474fc7917bc9eeb17c5a863d8d2c9e139c7d2ab90600090a2604051630f711e5960e31b81523060048201526001600160a01b0382166024820152600090819073b7d988106099eb535dfebc1e9c133c510fc1d6ef90637b88f2c8906044016040805180830381865af4158015614668573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061468c9190615a7c565b604080514281526020810184905260ff831691810191909152919350915084907ff6b9e8ef537f48782b0a0d76f2585ae57571a2e4d83b1e7a2835e2c48b8013de906060016142e4565b600054610100900460ff166146fd5760405162461bcd60e51b81526004016110dd906159c8565b611c2e33613bfc565b600054610100900460ff1661472d5760405162461bcd60e51b81526004016110dd906159c8565b6001606555565b606061164d8484600085856001600160a01b0385163b6147965760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016110dd565b600080866001600160a01b031685876040516147b29190615aa7565b60006040518083038185875af1925050503d80600081146147ef576040519150601f19603f3d011682016040523d82523d6000602084013e6147f4565b606091505b509150915061480482828661480f565b979650505050505050565b6060831561481e5750816126b1565b82511561482e5782518084602001fd5b8160405162461bcd60e51b81526004016110dd9190615593565b50805460008255906000526020600020908101906112ea9190614873565b6103e780615ac483390190565b5b808211156148885760008155600101614874565b5090565b6001600160a01b03811681146112ea57600080fd5b6000602082840312156148b357600080fd5b81356126b18161488c565b60008060008060008060c087890312156148d757600080fd5b86356148e28161488c565b955060208701356148f28161488c565b95989597505050506040840135936060810135936080820135935060a0909101359150565b60008083601f84011261492957600080fd5b5081356001600160401b0381111561494057600080fd5b6020830191508360208260051b850101111561402357600080fd5b6000806020838503121561496e57600080fd5b82356001600160401b0381111561498457600080fd5b61499085828601614917565b90969095509350505050565b604080825283519082018190526000906020906060840190828701845b828110156149de5781516001600160a01b0316845292840192908401906001016149b9565b5050508381038285015284518082528583019183019060005b81811015614a13578351835292840192918401916001016149f7565b5090979650505050505050565b600060208284031215614a3257600080fd5b5035919050565b600080600060608486031215614a4e57600080fd5b8335614a598161488c565b92506020840135614a698161488c565b929592945050506040919091013590565b60005b83811015614a95578181015183820152602001614a7d565b838111156120c75750506000910152565b60008151808452614abe816020860160208601614a7a565b601f01601f19169290920160200192915050565b82815260406020820152600061164d6040830184614aa6565b80151581146112ea57600080fd5b600080600080600060a08688031215614b1157600080fd5b8535614b1c8161488c565b94506020860135614b2c8161488c565b9350604086013592506060860135614b4381614aeb565b91506080860135614b5381614aeb565b809150509295509295909350565b60008060008060408587031215614b7757600080fd5b84356001600160401b0380821115614b8e57600080fd5b614b9a88838901614917565b90965094506020870135915080821115614bb357600080fd5b50614bc087828801614917565b95989497509550505050565b60008060408385031215614bdf57600080fd5b8235614bea8161488c565b946020939093013593505050565b60008060408385031215614c0b57600080fd5b50508035926020909101359150565b60008060408385031215614c2d57600080fd5b8235614c388161488c565b91506020830135614c4881614aeb565b809150509250929050565b60008083601f840112614c6557600080fd5b5081356001600160401b03811115614c7c57600080fd5b60208301915083602082850101111561402357600080fd5b60008060208385031215614ca757600080fd5b82356001600160401b03811115614cbd57600080fd5b61499085828601614c53565b600061018082019050825182526020830151602083015260408301516040830152606083015160608301526080830151614d0e60808401826001600160a01b03169052565b5060a0830151614d2960a08401826001600160a01b03169052565b5060c0830151614d4460c08401826001600160a01b03169052565b5060e0830151614d5860e084018215159052565b506101008381015190830152610120808401516001600160a01b038116828501525050610140838101516001600160a01b03811684830152505061016092830151919092015290565b821515815260406020820152600061164d6040830184614aa6565b634e487b7160e01b600052604160045260246000fd5b604051606081016001600160401b0381118282101715614df457614df4614dbc565b60405290565b60405161018081016001600160401b0381118282101715614df457614df4614dbc565b604051601f8201601f191681016001600160401b0381118282101715614e4557614e45614dbc565b604052919050565b60006001600160401b03821115614e6657614e66614dbc565b50601f01601f191660200190565b600082601f830112614e8557600080fd5b8135614e98614e9382614e4d565b614e1d565b818152846020838601011115614ead57600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080600080600080610100898b031215614ee757600080fd5b8835614ef28161488c565b97506020890135614f028161488c565b96506040890135614f128161488c565b9550606089013594506080890135935060a0890135925060c0890135915060e08901356001600160401b03811115614f4957600080fd5b614f558b828c01614e74565b9150509295985092959890939650565b600080600080600060a08688031215614f7d57600080fd5b505083359560208501359550604085013594606081013594506080013592509050565b60008060008060008060008060008060006101408c8e031215614fc257600080fd5b8b359a5060208c0135614fd48161488c565b995060408c0135985060608c0135614feb8161488c565b975060808c0135614ffb8161488c565b965060a08c0135955060c08c0135945060e08c013593506101008c013592506101208c01356001600160401b0381111561503457600080fd5b6150408e828f01614c53565b915080935050809150509295989b509295989b9093969950565b60008060008060008060008060006101208a8c03121561507957600080fd5b89356150848161488c565b985060208a01356150948161488c565b975060408a01356150a48161488c565b965060608a0135955060808a0135945060a08a0135935060c08a0135925060e08a013591506101008a01356001600160401b038111156150e357600080fd5b6150ef8c828d01614e74565b9150509295985092959850929598565b60008060006060848603121561511457600080fd5b833561511f8161488c565b9250602084013561512f8161488c565b9150604084013561513f8161488c565b809150509250925092565b602080825282518282018190526000919060409081850190868401855b828110156151965781518051855286810151878601528501518585015260609093019290850190600101615167565b5091979650505050505050565b6000806000606084860312156151b857600080fd5b8335925060208401356151ca8161488c565b915060408401356001600160401b038111156151e557600080fd5b6151f186828701614e74565b9150509250925092565b6000806000806080858703121561521157600080fd5b8435935060208501356152238161488c565b925060408501356001600160401b038082111561523f57600080fd5b61524b88838901614e74565b9350606087013591508082111561526157600080fd5b5061526e87828801614e74565b91505092959194509250565b60006020828403121561528c57600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b600082198211156152bc576152bc615293565b500190565b634e487b7160e01b600052603260045260246000fd5b6000600182016152e9576152e9615293565b5060010190565b60008282101561530257615302615293565b500390565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b600082601f83011261538457600080fd5b8151615392614e9382614e4d565b8181528460208386010111156153a757600080fd5b61164d826020830160208701614a7a565b600080604083850312156153cb57600080fd5b8251915060208301516001600160401b038111156153e857600080fd5b6153f485828601615373565b9150509250929050565b600063ffffffff80831681810361541757615417615293565b6001019392505050565b600081600019048311821515161561543b5761543b615293565b500290565b60008261545d57634e487b7160e01b600052601260045260246000fd5b500490565b6001600160a01b0393841681529183166020830152909116604082015260600190565b60006020828403121561549757600080fd5b81356001600160401b03808211156154ae57600080fd5b90830190606082860312156154c257600080fd5b6154ca614dd2565b82358152602083013560208201526040830135828111156154ea57600080fd5b6154f687828601614e74565b60408301525095945050505050565b805161551081614aeb565b919050565b60008060006060848603121561552a57600080fd5b835161553581614aeb565b6020850151604086015191945092506001600160401b0381111561555857600080fd5b6151f186828701615373565b6020815281516020820152602082015160408201526000604083015160608084015261164d6080840182614aa6565b6020815260006126b16020830184614aa6565b602081526000825160208084015261164d6040840182614aa6565b6000602082840312156155d357600080fd5b81516126b181614aeb565b6001600160a01b038516815260208101849052606060408201819052810182905260006001600160fb1b0383111561561557600080fd5b8260051b808560808501376000920160800191825250949350505050565b6000602080838503121561564657600080fd5b82516001600160401b038082111561565d57600080fd5b818501915085601f83011261567157600080fd5b81518181111561568357615683614dbc565b615691848260051b01614e1d565b818152848101925060609182028401850191888311156156b057600080fd5b938501935b828510156156fa5780858a0312156156cd5760008081fd5b6156d5614dd2565b85518152868601518782015260408087015190820152845293840193928501926156b5565b50979650505050505050565b848152600060018060a01b0380861660208401528085166040840152506080606083015282516020608084015261480460a0840182614aa6565b6000806040838503121561575357600080fd5b505080516020909101519092909150565b80516155108161488c565b60006020828403121561578157600080fd5b81516126b18161488c565b60006101a060018060a01b03808a168452808916602085015287604085015286516060850152806020880151166080850152604087015160a08501528060608801511660c08501528060808801511660e08501525060a086015161010084015260c086015161012084015260e0860151610140840152846101608401528061018084015261581c81840185614aa6565b9998505050505050505050565b6000610180828403121561583c57600080fd5b615844614dfa565b8251815260208301516020820152604083015160408201526060830151606082015261587260808401615764565b608082015261588360a08401615764565b60a082015261589460c08401615764565b60c08201526158a560e08401615505565b60e082015261010083810151908201526101206158c3818501615764565b908201526101406158d5848201615764565b90820152610160928301519281019290925250919050565b6000806040838503121561590057600080fd5b825191506020830151614c488161488c565b6001600160a01b03858116825284811660208301528316604082015260806060820181905260009061594690830184614aa6565b9695505050505050565b6001600160a01b0389811682528881166020830152878116604083015286811660608301526080820186905260a08201859052831660c082015261010060e082018190526000906159a383820185614aa6565b9b9a5050505050505050505050565b634e487b7160e01b600052602160045260246000fd5b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b634e487b7160e01b600052603160045260246000fd5b6001600160a01b0386811682526001600160e01b031986166020830152848116604083015260a06060830181905260009190615a6790840186614aa6565b91508084166080840152509695505050505050565b60008060408385031215615a8f57600080fd5b82519150602083015160ff81168114614c4857600080fd5b60008251615ab9818460208701614a7a565b919091019291505056fe60a060405234801561001057600080fd5b50336080526080516103b161003660003960008181609f01526101ad01526103b16000f3fe60806040526004361061002d5760003560e01c806320eb0c9e14610039578063a9059cbb1461007057600080fd5b3661003457005b600080fd5b34801561004557600080fd5b5061005961005436600461024d565b610090565b6040516100679291906102e2565b60405180910390f35b34801561007c57600080fd5b5061005961008b366004610341565b61019e565b60006060336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146100c957600080fd5b851561013357846001600160a01b031684846040516100e992919061036b565b600060405180830381855af49150503d8060008114610124576040519150601f19603f3d011682016040523d82523d6000602084013e610129565b606091505b5091509150610195565b846001600160a01b0316848460405161014d92919061036b565b6000604051808303816000865af19150503d806000811461018a576040519150601f19603f3d011682016040523d82523d6000602084013e61018f565b606091505b50915091505b94509492505050565b60006060336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146101d757600080fd5b6040516001600160a01b038516908490600081818185875af1925050503d8060008114610220576040519150601f19603f3d011682016040523d82523d6000602084013e610225565b606091505b50915091509250929050565b80356001600160a01b038116811461024857600080fd5b919050565b6000806000806060858703121561026357600080fd5b8435801515811461027357600080fd5b935061028160208601610231565b9250604085013567ffffffffffffffff8082111561029e57600080fd5b818701915087601f8301126102b257600080fd5b8135818111156102c157600080fd5b8860208285010111156102d357600080fd5b95989497505060200194505050565b821515815260006020604081840152835180604085015260005b81811015610318578581018301518582016060015282016102fc565b8181111561032a576000606083870101525b50601f01601f191692909201606001949350505050565b6000806040838503121561035457600080fd5b61035d83610231565b946020939093013593505050565b818382376000910190815291905056fea2646970667358221220c75971c3152323ad02e9dfbeb59ac913ad86ef6be72f6f346b2582ccbeae540a64736f6c634300080d0033a2646970667358221220fdb4875c35dc344bf72d7c9b1c8438abdcea14244f93698019a0f5a30f1a6f7464736f6c634300080d0033

Block Transaction Gas Used Reward
Age Block Fee Address BC Fee Address Voting Power Jailed Incoming
Block Uncle Number Difficulty Gas Used Reward
Loading
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.