S Price: $0.476066 (+5.54%)

Contract

0x1131E79BCF6A6Be85b8DC732bfCC412308f2f5eA

Overview

S Balance

Sonic LogoSonic LogoSonic Logo0 S

S Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

Parent Transaction Hash Block From To
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
GatewayImplementation

Compiler Version
v0.8.20+commit.a1b79de6

Optimization Enabled:
Yes with 200 runs

Other Settings:
paris EvmVersion
File 1 of 32 : GatewayImplementation.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

import '../vault/IVault.sol';
import './IGateway.sol';
import '../token/IDToken.sol';
import '../token/IIOU.sol';
import '../../oracle/IOracle.sol';
import '../swapper/ISwapper.sol';
import '../../library/Bytes32Map.sol';
import '../../library/ETHAndERC20.sol';
import '../../library/SafeMath.sol';
import { GatewayIndex as I } from './GatewayIndex.sol';
import './GatewayHelper.sol';
import './GatewayStorage.sol';
import '../../utils/ISwitchOracle.sol';

contract GatewayImplementation is GatewayStorage {

    using Bytes32Map for mapping(uint8 => bytes32);
    using ETHAndERC20 for address;
    using SafeMath for uint256;
    using SafeMath for int256;

    error InvalidBToken();
    error InvalidBAmount();
    error InvalidBPrice();
    error InvalidLTokenId();
    error InvalidPTokenId();
    error InvalidRequestId();
    error InsufficientMargin();
    error InsufficientB0();

    event RequestUpdateLiquidity(
        uint256 requestId,
        uint256 lTokenId,
        uint256 liquidity,
        int256  lastCumulativePnlOnEngine,
        int256  cumulativePnlOnGateway,
        uint256 removeBAmount
    );

    event RequestRemoveMargin(
        uint256 requestId,
        uint256 pTokenId,
        uint256 realMoneyMargin,
        int256  lastCumulativePnlOnEngine,
        int256  cumulativePnlOnGateway,
        uint256 bAmount
    );

    event RequestTrade(
        uint256 requestId,
        uint256 pTokenId,
        uint256 realMoneyMargin,
        int256  lastCumulativePnlOnEngine,
        int256  cumulativePnlOnGateway,
        bytes32 symbolId,
        int256[] tradeParams
    );

    event RequestLiquidate(
        uint256 requestId,
        uint256 pTokenId,
        uint256 realMoneyMargin,
        int256  lastCumulativePnlOnEngine,
        int256  cumulativePnlOnGateway
    );

    event RequestTradeAndRemoveMargin(
        uint256 requestId,
        uint256 pTokenId,
        uint256 realMoneyMargin,
        int256  lastCumulativePnlOnEngine,
        int256  cumulativePnlOnGateway,
        uint256 bAmount,
        bytes32 symbolId,
        int256[] tradeParams
    );

    event FinishAddLiquidity(
        uint256 requestId,
        uint256 lTokenId,
        uint256 liquidity,
        uint256 totalLiquidity
    );

    event FinishRemoveLiquidity(
        uint256 requestId,
        uint256 lTokenId,
        uint256 liquidity,
        uint256 totalLiquidity,
        address bToken,
        uint256 bAmount
    );

    event FinishAddMargin(
        uint256 requestId,
        uint256 pTokenId,
        address bToken,
        uint256 bAmount
    );

    event FinishRemoveMargin(
        uint256 requestId,
        uint256 pTokenId,
        address bToken,
        uint256 bAmount
    );

    event FinishLiquidate(
        uint256 requestId,
        uint256 pTokenId,
        int256  lpPnl
    );

    uint256 constant UONE = 1e18;
    int256  constant ONE = 1e18;
    address constant tokenETH = address(1);

    IDToken  internal immutable lToken;
    IDToken  internal immutable pToken;
    IOracle  internal immutable oracle;
    ISwapper internal immutable swapper;
    IVault   internal immutable vault0;  // Vault for holding reserved B0, used for payments on regular bases
    IIOU     internal immutable iou;     // IOU ERC20, issued to traders when B0 insufficent
    address  internal immutable tokenB0; // B0, settlement base token, e.g. USDC
    address  internal immutable dChainEventSigner;
    uint8    internal immutable decimalsB0;
    uint256  internal immutable b0ReserveRatio;
    int256   internal immutable liquidationRewardCutRatio;
    int256   internal immutable minLiquidationReward;
    int256   internal immutable maxLiquidationReward;
    address  internal immutable protocolFeeManager;
    address  internal immutable liqClaim;
    address  internal immutable switchOracle;

    constructor (IGateway.GatewayParam memory p) {
        lToken = IDToken(p.lToken);
        pToken = IDToken(p.pToken);
        oracle = IOracle(p.oracle);
        swapper = ISwapper(p.swapper);
        vault0 = IVault(p.vault0);
        iou = IIOU(p.iou);
        tokenB0 = p.tokenB0;
        decimalsB0 = p.tokenB0.decimals();
        dChainEventSigner = p.dChainEventSigner;
        b0ReserveRatio = p.b0ReserveRatio;
        liquidationRewardCutRatio = p.liquidationRewardCutRatio;
        minLiquidationReward = p.minLiquidationReward;
        maxLiquidationReward = p.maxLiquidationReward;
        protocolFeeManager = p.protocolFeeManager;
        liqClaim = p.liqClaim;
        switchOracle = p.switchOracle;
    }

    //================================================================================
    // Getters
    //================================================================================

    function getGatewayParam() external view returns (IGateway.GatewayParam memory p) {
        p.lToken = address(lToken);
        p.pToken = address(pToken);
        p.oracle = address(oracle);
        p.swapper = address(swapper);
        p.vault0 = address(vault0);
        p.iou = address(iou);
        p.tokenB0 = tokenB0;
        p.dChainEventSigner = dChainEventSigner;
        p.b0ReserveRatio = b0ReserveRatio;
        p.liquidationRewardCutRatio = liquidationRewardCutRatio;
        p.minLiquidationReward = minLiquidationReward;
        p.maxLiquidationReward = maxLiquidationReward;
        p.protocolFeeManager = protocolFeeManager;
        p.liqClaim = liqClaim;
    }

    function getGatewayState() external view returns (IGateway.GatewayState memory s) {
        return GatewayHelper.getGatewayState(_gatewayStates);
    }

    function getBTokenState(address bToken) external view returns (IGateway.BTokenState memory s) {
        return GatewayHelper.getBTokenState(_bTokenStates, bToken);
    }

    function getLpState(uint256 lTokenId) external view returns (IGateway.LpState memory s) {
        return GatewayHelper.getLpState(_bTokenStates, _dTokenStates, lTokenId);
    }

    function getTdState(uint256 pTokenId) external view returns (IGateway.TdState memory s) {
        return GatewayHelper.getTdState(_bTokenStates, _dTokenStates, pTokenId);
    }

    // @notice Calculate Lp's cumulative time, used in liquidity mining reward distributions
    function getCumulativeTime(uint256 lTokenId)
    public view returns (uint256 cumulativeTimePerLiquidity, uint256 cumulativeTime)
    {
        return GatewayHelper.getCumulativeTime(_gatewayStates, _dTokenStates, lTokenId);
    }

    function getExecutionFees() public view returns (uint256[] memory fees) {
        return GatewayHelper.getExecutionFees(_executionFees);
    }

    //================================================================================
    // Setters
    //================================================================================

    // function addBToken(
    //     address bToken,
    //     address vault,
    //     bytes32 oracleId,
    //     uint256 collateralFactor
    // ) external _onlyAdmin_ {
    //     GatewayHelper.addBToken(
    //         _bTokenStates,
    //         swapper,
    //         oracle,
    //         vault0,
    //         tokenB0,
    //         bToken,
    //         vault,
    //         oracleId,
    //         collateralFactor
    //     );
    // }

    // function delBToken(address bToken) external _onlyAdmin_ {
    //     GatewayHelper.delBToken(_bTokenStates, bToken);
    // }

    // // @dev This function can be used to change bToken collateral factor
    // function setBTokenParameter(address bToken, uint8 idx, bytes32 value) external _onlyAdmin_ {
    //     GatewayHelper.setBTokenParameter(_bTokenStates, bToken, idx, value);
    // }

    // // @notice Set execution fee for actionId
    // function setExecutionFee(uint256 actionId, uint256 executionFee) external _onlyAdmin_ {
    //     GatewayHelper.setExecutionFee(_executionFees, actionId, executionFee);
    // }

    // function setDChainExecutionFeePerRequest(uint256 dChainExecutionFeePerRequest) external _onlyAdmin_ {
    //     GatewayHelper.setDChainExecutionFeePerRequest(_gatewayStates, dChainExecutionFeePerRequest);
    // }

    // @notic Claim dChain executionFee to account `to`
    function claimDChainExecutionFee(address to) external _onlyAdmin_ {
        GatewayHelper.claimDChainExecutionFee(_gatewayStates, to);
    }

    // @notice Claim unused iChain execution fee for dTokenId
    function claimUnusedIChainExecutionFee(uint256 dTokenId, bool isLp) external {
        GatewayHelper.claimUnusedIChainExecutionFee(
            _gatewayStates,
            _dTokenStates,
            lToken,
            pToken,
            dTokenId,
            isLp
        );
    }

    // @notice Redeem B0 for burning IOU
    function redeemIOU(uint256 b0Amount) external {
        GatewayHelper.redeemIOU(tokenB0, vault0, iou, msg.sender, b0Amount);
    }

    //================================================================================
    // Interactions
    //================================================================================

    function finishCollectProtocolFee(bytes memory eventData, bytes memory signature) external _onlyAdmin_ {
        GatewayHelper.verifyEventData(eventData, signature, 64, dChainEventSigner);
        IGateway.VarOnExecuteCollectProtocolFee memory v = abi.decode(eventData, (IGateway.VarOnExecuteCollectProtocolFee));
        require(v.chainId == block.chainid);

        GatewayHelper.finishCollectProtocolFee(
            _gatewayStates,
            vault0,
            tokenB0,
            protocolFeeManager,
            v.cumulativeCollectedProtocolFeeOnEngine
        );
    }

    /**
     * @notice Request to add liquidity with specified base token.
     * @param lTokenId The unique identifier of the LToken.
     * @param bToken The address of the base token to add as liquidity.
     * @param bAmount The amount of base tokens to add as liquidity.
     */
    function requestAddLiquidity(uint256 lTokenId, address bToken, uint256 bAmount) external payable {
        if (lTokenId == 0) {
            lTokenId = lToken.mint(msg.sender);
        } else {
            _checkLTokenIdOwner(lTokenId, msg.sender);
        }
        _checkBTokenInitialized(bToken);

        Data memory data = _getDataAndCheckBTokenConsistency(msg.sender, lTokenId, bToken);

        uint256 ethAmount = _receiveExecutionFee(lTokenId, _executionFees[I.ACTION_REQUESTADDLIQUIDITY]);
        if (bToken == tokenETH) {
            bAmount = ethAmount;
        }
        if (bAmount == 0) {
            revert InvalidBAmount();
        }
        if (bToken != tokenETH) {
            bToken.transferIn(data.account, bAmount);
        }

        _deposit(data, bAmount);
        _getExParams(data);
        uint256 newLiquidity = _getDTokenLiquidity(data);

        _saveData(data);

        uint256 requestId = _incrementRequestId(lTokenId);
        emit RequestUpdateLiquidity(
            requestId,
            lTokenId,
            newLiquidity,
            data.lastCumulativePnlOnEngine,
            data.cumulativePnlOnGateway,
            0
        );
    }

    /**
     * @notice Request to remove liquidity with specified base token.
     * @param lTokenId The unique identifier of the LToken.
     * @param bToken The address of the base token to remove as liquidity.
     * @param bAmount The amount of base tokens to remove as liquidity.
     */
    function requestRemoveLiquidity(uint256 lTokenId, address bToken, uint256 bAmount) external payable {
        _checkLTokenIdOwner(lTokenId, msg.sender);

        _receiveExecutionFee(lTokenId, _executionFees[I.ACTION_REQUESTREMOVELIQUIDITY]);
        if (bAmount == 0) {
            revert InvalidBAmount();
        }

        Data memory data = _getData(msg.sender, lTokenId, _dTokenStates[lTokenId].getAddress(I.D_BTOKEN));

        _getExParams(data);
        uint256 oldLiquidity = _getDTokenLiquidity(data);
        uint256 newLiquidity;
        if (data.bToken == bToken) {
            newLiquidity = _getDTokenLiquidityWithRemove(data, bAmount);
        } else if (bToken == tokenB0) {
            newLiquidity = _getDTokenLiquidityWithRemoveB0(data, bAmount);
        } else {
            revert InvalidBToken();
        }
        if (newLiquidity <= oldLiquidity / 100) {
            newLiquidity = 0;
        }

        _dTokenStates[lTokenId].set(I.D_CURRENTOPERATETOKEN, bToken);
        uint256 requestId = _incrementRequestId(lTokenId);
        emit RequestUpdateLiquidity(
            requestId,
            lTokenId,
            newLiquidity,
            data.lastCumulativePnlOnEngine,
            data.cumulativePnlOnGateway,
            bAmount
        );
    }

    /**
     * @notice Request to add margin with specified base token.
     * @param pTokenId The unique identifier of the PToken.
     * @param bToken The address of the base token to add as margin.
     * @param bAmount The amount of base tokens to add as margin.
     * @param singlePosition The flag whether trader is using singlePosition margin.
     * @return The unique identifier pTokenId.
     */
    function requestAddMargin(uint256 pTokenId, address bToken, uint256 bAmount, bool singlePosition) public payable returns (uint256) {
        if (pTokenId == 0) {
            pTokenId = pToken.mint(msg.sender);
            if (singlePosition) {
                _dTokenStates[pTokenId].set(I.D_SINGLEPOSITION, true);
            }
        } else {
            _checkPTokenIdOwner(pTokenId, msg.sender);
        }
        _checkBTokenInitialized(bToken);

        Data memory data = _getDataAndCheckBTokenConsistency(msg.sender, pTokenId, bToken);

        if (bToken == tokenETH) {
            if (bAmount > msg.value) {
                revert InvalidBAmount();
            }
        }
        if (bAmount == 0) {
            revert InvalidBAmount();
        }
        if (bToken != tokenETH) {
            bToken.transferIn(data.account, bAmount);
        }

        _deposit(data, bAmount);

        _saveData(data);

        uint256 requestId = _incrementRequestId(pTokenId);
        emit FinishAddMargin(
            requestId,
            pTokenId,
            bToken,
            bAmount
        );

        return pTokenId;
    }

    function requestAddMarginB0(uint256 pTokenId, uint256 b0Amount) external {
        _checkPTokenIdOwner(pTokenId, msg.sender);
        tokenB0.transferIn(msg.sender, b0Amount);
        vault0.deposit(uint256(0), b0Amount);
        int256 curB0Amount = _dTokenStates[pTokenId].getInt(I.D_B0AMOUNT);
        _dTokenStates[pTokenId].set(I.D_B0AMOUNT, curB0Amount + b0Amount.utoi());
    }

    /**
     * @notice Request to remove margin with specified base token.
     * @param pTokenId The unique identifier of the PToken.
     * @param bToken The address of the base token to remove as margin.
     * @param bAmount The amount of base tokens to remove as margin.
     */
    function requestRemoveMargin(uint256 pTokenId, address bToken, uint256 bAmount) external payable {
        _checkPTokenIdOwner(pTokenId, msg.sender);

        _receiveExecutionFee(pTokenId, _executionFees[I.ACTION_REQUESTREMOVEMARGIN]);
        if (bAmount == 0) {
            revert InvalidBAmount();
        }

        Data memory data = _getDataAndCheckBTokenConsistency(msg.sender, pTokenId, bToken);
        _getExParams(data);
        uint256 oldMargin = _getDTokenLiquidity(data);
        uint256 newMargin = _getDTokenLiquidityWithRemove(data, bAmount);
        if (newMargin <= oldMargin / 100) {
            newMargin = 0;
        }

        uint256 requestId = _incrementRequestId(pTokenId);
        emit RequestRemoveMargin(
            requestId,
            pTokenId,
            newMargin,
            data.lastCumulativePnlOnEngine,
            data.cumulativePnlOnGateway,
            bAmount
        );
    }

    /**
     * @notice Request to initiate a trade using a specified PToken, symbol identifier, and trade parameters.
     * @param pTokenId The unique identifier of the PToken.
     * @param symbolId The identifier of the trading symbol.
     * @param tradeParams An array of trade parameters for the trade execution.
     */
    function requestTrade(uint256 pTokenId, bytes32 symbolId, int256[] calldata tradeParams) public payable {
        _checkPTokenIdOwner(pTokenId, msg.sender);

        _receiveExecutionFee(pTokenId, _executionFees[I.ACTION_REQUESTTRADE]);

        Data memory data = _getDataAndCheckBTokenConsistency(msg.sender, pTokenId, _dTokenStates[pTokenId].getAddress(I.D_BTOKEN));
        _getExParams(data);
        uint256 realMoneyMargin = _getDTokenLiquidity(data);

        uint256 requestId = _incrementRequestId(pTokenId);
        emit RequestTrade(
            requestId,
            pTokenId,
            realMoneyMargin,
            data.lastCumulativePnlOnEngine,
            data.cumulativePnlOnGateway,
            symbolId,
            tradeParams
        );
    }

    /**
     * @notice Request to liquidate a specified PToken.
     * @param pTokenId The unique identifier of the PToken.
     */
    function requestLiquidate(uint256 pTokenId) external {
        Data memory data = _getDataAndCheckBTokenConsistency(pToken.ownerOf(pTokenId), pTokenId, _dTokenStates[pTokenId].getAddress(I.D_BTOKEN));
        _getExParams(data);
        uint256 realMoneyMargin = _getDTokenLiquidity(data);

        uint256 requestId = _incrementRequestId(pTokenId);
        emit RequestLiquidate(
            requestId,
            pTokenId,
            realMoneyMargin,
            data.lastCumulativePnlOnEngine,
            data.cumulativePnlOnGateway
        );
    }

    /**
     * @notice Request to add margin and initiate a trade in a single transaction.
     * @param pTokenId The unique identifier of the PToken.
     * @param bToken The address of the base token to add as margin.
     * @param bAmount The amount of base tokens to add as margin.
     * @param symbolId The identifier of the trading symbol for the trade.
     * @param tradeParams An array of trade parameters for the trade execution.
     * @param singlePosition The flag whether trader is using singlePosition margin.
     */
    function requestAddMarginAndTrade(
        uint256 pTokenId,
        address bToken,
        uint256 bAmount,
        bytes32 symbolId,
        int256[] calldata tradeParams,
        bool singlePosition
    ) external payable {
        if (bToken == tokenETH) {
            uint256 executionFee = _executionFees[I.ACTION_REQUESTTRADE];
            if (bAmount + executionFee > msg.value) { // revert if bAmount > msg.value - executionFee
                revert InvalidBAmount();
            }
        }
        pTokenId = requestAddMargin(pTokenId, bToken, bAmount, singlePosition);
        requestTrade(pTokenId, symbolId, tradeParams);
    }

    /**
     * @notice Request to initiate a trade and simultaneously remove margin from a specified PToken.
     * @param pTokenId The unique identifier of the PToken.
     * @param bToken The address of the base token to remove as margin.
     * @param bAmount The amount of base tokens to remove as margin.
     * @param symbolId The identifier of the trading symbol for the trade.
     * @param tradeParams An array of trade parameters for the trade execution.
     */
    function requestTradeAndRemoveMargin(
        uint256 pTokenId,
        address bToken,
        uint256 bAmount,
        bytes32 symbolId,
        int256[] calldata tradeParams
    ) external payable {
        _checkPTokenIdOwner(pTokenId, msg.sender);

        _receiveExecutionFee(pTokenId, _executionFees[I.ACTION_REQUESTTRADEANDREMOVEMARGIN]);
        if (bAmount == 0) {
            revert InvalidBAmount();
        }

        Data memory data = _getDataAndCheckBTokenConsistency(msg.sender, pTokenId, bToken);
        _getExParams(data);
        uint256 oldMargin = _getDTokenLiquidity(data);
        uint256 newMargin = _getDTokenLiquidityWithRemove(data, bAmount);
        if (newMargin <= oldMargin / 100) {
            newMargin = 0;
        }

        uint256 requestId = _incrementRequestId(pTokenId);
        emit RequestTradeAndRemoveMargin(
            requestId,
            pTokenId,
            newMargin,
            data.lastCumulativePnlOnEngine,
            data.cumulativePnlOnGateway,
            bAmount,
            symbolId,
            tradeParams
        );
    }

    /**
     * @notice Finalize the liquidity update based on event emitted on d-chain.
     * @param eventData The encoded event data containing information about the liquidity update, emitted on d-chain.
     * @param signature The signature used to verify the event data.
     */
    function finishUpdateLiquidity(bytes memory eventData, bytes memory signature) external _reentryLock_ {
        GatewayHelper.verifyEventData(eventData, signature, 192, dChainEventSigner);
        IGateway.VarOnExecuteUpdateLiquidity memory v = abi.decode(eventData, (IGateway.VarOnExecuteUpdateLiquidity));
        _checkRequestId(v.lTokenId, v.requestId);

        _updateLiquidity(v.lTokenId, v.liquidity, v.totalLiquidity);

        // Cumulate unsettled PNL to b0Amount
        Data memory data = _getDataAndCheckBTokenConsistency(lToken.ownerOf(v.lTokenId), v.lTokenId, _dTokenStates[v.lTokenId].getAddress(I.D_BTOKEN));
        int256 diff = v.cumulativePnlOnEngine.minusUnchecked(data.lastCumulativePnlOnEngine);
        data.b0Amount += diff.rescaleDown(18, decimalsB0);
        data.lastCumulativePnlOnEngine = v.cumulativePnlOnEngine;

        uint256 bAmountRemoved;
        address operateToken = data.bToken;
        if (v.bAmountToRemove != 0) {
            operateToken = _dTokenStates[v.lTokenId].getAddress(I.D_CURRENTOPERATETOKEN);
            if (data.bToken == operateToken) {
                _getExParams(data);
                bAmountRemoved = _transferOut(data, v.liquidity == 0 ? type(uint256).max : v.bAmountToRemove, false);
            } else {
                require(operateToken == tokenB0);
                if (data.b0Amount > 0) {
                    bAmountRemoved = vault0.redeem(uint256(0), SafeMath.min(v.bAmountToRemove, data.b0Amount.itou()));
                    data.b0Amount -= bAmountRemoved.utoi();
                    tokenB0.transferOut(data.account, bAmountRemoved);
                }
            }
        }

        _saveData(data);

        _transferLastRequestIChainExecutionFee(v.lTokenId, msg.sender);

        if (v.bAmountToRemove == 0) {
            // If bAmountToRemove == 0, it is a AddLiqudiity finalization
            emit FinishAddLiquidity(
                v.requestId,
                v.lTokenId,
                v.liquidity,
                v.totalLiquidity
            );
        } else {
            // If bAmountToRemove != 0, it is a RemoveLiquidity finalization
            emit FinishRemoveLiquidity(
                v.requestId,
                v.lTokenId,
                v.liquidity,
                v.totalLiquidity,
                operateToken,
                bAmountRemoved
            );
        }
    }

    /**
     * @notice Finalize the remove of margin based on event emitted on d-chain.
     * @param eventData The encoded event data containing information about the margin remove, emitted on d-chain.
     * @param signature The signature used to verify the event data.
     */
    function finishRemoveMargin(bytes memory eventData, bytes memory signature) external _reentryLock_ {
        GatewayHelper.verifyEventData(eventData, signature, 160, dChainEventSigner);
        IGateway.VarOnExecuteRemoveMargin memory v = abi.decode(eventData, (IGateway.VarOnExecuteRemoveMargin));
        _checkRequestId(v.pTokenId, v.requestId);

        // Cumulate unsettled PNL to b0Amount
        Data memory data = _getDataAndCheckBTokenConsistency(pToken.ownerOf(v.pTokenId), v.pTokenId, _dTokenStates[v.pTokenId].getAddress(I.D_BTOKEN));
        int256 diff = v.cumulativePnlOnEngine.minusUnchecked(data.lastCumulativePnlOnEngine);
        data.b0Amount += diff.rescaleDown(18, decimalsB0);
        data.lastCumulativePnlOnEngine = v.cumulativePnlOnEngine;

        _getExParams(data);
        uint256 bAmount = _transferOut(data, v.bAmountToRemove, true);

        if (_getDTokenLiquidity(data) < v.requiredMargin) {
            revert InsufficientMargin();
        }

        _saveData(data);

        _transferLastRequestIChainExecutionFee(v.pTokenId, msg.sender);

        emit FinishRemoveMargin(
            v.requestId,
            v.pTokenId,
            data.bToken,
            bAmount
        );
    }

    /**
     * @notice Finalize the liquidation based on event emitted on d-chain.
     * @param eventData The encoded event data containing information about the liquidation, emitted on d-chain.
     * @param signature The signature used to verify the event data.
     */
    function finishLiquidate(bytes memory eventData, bytes memory signature) external _reentryLock_ {
        GatewayHelper.verifyEventData(eventData, signature, 224, dChainEventSigner);
        IGateway.VarOnExecuteLiquidate memory v = abi.decode(eventData, (IGateway.VarOnExecuteLiquidate));

        // Cumulate unsettled PNL to b0Amount
        Data memory data = _getDataAndCheckBTokenConsistency(pToken.ownerOf(v.pTokenId), v.pTokenId, _dTokenStates[v.pTokenId].getAddress(I.D_BTOKEN));
        int256 diff = v.cumulativePnlOnEngine.minusUnchecked(data.lastCumulativePnlOnEngine);
        data.b0Amount += diff.rescaleDown(18, decimalsB0);
        data.lastCumulativePnlOnEngine = v.cumulativePnlOnEngine;

        uint256 b0AmountIn;

        {
            uint256 bAmount = IVault(data.vault).redeem(data.dTokenId, type(uint256).max);
            if (data.bToken == tokenB0) {
                b0AmountIn += bAmount;
            } else {
                b0AmountIn += GatewayHelper.liquidateRedeemAndSwap(
                    decimalsB0,
                    data.bToken,
                    address(swapper),
                    liqClaim,
                    address(pToken),
                    data.dTokenId,
                    data.b0Amount,
                    bAmount,
                    v.maintenanceMarginRequired
                );
            }
        }

        int256 lpPnl = b0AmountIn.utoi() + data.b0Amount; // All Lp's PNL by liquidating this trader
        int256 reward;

        // Calculate liquidator's reward
        reward = GatewayHelper.calculateReward(lpPnl, minLiquidationReward, maxLiquidationReward, liquidationRewardCutRatio);
        (reward, b0AmountIn) = GatewayHelper.processReward(tokenB0, vault0, reward, b0AmountIn, v.executor, v.finisher);
        lpPnl -= reward;

        if (b0AmountIn > 0) {
            vault0.deposit(uint256(0), b0AmountIn);
        }

        // Cumulate lpPnl into cumulativePnlOnGateway,
        // which will be distributed to all LPs on all i-chains with next request process
        data.cumulativePnlOnGateway = data.cumulativePnlOnGateway.addUnchecked(lpPnl.rescale(decimalsB0, 18));
        data.b0Amount = 0;
        _saveData(data);

        {
            uint256 lastRequestIChainExecutionFee = _dTokenStates[v.pTokenId].getUint(I.D_LASTREQUESTICHAINEXECUTIONFEE);
            uint256 cumulativeUnusedIChainExecutionFee = _dTokenStates[v.pTokenId].getUint(I.D_CUMULATIVEUNUSEDICHAINEXECUTIONFEE);
            _dTokenStates[v.pTokenId].del(I.D_LASTREQUESTICHAINEXECUTIONFEE);
            _dTokenStates[v.pTokenId].del(I.D_CUMULATIVEUNUSEDICHAINEXECUTIONFEE);

            uint256 totalIChainExecutionFee = _gatewayStates.getUint(I.S_TOTALICHAINEXECUTIONFEE);
            totalIChainExecutionFee -= lastRequestIChainExecutionFee + cumulativeUnusedIChainExecutionFee;
            _gatewayStates.set(I.S_TOTALICHAINEXECUTIONFEE, totalIChainExecutionFee);
        }

        pToken.burn(v.pTokenId);

        emit FinishLiquidate(
            v.requestId,
            v.pTokenId,
            lpPnl
        );
    }

    //================================================================================
    // Internals
    //================================================================================

    // Temporary struct holding intermediate values passed around functions
    struct Data {
        address account;                   // Lp/Trader account address
        uint256 dTokenId;                  // Lp/Trader dTokenId
        address bToken;                    // Lp/Trader bToken address

        int256  cumulativePnlOnGateway;    // cumulative pnl on Gateway
        address vault;                     // Lp/Trader bToken's vault address

        int256  b0Amount;                  // Lp/Trader b0Amount
        int256  lastCumulativePnlOnEngine; // Lp/Trader last cumulative pnl on engine

        uint256 collateralFactor;          // bToken collateral factor
        uint256 bPrice;                    // bToken price
    }

    function _getData(address account, uint256 dTokenId, address bToken) internal view returns (Data memory data) {
        data.account = account;
        data.dTokenId = dTokenId;
        data.bToken = bToken;

        data.cumulativePnlOnGateway = _gatewayStates.getInt(I.S_CUMULATIVEPNLONGATEWAY);
        data.vault = _bTokenStates[bToken].getAddress(I.B_VAULT);

        data.b0Amount = _dTokenStates[dTokenId].getInt(I.D_B0AMOUNT);
        data.lastCumulativePnlOnEngine = _dTokenStates[dTokenId].getInt(I.D_LASTCUMULATIVEPNLONENGINE);
    }

    function _getDataAndCheckBTokenConsistency(address account, uint256 dTokenId, address bToken) internal view returns (Data memory data) {
        data = _getData(account, dTokenId, bToken);
        _checkBTokenConsistency(dTokenId, bToken);
    }

    function _saveData(Data memory data) internal {
        _gatewayStates.set(I.S_CUMULATIVEPNLONGATEWAY, data.cumulativePnlOnGateway);
        _dTokenStates[data.dTokenId].set(I.D_BTOKEN, data.bToken);
        _dTokenStates[data.dTokenId].set(I.D_B0AMOUNT, data.b0Amount);
        _dTokenStates[data.dTokenId].set(I.D_LASTCUMULATIVEPNLONENGINE, data.lastCumulativePnlOnEngine);
    }

    // @notice Check callback's requestId is the same as the current requestId stored for user
    // If a new request is submitted before the callback for last request, requestId will not match,
    // and this callback cannot be executed anymore
    function _checkRequestId(uint256 dTokenId, uint256 requestId) internal {
        uint128 userRequestId = uint128(requestId);
        if (_dTokenStates[dTokenId].getUint(I.D_REQUESTID) != uint256(userRequestId)) {
            revert InvalidRequestId();
        } else {
            // increment requestId so that callback can only be executed once
            _dTokenStates[dTokenId].set(I.D_REQUESTID, uint256(userRequestId + 1));
        }
    }

    // @notice Increment gateway requestId and user requestId
    // and returns the combined requestId for this request
    // The combined requestId contains 2 parts:
    //   * Lower 128 bits stores user's requestId, only increments when request is from this user
    //   * Higher 128 bits stores gateways's requestId, increments for all new requests in this contract
    function _incrementRequestId(uint256 dTokenId) internal returns (uint256) {
        uint128 gatewayRequestId = uint128(_gatewayStates.getUint(I.S_GATEWAYREQUESTID));
        gatewayRequestId += 1;
        _gatewayStates.set(I.S_GATEWAYREQUESTID, uint256(gatewayRequestId));

        uint128 userRequestId = uint128(_dTokenStates[dTokenId].getUint(I.D_REQUESTID));
        userRequestId += 1;
        _dTokenStates[dTokenId].set(I.D_REQUESTID, uint256(userRequestId));

        uint256 requestId = (uint256(gatewayRequestId) << 128) + uint256(userRequestId);
        return requestId;
    }

    function _checkBTokenInitialized(address bToken) internal view {
        if (_bTokenStates[bToken].getAddress(I.B_VAULT) == address(0)) {
            revert InvalidBToken();
        }
    }

    function _checkBTokenConsistency(uint256 dTokenId, address bToken) internal view {
        address preBToken = _dTokenStates[dTokenId].getAddress(I.D_BTOKEN);
        if (preBToken != address(0) && preBToken != bToken) {
            uint256 stAmount = IVault(_bTokenStates[preBToken].getAddress(I.B_VAULT)).stAmounts(dTokenId);
            if (stAmount != 0) {
                revert InvalidBToken();
            }
        }
    }

    function _checkLTokenIdOwner(uint256 lTokenId, address owner) internal view {
        if (lToken.ownerOf(lTokenId) != owner) {
            revert InvalidLTokenId();
        }
    }

    function _checkPTokenIdOwner(uint256 pTokenId, address owner) internal view {
        if (pToken.ownerOf(pTokenId) != owner) {
            revert InvalidPTokenId();
        }
    }

    function _receiveExecutionFee(uint256 dTokenId, uint256 executionFee) internal returns (uint256) {
        return GatewayHelper.receiveExecutionFee(_gatewayStates, _dTokenStates, dTokenId, executionFee);
    }

    function _transferLastRequestIChainExecutionFee(uint256 dTokenId, address to) internal {
        GatewayHelper.transferLastRequestIChainExecutionFee(_gatewayStates, _dTokenStates, dTokenId, to);
    }

    // @dev bPrice * bAmount / UONE = b0Amount, b0Amount in decimalsB0
    function _getBPrice(address bToken) internal view returns (uint256 bPrice) {
        if (bToken == tokenB0) {
            bPrice = UONE;
        } else {
            uint8 decimalsB = bToken.decimals();
            bPrice = oracle.getValue(_bTokenStates[bToken].getBytes32(I.B_ORACLEID)).itou().rescale(decimalsB, decimalsB0);
            if (bPrice == 0) {
                revert InvalidBPrice();
            }
        }
    }

    function _getExParams(Data memory data) internal view {
        data.collateralFactor = _bTokenStates[data.bToken].getUint(I.B_COLLATERALFACTOR);
        data.bPrice = _getBPrice(data.bToken);
    }

    // @notice Calculate the liquidity (in 18 decimals) associated with current dTokenId
    function _getDTokenLiquidity(Data memory data) internal view returns (uint256 liquidity) {
        uint256 b0AmountInVault = IVault(data.vault).getBalance(data.dTokenId) * data.bPrice / UONE * data.collateralFactor / UONE;
        uint256 b0Shortage = data.b0Amount >= 0 ? 0 : (-data.b0Amount).itou();
        if (b0AmountInVault >= b0Shortage) {
            liquidity = b0AmountInVault.add(data.b0Amount).rescale(decimalsB0, 18);
        }
    }

    // @notice Calculate the liquidity (in 18 decimals) associated with current dTokenId if `bAmount` in bToken is removed
    function _getDTokenLiquidityWithRemove(Data memory data, uint256 bAmount) internal view returns (uint256 liquidity) {
        if (bAmount < type(uint256).max / data.bPrice) { // make sure bAmount * bPrice won't overflow
            uint256 bAmountInVault = IVault(data.vault).getBalance(data.dTokenId);
            if (bAmount >= bAmountInVault) {
                if (data.b0Amount > 0) {
                    uint256 b0Shortage = (bAmount - bAmountInVault) * data.bPrice / UONE;
                    uint256 b0Amount = data.b0Amount.itou();
                    if (b0Amount > b0Shortage) {
                        liquidity = (b0Amount - b0Shortage);
                    }
                }
            } else {
                uint256 b0Excessive = (bAmountInVault - bAmount) * data.bPrice / UONE * data.collateralFactor / UONE; // discounted
                if (data.b0Amount >= 0) {
                    liquidity = b0Excessive.add(data.b0Amount);
                } else {
                    uint256 b0Shortage = (-data.b0Amount).itou();
                    if (b0Excessive > b0Shortage) {
                        liquidity = (b0Excessive - b0Shortage);
                    }
                }
            }
            if (liquidity > 0) {
                liquidity = liquidity.rescale(decimalsB0, 18);
            }
        }
    }

    function _getDTokenLiquidityWithRemoveB0(Data memory data, uint256 b0AmountToRemove) internal view returns (uint256 liquidity) {
        uint256 bAmountInVault = IVault(data.vault).getBalance(data.dTokenId);
        uint256 b0ValueOfBAmountInVault = bAmountInVault * data.bPrice / UONE * data.collateralFactor / UONE; // discounted
        uint256 b0Total;
        if (data.b0Amount >= 0) {
            b0Total = b0ValueOfBAmountInVault.add(data.b0Amount);
        } else {
            b0Total = b0ValueOfBAmountInVault - (-data.b0Amount).itou();
        }
        if (b0Total > b0AmountToRemove) {
            liquidity = (b0Total - b0AmountToRemove).rescale(decimalsB0, 18);
        }
    }

    // @notice Deposit bToken with `bAmount`
    function _deposit(Data memory data, uint256 bAmount) internal {
        if (data.bToken == tokenB0) {
            uint256 reserved = bAmount * b0ReserveRatio / UONE;
            bAmount -= reserved;
            vault0.deposit(uint256(0), reserved);
            data.b0Amount += reserved.utoi();
        }
        if (data.bToken == tokenETH) {
            IVault(data.vault).deposit{value: bAmount}(data.dTokenId, bAmount);
        } else {
            IVault(data.vault).deposit(data.dTokenId, bAmount);
        }
    }

    /**
     * @notice Transfer a specified amount of bToken, handling various cases.
     * @param data A Data struct containing information about the interaction.
     * @param bAmountOut The intended amount of tokens to transfer out.
     * @param isTd A flag indicating whether the transfer is for a trader (true) or not (false).
     * @return bAmount The amount of tokens actually transferred.
     */
    function _transferOut(Data memory data, uint256 bAmountOut, bool isTd) internal returns (uint256 bAmount) {
        require(!ISwitchOracle(switchOracle).state());

        uint256 minSwapB0Amount = 10 ** (decimalsB0 - 2); // min swap b0Amount of 0.01 USDC
        bAmount = bAmountOut;

        // Handle redemption of additional tokens to cover a negative B0 amount.
        if (bAmount < type(uint256).max / UONE && data.b0Amount < 0) {
            if (data.bToken == tokenB0) {
                // Redeem B0 tokens to cover the negative B0 amount.
                bAmount += (-data.b0Amount).itou();
            } else {
                // Swap tokens to B0 to cover the negative B0 amount, with a slight excess to account for possible slippage.
                bAmount += (-data.b0Amount).itou() * UONE / data.bPrice * 105 / 100;
            }
        }

        // Redeem tokens from the vault using IVault interface.
        bAmount = IVault(data.vault).redeem(data.dTokenId, bAmount); // bAmount now represents the actual redeemed bToken.

        uint256 b0AmountIn;  // Amount of B0 tokens going to reserves.
        uint256 b0AmountOut; // Amount of B0 tokens going to the user.
        uint256 iouAmount;   // Amount of IOU tokens going to the trader.

        // Handle excessive tokens (more than bAmountOut).
        if (bAmount > bAmountOut) {
            uint256 bExcessive = bAmount - bAmountOut;
            uint256 b0Excessive;
            if (data.bToken == tokenB0) {
                b0Excessive = bExcessive;
                bAmount -= bExcessive;
            } else if (data.bToken == tokenETH) {
                (uint256 resultB0, uint256 resultBX) = swapper.swapExactETHForB0{value: bExcessive}();
                b0Excessive = resultB0;
                bAmount -= resultBX;
            } else {
                (uint256 resultB0, uint256 resultBX) = swapper.swapExactBXForB0(data.bToken, bExcessive);
                b0Excessive = resultB0;
                bAmount -= resultBX;
            }
            b0AmountIn += b0Excessive;
            data.b0Amount += b0Excessive.utoi();
        }

        // Handle filling the negative B0 balance, by swapping bToken into B0, if necessary.
        if (bAmount > 0 && data.b0Amount < 0) {
            uint256 owe = (-data.b0Amount).itou();
            uint256 b0Fill;
            if (data.bToken == tokenB0) {
                if (bAmount >= owe) {
                    b0Fill = owe;
                    bAmount -= owe;
                } else {
                    b0Fill = bAmount;
                    bAmount = 0;
                }
            } else {
                // let owe equals to minSwapB0Amount if small, otherwise swap may fail
                if (owe < minSwapB0Amount) {
                    owe = minSwapB0Amount;
                }
                if (data.bToken == tokenETH) {
                    (uint256 resultB0, uint256 resultBX) = swapper.swapETHForExactB0{value: bAmount}(owe);
                    b0Fill = resultB0;
                    bAmount -= resultBX;
                } else {
                    (uint256 resultB0, uint256 resultBX) = swapper.swapBXForExactB0(data.bToken, owe, bAmount);
                    b0Fill = resultB0;
                    bAmount -= resultBX;
                }
            }
            b0AmountIn += b0Fill;
            data.b0Amount += b0Fill.utoi();
        }

        // Handle reserved portion when withdrawing all or operating token is tokenB0
        if (data.b0Amount > 0) {
            uint256 amount;
            if (bAmountOut >= type(uint256).max / UONE) { // withdraw all
                amount = data.b0Amount.itou();
            } else if (data.bToken == tokenB0 && bAmount < bAmountOut) { // shortage on tokenB0
                amount = SafeMath.min(data.b0Amount.itou(), bAmountOut - bAmount);
            }
            if (amount > 0) {
                uint256 b0Out;
                if (amount > b0AmountIn) {
                    // Redeem B0 tokens from vault0
                    uint256 b0Redeemed = vault0.redeem(uint256(0), amount - b0AmountIn);
                    if (b0Redeemed < amount - b0AmountIn) { // b0 insufficent
                        if (isTd) {
                            iouAmount = amount - b0AmountIn - b0Redeemed; // Issue IOU for trader when B0 insufficent
                        } else {
                            revert InsufficientB0(); // Revert for Lp when B0 insufficent
                        }
                    }
                    b0Out = b0AmountIn + b0Redeemed;
                    b0AmountIn = 0;
                } else {
                    b0Out = amount;
                    b0AmountIn -= amount;
                }
                b0AmountOut += b0Out;
                data.b0Amount -= b0Out.utoi() + iouAmount.utoi();
            }
        }

        // Deposit B0 tokens into the vault0, if any
        if (b0AmountIn > 0) {
            vault0.deposit(uint256(0), b0AmountIn);
        }

        // Transfer B0 tokens or swap them to the current operating token
        if (b0AmountOut > 0) {
            if (isTd) {
                // No swap from B0 to BX for trader
                if (data.bToken == tokenB0) {
                    bAmount += b0AmountOut;
                } else {
                    tokenB0.transferOut(data.account, b0AmountOut);
                }
            } else {
                // Swap B0 into BX for Lp
                if (data.bToken == tokenB0) {
                    bAmount += b0AmountOut;
                } else if (b0AmountOut < minSwapB0Amount) {
                    // cannot swap such small amount of B0, cumulate it into cumulativePnlOnGateway
                    data.cumulativePnlOnGateway = data.cumulativePnlOnGateway.addUnchecked(b0AmountOut.utoi().rescale(decimalsB0, 18));
                } else if (data.bToken == tokenETH) {
                    (, uint256 resultBX) = swapper.swapExactB0ForETH(b0AmountOut);
                    bAmount += resultBX;
                } else {
                    (, uint256 resultBX) = swapper.swapExactB0ForBX(data.bToken, b0AmountOut);
                    bAmount += resultBX;
                }
            }
        }

        // Transfer the remaining bAmount to the user's account.
        if (bAmount > 0) {
            data.bToken.transferOut(data.account, bAmount);
        }

        // Mint IOU tokens for the trader, if any.
        if (iouAmount > 0) {
            iou.mint(data.account, iouAmount);
        }
    }

    /**
     * @dev Update liquidity-related state variables for a specific `lTokenId`.
     * @param lTokenId The ID of the corresponding lToken.
     * @param newLiquidity The new liquidity amount for the lToken.
     * @param newTotalLiquidity The new total liquidity in the engine.
     */
    function _updateLiquidity(uint256 lTokenId, uint256 newLiquidity, uint256 newTotalLiquidity) internal {
        (uint256 cumulativeTimePerLiquidity, uint256 cumulativeTime) = getCumulativeTime(lTokenId);
        _gatewayStates.set(I.S_LIQUIDITYTIME, block.timestamp);
        _gatewayStates.set(I.S_TOTALLIQUIDITY, newTotalLiquidity);
        _gatewayStates.set(I.S_CUMULATIVETIMEPERLIQUIDITY, cumulativeTimePerLiquidity);
        _dTokenStates[lTokenId].set(I.D_LIQUIDITY, newLiquidity);
        _dTokenStates[lTokenId].set(I.D_CUMULATIVETIME, cumulativeTime);
        _dTokenStates[lTokenId].set(I.D_LASTCUMULATIVETIMEPERLIQUIDITY, cumulativeTimePerLiquidity);
    }

}

