ETH Price: $1,990.56 (-1.44%)

Contract

0x55FA902FDE96d1065F46F9b19745fc4Ebf5bFEa2
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
TradeLogic

Compiler Version
v0.8.9+commit.e5eed63a

Optimization Enabled:
Yes with 100 runs

Other Settings:
default evmVersion
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "../../types/DataTypes.sol";
import "../../configuration/UserConfiguration.sol";
import "../math/MathUtils.sol";
import "./ReserveLogic.sol";
import "./ReservePoolLogic.sol";
import "./GeneralLogic.sol";
import "./ValidationLogic.sol";
import "../storage/LedgerStorage.sol";

library TradeLogic {
    using MathUtils for uint256;
    using MathUtils for int256;
    using SafeERC20Upgradeable for IERC20Upgradeable;
    using ReserveLogic for DataTypes.ReserveData;
    using UserConfiguration for DataTypes.UserConfiguration;

    uint256 public constant VERSION = 4;

    event Trade(address indexed user, address indexed shortAsset, address indexed longAsset, uint256 soldAmount, uint256 boughtAmount, bytes data, uint256 shortAssetPrice, uint256 longAssetPrice);

    struct ExecuteTradeVars {
        DataTypes.AssetConfig shortAssetConfig;
        DataTypes.AssetConfig longAssetConfig;
        DataTypes.ReserveDataCache shortReserveCache;
        DataTypes.ReserveDataCache longReserveCache;
        DataTypes.ProtocolConfig protocolConfig;
        DataTypes.UserLiquidity currUserLiquidity;
        DataTypes.UserLiquidityCachedData cachedData;
        uint256 shortReservePid;
        uint256 longReservePid;
        uint256 receivedAmount;
        uint256 currShortReserveAvailableSupply;
        uint256 shortAssetPrice;
        uint256 shortAssetPriceDecimals;
        uint256 longAssetPrice;
        uint256 longAssetPriceDecimals;
        uint256 maxSellableAmount;
        uint256 maxBorrowableUsd;
        uint256 additionalSellableUsdFromSelling;
        uint256 additionalSellableUsdFromBuying;
        uint256 maxSellableUsd;
    }

    function executeTrade(
        address user,
        address shortAsset,
        address longAsset,
        uint256 amount,
        bytes memory data
    ) external {
        uint256 userLastTradeBlock = LedgerStorage.getMappingStorage().userLastTradeBlock[user];

        ExecuteTradeVars memory vars;

        vars.protocolConfig = LedgerStorage.getProtocolConfig();

        vars.shortReservePid = LedgerStorage.getReserveStorage().reservesList[shortAsset];
        vars.longReservePid = LedgerStorage.getReserveStorage().reservesList[longAsset];

        DataTypes.ReserveData storage shortReserve = LedgerStorage.getReserveStorage().reserves[vars.shortReservePid];
        DataTypes.ReserveData storage longReserve = LedgerStorage.getReserveStorage().reserves[vars.longReservePid];

        shortReserve.updateIndex();
        longReserve.updateIndex();

        vars.shortAssetConfig = LedgerStorage.getAssetStorage().assetConfigs[shortAsset];
        vars.longAssetConfig = LedgerStorage.getAssetStorage().assetConfigs[longAsset];

        (
        vars.currUserLiquidity,
        vars.cachedData
        ) = GeneralLogic.getUserLiquidity(
            user,
            shortAsset,
            longAsset
        );

        vars.shortReserveCache = shortReserve.cache();
        vars.longReserveCache = longReserve.cache();

        (vars.currShortReserveAvailableSupply,,,,) = shortReserve.getReserveSupplies();

        if (vars.cachedData.shortingPrice == 0) {
            (vars.shortAssetPrice, vars.shortAssetPriceDecimals) = vars.shortAssetConfig.oracle.getAssetPrice(shortAsset);
        } else {
            vars.shortAssetPrice = vars.cachedData.shortingPrice;
            vars.shortAssetPriceDecimals = vars.cachedData.shortingPriceDecimals;
        }

        if (vars.cachedData.longingPrice == 0) {
            (vars.longAssetPrice, vars.longAssetPriceDecimals) = vars.longAssetConfig.oracle.getAssetPrice(longAsset);
        } else {
            vars.longAssetPrice = vars.cachedData.longingPrice;
            vars.longAssetPriceDecimals = vars.cachedData.longingPriceDecimals;
        }

        vars.maxBorrowableUsd = vars.currUserLiquidity.availableLeverageUsd > 0
        ? uint256(vars.currUserLiquidity.availableLeverageUsd)
        : 0;

        // has value if selling asset is a long position
        vars.additionalSellableUsdFromSelling = vars.cachedData.currShortingPosition > 0
        ? GeneralLogic.getAssetUsdFromAmount(
            uint256(vars.cachedData.currShortingPosition),
            vars.shortAssetConfig.decimals,
            vars.shortAssetPrice,
            vars.shortAssetPriceDecimals
        )
        : 0;

        // has value if buying asset is a short position
        vars.additionalSellableUsdFromBuying = vars.cachedData.currLongingPosition < 0
        ? GeneralLogic.getAssetUsdFromAmount(
            uint256((vars.cachedData.currLongingPosition * (- 1))), // make it positive
            vars.longAssetConfig.decimals,
            vars.longAssetPrice,
            vars.longAssetPriceDecimals
        )
        : 0;

        vars.maxSellableUsd = vars.maxBorrowableUsd + vars.additionalSellableUsdFromSelling + vars.additionalSellableUsdFromBuying;

        vars.maxSellableAmount = GeneralLogic.getAssetAmountFromUsd(
            vars.maxSellableUsd,
            vars.shortAssetConfig.decimals,
            vars.shortAssetPrice,
            vars.shortAssetPriceDecimals
        );

        ValidationLogic.validateTrade(
            shortReserve,
            longReserve,
            vars.cachedData.currShortingPosition,
            DataTypes.ValidateTradeParams(
                user,
                amount,
                vars.currShortReserveAvailableSupply,
                vars.maxSellableAmount,
                userLastTradeBlock
            )
        );

        // update reserve data
        executeShorting(
            shortReserve,
            vars.shortAssetConfig,
            vars.cachedData.currShortingPosition,
            amount,
            true
        );

        // update user data
        IUserData(vars.protocolConfig.userData).changePosition(
            user,
            vars.shortReservePid,
            int256(amount) * (- 1),
            vars.shortReserveCache.currBorrowIndexRay,
            vars.shortAssetConfig.decimals
        );

        shortReserve.postUpdateReserveData();

        amount -= transferTradeFee(shortAsset, vars.protocolConfig.treasury, vars.protocolConfig.tradeFeeMantissa, amount);

        vars.receivedAmount = swap(vars.shortAssetConfig, shortAsset, longAsset, amount, data);

        uint256 increasedShortUsd = GeneralLogic.getAssetUsdFromAmount(
            amount,
            vars.shortAssetConfig.decimals,
            vars.shortAssetPrice,
            vars.shortAssetPriceDecimals
        );

        uint256 increasedLongUsd = GeneralLogic.getAssetUsdFromAmount(
            vars.receivedAmount,
            vars.longAssetConfig.decimals,
            vars.longAssetPrice,
            vars.longAssetPriceDecimals
        );

        vars.currUserLiquidity.pnlUsd += (int256(increasedLongUsd) - int256(increasedShortUsd));

        require(
            GeneralLogic.isLiquidatable(
                vars.currUserLiquidity.totalCollateralUsdPreLtv,
                vars.protocolConfig.liquidationRatioMantissa,
                vars.currUserLiquidity.pnlUsd
            ) == false,
            Errors.BAD_TRADE
        );

        // update reserve data
        executeLonging(
            longReserve,
            vars.longAssetConfig,
            vars.protocolConfig.treasury,
            vars.cachedData.currLongingPosition,
            vars.receivedAmount,
            true
        );

        // update user data
        IUserData(vars.protocolConfig.userData).changePosition(
            user,
            vars.longReservePid,
            int256(vars.receivedAmount),
            vars.longReserveCache.currBorrowIndexRay,
            vars.longAssetConfig.decimals
        );

        longReserve.postUpdateReserveData();

        LedgerStorage.getMappingStorage().userLastTradeBlock[user] = block.number;

        emit Trade(
            user,
            shortAsset,
            longAsset,
            amount,
            vars.receivedAmount,
            data,
            vars.shortAssetPrice,
            vars.longAssetPrice
        );
    }

    struct LiquidationTradeVars {
        DataTypes.ProtocolConfig protocolConfig;
        DataTypes.AssetConfig shortAssetConfig;
        DataTypes.AssetConfig longAssetConfig;
        DataTypes.ReserveDataCache shortReserveCache;
        DataTypes.ReserveDataCache longReserveCache;
        uint256 shortReservePid;
        uint256 longReservePid;
        int256 userShortPosition;
        int256 userLongPosition;
        uint256 amountShorted;
        uint256 maxAmountToShort;
        uint256 shortAssetPrice;
        uint256 shortAssetDecimals;
        uint256 longAssetPrice;
        uint256 longAssetDecimals;
        uint256 receivedAmount;
    }

    function liquidationTrade(
        address shortAsset,
        address longAsset,
        uint256 amount,
        bytes memory data
    ) external {
        LiquidationTradeVars memory vars;

        vars.protocolConfig = LedgerStorage.getProtocolConfig();

        vars.shortAssetConfig = LedgerStorage.getAssetStorage().assetConfigs[shortAsset];
        vars.longAssetConfig = LedgerStorage.getAssetStorage().assetConfigs[longAsset];

        vars.shortReservePid = LedgerStorage.getReserveStorage().reservesList[shortAsset];
        vars.longReservePid = LedgerStorage.getReserveStorage().reservesList[longAsset];

        DataTypes.ReserveData storage shortReserve = LedgerStorage.getReserveStorage().reserves[vars.shortReservePid];
        DataTypes.ReserveData storage longReserve = LedgerStorage.getReserveStorage().reserves[vars.longReservePid];

        shortReserve.updateIndex();
        longReserve.updateIndex();

        vars.shortReserveCache = shortReserve.cache();
        vars.longReserveCache = longReserve.cache();

        (vars.shortAssetPrice, vars.shortAssetDecimals) = vars.shortAssetConfig.oracle.getAssetPrice(shortAsset);
        (vars.longAssetPrice, vars.longAssetDecimals) = vars.longAssetConfig.oracle.getAssetPrice(longAsset);

        vars.userShortPosition = IUserData(vars.protocolConfig.userData).getUserPosition(DataTypes.LIQUIDATION_WALLET, shortAsset);
        vars.userLongPosition = IUserData(vars.protocolConfig.userData).getUserPosition(DataTypes.LIQUIDATION_WALLET, longAsset);

        (, vars.amountShorted) = executeShorting(
            shortReserve,
            vars.shortAssetConfig,
            vars.userShortPosition,
            amount,
            false
        );

        if (vars.amountShorted > 0) {
            require(vars.shortAssetConfig.kind == DataTypes.AssetKind.SingleStable, Errors.INVALID_ASSET_INPUT);

            vars.maxAmountToShort = GeneralLogic.getAssetAmountFromUsd(
                GeneralLogic.getAssetUsdFromAmount(
                    vars.userLongPosition.abs(),
                    vars.longAssetConfig.decimals,
                    vars.longAssetPrice,
                    vars.longAssetDecimals
                ),
                vars.shortAssetConfig.decimals,
                vars.shortAssetPrice,
                vars.shortAssetDecimals
            ).unitToWad(vars.shortAssetConfig.decimals)
            .wadMul(vars.protocolConfig.swapBufferLimitPercentage)
            .wadToUnit(vars.shortAssetConfig.decimals);

            require(vars.amountShorted <= vars.maxAmountToShort, Errors.INVALID_AMOUNT_INPUT);
        }

        IUserData(vars.protocolConfig.userData).changePosition(
            DataTypes.LIQUIDATION_WALLET,
            vars.shortReservePid,
            int256(amount) * (- 1),
            vars.shortReserveCache.currBorrowIndexRay,
            vars.shortAssetConfig.decimals
        );

        shortReserve.postUpdateReserveData();

        vars.receivedAmount = swap(vars.shortAssetConfig, shortAsset, longAsset, amount, data);

        executeLonging(
            longReserve,
            vars.longAssetConfig,
            vars.protocolConfig.treasury,
            vars.userLongPosition,
            vars.receivedAmount,
            false
        );

        IUserData(vars.protocolConfig.userData).changePosition(
            DataTypes.LIQUIDATION_WALLET,
            vars.longReservePid,
            int256(vars.receivedAmount),
            vars.longReserveCache.currBorrowIndexRay,
            vars.longAssetConfig.decimals
        );

        longReserve.postUpdateReserveData();

        emit Trade(DataTypes.LIQUIDATION_WALLET, shortAsset, longAsset, amount, vars.receivedAmount, data, vars.shortAssetPrice, vars.longAssetPrice);
    }

    function transferTradeFee(
        address asset,
        address treasury,
        uint256 tradeFeeMantissa,
        uint256 tradeAmount
    ) private returns (uint256) {
        if (tradeFeeMantissa == 0) return 0;

        uint256 feeAmount = tradeAmount.wadMul(tradeFeeMantissa);
        IERC20Upgradeable(asset).safeTransfer(treasury, feeAmount);

        return feeAmount;
    }

    function swap(
        DataTypes.AssetConfig memory shortAssetConfig,
        address shortAsset,
        address longAsset,
        uint256 amount,
        bytes memory data
    ) private returns (uint256) {
        if (
            IERC20Upgradeable(shortAsset).allowance(address(this), address(shortAssetConfig.swapAdapter)) < amount
        ) {
            IERC20Upgradeable(shortAsset).safeApprove(address(shortAssetConfig.swapAdapter), 0);
            IERC20Upgradeable(shortAsset).safeApprove(address(shortAssetConfig.swapAdapter), type(uint256).max);
        }

        return shortAssetConfig.swapAdapter.swap(shortAsset, longAsset, amount, data);
    }

    struct ExecuteShortingVars {
        uint256 unit;
        uint256 amountToBorrow;
        uint256 amountLongToWithdraw;
        uint256 amountReserveToDivest;
        int256 newPosition;
        DataTypes.ReserveDataCache reserveCache;
    }

    /**
     * @notice May decrease long supply, reserve supply and increase utilized supply depending on current users position and shorting amount
     * @param reserve reserveConfig
     * @param assetConfigCache assetConfigCache
     * @param currUserPosition currUserPosition
     * @param amountToShort shorting amount
     * @param fromLongSupply `true` will decrease long supply, `false` will not
     * @return amount
     * @return amount borrowed from the reserve
     **/
    function executeShorting(
        DataTypes.ReserveData storage reserve,
        DataTypes.AssetConfig memory assetConfigCache,
        int256 currUserPosition,
        uint256 amountToShort,
        bool fromLongSupply
    ) public returns (uint256, uint256){
        ExecuteShortingVars memory vars;

        vars.unit = assetConfigCache.decimals;

        vars.reserveCache = reserve.cache();

        if (currUserPosition < 0) {
            // current position is short already
            vars.amountToBorrow = amountToShort;
        } else {
            // use long position to cover for shorting amount when available
            uint256 absCurrUserPosition = currUserPosition.abs();
            if (amountToShort > absCurrUserPosition) {
                // long position is not enough, borrow only lacking amount from reserve
                vars.amountLongToWithdraw = absCurrUserPosition;
                vars.amountToBorrow = amountToShort - absCurrUserPosition;
            } else {
                // long position can cover whole shorting amount, only use required shorting amount
                vars.amountLongToWithdraw = amountToShort;
                vars.amountToBorrow = 0;
            }
        }

        if (vars.amountLongToWithdraw > 0 && fromLongSupply) {
            reserve.longSupply -= vars.amountLongToWithdraw;

            if (reserve.ext.longReinvestment != address(0)) {
                IReinvestment(reserve.ext.longReinvestment).divest(vars.amountLongToWithdraw);
            }
        }

        if (vars.amountToBorrow > 0) {
            reserve.scaledUtilizedSupplyRay += vars.amountToBorrow.unitToRay(vars.unit).rayDiv(vars.reserveCache.currBorrowIndexRay);

            if (reserve.ext.reinvestment != address(0)) {
                IReinvestment(reserve.ext.reinvestment).divest(vars.amountToBorrow);
            } else {
                reserve.liquidSupply -= vars.amountToBorrow;
            }
        }

        require(
            IERC20Upgradeable(reserve.asset).balanceOf(address(this)) >= amountToShort,
            Errors.NOT_ENOUGH_POOL_BALANCE
        );

        return (amountToShort, vars.amountToBorrow);
    }

    struct ExecuteLongingVars {
        uint256 protocolClaimableAmount;
        uint256 amountLongToDeposit;
        uint256 amountToRepay;
        int256 newPosition;
        DataTypes.ReserveDataCache reserveCache;
    }

    /**
     * @notice executeLonging
     * @param reserve reserveConfig
     * @param assetConfigCache assetConfigCache
     * @param treasury treasury
     * @param currUserPosition currUserPosition
     * @param amountToLong amountToLong
     * @param toLongSupply will long amount goes to reserve long supply
     **/
    function executeLonging(
        DataTypes.ReserveData storage reserve,
        DataTypes.AssetConfig memory assetConfigCache,
        address treasury,
        int256 currUserPosition,
        uint256 amountToLong,
        bool toLongSupply
    ) public {
        ExecuteLongingVars memory vars;

        // TODO: can refactor to better condition statement
        require(
            IERC20Upgradeable(reserve.asset).balanceOf(address(this)) >= amountToLong,
            Errors.MISSING_UNDERLYING_ASSET
        );

        vars.reserveCache = reserve.cache();

        if (currUserPosition < 0) {
            // repay current short position
            uint256 absCurrUserPosition = currUserPosition.abs();
            if (amountToLong > absCurrUserPosition) {
                // repay accumulated borrowed amount
                vars.amountToRepay = absCurrUserPosition;
                // long amount can cover all short, remaining long will be added to long supply
                vars.amountLongToDeposit = amountToLong - vars.amountToRepay;
            } else {
                // long amount is enough or not to pay short
                vars.amountLongToDeposit = 0;
                vars.amountToRepay = amountToLong;
            }
        } else {
            // current position is long already
            vars.amountLongToDeposit = amountToLong;
        }

        if (vars.amountLongToDeposit > 0 && toLongSupply) {
            reserve.longSupply += vars.amountLongToDeposit;

            if (reserve.ext.longReinvestment != address(0)) {
                invest(reserve.asset, reserve.ext.longReinvestment, vars.amountLongToDeposit);
            }
        }

        if (vars.amountToRepay > 0) {
            // protocol fee is included to users' debt

            // sent protocol fee portions to treasury
            vars.protocolClaimableAmount = vars.amountToRepay
            .unitToRay(assetConfigCache.decimals)
            .rayDiv(vars.reserveCache.currBorrowIndexRay)
            .rayMul(vars.reserveCache.currProtocolIndexRay)
            .rayToUnit(assetConfigCache.decimals);

            IERC20Upgradeable(reserve.asset).safeTransfer(treasury, vars.protocolClaimableAmount);

            // utilized supply is combination of protocol fee + reserve utilization.
            // reduce utilization according to amount repaid with protocol
            reserve.scaledUtilizedSupplyRay -= vars.amountToRepay
            .unitToRay(assetConfigCache.decimals)
            .rayDiv(vars.reserveCache.currBorrowIndexRay);

            // pay back to reserve pool the remainder
            vars.amountToRepay -= vars.protocolClaimableAmount;

            if (reserve.ext.reinvestment != address(0)) {
                invest(reserve.asset, reserve.ext.reinvestment, vars.amountToRepay);
            } else {
                reserve.liquidSupply += vars.amountToRepay;
            }
        }
    }

    function invest(address asset, address reinvestment, uint256 amount) private {
        if (IERC20Upgradeable(asset).allowance(address(this), reinvestment) < amount) {
            IERC20Upgradeable(asset).safeApprove(reinvestment, 0);
            IERC20Upgradeable(asset).safeApprove(reinvestment, type(uint256).max);
        }
        IReinvestment(reinvestment).invest(amount);
    }
}

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

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

// 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 4 of 21 : DataTypes.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import "../interfaces/ISwapAdapter.sol";
import "../interfaces/IPriceOracleGetter.sol";
import "../interfaces/IReinvestment.sol";
import "../interfaces/IBonusPool.sol";
import "../interfaces/IUserData.sol";

/// @dev This help resolves cyclic dependencies
library DataTypes {

    uint256 public constant VERSION = 1;

    address public constant LIQUIDATION_WALLET = 0x0000000000000000000000000000000000000001;

    enum AssetState {Disabled, Active, Withdrawing}

    enum PositionType {Long, Short}

    enum AssetMode {Disabled, OnlyReserve, OnlyLong, ReserveAndLong}

    enum AssetKind {SingleStable, SingleVolatile, LP}

    struct AssetStorage {
        uint256 assetsCount;
        mapping(uint256 => address) assetsList;
        mapping(address => DataTypes.AssetConfig) assetConfigs;
    }

    struct ReserveStorage {
        uint256 reservesCount;
        mapping(address => uint256) reservesList;
        mapping(uint256 => DataTypes.ReserveData) reserves;
    }

    struct CollateralStorage {
        uint256 collateralsCount;
        mapping(address => mapping(address => uint256)) collateralsList;
        mapping(uint256 => DataTypes.CollateralData) collaterals;
    }

    struct ProtocolConfig {
        address treasury;
        address configuratorAddress;
        address userData;
        uint256 leverageFactor;
        uint256 tradeFeeMantissa;
        uint256 liquidationRatioMantissa;
        uint256 swapBufferLimitPercentage;
    }

    struct MappingStorage {
        mapping(address => bool) whitelistedCallers;
        mapping(address => uint256) userLastTradeBlock;
        mapping(address => uint256) liquidatedCollaterals;
    }

    // Shared property of reserve, collateral and portfolio
    struct AssetConfig {
        uint256 assetId;
        uint8 decimals;
        AssetKind kind;
        ISwapAdapter swapAdapter;
        IPriceOracleGetter oracle;
    }

    struct ReserveConfiguration {
        uint32 depositFeeMantissaGwei;
        uint32 protocolRateMantissaGwei;
        uint32 utilizationBaseRateMantissaGwei;
        uint32 kinkMantissaGwei;
        uint32 multiplierAnnualGwei;
        uint32 jumpMultiplierAnnualGwei;
        // --- 208 bits used ---
        AssetState state;
        AssetMode mode;
    }

    struct ReserveDataExtension {
        address reinvestment;
        address longReinvestment;
        address bonusPool;
    }

    struct ReserveData {
        ReserveConfiguration configuration;
        ReserveDataExtension ext;
        address asset;
        uint256 poolId;
        uint256 liquidSupply;
        // scaled utilized supply on reserve, changes whenever a deposit, withdraw, borrow and repay is executed
        uint256 scaledUtilizedSupplyRay;
        uint256 longSupply;
        uint256 reserveIndexRay;
        uint256 utilizationPercentageRay;
        uint256 protocolIndexRay;
        uint256 lastUpdatedTimestamp;
    }

    struct ReserveDataCache {
        address asset;
        address reinvestment;
        address longReinvestment;
        uint256 currReserveIndexRay;
        uint256 currProtocolIndexRay;
        uint256 currBorrowIndexRay;
    }

    struct CollateralConfiguration {
        uint32 depositFeeMantissaGwei;
        uint32 ltvGwei;
        uint128 minBalance;
        // --- 192 bits used ---
        AssetState state;
    }

    struct CollateralData {
        CollateralConfiguration configuration;
        address asset;
        address reinvestment;
        uint256 poolId;
        uint256 liquidSupply;
        uint256 totalShareSupplyRay;
    }

    struct UserConfiguration {
        uint256 reserve;
        uint256 collateral;
        uint256 position;
    }

    struct UserData {
        UserConfiguration configuration;
        mapping(uint256 => uint256) reserveShares; // in ray
        mapping(uint256 => uint256) collateralShares; // in ray
        mapping(uint256 => int256) positions; // in ray
    }

    struct InitReserveData {
        address reinvestment;
        address bonusPool;
        address longReinvestment;
        uint32 depositFeeMantissa;
        uint32 protocolRateMantissaRay;
        uint32 utilizationBaseRateMantissaRay;
        uint32 kinkMantissaRay;
        uint32 multiplierAnnualRay;
        uint32 jumpMultiplierAnnualRay;
        AssetState state;
        AssetMode mode;
    }


    struct ValidateTradeParams {
        address user;
        uint256 amountToTrade;
        uint256 currShortReserveAvailableSupply;
        uint256 maxAmountToTrade;
        uint256 userLastTradeBlock;
    }

    struct UserLiquidity {
        uint256 totalCollateralUsdPreLtv;
        uint256 totalCollateralUsdPostLtv;
        uint256 totalLongUsd;
        uint256 totalShortUsd;
        int256 pnlUsd;
        int256 totalLeverageUsd;
        int256 availableLeverageUsd;
        bool isLiquidatable;
    }

    struct UserLiquidityCachedData {
        int256 currShortingPosition;
        int256 currLongingPosition;
        uint256 shortingPrice;
        uint256 shortingPriceDecimals;
        uint256 longingPrice;
        uint256 longingPriceDecimals;
    }
}

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

import "../types/DataTypes.sol";

library UserConfiguration {

    function setUsingReserve(
        DataTypes.UserConfiguration storage self,
        uint256 bitIndex,
        bool usingReserve
    ) internal {
        self.reserve = (self.reserve & ~(1 << bitIndex)) | (uint256(usingReserve ? 1 : 0) << bitIndex);
    }

    function setUsingCollateral(
        DataTypes.UserConfiguration storage self,
        uint256 bitIndex,
        bool usingCollateral
    ) internal {
        self.collateral = (self.collateral & ~(1 << bitIndex)) | (uint256(usingCollateral ? 1 : 0) << bitIndex);
    }

    function setUsingPosition(
        DataTypes.UserConfiguration storage self,
        uint256 bitIndex,
        bool usingPosition
    ) internal {
        self.position = (self.position & ~(1 << bitIndex)) | (uint256(usingPosition ? 1 : 0) << bitIndex);
    }

    function isUsingReserve(
        DataTypes.UserConfiguration memory self,
        uint256 bitIndex
    ) internal pure returns (bool) {
        return (self.reserve >> bitIndex) & 1 != 0;
    }

    function isUsingCollateral(
        DataTypes.UserConfiguration memory self,
        uint256 bitIndex
    ) internal pure returns (bool) {
        return (self.collateral >> bitIndex) & 1 != 0;
    }

    function isUsingPosition(
        DataTypes.UserConfiguration memory self,
        uint256 bitIndex
    ) internal pure returns (bool) {
        return (self.position >> bitIndex) & 1 != 0;
    }

    function hasReserve(
        DataTypes.UserConfiguration memory self,
        uint256 offSetIndex
    ) internal pure returns (bool) {
        return (self.reserve >> offSetIndex) > 0;
    }

    function hasCollateral(
        DataTypes.UserConfiguration memory self,
        uint256 offSetIndex
    ) internal pure returns (bool) {
        return (self.collateral >> offSetIndex) > 0;
    }

    function hasPosition(
        DataTypes.UserConfiguration memory self,
        uint256 offSetIndex
    ) internal pure returns (bool) {
        return (self.position >> offSetIndex) > 0;
    }

    function isEmpty(DataTypes.UserConfiguration memory self) internal pure returns (bool) {
        return self.reserve == 0 && self.collateral == 0 && self.position == 0;
    }
}

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

/**
 * @dev Provides mul and div function for wads (decimal numbers with 18 digits precision) and rays (decimals with 27 digits)
 **/
library MathUtils {
    uint256 public constant VERSION = 1;

    uint256 internal constant WAD_UNIT = 18;
    uint256 internal constant RAY_UNIT = 27;
    uint256 internal constant WAD_RAY_RATIO = 1e9;

    uint256 public constant WAD = 1e18;
    uint256 public constant RAY = 1e27;
    uint256 public constant HALF_WAD = WAD / 2;
    uint256 public constant HALF_RAY = RAY / 2;


    /**
     * @notice Multiplies two wad, rounding half up to the nearest wad
     * @param a Wad
     * @param b Wad
     * @return The result of a*b, in wad
     **/
    function wadMul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0 || b == 0) {
            return 0;
        }

        require(a <= (type(uint256).max - HALF_WAD) / b, "MathUtils: overflow");

        return (a * b + HALF_WAD) / WAD;
    }

    /**
     * @notice Divides two wad, rounding half up to the nearest wad
     * @param a Wad
     * @param b Wad
     * @return The result of a/b, in wad
     **/
    function wadDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0, "MathUtils: division by zero");
        uint256 halfB = b / 2;

        require(a <= (type(uint256).max - halfB) / WAD, "MathUtils: overflow");

        return (a * WAD + halfB) / b;
    }

    /**
     * @notice Multiplies two ray, rounding half up to the nearest ray
     * @param a Ray
     * @param b Ray
     * @return The result of a*b, in ray
     **/
    function rayMul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0 || b == 0) {
            return 0;
        }

        require(a <= (type(uint256).max - HALF_RAY) / b, "MathUtils: overflow");

        return (a * b + HALF_RAY) / RAY;
    }

    /**
     * @notice Divides two ray, rounding half up to the nearest ray
     * @param a Ray
     * @param b Ray
     * @return The result of a/b, in ray
     **/
    function rayDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0, "MathUtils: division by zero");
        uint256 halfB = b / 2;

        require(a <= (type(uint256).max - halfB) / RAY, "MathUtils: overflow");

        return (a * RAY + halfB) / b;
    }

    /**
     * @notice Casts ray down to wad
     * @param a Ray
     * @return a casted to wad, rounded half up to the nearest wad
     **/
    function rayToWad(uint256 a) internal pure returns (uint256) {
        uint256 halfRatio = WAD_RAY_RATIO / 2;
        uint256 result = halfRatio + a;
        require(result >= halfRatio, "MathUtils: overflow");

        return result / WAD_RAY_RATIO;
    }

    /**
     * @notice Converts wad up to ray
     * @param a Wad
     * @return a converted in ray
     **/
    function wadToRay(uint256 a) internal pure returns (uint256) {
        uint256 result = a * WAD_RAY_RATIO;
        require(result / WAD_RAY_RATIO == a, "MathUtils: overflow");
        return result;
    }

    /**
     * @notice Converts unit to wad
     * @param self Value
     * @param unit Value's unit
     * @return value converted in wad
     **/
    function unitToWad(uint256 self, uint256 unit) internal pure returns (uint256) {
        if (self == 0 || unit == WAD_UNIT) return self;

        if (unit < WAD_UNIT) {
            return self * 10**(WAD_UNIT - unit);
        } else {
            return self / 10**(unit - WAD_UNIT);
        }
    }

    /**
     * @notice Converts unit to ray
     * @param self Value
     * @param unit Value's unit
     * @return value converted in ray
     **/
    function unitToRay(uint256 self, uint256 unit) internal pure returns (uint256) {
        if (self == 0 || unit == RAY_UNIT) return self;

        if (unit < RAY_UNIT) {
            return self * 10**(RAY_UNIT -unit);
        } else {
            return self / 10**(unit - RAY_UNIT);
        }
    }

    /**
     * @notice Converts unit to ray
     * @param self Value
     * @param unit Value's unit
     * @return value converted in ray
     **/
    function unitToRay(int256 self, uint256 unit) internal pure returns (int256) {
        if (self == 0 || unit == RAY_UNIT) return self;

        if (unit < RAY_UNIT) {
            return self * int256(10**(RAY_UNIT -unit));
        } else {
            return self / int256(10**(unit - RAY_UNIT));
        }
    }

    /**
     * @notice Converts wad to unit
     * @param self Value
     * @param unit Value's unit
     * @return value converted in unit
     **/
    function wadToUnit(uint256 self, uint256 unit) internal pure returns (uint256) {
        if (self == 0 || unit == WAD) return self;

        if (unit < WAD_UNIT) {
            return self / 10**(WAD_UNIT - unit);
        } else {
            return self * 10**(unit - WAD_UNIT);
        }
    }

    /**
     * @notice Converts ray to unit
     * @param self Value
     * @param unit Value's unit
     * @return value converted in unit
     **/
    function rayToUnit(uint256 self, uint256 unit) internal pure returns (uint256) {
        if (self == 0 || unit == RAY_UNIT) return self;

        if (unit < RAY_UNIT) {
            return self / 10**(RAY_UNIT - unit);
        } else {
            return self * 10**(unit - RAY_UNIT);
        }
    }

    /**
     * @notice Converts ray to unit
     * @param self Value
     * @param unit Value's unit
     * @return value converted in unit
     **/
    function rayToUnit(int256 self, uint256 unit) internal pure returns (int256) {
        if (self == 0 || unit == RAY_UNIT) return self;

        if (unit < RAY_UNIT) {
            return self / int256(10**(RAY_UNIT - unit));
        } else {
            return self * int256(10**(unit - RAY_UNIT));
        }
    }

    function abs(int256 a) internal pure returns (uint256) {
        if (a < 0) {
            return uint256(a * (-1));
        } else {
            return uint256(a);
        }
    }
}

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

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "../../interfaces/IReinvestment.sol";
import "../../interfaces/IUserData.sol";
import "../../types/DataTypes.sol";
import "../math/MathUtils.sol";
import "../math/InterestUtils.sol";
import "./ValidationLogic.sol";
import "../storage/LedgerStorage.sol";