File 2 of 32 : IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

File 3 of 32 : IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

File 4 of 32 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

File 5 of 32 : SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // 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 cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    }
}

File 6 of 32 : IERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

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

pragma solidity ^0.8.1;

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

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

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

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

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

File 8 of 32 : ECDSA.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.0;

import "../Strings.sol";

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS,
        InvalidSignatureV // Deprecated in v4.8
    }

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature` or error string. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, signature);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
        bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
        uint8 v = uint8((uint256(vs) >> 255) + 27);
        return tryRecover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     *
     * _Available since v4.2._
     */
    function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, r, vs);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature);
        }

        return (signer, RecoverError.NoError);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, "\x19Ethereum Signed Message:\n32")
            mstore(0x1c, hash)
            message := keccak256(0x00, 0x3c)
        }
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from `s`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, "\x19\x01")
            mstore(add(ptr, 0x02), domainSeparator)
            mstore(add(ptr, 0x22), structHash)
            data := keccak256(ptr, 0x42)
        }
    }

    /**
     * @dev Returns an Ethereum Signed Data with intended validator, created from a
     * `validator` and `data` according to the version 0 of EIP-191.
     *
     * See {recover}.
     */
    function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x00", validator, data));
    }
}

File 9 of 32 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

File 10 of 32 : Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

File 11 of 32 : SignedMath.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

File 12 of 32 : Strings.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";
import "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toString(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

File 13 of 32 : GatewayHelper.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

import '../vault/IVault.sol';
import '../token/IDToken.sol';
import '../token/IIOU.sol';
import '../../oracle/IOracle.sol';
import '../swapper/ISwapper.sol';
import './IGateway.sol';
import '../liqclaim/ILiqClaim.sol';
import '../../library/Bytes32Map.sol';
import '../../library/ETHAndERC20.sol';
import '../../library/SafeMath.sol';
import '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
import { GatewayIndex as I } from './GatewayIndex.sol';

library GatewayHelper {

    using Bytes32Map for mapping(uint8 => bytes32);
    using ETHAndERC20 for address;
    using SafeMath for uint256;
    using SafeMath for int256;

    error CannotDelBToken();
    error BTokenDupInitialize();
    error BTokenNoSwapper();
    error BTokenNoOracle();
    error InvalidBToken();
    error InvalidSignature();
    error InsufficientExecutionFee();

    event AddBToken(address bToken, address vault, bytes32 oracleId, uint256 collateralFactor);

    event DelBToken(address bToken);

    event UpdateBToken(address bToken);

    event SetExecutionFee(uint256 actionId, uint256 executionFee);

    event FinishCollectProtocolFee(
        uint256 amount
    );

    uint256 constant UONE = 1e18;
    int256 constant ONE = 1e18;
    address constant tokenETH = address(1);

    //================================================================================
    // Getters
    //================================================================================

    function getGatewayState(mapping(uint8 => bytes32) storage gatewayStates)
    external view returns (IGateway.GatewayState memory s)
    {
        s.cumulativePnlOnGateway = gatewayStates.getInt(I.S_CUMULATIVEPNLONGATEWAY);
        s.liquidityTime = gatewayStates.getUint(I.S_LIQUIDITYTIME);
        s.totalLiquidity = gatewayStates.getUint(I.S_TOTALLIQUIDITY);
        s.cumulativeTimePerLiquidity = gatewayStates.getInt(I.S_CUMULATIVETIMEPERLIQUIDITY);
        s.gatewayRequestId = gatewayStates.getUint(I.S_GATEWAYREQUESTID);
        s.dChainExecutionFeePerRequest = gatewayStates.getUint(I.S_DCHAINEXECUTIONFEEPERREQUEST);
        s.totalIChainExecutionFee = gatewayStates.getUint(I.S_TOTALICHAINEXECUTIONFEE);
        s.cumulativeCollectedProtocolFee = gatewayStates.getUint(I.S_CUMULATIVECOLLECTEDPROTOCOLFEE);
    }

    function getBTokenState(
        mapping(address => mapping(uint8 => bytes32)) storage bTokenStates,
        address bToken
    ) external view returns (IGateway.BTokenState memory s)
    {
        s.vault = bTokenStates[bToken].getAddress(I.B_VAULT);
        s.oracleId = bTokenStates[bToken].getBytes32(I.B_ORACLEID);
        s.collateralFactor = bTokenStates[bToken].getUint(I.B_COLLATERALFACTOR);
    }

    function getLpState(
        mapping(address => mapping(uint8 => bytes32)) storage bTokenStates,
        mapping(uint256 => mapping(uint8 => bytes32)) storage dTokenStates,
        uint256 lTokenId
    ) external view returns (IGateway.LpState memory s)
    {
        s.requestId = dTokenStates[lTokenId].getUint(I.D_REQUESTID);
        s.bToken = dTokenStates[lTokenId].getAddress(I.D_BTOKEN);
        s.bAmount = IVault(bTokenStates[s.bToken].getAddress(I.B_VAULT)).getBalance(lTokenId);
        s.b0Amount = dTokenStates[lTokenId].getInt(I.D_B0AMOUNT);
        s.lastCumulativePnlOnEngine = dTokenStates[lTokenId].getInt(I.D_LASTCUMULATIVEPNLONENGINE);
        s.liquidity = dTokenStates[lTokenId].getUint(I.D_LIQUIDITY);
        s.cumulativeTime = dTokenStates[lTokenId].getUint(I.D_CUMULATIVETIME);
        s.lastCumulativeTimePerLiquidity = dTokenStates[lTokenId].getUint(I.D_LASTCUMULATIVETIMEPERLIQUIDITY);
        s.lastRequestIChainExecutionFee = dTokenStates[lTokenId].getUint(I.D_LASTREQUESTICHAINEXECUTIONFEE);
        s.cumulativeUnusedIChainExecutionFee = dTokenStates[lTokenId].getUint(I.D_CUMULATIVEUNUSEDICHAINEXECUTIONFEE);
    }

    function getTdState(
        mapping(address => mapping(uint8 => bytes32)) storage bTokenStates,
        mapping(uint256 => mapping(uint8 => bytes32)) storage dTokenStates,
        uint256 pTokenId
    ) external view returns (IGateway.TdState memory s)
    {
        s.requestId = dTokenStates[pTokenId].getUint(I.D_REQUESTID);
        s.bToken = dTokenStates[pTokenId].getAddress(I.D_BTOKEN);
        s.bAmount = IVault(bTokenStates[s.bToken].getAddress(I.B_VAULT)).getBalance(pTokenId);
        s.b0Amount = dTokenStates[pTokenId].getInt(I.D_B0AMOUNT);
        s.lastCumulativePnlOnEngine = dTokenStates[pTokenId].getInt(I.D_LASTCUMULATIVEPNLONENGINE);
        s.singlePosition = dTokenStates[pTokenId].getBool(I.D_SINGLEPOSITION);
        s.lastRequestIChainExecutionFee = dTokenStates[pTokenId].getUint(I.D_LASTREQUESTICHAINEXECUTIONFEE);
        s.cumulativeUnusedIChainExecutionFee = dTokenStates[pTokenId].getUint(I.D_CUMULATIVEUNUSEDICHAINEXECUTIONFEE);
    }

    function getCumulativeTime(
        mapping(uint8 => bytes32) storage gatewayStates,
        mapping(uint256 => mapping(uint8 => bytes32)) storage dTokenStates,
        uint256 lTokenId
    ) external view returns (uint256 cumulativeTimePerLiquidity, uint256 cumulativeTime)
    {
        uint256 liquidityTime = gatewayStates.getUint(I.S_LIQUIDITYTIME);
        uint256 totalLiquidity = gatewayStates.getUint(I.S_TOTALLIQUIDITY);
        cumulativeTimePerLiquidity = gatewayStates.getUint(I.S_CUMULATIVETIMEPERLIQUIDITY);
        uint256 liquidity = dTokenStates[lTokenId].getUint(I.D_LIQUIDITY);
        cumulativeTime = dTokenStates[lTokenId].getUint(I.D_CUMULATIVETIME);
        uint256 lastCumulativeTimePerLiquidity = dTokenStates[lTokenId].getUint(I.D_LASTCUMULATIVETIMEPERLIQUIDITY);

        if (totalLiquidity != 0) {
            uint256 diff1 = (block.timestamp - liquidityTime) * UONE * UONE / totalLiquidity;
            unchecked { cumulativeTimePerLiquidity += diff1; }

            if (liquidity != 0) {
                uint256 diff2;
                unchecked { diff2 = cumulativeTimePerLiquidity - lastCumulativeTimePerLiquidity; }
                cumulativeTime += diff2 * liquidity / UONE;
            }
        }
    }

    function getExecutionFees(mapping(uint256 => uint256) storage executionFees)
    external view returns (uint256[] memory fees)
    {
        fees = new uint256[](5);
        fees[0] = executionFees[I.ACTION_REQUESTADDLIQUIDITY];
        fees[1] = executionFees[I.ACTION_REQUESTREMOVELIQUIDITY];
        fees[2] = executionFees[I.ACTION_REQUESTREMOVEMARGIN];
        fees[3] = executionFees[I.ACTION_REQUESTTRADE];
        fees[4] = executionFees[I.ACTION_REQUESTTRADEANDREMOVEMARGIN];
    }

    //================================================================================
    // Setters
    //================================================================================

    function addBToken(
        mapping(address => mapping(uint8 => bytes32)) storage bTokenStates,
        ISwapper swapper,
        IOracle oracle,
        IVault vault0,
        address tokenB0,
        address bToken,
        address vault,
        bytes32 oracleId,
        uint256 collateralFactor
    ) external
    {
        if (bTokenStates[bToken].getAddress(I.B_VAULT) != address(0)) {
            revert BTokenDupInitialize();
        }
        if (IVault(vault).asset() != bToken) {
            revert InvalidBToken();
        }
        if (bToken != tokenETH) {
            if (!swapper.isSupportedToken(bToken)) {
                revert BTokenNoSwapper();
            }
            // Approve for swapper and vault
            bToken.approveMax(address(swapper));
            bToken.approveMax(vault);
            if (bToken == tokenB0) {
                // The reserved portion for B0 will be deposited to vault0
                bToken.approveMax(address(vault0));
            }
        }
        // Check bToken oracle except B0
        if (bToken != tokenB0 && oracle.getValue(oracleId) == 0) {
            revert BTokenNoOracle();
        }
        bTokenStates[bToken].set(I.B_VAULT, vault);
        bTokenStates[bToken].set(I.B_ORACLEID, oracleId);
        bTokenStates[bToken].set(I.B_COLLATERALFACTOR, collateralFactor);

        emit AddBToken(bToken, vault, oracleId, collateralFactor);
    }

    function delBToken(
        mapping(address => mapping(uint8 => bytes32)) storage bTokenStates,
        address bToken
    ) external
    {
        // bToken can only be deleted when there is no deposits
        if (IVault(bTokenStates[bToken].getAddress(I.B_VAULT)).stTotalAmount() != 0) {
            revert CannotDelBToken();
        }

        bTokenStates[bToken].del(I.B_VAULT);
        bTokenStates[bToken].del(I.B_ORACLEID);
        bTokenStates[bToken].del(I.B_COLLATERALFACTOR);

        emit DelBToken(bToken);
    }

    // @dev This function can be used to change bToken collateral factor
    function setBTokenParameter(
        mapping(address => mapping(uint8 => bytes32)) storage bTokenStates,
        address bToken,
        uint8 idx,
        bytes32 value
    ) external
    {
        bTokenStates[bToken].set(idx, value);
        emit UpdateBToken(bToken);
    }

    // @notice Set execution fee for actionId
    function setExecutionFee(
        mapping(uint256 => uint256) storage executionFees,
        uint256 actionId,
        uint256 executionFee
    ) external
    {
        executionFees[actionId] = executionFee;
        emit SetExecutionFee(actionId, executionFee);
    }

    function setDChainExecutionFeePerRequest(
        mapping(uint8 => bytes32) storage gatewayStates,
        uint256 dChainExecutionFeePerRequest
    ) external
    {
        gatewayStates.set(I.S_DCHAINEXECUTIONFEEPERREQUEST, dChainExecutionFeePerRequest);
    }

    // @notic Claim dChain executionFee to account `to`
    function claimDChainExecutionFee(
        mapping(uint8 => bytes32) storage gatewayStates,
        address to
    ) external
    {
        tokenETH.transferOut(to, tokenETH.balanceOfThis() - gatewayStates.getUint(I.S_TOTALICHAINEXECUTIONFEE));
    }

    // @notice Claim unused iChain execution fee for dTokenId
    function claimUnusedIChainExecutionFee(
        mapping(uint8 => bytes32) storage gatewayStates,
        mapping(uint256 => mapping(uint8 => bytes32)) storage dTokenStates,
        IDToken lToken,
        IDToken pToken,
        uint256 dTokenId,
        bool isLp
    ) external
    {
        address owner = isLp ? lToken.ownerOf(dTokenId) : pToken.ownerOf(dTokenId);
        uint256 cumulativeUnusedIChainExecutionFee = dTokenStates[dTokenId].getUint(I.D_CUMULATIVEUNUSEDICHAINEXECUTIONFEE);
        if (cumulativeUnusedIChainExecutionFee > 0) {
            uint256 totalIChainExecutionFee = gatewayStates.getUint(I.S_TOTALICHAINEXECUTIONFEE);
            totalIChainExecutionFee -= cumulativeUnusedIChainExecutionFee;
            gatewayStates.set(I.S_TOTALICHAINEXECUTIONFEE, totalIChainExecutionFee);

            dTokenStates[dTokenId].del(I.D_CUMULATIVEUNUSEDICHAINEXECUTIONFEE);

            tokenETH.transferOut(owner, cumulativeUnusedIChainExecutionFee);
        }
    }

    // @notice Redeem B0 for burning IOU
    function redeemIOU(
        address tokenB0,
        IVault vault0,
        IIOU iou,
        address to,
        uint256 b0Amount
    ) external {
        if (b0Amount > 0) {
            uint256 b0Redeemed = vault0.redeem(uint256(0), b0Amount);
            if (b0Redeemed > 0) {
                iou.burn(to, b0Redeemed);
                tokenB0.transferOut(to, b0Redeemed);
            }
        }
    }

    function verifyEventData(
        bytes memory eventData,
        bytes memory signature,
        uint256 eventDataLength,
        address dChainEventSigner
    ) external pure {
        require(eventData.length == eventDataLength, 'Wrong eventData length');
        bytes32 hash = ECDSA.toEthSignedMessageHash(keccak256(eventData));
        if (ECDSA.recover(hash, signature) != dChainEventSigner) {
            revert InvalidSignature();
        }
    }


    //================================================================================
    // Interactions
    //================================================================================

    function finishCollectProtocolFee(
        mapping(uint8 => bytes32) storage gatewayStates,
        IVault vault0,
        address tokenB0,
        address protocolFeeManager,
        uint256 cumulativeCollectedProtocolFeeOnEngine
    ) external {
        uint8 decimalsB0 = tokenB0.decimals();
        uint256 cumulativeCollectedProtocolFeeOnGateway = gatewayStates.getUint(I.S_CUMULATIVECOLLECTEDPROTOCOLFEE);
        if (cumulativeCollectedProtocolFeeOnEngine > cumulativeCollectedProtocolFeeOnGateway) {
            uint256 amount = (cumulativeCollectedProtocolFeeOnEngine - cumulativeCollectedProtocolFeeOnGateway).rescaleDown(18, decimalsB0);
            if (amount > 0) {
                amount = vault0.redeem(uint256(0), amount);
                tokenB0.transferOut(protocolFeeManager, amount);
                cumulativeCollectedProtocolFeeOnGateway += amount.rescale(decimalsB0, 18);
                gatewayStates.set(I.S_CUMULATIVECOLLECTEDPROTOCOLFEE, cumulativeCollectedProtocolFeeOnGateway);
                emit FinishCollectProtocolFee(
                    amount
                );
            }
        }
    }

    function liquidateRedeemAndSwap(
        uint8 decimalsB0,
        address bToken,
        address swapper,
        address liqClaim,
        address pToken,
        uint256 pTokenId,
        int256 b0Amount,
        uint256 bAmount,
        int256 maintenanceMarginRequired
    ) external returns (uint256) {
        uint256 b0AmountIn;

        // only swap needed B0 to cover maintenanceMarginRequired
        int256 requiredB0Amount = maintenanceMarginRequired.rescaleUp(18, decimalsB0) - b0Amount;
        if (requiredB0Amount > 0) {
            if (bToken == tokenETH) {
                (uint256 resultB0, uint256 resultBX) = ISwapper(swapper).swapETHForExactB0{value:bAmount}(requiredB0Amount.itou());
                b0AmountIn += resultB0;
                bAmount -= resultBX;
            } else {
                (uint256 resultB0, uint256 resultBX) = ISwapper(swapper).swapBXForExactB0(bToken, requiredB0Amount.itou(), bAmount);
                b0AmountIn += resultB0;
                bAmount -= resultBX;
            }
        }
        if (bAmount > 0) {
            bToken.transferOut(liqClaim, bAmount);
            ILiqClaim(liqClaim).registerDeposit(IDToken(pToken).ownerOf(pTokenId), bToken, bAmount);
        }

        return b0AmountIn;
    }

    function receiveExecutionFee(
        mapping(uint8 => bytes32) storage gatewayStates,
        mapping(uint256 => mapping(uint8 => bytes32)) storage dTokenStates,
        uint256 dTokenId,
        uint256 executionFee
    ) external returns (uint256)
    {
        uint256 dChainExecutionFee = gatewayStates.getUint(I.S_DCHAINEXECUTIONFEEPERREQUEST);
        if (msg.value < executionFee) {
            revert InsufficientExecutionFee();
        }
        uint256 iChainExecutionFee = executionFee - dChainExecutionFee;

        uint256 totalIChainExecutionFee = gatewayStates.getUint(I.S_TOTALICHAINEXECUTIONFEE) + iChainExecutionFee;
        gatewayStates.set(I.S_TOTALICHAINEXECUTIONFEE,  totalIChainExecutionFee);

        uint256 lastRequestIChainExecutionFee = dTokenStates[dTokenId].getUint(I.D_LASTREQUESTICHAINEXECUTIONFEE);
        uint256 cumulativeUnusedIChainExecutionFee = dTokenStates[dTokenId].getUint(I.D_CUMULATIVEUNUSEDICHAINEXECUTIONFEE);
        cumulativeUnusedIChainExecutionFee += lastRequestIChainExecutionFee;
        lastRequestIChainExecutionFee = iChainExecutionFee;
        dTokenStates[dTokenId].set(I.D_LASTREQUESTICHAINEXECUTIONFEE, lastRequestIChainExecutionFee);
        dTokenStates[dTokenId].set(I.D_CUMULATIVEUNUSEDICHAINEXECUTIONFEE, cumulativeUnusedIChainExecutionFee);

        return msg.value - executionFee;
    }

    function transferLastRequestIChainExecutionFee(
        mapping(uint8 => bytes32) storage gatewayStates,
        mapping(uint256 => mapping(uint8 => bytes32)) storage dTokenStates,
        uint256 dTokenId,
        address to
    ) external
    {
        uint256 lastRequestIChainExecutionFee = dTokenStates[dTokenId].getUint(I.D_LASTREQUESTICHAINEXECUTIONFEE);

        if (lastRequestIChainExecutionFee > 0) {
            uint256 totalIChainExecutionFee = gatewayStates.getUint(I.S_TOTALICHAINEXECUTIONFEE);
            totalIChainExecutionFee -= lastRequestIChainExecutionFee;
            gatewayStates.set(I.S_TOTALICHAINEXECUTIONFEE, totalIChainExecutionFee);

            dTokenStates[dTokenId].del(I.D_LASTREQUESTICHAINEXECUTIONFEE);

            tokenETH.transferOut(to, lastRequestIChainExecutionFee);
        }
    }

    function calculateReward(
        int256 lpPnl,
        int256 minLiquidationReward,
        int256 maxLiquidationReward,
        int256 liquidationRewardCutRatio
    ) external pure returns (int256 reward)
    {
        if (lpPnl <= minLiquidationReward) {
            reward = minLiquidationReward;
        } else {
            reward = SafeMath.min(
                (lpPnl - minLiquidationReward) * liquidationRewardCutRatio / ONE + minLiquidationReward,
                maxLiquidationReward
            );
        }
    }

    function processReward(
        address tokenB0,
        IVault vault0,
        int256 reward,
        uint256 b0AmountIn,
        address executor,
        address finisher
    ) external returns (int256, uint256)
    {
        uint256 uReward = reward.itou();
        if (uReward <= b0AmountIn) {
            b0AmountIn -= uReward;
        } else {
            uint256 b0Redeemed = vault0.redeem(uint256(0), uReward - b0AmountIn);
            uReward = b0AmountIn + b0Redeemed;
            reward = uReward.utoi();
            b0AmountIn = 0;
        }

        if (uReward > 0) {
            uint256 rewardExecutor = uReward * 80 / 100;  // 80%
            uint256 rewardFinisher = uReward - rewardExecutor; // 20%
            tokenB0.transferOut(executor, rewardExecutor);
            tokenB0.transferOut(finisher, rewardFinisher);
        }

        return (reward, b0AmountIn);
    }

}

File 14 of 32 : GatewayIndex.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

library GatewayIndex {

    uint8 constant S_CUMULATIVEPNLONGATEWAY       = 1; // Cumulative pnl on Gateway
    uint8 constant S_LIQUIDITYTIME                = 2; // Last timestamp when liquidity updated
    uint8 constant S_TOTALLIQUIDITY               = 3; // Total liquidity on d-chain
    uint8 constant S_CUMULATIVETIMEPERLIQUIDITY   = 4; // Cumulavie time per liquidity
    uint8 constant S_GATEWAYREQUESTID             = 5; // Gateway request id
    uint8 constant S_DCHAINEXECUTIONFEEPERREQUEST = 6; // dChain execution fee for executing request on dChain
    uint8 constant S_TOTALICHAINEXECUTIONFEE      = 7; // Total iChain execution fee paid by all requests
    uint8 constant S_CUMULATIVECOLLECTEDPROTOCOLFEE = 8; // Cumulative collected protocol fee on Gateway

    uint8 constant B_VAULT             = 1; // BToken vault address
    uint8 constant B_ORACLEID          = 2; // BToken oracle id
    uint8 constant B_COLLATERALFACTOR  = 3; // BToken collateral factor

    uint8 constant D_REQUESTID                          = 1;  // Lp/Trader request id
    uint8 constant D_BTOKEN                             = 2;  // Lp/Trader bToken
    uint8 constant D_B0AMOUNT                           = 3;  // Lp/Trader b0Amount
    uint8 constant D_LASTCUMULATIVEPNLONENGINE          = 4;  // Lp/Trader last cumulative pnl on engine
    uint8 constant D_LIQUIDITY                          = 5;  // Lp liquidity
    uint8 constant D_CUMULATIVETIME                     = 6;  // Lp cumulative time
    uint8 constant D_LASTCUMULATIVETIMEPERLIQUIDITY     = 7;  // Lp last cumulative time per liquidity
    uint8 constant D_SINGLEPOSITION                     = 8;  // Td single position flag
    uint8 constant D_LASTREQUESTICHAINEXECUTIONFEE      = 9;  // User last request's iChain execution fee
    uint8 constant D_CUMULATIVEUNUSEDICHAINEXECUTIONFEE = 10; // User cumulaitve iChain execution fee for requests cannot be finished, users can claim back
    uint8 constant D_CURRENTOPERATETOKEN                = 11;

    uint256 constant ACTION_REQUESTADDLIQUIDITY         = 1;
    uint256 constant ACTION_REQUESTREMOVELIQUIDITY      = 2;
    uint256 constant ACTION_REQUESTREMOVEMARGIN         = 3;
    uint256 constant ACTION_REQUESTTRADE                = 4;
    uint256 constant ACTION_REQUESTTRADEANDREMOVEMARGIN = 5;

}

File 15 of 32 : GatewayStorage.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

import '../../utils/Admin.sol';
import '../../utils/Implementation.sol';
import '../../utils/ReentryLock.sol';

abstract contract GatewayStorage is Admin, Implementation, ReentryLock {

    // stateId => value
    mapping(uint8 => bytes32) internal _gatewayStates;

    // bToken => stateId => value
    mapping(address => mapping(uint8 => bytes32)) internal _bTokenStates;

    // dTokenId => stateId => value
    mapping(uint256 => mapping(uint8 => bytes32)) internal _dTokenStates;

    // actionId => executionFee
    mapping(uint256 => uint256) internal _executionFees;

}

File 16 of 32 : IGateway.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

interface IGateway {

    struct GatewayParam {
        address lToken;
        address pToken;
        address oracle;
        address swapper;
        address vault0;
        address iou;
        address tokenB0;
        address dChainEventSigner;
        uint256 b0ReserveRatio;
        int256  liquidationRewardCutRatio;
        int256  minLiquidationReward;
        int256  maxLiquidationReward;
        address protocolFeeManager;
        address liqClaim;
        address switchOracle;
    }

    struct GatewayState {
        int256  cumulativePnlOnGateway;
        uint256 liquidityTime;
        uint256 totalLiquidity;
        int256  cumulativeTimePerLiquidity;
        uint256 gatewayRequestId;
        uint256 dChainExecutionFeePerRequest;
        uint256 totalIChainExecutionFee;
        uint256 cumulativeCollectedProtocolFee;
    }

    struct BTokenState {
        address vault;
        bytes32 oracleId;
        uint256 collateralFactor;
    }

    struct LpState {
        uint256 requestId;
        address bToken;
        uint256 bAmount;
        int256  b0Amount;
        int256  lastCumulativePnlOnEngine;
        uint256 liquidity;
        uint256 cumulativeTime;
        uint256 lastCumulativeTimePerLiquidity;
        uint256 lastRequestIChainExecutionFee;
        uint256 cumulativeUnusedIChainExecutionFee;
    }

    struct TdState {
        uint256 requestId;
        address bToken;
        uint256 bAmount;
        int256  b0Amount;
        int256  lastCumulativePnlOnEngine;
        bool    singlePosition;
        uint256 lastRequestIChainExecutionFee;
        uint256 cumulativeUnusedIChainExecutionFee;
    }

    struct VarOnExecuteUpdateLiquidity {
        uint256 requestId;
        uint256 lTokenId;
        uint256 liquidity;
        uint256 totalLiquidity;
        int256  cumulativePnlOnEngine;
        uint256 bAmountToRemove;
    }

    struct VarOnExecuteRemoveMargin {
        uint256 requestId;
        uint256 pTokenId;
        uint256 requiredMargin;
        int256  cumulativePnlOnEngine;
        uint256 bAmountToRemove;
    }

    struct VarOnExecuteLiquidate {
        address requester;
        address executor;
        address finisher;
        uint256 requestId;
        uint256 pTokenId;
        int256  cumulativePnlOnEngine;
        int256  maintenanceMarginRequired;
    }

    struct VarOnExecuteCollectProtocolFee {
        uint256 chainId;
        uint256 cumulativeCollectedProtocolFeeOnEngine;
    }

    function getGatewayParam() external view returns (GatewayParam memory p);

    function getGatewayState() external view returns (GatewayState memory s);

    function getBTokenState(address bToken) external view returns (BTokenState memory s);

    function getLpState(uint256 lTokenId) external view returns (LpState memory s);

    function getTdState(uint256 pTokenId) external view returns (TdState memory s);

}

File 17 of 32 : ILiqClaim.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

interface ILiqClaim {

    struct Claimable {
        address bToken;
        uint256 amount;
    }

    function getClaimables(address owner) external view returns (Claimable[] memory);

    function getTotalAmount(address bToken) external view returns (uint256);

    function registerDeposit(address owner, address bToken, uint256 amount) external;

    function redeem() external;

}

File 18 of 32 : ISwapper.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

interface ISwapper {

    function isSupportedToken(address tokenBX) external view returns (bool);

    function swapExactB0ForBX(address tokenBX, uint256 amountB0)
    external returns (uint256 resultB0, uint256 resultBX);

    function swapExactBXForB0(address tokenBX, uint256 amountBX)
    external returns (uint256 resultB0, uint256 resultBX);

    function swapB0ForExactBX(address tokenBX, uint256 maxAmountB0, uint256 amountBX)
    external returns (uint256 resultB0, uint256 resultBX);

    function swapBXForExactB0(address tokenBX, uint256 amountB0, uint256 maxAmountBX)
    external returns (uint256 resultB0, uint256 resultBX);

    function swapExactB0ForETH(uint256 amountB0)
    external returns (uint256 resultB0, uint256 resultBX);

    function swapExactETHForB0()
    external payable returns (uint256 resultB0, uint256 resultBX);

    function swapB0ForExactETH(uint256 maxAmountB0, uint256 amountBX)
    external returns (uint256 resultB0, uint256 resultBX);

    function swapETHForExactB0(uint256 amountB0)
    external payable returns (uint256 resultB0, uint256 resultBX);

}

File 19 of 32 : IDToken.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

import '@openzeppelin/contracts/token/ERC721/IERC721.sol';

interface IDToken is IERC721 {

    function ownerOf(uint256) external view returns (address);

    function totalMinted() external view returns (uint160);

    function mint(address owner) external returns (uint256 tokenId);

    function burn(uint256 tokenId) external;

}

File 20 of 32 : IIOU.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

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

interface IIOU is IERC20 {

    function vault() external view returns (address);

    function mint(address account, uint256 amount) external;

    function burn(address account, uint256 amount) external;

}

File 21 of 32 : IVault.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

interface IVault {

    function stAmounts(uint256 dTokenId) external view returns (uint256);

    function stTotalAmount() external view returns (uint256);

    function requester() external view returns (address);

    function asset() external view returns (address);

    function getBalance(uint256 dTokenId) external view returns (uint256 balance);

    function deposit(uint256 dTokenId, uint256 amount) external payable returns (uint256 mintedSt);

    function redeem(uint256 dTokenId, uint256 amount) external returns (uint256 redeemedAmount);

}

File 22 of 32 : Bytes32.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

library Bytes32 {

    error StringExceeds31Bytes(string value);

    function toUint(bytes32 value) internal pure returns (uint256) {
        return uint256(value);
    }

    function toInt(bytes32 value) internal pure returns (int256) {
        return int256(uint256(value));
    }

    function toAddress(bytes32 value) internal pure returns (address) {
        return address(uint160(uint256(value)));
    }

    function toBool(bytes32 value) internal pure returns (bool) {
        return value != bytes32(0);
    }

    /**
     * @notice Convert a bytes32 value to a string.
     * @dev This function takes an input bytes32 'value' and converts it into a string.
     *      It dynamically determines the length of the string based on non-null characters in 'value'.
     * @param value The input bytes32 value to be converted.
     * @return The string representation of the input bytes32.
     */
    function toString(bytes32 value) internal pure returns (string memory) {
        bytes memory bytesArray = new bytes(32);
        for (uint256 i = 0; i < 32; i++) {
            if (value[i] == 0) {
                assembly {
                    mstore(bytesArray, i)
                }
                break;
            } else {
                bytesArray[i] = value[i];
            }
        }
        return string(bytesArray);
    }

    function toBytes32(uint256 value) internal pure returns (bytes32) {
        return bytes32(value);
    }

    function toBytes32(int256 value) internal pure returns (bytes32) {
        return bytes32(uint256(value));
    }

    function toBytes32(address value) internal pure returns (bytes32) {
        return bytes32(uint256(uint160(value)));
    }

    function toBytes32(bool value) internal pure returns (bytes32) {
        return bytes32(uint256(value ? 1 : 0));
    }

    /**
     * @notice Convert a string to a bytes32 value.
     * @dev This function takes an input string 'value' and converts it into a bytes32 value.
     *      It enforces a length constraint of 31 characters or less to ensure it fits within a bytes32.
     *      The function uses inline assembly to efficiently copy the string data into the bytes32.
     * @param value The input string to be converted.
     * @return The bytes32 representation of the input string.
     */
    function toBytes32(string memory value) internal pure returns (bytes32) {
        if (bytes(value).length > 31) {
            revert StringExceeds31Bytes(value);
        }
        bytes32 res;
        assembly {
            res := mload(add(value, 0x20))
        }
        return res;
    }

}

File 23 of 32 : Bytes32Map.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

import './Bytes32.sol';

library Bytes32Map {

    function getBytes32(mapping(uint8 => bytes32) storage store, uint8 idx) internal view returns (bytes32) {
        return store[idx];
    }

    function getAddress(mapping(uint8 => bytes32) storage store, uint8 idx) internal view returns (address) {
        return Bytes32.toAddress(store[idx]);
    }

    function getUint(mapping(uint8 => bytes32) storage store, uint8 idx) internal view returns (uint256) {
        return Bytes32.toUint(store[idx]);
    }

    function getInt(mapping(uint8 => bytes32) storage store, uint8 idx) internal view returns (int256) {
        return Bytes32.toInt(store[idx]);
    }

    function getBool(mapping(uint8 => bytes32) storage store, uint8 idx) internal view returns (bool) {
        return Bytes32.toBool(store[idx]);
    }

    function getString(mapping(uint8 => bytes32) storage store, uint8 idx) internal view returns (string memory) {
        return Bytes32.toString(store[idx]);
    }


    function set(mapping(uint8 => bytes32) storage store, uint8 idx, bytes32 value) internal {
        store[idx] = value;
    }

    function set(mapping(uint8 => bytes32) storage store, uint8 idx, address value) internal {
        store[idx] = Bytes32.toBytes32(value);
    }

    function set(mapping(uint8 => bytes32) storage store, uint8 idx, uint256 value) internal {
        store[idx] = Bytes32.toBytes32(value);
    }

    function set(mapping(uint8 => bytes32) storage store, uint8 idx, int256 value) internal {
        store[idx] = Bytes32.toBytes32(value);
    }

    function set(mapping(uint8 => bytes32) storage store, uint8 idx, bool value) internal {
        store[idx] = Bytes32.toBytes32(value);
    }

    function set(mapping(uint8 => bytes32) storage store, uint8 idx, string memory value) internal {
        store[idx] = Bytes32.toBytes32(value);
    }

    function del(mapping(uint8 => bytes32) storage store, uint8 idx) internal {
        delete store[idx];
    }

}

File 24 of 32 : ETHAndERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol';
import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';

/// Library for operating ERC20 and ETH in one logic
/// ETH is represented by address: 0x0000000000000000000000000000000000000001

library ETHAndERC20 {

    using SafeERC20 for IERC20;

    error SendEthFail();
    error WrongTokenInAmount();
    error WrongTokenOutAmount();

    function decimals(address token) internal view returns (uint8) {
        return token == address(1) ? 18 : IERC20Metadata(token).decimals();
    }

    // @notice Get the balance of ERC20 tokens or Ether held by this contract
    function balanceOfThis(address token) internal view returns (uint256) {
        return token == address(1)
            ? address(this).balance
            : IERC20(token).balanceOf(address(this));
    }

    function approveMax(address token, address spender) internal {
        if (token != address(1)) {
            uint256 allowance = IERC20(token).allowance(address(this), spender);
            if (allowance != type(uint256).max) {
                if (allowance != 0) {
                    IERC20(token).safeApprove(spender, 0);
                }
                IERC20(token).safeApprove(spender, type(uint256).max);
            }
        }
    }

    function unapprove(address token, address spender) internal {
        if (token != address(1)) {
            uint256 allowance = IERC20(token).allowance(address(this), spender);
            if (allowance != 0) {
                IERC20(token).safeApprove(spender, 0);
            }
        }
    }

    // @notice Transfer ERC20 tokens or Ether from 'from' to this contract
    function transferIn(address token, address from, uint256 amount) internal {
        if (token == address(1)) {
            if (amount != msg.value) {
                revert WrongTokenInAmount();
            }
        } else {
            uint256 balance1 = balanceOfThis(token);
            IERC20(token).safeTransferFrom(from, address(this), amount);
            uint256 balance2 = balanceOfThis(token);
            if (balance2 != balance1 + amount) {
                revert WrongTokenInAmount();
            }
        }
    }

    // @notice Transfer ERC20 tokens or Ether from this contract to 'to'
    function transferOut(address token, address to, uint256 amount) internal {
        uint256 balance1 = balanceOfThis(token);
        if (token == address(1)) {
            (bool success, ) = payable(to).call{value: amount}('');
            if (!success) {
                revert SendEthFail();
            }
        } else {
            IERC20(token).safeTransfer(to, amount);
        }
        uint256 balance2 = balanceOfThis(token);
        if (balance1 != balance2 + amount) {
            revert WrongTokenOutAmount();
        }
    }

}

File 25 of 32 : SafeMath.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

library SafeMath {

    error UtoIOverflow(uint256);
    error IToUOverflow(int256);
    error AbsOverflow(int256);

    uint256 constant IMAX = 2**255 - 1;
    int256  constant IMIN = -2**255;

    function utoi(uint256 a) internal pure returns (int256) {
        if (a > IMAX) {
            revert UtoIOverflow(a);
        }
        return int256(a);
    }

    function itou(int256 a) internal pure returns (uint256) {
        if (a < 0) {
            revert IToUOverflow(a);
        }
        return uint256(a);
    }

    function abs(int256 a) internal pure returns (int256) {
        if (a == IMIN) {
            revert AbsOverflow(a);
        }
        return a >= 0 ? a : -a;
    }

    function add(uint256 a, int256 b) internal pure returns (uint256) {
        if (b >= 0) {
            return a + uint256(b);
        } else {
            return a - uint256(-b);
        }
    }

    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a >= b ? a : b;
    }

    function max(int256 a, int256 b) internal pure returns (int256) {
        return a >= b ? a : b;
    }

    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a <= b ? a : b;
    }

    function min(int256 a, int256 b) internal pure returns (int256) {
        return a <= b ? a : b;
    }

    function divRoundingUp(uint256 a, uint256 b) internal pure returns (uint256 c) {
        c = a / b;
        if (b * c != a) {
            c += 1;
        }
    }

    // @notice Rescale a uint256 value from a base of 10^decimals1 to 10^decimals2
    function rescale(uint256 value, uint256 decimals1, uint256 decimals2) internal pure returns (uint256) {
        return decimals1 == decimals2 ? value : value * 10**decimals2 / 10**decimals1;
    }

    // @notice Rescale value with rounding down
    function rescaleDown(uint256 value, uint256 decimals1, uint256 decimals2) internal pure returns (uint256) {
        return rescale(value, decimals1, decimals2);
    }

    // @notice Rescale value with rounding up
    function rescaleUp(uint256 value, uint256 decimals1, uint256 decimals2) internal pure returns (uint256) {
        uint256 rescaled = rescale(value, decimals1, decimals2);
        if (rescale(rescaled, decimals2, decimals1) != value) {
            rescaled += 1;
        }
        return rescaled;
    }

    function rescale(int256 value, uint256 decimals1, uint256 decimals2) internal pure returns (int256) {
        return decimals1 == decimals2 ? value : value * int256(10**decimals2) / int256(10**decimals1);
    }

    function rescaleDown(int256 value, uint256 decimals1, uint256 decimals2) internal pure returns (int256) {
        int256 rescaled = rescale(value, decimals1, decimals2);
        if (value < 0 && rescale(rescaled, decimals2, decimals1) != value) {
            rescaled -= 1;
        }
        return rescaled;
    }

    function rescaleUp(int256 value, uint256 decimals1, uint256 decimals2) internal pure returns (int256) {
        int256 rescaled = rescale(value, decimals1, decimals2);
        if (value > 0 && rescale(rescaled, decimals2, decimals1) != value) {
            rescaled += 1;
        }
        return rescaled;
    }

    // @notice Calculate a + b with overflow allowed
    function addUnchecked(int256 a, int256 b) internal pure returns (int256 c) {
        unchecked { c = a + b; }
    }

    // @notice Calculate a - b with overflow allowed
    function minusUnchecked(int256 a, int256 b) internal pure returns (int256 c) {
        unchecked { c = a - b; }
    }

}

File 26 of 32 : IOracle.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

import '../utils/IAdmin.sol';
import '../utils/IImplementation.sol';

interface IOracle is IAdmin, IImplementation {

    struct Signature {
        bytes32 oracleId;
        uint256 timestamp;
        int256  value;
        uint8   v;
        bytes32 r;
        bytes32 s;
    }

    function getValue(bytes32 oracleId) external view returns (int256);

    function getValueCurrentBlock(bytes32 oracleId) external returns (int256);

    function updateOffchainValue(Signature memory s) external;

    function updateOffchainValues(Signature[] memory ss) external;

}

File 27 of 32 : Admin.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

abstract contract Admin {

    error OnlyAdmin();

    event NewAdmin(address newAdmin);

    address public admin;

    modifier _onlyAdmin_() {
        if (msg.sender != admin) {
            revert OnlyAdmin();
        }
        _;
    }

    constructor () {
        admin = msg.sender;
        emit NewAdmin(admin);
    }

    /**
     * @notice Set a new admin for the contract.
     * @dev This function allows the current admin to assign a new admin address without performing any explicit verification.
     *      It's the current admin's responsibility to ensure that the 'newAdmin' address is correct and secure.
     * @param newAdmin The address of the new admin.
     */
    function setAdmin(address newAdmin) external _onlyAdmin_ {
        admin = newAdmin;
        emit NewAdmin(newAdmin);
    }

}

File 28 of 32 : IAdmin.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

interface IAdmin {

    function admin() external view returns (address);

    function setAdmin(address newAdmin) external;

}

File 29 of 32 : IImplementation.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

interface IImplementation {

    function setImplementation(address newImplementation) external;

}

File 30 of 32 : Implementation.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

import './Admin.sol';

abstract contract Implementation is Admin {

    event NewImplementation(address newImplementation);

    address public implementation;

    // @notice Set a new implementation address for the contract
    function setImplementation(address newImplementation) external _onlyAdmin_ {
        implementation = newImplementation;
        emit NewImplementation(newImplementation);
    }

}

File 31 of 32 : ISwitchOracle.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

interface ISwitchOracle {

    function operator() external view returns (address);

    function state() external view returns (bool);

    function setOperator(address operator_) external;

    function resetState() external;

    function setState() external;

}

File 32 of 32 : ReentryLock.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

abstract contract ReentryLock {

    error Reentry();

    bool internal _mutex;

    // @notice Lock for preventing reentrancy attacks
    modifier _reentryLock_() {
        if (_mutex) {
            revert Reentry();
        }
        _mutex = true;
        _;
        _mutex = false;
    }

}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "evmVersion": "paris",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {
    "contracts/ichain/gateway/GatewayHelper.sol": {
      "GatewayHelper": "0xcbca586bf9706706398164bb5eb8e48f220fe408"
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"components":[{"internalType":"address","name":"lToken","type":"address"},{"internalType":"address","name":"pToken","type":"address"},{"internalType":"address","name":"oracle","type":"address"},{"internalType":"address","name":"swapper","type":"address"},{"internalType":"address","name":"vault0","type":"address"},{"internalType":"address","name":"iou","type":"address"},{"internalType":"address","name":"tokenB0","type":"address"},{"internalType":"address","name":"dChainEventSigner","type":"address"},{"internalType":"uint256","name":"b0ReserveRatio","type":"uint256"},{"internalType":"int256","name":"liquidationRewardCutRatio","type":"int256"},{"internalType":"int256","name":"minLiquidationReward","type":"int256"},{"internalType":"int256","name":"maxLiquidationReward","type":"int256"},{"internalType":"address","name":"protocolFeeManager","type":"address"},{"internalType":"address","name":"liqClaim","type":"address"},{"internalType":"address","name":"switchOracle","type":"address"}],"internalType":"struct IGateway.GatewayParam","name":"p","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"int256","name":"","type":"int256"}],"name":"IToUOverflow","type":"error"},{"inputs":[],"name":"InsufficientB0","type":"error"},{"inputs":[],"name":"InsufficientMargin","type":"error"},{"inputs":[],"name":"InvalidBAmount","type":"error"},{"inputs":[],"name":"InvalidBPrice","type":"error"},{"inputs":[],"name":"InvalidBToken","type":"error"},{"inputs":[],"name":"InvalidLTokenId","type":"error"},{"inputs":[],"name":"InvalidPTokenId","type":"error"},{"inputs":[],"name":"InvalidRequestId","type":"error"},{"inputs":[],"name":"OnlyAdmin","type":"error"},{"inputs":[],"name":"Reentry","type":"error"},{"inputs":[],"name":"SendEthFail","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"UtoIOverflow","type":"error"},{"inputs":[],"name":"WrongTokenInAmount","type":"error"},{"inputs":[],"name":"WrongTokenOutAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"liquidity","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalLiquidity","type":"uint256"}],"name":"FinishAddLiquidity","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"pTokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"bToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"bAmount","type":"uint256"}],"name":"FinishAddMargin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"pTokenId","type":"uint256"},{"indexed":false,"internalType":"int256","name":"lpPnl","type":"int256"}],"name":"FinishLiquidate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"liquidity","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalLiquidity","type":"uint256"},{"indexed":false,"internalType":"address","name":"bToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"bAmount","type":"uint256"}],"name":"FinishRemoveLiquidity","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"pTokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"bToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"bAmount","type":"uint256"}],"name":"FinishRemoveMargin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"NewAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newImplementation","type":"address"}],"name":"NewImplementation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"pTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"realMoneyMargin","type":"uint256"},{"indexed":false,"internalType":"int256","name":"lastCumulativePnlOnEngine","type":"int256"},{"indexed":false,"internalType":"int256","name":"cumulativePnlOnGateway","type":"int256"}],"name":"RequestLiquidate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"pTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"realMoneyMargin","type":"uint256"},{"indexed":false,"internalType":"int256","name":"lastCumulativePnlOnEngine","type":"int256"},{"indexed":false,"internalType":"int256","name":"cumulativePnlOnGateway","type":"int256"},{"indexed":false,"internalType":"uint256","name":"bAmount","type":"uint256"}],"name":"RequestRemoveMargin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"pTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"realMoneyMargin","type":"uint256"},{"indexed":false,"internalType":"int256","name":"lastCumulativePnlOnEngine","type":"int256"},{"indexed":false,"internalType":"int256","name":"cumulativePnlOnGateway","type":"int256"},{"indexed":false,"internalType":"bytes32","name":"symbolId","type":"bytes32"},{"indexed":false,"internalType":"int256[]","name":"tradeParams","type":"int256[]"}],"name":"RequestTrade","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"pTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"realMoneyMargin","type":"uint256"},{"indexed":false,"internalType":"int256","name":"lastCumulativePnlOnEngine","type":"int256"},{"indexed":false,"internalType":"int256","name":"cumulativePnlOnGateway","type":"int256"},{"indexed":false,"internalType":"uint256","name":"bAmount","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"symbolId","type":"bytes32"},{"indexed":false,"internalType":"int256[]","name":"tradeParams","type":"int256[]"}],"name":"RequestTradeAndRemoveMargin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"liquidity","type":"uint256"},{"indexed":false,"internalType":"int256","name":"lastCumulativePnlOnEngine","type":"int256"},{"indexed":false,"internalType":"int256","name":"cumulativePnlOnGateway","type":"int256"},{"indexed":false,"internalType":"uint256","name":"removeBAmount","type":"uint256"}],"name":"RequestUpdateLiquidity","type":"event"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"claimDChainExecutionFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"dTokenId","type":"uint256"},{"internalType":"bool","name":"isLp","type":"bool"}],"name":"claimUnusedIChainExecutionFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"eventData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"finishCollectProtocolFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"eventData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"finishLiquidate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"eventData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"finishRemoveMargin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"eventData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"finishUpdateLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"bToken","type":"address"}],"name":"getBTokenState","outputs":[{"components":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"bytes32","name":"oracleId","type":"bytes32"},{"internalType":"uint256","name":"collateralFactor","type":"uint256"}],"internalType":"struct IGateway.BTokenState","name":"s","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"lTokenId","type":"uint256"}],"name":"getCumulativeTime","outputs":[{"internalType":"uint256","name":"cumulativeTimePerLiquidity","type":"uint256"},{"internalType":"uint256","name":"cumulativeTime","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExecutionFees","outputs":[{"internalType":"uint256[]","name":"fees","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getGatewayParam","outputs":[{"components":[{"internalType":"address","name":"lToken","type":"address"},{"internalType":"address","name":"pToken","type":"address"},{"internalType":"address","name":"oracle","type":"address"},{"internalType":"address","name":"swapper","type":"address"},{"internalType":"address","name":"vault0","type":"address"},{"internalType":"address","name":"iou","type":"address"},{"internalType":"address","name":"tokenB0","type":"address"},{"internalType":"address","name":"dChainEventSigner","type":"address"},{"internalType":"uint256","name":"b0ReserveRatio","type":"uint256"},{"internalType":"int256","name":"liquidationRewardCutRatio","type":"int256"},{"internalType":"int256","name":"minLiquidationReward","type":"int256"},{"internalType":"int256","name":"maxLiquidationReward","type":"int256"},{"internalType":"address","name":"protocolFeeManager","type":"address"},{"internalType":"address","name":"liqClaim","type":"address"},{"internalType":"address","name":"switchOracle","type":"address"}],"internalType":"struct IGateway.GatewayParam","name":"p","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getGatewayState","outputs":[{"components":[{"internalType":"int256","name":"cumulativePnlOnGateway","type":"int256"},{"internalType":"uint256","name":"liquidityTime","type":"uint256"},{"internalType":"uint256","name":"totalLiquidity","type":"uint256"},{"internalType":"int256","name":"cumulativeTimePerLiquidity","type":"int256"},{"internalType":"uint256","name":"gatewayRequestId","type":"uint256"},{"internalType":"uint256","name":"dChainExecutionFeePerRequest","type":"uint256"},{"internalType":"uint256","name":"totalIChainExecutionFee","type":"uint256"},{"internalType":"uint256","name":"cumulativeCollectedProtocolFee","type":"uint256"}],"internalType":"struct IGateway.GatewayState","name":"s","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"lTokenId","type":"uint256"}],"name":"getLpState","outputs":[{"components":[{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"address","name":"bToken","type":"address"},{"internalType":"uint256","name":"bAmount","type":"uint256"},{"internalType":"int256","name":"b0Amount","type":"int256"},{"internalType":"int256","name":"lastCumulativePnlOnEngine","type":"int256"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"cumulativeTime","type":"uint256"},{"internalType":"uint256","name":"lastCumulativeTimePerLiquidity","type":"uint256"},{"internalType":"uint256","name":"lastRequestIChainExecutionFee","type":"uint256"},{"internalType":"uint256","name":"cumulativeUnusedIChainExecutionFee","type":"uint256"}],"internalType":"struct IGateway.LpState","name":"s","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"pTokenId","type":"uint256"}],"name":"getTdState","outputs":[{"components":[{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"address","name":"bToken","type":"address"},{"internalType":"uint256","name":"bAmount","type":"uint256"},{"internalType":"int256","name":"b0Amount","type":"int256"},{"internalType":"int256","name":"lastCumulativePnlOnEngine","type":"int256"},{"internalType":"bool","name":"singlePosition","type":"bool"},{"internalType":"uint256","name":"lastRequestIChainExecutionFee","type":"uint256"},{"internalType":"uint256","name":"cumulativeUnusedIChainExecutionFee","type":"uint256"}],"internalType":"struct IGateway.TdState","name":"s","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"b0Amount","type":"uint256"}],"name":"redeemIOU","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"lTokenId","type":"uint256"},{"internalType":"address","name":"bToken","type":"address"},{"internalType":"uint256","name":"bAmount","type":"uint256"}],"name":"requestAddLiquidity","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"pTokenId","type":"uint256"},{"internalType":"address","name":"bToken","type":"address"},{"internalType":"uint256","name":"bAmount","type":"uint256"},{"internalType":"bool","name":"singlePosition","type":"bool"}],"name":"requestAddMargin","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"pTokenId","type":"uint256"},{"internalType":"address","name":"bToken","type":"address"},{"internalType":"uint256","name":"bAmount","type":"uint256"},{"internalType":"bytes32","name":"symbolId","type":"bytes32"},{"internalType":"int256[]","name":"tradeParams","type":"int256[]"},{"internalType":"bool","name":"singlePosition","type":"bool"}],"name":"requestAddMarginAndTrade","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"pTokenId","type":"uint256"},{"internalType":"uint256","name":"b0Amount","type":"uint256"}],"name":"requestAddMarginB0","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"pTokenId","type":"uint256"}],"name":"requestLiquidate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"lTokenId","type":"uint256"},{"internalType":"address","name":"bToken","type":"address"},{"internalType":"uint256","name":"bAmount","type":"uint256"}],"name":"requestRemoveLiquidity","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"pTokenId","type":"uint256"},{"internalType":"address","name":"bToken","type":"address"},{"internalType":"uint256","name":"bAmount","type":"uint256"}],"name":"requestRemoveMargin","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"pTokenId","type":"uint256"},{"internalType":"bytes32","name":"symbolId","type":"bytes32"},{"internalType":"int256[]","name":"tradeParams","type":"int256[]"}],"name":"requestTrade","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"pTokenId","type":"uint256"},{"internalType":"address","name":"bToken","type":"address"},{"internalType":"uint256","name":"bAmount","type":"uint256"},{"internalType":"bytes32","name":"symbolId","type":"bytes32"},{"internalType":"int256[]","name":"tradeParams","type":"int256[]"}],"name":"requestTradeAndRemoveMargin","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"}],"name":"setImplementation","outputs":[],"stateMutability":"nonpayable","type":"function"}]

6102806040523480156200001257600080fd5b506040516200658e3803806200658e833981016040819052620000359162000223565b600080546001600160a01b031916339081179091556040519081527f71614071b88dee5e0b2ae578a9dd7b2ebbe9ae832ba419dc0242cd065a290b6c9060200160405180910390a180516001600160a01b0390811660809081526020830151821660a09081526040840151831660c09081526060850151841660e052918401518316610100528301518216610120528201805182166101405251620000db911662000146565b60ff1661018090815260e08201516001600160a01b039081166101609081526101008401516101a09081526101208501516101c09081526101408601516101e0529185015161020052928401518216610220529183015181166102405291015116610260526200036e565b60006001600160a01b038216600114620001c557816001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000199573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001bf919062000342565b620001c8565b60125b92915050565b6040516101e081016001600160401b03811182821017156200020057634e487b7160e01b600052604160045260246000fd5b60405290565b80516001600160a01b03811681146200021e57600080fd5b919050565b60006101e082840312156200023757600080fd5b62000241620001ce565b6200024c8362000206565b81526200025c6020840162000206565b60208201526200026f6040840162000206565b6040820152620002826060840162000206565b6060820152620002956080840162000206565b6080820152620002a860a0840162000206565b60a0820152620002bb60c0840162000206565b60c0820152620002ce60e0840162000206565b60e082015261010083810151908201526101208084015190820152610140808401519082015261016080840151908201526101806200030f81850162000206565b908201526101a06200032384820162000206565b908201526101c06200033784820162000206565b908201529392505050565b6000602082840312156200035557600080fd5b815160ff811681146200036757600080fd5b9392505050565b60805160a05160c05160e05161010051610120516101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051615f866200060860003960006137120152600081816106320152610e8501526000818161060901526127000152600081816105e20152610fb50152600081816105bb0152610f8f0152600081816105940152610fdb01526000818161056d01526146ff015260008181610cea01528181610e240152818161120801528181611a9601528181612191015281816130540152818161325d015281816133a5015281816137a50152818161412f015261497c01526000818161054501528181610b940152818161191a0152818161208d015261260f01526000818161051d0152818161099101528181610db5015281816110860152818161178001528181611b6101528181611c8b015281816124a8015281816126d8015281816138060152818161395f01528181613b8001528181613dde01528181614050015281816140ac015281816140da015281816146b401526149170152600081816104f5015281816117d001526143340152600081816104cd015281816110ae01528181611186015281816117a801528181611bac015281816124ec015281816126b001528181613e5e01528181613fcb015261475a0152600081816104a501528181610e5d015281816139d001528181613a9d01528181613c0d01528181613cee015281816141ad015261426501526000818161047e01526149a301526000818161045601528181610c0b01528181610ead015281816113190152818161142d015281816114da01528181611dce01528181612116015261452a01526000818161043101528181611405015281816119ba015281816122c20152612d080152615f866000f3fe60806040526004361061019c5760003560e01c80638769da2d116100ec578063db4015661161008a578063f7c60d4411610064578063f7c60d441461074f578063f802a69714610762578063f851a440146107e9578063fa321c261461080957600080fd5b8063db401566146106a4578063e1faff561461071a578063e926bc9d1461072d57600080fd5b8063be801c08116100c6578063be801c08146103e7578063c323acb614610407578063cd18dde314610664578063d784d4261461068457600080fd5b80638769da2d1461037f5780639b1a2e94146103b4578063aaba0398146103d457600080fd5b80635c60da1b11610159578063702e6c0611610133578063702e6c06146102fe578063704b6c021461031e57806370a1848c1461033e5780637e9459ba1461035e57600080fd5b80635c60da1b146102795780635c8e13a6146102b1578063619cf2be146102d157600080fd5b8063068689cd146101a15780630c8aeb56146101f157806316890fc2146102065780631c58d9c11461021957806323cc0ebf146102395780633e0c7fc114610259575b600080fd5b3480156101ad57600080fd5b506101c16101bc366004615036565b61081c565b6040805182516001600160a01b031681526020808401519082015291810151908201526060015b60405180910390f35b6102046101ff366004615053565b6108be565b005b6102046102143660046150e4565b610a93565b34801561022557600080fd5b5061020461023436600461526b565b610b20565b34801561024557600080fd5b506102046102543660046152ce565b6113de565b34801561026557600080fd5b506102046102743660046152fe565b6114b5565b34801561028557600080fd5b50600154610299906001600160a01b031681565b6040516001600160a01b0390911681526020016101e8565b3480156102bd57600080fd5b506102046102cc366004615036565b6115e4565b3480156102dd57600080fd5b506102f16102ec3660046152fe565b611685565b6040516101e89190615317565b34801561030a57600080fd5b506102046103193660046152fe565b611769565b34801561032a57600080fd5b50610204610339366004615036565b611826565b34801561034a57600080fd5b5061020461035936600461526b565b6118a6565b61037161036c366004615396565b611daf565b6040519081526020016101e8565b34801561038b57600080fd5b5061039f61039a3660046152fe565b611f88565b604080519283526020830191909152016101e8565b3480156103c057600080fd5b506102046103cf36600461526b565b612019565b6102046103e2366004615053565b6122a5565b3480156103f357600080fd5b506102046104023660046153e0565b612491565b34801561041357600080fd5b50604080516101e08101825260006101c08201526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811682527f0000000000000000000000000000000000000000000000000000000000000000811660208301527f00000000000000000000000000000000000000000000000000000000000000008116828401527f0000000000000000000000000000000000000000000000000000000000000000811660608301527f0000000000000000000000000000000000000000000000000000000000000000811660808301527f0000000000000000000000000000000000000000000000000000000000000000811660a08301527f0000000000000000000000000000000000000000000000000000000000000000811660c08301527f0000000000000000000000000000000000000000000000000000000000000000811660e08301527f00000000000000000000000000000000000000000000000000000000000000006101008301527f00000000000000000000000000000000000000000000000000000000000000006101208301527f00000000000000000000000000000000000000000000000000000000000000006101408301527f00000000000000000000000000000000000000000000000000000000000000006101608301527f000000000000000000000000000000000000000000000000000000000000000081166101808301527f0000000000000000000000000000000000000000000000000000000000000000166101a082015290516101e89190615402565b34801561067057600080fd5b5061020461067f36600461526b565b6125af565b34801561069057600080fd5b5061020461069f366004615036565b612778565b3480156106b057600080fd5b506106b96127f1565b6040516101e89190600061010082019050825182526020830151602083015260408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015260e083015160e083015292915050565b610204610728366004615549565b6128b3565b34801561073957600080fd5b506107426129c5565b6040516101e891906155bb565b61020461075d366004615053565b612a40565b34801561076e57600080fd5b5061078261077d3660046152fe565b612b57565b6040516101e89190815181526020808301516001600160a01b03169082015260408083015190820152606080830151908201526080808301519082015260a08083015115159082015260c0808301519082015260e091820151918101919091526101000190565b3480156107f557600080fd5b50600054610299906001600160a01b031681565b6102046108173660046155ff565b612c2f565b604080516060810182526000808252602082018190528183015290516340d24f1f60e11b8152600360048201526001600160a01b038316602482015273cbca586bf9706706398164bb5eb8e48f220fe408906381a49e3e90604401606060405180830381865af4158015610894573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108b8919061565c565b92915050565b6108c88333612cfc565b600260005260056020527f89832631fb3c3307a103ba2c84ab569c64d6182a18893dcd163f0f1c2090733a546108ff908490612dc0565b5080600003610921576040516394613e9960e01b815260040160405180910390fd5b60008381526004602052604081206109479033908690610942906002612e55565b612e6d565b905061095281612f16565b600061095d82612f59565b90506000846001600160a01b031683604001516001600160a01b03160361098f5761098883856130a0565b90506109eb565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316856001600160a01b0316036109d257610988838561328e565b604051634033aec960e01b815260040160405180910390fd5b6109f66064836156e9565b8111610a00575060005b6000868152600460205260409020610a1a90600b876133dd565b6000610a2587613401565b60c08086015160608088015160408051868152602081018e905290810188905291820192909252608081019190915260a081018890529192507f0be79565c9c0f7b4144002bd9abeb6bd0e8f43023abae7066992c58bdff9397e91015b60405180910390a150505050505050565b6000196001600160a01b03871601610afd57600460005260056020527f3eec716f11ba9e820c81ca75eb978ffb45831ef8b7a53e5e422c26008e1ca6d55434610adc82886156fd565b1115610afb576040516394613e9960e01b815260040160405180910390fd5b505b610b0987878784611daf565b9650610b1787858585612c2f565b50505050505050565b600154600160a01b900460ff1615610b4b576040516325dbe6e160e21b815260040160405180910390fd5b6001805460ff60a01b1916600160a01b179055604051633db1a4d360e01b815273cbca586bf9706706398164bb5eb8e48f220fe40890633db1a4d390610bbc908590859060e0907f000000000000000000000000000000000000000000000000000000000000000090600401615760565b60006040518083038186803b158015610bd457600080fd5b505af4158015610be8573d6000803e3d6000fd5b50505050600082806020019051810190610c0291906157a7565b90506000610cbf7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316636352211e84608001516040518263ffffffff1660e01b8152600401610c5b91815260200190565b602060405180830381865afa158015610c78573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c9c9190615845565b60808401516000818152600460205260409020610cba906002612e55565b6134b6565b90506000610cde8260c001518460a001516134d590919063ffffffff16565b9050610d0f81601260ff7f0000000000000000000000000000000000000000000000000000000000000000166134da565b8260a001818151610d209190615862565b90525060a083015160c083015260808201516020830151604051637cbc237360e01b81526004810191909152600019602482015260009182916001600160a01b0390911690637cbc2373906044016020604051808303816000875af1158015610d8d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610db19190615882565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031684604001516001600160a01b031603610e0157610dfa81836156fd565b9150610f5f565b604084810151602086015160a087015160c089015193516330706e4160e01b81527f000000000000000000000000000000000000000000000000000000000000000060ff1660048201526001600160a01b0393841660248201527f0000000000000000000000000000000000000000000000000000000000000000841660448201527f0000000000000000000000000000000000000000000000000000000000000000841660648201527f0000000000000000000000000000000000000000000000000000000000000000909316608484015260a483019190915260c482015260e4810183905261010481019190915273cbca586bf9706706398164bb5eb8e48f220fe408906330706e419061012401602060405180830381865af4158015610f2e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f529190615882565b610f5c90836156fd565b91505b5060008360a00151610f7083613514565b610f7a9190615862565b60405162abd53960e21b8152600481018290527f000000000000000000000000000000000000000000000000000000000000000060248201527f000000000000000000000000000000000000000000000000000000000000000060448201527f0000000000000000000000000000000000000000000000000000000000000000606482015290915060009073cbca586bf9706706398164bb5eb8e48f220fe408906302af54e490608401602060405180830381865af4158015611041573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110659190615882565b6020870151604080890151905163101b43e760e31b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301527f00000000000000000000000000000000000000000000000000000000000000008116602483015260448201859052606482018890529283166084820152911660a482015290915073cbca586bf9706706398164bb5eb8e48f220fe408906380da1f389060c4016040805180830381865af415801561112f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611153919061589b565b9350905061116181836158bf565b915082156111fd57604051631c57762b60e31b815260006004820152602481018490527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063e2bbb158906044016020604051808303816000875af11580156111d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111fb9190615882565b505b61123861122f8360ff7f000000000000000000000000000000000000000000000000000000000000000016601261354a565b60608701510190565b6060860152600060a086015261124d8561358a565b6080860151600090815260046020526040812061126b906009612e55565b608088015160009081526004602052604081209192509061128d90600a612e55565b608089018051600090815260046020818152604080842060098552825280842084905593518352908152828220600a835290529081208190559091506112d560026007612e55565b90506112e182846156fd565b6112eb90826158df565b90506112fa6002600783613612565b5050506080860151604051630852cd8d60e31b815260048101919091527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906342966c6890602401600060405180830381600087803b15801561136557600080fd5b505af1158015611379573d6000803e3d6000fd5b505050606080880151608089015160408051928352602083019190915281018590527f8c3b5c5c5dddce5cc5abfa2cc66ec5dd06f76f9e9bb21debb1a85ad88fdbdb6f92500160405180910390a150506001805460ff60a01b19169055505050505050565b604051630429dd8560e41b8152600260048083019190915260248201526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660448301527f00000000000000000000000000000000000000000000000000000000000000001660648201526084810183905281151560a482015273cbca586bf9706706398164bb5eb8e48f220fe4089063429dd8509060c4015b60006040518083038186803b15801561149957600080fd5b505af41580156114ad573d6000803e3d6000fd5b505050505050565b6040516331a9108f60e11b815260048101829052600090611560906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690636352211e90602401602060405180830381865afa158015611521573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115459190615845565b60008481526004602052604090208490610cba906002612e55565b905061156b81612f16565b600061157682612f59565b9050600061158384613401565b60c084015160608086015160408051858152602081018a905280820188905292830193909352608082015290519192507fb299642ff40e6ba13eec0f77203c3ef9816a9f64212a40467d63db0eba259b4f919081900360a00190a150505050565b6000546001600160a01b0316331461160f57604051634755657960e01b815260040160405180910390fd5b604051631428438f60e11b8152600260048201526001600160a01b038216602482015273cbca586bf9706706398164bb5eb8e48f220fe40890632850871e906044015b60006040518083038186803b15801561166a57600080fd5b505af415801561167e573d6000803e3d6000fd5b5050505050565b6116e46040518061014001604052806000815260200160006001600160a01b0316815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040516393760d3d60e01b8152600360048083019190915260248201526044810183905273cbca586bf9706706398164bb5eb8e48f220fe408906393760d3d9060640161014060405180830381865af4158015611745573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108b891906158f2565b6040516332f6692360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301527f0000000000000000000000000000000000000000000000000000000000000000811660248301527f00000000000000000000000000000000000000000000000000000000000000001660448201523360648201526084810182905273cbca586bf9706706398164bb5eb8e48f220fe408906332f669239060a401611652565b6000546001600160a01b0316331461185157604051634755657960e01b815260040160405180910390fd5b600080546001600160a01b0319166001600160a01b0383169081179091556040519081527f71614071b88dee5e0b2ae578a9dd7b2ebbe9ae832ba419dc0242cd065a290b6c906020015b60405180910390a150565b600154600160a01b900460ff16156118d1576040516325dbe6e160e21b815260040160405180910390fd5b6001805460ff60a01b1916600160a01b179055604051633db1a4d360e01b815273cbca586bf9706706398164bb5eb8e48f220fe40890633db1a4d390611942908590859060c0907f000000000000000000000000000000000000000000000000000000000000000090600401615760565b60006040518083038186803b15801561195a57600080fd5b505af415801561196e573d6000803e3d6000fd5b505050506000828060200190518101906119889190615980565b905061199c81602001518260000151613618565b6119b3816020015182604001518360600151613689565b6000611a6b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316636352211e84602001516040518263ffffffff1660e01b8152600401611a0a91815260200190565b602060405180830381865afa158015611a27573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a4b9190615845565b602080850151600081815260049092526040909120610cba906002612e55565b90506000611a8a8260c0015184608001516134d590919063ffffffff16565b9050611abb81601260ff7f0000000000000000000000000000000000000000000000000000000000000000166134da565b8260a001818151611acc9190615862565b905250608083015160c0830152604082015160a08401516000919015611cb2576020808601516000908152600490915260409020611b0b90600b612e55565b9050806001600160a01b031684604001516001600160a01b031603611b5f57611b3384612f16565b611b58848660400151600014611b4d578660a00151611b51565b6000195b600061370e565b9150611cb2565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316816001600160a01b031614611b9d57600080fd5b60008460a001511315611cb2577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316637cbc23736000611bf58860a00151611bf08960a0015161439e565b6143c4565b6040516001600160e01b031960e085901b168152600481019290925260248201526044016020604051808303816000875af1158015611c38573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c5c9190615882565b9150611c6782613514565b8460a001818151611c7891906158bf565b9052508351611cb2906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690846143db565b611cbb8461358a565b611cc98560200151336144be565b8460a00151600003611d2e5784516020808701516040808901516060808b0151835196875294860193909352908401528201527f3b963caf777561b495590bdc6ec8abd33838524d0a3aafc3f6f1b7857449d0999060800160405180910390a1611d99565b84516020808701516040808901516060808b0151835196875294860193909352908401528201526001600160a01b038216608082015260a081018390527fc72ca5dc9c24b437f43b8c112e7b4e24b4a7bce4b1f405e591eee1cc32d327c29060c00160405180910390a15b50506001805460ff60a01b191690555050505050565b600084600003611e6b576040516335313c2160e11b81523360048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690636a627842906024016020604051808303816000875af1158015611e1f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e439190615882565b94508115611e66576000858152600460205260409020611e669060086001614515565b611e75565b611e75853361451e565b611e7e846145de565b6000611e8b3387876134b6565b90506000196001600160a01b03861601611ec05734841115611ec0576040516394613e9960e01b815260040160405180910390fd5b83600003611ee1576040516394613e9960e01b815260040160405180910390fd5b6001600160a01b038516600114611f09578051611f09906001600160a01b0387169086614628565b611f1381856146b2565b611f1c8161358a565b6000611f2787613401565b60408051828152602081018a90526001600160a01b038916818301526060810188905290519192507f8eee26bd33caee967b96e711aa45ef94c734251b0457a0f2213fb392c01ef53a919081900360800190a186925050505b949350505050565b604051632a8bb6c960e11b81526002600482810191909152602482015260448101829052600090819073cbca586bf9706706398164bb5eb8e48f220fe408906355176d92906064016040805180830381865af4158015611fec573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612010919061589b565b91509150915091565b600154600160a01b900460ff1615612044576040516325dbe6e160e21b815260040160405180910390fd5b6001805460ff60a01b1916600160a01b179055604051633db1a4d360e01b815273cbca586bf9706706398164bb5eb8e48f220fe40890633db1a4d3906120b5908590859060a0907f000000000000000000000000000000000000000000000000000000000000000090600401615760565b60006040518083038186803b1580156120cd57600080fd5b505af41580156120e1573d6000803e3d6000fd5b505050506000828060200190518101906120fb91906159f9565b905061210f81602001518260000151613618565b60006121667f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316636352211e84602001516040518263ffffffff1660e01b8152600401611a0a91815260200190565b905060006121858260c0015184606001516134d590919063ffffffff16565b90506121b681601260ff7f0000000000000000000000000000000000000000000000000000000000000000166134da565b8260a0018181516121c79190615862565b905250606083015160c08301526121dd82612f16565b60006121ef838560800151600161370e565b905083604001516121ff84612f59565b101561221e576040516341c092a960e01b815260040160405180910390fd5b6122278361358a565b6122358460200151336144be565b83516020808601516040868101518151948552928401919091526001600160a01b039091168282015260608201839052517f39f415ac8ef6b49b1ba40c4fafb2b911df1c5306c49eef15c4bc38a0128fca989181900360800190a150506001805460ff60a01b1916905550505050565b8260000361233e576040516335313c2160e11b81523360048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690636a627842906024016020604051808303816000875af1158015612313573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123379190615882565b9250612348565b6123488333612cfc565b612351826145de565b600061235e3385856134b6565b6001600090815260056020527f1471eb6eb2c5e789fc3de43f8ce62938c7d1836ec861730447e2ada8fd81017b549192509061239b908690612dc0565b90506000196001600160a01b038516016123b3578092505b826000036123d4576040516394613e9960e01b815260040160405180910390fd5b6001600160a01b0384166001146123fc5781516123fc906001600160a01b0386169085614628565b61240682846146b2565b61240f82612f16565b600061241a83612f59565b90506124258361358a565b600061243087613401565b60c08086015160608088015160408051868152602081018e9052908101889052918201929092526080810191909152600060a08201529192507f0be79565c9c0f7b4144002bd9abeb6bd0e8f43023abae7066992c58bdff9397e9101610a82565b61249b823361451e565b6124cf6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163383614628565b604051631c57762b60e31b815260006004820152602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063e2bbb158906044016020604051808303816000875af115801561253d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125619190615882565b50600082815260046020526040812061257b906003612e55565b90506125aa600361258b84613514565b6125959084615862565b60008681526004602052604090209190613612565b505050565b6000546001600160a01b031633146125da57604051634755657960e01b815260040160405180910390fd5b60408051633db1a4d360e01b815273cbca586bf9706706398164bb5eb8e48f220fe40891633db1a4d3916126379186918691907f000000000000000000000000000000000000000000000000000000000000000090600401615760565b60006040518083038186803b15801561264f57600080fd5b505af4158015612663573d6000803e3d6000fd5b5050505060008280602001905181019061267d9190615a68565b8051909150461461268d57600080fd5b602081015160405163ac46ae5160e01b8152600260048201526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660248301527f0000000000000000000000000000000000000000000000000000000000000000811660448301527f0000000000000000000000000000000000000000000000000000000000000000166064820152608481019190915273cbca586bf9706706398164bb5eb8e48f220fe4089063ac46ae519060a40160006040518083038186803b15801561276457600080fd5b505af4158015610b17573d6000803e3d6000fd5b6000546001600160a01b031633146127a357604051634755657960e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f6b70829fcbe4891157f7a7496f9870927de3c8237adbe9cd39bae09b7382c4099060200161189b565b61283960405180610100016040528060008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b604051637d4b09b360e01b81526002600482015273cbca586bf9706706398164bb5eb8e48f220fe40890637d4b09b39060240161010060405180830381865af415801561288a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128ae9190615ab6565b905090565b6128bd863361451e565b600560008190526020527f458b30c2d72bfd2c6317304a4594ecbafe5f729d3111b65fdc3a33bd48e5432d546128f4908790612dc0565b5083600003612916576040516394613e9960e01b815260040160405180910390fd5b60006129233388886134b6565b905061292e81612f16565b600061293982612f59565b9050600061294783886130a0565b90506129546064836156e9565b811161295e575060005b60006129698a613401565b90507ecae5d4a40f5ab841867a076a796230392d8ce403d409f84f18377b49a602d5818b848760c0015188606001518d8d8d8d6040516129b199989796959493929190615b5c565b60405180910390a150505050505050505050565b604051630afc7a3960e31b81526005600482015260609073cbca586bf9706706398164bb5eb8e48f220fe408906357e3d1c890602401600060405180830381865af4158015612a18573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526128ae9190810190615bab565b612a4a833361451e565b600360005260056020527fa9bc9a3a348c357ba16b37005d7e6b3236198c0e939f4af8c5f19b8deeb8ebc054612a81908490612dc0565b5080600003612aa3576040516394613e9960e01b815260040160405180910390fd5b6000612ab03385856134b6565b9050612abb81612f16565b6000612ac682612f59565b90506000612ad483856130a0565b9050612ae16064836156e9565b8111612aeb575060005b6000612af687613401565b60c08086015160608088015160408051868152602081018e905290810188905291820192909252608081019190915260a081018890529192507f88ad2ade0e436c3cffc2b0d1b51fb1379e5bca5dd3aaef801b234b18aa87d2a99101610a82565b612baa6040518061010001604052806000815260200160006001600160a01b0316815260200160008152602001600081526020016000815260200160001515815260200160008152602001600081525090565b60405163ee55e7b560e01b8152600360048083019190915260248201526044810183905273cbca586bf9706706398164bb5eb8e48f220fe4089063ee55e7b59060640161010060405180830381865af4158015612c0b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108b89190615c50565b612c39843361451e565b600460005260056020527f3eec716f11ba9e820c81ca75eb978ffb45831ef8b7a53e5e422c26008e1ca6d554612c70908590612dc0565b506000848152600460205260408120612c929033908790610cba906002612e55565b9050612c9d81612f16565b6000612ca882612f59565b90506000612cb587613401565b90507fd8eb4847ae2a642c0c24f4336eb3ac86a4cac889da47bb554f95c4d6163d80ff8188848660c0015187606001518b8b8b604051610a82989796959493929190615cd2565b806001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316636352211e846040518263ffffffff1660e01b8152600401612d5491815260200190565b602060405180830381865afa158015612d71573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d959190615845565b6001600160a01b031614612dbc5760405163a03dca6f60e01b815260040160405180910390fd5b5050565b60405163659d6d9960e11b815260026004808301919091526024820152604481018390526064810182905260009073cbca586bf9706706398164bb5eb8e48f220fe4089063cb3adb3290608401602060405180830381865af4158015612e2a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612e4e9190615882565b9392505050565b60ff8116600090815260208390526040812054612e4e565b612e75614fba565b6001600160a01b0380851682526020820184905282166040820152612e9c60026001612e55565b60608201526001600160a01b0382166000908152600360205260409020612ec4906001612e55565b6001600160a01b031660808201526000838152600460205260409020612eeb906003612e55565b60a08201526000838152600460208190526040909120612f0a91612e55565b60c08201529392505050565b6040808201516001600160a01b0316600090815260036020819052919020612f3d91612e55565b60e08201526040810151612f5090614913565b61010090910152565b600080670de0b6b3a76400008360e00151670de0b6b3a764000085610100015186608001516001600160a01b0316631e01043988602001516040518263ffffffff1660e01b8152600401612faf91815260200190565b602060405180830381865afa158015612fcc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ff09190615882565b612ffa9190615d18565b61300491906156e9565b61300e9190615d18565b61301891906156e9565b90506000808460a0015112156130435761303e8460a0015161303990615d2f565b61439e565b613046565b60005b9050808210613099576130967f000000000000000000000000000000000000000000000000000000000000000060ff16601261308f8760a0015186614a9190919063ffffffff16565b9190614abe565b92505b5050919050565b60008261010001516000196130b591906156e9565b8210156108b85760808301516020840151604051631e01043960e01b815260048101919091526000916001600160a01b031690631e01043990602401602060405180830381865afa15801561310e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906131329190615882565b90508083106131a95760008460a0015113156131a4576000670de0b6b3a7640000856101000151838661316591906158df565b61316f9190615d18565b61317991906156e9565b9050600061318a8660a0015161439e565b9050818111156131a15761319e82826158df565b93505b50505b61324f565b6000670de0b6b3a76400008560e00151670de0b6b3a764000087610100015187866131d491906158df565b6131de9190615d18565b6131e891906156e9565b6131f29190615d18565b6131fc91906156e9565b905060008560a00151126132215760a085015161321a908290614a91565b925061324d565b60006132348660a0015161303990615d2f565b90508082111561324b5761324881836158df565b93505b505b505b8115613287576132848260ff7f0000000000000000000000000000000000000000000000000000000000000000166012614abe565b91505b5092915050565b60008083608001516001600160a01b0316631e01043985602001516040518263ffffffff1660e01b81526004016132c791815260200190565b602060405180830381865afa1580156132e4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133089190615882565b90506000670de0b6b3a76400008560e00151670de0b6b3a7640000876101000151856133349190615d18565b61333e91906156e9565b6133489190615d18565b61335291906156e9565b90506000808660a00151126133785760a0860151613371908390614a91565b9050613396565b6133898660a0015161303990615d2f565b61339390836158df565b90505b848111156133d4576133d160ff7f000000000000000000000000000000000000000000000000000000000000000016601261308f88856158df565b93505b50505092915050565b6001600160a01b0381165b60ff909216600090815260209390935250604090912055565b60008061341060026005612e55565b905061341d600182615d4b565b9050613435600260056001600160801b038416613612565b600083815260046020526040812061344e906001612e55565b905061345b600182615d4b565b60008581526004602052604090209091506134819060016001600160801b038416613612565b60006134ad6001600160801b0383166fffffffffffffffffffffffffffffffff19608086901b166156fd565b95945050505050565b6134be614fba565b6134c9848484612e6d565b9050612e4e8383614af1565b900390565b6000806134e885858561354a565b905060008512801561350457508461350182858761354a565b14155b15611f80576134ad6001826158bf565b60006001600160ff1b0382111561354657604051632a26bd1360e01b8152600481018390526024015b60405180910390fd5b5090565b60008183146135825761355e83600a615e4f565b61356983600a615e4f565b6135739086615e5b565b61357d9190615e8b565b611f80565b509192915050565b606081015161359e90600290600190613612565b60408082015160208084015160009081526004909152919091206135c4916002906133dd565b60a081015160208083015160009081526004909152604090206135e991600390613612565b60c081015160208083015160009081526004918290526040902061360f92909190613612565b50565b806133e8565b600082815260046020526040902081906001600160801b0382169061363e906001612e55565b1461365c576040516302e8145360e61b815260040160405180910390fd5b6125aa600161366b8382615d4b565b600086815260046020526040902091906001600160801b0316613612565b60008061369585611f88565b90925090506136a660028042613612565b6136b36002600385613612565b6136c06002600484613612565b60008581526004602052604090206136da90600586613612565b60008581526004602052604090206136f490600683613612565b600085815260046020526040902061167e90600784613612565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663c19d93fb6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561376e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137929190615eb9565b1561379c57600080fd5b60006137c960027f0000000000000000000000000000000000000000000000000000000000000000615ed6565b6137d490600a615eef565b84925090506137ed670de0b6b3a76400006000196156e9565b821080156137ff575060008560a00151125b156138bb577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031685604001516001600160a01b031603613863576138528560a0015161303990615d2f565b61385c90836156fd565b91506138bb565b6064856101000151670de0b6b3a76400006138858860a0015161303990615d2f565b61388f9190615d18565b61389991906156e9565b6138a4906069615d18565b6138ae91906156e9565b6138b890836156fd565b91505b60808501516020860151604051637cbc237360e01b81526001600160a01b0390921691637cbc2373916138fb918690600401918252602082015260400190565b6020604051808303816000875af115801561391a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061393e9190615882565b9150600080600086851115613b4e57600061395988876158df565b905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168a604001516001600160a01b0316036139ad5750806139a681886158df565b9650613b22565b60016001600160a01b03168a604001516001600160a01b031603613a6d576000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663f29216e2856040518263ffffffff1660e01b8152600401604080518083038185885af1158015613a2d573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190613a52919061589b565b9093508392509050613a64818a6158df565b98505050613b22565b60408a8101519051633e87f49360e01b81526001600160a01b0391821660048201526024810184905260009182917f000000000000000000000000000000000000000000000000000000000000000090911690633e87f4939060440160408051808303816000875af1158015613ae7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613b0b919061589b565b9093508392509050613b1d818a6158df565b985050505b613b2c81866156fd565b9450613b3781613514565b8a60a001818151613b489190615862565b90525050505b600085118015613b62575060008860a00151125b15613d9f576000613b7a8960a0015161303990615d2f565b905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168a604001516001600160a01b031603613bde57818710613bd5575080613bce81886158df565b9650613d73565b50600095613d73565b85821015613bea578591505b60016001600160a01b03168a604001516001600160a01b031603613cb7576000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316637661f4f68a866040518363ffffffff1660e01b8152600401613c5a91815260200190565b604080518083038185885af1158015613c77573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190613c9c919061589b565b9093508392509050613cae818a6158df565b98505050613d73565b60408a810151905163bf441a5960e01b81526001600160a01b039182166004820152602481018490526044810189905260009182917f00000000000000000000000000000000000000000000000000000000000000009091169063bf441a599060640160408051808303816000875af1158015613d38573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613d5c919061589b565b9093508392509050613d6e818a6158df565b985050505b613d7d81866156fd565b9450613d8881613514565b8a60a001818151613d999190615862565b90525050505b60008860a001511315613fa8576000613dc2670de0b6b3a76400006000196156e9565b8810613ddc57613dd58960a0015161439e565b9050613e42565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031689604001516001600160a01b0316148015613e2057508786105b15613e4257613e3f613e358a60a0015161439e565b611bf0888b6158df565b90505b8015613fa657600084821115613f595760006001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016637cbc237382613e8e89876158df565b6040516001600160e01b031960e085901b168152600481019290925260248201526044016020604051808303816000875af1158015613ed1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613ef59190615882565b9050613f0186846158df565b811015613f43578815613f2a5780613f1987856158df565b613f2391906158df565b9350613f43565b6040516320b7831f60e11b815260040160405180910390fd5b613f4d81876156fd565b91506000955050613f68565b5080613f6581866158df565b94505b613f7281856156fd565b9350613f7d83613514565b613f8682613514565b613f909190615862565b8a60a001818151613fa191906158bf565b905250505b505b821561404257604051631c57762b60e31b815260006004820152602481018490527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063e2bbb158906044016020604051808303816000875af115801561401c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140409190615882565b505b81156142e35785156140d8577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031688604001516001600160a01b03160361409c5761409582866156fd565b94506142e3565b87516140d3906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690846143db565b6142e3565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031688604001516001600160a01b03160361411f5761409582866156fd565b838210156141765761416c6141637f000000000000000000000000000000000000000000000000000000000000000060ff16601261415c86613514565b919061354a565b60608a01510190565b60608901526142e3565b60016001600160a01b031688604001516001600160a01b03160361423757604051632cb4d9d960e11b8152600481018390526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690635969b3b29060240160408051808303816000875af11580156141fd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614221919061589b565b915061422f905081876156fd565b9550506142e3565b604088810151905163045608af60e01b81526001600160a01b039182166004820152602481018490526000917f0000000000000000000000000000000000000000000000000000000000000000169063045608af9060440160408051808303816000875af11580156142ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906142d1919061589b565b91506142df905081876156fd565b9550505b84156143065787516040890151614306916001600160a01b0390911690876143db565b80156143935787516040516340c10f1960e01b81526001600160a01b039182166004820152602481018390527f0000000000000000000000000000000000000000000000000000000000000000909116906340c10f1990604401600060405180830381600087803b15801561437a57600080fd5b505af115801561438e573d6000803e3d6000fd5b505050505b505050509392505050565b60008082121561354657604051632a33bb3160e01b81526004810183905260240161353d565b6000818311156143d45781612e4e565b5090919050565b60006143e684614bf3565b90506000196001600160a01b03851601614474576000836001600160a01b03168360405160006040518083038185875af1925050503d8060008114614447576040519150601f19603f3d011682016040523d82523d6000602084013e61444c565b606091505b505090508061446e5760405163e277d13760e01b815260040160405180910390fd5b50614488565b6144886001600160a01b0385168484614c7a565b600061449385614bf3565b905061449f83826156fd565b821461167e5760405163f4d4667360e01b815260040160405180910390fd5b60405163901b479760e01b815260026004808301919091526024820152604481018390526001600160a01b038216606482015273cbca586bf9706706398164bb5eb8e48f220fe4089063901b479790608401611481565b6133e881614cdd565b806001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316636352211e846040518263ffffffff1660e01b815260040161457691815260200190565b602060405180830381865afa158015614593573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906145b79190615845565b6001600160a01b031614612dbc57604051639289a15b60e01b815260040160405180910390fd5b6001600160a01b0381166000908152600360205260408120614601906001612e55565b6001600160a01b03160361360f57604051634033aec960e01b815260040160405180910390fd5b6000196001600160a01b0384160161465a573481146125aa57604051630fc9bd0f60e11b815260040160405180910390fd5b600061466584614bf3565b905061467c6001600160a01b038516843085614cf7565b600061468785614bf3565b905061469383836156fd565b811461167e57604051630fc9bd0f60e11b815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031682604001516001600160a01b0316036147ef576000670de0b6b3a76400006147247f000000000000000000000000000000000000000000000000000000000000000084615d18565b61472e91906156e9565b905061473a81836158df565b604051631c57762b60e31b815260006004820152602481018390529092507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063e2bbb158906044016020604051808303816000875af11580156147ab573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906147cf9190615882565b506147d981613514565b8360a0018181516147ea9190615862565b905250505b60016001600160a01b031682604001516001600160a01b0316036148905781608001516001600160a01b031663e2bbb158828460200151846040518463ffffffff1660e01b815260040161484d929190918252602082015260400190565b60206040518083038185885af115801561486b573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906125aa9190615882565b60808201516020830151604051631c57762b60e31b81526001600160a01b039092169163e2bbb158916148d0918590600401918252602082015260400190565b6020604051808303816000875af11580156148ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125aa9190615882565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b03160361495d5750670de0b6b3a7640000919050565b6000614971836001600160a01b0316614d2f565b9050614a678160ff167f000000000000000000000000000000000000000000000000000000000000000060ff1661308f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166369843940614a086002600360008c6001600160a01b03166001600160a01b03168152602001908152602001600020614dac90919063ffffffff16565b6040518263ffffffff1660e01b8152600401614a2691815260200190565b602060405180830381865afa158015614a43573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130399190615882565b915081600003614a8a5760405163089112d560e21b815260040160405180910390fd5b505b919050565b6000808212614aab57614aa482846156fd565b90506108b8565b614ab482615d2f565b614aa490846158df565b600081831461358257614ad283600a615e4f565b614add83600a615e4f565b614ae79086615d18565b61357d91906156e9565b6000828152600460205260408120614b0a906002612e55565b90506001600160a01b03811615801590614b365750816001600160a01b0316816001600160a01b031614155b156125aa576001600160a01b0381166000908152600360205260408120614b5e906001612e55565b6001600160a01b0316635d988967856040518263ffffffff1660e01b8152600401614b8b91815260200190565b602060405180830381865afa158015614ba8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614bcc9190615882565b90508015614bed57604051634033aec960e01b815260040160405180910390fd5b50505050565b60006001600160a01b038216600114614c73576040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401602060405180830381865afa158015614c4a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614c6e9190615882565b6108b8565b4792915050565b6040516001600160a01b0383166024820152604481018290526125aa90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152614dc2565b600081614ceb576000614cee565b60015b60ff1692915050565b6040516001600160a01b0380851660248301528316604482015260648101829052614bed9085906323b872dd60e01b90608401614ca6565b60006001600160a01b038216600114614da457816001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015614d80573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614c6e9190615efe565b601292915050565b60ff166000908152602091909152604090205490565b6000614e17826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316614e979092919063ffffffff16565b9050805160001480614e38575080806020019051810190614e389190615eb9565b6125aa5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161353d565b6060611f80848460008585600080866001600160a01b03168587604051614ebe9190615f21565b60006040518083038185875af1925050503d8060008114614efb576040519150601f19603f3d011682016040523d82523d6000602084013e614f00565b606091505b5091509150614f1187838387614f1c565b979650505050505050565b60608315614f8b578251600003614f84576001600160a01b0385163b614f845760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161353d565b5081611f80565b611f808383815115614fa05781518083602001fd5b8060405162461bcd60e51b815260040161353d9190615f3d565b60405180610120016040528060006001600160a01b031681526020016000815260200160006001600160a01b031681526020016000815260200160006001600160a01b03168152602001600081526020016000815260200160008152602001600081525090565b6001600160a01b038116811461360f57600080fd5b60006020828403121561504857600080fd5b8135612e4e81615021565b60008060006060848603121561506857600080fd5b83359250602084013561507a81615021565b929592945050506040919091013590565b60008083601f84011261509d57600080fd5b5081356001600160401b038111156150b457600080fd5b6020830191508360208260051b85010111156150cf57600080fd5b9250929050565b801515811461360f57600080fd5b600080600080600080600060c0888a0312156150ff57600080fd5b87359650602088013561511181615021565b9550604088013594506060880135935060808801356001600160401b0381111561513a57600080fd5b6151468a828b0161508b565b90945092505060a088013561515a816150d6565b8091505092959891949750929550565b634e487b7160e01b600052604160045260246000fd5b60405161014081016001600160401b03811182821017156151a3576151a361516a565b60405290565b60405161010081016001600160401b03811182821017156151a3576151a361516a565b604051601f8201601f191681016001600160401b03811182821017156151f4576151f461516a565b604052919050565b600082601f83011261520d57600080fd5b81356001600160401b038111156152265761522661516a565b615239601f8201601f19166020016151cc565b81815284602083860101111561524e57600080fd5b816020850160208301376000918101602001919091529392505050565b6000806040838503121561527e57600080fd5b82356001600160401b038082111561529557600080fd5b6152a1868387016151fc565b935060208501359150808211156152b757600080fd5b506152c4858286016151fc565b9150509250929050565b600080604083850312156152e157600080fd5b8235915060208301356152f3816150d6565b809150509250929050565b60006020828403121561531057600080fd5b5035919050565b8151815260208083015161014083019161533b908401826001600160a01b03169052565b5060408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015260e083015160e083015261010080840151818401525061012080840151818401525092915050565b600080600080608085870312156153ac57600080fd5b8435935060208501356153be81615021565b92506040850135915060608501356153d5816150d6565b939692955090935050565b600080604083850312156153f357600080fd5b50508035926020909101359150565b81516001600160a01b031681526101e08101602083015161542e60208401826001600160a01b03169052565b50604083015161544960408401826001600160a01b03169052565b50606083015161546460608401826001600160a01b03169052565b50608083015161547f60808401826001600160a01b03169052565b5060a083015161549a60a08401826001600160a01b03169052565b5060c08301516154b560c08401826001600160a01b03169052565b5060e08301516154d060e08401826001600160a01b03169052565b506101008381015190830152610120808401519083015261014080840151908301526101608084015190830152610180808401516001600160a01b0381168285015250506101a0838101516001600160a01b0381168483015250506101c0838101516001600160a01b038116848301525b505092915050565b60008060008060008060a0878903121561556257600080fd5b86359550602087013561557481615021565b9450604087013593506060870135925060808701356001600160401b0381111561559d57600080fd5b6155a989828a0161508b565b979a9699509497509295939492505050565b6020808252825182820181905260009190848201906040850190845b818110156155f3578351835292840192918401916001016155d7565b50909695505050505050565b6000806000806060858703121561561557600080fd5b843593506020850135925060408501356001600160401b0381111561563957600080fd5b6156458782880161508b565b95989497509550505050565b8051614a8c81615021565b60006060828403121561566e57600080fd5b604051606081018181106001600160401b03821117156156905761569061516a565b604052825161569e81615021565b8152602083810151908201526040928301519281019290925250919050565b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000826156f8576156f86156bd565b500490565b808201808211156108b8576108b86156d3565b60005b8381101561572b578181015183820152602001615713565b50506000910152565b6000815180845261574c816020860160208601615710565b601f01601f19169290920160200192915050565b6080815260006157736080830187615734565b82810360208401526157858187615734565b604084019590955250506001600160a01b039190911660609091015292915050565b600060e082840312156157b957600080fd5b60405160e081018181106001600160401b03821117156157db576157db61516a565b60405282516157e981615021565b815260208301516157f981615021565b6020820152604083015161580c81615021565b80604083015250606083015160608201526080830151608082015260a083015160a082015260c083015160c08201528091505092915050565b60006020828403121561585757600080fd5b8151612e4e81615021565b8082018281126000831280158216821582161715615541576155416156d3565b60006020828403121561589457600080fd5b5051919050565b600080604083850312156158ae57600080fd5b505080516020909101519092909150565b8181036000831280158383131683831282161715613287576132876156d3565b818103818111156108b8576108b86156d3565b6000610140828403121561590557600080fd5b61590d615180565b8251815261591d60208401615651565b602082015260408301516040820152606083015160608201526080830151608082015260a083015160a082015260c083015160c082015260e083015160e08201526101008084015181830152506101208084015181830152508091505092915050565b600060c0828403121561599257600080fd5b60405160c081018181106001600160401b03821117156159b4576159b461516a565b8060405250825181526020830151602082015260408301516040820152606083015160608201526080830151608082015260a083015160a08201528091505092915050565b600060a08284031215615a0b57600080fd5b60405160a081018181106001600160401b0382111715615a2d57615a2d61516a565b806040525082518152602083015160208201526040830151604082015260608301516060820152608083015160808201528091505092915050565b600060408284031215615a7a57600080fd5b604051604081018181106001600160401b0382111715615a9c57615a9c61516a565b604052825181526020928301519281019290925250919050565b60006101008284031215615ac957600080fd5b615ad16151a9565b825181526020830151602082015260408301516040820152606083015160608201526080830151608082015260a083015160a082015260c083015160c082015260e083015160e08201528091505092915050565b8183526000602080850194508260005b85811015615b5157813587529582019590820190600101615b35565b509495945050505050565b60006101008b83528a60208401528960408401528860608401528760808401528660a08401528560c08401528060e0840152615b9b8184018587615b25565b9c9b505050505050505050505050565b60006020808385031215615bbe57600080fd5b82516001600160401b0380821115615bd557600080fd5b818501915085601f830112615be957600080fd5b815181811115615bfb57615bfb61516a565b8060051b9150615c0c8483016151cc565b8181529183018401918481019088841115615c2657600080fd5b938501935b83851015615c4457845182529385019390850190615c2b565b98975050505050505050565b60006101008284031215615c6357600080fd5b615c6b6151a9565b825181526020830151615c7d81615021565b8060208301525060408301516040820152606083015160608201526080830151608082015260a0830151615cb0816150d6565b60a082015260c0838101519082015260e0928301519281019290925250919050565b8881528760208201528660408201528560608201528460808201528360a082015260e060c08201526000615d0a60e083018486615b25565b9a9950505050505050505050565b80820281158282048414176108b8576108b86156d3565b6000600160ff1b8201615d4457615d446156d3565b5060000390565b6001600160801b03818116838216019080821115613287576132876156d3565b600181815b80851115615da6578160001904821115615d8c57615d8c6156d3565b80851615615d9957918102915b93841c9390800290615d70565b509250929050565b600082615dbd575060016108b8565b81615dca575060006108b8565b8160018114615de05760028114615dea57615e06565b60019150506108b8565b60ff841115615dfb57615dfb6156d3565b50506001821b6108b8565b5060208310610133831016604e8410600b8410161715615e29575081810a6108b8565b615e338383615d6b565b8060001904821115615e4757615e476156d3565b029392505050565b6000612e4e8383615dae565b80820260008212600160ff1b84141615615e7757615e776156d3565b81810583148215176108b8576108b86156d3565b600082615e9a57615e9a6156bd565b600160ff1b821460001984141615615eb457615eb46156d3565b500590565b600060208284031215615ecb57600080fd5b8151612e4e816150d6565b60ff82811682821603908111156108b8576108b86156d3565b6000612e4e60ff841683615dae565b600060208284031215615f1057600080fd5b815160ff81168114612e4e57600080fd5b60008251615f33818460208701615710565b9190910192915050565b602081526000612e4e602083018461573456fea2646970667358221220e2baf2c8c978e5b836b2d7d23da6f71327c5bd537b9b46c1a3da24f0c128d04b64736f6c6343000814003300000000000000000000000060138081198b75aaf15aca3a17ec7f5ffc5d460500000000000000000000000067bdd68f20a1bb06f487d29b26ad63e162a2f828000000000000000000000000bca4439e99091afb297ecb4c5672357e467664f20000000000000000000000009a34b3810d422373ba5128ffee880235003f5cac00000000000000000000000087664d190669a00ce699944c2485326d574ecd02000000000000000000000000763f02688cc0c78e8c4e852302bfae6e1b33f42900000000000000000000000029219dd400f2bf60e5a23d13be72b486d4038894000000000000000000000000a51cd97f3090f6a16cf0cdc12b0cb4b0a95b38ea00000000000000000000000000000000000000000000000002c68af0bb14000000000000000000000000000000000000000000000000000006f05b59d3b200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001dcd6500000000000000000000000000b5284ed1606b91e0129182d55ee7ee31c31c920c000000000000000000000000d4e08c940ddec162c2d8f3034c75c3e08f1f6032000000000000000000000000cf82aaa68b3868fd2f280f9b211edcdea9f4770d

Deployed Bytecode

0x60806040526004361061019c5760003560e01c80638769da2d116100ec578063db4015661161008a578063f7c60d4411610064578063f7c60d441461074f578063f802a69714610762578063f851a440146107e9578063fa321c261461080957600080fd5b8063db401566146106a4578063e1faff561461071a578063e926bc9d1461072d57600080fd5b8063be801c08116100c6578063be801c08146103e7578063c323acb614610407578063cd18dde314610664578063d784d4261461068457600080fd5b80638769da2d1461037f5780639b1a2e94146103b4578063aaba0398146103d457600080fd5b80635c60da1b11610159578063702e6c0611610133578063702e6c06146102fe578063704b6c021461031e57806370a1848c1461033e5780637e9459ba1461035e57600080fd5b80635c60da1b146102795780635c8e13a6146102b1578063619cf2be146102d157600080fd5b8063068689cd146101a15780630c8aeb56146101f157806316890fc2146102065780631c58d9c11461021957806323cc0ebf146102395780633e0c7fc114610259575b600080fd5b3480156101ad57600080fd5b506101c16101bc366004615036565b61081c565b6040805182516001600160a01b031681526020808401519082015291810151908201526060015b60405180910390f35b6102046101ff366004615053565b6108be565b005b6102046102143660046150e4565b610a93565b34801561022557600080fd5b5061020461023436600461526b565b610b20565b34801561024557600080fd5b506102046102543660046152ce565b6113de565b34801561026557600080fd5b506102046102743660046152fe565b6114b5565b34801561028557600080fd5b50600154610299906001600160a01b031681565b6040516001600160a01b0390911681526020016101e8565b3480156102bd57600080fd5b506102046102cc366004615036565b6115e4565b3480156102dd57600080fd5b506102f16102ec3660046152fe565b611685565b6040516101e89190615317565b34801561030a57600080fd5b506102046103193660046152fe565b611769565b34801561032a57600080fd5b50610204610339366004615036565b611826565b34801561034a57600080fd5b5061020461035936600461526b565b6118a6565b61037161036c366004615396565b611daf565b6040519081526020016101e8565b34801561038b57600080fd5b5061039f61039a3660046152fe565b611f88565b604080519283526020830191909152016101e8565b3480156103c057600080fd5b506102046103cf36600461526b565b612019565b6102046103e2366004615053565b6122a5565b3480156103f357600080fd5b506102046104023660046153e0565b612491565b34801561041357600080fd5b50604080516101e08101825260006101c08201526001600160a01b037f00000000000000000000000060138081198b75aaf15aca3a17ec7f5ffc5d4605811682527f00000000000000000000000067bdd68f20a1bb06f487d29b26ad63e162a2f828811660208301527f000000000000000000000000bca4439e99091afb297ecb4c5672357e467664f28116828401527f0000000000000000000000009a34b3810d422373ba5128ffee880235003f5cac811660608301527f00000000000000000000000087664d190669a00ce699944c2485326d574ecd02811660808301527f000000000000000000000000763f02688cc0c78e8c4e852302bfae6e1b33f429811660a08301527f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d4038894811660c08301527f000000000000000000000000a51cd97f3090f6a16cf0cdc12b0cb4b0a95b38ea811660e08301527f00000000000000000000000000000000000000000000000002c68af0bb1400006101008301527f00000000000000000000000000000000000000000000000006f05b59d3b200006101208301527f00000000000000000000000000000000000000000000000000000000000000006101408301527f000000000000000000000000000000000000000000000000000000001dcd65006101608301527f000000000000000000000000b5284ed1606b91e0129182d55ee7ee31c31c920c81166101808301527f000000000000000000000000d4e08c940ddec162c2d8f3034c75c3e08f1f6032166101a082015290516101e89190615402565b34801561067057600080fd5b5061020461067f36600461526b565b6125af565b34801561069057600080fd5b5061020461069f366004615036565b612778565b3480156106b057600080fd5b506106b96127f1565b6040516101e89190600061010082019050825182526020830151602083015260408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015260e083015160e083015292915050565b610204610728366004615549565b6128b3565b34801561073957600080fd5b506107426129c5565b6040516101e891906155bb565b61020461075d366004615053565b612a40565b34801561076e57600080fd5b5061078261077d3660046152fe565b612b57565b6040516101e89190815181526020808301516001600160a01b03169082015260408083015190820152606080830151908201526080808301519082015260a08083015115159082015260c0808301519082015260e091820151918101919091526101000190565b3480156107f557600080fd5b50600054610299906001600160a01b031681565b6102046108173660046155ff565b612c2f565b604080516060810182526000808252602082018190528183015290516340d24f1f60e11b8152600360048201526001600160a01b038316602482015273cbca586bf9706706398164bb5eb8e48f220fe408906381a49e3e90604401606060405180830381865af4158015610894573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108b8919061565c565b92915050565b6108c88333612cfc565b600260005260056020527f89832631fb3c3307a103ba2c84ab569c64d6182a18893dcd163f0f1c2090733a546108ff908490612dc0565b5080600003610921576040516394613e9960e01b815260040160405180910390fd5b60008381526004602052604081206109479033908690610942906002612e55565b612e6d565b905061095281612f16565b600061095d82612f59565b90506000846001600160a01b031683604001516001600160a01b03160361098f5761098883856130a0565b90506109eb565b7f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388946001600160a01b0316856001600160a01b0316036109d257610988838561328e565b604051634033aec960e01b815260040160405180910390fd5b6109f66064836156e9565b8111610a00575060005b6000868152600460205260409020610a1a90600b876133dd565b6000610a2587613401565b60c08086015160608088015160408051868152602081018e905290810188905291820192909252608081019190915260a081018890529192507f0be79565c9c0f7b4144002bd9abeb6bd0e8f43023abae7066992c58bdff9397e91015b60405180910390a150505050505050565b6000196001600160a01b03871601610afd57600460005260056020527f3eec716f11ba9e820c81ca75eb978ffb45831ef8b7a53e5e422c26008e1ca6d55434610adc82886156fd565b1115610afb576040516394613e9960e01b815260040160405180910390fd5b505b610b0987878784611daf565b9650610b1787858585612c2f565b50505050505050565b600154600160a01b900460ff1615610b4b576040516325dbe6e160e21b815260040160405180910390fd5b6001805460ff60a01b1916600160a01b179055604051633db1a4d360e01b815273cbca586bf9706706398164bb5eb8e48f220fe40890633db1a4d390610bbc908590859060e0907f000000000000000000000000a51cd97f3090f6a16cf0cdc12b0cb4b0a95b38ea90600401615760565b60006040518083038186803b158015610bd457600080fd5b505af4158015610be8573d6000803e3d6000fd5b50505050600082806020019051810190610c0291906157a7565b90506000610cbf7f00000000000000000000000067bdd68f20a1bb06f487d29b26ad63e162a2f8286001600160a01b0316636352211e84608001516040518263ffffffff1660e01b8152600401610c5b91815260200190565b602060405180830381865afa158015610c78573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c9c9190615845565b60808401516000818152600460205260409020610cba906002612e55565b6134b6565b90506000610cde8260c001518460a001516134d590919063ffffffff16565b9050610d0f81601260ff7f0000000000000000000000000000000000000000000000000000000000000006166134da565b8260a001818151610d209190615862565b90525060a083015160c083015260808201516020830151604051637cbc237360e01b81526004810191909152600019602482015260009182916001600160a01b0390911690637cbc2373906044016020604051808303816000875af1158015610d8d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610db19190615882565b90507f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388946001600160a01b031684604001516001600160a01b031603610e0157610dfa81836156fd565b9150610f5f565b604084810151602086015160a087015160c089015193516330706e4160e01b81527f000000000000000000000000000000000000000000000000000000000000000660ff1660048201526001600160a01b0393841660248201527f0000000000000000000000009a34b3810d422373ba5128ffee880235003f5cac841660448201527f000000000000000000000000d4e08c940ddec162c2d8f3034c75c3e08f1f6032841660648201527f00000000000000000000000067bdd68f20a1bb06f487d29b26ad63e162a2f828909316608484015260a483019190915260c482015260e4810183905261010481019190915273cbca586bf9706706398164bb5eb8e48f220fe408906330706e419061012401602060405180830381865af4158015610f2e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f529190615882565b610f5c90836156fd565b91505b5060008360a00151610f7083613514565b610f7a9190615862565b60405162abd53960e21b8152600481018290527f000000000000000000000000000000000000000000000000000000000000000060248201527f000000000000000000000000000000000000000000000000000000001dcd650060448201527f00000000000000000000000000000000000000000000000006f05b59d3b20000606482015290915060009073cbca586bf9706706398164bb5eb8e48f220fe408906302af54e490608401602060405180830381865af4158015611041573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110659190615882565b6020870151604080890151905163101b43e760e31b81526001600160a01b037f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d4038894811660048301527f00000000000000000000000087664d190669a00ce699944c2485326d574ecd028116602483015260448201859052606482018890529283166084820152911660a482015290915073cbca586bf9706706398164bb5eb8e48f220fe408906380da1f389060c4016040805180830381865af415801561112f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611153919061589b565b9350905061116181836158bf565b915082156111fd57604051631c57762b60e31b815260006004820152602481018490527f00000000000000000000000087664d190669a00ce699944c2485326d574ecd026001600160a01b03169063e2bbb158906044016020604051808303816000875af11580156111d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111fb9190615882565b505b61123861122f8360ff7f000000000000000000000000000000000000000000000000000000000000000616601261354a565b60608701510190565b6060860152600060a086015261124d8561358a565b6080860151600090815260046020526040812061126b906009612e55565b608088015160009081526004602052604081209192509061128d90600a612e55565b608089018051600090815260046020818152604080842060098552825280842084905593518352908152828220600a835290529081208190559091506112d560026007612e55565b90506112e182846156fd565b6112eb90826158df565b90506112fa6002600783613612565b5050506080860151604051630852cd8d60e31b815260048101919091527f00000000000000000000000067bdd68f20a1bb06f487d29b26ad63e162a2f8286001600160a01b0316906342966c6890602401600060405180830381600087803b15801561136557600080fd5b505af1158015611379573d6000803e3d6000fd5b505050606080880151608089015160408051928352602083019190915281018590527f8c3b5c5c5dddce5cc5abfa2cc66ec5dd06f76f9e9bb21debb1a85ad88fdbdb6f92500160405180910390a150506001805460ff60a01b19169055505050505050565b604051630429dd8560e41b8152600260048083019190915260248201526001600160a01b037f00000000000000000000000060138081198b75aaf15aca3a17ec7f5ffc5d4605811660448301527f00000000000000000000000067bdd68f20a1bb06f487d29b26ad63e162a2f8281660648201526084810183905281151560a482015273cbca586bf9706706398164bb5eb8e48f220fe4089063429dd8509060c4015b60006040518083038186803b15801561149957600080fd5b505af41580156114ad573d6000803e3d6000fd5b505050505050565b6040516331a9108f60e11b815260048101829052600090611560906001600160a01b037f00000000000000000000000067bdd68f20a1bb06f487d29b26ad63e162a2f8281690636352211e90602401602060405180830381865afa158015611521573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115459190615845565b60008481526004602052604090208490610cba906002612e55565b905061156b81612f16565b600061157682612f59565b9050600061158384613401565b60c084015160608086015160408051858152602081018a905280820188905292830193909352608082015290519192507fb299642ff40e6ba13eec0f77203c3ef9816a9f64212a40467d63db0eba259b4f919081900360a00190a150505050565b6000546001600160a01b0316331461160f57604051634755657960e01b815260040160405180910390fd5b604051631428438f60e11b8152600260048201526001600160a01b038216602482015273cbca586bf9706706398164bb5eb8e48f220fe40890632850871e906044015b60006040518083038186803b15801561166a57600080fd5b505af415801561167e573d6000803e3d6000fd5b5050505050565b6116e46040518061014001604052806000815260200160006001600160a01b0316815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040516393760d3d60e01b8152600360048083019190915260248201526044810183905273cbca586bf9706706398164bb5eb8e48f220fe408906393760d3d9060640161014060405180830381865af4158015611745573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108b891906158f2565b6040516332f6692360e01b81526001600160a01b037f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d4038894811660048301527f00000000000000000000000087664d190669a00ce699944c2485326d574ecd02811660248301527f000000000000000000000000763f02688cc0c78e8c4e852302bfae6e1b33f4291660448201523360648201526084810182905273cbca586bf9706706398164bb5eb8e48f220fe408906332f669239060a401611652565b6000546001600160a01b0316331461185157604051634755657960e01b815260040160405180910390fd5b600080546001600160a01b0319166001600160a01b0383169081179091556040519081527f71614071b88dee5e0b2ae578a9dd7b2ebbe9ae832ba419dc0242cd065a290b6c906020015b60405180910390a150565b600154600160a01b900460ff16156118d1576040516325dbe6e160e21b815260040160405180910390fd5b6001805460ff60a01b1916600160a01b179055604051633db1a4d360e01b815273cbca586bf9706706398164bb5eb8e48f220fe40890633db1a4d390611942908590859060c0907f000000000000000000000000a51cd97f3090f6a16cf0cdc12b0cb4b0a95b38ea90600401615760565b60006040518083038186803b15801561195a57600080fd5b505af415801561196e573d6000803e3d6000fd5b505050506000828060200190518101906119889190615980565b905061199c81602001518260000151613618565b6119b3816020015182604001518360600151613689565b6000611a6b7f00000000000000000000000060138081198b75aaf15aca3a17ec7f5ffc5d46056001600160a01b0316636352211e84602001516040518263ffffffff1660e01b8152600401611a0a91815260200190565b602060405180830381865afa158015611a27573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a4b9190615845565b602080850151600081815260049092526040909120610cba906002612e55565b90506000611a8a8260c0015184608001516134d590919063ffffffff16565b9050611abb81601260ff7f0000000000000000000000000000000000000000000000000000000000000006166134da565b8260a001818151611acc9190615862565b905250608083015160c0830152604082015160a08401516000919015611cb2576020808601516000908152600490915260409020611b0b90600b612e55565b9050806001600160a01b031684604001516001600160a01b031603611b5f57611b3384612f16565b611b58848660400151600014611b4d578660a00151611b51565b6000195b600061370e565b9150611cb2565b7f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388946001600160a01b0316816001600160a01b031614611b9d57600080fd5b60008460a001511315611cb2577f00000000000000000000000087664d190669a00ce699944c2485326d574ecd026001600160a01b0316637cbc23736000611bf58860a00151611bf08960a0015161439e565b6143c4565b6040516001600160e01b031960e085901b168152600481019290925260248201526044016020604051808303816000875af1158015611c38573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c5c9190615882565b9150611c6782613514565b8460a001818151611c7891906158bf565b9052508351611cb2906001600160a01b037f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388941690846143db565b611cbb8461358a565b611cc98560200151336144be565b8460a00151600003611d2e5784516020808701516040808901516060808b0151835196875294860193909352908401528201527f3b963caf777561b495590bdc6ec8abd33838524d0a3aafc3f6f1b7857449d0999060800160405180910390a1611d99565b84516020808701516040808901516060808b0151835196875294860193909352908401528201526001600160a01b038216608082015260a081018390527fc72ca5dc9c24b437f43b8c112e7b4e24b4a7bce4b1f405e591eee1cc32d327c29060c00160405180910390a15b50506001805460ff60a01b191690555050505050565b600084600003611e6b576040516335313c2160e11b81523360048201527f00000000000000000000000067bdd68f20a1bb06f487d29b26ad63e162a2f8286001600160a01b031690636a627842906024016020604051808303816000875af1158015611e1f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e439190615882565b94508115611e66576000858152600460205260409020611e669060086001614515565b611e75565b611e75853361451e565b611e7e846145de565b6000611e8b3387876134b6565b90506000196001600160a01b03861601611ec05734841115611ec0576040516394613e9960e01b815260040160405180910390fd5b83600003611ee1576040516394613e9960e01b815260040160405180910390fd5b6001600160a01b038516600114611f09578051611f09906001600160a01b0387169086614628565b611f1381856146b2565b611f1c8161358a565b6000611f2787613401565b60408051828152602081018a90526001600160a01b038916818301526060810188905290519192507f8eee26bd33caee967b96e711aa45ef94c734251b0457a0f2213fb392c01ef53a919081900360800190a186925050505b949350505050565b604051632a8bb6c960e11b81526002600482810191909152602482015260448101829052600090819073cbca586bf9706706398164bb5eb8e48f220fe408906355176d92906064016040805180830381865af4158015611fec573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612010919061589b565b91509150915091565b600154600160a01b900460ff1615612044576040516325dbe6e160e21b815260040160405180910390fd5b6001805460ff60a01b1916600160a01b179055604051633db1a4d360e01b815273cbca586bf9706706398164bb5eb8e48f220fe40890633db1a4d3906120b5908590859060a0907f000000000000000000000000a51cd97f3090f6a16cf0cdc12b0cb4b0a95b38ea90600401615760565b60006040518083038186803b1580156120cd57600080fd5b505af41580156120e1573d6000803e3d6000fd5b505050506000828060200190518101906120fb91906159f9565b905061210f81602001518260000151613618565b60006121667f00000000000000000000000067bdd68f20a1bb06f487d29b26ad63e162a2f8286001600160a01b0316636352211e84602001516040518263ffffffff1660e01b8152600401611a0a91815260200190565b905060006121858260c0015184606001516134d590919063ffffffff16565b90506121b681601260ff7f0000000000000000000000000000000000000000000000000000000000000006166134da565b8260a0018181516121c79190615862565b905250606083015160c08301526121dd82612f16565b60006121ef838560800151600161370e565b905083604001516121ff84612f59565b101561221e576040516341c092a960e01b815260040160405180910390fd5b6122278361358a565b6122358460200151336144be565b83516020808601516040868101518151948552928401919091526001600160a01b039091168282015260608201839052517f39f415ac8ef6b49b1ba40c4fafb2b911df1c5306c49eef15c4bc38a0128fca989181900360800190a150506001805460ff60a01b1916905550505050565b8260000361233e576040516335313c2160e11b81523360048201527f00000000000000000000000060138081198b75aaf15aca3a17ec7f5ffc5d46056001600160a01b031690636a627842906024016020604051808303816000875af1158015612313573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123379190615882565b9250612348565b6123488333612cfc565b612351826145de565b600061235e3385856134b6565b6001600090815260056020527f1471eb6eb2c5e789fc3de43f8ce62938c7d1836ec861730447e2ada8fd81017b549192509061239b908690612dc0565b90506000196001600160a01b038516016123b3578092505b826000036123d4576040516394613e9960e01b815260040160405180910390fd5b6001600160a01b0384166001146123fc5781516123fc906001600160a01b0386169085614628565b61240682846146b2565b61240f82612f16565b600061241a83612f59565b90506124258361358a565b600061243087613401565b60c08086015160608088015160408051868152602081018e9052908101889052918201929092526080810191909152600060a08201529192507f0be79565c9c0f7b4144002bd9abeb6bd0e8f43023abae7066992c58bdff9397e9101610a82565b61249b823361451e565b6124cf6001600160a01b037f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d4038894163383614628565b604051631c57762b60e31b815260006004820152602481018290527f00000000000000000000000087664d190669a00ce699944c2485326d574ecd026001600160a01b03169063e2bbb158906044016020604051808303816000875af115801561253d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125619190615882565b50600082815260046020526040812061257b906003612e55565b90506125aa600361258b84613514565b6125959084615862565b60008681526004602052604090209190613612565b505050565b6000546001600160a01b031633146125da57604051634755657960e01b815260040160405180910390fd5b60408051633db1a4d360e01b815273cbca586bf9706706398164bb5eb8e48f220fe40891633db1a4d3916126379186918691907f000000000000000000000000a51cd97f3090f6a16cf0cdc12b0cb4b0a95b38ea90600401615760565b60006040518083038186803b15801561264f57600080fd5b505af4158015612663573d6000803e3d6000fd5b5050505060008280602001905181019061267d9190615a68565b8051909150461461268d57600080fd5b602081015160405163ac46ae5160e01b8152600260048201526001600160a01b037f00000000000000000000000087664d190669a00ce699944c2485326d574ecd02811660248301527f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d4038894811660448301527f000000000000000000000000b5284ed1606b91e0129182d55ee7ee31c31c920c166064820152608481019190915273cbca586bf9706706398164bb5eb8e48f220fe4089063ac46ae519060a40160006040518083038186803b15801561276457600080fd5b505af4158015610b17573d6000803e3d6000fd5b6000546001600160a01b031633146127a357604051634755657960e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f6b70829fcbe4891157f7a7496f9870927de3c8237adbe9cd39bae09b7382c4099060200161189b565b61283960405180610100016040528060008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b604051637d4b09b360e01b81526002600482015273cbca586bf9706706398164bb5eb8e48f220fe40890637d4b09b39060240161010060405180830381865af415801561288a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128ae9190615ab6565b905090565b6128bd863361451e565b600560008190526020527f458b30c2d72bfd2c6317304a4594ecbafe5f729d3111b65fdc3a33bd48e5432d546128f4908790612dc0565b5083600003612916576040516394613e9960e01b815260040160405180910390fd5b60006129233388886134b6565b905061292e81612f16565b600061293982612f59565b9050600061294783886130a0565b90506129546064836156e9565b811161295e575060005b60006129698a613401565b90507ecae5d4a40f5ab841867a076a796230392d8ce403d409f84f18377b49a602d5818b848760c0015188606001518d8d8d8d6040516129b199989796959493929190615b5c565b60405180910390a150505050505050505050565b604051630afc7a3960e31b81526005600482015260609073cbca586bf9706706398164bb5eb8e48f220fe408906357e3d1c890602401600060405180830381865af4158015612a18573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526128ae9190810190615bab565b612a4a833361451e565b600360005260056020527fa9bc9a3a348c357ba16b37005d7e6b3236198c0e939f4af8c5f19b8deeb8ebc054612a81908490612dc0565b5080600003612aa3576040516394613e9960e01b815260040160405180910390fd5b6000612ab03385856134b6565b9050612abb81612f16565b6000612ac682612f59565b90506000612ad483856130a0565b9050612ae16064836156e9565b8111612aeb575060005b6000612af687613401565b60c08086015160608088015160408051868152602081018e905290810188905291820192909252608081019190915260a081018890529192507f88ad2ade0e436c3cffc2b0d1b51fb1379e5bca5dd3aaef801b234b18aa87d2a99101610a82565b612baa6040518061010001604052806000815260200160006001600160a01b0316815260200160008152602001600081526020016000815260200160001515815260200160008152602001600081525090565b60405163ee55e7b560e01b8152600360048083019190915260248201526044810183905273cbca586bf9706706398164bb5eb8e48f220fe4089063ee55e7b59060640161010060405180830381865af4158015612c0b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108b89190615c50565b612c39843361451e565b600460005260056020527f3eec716f11ba9e820c81ca75eb978ffb45831ef8b7a53e5e422c26008e1ca6d554612c70908590612dc0565b506000848152600460205260408120612c929033908790610cba906002612e55565b9050612c9d81612f16565b6000612ca882612f59565b90506000612cb587613401565b90507fd8eb4847ae2a642c0c24f4336eb3ac86a4cac889da47bb554f95c4d6163d80ff8188848660c0015187606001518b8b8b604051610a82989796959493929190615cd2565b806001600160a01b03167f00000000000000000000000060138081198b75aaf15aca3a17ec7f5ffc5d46056001600160a01b0316636352211e846040518263ffffffff1660e01b8152600401612d5491815260200190565b602060405180830381865afa158015612d71573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d959190615845565b6001600160a01b031614612dbc5760405163a03dca6f60e01b815260040160405180910390fd5b5050565b60405163659d6d9960e11b815260026004808301919091526024820152604481018390526064810182905260009073cbca586bf9706706398164bb5eb8e48f220fe4089063cb3adb3290608401602060405180830381865af4158015612e2a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612e4e9190615882565b9392505050565b60ff8116600090815260208390526040812054612e4e565b612e75614fba565b6001600160a01b0380851682526020820184905282166040820152612e9c60026001612e55565b60608201526001600160a01b0382166000908152600360205260409020612ec4906001612e55565b6001600160a01b031660808201526000838152600460205260409020612eeb906003612e55565b60a08201526000838152600460208190526040909120612f0a91612e55565b60c08201529392505050565b6040808201516001600160a01b0316600090815260036020819052919020612f3d91612e55565b60e08201526040810151612f5090614913565b61010090910152565b600080670de0b6b3a76400008360e00151670de0b6b3a764000085610100015186608001516001600160a01b0316631e01043988602001516040518263ffffffff1660e01b8152600401612faf91815260200190565b602060405180830381865afa158015612fcc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ff09190615882565b612ffa9190615d18565b61300491906156e9565b61300e9190615d18565b61301891906156e9565b90506000808460a0015112156130435761303e8460a0015161303990615d2f565b61439e565b613046565b60005b9050808210613099576130967f000000000000000000000000000000000000000000000000000000000000000660ff16601261308f8760a0015186614a9190919063ffffffff16565b9190614abe565b92505b5050919050565b60008261010001516000196130b591906156e9565b8210156108b85760808301516020840151604051631e01043960e01b815260048101919091526000916001600160a01b031690631e01043990602401602060405180830381865afa15801561310e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906131329190615882565b90508083106131a95760008460a0015113156131a4576000670de0b6b3a7640000856101000151838661316591906158df565b61316f9190615d18565b61317991906156e9565b9050600061318a8660a0015161439e565b9050818111156131a15761319e82826158df565b93505b50505b61324f565b6000670de0b6b3a76400008560e00151670de0b6b3a764000087610100015187866131d491906158df565b6131de9190615d18565b6131e891906156e9565b6131f29190615d18565b6131fc91906156e9565b905060008560a00151126132215760a085015161321a908290614a91565b925061324d565b60006132348660a0015161303990615d2f565b90508082111561324b5761324881836158df565b93505b505b505b8115613287576132848260ff7f0000000000000000000000000000000000000000000000000000000000000006166012614abe565b91505b5092915050565b60008083608001516001600160a01b0316631e01043985602001516040518263ffffffff1660e01b81526004016132c791815260200190565b602060405180830381865afa1580156132e4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133089190615882565b90506000670de0b6b3a76400008560e00151670de0b6b3a7640000876101000151856133349190615d18565b61333e91906156e9565b6133489190615d18565b61335291906156e9565b90506000808660a00151126133785760a0860151613371908390614a91565b9050613396565b6133898660a0015161303990615d2f565b61339390836158df565b90505b848111156133d4576133d160ff7f000000000000000000000000000000000000000000000000000000000000000616601261308f88856158df565b93505b50505092915050565b6001600160a01b0381165b60ff909216600090815260209390935250604090912055565b60008061341060026005612e55565b905061341d600182615d4b565b9050613435600260056001600160801b038416613612565b600083815260046020526040812061344e906001612e55565b905061345b600182615d4b565b60008581526004602052604090209091506134819060016001600160801b038416613612565b60006134ad6001600160801b0383166fffffffffffffffffffffffffffffffff19608086901b166156fd565b95945050505050565b6134be614fba565b6134c9848484612e6d565b9050612e4e8383614af1565b900390565b6000806134e885858561354a565b905060008512801561350457508461350182858761354a565b14155b15611f80576134ad6001826158bf565b60006001600160ff1b0382111561354657604051632a26bd1360e01b8152600481018390526024015b60405180910390fd5b5090565b60008183146135825761355e83600a615e4f565b61356983600a615e4f565b6135739086615e5b565b61357d9190615e8b565b611f80565b509192915050565b606081015161359e90600290600190613612565b60408082015160208084015160009081526004909152919091206135c4916002906133dd565b60a081015160208083015160009081526004909152604090206135e991600390613612565b60c081015160208083015160009081526004918290526040902061360f92909190613612565b50565b806133e8565b600082815260046020526040902081906001600160801b0382169061363e906001612e55565b1461365c576040516302e8145360e61b815260040160405180910390fd5b6125aa600161366b8382615d4b565b600086815260046020526040902091906001600160801b0316613612565b60008061369585611f88565b90925090506136a660028042613612565b6136b36002600385613612565b6136c06002600484613612565b60008581526004602052604090206136da90600586613612565b60008581526004602052604090206136f490600683613612565b600085815260046020526040902061167e90600784613612565b60007f000000000000000000000000cf82aaa68b3868fd2f280f9b211edcdea9f4770d6001600160a01b031663c19d93fb6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561376e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137929190615eb9565b1561379c57600080fd5b60006137c960027f0000000000000000000000000000000000000000000000000000000000000006615ed6565b6137d490600a615eef565b84925090506137ed670de0b6b3a76400006000196156e9565b821080156137ff575060008560a00151125b156138bb577f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388946001600160a01b031685604001516001600160a01b031603613863576138528560a0015161303990615d2f565b61385c90836156fd565b91506138bb565b6064856101000151670de0b6b3a76400006138858860a0015161303990615d2f565b61388f9190615d18565b61389991906156e9565b6138a4906069615d18565b6138ae91906156e9565b6138b890836156fd565b91505b60808501516020860151604051637cbc237360e01b81526001600160a01b0390921691637cbc2373916138fb918690600401918252602082015260400190565b6020604051808303816000875af115801561391a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061393e9190615882565b9150600080600086851115613b4e57600061395988876158df565b905060007f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388946001600160a01b03168a604001516001600160a01b0316036139ad5750806139a681886158df565b9650613b22565b60016001600160a01b03168a604001516001600160a01b031603613a6d576000807f0000000000000000000000009a34b3810d422373ba5128ffee880235003f5cac6001600160a01b031663f29216e2856040518263ffffffff1660e01b8152600401604080518083038185885af1158015613a2d573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190613a52919061589b565b9093508392509050613a64818a6158df565b98505050613b22565b60408a8101519051633e87f49360e01b81526001600160a01b0391821660048201526024810184905260009182917f0000000000000000000000009a34b3810d422373ba5128ffee880235003f5cac90911690633e87f4939060440160408051808303816000875af1158015613ae7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613b0b919061589b565b9093508392509050613b1d818a6158df565b985050505b613b2c81866156fd565b9450613b3781613514565b8a60a001818151613b489190615862565b90525050505b600085118015613b62575060008860a00151125b15613d9f576000613b7a8960a0015161303990615d2f565b905060007f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388946001600160a01b03168a604001516001600160a01b031603613bde57818710613bd5575080613bce81886158df565b9650613d73565b50600095613d73565b85821015613bea578591505b60016001600160a01b03168a604001516001600160a01b031603613cb7576000807f0000000000000000000000009a34b3810d422373ba5128ffee880235003f5cac6001600160a01b0316637661f4f68a866040518363ffffffff1660e01b8152600401613c5a91815260200190565b604080518083038185885af1158015613c77573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190613c9c919061589b565b9093508392509050613cae818a6158df565b98505050613d73565b60408a810151905163bf441a5960e01b81526001600160a01b039182166004820152602481018490526044810189905260009182917f0000000000000000000000009a34b3810d422373ba5128ffee880235003f5cac9091169063bf441a599060640160408051808303816000875af1158015613d38573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613d5c919061589b565b9093508392509050613d6e818a6158df565b985050505b613d7d81866156fd565b9450613d8881613514565b8a60a001818151613d999190615862565b90525050505b60008860a001511315613fa8576000613dc2670de0b6b3a76400006000196156e9565b8810613ddc57613dd58960a0015161439e565b9050613e42565b7f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388946001600160a01b031689604001516001600160a01b0316148015613e2057508786105b15613e4257613e3f613e358a60a0015161439e565b611bf0888b6158df565b90505b8015613fa657600084821115613f595760006001600160a01b037f00000000000000000000000087664d190669a00ce699944c2485326d574ecd0216637cbc237382613e8e89876158df565b6040516001600160e01b031960e085901b168152600481019290925260248201526044016020604051808303816000875af1158015613ed1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613ef59190615882565b9050613f0186846158df565b811015613f43578815613f2a5780613f1987856158df565b613f2391906158df565b9350613f43565b6040516320b7831f60e11b815260040160405180910390fd5b613f4d81876156fd565b91506000955050613f68565b5080613f6581866158df565b94505b613f7281856156fd565b9350613f7d83613514565b613f8682613514565b613f909190615862565b8a60a001818151613fa191906158bf565b905250505b505b821561404257604051631c57762b60e31b815260006004820152602481018490527f00000000000000000000000087664d190669a00ce699944c2485326d574ecd026001600160a01b03169063e2bbb158906044016020604051808303816000875af115801561401c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140409190615882565b505b81156142e35785156140d8577f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388946001600160a01b031688604001516001600160a01b03160361409c5761409582866156fd565b94506142e3565b87516140d3906001600160a01b037f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388941690846143db565b6142e3565b7f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388946001600160a01b031688604001516001600160a01b03160361411f5761409582866156fd565b838210156141765761416c6141637f000000000000000000000000000000000000000000000000000000000000000660ff16601261415c86613514565b919061354a565b60608a01510190565b60608901526142e3565b60016001600160a01b031688604001516001600160a01b03160361423757604051632cb4d9d960e11b8152600481018390526000907f0000000000000000000000009a34b3810d422373ba5128ffee880235003f5cac6001600160a01b031690635969b3b29060240160408051808303816000875af11580156141fd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614221919061589b565b915061422f905081876156fd565b9550506142e3565b604088810151905163045608af60e01b81526001600160a01b039182166004820152602481018490526000917f0000000000000000000000009a34b3810d422373ba5128ffee880235003f5cac169063045608af9060440160408051808303816000875af11580156142ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906142d1919061589b565b91506142df905081876156fd565b9550505b84156143065787516040890151614306916001600160a01b0390911690876143db565b80156143935787516040516340c10f1960e01b81526001600160a01b039182166004820152602481018390527f000000000000000000000000763f02688cc0c78e8c4e852302bfae6e1b33f429909116906340c10f1990604401600060405180830381600087803b15801561437a57600080fd5b505af115801561438e573d6000803e3d6000fd5b505050505b505050509392505050565b60008082121561354657604051632a33bb3160e01b81526004810183905260240161353d565b6000818311156143d45781612e4e565b5090919050565b60006143e684614bf3565b90506000196001600160a01b03851601614474576000836001600160a01b03168360405160006040518083038185875af1925050503d8060008114614447576040519150601f19603f3d011682016040523d82523d6000602084013e61444c565b606091505b505090508061446e5760405163e277d13760e01b815260040160405180910390fd5b50614488565b6144886001600160a01b0385168484614c7a565b600061449385614bf3565b905061449f83826156fd565b821461167e5760405163f4d4667360e01b815260040160405180910390fd5b60405163901b479760e01b815260026004808301919091526024820152604481018390526001600160a01b038216606482015273cbca586bf9706706398164bb5eb8e48f220fe4089063901b479790608401611481565b6133e881614cdd565b806001600160a01b03167f00000000000000000000000067bdd68f20a1bb06f487d29b26ad63e162a2f8286001600160a01b0316636352211e846040518263ffffffff1660e01b815260040161457691815260200190565b602060405180830381865afa158015614593573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906145b79190615845565b6001600160a01b031614612dbc57604051639289a15b60e01b815260040160405180910390fd5b6001600160a01b0381166000908152600360205260408120614601906001612e55565b6001600160a01b03160361360f57604051634033aec960e01b815260040160405180910390fd5b6000196001600160a01b0384160161465a573481146125aa57604051630fc9bd0f60e11b815260040160405180910390fd5b600061466584614bf3565b905061467c6001600160a01b038516843085614cf7565b600061468785614bf3565b905061469383836156fd565b811461167e57604051630fc9bd0f60e11b815260040160405180910390fd5b7f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388946001600160a01b031682604001516001600160a01b0316036147ef576000670de0b6b3a76400006147247f00000000000000000000000000000000000000000000000002c68af0bb14000084615d18565b61472e91906156e9565b905061473a81836158df565b604051631c57762b60e31b815260006004820152602481018390529092507f00000000000000000000000087664d190669a00ce699944c2485326d574ecd026001600160a01b03169063e2bbb158906044016020604051808303816000875af11580156147ab573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906147cf9190615882565b506147d981613514565b8360a0018181516147ea9190615862565b905250505b60016001600160a01b031682604001516001600160a01b0316036148905781608001516001600160a01b031663e2bbb158828460200151846040518463ffffffff1660e01b815260040161484d929190918252602082015260400190565b60206040518083038185885af115801561486b573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906125aa9190615882565b60808201516020830151604051631c57762b60e31b81526001600160a01b039092169163e2bbb158916148d0918590600401918252602082015260400190565b6020604051808303816000875af11580156148ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125aa9190615882565b60007f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388946001600160a01b0316826001600160a01b03160361495d5750670de0b6b3a7640000919050565b6000614971836001600160a01b0316614d2f565b9050614a678160ff167f000000000000000000000000000000000000000000000000000000000000000660ff1661308f7f000000000000000000000000bca4439e99091afb297ecb4c5672357e467664f26001600160a01b03166369843940614a086002600360008c6001600160a01b03166001600160a01b03168152602001908152602001600020614dac90919063ffffffff16565b6040518263ffffffff1660e01b8152600401614a2691815260200190565b602060405180830381865afa158015614a43573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130399190615882565b915081600003614a8a5760405163089112d560e21b815260040160405180910390fd5b505b919050565b6000808212614aab57614aa482846156fd565b90506108b8565b614ab482615d2f565b614aa490846158df565b600081831461358257614ad283600a615e4f565b614add83600a615e4f565b614ae79086615d18565b61357d91906156e9565b6000828152600460205260408120614b0a906002612e55565b90506001600160a01b03811615801590614b365750816001600160a01b0316816001600160a01b031614155b156125aa576001600160a01b0381166000908152600360205260408120614b5e906001612e55565b6001600160a01b0316635d988967856040518263ffffffff1660e01b8152600401614b8b91815260200190565b602060405180830381865afa158015614ba8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614bcc9190615882565b90508015614bed57604051634033aec960e01b815260040160405180910390fd5b50505050565b60006001600160a01b038216600114614c73576040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401602060405180830381865afa158015614c4a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614c6e9190615882565b6108b8565b4792915050565b6040516001600160a01b0383166024820152604481018290526125aa90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152614dc2565b600081614ceb576000614cee565b60015b60ff1692915050565b6040516001600160a01b0380851660248301528316604482015260648101829052614bed9085906323b872dd60e01b90608401614ca6565b60006001600160a01b038216600114614da457816001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015614d80573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614c6e9190615efe565b601292915050565b60ff166000908152602091909152604090205490565b6000614e17826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316614e979092919063ffffffff16565b9050805160001480614e38575080806020019051810190614e389190615eb9565b6125aa5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161353d565b6060611f80848460008585600080866001600160a01b03168587604051614ebe9190615f21565b60006040518083038185875af1925050503d8060008114614efb576040519150601f19603f3d011682016040523d82523d6000602084013e614f00565b606091505b5091509150614f1187838387614f1c565b979650505050505050565b60608315614f8b578251600003614f84576001600160a01b0385163b614f845760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161353d565b5081611f80565b611f808383815115614fa05781518083602001fd5b8060405162461bcd60e51b815260040161353d9190615f3d565b60405180610120016040528060006001600160a01b031681526020016000815260200160006001600160a01b031681526020016000815260200160006001600160a01b03168152602001600081526020016000815260200160008152602001600081525090565b6001600160a01b038116811461360f57600080fd5b60006020828403121561504857600080fd5b8135612e4e81615021565b60008060006060848603121561506857600080fd5b83359250602084013561507a81615021565b929592945050506040919091013590565b60008083601f84011261509d57600080fd5b5081356001600160401b038111156150b457600080fd5b6020830191508360208260051b85010111156150cf57600080fd5b9250929050565b801515811461360f57600080fd5b600080600080600080600060c0888a0312156150ff57600080fd5b87359650602088013561511181615021565b9550604088013594506060880135935060808801356001600160401b0381111561513a57600080fd5b6151468a828b0161508b565b90945092505060a088013561515a816150d6565b8091505092959891949750929550565b634e487b7160e01b600052604160045260246000fd5b60405161014081016001600160401b03811182821017156151a3576151a361516a565b60405290565b60405161010081016001600160401b03811182821017156151a3576151a361516a565b604051601f8201601f191681016001600160401b03811182821017156151f4576151f461516a565b604052919050565b600082601f83011261520d57600080fd5b81356001600160401b038111156152265761522661516a565b615239601f8201601f19166020016151cc565b81815284602083860101111561524e57600080fd5b816020850160208301376000918101602001919091529392505050565b6000806040838503121561527e57600080fd5b82356001600160401b038082111561529557600080fd5b6152a1868387016151fc565b935060208501359150808211156152b757600080fd5b506152c4858286016151fc565b9150509250929050565b600080604083850312156152e157600080fd5b8235915060208301356152f3816150d6565b809150509250929050565b60006020828403121561531057600080fd5b5035919050565b8151815260208083015161014083019161533b908401826001600160a01b03169052565b5060408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015260e083015160e083015261010080840151818401525061012080840151818401525092915050565b600080600080608085870312156153ac57600080fd5b8435935060208501356153be81615021565b92506040850135915060608501356153d5816150d6565b939692955090935050565b600080604083850312156153f357600080fd5b50508035926020909101359150565b81516001600160a01b031681526101e08101602083015161542e60208401826001600160a01b03169052565b50604083015161544960408401826001600160a01b03169052565b50606083015161546460608401826001600160a01b03169052565b50608083015161547f60808401826001600160a01b03169052565b5060a083015161549a60a08401826001600160a01b03169052565b5060c08301516154b560c08401826001600160a01b03169052565b5060e08301516154d060e08401826001600160a01b03169052565b506101008381015190830152610120808401519083015261014080840151908301526101608084015190830152610180808401516001600160a01b0381168285015250506101a0838101516001600160a01b0381168483015250506101c0838101516001600160a01b038116848301525b505092915050565b60008060008060008060a0878903121561556257600080fd5b86359550602087013561557481615021565b9450604087013593506060870135925060808701356001600160401b0381111561559d57600080fd5b6155a989828a0161508b565b979a9699509497509295939492505050565b6020808252825182820181905260009190848201906040850190845b818110156155f3578351835292840192918401916001016155d7565b50909695505050505050565b6000806000806060858703121561561557600080fd5b843593506020850135925060408501356001600160401b0381111561563957600080fd5b6156458782880161508b565b95989497509550505050565b8051614a8c81615021565b60006060828403121561566e57600080fd5b604051606081018181106001600160401b03821117156156905761569061516a565b604052825161569e81615021565b8152602083810151908201526040928301519281019290925250919050565b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000826156f8576156f86156bd565b500490565b808201808211156108b8576108b86156d3565b60005b8381101561572b578181015183820152602001615713565b50506000910152565b6000815180845261574c816020860160208601615710565b601f01601f19169290920160200192915050565b6080815260006157736080830187615734565b82810360208401526157858187615734565b604084019590955250506001600160a01b039190911660609091015292915050565b600060e082840312156157b957600080fd5b60405160e081018181106001600160401b03821117156157db576157db61516a565b60405282516157e981615021565b815260208301516157f981615021565b6020820152604083015161580c81615021565b80604083015250606083015160608201526080830151608082015260a083015160a082015260c083015160c08201528091505092915050565b60006020828403121561585757600080fd5b8151612e4e81615021565b8082018281126000831280158216821582161715615541576155416156d3565b60006020828403121561589457600080fd5b5051919050565b600080604083850312156158ae57600080fd5b505080516020909101519092909150565b8181036000831280158383131683831282161715613287576132876156d3565b818103818111156108b8576108b86156d3565b6000610140828403121561590557600080fd5b61590d615180565b8251815261591d60208401615651565b602082015260408301516040820152606083015160608201526080830151608082015260a083015160a082015260c083015160c082015260e083015160e08201526101008084015181830152506101208084015181830152508091505092915050565b600060c0828403121561599257600080fd5b60405160c081018181106001600160401b03821117156159b4576159b461516a565b8060405250825181526020830151602082015260408301516040820152606083015160608201526080830151608082015260a083015160a08201528091505092915050565b600060a08284031215615a0b57600080fd5b60405160a081018181106001600160401b0382111715615a2d57615a2d61516a565b806040525082518152602083015160208201526040830151604082015260608301516060820152608083015160808201528091505092915050565b600060408284031215615a7a57600080fd5b604051604081018181106001600160401b0382111715615a9c57615a9c61516a565b604052825181526020928301519281019290925250919050565b60006101008284031215615ac957600080fd5b615ad16151a9565b825181526020830151602082015260408301516040820152606083015160608201526080830151608082015260a083015160a082015260c083015160c082015260e083015160e08201528091505092915050565b8183526000602080850194508260005b85811015615b5157813587529582019590820190600101615b35565b509495945050505050565b60006101008b83528a60208401528960408401528860608401528760808401528660a08401528560c08401528060e0840152615b9b8184018587615b25565b9c9b505050505050505050505050565b60006020808385031215615bbe57600080fd5b82516001600160401b0380821115615bd557600080fd5b818501915085601f830112615be957600080fd5b815181811115615bfb57615bfb61516a565b8060051b9150615c0c8483016151cc565b8181529183018401918481019088841115615c2657600080fd5b938501935b83851015615c4457845182529385019390850190615c2b565b98975050505050505050565b60006101008284031215615c6357600080fd5b615c6b6151a9565b825181526020830151615c7d81615021565b8060208301525060408301516040820152606083015160608201526080830151608082015260a0830151615cb0816150d6565b60a082015260c0838101519082015260e0928301519281019290925250919050565b8881528760208201528660408201528560608201528460808201528360a082015260e060c08201526000615d0a60e083018486615b25565b9a9950505050505050505050565b80820281158282048414176108b8576108b86156d3565b6000600160ff1b8201615d4457615d446156d3565b5060000390565b6001600160801b03818116838216019080821115613287576132876156d3565b600181815b80851115615da6578160001904821115615d8c57615d8c6156d3565b80851615615d9957918102915b93841c9390800290615d70565b509250929050565b600082615dbd575060016108b8565b81615dca575060006108b8565b8160018114615de05760028114615dea57615e06565b60019150506108b8565b60ff841115615dfb57615dfb6156d3565b50506001821b6108b8565b5060208310610133831016604e8410600b8410161715615e29575081810a6108b8565b615e338383615d6b565b8060001904821115615e4757615e476156d3565b029392505050565b6000612e4e8383615dae565b80820260008212600160ff1b84141615615e7757615e776156d3565b81810583148215176108b8576108b86156d3565b600082615e9a57615e9a6156bd565b600160ff1b821460001984141615615eb457615eb46156d3565b500590565b600060208284031215615ecb57600080fd5b8151612e4e816150d6565b60ff82811682821603908111156108b8576108b86156d3565b6000612e4e60ff841683615dae565b600060208284031215615f1057600080fd5b815160ff81168114612e4e57600080fd5b60008251615f33818460208701615710565b9190910192915050565b602081526000612e4e602083018461573456fea2646970667358221220e2baf2c8c978e5b836b2d7d23da6f71327c5bd537b9b46c1a3da24f0c128d04b64736f6c63430008140033

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

00000000000000000000000060138081198b75aaf15aca3a17ec7f5ffc5d460500000000000000000000000067bdd68f20a1bb06f487d29b26ad63e162a2f828000000000000000000000000bca4439e99091afb297ecb4c5672357e467664f20000000000000000000000009a34b3810d422373ba5128ffee880235003f5cac00000000000000000000000087664d190669a00ce699944c2485326d574ecd02000000000000000000000000763f02688cc0c78e8c4e852302bfae6e1b33f42900000000000000000000000029219dd400f2bf60e5a23d13be72b486d4038894000000000000000000000000a51cd97f3090f6a16cf0cdc12b0cb4b0a95b38ea00000000000000000000000000000000000000000000000002c68af0bb14000000000000000000000000000000000000000000000000000006f05b59d3b200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001dcd6500000000000000000000000000b5284ed1606b91e0129182d55ee7ee31c31c920c000000000000000000000000d4e08c940ddec162c2d8f3034c75c3e08f1f6032000000000000000000000000cf82aaa68b3868fd2f280f9b211edcdea9f4770d

-----Decoded View---------------
Arg [0] : p (tuple): System.Collections.Generic.List`1[Nethereum.ABI.FunctionEncoding.ParameterOutput]