library ReserveLogic {
    using SafeERC20Upgradeable for IERC20Upgradeable;
    using MathUtils for uint256;

    uint256 public constant VERSION = 1;

    /**
     * @dev The reserve supplies
     */
    function getReserveSupplies(
        DataTypes.ReserveData memory reserve
    ) internal view returns (uint256, uint256, uint256, uint256, uint256) {
        uint256 unit = LedgerStorage.getAssetStorage().assetConfigs[reserve.asset].decimals;
        uint256 currAvailableSupply;

        if (reserve.ext.reinvestment == address(0)) {
            currAvailableSupply += reserve.liquidSupply;
        } else {
            currAvailableSupply += IReinvestment(reserve.ext.reinvestment).totalSupply();
        }

        (uint256 nextReserveIndexRay, uint256 nextProtocolIndexRay) = calculateIndexes(reserve, block.timestamp);

        uint256 currLockedReserveSupplyRay = reserve.scaledUtilizedSupplyRay.rayMul(nextReserveIndexRay);

        uint256 currProtocolUtilizedSupplyRay = reserve.scaledUtilizedSupplyRay.rayMul(nextProtocolIndexRay);

        uint256 currReserveSupply = currAvailableSupply + currLockedReserveSupplyRay.rayToUnit(unit);

        uint256 currUtilizedSupplyRay = currLockedReserveSupplyRay + currProtocolUtilizedSupplyRay;

        uint256 currTotalSupplyRay = currAvailableSupply.unitToRay(unit) + currUtilizedSupplyRay;

        return (
        currAvailableSupply,
        currReserveSupply,
        currProtocolUtilizedSupplyRay.rayToUnit(unit),
        currTotalSupplyRay.rayToUnit(unit),
        currUtilizedSupplyRay.rayToUnit(unit)
        );
    }

    /**
     * Get normalized debt
     * @return the normalized debt. expressed in ray
     **/
    function getReserveIndexes(
        DataTypes.ReserveData memory reserve
    ) internal view returns (uint256, uint256, uint256) {
        (uint256 nextReserveIndexRay, uint256 nextProtocolIndexRay) = calculateIndexes(reserve, block.timestamp);

        return (
        nextReserveIndexRay,
        nextProtocolIndexRay,
        nextProtocolIndexRay + nextReserveIndexRay
        );
    }

    function updateIndex(
        DataTypes.ReserveData storage reserve
    ) internal {
        (uint256 nextReserveIndexRay, uint256 nextProtocolIndexRay) = calculateIndexes(reserve, block.timestamp);

        reserve.reserveIndexRay = nextReserveIndexRay;
        reserve.protocolIndexRay = nextProtocolIndexRay;

        reserve.lastUpdatedTimestamp = block.timestamp;
    }

    function postUpdateReserveData(DataTypes.ReserveData storage reserve) internal {
        uint256 decimals = LedgerStorage.getAssetStorage().assetConfigs[reserve.asset].decimals;

        (,,,uint256 currTotalSupply, uint256 currUtilizedSupply) = getReserveSupplies(reserve);

        reserve.utilizationPercentageRay = currTotalSupply > 0 ? currUtilizedSupply.unitToRay(decimals).rayDiv(
            currTotalSupply.unitToRay(decimals)
        ) : 0;
    }

    function calculateIndexes(
        DataTypes.ReserveData memory reserve,
        uint256 blockTimestamp
    ) private pure returns (uint256, uint256) {
        if (reserve.utilizationPercentageRay == 0) {
            return (
            reserve.reserveIndexRay,
            reserve.protocolIndexRay
            );
        }

        uint256 currBorrowIndexRay = reserve.reserveIndexRay + reserve.protocolIndexRay;

        uint256 interestRateRay = getInterestRate(
            reserve.utilizationPercentageRay,
            uint256(reserve.configuration.protocolRateMantissaGwei).unitToRay(9),
            uint256(reserve.configuration.utilizationBaseRateMantissaGwei).unitToRay(9),
            uint256(reserve.configuration.kinkMantissaGwei).unitToRay(9),
            uint256(reserve.configuration.multiplierAnnualGwei).unitToRay(9),
            uint256(reserve.configuration.jumpMultiplierAnnualGwei).unitToRay(9)
        );

        if (interestRateRay == 0) {
            return (
            reserve.reserveIndexRay,
            reserve.protocolIndexRay
            );
        }

        uint256 cumulatedInterestIndexRay = InterestUtils.getCompoundedInterest(
            interestRateRay, reserve.lastUpdatedTimestamp, blockTimestamp
        );

        uint256 growthIndexRay = currBorrowIndexRay.rayMul(cumulatedInterestIndexRay) - currBorrowIndexRay;

        uint256 protocolInterestRatioRay = uint256(reserve.configuration.protocolRateMantissaGwei).unitToRay(9).rayDiv(interestRateRay);

        uint256 nextProtocolIndexRay = reserve.protocolIndexRay + growthIndexRay.rayMul(protocolInterestRatioRay);

        uint256 nextReserveIndexRay = reserve.reserveIndexRay + growthIndexRay.rayMul(MathUtils.RAY - protocolInterestRatioRay);

        return (nextReserveIndexRay, nextProtocolIndexRay);
    }

    /**
    * @notice Get the interest rate: `rate + utilizationBaseRate + protocolRate`
    * @param utilizationPercentageRay scaledTotalSupplyRay
    * @param protocolRateMantissaRay protocolRateMantissaRay
    * @param utilizationBaseRateMantissaRay utilizationBaseRateMantissaRay
    * @param kinkMantissaRay kinkMantissaRay
    * @param multiplierAnnualRay multiplierAnnualRay
    * @param jumpMultiplierAnnualRay jumpMultiplierAnnualRay
    **/
    function getInterestRate(
        uint256 utilizationPercentageRay,
        uint256 protocolRateMantissaRay,
        uint256 utilizationBaseRateMantissaRay,
        uint256 kinkMantissaRay,
        uint256 multiplierAnnualRay,
        uint256 jumpMultiplierAnnualRay
    ) private pure returns (uint256) {
        uint256 rateRay;

        if (utilizationPercentageRay <= kinkMantissaRay) {
            rateRay = utilizationPercentageRay.rayMul(multiplierAnnualRay);
        } else {
            uint256 normalRateRay = kinkMantissaRay.rayMul(multiplierAnnualRay);
            uint256 excessUtilRay = utilizationPercentageRay - kinkMantissaRay;
            rateRay = excessUtilRay.rayMul(jumpMultiplierAnnualRay) + normalRateRay;
        }

        return rateRay + utilizationBaseRateMantissaRay + protocolRateMantissaRay;
    }

    function cache(
        DataTypes.ReserveData storage reserve
    ) internal view returns (
        DataTypes.ReserveDataCache memory
    ) {
        DataTypes.ReserveDataCache memory reserveCache;

        reserveCache.asset = reserve.asset;
        reserveCache.reinvestment = reserve.ext.reinvestment;
        reserveCache.longReinvestment = reserve.ext.longReinvestment;

        // if the action involves mint/burn of debt, the cache needs to be updated
        reserveCache.currReserveIndexRay = reserve.reserveIndexRay;
        reserveCache.currProtocolIndexRay = reserve.protocolIndexRay;
        reserveCache.currBorrowIndexRay = reserveCache.currReserveIndexRay + reserveCache.currProtocolIndexRay;

        return reserveCache;
    }
}

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

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "../../types/DataTypes.sol";
import "../../configuration/UserConfiguration.sol";
import "../math/MathUtils.sol";
import "./HelpersLogic.sol";
import "./ReserveLogic.sol";
import "../../interfaces/IUserData.sol";
import "../../interfaces/IBonusPool.sol";
import "../storage/LedgerStorage.sol";

library ReservePoolLogic {
    using MathUtils for uint256;
    using SafeERC20Upgradeable for IERC20Upgradeable;
    using ReserveLogic for DataTypes.ReserveData;
    using UserConfiguration for DataTypes.UserConfiguration;

    uint256 public constant VERSION = 3;

    event DepositedReserve(address user, address asset, address reinvestment, uint256 amount);
    event WithdrawnReserve(address user, address asset, address reinvestment, uint256 amount);
    event EmergencyWithdrawnReserve(address asset, uint256 supply);
    event ReinvestedReserveSupply(address asset, uint256 supply);
    event EmergencyWithdrawnLong(address asset, uint256 supply, uint256 amountToTreasury);
    event ReinvestedLongSupply(address asset, uint256 supply);
    event SweepLongReinvestment(address asset, uint256 amountToTreasury);

    function setReserveConfiguration(uint256 pid, DataTypes.ReserveConfiguration memory configuration) external {
        DataTypes.ReserveData storage reserve = LedgerStorage.getReserveStorage().reserves[pid];
        require(reserve.asset != address(0), Errors.POOL_NOT_INITIALIZED);
        reserve.updateIndex();
        reserve.postUpdateReserveData();
        reserve.configuration = configuration;
    }

    function getReserveIndexes(address asset) external view returns (uint256, uint256, uint256) {
        return LedgerStorage.getReserveStorage().reserves[
            LedgerStorage.getReserveStorage().reservesList[asset]
        ].getReserveIndexes();
    }

    function getReserveSupplies(address asset) external view returns (uint256, uint256, uint256, uint256, uint256) {
        return LedgerStorage.getReserveStorage().reserves[
            LedgerStorage.getReserveStorage().reservesList[asset]
        ].getReserveSupplies();
    }

    function checkpointReserve(address asset) external {
        DataTypes.ReserveData storage reserve = LedgerStorage.getReserveStorage().reserves[
            LedgerStorage.getReserveStorage().reservesList[asset]
        ];

        reserve.updateIndex();
        reserve.postUpdateReserveData();
    }

    function executeDepositReserve(
        address user, address asset, uint256 amount
    ) external {
        DataTypes.ProtocolConfig memory protocolConfig = LedgerStorage.getProtocolConfig();

        uint256 pid = LedgerStorage.getReserveStorage().reservesList[asset];
        DataTypes.ReserveData storage reserve = LedgerStorage.getReserveStorage().reserves[pid];
        DataTypes.ReserveData memory localReserve = reserve;
        DataTypes.AssetConfig memory assetConfig = LedgerStorage.getAssetStorage().assetConfigs[asset];

        ValidationLogic.validateDepositReserve(localReserve, amount);

        reserve.updateIndex();

        (,uint256 currReserveSupply,,,) = reserve.getReserveSupplies();
        uint256 currUserReserveBalance = IUserData(protocolConfig.userData).getUserReserve(user, asset, false);

        IUserData(protocolConfig.userData).depositReserve(user, pid, amount, assetConfig.decimals, currReserveSupply);

        IERC20Upgradeable(asset).safeTransferFrom(user, address(this), amount);

        if (localReserve.ext.reinvestment != address(0)) {
            HelpersLogic.approveMax(asset, localReserve.ext.reinvestment, amount);

            IReinvestment(localReserve.ext.reinvestment).checkpoint(user, currUserReserveBalance);
            IReinvestment(localReserve.ext.reinvestment).invest(amount);
        } else {
            reserve.liquidSupply += amount;
        }

        if (localReserve.ext.bonusPool != address(0)) {
            uint256 nextUserReserveBalance = IUserData(protocolConfig.userData).getUserReserve(user, asset, false);
            IBonusPool(localReserve.ext.bonusPool).updatePoolUser(asset, user, nextUserReserveBalance);
        }

        reserve.postUpdateReserveData();

        emit DepositedReserve(user, asset, localReserve.ext.reinvestment, amount);
    }

    function executeWithdrawReserve(
        address user, address asset, uint256 amount
    ) external {
        DataTypes.ProtocolConfig memory protocolConfig = LedgerStorage.getProtocolConfig();

        uint256 pid = LedgerStorage.getReserveStorage().reservesList[asset];
        DataTypes.ReserveData storage reserve = LedgerStorage.getReserveStorage().reserves[pid];
        DataTypes.ReserveData memory localReserve = reserve;
        DataTypes.AssetConfig memory assetConfig = LedgerStorage.getAssetStorage().assetConfigs[asset];

        reserve.updateIndex();

        (,uint256 currReserveSupply,,,) = reserve.getReserveSupplies();

        uint256 currUserReserveBalance = IUserData(protocolConfig.userData).getUserReserve(user, asset, false);
        uint256 currUserMaxClaimReserve = IUserData(protocolConfig.userData).getUserReserve(user, asset, true);

        if (amount > currUserMaxClaimReserve) {
            amount = currUserMaxClaimReserve;
        }

        ValidationLogic.validateWithdrawReserve(localReserve, currReserveSupply, amount);

        IUserData(protocolConfig.userData).withdrawReserve(user, pid, amount, assetConfig.decimals, currReserveSupply);

        if (localReserve.ext.reinvestment != address(0)) {
            IReinvestment(localReserve.ext.reinvestment).checkpoint(user, currUserReserveBalance);
            IReinvestment(localReserve.ext.reinvestment).divest(amount);
        } else {
            reserve.liquidSupply -= amount;
        }

        uint256 withdrawalFee;
        if (localReserve.configuration.depositFeeMantissaGwei > 0) {
            withdrawalFee = amount.wadMul(
                uint256(localReserve.configuration.depositFeeMantissaGwei).unitToWad(9)
            );

            IERC20Upgradeable(asset).safeTransfer(protocolConfig.treasury, withdrawalFee);
        }

        if (localReserve.ext.bonusPool != address(0)) {
            uint256 nextUserReserveBalance = IUserData(protocolConfig.userData).getUserReserve(user, asset, false);
            IBonusPool(localReserve.ext.bonusPool).updatePoolUser(asset, user, nextUserReserveBalance);
        }

        reserve.postUpdateReserveData();

        IERC20Upgradeable(asset).safeTransfer(user, amount - withdrawalFee);

        emit WithdrawnReserve(user, asset, localReserve.ext.reinvestment, amount - withdrawalFee);
    }

    function executeEmergencyWithdrawReserve(uint256 pid) external {
        DataTypes.ReserveData storage reserve = LedgerStorage.getReserveStorage().reserves[pid];

        uint256 priorBalance = IERC20Upgradeable(reserve.asset).balanceOf(address(this));

        uint256 withdrawn = IReinvestment(reserve.ext.reinvestment).emergencyWithdraw();

        uint256 receivedBalance = IERC20Upgradeable(reserve.asset).balanceOf(address(this)) - priorBalance;
        require(receivedBalance == withdrawn, Errors.ERROR_EMERGENCY_WITHDRAW);

        reserve.liquidSupply += withdrawn;

        emit EmergencyWithdrawnReserve(reserve.asset, withdrawn);
    }

    function executeReinvestReserveSupply(uint256 pid) external {
        DataTypes.ReserveData storage reserve = LedgerStorage.getReserveStorage().reserves[pid];

        IERC20Upgradeable(reserve.asset).safeApprove(reserve.ext.reinvestment, reserve.liquidSupply);
        IReinvestment(reserve.ext.reinvestment).invest(reserve.liquidSupply);

        emit ReinvestedReserveSupply(reserve.asset, reserve.liquidSupply);

        reserve.liquidSupply = 0;
    }

    function executeEmergencyWithdrawLong(uint256 pid) external {
        DataTypes.ProtocolConfig memory protocolConfig = LedgerStorage.getProtocolConfig();
        DataTypes.ReserveData storage reserve = LedgerStorage.getReserveStorage().reserves[pid];

        uint256 priorBalance = IERC20Upgradeable(reserve.asset).balanceOf(address(this));

        uint256 withdrawn = IReinvestment(reserve.ext.longReinvestment).emergencyWithdraw();

        uint256 receivedBalance = IERC20Upgradeable(reserve.asset).balanceOf(address(this)) - priorBalance;
        require(receivedBalance == withdrawn, Errors.ERROR_EMERGENCY_WITHDRAW);

        uint256 amountToTreasury = withdrawn - reserve.longSupply;

        if (amountToTreasury > 0) {
            IERC20Upgradeable(reserve.asset).safeTransfer(protocolConfig.treasury, amountToTreasury);
        }

        emit EmergencyWithdrawnLong(reserve.asset, reserve.longSupply, amountToTreasury);
    }

    // @dev long supply is static and always has value, accrued amount from reinvestment will be transferred to treasury
    // @param reserve Reserve data
    function executeReinvestLongSupply(uint256 pid) external {
        DataTypes.ReserveData storage reserve = LedgerStorage.getReserveStorage().reserves[pid];

        IERC20Upgradeable(reserve.asset).safeApprove(reserve.ext.longReinvestment, reserve.longSupply);
        IReinvestment(reserve.ext.longReinvestment).invest(reserve.longSupply);

        emit ReinvestedLongSupply(reserve.asset, reserve.longSupply);
    }

    function executeSweepLongReinvestment(address asset) external {
        DataTypes.ProtocolConfig memory protocolConfig = LedgerStorage.getProtocolConfig();

        uint256 pid = LedgerStorage.getReserveStorage().reservesList[asset];
        require(pid != 0, Errors.POOL_NOT_INITIALIZED);

        DataTypes.ReserveData storage reserve = LedgerStorage.getReserveStorage().reserves[pid];
        DataTypes.ReserveData memory localReserve = reserve;

        reserve.updateIndex();

        require(localReserve.ext.longReinvestment != address(0), Errors.INVALID_ZERO_ADDRESS);

        uint256 reinvestmentBalance = IReinvestment(localReserve.ext.longReinvestment).totalSupply();
        uint256 amountToTreasury = reinvestmentBalance - reserve.longSupply;

        require(amountToTreasury > 0, Errors.INVALID_ZERO_AMOUNT);

        IReinvestment(localReserve.ext.longReinvestment).divest(amountToTreasury);
        IERC20Upgradeable(asset).safeTransfer(protocolConfig.treasury, amountToTreasury);

        reserve.postUpdateReserveData();

        emit SweepLongReinvestment(asset, amountToTreasury);
    }
}

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

import "../../interfaces/IPriceOracleGetter.sol";
import "../../configuration/UserConfiguration.sol";
import "../../types/DataTypes.sol";
import "../math/MathUtils.sol";
import "./ReserveLogic.sol";
import "./CollateralLogic.sol";
import "../storage/LedgerStorage.sol";
import "../../interfaces/IUserData.sol";

library GeneralLogic {
    using MathUtils for uint256;
    using MathUtils for int256;
    using UserConfiguration for DataTypes.UserConfiguration;
    using ReserveLogic for DataTypes.ReserveData;
    using CollateralLogic for DataTypes.CollateralData;

    uint256 public constant VERSION = 2;

    function getAssetAmountFromUsd(
        uint256 usdAmount,
        uint256 assetUnit,
        uint256 assetPrice,
        uint256 assetPriceUnit
    ) public pure returns (uint256) {
        return usdAmount.wadDiv(assetPrice.unitToWad(assetPriceUnit)).wadToUnit(assetUnit);
    }

    function getAssetUsdFromAmount(
        uint256 amount,
        uint256 assetUnit,
        uint256 assetPrice,
        uint256 assetPriceUnit
    ) public pure returns (uint256) {
        return amount.unitToWad(assetUnit).wadMul(assetPrice.unitToWad(assetPriceUnit));
    }

    struct CalculateUserLiquidityVars {
        address asset;
        address reinvestment;
        uint256 currUserCollateral;
        uint256 collateralUsd;
        uint256 positionUsd;
        uint16 i;
        uint256 ltv;
        uint256 assetPrice;
        uint256 assetPriceDecimal;
        int256 currUserPosition;
        DataTypes.ReserveData reserve;
        DataTypes.CollateralData collateral;
        DataTypes.AssetConfig assetConfig;
        DataTypes.UserConfiguration localUserConfig;
    }

    function getUserLiquidity(
        address user,
        address shortingAssetAddress,
        address longingAssetAddress
    ) external view returns (
        DataTypes.UserLiquidity memory,
        DataTypes.UserLiquidityCachedData memory
    ) {
        DataTypes.UserLiquidity memory result;
        DataTypes.UserLiquidityCachedData memory cachedData;
        CalculateUserLiquidityVars memory vars;

        DataTypes.ProtocolConfig memory protocolConfig = LedgerStorage.getProtocolConfig();

        vars.localUserConfig = IUserData(protocolConfig.userData).getUserConfiguration(user);

        if (vars.localUserConfig.isEmpty()) {
            return (result, cachedData);
        }

        vars.i = 0;

        while (vars.localUserConfig.collateral != 0 || vars.localUserConfig.position != 0) {
            // TODO: can it use vars.i?
            if (vars.localUserConfig.isUsingCollateral(0)) {

                vars.collateral = LedgerStorage.getCollateralStorage().collaterals[vars.i];

                vars.asset = vars.collateral.asset;

                vars.reinvestment = vars.collateral.reinvestment;

                vars.assetConfig = LedgerStorage.getAssetStorage().assetConfigs[vars.asset];

                vars.currUserCollateral = IUserData(protocolConfig.userData).getUserCollateralInternal(
                    user,
                    vars.i,
                    vars.collateral.getCollateralSupply(),
                    vars.assetConfig.decimals
                );

                (vars.assetPrice, vars.assetPriceDecimal) = vars.assetConfig.oracle.getAssetPrice(vars.asset);

                vars.collateralUsd = getAssetUsdFromAmount(
                    vars.currUserCollateral,
                    vars.assetConfig.decimals,
                    vars.assetPrice,
                    vars.assetPriceDecimal
                );

                result.totalCollateralUsdPreLtv += vars.collateralUsd;

                result.totalCollateralUsdPostLtv += vars.collateralUsd.wadMul(
                    uint256(vars.collateral.configuration.ltvGwei).unitToWad(9)
                );
            }

            if (vars.localUserConfig.isUsingPosition(0)) {
                vars.reserve = LedgerStorage.getReserveStorage().reserves[vars.i];

                vars.asset = vars.reserve.asset;
                vars.assetConfig = LedgerStorage.getAssetStorage().assetConfigs[vars.asset];

                (,,uint256 borrowIndex) = vars.reserve.getReserveIndexes();

                vars.currUserPosition = IUserData(protocolConfig.userData).getUserPositionInternal(
                    user,
                    vars.reserve.poolId,
                    borrowIndex,
                    vars.assetConfig.decimals
                );

                (vars.assetPrice, vars.assetPriceDecimal) = vars.assetConfig.oracle.getAssetPrice(vars.asset);

                if (shortingAssetAddress == vars.asset) {
                    cachedData.currShortingPosition = vars.currUserPosition;
                    cachedData.shortingPrice = vars.assetPrice;
                    cachedData.shortingPriceDecimals = vars.assetPriceDecimal;
                } else if (longingAssetAddress == vars.asset) {
                    cachedData.currLongingPosition = vars.currUserPosition;
                    cachedData.longingPrice = vars.assetPrice;
                    cachedData.longingPriceDecimals = vars.assetPriceDecimal;
                }

                vars.positionUsd = getAssetUsdFromAmount(
                    vars.currUserPosition.abs(),
                    vars.assetConfig.decimals,
                    vars.assetPrice,
                    vars.assetPriceDecimal
                );

                if (vars.currUserPosition < 0) {
                    result.totalShortUsd += vars.positionUsd;
                } else {
                    result.totalLongUsd += vars.positionUsd;
                }
            }

            vars.localUserConfig.collateral = vars.localUserConfig.collateral >> 1;

            vars.localUserConfig.position = vars.localUserConfig.position >> 1;

            vars.i++;
        }

        result.pnlUsd = int256(result.totalLongUsd) - int256(result.totalShortUsd);

        result.isLiquidatable = isLiquidatable(result.totalCollateralUsdPreLtv, protocolConfig.liquidationRatioMantissa, result.pnlUsd);

        result.totalLeverageUsd = (int256(result.totalCollateralUsdPostLtv) + result.pnlUsd) * int256(protocolConfig.leverageFactor) / int256(1e18);

        result.availableLeverageUsd = result.totalLeverageUsd - int(result.totalShortUsd);

        return (result, cachedData);
    }

    function isLiquidatable(
        uint256 totalCollateralUsdPreLtv,
        uint256 liquidationRatioMantissa,
        int256 pnlUsd
    ) public pure returns (bool) {
        return (int256(totalCollateralUsdPreLtv.wadMul(liquidationRatioMantissa)) + pnlUsd) < 0;
    }

}

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


import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "../../types/DataTypes.sol";
import "../math/MathUtils.sol";
import "../helpers/Errors.sol";

library ValidationLogic {
    using MathUtils for uint256;
    using SafeERC20Upgradeable for IERC20Upgradeable;

    uint256 public constant VERSION = 1;

    /**
     * @notice Validate a Deposit to Reserve
     * @param reserve reserve
     * @param amount amount
     **/
    function validateDepositReserve(
        DataTypes.ReserveData memory reserve,
        uint256 amount
    ) internal pure {
        require(
            reserve.configuration.mode == DataTypes.AssetMode.OnlyReserve ||
            reserve.configuration.mode == DataTypes.AssetMode.ReserveAndLong,
            "reserve mode disabled"
        );
        require(reserve.configuration.state == DataTypes.AssetState.Active, Errors.POOL_INACTIVE);
        require(amount != 0, Errors.INVALID_ZERO_AMOUNT);
    }

    /**
     * @notice Validate a Withdraw from Reserve
     * @param reserve reserve
     * @param amount amount
     **/
    function validateWithdrawReserve(
        DataTypes.ReserveData memory reserve,
        uint256 currReserveSupply,
        uint256 amount
    ) internal pure {
        require(
            reserve.configuration.mode == DataTypes.AssetMode.OnlyReserve ||
            reserve.configuration.mode == DataTypes.AssetMode.ReserveAndLong,
            "reserve mode disabled"
        );
        require(reserve.configuration.state != DataTypes.AssetState.Disabled, Errors.POOL_ACTIVE);
        require(amount != 0, Errors.INVALID_ZERO_AMOUNT);
        require(currReserveSupply >= amount, Errors.NOT_ENOUGH_POOL_BALANCE);
    }

    /**
     * @notice Validate a Deposit to Collateral
     * @param collateral collateral
     * @param userLastTradeBlock userLastTradeBlock
     * @param amount amount
     * @param userCollateral userCollateral
     **/
    function validateDepositCollateral(
        DataTypes.CollateralData memory collateral,
        uint256 userLastTradeBlock,
        uint256 amount,
        uint256 userCollateral
    ) internal view {
        require(userLastTradeBlock != block.number, Errors.USER_TRADE_BLOCK);
        require(collateral.configuration.state == DataTypes.AssetState.Active, Errors.POOL_INACTIVE);
        require(amount != 0, Errors.INVALID_ZERO_AMOUNT);
        require(
            (userCollateral + amount) >= collateral.configuration.minBalance,
            "collateral will under the minimum collateral balance"
        );
    }

    /**
     * @notice Validate a Withdraw from Collateral
     * @param collateral collateral
     * @param userLastTradeBlock userLastTradeBlock
     * @param amount amount
     * @param userCollateral userCollateral
     **/
    function validateWithdrawCollateral(
        DataTypes.CollateralData memory collateral,
        uint256 userLastTradeBlock,
        uint256 amount,
        uint256 userCollateral,
        uint256 currCollateralSupply
    ) internal view {
        require(userLastTradeBlock != block.number, Errors.USER_TRADE_BLOCK);
        require(collateral.configuration.state != DataTypes.AssetState.Disabled, Errors.POOL_ACTIVE);
        require(amount != 0, Errors.INVALID_ZERO_AMOUNT);
        require(currCollateralSupply >= amount, Errors.NOT_ENOUGH_POOL_BALANCE);
        require(
            (userCollateral - amount) == 0 || (userCollateral - amount) >= collateral.configuration.minBalance,
            "collateral will under the minimum collateral balance"
        );
    }

    /**
     * @notice Validate Short Repayment
     * @param userLastTradeBlock userLastTradeBlock
     * @param user user
     * @param asset asset
     * @param amount amount
     **/
    function validateRepayShort(
        int256 currNormalizedPosition,
        uint256 userLastTradeBlock,
        address user,
        address asset,
        uint256 amount,
        DataTypes.AssetState state,
        DataTypes.AssetMode mode
    ) internal view {
        require(
            state == DataTypes.AssetState.Active &&
            (mode == DataTypes.AssetMode.OnlyReserve ||
            mode == DataTypes.AssetMode.ReserveAndLong),
            Errors.POOL_INACTIVE
        );
        require(userLastTradeBlock != block.number, Errors.USER_TRADE_BLOCK);
        require(currNormalizedPosition < 0, Errors.INVALID_POSITION_TYPE);
        require(amount != 0, Errors.INVALID_ZERO_AMOUNT);
        /*
        TODO: is allowance checked can be omitted?
        it will still revert during transfer if amount is not enough
        */
        require(
            IERC20Upgradeable(asset).allowance(user, address(this)) >= amount,
            "need to approve first"
        );
    }

    /**
     * @notice Validate a Withdraw Long
     * @param userPosition User position
     * @param userLastTradeBlock userLastTradeBlock
     **/
    function validateWithdrawLong(
        int256 userPosition,
        uint256 userLastTradeBlock,
        uint256 amount,
        DataTypes.AssetState state,
        DataTypes.AssetMode mode
    ) internal view {
        require(
            state == DataTypes.AssetState.Active &&
            (mode == DataTypes.AssetMode.OnlyLong ||
            mode == DataTypes.AssetMode.ReserveAndLong),
            Errors.POOL_INACTIVE
        );
        require(userLastTradeBlock != block.number, Errors.USER_TRADE_BLOCK);
        require(userPosition > 0, Errors.NOT_ENOUGH_LONG_BALANCE);
        require(amount > 0, Errors.INVALID_AMOUNT_INPUT);
    }

    /**
     * @notice Validate a Trade
     * @param shortReserve Shorting reserve
     * @param longReserve Longing reserve
     * @param shortingAssetPosition User shorting asset position
     * @param params ValidateTradeParams object
     **/
    function validateTrade(
        DataTypes.ReserveData memory shortReserve,
        DataTypes.ReserveData memory longReserve,
        int256 shortingAssetPosition,
        DataTypes.ValidateTradeParams memory params
    ) internal view {
        require(shortReserve.asset != longReserve.asset, Errors.CANNOT_TRADE_SAME_ASSET);
        // is pool active
        require(
            shortReserve.configuration.mode == DataTypes.AssetMode.ReserveAndLong ||
            shortReserve.configuration.mode == DataTypes.AssetMode.OnlyReserve,
            "asset cannot short"
        );
        require(
            longReserve.configuration.mode == DataTypes.AssetMode.ReserveAndLong ||
            longReserve.configuration.mode == DataTypes.AssetMode.OnlyLong,
            "asset cannot long"
        );
        require(shortReserve.configuration.state == DataTypes.AssetState.Active, Errors.POOL_INACTIVE);
        require(longReserve.configuration.state == DataTypes.AssetState.Active, Errors.POOL_INACTIVE);

        // user constraint
        require(params.userLastTradeBlock != block.number, Errors.USER_TRADE_BLOCK);
        require(params.amountToTrade != 0, Errors.INVALID_ZERO_AMOUNT);

        // max short amount
        require(params.amountToTrade <= params.maxAmountToTrade, Errors.NOT_ENOUGH_USER_LEVERAGE);

        uint256 amountToBorrow;

        if (shortingAssetPosition < 0) {
            // Already negative on short side, so the entire trading amount will be borrowed
            amountToBorrow = params.amountToTrade;
        } else {
            // Not negative on short side: there may be something to sell before borrowing
            if (uint256(shortingAssetPosition) < params.amountToTrade) {
                amountToBorrow = params.amountToTrade - uint256(shortingAssetPosition);
            }
            // else, curr position is long and has enough to fill the trade
        }


        // check available reserve
        if (amountToBorrow > 0) {
            require(amountToBorrow <= params.currShortReserveAvailableSupply, Errors.NOT_ENOUGH_POOL_BALANCE);
        }
    }
}

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

import "../../types/DataTypes.sol";

library LedgerStorage {
    bytes32 constant ASSET_STORAGE_HASH = keccak256("asset_storage");
    bytes32 constant RESERVE_STORAGE_HASH = keccak256("reserve_storage");
    bytes32 constant COLLATERAL_STORAGE_HASH = keccak256("collateral_storage");
    bytes32 constant PROTOCOL_CONFIG_HASH = keccak256("protocol_config");
    bytes32 constant MAPPING_STORAGE_HASH = keccak256("mapping_storage");

    function getAssetStorage() internal pure returns (DataTypes.AssetStorage storage assetStorage) {
        bytes32 hash = ASSET_STORAGE_HASH;
        assembly {assetStorage.slot := hash}
    }

    function getReserveStorage() internal pure returns (DataTypes.ReserveStorage storage rs) {
        bytes32 hash = RESERVE_STORAGE_HASH;
        assembly {rs.slot := hash}
    }

    function getCollateralStorage() internal pure returns (DataTypes.CollateralStorage storage cs) {
        bytes32 hash = COLLATERAL_STORAGE_HASH;
        assembly {cs.slot := hash}
    }

    function getProtocolConfig() internal pure returns (DataTypes.ProtocolConfig storage pc) {
        bytes32 hash = PROTOCOL_CONFIG_HASH;
        assembly {pc.slot := hash}
    }

    function getMappingStorage() internal pure returns (DataTypes.MappingStorage storage ms) {
        bytes32 hash = MAPPING_STORAGE_HASH;
        assembly {ms.slot := hash}
    }
}

// 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 13 of 21 : ISwapAdapter.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

interface ISwapAdapter {
    function swap(
        address selling,
        address buying,
        uint256 amount,
        bytes memory data
    ) external returns (uint256);
}

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

interface IPriceOracleGetter {
    function getAssetPrice(address asset) external view returns (uint256, uint256);
}

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

interface IReinvestmentProxy {
    function owner() external view returns (address);

    function logic() external view returns (address);

    function setLogic() external view returns (address);

    function supportedInterfaceId() external view returns (bytes4);
}

interface IReinvestmentLogic {

    event UpdatedTreasury(address oldAddress, address newAddress);
    event UpdatedFeeMantissa(uint256 oldFee, uint256 newFee);

    struct Reward {
        address asset;
        uint256 claimable;
    }

    function setTreasury(address treasury_) external;

    function setFeeMantissa(uint256 feeMantissa_) external;

    function asset() external view returns (address);

    function treasury() external view returns (address);

    function ledger() external view returns (address);

    function feeMantissa() external view returns (uint256);

    function receipt() external view returns (address);

    function platform() external view returns (address);

    function rewardOf(address, uint256) external view returns (Reward[] memory);

    function rewardLength() external view returns (uint256);

    function totalSupply() external view returns (uint256);

    function claim(address, uint256) external;

    function checkpoint(address, uint256) external;

    function invest(uint256) external;

    function divest(uint256) external;

    function emergencyWithdraw() external returns (uint256);

    function sweep(address) external;
}

interface IReinvestment is IReinvestmentProxy, IReinvestmentLogic {}

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

interface IBonusPool {
    function updatePoolUser(address _token, address _account, uint256 _amount) external;
}

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

import "../types/DataTypes.sol";

interface IUserData {
    function depositReserve(
        address user,
        uint256 pid,
        uint256 amount,
        uint256 decimals,
        uint256 currReserveSupply
    ) external;

    function withdrawReserve(
        address user,
        uint256 pid,
        uint256 amount,
        uint256 decimals,
        uint256 currReserveSupply
    ) external;

    function depositCollateral(
        address user,
        uint256 pid,
        uint256 amount,
        uint256 decimals,
        uint256 currReserveSupply
    ) external;

    function withdrawCollateral(
        address user,
        uint256 pid,
        uint256 amount,
        uint256 currReserveSupply,
        uint256 decimals
    ) external;

    function changePosition(
        address user,
        uint256 pid,
        int256 incomingPosition,
        uint256 borrowIndex,
        uint256 decimals
    ) external;

    function getUserConfiguration(address user) external view returns (DataTypes.UserConfiguration memory);

    function getUserReserve(address user, address asset, bool claimable) external view returns (uint256);

    function getUserCollateral(address user, address asset, address reinvestment, bool claimable) external view returns (uint256);

    function getUserCollateralInternal(address user, uint256 pid, uint256 currPoolSupply, uint256 decimals) external view returns (uint256);

    function getUserPosition(address user, address asset) external view returns (int256);

    function getUserPositionInternal(address user, uint256 pid, uint256 borrowIndex, uint256 decimals) external view returns (int256);
}

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

import "./MathUtils.sol";

library InterestUtils {
    using MathUtils for uint256;

    uint256 public constant VERSION = 1;

    uint256 internal constant SECONDS_PER_YEAR = 365 days;

    /**
   * @notice Function to calculate the interest using a compounded interest rate formula
   * @param rate The interest rate, in ray
   * @param lastUpdateTimestamp The timestamp of the last update of the interest
   * @return The interest rate compounded during the timeDelta, in ray
   **/
    function getCompoundedInterest(
        uint256 rate,
        uint256 lastUpdateTimestamp,
        uint256 currentTimestamp
    ) internal pure returns (uint256) {
        uint256 exp = currentTimestamp - lastUpdateTimestamp;

        if (exp == 0) {
            return MathUtils.RAY;
        }

        uint256 expMinusOne = exp - 1;

        uint256 expMinusTwo = exp > 2 ? exp - 2 : 0;

        uint256 ratePerSecond = rate / SECONDS_PER_YEAR;

        uint256 basePowerTwo = ratePerSecond.rayMul(ratePerSecond);
        uint256 basePowerThree = basePowerTwo.rayMul(ratePerSecond);

        uint256 secondTerm = exp * expMinusOne * basePowerTwo / 2;
        uint256 thirdTerm = exp * expMinusOne * expMinusTwo * basePowerThree / 6;

        return MathUtils.RAY + (ratePerSecond * exp) + secondTerm + thirdTerm;
    }
}