-----Encoded View---------------
15 Constructor Arguments found :
Arg [0] : 00000000000000000000000060138081198b75aaf15aca3a17ec7f5ffc5d4605
Arg [1] : 00000000000000000000000067bdd68f20a1bb06f487d29b26ad63e162a2f828
Arg [2] : 000000000000000000000000bca4439e99091afb297ecb4c5672357e467664f2
Arg [3] : 0000000000000000000000009a34b3810d422373ba5128ffee880235003f5cac
Arg [4] : 00000000000000000000000087664d190669a00ce699944c2485326d574ecd02
Arg [5] : 000000000000000000000000763f02688cc0c78e8c4e852302bfae6e1b33f429
Arg [6] : 00000000000000000000000029219dd400f2bf60e5a23d13be72b486d4038894
Arg [7] : 000000000000000000000000a51cd97f3090f6a16cf0cdc12b0cb4b0a95b38ea
Arg [8] : 00000000000000000000000000000000000000000000000002c68af0bb140000
Arg [9] : 00000000000000000000000000000000000000000000000006f05b59d3b20000
Arg [10] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [11] : 000000000000000000000000000000000000000000000000000000001dcd6500
Arg [12] : 000000000000000000000000b5284ed1606b91e0129182d55ee7ee31c31c920c
Arg [13] : 000000000000000000000000d4e08c940ddec162c2d8f3034c75c3e08f1f6032
Arg [14] : 000000000000000000000000cf82aaa68b3868fd2f280f9b211edcdea9f4770d


Block Transaction Gas Used Reward
view all blocks ##produced##

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

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits

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.