File 19 of 21 : Errors.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

library Errors {
    string public constant LEDGER_INITIALIZED = 'LEDGER_INITIALIZED';
    string public constant CALLER_NOT_OPERATOR = 'CALLER_NOT_OPERATOR';
    string public constant CALLER_NOT_LIQUIDATE_EXECUTOR = 'CALLER_NOT_LIQUIDATE_EXECUTOR';
    string public constant CALLER_NOT_CONFIGURATOR = 'CALLER_NOT_CONFIGURATOR';
    string public constant CALLER_NOT_WHITELISTED = 'CALLER_NOT_WHITELISTED';
    string public constant CALLER_NOT_LEDGER = 'ONLY_LEDGER';
    string public constant INVALID_LEVERAGE_FACTOR = 'INVALID_LEVERAGE_FACTOR';
    string public constant INVALID_LIQUIDATION_RATIO = 'INVALID_LIQUIDATION_RATIO';
    string public constant INVALID_TRADE_FEE = 'INVALID_TRADE_FEE';
    string public constant INVALID_ZERO_ADDRESS = 'INVALID_ZERO_ADDRESS';
    string public constant INVALID_ASSET_CONFIGURATION = 'INVALID_ASSET_CONFIGURATION';
    string public constant ASSET_INACTIVE = 'ASSET_INACTIVE';
    string public constant ASSET_ACTIVE = 'ASSET_ACTIVE';
    string public constant POOL_INACTIVE = 'POOL_INACTIVE';
    string public constant POOL_ACTIVE = 'POOL_ACTIVE';
    string public constant POOL_EXIST = 'POOL_EXIST';
    string public constant INVALID_POOL_REINVESTMENT = 'INVALID_POOL_REINVESTMENT';
    string public constant ASSET_INITIALIZED = 'ASSET_INITIALIZED';
    string public constant ASSET_NOT_INITIALIZED = 'ASSET_NOT_INITIALIZED';
    string public constant POOL_INITIALIZED = 'POOL_INITIALIZED';
    string public constant POOL_NOT_INITIALIZED = 'POOL_NOT_INITIALIZED';
    string public constant INVALID_ZERO_AMOUNT = 'INVALID_ZERO_AMOUNT';
    string public constant CANNOT_SWEEP_REGISTERED_ASSET = 'CANNOT_SWEEP_REGISTERED_ASSET';
    string public constant INVALID_ACTION_ID = 'INVALID_ACTION_ID';
    string public constant INVALID_POSITION_TYPE = 'INVALID_POSITION_TYPE';
    string public constant INVALID_AMOUNT_INPUT = 'INVALID_AMOUNT_INPUT';
    string public constant INVALID_ASSET_INPUT = 'INVALID_ASSET_INPUT';
    string public constant INVALID_SWAP_BUFFER_LIMIT = 'INVALID_SWAP_BUFFER_LIMIT';
    string public constant NOT_ENOUGH_BALANCE = 'NOT_ENOUGH_BALANCE';
    string public constant NOT_ENOUGH_LONG_BALANCE = 'NOT_ENOUGH_LONG_BALANCE';
    string public constant NOT_ENOUGH_POOL_BALANCE = 'NOT_ENOUGH_POOL_BALANCE';
    string public constant NOT_ENOUGH_USER_LEVERAGE = 'NOT_ENOUGH_USER_LEVERAGE';
    string public constant MISSING_UNDERLYING_ASSET = 'MISSING_UNDERLYING_ASSET';
    string public constant NEGATIVE_PNL = 'NEGATIVE_PNL';
    string public constant NEGATIVE_AVAILABLE_LEVERAGE = 'NEGATIVE_AVAILABLE_LEVERAGE';
    string public constant BAD_TRADE = 'BAD_TRADE';
    string public constant USER_TRADE_BLOCK = 'USER_TRADE_BLOCK';
    string public constant ERROR_EMERGENCY_WITHDRAW = 'ERROR_EMERGENCY_WITHDRAW';
    string public constant ERROR_UNWRAP_LP = 'ERROR_UNWRAP_LP';
    string public constant CANNOT_TRADE_SAME_ASSET = 'CANNOT_TRADE_SAME_ASSET';
}

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

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

library HelpersLogic {
    using SafeERC20Upgradeable for IERC20Upgradeable;

    function approveMax(address asset, address spender, uint256 minAmount) internal {
        uint256 currAllowance = IERC20Upgradeable(asset).allowance(address(this), spender);

        if (currAllowance < minAmount) {
            IERC20Upgradeable(asset).safeApprove(spender, 0);
            IERC20Upgradeable(asset).safeApprove(spender, type(uint256).max);
        }
    }
}

File 21 of 21 : CollateralLogic.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import "../math/MathUtils.sol";
import "../../types/DataTypes.sol";

library CollateralLogic {
    using MathUtils for uint256;

    function getCollateralSupply(
        DataTypes.CollateralData memory collateral
    ) internal view returns (uint256){
        return collateral.reinvestment == address(0)
        ? collateral.liquidSupply
        : IReinvestment(collateral.reinvestment).totalSupply();
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 100
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {
    "contracts/libraries/logic/GeneralLogic.sol": {
      "GeneralLogic": "0x70b995a9e0ae732495368346d131a3947d404436"
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"shortAsset","type":"address"},{"indexed":true,"internalType":"address","name":"longAsset","type":"address"},{"indexed":false,"internalType":"uint256","name":"soldAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"boughtAmount","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"shortAssetPrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"longAssetPrice","type":"uint256"}],"name":"Trade","type":"event"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]

61443f61003a600b82828239805160001a60731461002d57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106100615760003560e01c80633f8eec42146100665780634b79845414610088578063e2f414b0146100a8578063f539a676146100c8578063ffa1ad7414610102575b600080fd5b81801561007257600080fd5b50610086610081366004613c0d565b610118565b005b81801561009457600080fd5b506100866100a3366004613c8c565b611358565b8180156100b457600080fd5b506100866100c3366004613dab565b611d18565b8180156100d457600080fd5b506100e86100e3366004613e14565b611ffe565b604080519283526020830191909152015b60405180910390f35b61010a600481565b6040519081526020016100f9565b6001600160a01b03851660009081527f301c5fb73b58cb5622cfce1716564fcba57dd85d3ee1f87fde02505244afeecf6020526040902054610158613819565b6040805160e0810182527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f8e546001600160a01b0390811682527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f8f54811660208301527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f905416918101919091527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f915460608201527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f92546080808301919091527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f935460a08301527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f945460c083015282015261028f6122f0565b6001600160a01b0387166000908152600191909101602052604090205460e08201526102b96122f0565b6001600160a01b038616600090815260019190910160205260408120546101008301526102e46122f0565b60e083015160009081526002919091016020526040812091506103056122f0565b60020160008461010001518152602001908152602001600020905061032982612314565b61033281612314565b61033a6124ba565b6001600160a01b038916600090815260029182016020908152604091829020825160a08101845281548152600182015460ff8082169483019490945290949193850192610100909104169081111561039457610394613e6b565b60028111156103a5576103a5613e6b565b815260018201546001600160a01b03620100009091048116602083015260029092015490911660409091015283526103db6124ba565b6001600160a01b038816600090815260029182016020908152604091829020825160a08101845281548152600182015460ff8082169483019490945290949193850192610100909104169081111561043557610435613e6b565b600281111561044657610446613e6b565b815260018201546201000090046001600160a01b039081166020808401919091526002909301548116604092830152918601929092529051639c8d8cf160e01b81528a82166004820152898216602482015290881660448201527370b995a9e0ae732495368346d131a3947d40443690639c8d8cf1906064016101c06040518083038186803b1580156104d857600080fd5b505af41580156104ec573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105109190613efb565b60c085015260a0840152610523826124de565b6040840152610531816124de565b6060840152604080516102608101909152825463ffffffff8082166101608401908152600160201b83048216610180850152600160401b830482166101a0850152600160601b830482166101c0850152600160801b830482166101e0850152600160a01b83049091166102008401526106be9291859183918390610220840190600160c01b900460ff1660028111156105cc576105cc613e6b565b60028111156105dd576105dd613e6b565b81528154602090910190600160c81b900460ff16600381111561060257610602613e6b565b600381111561061357610613613e6b565b9052508152604080516060808201835260018501546001600160a01b03908116835260028601548116602084810191909152600387015482168486015285019290925260048501549091169183019190915260058301549082015260068201546080820152600782015460a0820152600882015460c0820152600982015460e0820152600a820154610100820152600b820154610120820152600c9091015461014090910152612543565b5050505061014084015260c0830151604001516107685782516080015160405163b3596f0760e01b81526001600160a01b039091169063b3596f0790610708908b90600401613f99565b604080518083038186803b15801561071f57600080fd5b505afa158015610733573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107579190613fad565b610180850152610160840152610783565b60c08301516040810151610160850152606001516101808401525b60c083015160800151610824578260200151608001516001600160a01b031663b3596f07886040518263ffffffff1660e01b81526004016107c49190613f99565b604080518083038186803b1580156107db57600080fd5b505afa1580156107ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108139190613fad565b6101c08501526101a084015261083f565b60c083015160808101516101a085015260a001516101c08401525b60008360a0015160c0015113610856576000610860565b8260a0015160c001515b61020084015260c08301515160001261087a57600061091b565b60c08301515183516020015161016085015161018086015160405163867a7d4760e01b81527370b995a9e0ae732495368346d131a3947d4044369463867a7d47946108cb9491939092600401613fd1565b60206040518083038186803b1580156108e357600080fd5b505af41580156108f7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061091b9190613fef565b61022084015260c0830151602001516000136109385760006109ec565b7370b995a9e0ae732495368346d131a3947d40443663867a7d478460c0015160200151600019610968919061401e565b856020015160200151866101a00151876101c001516040518563ffffffff1660e01b815260040161099c9493929190613fd1565b60206040518083038186803b1580156109b457600080fd5b505af41580156109c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109ec9190613fef565b6102408401819052610220840151610200850151610a0a91906140a3565b610a1491906140a3565b6102608401819052835160200151610160850151610180860151604051630d82217f60e21b81527370b995a9e0ae732495368346d131a3947d4044369463360885fc94610a679491939092600401613fd1565b60206040518083038186803b158015610a7f57600080fd5b505af4158015610a93573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ab79190613fef565b6101e084810191909152604080516102608101909152835463ffffffff8082166101608401908152600160201b83048216610180850152600160401b830482166101a0850152600160601b830482166101c0850152600160801b8304821694840194909452600160a01b820416610200830152610e8c9285918391908390610220840190600160c01b900460ff166002811115610b5657610b56613e6b565b6002811115610b6757610b67613e6b565b81528154602090910190600160c81b900460ff166003811115610b8c57610b8c613e6b565b6003811115610b9d57610b9d613e6b565b9052508152604080516060808201835260018501546001600160a01b039081168352600280870154821660208086019190915260038801548316858701528601939093526004860154168385015260058501549084015260068401546080840152600784015460a0840152600884015460c0840152600984015460e0840152600a840154610100840152600b840154610120840152600c909301546101409092019190915280516102608101909152845463ffffffff8082166101608401908152600160201b83048216610180850152600160401b830482166101a0850152600160601b830482166101c0850152600160801b830482166101e0850152600160a01b830490911661020084015291928692849290918491610220850191600160c01b90910460ff1690811115610cd557610cd5613e6b565b6002811115610ce657610ce6613e6b565b81528154602090910190600160c81b900460ff166003811115610d0b57610d0b613e6b565b6003811115610d1c57610d1c613e6b565b815250508152602001600182016040518060600160405290816000820160009054906101000a90046001600160a01b03166001600160a01b03166001600160a01b031681526020016001820160009054906101000a90046001600160a01b03166001600160a01b03166001600160a01b031681526020016002820160009054906101000a90046001600160a01b03166001600160a01b03166001600160a01b03168152505081526020016004820160009054906101000a90046001600160a01b03166001600160a01b03166001600160a01b031681526020016005820154815260200160068201548152602001600782015481526020016008820154815260200160098201548152602001600a8201548152602001600b8201548152602001600c820154815250508560c00151600001516040518060a001604052808e6001600160a01b031681526020018b81526020018861014001518152602001886101e001518152602001898152506126eb565b825160c084015151610ea2918491896001611ffe565b50508260800151604001516001600160a01b031663d67622ff8a8560e0015189600019610ecf919061401e565b60408089015160a0015189516020015191516001600160e01b031960e088901b168152610f0295949392906004016140bb565b600060405180830381600087803b158015610f1c57600080fd5b505af1158015610f30573d6000803e3d6000fd5b50505050610f3d82612aa2565b6080808401518051910151610f54918a9189612ba7565b610f5e90876140ec565b9550610f71836000015189898989612be3565b61012084015282516020015161016084015161018085015160405163867a7d4760e01b81526000937370b995a9e0ae732495368346d131a3947d4044369363867a7d4793610fc6938d93929190600401613fd1565b60206040518083038186803b158015610fde57600080fd5b505af4158015610ff2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110169190613fef565b905060007370b995a9e0ae732495368346d131a3947d40443663867a7d47866101200151876020015160200151886101a00151896101c001516040518563ffffffff1660e01b815260040161106e9493929190613fd1565b60206040518083038186803b15801561108657600080fd5b505af415801561109a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110be9190613fef565b90506110ca8282614103565b8560a001516080018181516110df9190614142565b90525060a0808601518051608080890151909301519290910151604051634b3ad96760e01b81527370b995a9e0ae732495368346d131a3947d40443693634b3ad967936111419390926004019283526020830191909152604082015260600190565b60206040518083038186803b15801561115957600080fd5b505af415801561116d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111919190614183565b6040805180820190915260098152684241445f545241444560b81b602082015290156111d95760405162461bcd60e51b81526004016111d091906141fc565b60405180910390fd5b506112028386602001518760800151600001518860c00151602001518961012001516001611d18565b8460800151604001516001600160a01b031663d67622ff8c876101000151886101200151896060015160a001518a60200151602001516040518663ffffffff1660e01b81526004016112589594939291906140bb565b600060405180830381600087803b15801561127257600080fd5b505af1158015611286573d6000803e3d6000fd5b5050505061129383612aa2565b437f301c5fb73b58cb5622cfce1716564fcba57dd85d3ee1f87fde02505244afeece60010160008d6001600160a01b03166001600160a01b0316815260200190815260200160002081905550886001600160a01b03168a6001600160a01b03168c6001600160a01b03167fd7118a889d6b39069ba246fd075a16611775364aead2ee9378d3b74e0787fe8f8b8961012001518c8b61016001518c6101a0015160405161134395949392919061420f565b60405180910390a45050505050505050505050565b61136061394f565b6040805160e0810182527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f8e546001600160a01b0390811682527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f8f54811660208301527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f905416918101919091527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f915460608201527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f925460808201527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f935460a08201527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f945460c082015281526114926124ba565b6001600160a01b038616600090815260029182016020908152604091829020825160a08101845281548152600182015460ff808216948301949094529094919385019261010090910416908111156114ec576114ec613e6b565b60028111156114fd576114fd613e6b565b815260018201546001600160a01b03620100009091048116602080840191909152600290930154166040909101528201526115366124ba565b6001600160a01b038516600090815260029182016020908152604091829020825160a08101845281548152600182015460ff8082169483019490945290949193850192610100909104169081111561159057611590613e6b565b60028111156115a1576115a1613e6b565b815260018201546001600160a01b0362010000909104811660208301526002909201549091166040918201528201526115d86122f0565b6001600160a01b0386166000908152600191909101602052604090205460a08201526116026122f0565b6001600160a01b0385166000908152600191909101602052604081205460c083015261162c6122f0565b60a0830151600090815260029190910160205260408120915061164d6122f0565b60020160008460c001518152602001908152602001600020905061167082612314565b61167981612314565b611682826124de565b6060840152611690816124de565b83608001819052508260200151608001516001600160a01b031663b3596f07886040518263ffffffff1660e01b81526004016116cc9190613f99565b604080518083038186803b1580156116e357600080fd5b505afa1580156116f7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061171b9190613fad565b61018085015261016084015260408084015160800151905163b3596f0760e01b81526001600160a01b039091169063b3596f079061175d908990600401613f99565b604080518083038186803b15801561177457600080fd5b505afa158015611788573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117ac9190613fad565b6101c08501526101a084015282516040908101519051634952bb7360e11b81526001600160a01b03909116906392a576e6906117ef906001908b90600401614242565b60206040518083038186803b15801561180757600080fd5b505afa15801561181b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061183f9190613fef565b60e084015282516040908101519051634952bb7360e11b81526001600160a01b03909116906392a576e69061187b906001908a90600401614242565b60206040518083038186803b15801561189357600080fd5b505afa1580156118a7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118cb9190613fef565b610100840152602083015160e08401516118e9918491886000611ffe565b6101208501819052159050611b41576000836020015160400151600281111561191457611914613e6b565b14604051806040016040528060138152602001721253959053125117d054d4d15517d253941555606a1b8152509061195f5760405162461bcd60e51b81526004016111d091906141fc565b50611ae683602001516020015160ff16611ae0856000015160c00151611ada87602001516020015160ff167370b995a9e0ae732495368346d131a3947d40443663360885fc7370b995a9e0ae732495368346d131a3947d40443663867a7d476119cc8d6101000151612d38565b8d60400151602001518e6101a001518f6101c001516040518563ffffffff1660e01b8152600401611a009493929190613fd1565b60206040518083038186803b158015611a1857600080fd5b505af4158015611a2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a509190613fef565b8b60200151602001518c61016001518d61018001516040518563ffffffff1660e01b8152600401611a849493929190613fd1565b60206040518083038186803b158015611a9c57600080fd5b505af4158015611ab0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ad49190613fef565b90612d5d565b90612dc8565b90612e64565b61014084018190526101208401516040805180820190915260148152731253959053125117d05353d5539517d25394155560621b6020820152911015611b3f5760405162461bcd60e51b81526004016111d091906141fc565b505b8260000151604001516001600160a01b031663d67622ff60018560a0015188600019611b6d919061401e565b876060015160a001518860200151602001516040518663ffffffff1660e01b8152600401611b9f9594939291906140bb565b600060405180830381600087803b158015611bb957600080fd5b505af1158015611bcd573d6000803e3d6000fd5b50505050611bda82612aa2565b611beb836020015188888888612be3565b6101e084018190526040840151845151610100860151611c119385939291906000611d18565b8260000151604001516001600160a01b031663d67622ff60018560c00151866101e00151876080015160a001518860400151602001516040518663ffffffff1660e01b8152600401611c679594939291906140bb565b600060405180830381600087803b158015611c8157600080fd5b505af1158015611c95573d6000803e3d6000fd5b50505050611ca281612aa2565b856001600160a01b0316876001600160a01b031660016001600160a01b03167fd7118a889d6b39069ba246fd075a16611775364aead2ee9378d3b74e0787fe8f88876101e00151898961016001518a6101a00151604051611d0795949392919061420f565b60405180910390a450505050505050565b611d206139ea565b6004808801546040516370a0823160e01b815285926001600160a01b03909216916370a0823191611d5391309101613f99565b60206040518083038186803b158015611d6b57600080fd5b505afa158015611d7f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611da39190613fef565b101560405180604001604052806018815260200177135254d4d25391d7d5539111549316525391d7d054d4d15560421b81525090611df45760405162461bcd60e51b81526004016111d091906141fc565b50611dfe876124de565b60808201526000841215611e51576000611e1785612d38565b905080841115611e3c5760408201819052611e3281856140ec565b6020830152611e4b565b60006020830152604082018490525b50611e59565b602081018390525b60008160200151118015611e6a5750815b15611ec5578060200151876008016000828254611e8791906140a3565b909155505060028701546001600160a01b031615611ec557600487015460028801546020830151611ec5926001600160a01b03908116921690612ea5565b604081015115611ff557611f22866020015160ff16611f1c836080015160800151611f16856080015160a00151611f108c6020015160ff168860400151612fab90919063ffffffff16565b90612fe5565b906130af565b9061312c565b8082526004880154611f41916001600160a01b03909116908790613166565b611f6b816080015160a00151611f10886020015160ff168460400151612fab90919063ffffffff16565b876007016000828254611f7e91906140ec565b90915550508051604082018051611f969083906140ec565b90525060018701546001600160a01b031615611fd757600487015460018801546040830151611fd2926001600160a01b03908116921690612ea5565b611ff5565b8060400151876006016000828254611fef91906140a3565b90915550505b50505050505050565b600080612009613a1e565b602087015160ff16815261201c886124de565b60a082015260008612156120365760208101859052612077565b600061204187612d38565b905080861115612066576040820181905261205c81876140ec565b6020830152612075565b60408201869052600060208301525b505b600081604001511180156120885750835b156121295780604001518860080160008282546120a591906140ec565b909155505060028801546001600160a01b0316156121295760028801546040808301519051638ca1799560e01b81526001600160a01b0390921691638ca17995916120f69160040190815260200190565b600060405180830381600087803b15801561211057600080fd5b505af1158015612124573d6000803e3d6000fd5b505050505b60208101511561220a5760a0808201510151815160208301516121519291611f109190612fab565b88600701600082825461216491906140a3565b909155505060018801546001600160a01b0316156121ec5760018801546020820151604051638ca1799560e01b81526001600160a01b0390921691638ca17995916121b59160040190815260200190565b600060405180830381600087803b1580156121cf57600080fd5b505af11580156121e3573d6000803e3d6000fd5b5050505061220a565b806020015188600601600082825461220491906140ec565b90915550505b6004808901546040516370a0823160e01b815287926001600160a01b03909216916370a082319161223d91309101613f99565b60206040518083038186803b15801561225557600080fd5b505afa158015612269573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061228d9190613fef565b1015604051806040016040528060178152602001764e4f545f454e4f5547485f504f4f4c5f42414c414e434560481b815250906122dd5760405162461bcd60e51b81526004016111d091906141fc565b5060200151939793965092945050505050565b7f31086be12a0da5cefd664dbc36245bc20218d0358b0623169ff6fd924d16042390565b604080516102608101909152815463ffffffff8082166101608401908152600160201b83048216610180850152600160401b830482166101a0850152600160601b830482166101c0850152600160801b830482166101e0850152600160a01b830490911661020084015260009283926124a29286918391908390610220840190600160c01b900460ff1660028111156123af576123af613e6b565b60028111156123c0576123c0613e6b565b81528154602090910190600160c81b900460ff1660038111156123e5576123e5613e6b565b60038111156123f6576123f6613e6b565b9052508152604080516060808201835260018501546001600160a01b03908116835260028601548116602084810191909152600387015482168486015285019290925260048501549091169183019190915260058301549082015260068201546080820152600782015460a0820152600882015460c0820152600982015460e0820152600a820154610100820152600b820154610120820152600c9091015461014090910152426131ce565b6009850191909155600b840155505042600c90910155565b7fb0511cf1f9df08d578b30a0c7a6e3581a567d2c1214b3100e67d9f9401553afd90565b6124e6613a50565b6124ee613a50565b60048301546001600160a01b0390811682526001840154811660208301526002840154166040820152600983015460608201819052600b84015460808301819052612538916140a3565b60a082015292915050565b6000806000806000806125546124ba565b6040808901516001600160a01b039081166000908152600293909301602090815291832060010154918a01515160ff9092169350166125a357608088015161259c90826140a3565b9050612629565b8760200151600001516001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b1580156125e457600080fd5b505afa1580156125f8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061261c9190613fef565b61262690826140a3565b90505b6000806126368a426131ce565b915091506000612653838c60a001516130af90919063ffffffff16565b9050600061266e838d60a001516130af90919063ffffffff16565b9050600061267c838861312c565b61268690876140a3565b9050600061269483856140a3565b90506000816126a3898b612fab565b6126ad91906140a3565b905087836126bb868c61312c565b6126c5848d61312c565b6126cf868e61312c565b9d509d509d509d509d5050505050505050505091939590929450565b82604001516001600160a01b031684604001516001600160a01b031614156040518060400160405280601781526020017610d0539393d517d51490511157d4d0535157d054d4d155604a1b815250906127575760405162461bcd60e51b81526004016111d091906141fc565b506003845160e00151600381111561277157612771613e6b565b148061279357506001845160e00151600381111561279157612791613e6b565b145b6127d45760405162461bcd60e51b8152602060048201526012602482015271185cdcd95d0818d85b9b9bdd081cda1bdc9d60721b60448201526064016111d0565b6003835160e0015160038111156127ed576127ed613e6b565b148061280f57506002835160e00151600381111561280d5761280d613e6b565b145b61284f5760405162461bcd60e51b815260206004820152601160248201527061737365742063616e6e6f74206c6f6e6760781b60448201526064016111d0565b6001845160c00151600281111561286857612868613e6b565b146040518060400160405280600d81526020016c504f4f4c5f494e41435449564560981b815250906128ad5760405162461bcd60e51b81526004016111d091906141fc565b506001835160c0015160028111156128c7576128c7613e6b565b146040518060400160405280600d81526020016c504f4f4c5f494e41435449564560981b8152509061290c5760405162461bcd60e51b81526004016111d091906141fc565b5043816080015114156040518060400160405280601081526020016f555345525f54524144455f424c4f434b60801b8152509061295c5760405162461bcd60e51b81526004016111d091906141fc565b50806020015160001415604051806040016040528060138152602001721253959053125117d6915493d7d05353d55395606a1b815250906129b05760405162461bcd60e51b81526004016111d091906141fc565b50806060015181602001511115604051806040016040528060188152602001774e4f545f454e4f5547485f555345525f4c4556455241474560401b81525090612a0c5760405162461bcd60e51b81526004016111d091906141fc565b50600080831215612a2257506020810151612a41565b8160200151831015612a4157828260200151612a3e91906140ec565b90505b8015612a9b57604080830151815180830190925260178252764e4f545f454e4f5547485f504f4f4c5f42414c414e434560481b6020830152821115612a995760405162461bcd60e51b81526004016111d091906141fc565b505b5050505050565b6000612aac6124ba565b60048301546001600160a01b03166000908152600291820160205260408082206001015481516102608101909252855463ffffffff8082166101608501908152600160201b83048216610180860152600160401b830482166101a0860152600160601b830482166101c0860152600160801b830482166101e0860152600160a01b830490911661020085015260ff928316965093948594612b6c9493899385938592610220860192600160c01b900416908111156105cc576105cc613e6b565b9450945050505060008211612b82576000612b99565b612b99612b8f8385612fab565b611f108386612fab565b84600a018190555050505050565b600082612bb657506000612bdb565b6000612bc28385612dc8565b9050612bd86001600160a01b0387168683613166565b90505b949350505050565b6060850151604051636eb1769f60e11b815260009184916001600160a01b0388169163dd62ed3e91612c19913091600401614242565b60206040518083038186803b158015612c3157600080fd5b505afa158015612c45573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c699190613fef565b1015612ca6576060860151612c8a906001600160a01b038716906000613381565b6060860151612ca6906001600160a01b03871690600019613381565b85606001516001600160a01b03166343a0a7f2868686866040518563ffffffff1660e01b8152600401612cdc949392919061425c565b602060405180830381600087803b158015612cf657600080fd5b505af1158015612d0a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d2e9190613fef565b9695505050505050565b600080821215612d5457612d4e8260001961401e565b92915050565b5090565b919050565b6000821580612d6c5750601282145b15612d78575081612d4e565b6012821015612da857612d8c8260126140ec565b612d9790600a614373565b612da1908461437f565b9050612d4e565b612db36012836140ec565b612dbe90600a614373565b612da1908461439e565b6000821580612dd5575081155b15612de257506000612d4e565b81612df66002670de0b6b3a764000061439e565b612e02906000196140ec565b612e0c919061439e565b831115612e2b5760405162461bcd60e51b81526004016111d0906143c0565b670de0b6b3a7640000612e3f60028261439e565b612e49848661437f565b612e5391906140a3565b612e5d919061439e565b9392505050565b6000821580612e7a5750670de0b6b3a764000082145b15612e86575081612d4e565b6012821015612e9a57612db38260126140ec565b612d8c6012836140ec565b604051636eb1769f60e11b815281906001600160a01b0385169063dd62ed3e90612ed59030908790600401614242565b60206040518083038186803b158015612eed57600080fd5b505afa158015612f01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f259190613fef565b1015612f5657612f406001600160a01b038416836000613381565b612f566001600160a01b03841683600019613381565b6040516255f9e960e71b8152600481018290526001600160a01b03831690632afcf48090602401600060405180830381600087803b158015612f9757600080fd5b505af1158015611ff5573d6000803e3d6000fd5b6000821580612fba5750601b82145b15612fc6575081612d4e565b601b821015612fda57612d8c82601b6140ec565b612db3601b836140ec565b6000816130345760405162461bcd60e51b815260206004820152601b60248201527f4d6174685574696c733a206469766973696f6e206279207a65726f000000000060448201526064016111d0565b600061304160028461439e565b9050676765c793fa10079d601b1b61305b826000196140ec565b613065919061439e565b8411156130845760405162461bcd60e51b81526004016111d0906143c0565b828161309b676765c793fa10079d601b1b8761437f565b6130a591906140a3565b612bdb919061439e565b60008215806130bc575081155b156130c957506000612d4e565b816130e06002676765c793fa10079d601b1b61439e565b6130ec906000196140ec565b6130f6919061439e565b8311156131155760405162461bcd60e51b81526004016111d0906143c0565b676765c793fa10079d601b1b612e3f60028261439e565b600082158061313b5750601b82145b15613147575081612d4e565b601b82101561315b57612db382601b6140ec565b612d8c601b836140ec565b6040516001600160a01b0383166024820152604481018290526131c990849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526134a4565b505050565b600080836101000151600014156131f157505060e082015161012083015161337a565b60008461012001518560e0015161320891906140a3565b905060006132ac866101000151613237600989600001516020015163ffffffff16612fab90919063ffffffff16565b8851604001516132539063ffffffff90811690600990612fab16565b89516060015161326f9063ffffffff90811690600990612fab16565b8a516080015161328b9063ffffffff90811690600990612fab16565b8b5160a001516132a79063ffffffff90811690600990612fab16565b613576565b9050806132c9578560e0015186610120015193509350505061337a565b60006132db82886101400151886135e8565b90506000836132ea81846130af565b6132f491906140ec565b9050600061331e84611f1060098c600001516020015163ffffffff16612fab90919063ffffffff16565b9050600061332c83836130af565b8a610120015161333c91906140a3565b9050600061335f61335884676765c793fa10079d601b1b6140ec565b85906130af565b8b60e0015161336e91906140a3565b98509096505050505050505b9250929050565b8015806134095750604051636eb1769f60e11b81526001600160a01b0384169063dd62ed3e906133b79030908690600401614242565b60206040518083038186803b1580156133cf57600080fd5b505afa1580156133e3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134079190613fef565b155b6134745760405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b60648201526084016111d0565b6040516001600160a01b0383166024820152604481018290526131c990849063095ea7b360e01b90606401613192565b60006134f9826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166137059092919063ffffffff16565b8051909150156131c957808060200190518101906135179190614183565b6131c95760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016111d0565b6000808488116135915761358a88856130af565b90506135c7565b600061359d86866130af565b905060006135ab878b6140ec565b9050816135b882876130af565b6135c291906140a3565b925050505b866135d287836140a3565b6135dc91906140a3565b98975050505050505050565b6000806135f584846140ec565b90508061361057676765c793fa10079d601b1b915050612e5d565b600061361d6001836140ec565b905060006002831161363057600061363b565b61363b6002846140ec565b9050600061364d6301e133808961439e565b9050600061365b82806130af565b9050600061366982846130af565b9050600060028361367a888a61437f565b613684919061437f565b61368e919061439e565b90506000600683876136a08a8c61437f565b6136aa919061437f565b6136b4919061437f565b6136be919061439e565b905080826136cc8a8861437f565b6136e190676765c793fa10079d601b1b6140a3565b6136eb91906140a3565b6136f591906140a3565b9c9b505050505050505050505050565b6060612bdb8484600085856001600160a01b0385163b6137675760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016111d0565b600080866001600160a01b0316858760405161378391906143ed565b60006040518083038185875af1925050503d80600081146137c0576040519150601f19603f3d011682016040523d82523d6000602084013e6137c5565b606091505b50915091506137d58282866137e0565b979650505050505050565b606083156137ef575081612e5d565b8251156137ff5782518084602001fd5b8160405162461bcd60e51b81526004016111d091906141fc565b60405180610280016040528061382d613aa1565b815260200161383a613aa1565b8152602001613847613a50565b8152602001613854613a50565b8152602001613861613ad0565b81526020016138b0604051806101000160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000151581525090565b81526020016138ee6040518060c001604052806000815260200160008152602001600081526020016000815260200160008152602001600081525090565b8152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b604051806102000160405280613963613ad0565b8152602001613970613aa1565b815260200161397d613aa1565b815260200161398a613a50565b8152602001613997613a50565b815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040518060a0016040528060008152602001600081526020016000815260200160008152602001613a19613a50565b905290565b6040518060c001604052806000815260200160008152602001600081526020016000815260200160008152602001613a195b6040518060c0016040528060006001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b031681526020016000815260200160008152602001600081525090565b6040805160a0810182526000808252602082018190529091820190815260006020820181905260409091015290565b6040518060e0016040528060006001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b03168152602001600081526020016000815260200160008152602001600081525090565b6001600160a01b0381168114613b3d57600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b604051610100810167ffffffffffffffff81118282101715613b7a57613b7a613b40565b60405290565b600082601f830112613b9157600080fd5b813567ffffffffffffffff80821115613bac57613bac613b40565b604051601f8301601f19908116603f01168101908282118183101715613bd457613bd4613b40565b81604052838152866020858801011115613bed57600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600080600060a08688031215613c2557600080fd5b8535613c3081613b28565b94506020860135613c4081613b28565b93506040860135613c5081613b28565b925060608601359150608086013567ffffffffffffffff811115613c7357600080fd5b613c7f88828901613b80565b9150509295509295909350565b60008060008060808587031215613ca257600080fd5b8435613cad81613b28565b93506020850135613cbd81613b28565b925060408501359150606085013567ffffffffffffffff811115613ce057600080fd5b613cec87828801613b80565b91505092959194509250565b8035612d5881613b28565b600060a08284031215613d1557600080fd5b60405160a0810181811067ffffffffffffffff82111715613d3857613d38613b40565b60405282358152905080602083013560ff81168114613d5657600080fd5b6020820152604083013560038110613d6d57600080fd5b60408201526060830135613d8081613b28565b6060820152613d9160808401613cf8565b60808201525092915050565b8015158114613b3d57600080fd5b6000806000806000806101408789031215613dc557600080fd5b86359550613dd68860208901613d03565b945060c0870135613de681613b28565b935060e087013592506101008701359150610120870135613e0681613d9d565b809150509295509295509295565b60008060008060006101208688031215613e2d57600080fd5b85359450613e3e8760208801613d03565b935060c0860135925060e08601359150610100860135613e5d81613d9d565b809150509295509295909350565b634e487b7160e01b600052602160045260246000fd5b600060c08284031215613e9357600080fd5b60405160c0810181811067ffffffffffffffff82111715613eb657613eb6613b40565b8060405250809150825181526020830151602082015260408301516040820152606083015160608201526080830151608082015260a083015160a08201525092915050565b6000808284036101c0811215613f1057600080fd5b61010080821215613f2057600080fd5b613f28613b56565b9150845182526020850151602083015260408501516040830152606085015160608301526080850151608083015260a085015160a083015260c085015160c083015260e0850151613f7881613d9d565b8060e084015250819350613f8e86828701613e81565b925050509250929050565b6001600160a01b0391909116815260200190565b60008060408385031215613fc057600080fd5b505080516020909101519092909150565b93845260ff9290921660208401526040830152606082015260800190565b60006020828403121561400157600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b60006001600160ff1b038184138284138082168684048611161561404457614044614008565b600160ff1b600087128281168783058912161561406357614063614008565b6000871292508782058712848416161561407f5761407f614008565b8785058712818416161561409557614095614008565b505050929093029392505050565b600082198211156140b6576140b6614008565b500190565b6001600160a01b0395909516855260208501939093526040840191909152606083015260ff16608082015260a00190565b6000828210156140fe576140fe614008565b500390565b60008083128015600160ff1b85018412161561412157614121614008565b6001600160ff1b038401831381161561413c5761413c614008565b50500390565b600080821280156001600160ff1b038490038513161561416457614164614008565b600160ff1b839003841281161561417d5761417d614008565b50500190565b60006020828403121561419557600080fd5b8151612e5d81613d9d565b60005b838110156141bb5781810151838201526020016141a3565b838111156141ca576000848401525b50505050565b600081518084526141e88160208601602086016141a0565b601f01601f19169290920160200192915050565b602081526000612e5d60208301846141d0565b85815284602082015260a06040820152600061422e60a08301866141d0565b606083019490945250608001529392505050565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b0385811682528416602082015260408101839052608060608201819052600090612d2e908301846141d0565b600181815b808511156142ca5781600019048211156142b0576142b0614008565b808516156142bd57918102915b93841c9390800290614294565b509250929050565b6000826142e157506001612d4e565b816142ee57506000612d4e565b8160018114614304576002811461430e5761432a565b6001915050612d4e565b60ff84111561431f5761431f614008565b50506001821b612d4e565b5060208310610133831016604e8410600b841016171561434d575081810a612d4e565b614357838361428f565b806000190482111561436b5761436b614008565b029392505050565b6000612e5d83836142d2565b600081600019048311821515161561439957614399614008565b500290565b6000826143bb57634e487b7160e01b600052601260045260246000fd5b500490565b6020808252601390820152724d6174685574696c733a206f766572666c6f7760681b604082015260600190565b600082516143ff8184602087016141a0565b919091019291505056fea2646970667358221220b866795a3c666ba17741d1dfaabc480f5566a035c157ae2dab50a5c14acffbed64736f6c63430008090033

Deployed Bytecode

0x7355fa902fde96d1065f46f9b19745fc4ebf5bfea230146080604052600436106100615760003560e01c80633f8eec42146100665780634b79845414610088578063e2f414b0146100a8578063f539a676146100c8578063ffa1ad7414610102575b600080fd5b81801561007257600080fd5b50610086610081366004613c0d565b610118565b005b81801561009457600080fd5b506100866100a3366004613c8c565b611358565b8180156100b457600080fd5b506100866100c3366004613dab565b611d18565b8180156100d457600080fd5b506100e86100e3366004613e14565b611ffe565b604080519283526020830191909152015b60405180910390f35b61010a600481565b6040519081526020016100f9565b6001600160a01b03851660009081527f301c5fb73b58cb5622cfce1716564fcba57dd85d3ee1f87fde02505244afeecf6020526040902054610158613819565b6040805160e0810182527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f8e546001600160a01b0390811682527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f8f54811660208301527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f905416918101919091527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f915460608201527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f92546080808301919091527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f935460a08301527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f945460c083015282015261028f6122f0565b6001600160a01b0387166000908152600191909101602052604090205460e08201526102b96122f0565b6001600160a01b038616600090815260019190910160205260408120546101008301526102e46122f0565b60e083015160009081526002919091016020526040812091506103056122f0565b60020160008461010001518152602001908152602001600020905061032982612314565b61033281612314565b61033a6124ba565b6001600160a01b038916600090815260029182016020908152604091829020825160a08101845281548152600182015460ff8082169483019490945290949193850192610100909104169081111561039457610394613e6b565b60028111156103a5576103a5613e6b565b815260018201546001600160a01b03620100009091048116602083015260029092015490911660409091015283526103db6124ba565b6001600160a01b038816600090815260029182016020908152604091829020825160a08101845281548152600182015460ff8082169483019490945290949193850192610100909104169081111561043557610435613e6b565b600281111561044657610446613e6b565b815260018201546201000090046001600160a01b039081166020808401919091526002909301548116604092830152918601929092529051639c8d8cf160e01b81528a82166004820152898216602482015290881660448201527370b995a9e0ae732495368346d131a3947d40443690639c8d8cf1906064016101c06040518083038186803b1580156104d857600080fd5b505af41580156104ec573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105109190613efb565b60c085015260a0840152610523826124de565b6040840152610531816124de565b6060840152604080516102608101909152825463ffffffff8082166101608401908152600160201b83048216610180850152600160401b830482166101a0850152600160601b830482166101c0850152600160801b830482166101e0850152600160a01b83049091166102008401526106be9291859183918390610220840190600160c01b900460ff1660028111156105cc576105cc613e6b565b60028111156105dd576105dd613e6b565b81528154602090910190600160c81b900460ff16600381111561060257610602613e6b565b600381111561061357610613613e6b565b9052508152604080516060808201835260018501546001600160a01b03908116835260028601548116602084810191909152600387015482168486015285019290925260048501549091169183019190915260058301549082015260068201546080820152600782015460a0820152600882015460c0820152600982015460e0820152600a820154610100820152600b820154610120820152600c9091015461014090910152612543565b5050505061014084015260c0830151604001516107685782516080015160405163b3596f0760e01b81526001600160a01b039091169063b3596f0790610708908b90600401613f99565b604080518083038186803b15801561071f57600080fd5b505afa158015610733573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107579190613fad565b610180850152610160840152610783565b60c08301516040810151610160850152606001516101808401525b60c083015160800151610824578260200151608001516001600160a01b031663b3596f07886040518263ffffffff1660e01b81526004016107c49190613f99565b604080518083038186803b1580156107db57600080fd5b505afa1580156107ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108139190613fad565b6101c08501526101a084015261083f565b60c083015160808101516101a085015260a001516101c08401525b60008360a0015160c0015113610856576000610860565b8260a0015160c001515b61020084015260c08301515160001261087a57600061091b565b60c08301515183516020015161016085015161018086015160405163867a7d4760e01b81527370b995a9e0ae732495368346d131a3947d4044369463867a7d47946108cb9491939092600401613fd1565b60206040518083038186803b1580156108e357600080fd5b505af41580156108f7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061091b9190613fef565b61022084015260c0830151602001516000136109385760006109ec565b7370b995a9e0ae732495368346d131a3947d40443663867a7d478460c0015160200151600019610968919061401e565b856020015160200151866101a00151876101c001516040518563ffffffff1660e01b815260040161099c9493929190613fd1565b60206040518083038186803b1580156109b457600080fd5b505af41580156109c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109ec9190613fef565b6102408401819052610220840151610200850151610a0a91906140a3565b610a1491906140a3565b6102608401819052835160200151610160850151610180860151604051630d82217f60e21b81527370b995a9e0ae732495368346d131a3947d4044369463360885fc94610a679491939092600401613fd1565b60206040518083038186803b158015610a7f57600080fd5b505af4158015610a93573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ab79190613fef565b6101e084810191909152604080516102608101909152835463ffffffff8082166101608401908152600160201b83048216610180850152600160401b830482166101a0850152600160601b830482166101c0850152600160801b8304821694840194909452600160a01b820416610200830152610e8c9285918391908390610220840190600160c01b900460ff166002811115610b5657610b56613e6b565b6002811115610b6757610b67613e6b565b81528154602090910190600160c81b900460ff166003811115610b8c57610b8c613e6b565b6003811115610b9d57610b9d613e6b565b9052508152604080516060808201835260018501546001600160a01b039081168352600280870154821660208086019190915260038801548316858701528601939093526004860154168385015260058501549084015260068401546080840152600784015460a0840152600884015460c0840152600984015460e0840152600a840154610100840152600b840154610120840152600c909301546101409092019190915280516102608101909152845463ffffffff8082166101608401908152600160201b83048216610180850152600160401b830482166101a0850152600160601b830482166101c0850152600160801b830482166101e0850152600160a01b830490911661020084015291928692849290918491610220850191600160c01b90910460ff1690811115610cd557610cd5613e6b565b6002811115610ce657610ce6613e6b565b81528154602090910190600160c81b900460ff166003811115610d0b57610d0b613e6b565b6003811115610d1c57610d1c613e6b565b815250508152602001600182016040518060600160405290816000820160009054906101000a90046001600160a01b03166001600160a01b03166001600160a01b031681526020016001820160009054906101000a90046001600160a01b03166001600160a01b03166001600160a01b031681526020016002820160009054906101000a90046001600160a01b03166001600160a01b03166001600160a01b03168152505081526020016004820160009054906101000a90046001600160a01b03166001600160a01b03166001600160a01b031681526020016005820154815260200160068201548152602001600782015481526020016008820154815260200160098201548152602001600a8201548152602001600b8201548152602001600c820154815250508560c00151600001516040518060a001604052808e6001600160a01b031681526020018b81526020018861014001518152602001886101e001518152602001898152506126eb565b825160c084015151610ea2918491896001611ffe565b50508260800151604001516001600160a01b031663d67622ff8a8560e0015189600019610ecf919061401e565b60408089015160a0015189516020015191516001600160e01b031960e088901b168152610f0295949392906004016140bb565b600060405180830381600087803b158015610f1c57600080fd5b505af1158015610f30573d6000803e3d6000fd5b50505050610f3d82612aa2565b6080808401518051910151610f54918a9189612ba7565b610f5e90876140ec565b9550610f71836000015189898989612be3565b61012084015282516020015161016084015161018085015160405163867a7d4760e01b81526000937370b995a9e0ae732495368346d131a3947d4044369363867a7d4793610fc6938d93929190600401613fd1565b60206040518083038186803b158015610fde57600080fd5b505af4158015610ff2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110169190613fef565b905060007370b995a9e0ae732495368346d131a3947d40443663867a7d47866101200151876020015160200151886101a00151896101c001516040518563ffffffff1660e01b815260040161106e9493929190613fd1565b60206040518083038186803b15801561108657600080fd5b505af415801561109a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110be9190613fef565b90506110ca8282614103565b8560a001516080018181516110df9190614142565b90525060a0808601518051608080890151909301519290910151604051634b3ad96760e01b81527370b995a9e0ae732495368346d131a3947d40443693634b3ad967936111419390926004019283526020830191909152604082015260600190565b60206040518083038186803b15801561115957600080fd5b505af415801561116d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111919190614183565b6040805180820190915260098152684241445f545241444560b81b602082015290156111d95760405162461bcd60e51b81526004016111d091906141fc565b60405180910390fd5b506112028386602001518760800151600001518860c00151602001518961012001516001611d18565b8460800151604001516001600160a01b031663d67622ff8c876101000151886101200151896060015160a001518a60200151602001516040518663ffffffff1660e01b81526004016112589594939291906140bb565b600060405180830381600087803b15801561127257600080fd5b505af1158015611286573d6000803e3d6000fd5b5050505061129383612aa2565b437f301c5fb73b58cb5622cfce1716564fcba57dd85d3ee1f87fde02505244afeece60010160008d6001600160a01b03166001600160a01b0316815260200190815260200160002081905550886001600160a01b03168a6001600160a01b03168c6001600160a01b03167fd7118a889d6b39069ba246fd075a16611775364aead2ee9378d3b74e0787fe8f8b8961012001518c8b61016001518c6101a0015160405161134395949392919061420f565b60405180910390a45050505050505050505050565b61136061394f565b6040805160e0810182527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f8e546001600160a01b0390811682527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f8f54811660208301527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f905416918101919091527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f915460608201527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f925460808201527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f935460a08201527fbb533019ebed682c62a15bb043bf5bfa75710053df9a34e2c989d958a9684f945460c082015281526114926124ba565b6001600160a01b038616600090815260029182016020908152604091829020825160a08101845281548152600182015460ff808216948301949094529094919385019261010090910416908111156114ec576114ec613e6b565b60028111156114fd576114fd613e6b565b815260018201546001600160a01b03620100009091048116602080840191909152600290930154166040909101528201526115366124ba565b6001600160a01b038516600090815260029182016020908152604091829020825160a08101845281548152600182015460ff8082169483019490945290949193850192610100909104169081111561159057611590613e6b565b60028111156115a1576115a1613e6b565b815260018201546001600160a01b0362010000909104811660208301526002909201549091166040918201528201526115d86122f0565b6001600160a01b0386166000908152600191909101602052604090205460a08201526116026122f0565b6001600160a01b0385166000908152600191909101602052604081205460c083015261162c6122f0565b60a0830151600090815260029190910160205260408120915061164d6122f0565b60020160008460c001518152602001908152602001600020905061167082612314565b61167981612314565b611682826124de565b6060840152611690816124de565b83608001819052508260200151608001516001600160a01b031663b3596f07886040518263ffffffff1660e01b81526004016116cc9190613f99565b604080518083038186803b1580156116e357600080fd5b505afa1580156116f7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061171b9190613fad565b61018085015261016084015260408084015160800151905163b3596f0760e01b81526001600160a01b039091169063b3596f079061175d908990600401613f99565b604080518083038186803b15801561177457600080fd5b505afa158015611788573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117ac9190613fad565b6101c08501526101a084015282516040908101519051634952bb7360e11b81526001600160a01b03909116906392a576e6906117ef906001908b90600401614242565b60206040518083038186803b15801561180757600080fd5b505afa15801561181b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061183f9190613fef565b60e084015282516040908101519051634952bb7360e11b81526001600160a01b03909116906392a576e69061187b906001908a90600401614242565b60206040518083038186803b15801561189357600080fd5b505afa1580156118a7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118cb9190613fef565b610100840152602083015160e08401516118e9918491886000611ffe565b6101208501819052159050611b41576000836020015160400151600281111561191457611914613e6b565b14604051806040016040528060138152602001721253959053125117d054d4d15517d253941555606a1b8152509061195f5760405162461bcd60e51b81526004016111d091906141fc565b50611ae683602001516020015160ff16611ae0856000015160c00151611ada87602001516020015160ff167370b995a9e0ae732495368346d131a3947d40443663360885fc7370b995a9e0ae732495368346d131a3947d40443663867a7d476119cc8d6101000151612d38565b8d60400151602001518e6101a001518f6101c001516040518563ffffffff1660e01b8152600401611a009493929190613fd1565b60206040518083038186803b158015611a1857600080fd5b505af4158015611a2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a509190613fef565b8b60200151602001518c61016001518d61018001516040518563ffffffff1660e01b8152600401611a849493929190613fd1565b60206040518083038186803b158015611a9c57600080fd5b505af4158015611ab0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ad49190613fef565b90612d5d565b90612dc8565b90612e64565b61014084018190526101208401516040805180820190915260148152731253959053125117d05353d5539517d25394155560621b6020820152911015611b3f5760405162461bcd60e51b81526004016111d091906141fc565b505b8260000151604001516001600160a01b031663d67622ff60018560a0015188600019611b6d919061401e565b876060015160a001518860200151602001516040518663ffffffff1660e01b8152600401611b9f9594939291906140bb565b600060405180830381600087803b158015611bb957600080fd5b505af1158015611bcd573d6000803e3d6000fd5b50505050611bda82612aa2565b611beb836020015188888888612be3565b6101e084018190526040840151845151610100860151611c119385939291906000611d18565b8260000151604001516001600160a01b031663d67622ff60018560c00151866101e00151876080015160a001518860400151602001516040518663ffffffff1660e01b8152600401611c679594939291906140bb565b600060405180830381600087803b158015611c8157600080fd5b505af1158015611c95573d6000803e3d6000fd5b50505050611ca281612aa2565b856001600160a01b0316876001600160a01b031660016001600160a01b03167fd7118a889d6b39069ba246fd075a16611775364aead2ee9378d3b74e0787fe8f88876101e00151898961016001518a6101a00151604051611d0795949392919061420f565b60405180910390a450505050505050565b611d206139ea565b6004808801546040516370a0823160e01b815285926001600160a01b03909216916370a0823191611d5391309101613f99565b60206040518083038186803b158015611d6b57600080fd5b505afa158015611d7f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611da39190613fef565b101560405180604001604052806018815260200177135254d4d25391d7d5539111549316525391d7d054d4d15560421b81525090611df45760405162461bcd60e51b81526004016111d091906141fc565b50611dfe876124de565b60808201526000841215611e51576000611e1785612d38565b905080841115611e3c5760408201819052611e3281856140ec565b6020830152611e4b565b60006020830152604082018490525b50611e59565b602081018390525b60008160200151118015611e6a5750815b15611ec5578060200151876008016000828254611e8791906140a3565b909155505060028701546001600160a01b031615611ec557600487015460028801546020830151611ec5926001600160a01b03908116921690612ea5565b604081015115611ff557611f22866020015160ff16611f1c836080015160800151611f16856080015160a00151611f108c6020015160ff168860400151612fab90919063ffffffff16565b90612fe5565b906130af565b9061312c565b8082526004880154611f41916001600160a01b03909116908790613166565b611f6b816080015160a00151611f10886020015160ff168460400151612fab90919063ffffffff16565b876007016000828254611f7e91906140ec565b90915550508051604082018051611f969083906140ec565b90525060018701546001600160a01b031615611fd757600487015460018801546040830151611fd2926001600160a01b03908116921690612ea5565b611ff5565b8060400151876006016000828254611fef91906140a3565b90915550505b50505050505050565b600080612009613a1e565b602087015160ff16815261201c886124de565b60a082015260008612156120365760208101859052612077565b600061204187612d38565b905080861115612066576040820181905261205c81876140ec565b6020830152612075565b60408201869052600060208301525b505b600081604001511180156120885750835b156121295780604001518860080160008282546120a591906140ec565b909155505060028801546001600160a01b0316156121295760028801546040808301519051638ca1799560e01b81526001600160a01b0390921691638ca17995916120f69160040190815260200190565b600060405180830381600087803b15801561211057600080fd5b505af1158015612124573d6000803e3d6000fd5b505050505b60208101511561220a5760a0808201510151815160208301516121519291611f109190612fab565b88600701600082825461216491906140a3565b909155505060018801546001600160a01b0316156121ec5760018801546020820151604051638ca1799560e01b81526001600160a01b0390921691638ca17995916121b59160040190815260200190565b600060405180830381600087803b1580156121cf57600080fd5b505af11580156121e3573d6000803e3d6000fd5b5050505061220a565b806020015188600601600082825461220491906140ec565b90915550505b6004808901546040516370a0823160e01b815287926001600160a01b03909216916370a082319161223d91309101613f99565b60206040518083038186803b15801561225557600080fd5b505afa158015612269573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061228d9190613fef565b1015604051806040016040528060178152602001764e4f545f454e4f5547485f504f4f4c5f42414c414e434560481b815250906122dd5760405162461bcd60e51b81526004016111d091906141fc565b5060200151939793965092945050505050565b7f31086be12a0da5cefd664dbc36245bc20218d0358b0623169ff6fd924d16042390565b604080516102608101909152815463ffffffff8082166101608401908152600160201b83048216610180850152600160401b830482166101a0850152600160601b830482166101c0850152600160801b830482166101e0850152600160a01b830490911661020084015260009283926124a29286918391908390610220840190600160c01b900460ff1660028111156123af576123af613e6b565b60028111156123c0576123c0613e6b565b81528154602090910190600160c81b900460ff1660038111156123e5576123e5613e6b565b60038111156123f6576123f6613e6b565b9052508152604080516060808201835260018501546001600160a01b03908116835260028601548116602084810191909152600387015482168486015285019290925260048501549091169183019190915260058301549082015260068201546080820152600782015460a0820152600882015460c0820152600982015460e0820152600a820154610100820152600b820154610120820152600c9091015461014090910152426131ce565b6009850191909155600b840155505042600c90910155565b7fb0511cf1f9df08d578b30a0c7a6e3581a567d2c1214b3100e67d9f9401553afd90565b6124e6613a50565b6124ee613a50565b60048301546001600160a01b0390811682526001840154811660208301526002840154166040820152600983015460608201819052600b84015460808301819052612538916140a3565b60a082015292915050565b6000806000806000806125546124ba565b6040808901516001600160a01b039081166000908152600293909301602090815291832060010154918a01515160ff9092169350166125a357608088015161259c90826140a3565b9050612629565b8760200151600001516001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b1580156125e457600080fd5b505afa1580156125f8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061261c9190613fef565b61262690826140a3565b90505b6000806126368a426131ce565b915091506000612653838c60a001516130af90919063ffffffff16565b9050600061266e838d60a001516130af90919063ffffffff16565b9050600061267c838861312c565b61268690876140a3565b9050600061269483856140a3565b90506000816126a3898b612fab565b6126ad91906140a3565b905087836126bb868c61312c565b6126c5848d61312c565b6126cf868e61312c565b9d509d509d509d509d5050505050505050505091939590929450565b82604001516001600160a01b031684604001516001600160a01b031614156040518060400160405280601781526020017610d0539393d517d51490511157d4d0535157d054d4d155604a1b815250906127575760405162461bcd60e51b81526004016111d091906141fc565b506003845160e00151600381111561277157612771613e6b565b148061279357506001845160e00151600381111561279157612791613e6b565b145b6127d45760405162461bcd60e51b8152602060048201526012602482015271185cdcd95d0818d85b9b9bdd081cda1bdc9d60721b60448201526064016111d0565b6003835160e0015160038111156127ed576127ed613e6b565b148061280f57506002835160e00151600381111561280d5761280d613e6b565b145b61284f5760405162461bcd60e51b815260206004820152601160248201527061737365742063616e6e6f74206c6f6e6760781b60448201526064016111d0565b6001845160c00151600281111561286857612868613e6b565b146040518060400160405280600d81526020016c504f4f4c5f494e41435449564560981b815250906128ad5760405162461bcd60e51b81526004016111d091906141fc565b506001835160c0015160028111156128c7576128c7613e6b565b146040518060400160405280600d81526020016c504f4f4c5f494e41435449564560981b8152509061290c5760405162461bcd60e51b81526004016111d091906141fc565b5043816080015114156040518060400160405280601081526020016f555345525f54524144455f424c4f434b60801b8152509061295c5760405162461bcd60e51b81526004016111d091906141fc565b50806020015160001415604051806040016040528060138152602001721253959053125117d6915493d7d05353d55395606a1b815250906129b05760405162461bcd60e51b81526004016111d091906141fc565b50806060015181602001511115604051806040016040528060188152602001774e4f545f454e4f5547485f555345525f4c4556455241474560401b81525090612a0c5760405162461bcd60e51b81526004016111d091906141fc565b50600080831215612a2257506020810151612a41565b8160200151831015612a4157828260200151612a3e91906140ec565b90505b8015612a9b57604080830151815180830190925260178252764e4f545f454e4f5547485f504f4f4c5f42414c414e434560481b6020830152821115612a995760405162461bcd60e51b81526004016111d091906141fc565b505b5050505050565b6000612aac6124ba565b60048301546001600160a01b03166000908152600291820160205260408082206001015481516102608101909252855463ffffffff8082166101608501908152600160201b83048216610180860152600160401b830482166101a0860152600160601b830482166101c0860152600160801b830482166101e0860152600160a01b830490911661020085015260ff928316965093948594612b6c9493899385938592610220860192600160c01b900416908111156105cc576105cc613e6b565b9450945050505060008211612b82576000612b99565b612b99612b8f8385612fab565b611f108386612fab565b84600a018190555050505050565b600082612bb657506000612bdb565b6000612bc28385612dc8565b9050612bd86001600160a01b0387168683613166565b90505b949350505050565b6060850151604051636eb1769f60e11b815260009184916001600160a01b0388169163dd62ed3e91612c19913091600401614242565b60206040518083038186803b158015612c3157600080fd5b505afa158015612c45573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c699190613fef565b1015612ca6576060860151612c8a906001600160a01b038716906000613381565b6060860151612ca6906001600160a01b03871690600019613381565b85606001516001600160a01b03166343a0a7f2868686866040518563ffffffff1660e01b8152600401612cdc949392919061425c565b602060405180830381600087803b158015612cf657600080fd5b505af1158015612d0a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d2e9190613fef565b9695505050505050565b600080821215612d5457612d4e8260001961401e565b92915050565b5090565b919050565b6000821580612d6c5750601282145b15612d78575081612d4e565b6012821015612da857612d8c8260126140ec565b612d9790600a614373565b612da1908461437f565b9050612d4e565b612db36012836140ec565b612dbe90600a614373565b612da1908461439e565b6000821580612dd5575081155b15612de257506000612d4e565b81612df66002670de0b6b3a764000061439e565b612e02906000196140ec565b612e0c919061439e565b831115612e2b5760405162461bcd60e51b81526004016111d0906143c0565b670de0b6b3a7640000612e3f60028261439e565b612e49848661437f565b612e5391906140a3565b612e5d919061439e565b9392505050565b6000821580612e7a5750670de0b6b3a764000082145b15612e86575081612d4e565b6012821015612e9a57612db38260126140ec565b612d8c6012836140ec565b604051636eb1769f60e11b815281906001600160a01b0385169063dd62ed3e90612ed59030908790600401614242565b60206040518083038186803b158015612eed57600080fd5b505afa158015612f01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f259190613fef565b1015612f5657612f406001600160a01b038416836000613381565b612f566001600160a01b03841683600019613381565b6040516255f9e960e71b8152600481018290526001600160a01b03831690632afcf48090602401600060405180830381600087803b158015612f9757600080fd5b505af1158015611ff5573d6000803e3d6000fd5b6000821580612fba5750601b82145b15612fc6575081612d4e565b601b821015612fda57612d8c82601b6140ec565b612db3601b836140ec565b6000816130345760405162461bcd60e51b815260206004820152601b60248201527f4d6174685574696c733a206469766973696f6e206279207a65726f000000000060448201526064016111d0565b600061304160028461439e565b9050676765c793fa10079d601b1b61305b826000196140ec565b613065919061439e565b8411156130845760405162461bcd60e51b81526004016111d0906143c0565b828161309b676765c793fa10079d601b1b8761437f565b6130a591906140a3565b612bdb919061439e565b60008215806130bc575081155b156130c957506000612d4e565b816130e06002676765c793fa10079d601b1b61439e565b6130ec906000196140ec565b6130f6919061439e565b8311156131155760405162461bcd60e51b81526004016111d0906143c0565b676765c793fa10079d601b1b612e3f60028261439e565b600082158061313b5750601b82145b15613147575081612d4e565b601b82101561315b57612db382601b6140ec565b612d8c601b836140ec565b6040516001600160a01b0383166024820152604481018290526131c990849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526134a4565b505050565b600080836101000151600014156131f157505060e082015161012083015161337a565b60008461012001518560e0015161320891906140a3565b905060006132ac866101000151613237600989600001516020015163ffffffff16612fab90919063ffffffff16565b8851604001516132539063ffffffff90811690600990612fab16565b89516060015161326f9063ffffffff90811690600990612fab16565b8a516080015161328b9063ffffffff90811690600990612fab16565b8b5160a001516132a79063ffffffff90811690600990612fab16565b613576565b9050806132c9578560e0015186610120015193509350505061337a565b60006132db82886101400151886135e8565b90506000836132ea81846130af565b6132f491906140ec565b9050600061331e84611f1060098c600001516020015163ffffffff16612fab90919063ffffffff16565b9050600061332c83836130af565b8a610120015161333c91906140a3565b9050600061335f61335884676765c793fa10079d601b1b6140ec565b85906130af565b8b60e0015161336e91906140a3565b98509096505050505050505b9250929050565b8015806134095750604051636eb1769f60e11b81526001600160a01b0384169063dd62ed3e906133b79030908690600401614242565b60206040518083038186803b1580156133cf57600080fd5b505afa1580156133e3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134079190613fef565b155b6134745760405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b60648201526084016111d0565b6040516001600160a01b0383166024820152604481018290526131c990849063095ea7b360e01b90606401613192565b60006134f9826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166137059092919063ffffffff16565b8051909150156131c957808060200190518101906135179190614183565b6131c95760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016111d0565b6000808488116135915761358a88856130af565b90506135c7565b600061359d86866130af565b905060006135ab878b6140ec565b9050816135b882876130af565b6135c291906140a3565b925050505b866135d287836140a3565b6135dc91906140a3565b98975050505050505050565b6000806135f584846140ec565b90508061361057676765c793fa10079d601b1b915050612e5d565b600061361d6001836140ec565b905060006002831161363057600061363b565b61363b6002846140ec565b9050600061364d6301e133808961439e565b9050600061365b82806130af565b9050600061366982846130af565b9050600060028361367a888a61437f565b613684919061437f565b61368e919061439e565b90506000600683876136a08a8c61437f565b6136aa919061437f565b6136b4919061437f565b6136be919061439e565b905080826136cc8a8861437f565b6136e190676765c793fa10079d601b1b6140a3565b6136eb91906140a3565b6136f591906140a3565b9c9b505050505050505050505050565b6060612bdb8484600085856001600160a01b0385163b6137675760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016111d0565b600080866001600160a01b0316858760405161378391906143ed565b60006040518083038185875af1925050503d80600081146137c0576040519150601f19603f3d011682016040523d82523d6000602084013e6137c5565b606091505b50915091506137d58282866137e0565b979650505050505050565b606083156137ef575081612e5d565b8251156137ff5782518084602001fd5b8160405162461bcd60e51b81526004016111d091906141fc565b60405180610280016040528061382d613aa1565b815260200161383a613aa1565b8152602001613847613a50565b8152602001613854613a50565b8152602001613861613ad0565b81526020016138b0604051806101000160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000151581525090565b81526020016138ee6040518060c001604052806000815260200160008152602001600081526020016000815260200160008152602001600081525090565b8152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b604051806102000160405280613963613ad0565b8152602001613970613aa1565b815260200161397d613aa1565b815260200161398a613a50565b8152602001613997613a50565b815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040518060a0016040528060008152602001600081526020016000815260200160008152602001613a19613a50565b905290565b6040518060c001604052806000815260200160008152602001600081526020016000815260200160008152602001613a195b6040518060c0016040528060006001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b031681526020016000815260200160008152602001600081525090565b6040805160a0810182526000808252602082018190529091820190815260006020820181905260409091015290565b6040518060e0016040528060006001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b03168152602001600081526020016000815260200160008152602001600081525090565b6001600160a01b0381168114613b3d57600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b604051610100810167ffffffffffffffff81118282101715613b7a57613b7a613b40565b60405290565b600082601f830112613b9157600080fd5b813567ffffffffffffffff80821115613bac57613bac613b40565b604051601f8301601f19908116603f01168101908282118183101715613bd457613bd4613b40565b81604052838152866020858801011115613bed57600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600080600060a08688031215613c2557600080fd5b8535613c3081613b28565b94506020860135613c4081613b28565b93506040860135613c5081613b28565b925060608601359150608086013567ffffffffffffffff811115613c7357600080fd5b613c7f88828901613b80565b9150509295509295909350565b60008060008060808587031215613ca257600080fd5b8435613cad81613b28565b93506020850135613cbd81613b28565b925060408501359150606085013567ffffffffffffffff811115613ce057600080fd5b613cec87828801613b80565b91505092959194509250565b8035612d5881613b28565b600060a08284031215613d1557600080fd5b60405160a0810181811067ffffffffffffffff82111715613d3857613d38613b40565b60405282358152905080602083013560ff81168114613d5657600080fd5b6020820152604083013560038110613d6d57600080fd5b60408201526060830135613d8081613b28565b6060820152613d9160808401613cf8565b60808201525092915050565b8015158114613b3d57600080fd5b6000806000806000806101408789031215613dc557600080fd5b86359550613dd68860208901613d03565b945060c0870135613de681613b28565b935060e087013592506101008701359150610120870135613e0681613d9d565b809150509295509295509295565b60008060008060006101208688031215613e2d57600080fd5b85359450613e3e8760208801613d03565b935060c0860135925060e08601359150610100860135613e5d81613d9d565b809150509295509295909350565b634e487b7160e01b600052602160045260246000fd5b600060c08284031215613e9357600080fd5b60405160c0810181811067ffffffffffffffff82111715613eb657613eb6613b40565b8060405250809150825181526020830151602082015260408301516040820152606083015160608201526080830151608082015260a083015160a08201525092915050565b6000808284036101c0811215613f1057600080fd5b61010080821215613f2057600080fd5b613f28613b56565b9150845182526020850151602083015260408501516040830152606085015160608301526080850151608083015260a085015160a083015260c085015160c083015260e0850151613f7881613d9d565b8060e084015250819350613f8e86828701613e81565b925050509250929050565b6001600160a01b0391909116815260200190565b60008060408385031215613fc057600080fd5b505080516020909101519092909150565b93845260ff9290921660208401526040830152606082015260800190565b60006020828403121561400157600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b60006001600160ff1b038184138284138082168684048611161561404457614044614008565b600160ff1b600087128281168783058912161561406357614063614008565b6000871292508782058712848416161561407f5761407f614008565b8785058712818416161561409557614095614008565b505050929093029392505050565b600082198211156140b6576140b6614008565b500190565b6001600160a01b0395909516855260208501939093526040840191909152606083015260ff16608082015260a00190565b6000828210156140fe576140fe614008565b500390565b60008083128015600160ff1b85018412161561412157614121614008565b6001600160ff1b038401831381161561413c5761413c614008565b50500390565b600080821280156001600160ff1b038490038513161561416457614164614008565b600160ff1b839003841281161561417d5761417d614008565b50500190565b60006020828403121561419557600080fd5b8151612e5d81613d9d565b60005b838110156141bb5781810151838201526020016141a3565b838111156141ca576000848401525b50505050565b600081518084526141e88160208601602086016141a0565b601f01601f19169290920160200192915050565b602081526000612e5d60208301846141d0565b85815284602082015260a06040820152600061422e60a08301866141d0565b606083019490945250608001529392505050565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b0385811682528416602082015260408101839052608060608201819052600090612d2e908301846141d0565b600181815b808511156142ca5781600019048211156142b0576142b0614008565b808516156142bd57918102915b93841c9390800290614294565b509250929050565b6000826142e157506001612d4e565b816142ee57506000612d4e565b8160018114614304576002811461430e5761432a565b6001915050612d4e565b60ff84111561431f5761431f614008565b50506001821b612d4e565b5060208310610133831016604e8410600b841016171561434d575081810a612d4e565b614357838361428f565b806000190482111561436b5761436b614008565b029392505050565b6000612e5d83836142d2565b600081600019048311821515161561439957614399614008565b500290565b6000826143bb57634e487b7160e01b600052601260045260246000fd5b500490565b6020808252601390820152724d6174685574696c733a206f766572666c6f7760681b604082015260600190565b600082516143ff8184602087016141a0565b919091019291505056fea2646970667358221220b866795a3c666ba17741d1dfaabc480f5566a035c157ae2dab50a5c14acffbed64736f6c63430008090033

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

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