ETH Price: $1,812.94 (+10.31%)
Gas: 0.15 GWei

Contract

0x55746AC5C15bE05B43D7392f0E3867E86f573A66

Overview

ETH Balance

Linea Mainnet LogoLinea Mainnet LogoLinea Mainnet Logo0 ETH

ETH Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

> 10 Internal Transactions found.

Latest 25 internal transactions (View All)

Parent Transaction Hash Block From To
13188242023-12-22 5:26:25488 days ago1703222785
0x55746AC5...86f573A66
0 ETH
13188232023-12-22 5:26:19488 days ago1703222779
0x55746AC5...86f573A66
0.004 ETH
13187502023-12-22 5:19:01488 days ago1703222341
0x55746AC5...86f573A66
0 ETH
13187492023-12-22 5:18:55488 days ago1703222335
0x55746AC5...86f573A66
0.004 ETH
13182662023-12-22 4:30:37488 days ago1703219437
0x55746AC5...86f573A66
0 ETH
13182652023-12-22 4:30:31488 days ago1703219431
0x55746AC5...86f573A66
0.004 ETH
13182382023-12-22 4:27:49488 days ago1703219269
0x55746AC5...86f573A66
0 ETH
13182372023-12-22 4:27:43488 days ago1703219263
0x55746AC5...86f573A66
0.004 ETH
13182152023-12-22 4:25:31488 days ago1703219131
0x55746AC5...86f573A66
0 ETH
13182142023-12-22 4:25:25488 days ago1703219125
0x55746AC5...86f573A66
0.004 ETH
13182142023-12-22 4:25:25488 days ago1703219125
0x55746AC5...86f573A66
0 ETH
13182132023-12-22 4:25:19488 days ago1703219119
0x55746AC5...86f573A66
0.004 ETH
13182132023-12-22 4:25:19488 days ago1703219119
0x55746AC5...86f573A66
0 ETH
13182122023-12-22 4:25:13488 days ago1703219113
0x55746AC5...86f573A66
0.004 ETH
13182112023-12-22 4:25:07488 days ago1703219107
0x55746AC5...86f573A66
0 ETH
13182102023-12-22 4:25:01488 days ago1703219101
0x55746AC5...86f573A66
0.004 ETH
13181832023-12-22 4:22:19488 days ago1703218939
0x55746AC5...86f573A66
0 ETH
13181822023-12-22 4:22:13488 days ago1703218933
0x55746AC5...86f573A66
0.004 ETH
13181762023-12-22 4:21:37488 days ago1703218897
0x55746AC5...86f573A66
0 ETH
13181752023-12-22 4:21:31488 days ago1703218891
0x55746AC5...86f573A66
0.004 ETH
13181622023-12-22 4:20:13488 days ago1703218813
0x55746AC5...86f573A66
0 ETH
13181612023-12-22 4:20:07488 days ago1703218807
0x55746AC5...86f573A66
0.004 ETH
13181592023-12-22 4:19:55488 days ago1703218795
0x55746AC5...86f573A66
0 ETH
13181582023-12-22 4:19:49488 days ago1703218789
0x55746AC5...86f573A66
0.0039 ETH
13181582023-12-22 4:19:49488 days ago1703218789
0x55746AC5...86f573A66
0 ETH
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
GatewayImplementationGasSplit

Compiler Version
v0.8.20+commit.a1b79de6

Optimization Enabled:
Yes with 200 runs

Other Settings:
paris EvmVersion
File 1 of 32 : GatewayImplementationGasSplit.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 '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
import '../../library/Bytes32Map.sol';
import '../../library/ETHAndERC20.sol';
import '../../library/SafeMath.sol';
import './GatewayStorage.sol';

contract GatewayImplementationGasSplit is GatewayStorage {

    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 InvalidBAmount();
    error InvalidBPrice();
    error InvalidCustodian();
    error InvalidLTokenId();
    error InvalidPTokenId();
    error InvalidRequestId();
    error InsufficientMargin();
    error InvalidSignature();
    error InsufficientB0();
    error InsufficientGasFee();

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

    event DelBToken(address bToken);

    event UpdateBToken(address bToken);

    event SetGasFee(uint256 actionId, uint256 gasFee);

    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
    );

    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_DCHAINGASFEEPERREQUEST     = 6; // dChain gas fee for executing request on dChain
    uint8 constant S_TOTALICHAINGASFEE          = 7; // Total iChain gas fee paid by all requests

    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_LASTREQUESTICHAINGASFEE        = 9;  // User last request's iChain gas fee
    uint8 constant D_CUMULATIVEUNUSEDICHAINGASFEE   = 10; // User cumulaitve iChain gas fee for requests cannot be finished, users can claim back

    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;

    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;

    constructor (
        address lToken_,
        address pToken_,
        address oracle_,
        address swapper_,
        address vault0_,
        address iou_,
        address tokenB0_,
        address dChainEventSigner_,
        uint256 b0ReserveRatio_,
        int256  liquidationRewardCutRatio_,
        int256  minLiquidationReward_,
        int256  maxLiquidationReward_
    ) {
        lToken = IDToken(lToken_);
        pToken = IDToken(pToken_);
        oracle = IOracle(oracle_);
        swapper = ISwapper(swapper_);
        vault0 = IVault(vault0_);
        iou = IIOU(iou_);
        tokenB0 = tokenB0_;
        decimalsB0 = tokenB0_.decimals();
        dChainEventSigner = dChainEventSigner_;
        b0ReserveRatio = b0ReserveRatio_;
        liquidationRewardCutRatio = liquidationRewardCutRatio_;
        minLiquidationReward = minLiquidationReward_;
        maxLiquidationReward = maxLiquidationReward_;
    }

    //================================================================================
    // 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;
    }

    function getGatewayState() external view returns (IGateway.GatewayState memory s) {
        s.cumulativePnlOnGateway = _gatewayStates.getInt(S_CUMULATIVEPNLONGATEWAY);
        s.liquidityTime = _gatewayStates.getUint(S_LIQUIDITYTIME);
        s.totalLiquidity = _gatewayStates.getUint(S_TOTALLIQUIDITY);
        s.cumulativeTimePerLiquidity = _gatewayStates.getInt(S_CUMULATIVETIMEPERLIQUIDITY);
        s.gatewayRequestId = _gatewayStates.getUint(S_GATEWAYREQUESTID);
        s.dChainGasFeePerRequest = _gatewayStates.getUint(S_DCHAINGASFEEPERREQUEST);
        s.totalIChainGasFee = _gatewayStates.getUint(S_TOTALICHAINGASFEE);
    }

    function getBTokenState(address bToken) external view returns (IGateway.BTokenState memory s) {
        s.vault = _bTokenStates[bToken].getAddress(B_VAULT);
        s.oracleId = _bTokenStates[bToken].getBytes32(B_ORACLEID);
        s.collateralFactor = _bTokenStates[bToken].getUint(B_COLLATERALFACTOR);
    }

    function getLpState(uint256 lTokenId) external view returns (IGateway.LpState memory s) {
        s.requestId = _dTokenStates[lTokenId].getUint(D_REQUESTID);
        s.bToken = _dTokenStates[lTokenId].getAddress(D_BTOKEN);
        s.bAmount = IVault(_bTokenStates[s.bToken].getAddress(B_VAULT)).getBalance(lTokenId);
        s.b0Amount = _dTokenStates[lTokenId].getInt(D_B0AMOUNT);
        s.lastCumulativePnlOnEngine = _dTokenStates[lTokenId].getInt(D_LASTCUMULATIVEPNLONENGINE);
        s.liquidity = _dTokenStates[lTokenId].getUint(D_LIQUIDITY);
        s.cumulativeTime = _dTokenStates[lTokenId].getUint(D_CUMULATIVETIME);
        s.lastCumulativeTimePerLiquidity = _dTokenStates[lTokenId].getUint(D_LASTCUMULATIVETIMEPERLIQUIDITY);
        s.lastRequestIChainGasFee = _dTokenStates[lTokenId].getUint(D_LASTREQUESTICHAINGASFEE);
        s.cumulativeUnusedIChainGasFee = _dTokenStates[lTokenId].getUint(D_CUMULATIVEUNUSEDICHAINGASFEE);
    }

    function getTdState(uint256 pTokenId) external view returns (IGateway.TdState memory s) {
        s.requestId = _dTokenStates[pTokenId].getUint(D_REQUESTID);
        s.bToken = _dTokenStates[pTokenId].getAddress(D_BTOKEN);
        s.bAmount = IVault(_bTokenStates[s.bToken].getAddress(B_VAULT)).getBalance(pTokenId);
        s.b0Amount = _dTokenStates[pTokenId].getInt(D_B0AMOUNT);
        s.lastCumulativePnlOnEngine = _dTokenStates[pTokenId].getInt(D_LASTCUMULATIVEPNLONENGINE);
        s.singlePosition = _dTokenStates[pTokenId].getBool(D_SINGLEPOSITION);
        s.lastRequestIChainGasFee = _dTokenStates[pTokenId].getUint(D_LASTREQUESTICHAINGASFEE);
        s.cumulativeUnusedIChainGasFee = _dTokenStates[pTokenId].getUint(D_CUMULATIVEUNUSEDICHAINGASFEE);
    }

    // @notice Calculate Lp's cumulative time, used in liquidity mining reward distributions
    function getCumulativeTime(uint256 lTokenId)
    public view returns (uint256 cumulativeTimePerLiquidity, uint256 cumulativeTime)
    {
        uint256 liquidityTime = _gatewayStates.getUint(S_LIQUIDITYTIME);
        uint256 totalLiquidity = _gatewayStates.getUint(S_TOTALLIQUIDITY);
        cumulativeTimePerLiquidity = _gatewayStates.getUint(S_CUMULATIVETIMEPERLIQUIDITY);
        uint256 liquidity = _dTokenStates[lTokenId].getUint(D_LIQUIDITY);
        cumulativeTime = _dTokenStates[lTokenId].getUint(D_CUMULATIVETIME);
        uint256 lastCumulativeTimePerLiquidity = _dTokenStates[lTokenId].getUint(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 getGasFees() public view returns (uint256[] memory fees) {
        fees = new uint256[](5);
        fees[0] = _gasFees[ACTION_REQUESTADDLIQUIDITY];
        fees[1] = _gasFees[ACTION_REQUESTREMOVELIQUIDITY];
        fees[2] = _gasFees[ACTION_REQUESTREMOVEMARGIN];
        fees[3] = _gasFees[ACTION_REQUESTTRADE];
        fees[4] = _gasFees[ACTION_REQUESTTRADEANDREMOVEMARGIN];
    }

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

    // function addBToken(
    //     address bToken,
    //     address vault,
    //     bytes32 oracleId,
    //     uint256 collateralFactor
    // ) external _onlyAdmin_ {
    //     if (_bTokenStates[bToken].getAddress(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(B_VAULT, vault);
    //     _bTokenStates[bToken].set(B_ORACLEID, oracleId);
    //     _bTokenStates[bToken].set(B_COLLATERALFACTOR, collateralFactor);

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

    // function delBToken(address bToken) external _onlyAdmin_ {
    //     // bToken can only be deleted when there is no deposits
    //     if (IVault(_bTokenStates[bToken].getAddress(B_VAULT)).stTotalAmount() != 0) {
    //         revert CannotDelBToken();
    //     }

    //     _bTokenStates[bToken].del(B_VAULT);
    //     _bTokenStates[bToken].del(B_ORACLEID);
    //     _bTokenStates[bToken].del(B_COLLATERALFACTOR);

    //     emit DelBToken(bToken);
    // }

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

    // @notice Set gas fee for actionId
    function setGasFee(uint256 actionId, uint256 gasFee) external _onlyAdmin_ {
        _gasFees[actionId] = gasFee;
        emit SetGasFee(actionId, gasFee);
    }

    function setDChainGasFeePerRequest(uint256 dChainGasFeePerRequest) external _onlyAdmin_ {
        _gatewayStates.set(S_DCHAINGASFEEPERREQUEST, dChainGasFeePerRequest);
    }

    // @notic Claim dChain gasFee to account `to`
    function claimDChainGasFee(address to) external _onlyAdmin_ {
        tokenETH.transferOut(to, tokenETH.balanceOfThis() - _gatewayStates.getUint(S_TOTALICHAINGASFEE));
    }

    // @notice Claim unused iChain gas fee for dTokenId
    function claimUnusedIChainGasFee(uint256 dTokenId, bool isLp) external {
        address owner = isLp ? lToken.ownerOf(dTokenId) : pToken.ownerOf(dTokenId);
        uint256 cumulativeUnusedIChainGasFee = _dTokenStates[dTokenId].getUint(D_CUMULATIVEUNUSEDICHAINGASFEE);
        if (cumulativeUnusedIChainGasFee > 0) {
            uint256 totalIChainGasFee = _gatewayStates.getUint(S_TOTALICHAINGASFEE);
            totalIChainGasFee -= cumulativeUnusedIChainGasFee;
            _gatewayStates.set(S_TOTALICHAINGASFEE, totalIChainGasFee);

            _dTokenStates[dTokenId].del(D_CUMULATIVEUNUSEDICHAINGASFEE);

            tokenETH.transferOut(owner, cumulativeUnusedIChainGasFee);
        }
    }

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

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

    /**
     * @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 = _getData(msg.sender, lTokenId, bToken);

        uint256 ethAmount = _receiveGasFee(lTokenId, _gasFees[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);

        _receiveGasFee(lTokenId, _gasFees[ACTION_REQUESTREMOVELIQUIDITY]);
        if (bAmount == 0) {
            revert InvalidBAmount();
        }

        Data memory data = _getData(msg.sender, lTokenId, bToken);
        _getExParams(data);
        uint256 oldLiquidity = _getDTokenLiquidity(data);
        uint256 newLiquidity = _getDTokenLiquidityWithRemove(data, bAmount);
        if (newLiquidity <= oldLiquidity / 100) {
            newLiquidity = 0;
        }

        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(D_SINGLEPOSITION, true);
            }
        } else {
            _checkPTokenIdOwner(pTokenId, msg.sender);
        }
        _checkBTokenInitialized(bToken);

        Data memory data = _getData(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;
    }

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

        _receiveGasFee(pTokenId, _gasFees[ACTION_REQUESTREMOVEMARGIN]);
        if (bAmount == 0) {
            revert InvalidBAmount();
        }

        Data memory data = _getData(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);

        _receiveGasFee(pTokenId, _gasFees[ACTION_REQUESTTRADE]);

        Data memory data = _getData(msg.sender, pTokenId, _dTokenStates[pTokenId].getAddress(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 = _getData(pToken.ownerOf(pTokenId), pTokenId, _dTokenStates[pTokenId].getAddress(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 gasFee = _gasFees[ACTION_REQUESTTRADE];
            if (bAmount + gasFee > msg.value) { // revert if bAmount > msg.value - gasFee
                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);

        _receiveGasFee(pTokenId, _gasFees[ACTION_REQUESTTRADEANDREMOVEMARGIN]);
        if (bAmount == 0) {
            revert InvalidBAmount();
        }

        Data memory data = _getData(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_ {
        _verifyEventData(eventData, signature);
        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 = _getData(lToken.ownerOf(v.lTokenId), v.lTokenId, _dTokenStates[v.lTokenId].getAddress(D_BTOKEN));
        int256 diff = v.cumulativePnlOnEngine.minusUnchecked(data.lastCumulativePnlOnEngine);
        data.b0Amount += diff.rescaleDown(18, decimalsB0);
        data.lastCumulativePnlOnEngine = v.cumulativePnlOnEngine;

        uint256 bAmountRemoved;
        if (v.bAmountToRemove != 0) {
            _getExParams(data);
            bAmountRemoved = _transferOut(data, v.liquidity == 0 ? type(uint256).max : v.bAmountToRemove, false);
        }

        _saveData(data);

        _transferLastRequestIChainGasFee(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,
                data.bToken,
                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_ {
        _verifyEventData(eventData, signature);
        IGateway.VarOnExecuteRemoveMargin memory v = abi.decode(eventData, (IGateway.VarOnExecuteRemoveMargin));
        _checkRequestId(v.pTokenId, v.requestId);

        // Cumulate unsettled PNL to b0Amount
        Data memory data = _getData(pToken.ownerOf(v.pTokenId), v.pTokenId, _dTokenStates[v.pTokenId].getAddress(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);

        _transferLastRequestIChainGasFee(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_ {
        _verifyEventData(eventData, signature);
        IGateway.VarOnExecuteLiquidate memory v = abi.decode(eventData, (IGateway.VarOnExecuteLiquidate));

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

        uint256 b0AmountIn;

        // Redeem all bToken from vault and swap into B0
        {
            uint256 bAmount = IVault(data.vault).redeem(data.dTokenId, type(uint256).max);
            if (data.bToken == tokenB0) {
                b0AmountIn += bAmount;
            } else if (data.bToken == tokenETH) {
                (uint256 resultB0, ) = swapper.swapExactETHForB0{value:bAmount}();
                b0AmountIn += resultB0;
            } else {
                (uint256 resultB0, ) = swapper.swapExactBXForB0(data.bToken, bAmount);
                b0AmountIn += resultB0;
            }
        }

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

        // Calculate liquidator's reward
        {
            if (lpPnl <= minLiquidationReward) {
                reward = minLiquidationReward;
            } else {
                reward = SafeMath.min(
                    (lpPnl - minLiquidationReward) * liquidationRewardCutRatio / ONE + minLiquidationReward,
                    maxLiquidationReward
                );
            }

            uint256 uReward = reward.itou();
            if (uReward <= b0AmountIn) {
                tokenB0.transferOut(msg.sender, uReward);
                b0AmountIn -= uReward;
            } else {
                uint256 b0Redeemed = vault0.redeem(uint256(0), uReward - b0AmountIn);
                tokenB0.transferOut(msg.sender, b0AmountIn + b0Redeemed);
                reward = (b0AmountIn + b0Redeemed).utoi();
                b0AmountIn = 0;
            }

            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 lastRequestIChainGasFee = _dTokenStates[v.pTokenId].getUint(D_LASTREQUESTICHAINGASFEE);
            uint256 cumulativeUnusedIChainGasFee = _dTokenStates[v.pTokenId].getUint(D_CUMULATIVEUNUSEDICHAINGASFEE);
            _dTokenStates[v.pTokenId].del(D_LASTREQUESTICHAINGASFEE);
            _dTokenStates[v.pTokenId].del(D_CUMULATIVEUNUSEDICHAINGASFEE);

            uint256 totalIChainGasFee = _gatewayStates.getUint(S_TOTALICHAINGASFEE);
            totalIChainGasFee -= lastRequestIChainGasFee + cumulativeUnusedIChainGasFee;
            _gatewayStates.set(S_TOTALICHAINGASFEE, totalIChainGasFee);
        }

        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(S_CUMULATIVEPNLONGATEWAY);
        data.vault = _bTokenStates[bToken].getAddress(B_VAULT);

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

        _checkBTokenConsistency(dTokenId, bToken);
    }

    function _saveData(Data memory data) internal {
        _gatewayStates.set(S_CUMULATIVEPNLONGATEWAY, data.cumulativePnlOnGateway);
        _dTokenStates[data.dTokenId].set(D_BTOKEN, data.bToken);
        _dTokenStates[data.dTokenId].set(D_B0AMOUNT, data.b0Amount);
        _dTokenStates[data.dTokenId].set(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(D_REQUESTID) != uint256(userRequestId)) {
            revert InvalidRequestId();
        } else {
            // increment requestId so that callback can only be executed once
            _dTokenStates[dTokenId].set(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(S_GATEWAYREQUESTID));
        gatewayRequestId += 1;
        _gatewayStates.set(S_GATEWAYREQUESTID, uint256(gatewayRequestId));

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

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

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

    function _checkBTokenConsistency(uint256 dTokenId, address bToken) internal view {
        address preBToken = _dTokenStates[dTokenId].getAddress(D_BTOKEN);
        if (preBToken != address(0) && preBToken != bToken) {
            uint256 stAmount = IVault(_bTokenStates[preBToken].getAddress(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 _receiveGasFee(uint256 dTokenId, uint256 gasFee) internal returns (uint256) {
        uint256 dChainGasFee = _gatewayStates.getUint(S_DCHAINGASFEEPERREQUEST);
        if (msg.value < gasFee) {
            revert InsufficientGasFee();
        }
        uint256 iChainGasFee = gasFee - dChainGasFee;

        uint256 totalIChainGasFee = _gatewayStates.getUint(S_TOTALICHAINGASFEE) + iChainGasFee;
        _gatewayStates.set(S_TOTALICHAINGASFEE,  totalIChainGasFee);

        uint256 lastRequestIChainGasFee = _dTokenStates[dTokenId].getUint(D_LASTREQUESTICHAINGASFEE);
        uint256 cumulativeUnusedIChainGasFee = _dTokenStates[dTokenId].getUint(D_CUMULATIVEUNUSEDICHAINGASFEE);
        cumulativeUnusedIChainGasFee += lastRequestIChainGasFee;
        lastRequestIChainGasFee = iChainGasFee;
        _dTokenStates[dTokenId].set(D_LASTREQUESTICHAINGASFEE, lastRequestIChainGasFee);
        _dTokenStates[dTokenId].set(D_CUMULATIVEUNUSEDICHAINGASFEE, cumulativeUnusedIChainGasFee);

        return msg.value - gasFee;
    }

    function _transferLastRequestIChainGasFee(uint256 dTokenId, address to) internal {
        uint256 lastRequestIChainGasFee = _dTokenStates[dTokenId].getUint(D_LASTREQUESTICHAINGASFEE);

        if (lastRequestIChainGasFee > 0) {
            uint256 totalIChainGasFee = _gatewayStates.getUint(S_TOTALICHAINGASFEE);
            totalIChainGasFee -= lastRequestIChainGasFee;
            _gatewayStates.set(S_TOTALICHAINGASFEE, totalIChainGasFee);

            _dTokenStates[dTokenId].del(D_LASTREQUESTICHAINGASFEE);

            tokenETH.transferOut(to, lastRequestIChainGasFee);
        }
    }

    // @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(B_ORACLEID)).itou().rescale(decimalsB, decimalsB0);
            if (bPrice == 0) {
                revert InvalidBPrice();
            }
        }
    }

    function _getExParams(Data memory data) internal view {
        data.collateralFactor = _bTokenStates[data.bToken].getUint(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).rescale(decimalsB0, 18);
                    }
                }
            } else {
                uint256 b0Excessive = (bAmountInVault - bAmount) * data.bPrice / UONE * data.collateralFactor / UONE; // discounted
                if (data.b0Amount >= 0) {
                    liquidity = b0Excessive.add(data.b0Amount).rescale(decimalsB0, 18);
                } else {
                    uint256 b0Shortage = (-data.b0Amount).itou();
                    if (b0Excessive > b0Shortage) {
                        liquidity = (b0Excessive - b0Shortage).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) {
        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 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 (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(S_LIQUIDITYTIME, block.timestamp);
        _gatewayStates.set(S_TOTALLIQUIDITY, newTotalLiquidity);
        _gatewayStates.set(S_CUMULATIVETIMEPERLIQUIDITY, cumulativeTimePerLiquidity);
        _dTokenStates[lTokenId].set(D_LIQUIDITY, newLiquidity);
        _dTokenStates[lTokenId].set(D_CUMULATIVETIME, cumulativeTime);
        _dTokenStates[lTokenId].set(D_LASTCUMULATIVETIMEPERLIQUIDITY, cumulativeTimePerLiquidity);
    }

    function _verifyEventData(bytes memory eventData, bytes memory signature) internal view {
        bytes32 hash = ECDSA.toEthSignedMessageHash(keccak256(eventData));
        if (ECDSA.recover(hash, signature) != dChainEventSigner) {
            revert InvalidSignature();
        }
    }

}

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.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

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

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

File 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.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

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

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

    /**
     * @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. Compatible with tokens that require the approval to be set to
     * 0 before setting it to a non-zero value.
     */
    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 : 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 => gasFee
    mapping(uint256 => uint256) internal _gasFees;

}

File 14 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;
    }

    struct GatewayState {
        int256  cumulativePnlOnGateway;
        uint256 liquidityTime;
        uint256 totalLiquidity;
        int256  cumulativeTimePerLiquidity;
        uint256 gatewayRequestId;
        uint256 dChainGasFeePerRequest;
        uint256 totalIChainGasFee;
    }

    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 lastRequestIChainGasFee;
        uint256 cumulativeUnusedIChainGasFee;
    }

    struct TdState {
        uint256 requestId;
        address bToken;
        uint256 bAmount;
        int256  b0Amount;
        int256  lastCumulativePnlOnEngine;
        bool    singlePosition;
        uint256 lastRequestIChainGasFee;
        uint256 cumulativeUnusedIChainGasFee;
    }

    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 {
        uint256 requestId;
        uint256 pTokenId;
        int256  cumulativePnlOnEngine;
    }

    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 15 of 32 : ISwapper.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

import './IUniswapV2Factory.sol';
import './IUniswapV2Router02.sol';
import '../../oracle/IOracle.sol';

interface ISwapper {

    function factory() external view returns (IUniswapV2Factory);

    function router() external view returns (IUniswapV2Router02);

    function oracle() external view returns (IOracle);

    function tokenB0() external view returns (address);

    function tokenWETH() external view returns (address);

    function maxSlippageRatio() external view returns (uint256);

    function oracleSymbolIds(address tokenBX) external view returns (bytes32);

    function setPath(string memory priceSymbol, address[] calldata path) external;

    function getPath(address tokenBX) external view returns (address[] memory);

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

    function getTokenPrice(address tokenBX) external view returns (uint256);

    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 16 of 32 : IUniswapV2Factory.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.5.0;

interface IUniswapV2Factory {
    event PairCreated(address indexed token0, address indexed token1, address pair, uint);

    function feeTo() external view returns (address);
    function feeToSetter() external view returns (address);

    function getPair(address tokenA, address tokenB) external view returns (address pair);
    function allPairs(uint) external view returns (address pair);
    function allPairsLength() external view returns (uint);

    function createPair(address tokenA, address tokenB) external returns (address pair);

    function setFeeTo(address) external;
    function setFeeToSetter(address) external;
}

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

pragma solidity >=0.6.2;

interface IUniswapV2Router01 {
    function factory() external pure returns (address);
    function WETH() external pure returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB, uint liquidity);
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETH(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountToken, uint amountETH);
    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETHWithPermit(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountToken, uint amountETH);
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);
    function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);

    function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
    function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

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

pragma solidity >=0.6.2;

import './IUniswapV2Router01.sol';

interface IUniswapV2Router02 is IUniswapV2Router01 {
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountETH);
    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountETH);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable;
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
}

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';
import '../utils/IVerifier.sol';

interface IOracle is IAdmin, IImplementation, IVerifier {

    struct State {
        string  symbol;
        uint256 source;
        uint256 delayAllowance;
        uint256 blockNumber;
        uint256 timestamp;
        int256  value;
        address chainlinkFeed;
    }

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

    function getOracleId(string memory symbol) external pure returns (bytes32);

    function getState(bytes32 oracleId) external view returns (State memory s);

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

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

    function setOffchainOracle(string memory symbol, uint256 delayAllowance) external;

    function setChainlinkOracle(string memory symbol, address feed) external;

    function updateOffchainValue(Signature memory sig) external;

    function updateOffchainValues(Signature[] memory sigs) 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 : IVerifier.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

interface IVerifier {

    function setVerifier(address newVerifier) 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": {}
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"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"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"BTokenDupInitialize","type":"error"},{"inputs":[],"name":"BTokenNoOracle","type":"error"},{"inputs":[],"name":"BTokenNoSwapper","type":"error"},{"inputs":[],"name":"CannotDelBToken","type":"error"},{"inputs":[{"internalType":"int256","name":"","type":"int256"}],"name":"IToUOverflow","type":"error"},{"inputs":[],"name":"InsufficientB0","type":"error"},{"inputs":[],"name":"InsufficientGasFee","type":"error"},{"inputs":[],"name":"InsufficientMargin","type":"error"},{"inputs":[],"name":"InvalidBAmount","type":"error"},{"inputs":[],"name":"InvalidBPrice","type":"error"},{"inputs":[],"name":"InvalidBToken","type":"error"},{"inputs":[],"name":"InvalidCustodian","type":"error"},{"inputs":[],"name":"InvalidLTokenId","type":"error"},{"inputs":[],"name":"InvalidPTokenId","type":"error"},{"inputs":[],"name":"InvalidRequestId","type":"error"},{"inputs":[],"name":"InvalidSignature","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":"address","name":"bToken","type":"address"},{"indexed":false,"internalType":"address","name":"vault","type":"address"},{"indexed":false,"internalType":"bytes32","name":"oracleId","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"collateralFactor","type":"uint256"}],"name":"AddBToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"bToken","type":"address"}],"name":"DelBToken","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"}],"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"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"actionId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gasFee","type":"uint256"}],"name":"SetGasFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"bToken","type":"address"}],"name":"UpdateBToken","type":"event"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"claimDChainGasFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"dTokenId","type":"uint256"},{"internalType":"bool","name":"isLp","type":"bool"}],"name":"claimUnusedIChainGasFee","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":"getGasFees","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":"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":"dChainGasFeePerRequest","type":"uint256"},{"internalType":"uint256","name":"totalIChainGasFee","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":"lastRequestIChainGasFee","type":"uint256"},{"internalType":"uint256","name":"cumulativeUnusedIChainGasFee","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":"lastRequestIChainGasFee","type":"uint256"},{"internalType":"uint256","name":"cumulativeUnusedIChainGasFee","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"}],"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":"uint256","name":"dChainGasFeePerRequest","type":"uint256"}],"name":"setDChainGasFeePerRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"actionId","type":"uint256"},{"internalType":"uint256","name":"gasFee","type":"uint256"}],"name":"setGasFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"}],"name":"setImplementation","outputs":[],"stateMutability":"nonpayable","type":"function"}]

6102206040523480156200001257600080fd5b5060405162005fac38038062005fac83398101604081905262000035916200019a565b600080546001600160a01b031916339081179091556040519081527f71614071b88dee5e0b2ae578a9dd7b2ebbe9ae832ba419dc0242cd065a290b6c9060200160405180910390a16001600160a01b03808d166080528b811660a0528a811660c05289811660e05288811661010052878116610120528616610140819052620000be90620000f5565b60ff16610180526001600160a01b03909416610160526101a0929092526101c0526101e05261020052506200029795505050505050565b60006001600160a01b0382166001146200017457816001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000148573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200016e91906200026b565b62000177565b60125b92915050565b80516001600160a01b03811681146200019557600080fd5b919050565b6000806000806000806000806000806000806101808d8f031215620001be57600080fd5b620001c98d6200017d565b9b50620001d960208e016200017d565b9a50620001e960408e016200017d565b9950620001f960608e016200017d565b98506200020960808e016200017d565b97506200021960a08e016200017d565b96506200022960c08e016200017d565b95506200023960e08e016200017d565b94506101008d015193506101208d015192506101408d015191506101608d015190509295989b509295989b509295989b565b6000602082840312156200027e57600080fd5b815160ff811681146200029057600080fd5b9392505050565b60805160a05160c05160e05161010051610120516101405161016051610180516101a0516101c0516101e05161020051615af0620004bc600039600081816105fb0152610e950152600081816105d401528181610dd101528181610df90152610e220152600081816105ad0152610e4c01526000818161058601526144d6015260008181610b37015281816110c50152818161191301528181611ff801528181612f560152818161309d0152818161313a015281816131a0015261486901526000818161055f01526132d001526000818161053701528181610c0001528181610edd01528181610fd20152818161175b015281816137420152818161389b01528181613abc01528181613d0e01528181613f8501528181613fe10152818161400f0152818161448b015261480401526000818161050f015281816116ea01526142120152600081816104e701528181610f21015281816110430152818161165101528181613d9301528181613f0001526145310152600081816104bf01528181610c6e01528181610d330152818161390c015281816139d901528181613b3d01528181613c1e0152818161408b0152614143015260008181610498015261489001526000818161047001528181610a56015281816111d6015281816112bc01528181611ab301528181611f7d01528181612337015261430101526000818161044b015281816118980152818161214b015281816123c50152612b550152615af06000f3fe60806040526004361061019c5760003560e01c8063976b4997116100ec578063db4015661161008a578063f7c60d4411610064578063f7c60d44146106ea578063f802a697146106fd578063f851a44014610784578063fa321c26146107a457600080fd5b8063db4015661461064c578063e1faff56146106b7578063ed7ebd0d146106ca57600080fd5b8063aaba0398116100c6578063aaba0398146103f6578063b77352d314610409578063c323acb614610429578063d784d4261461062c57600080fd5b8063976b4997146103945780639b1a2e94146103b6578063a67a40e9146103d657600080fd5b806360499aba11610159578063704b6c0211610133578063704b6c02146102fe57806370a1848c1461031e5780637e9459ba1461033e5780638769da2d1461035f57600080fd5b806360499aba14610291578063619cf2be146102b1578063702e6c06146102de57600080fd5b8063068689cd146101a15780630c8aeb56146101f157806316890fc2146102065780631c58d9c1146102195780633e0c7fc1146102395780635c60da1b14610259575b600080fd5b3480156101ad57600080fd5b506101c16101bc366004614fb2565b6107b7565b6040805182516001600160a01b031681526020808401519082015291810151908201526060015b60405180910390f35b6102046101ff366004614fcf565b61083e565b005b61020461021436600461505a565b610962565b34801561022557600080fd5b50610204610234366004615184565b6109ef565b34801561024557600080fd5b506102046102543660046151e8565b611297565b34801561026557600080fd5b50600154610279906001600160a01b031681565b6040516001600160a01b0390911681526020016101e8565b34801561029d57600080fd5b506102046102ac366004614fb2565b6113c6565b3480156102bd57600080fd5b506102d16102cc3660046151e8565b611421565b6040516101e89190615201565b3480156102ea57600080fd5b506102046102f93660046151e8565b61162b565b34801561030a57600080fd5b50610204610319366004614fb2565b611788565b34801561032a57600080fd5b50610204610339366004615184565b611808565b61035161034c366004615280565b611a94565b6040519081526020016101e8565b34801561036b57600080fd5b5061037f61037a3660046151e8565b611c6d565b604080519283526020830191909152016101e8565b3480156103a057600080fd5b506103a9611d72565b6040516101e891906152ca565b3480156103c257600080fd5b506102046103d1366004615184565b611f04565b3480156103e257600080fd5b506102046103f13660046151e8565b6120f6565b610204610404366004614fcf565b61212e565b34801561041557600080fd5b5061020461042436600461530e565b61231a565b34801561043557600080fd5b5060408051610180810182526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811682527f0000000000000000000000000000000000000000000000000000000000000000811660208301527f00000000000000000000000000000000000000000000000000000000000000008116828401527f0000000000000000000000000000000000000000000000000000000000000000811660608301527f0000000000000000000000000000000000000000000000000000000000000000811660808301527f0000000000000000000000000000000000000000000000000000000000000000811660a08301527f0000000000000000000000000000000000000000000000000000000000000000811660c08301527f00000000000000000000000000000000000000000000000000000000000000001660e08201527f00000000000000000000000000000000000000000000000000000000000000006101008201527f00000000000000000000000000000000000000000000000000000000000000006101208201527f00000000000000000000000000000000000000000000000000000000000000006101408201527f000000000000000000000000000000000000000000000000000000000000000061016082015290516101e8919061533e565b34801561063857600080fd5b50610204610647366004614fb2565b6124b5565b34801561065857600080fd5b5061066161252e565b6040516101e89190600060e082019050825182526020830151602083015260408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015292915050565b6102046106c5366004615440565b6125e4565b3480156106d657600080fd5b506102046106e53660046154b3565b6126f6565b6102046106f8366004614fcf565b612770565b34801561070957600080fd5b5061071d6107183660046151e8565b612887565b6040516101e89190815181526020808301516001600160a01b03169082015260408083015190820152606080830151908201526080808301519082015260a08083015115159082015260c0808301519082015260e091820151918101919091526101000190565b34801561079057600080fd5b50600054610279906001600160a01b031681565b6102046107b23660046154d5565b612a49565b60408051606081018252600080825260208083018290528284018290526001600160a01b038516825260039052919091206107f3906001612b16565b6001600160a01b0390811682528216600081815260036020818152604080842060028552808352908420548287015293909252908190526108349190612b16565b6040820152919050565b6108488333612b49565b600260005260056020527f89832631fb3c3307a103ba2c84ab569c64d6182a18893dcd163f0f1c2090733a5461087f908490612c09565b50806000036108a1576040516394613e9960e01b815260040160405180910390fd5b60006108ae338585612d03565b90506108b981612e18565b60006108c482612e5b565b905060006108d28385612fa2565b90506108df606483615554565b81116108e9575060005b60006108f4876131da565b60c08086015160608088015160408051868152602081018e905290810188905291820192909252608081019190915260a081018890529192507f0be79565c9c0f7b4144002bd9abeb6bd0e8f43023abae7066992c58bdff9397e91015b60405180910390a150505050505050565b6000196001600160a01b038716016109cc57600460005260056020527f3eec716f11ba9e820c81ca75eb978ffb45831ef8b7a53e5e422c26008e1ca6d554346109ab8288615568565b11156109ca576040516394613e9960e01b815260040160405180910390fd5b505b6109d887878784611a94565b96506109e687858585612a49565b50505050505050565b600154600160a01b900460ff1615610a1a576040516325dbe6e160e21b815260040160405180910390fd5b6001805460ff60a01b1916600160a01b179055610a37828261328f565b600082806020019051810190610a4d919061557b565b90506000610b0c7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316636352211e84602001516040518263ffffffff1660e01b8152600401610aa691815260200190565b602060405180830381865afa158015610ac3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ae791906155d7565b602080850151600081815260049092526040909120610b07906002612b16565b612d03565b90506000610b2b8260c00151846040015161332690919063ffffffff16565b9050610b5c81601260ff7f00000000000000000000000000000000000000000000000000000000000000001661332b565b8260a001818151610b6d91906155f4565b90525060408381015160c0840152608083015160208401519151637cbc237360e01b81526004810192909252600019602483015260009182916001600160a01b031690637cbc2373906044016020604051808303816000875af1158015610bd8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bfc9190615614565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031684604001516001600160a01b031603610c4c57610c458183615568565b9150610db0565b60016001600160a01b031684604001516001600160a01b031603610d055760007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663f29216e2836040518263ffffffff1660e01b8152600401604080518083038185885af1158015610ccb573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190610cf0919061562d565b509050610cfd8184615568565b925050610db0565b6040848101519051633e87f49360e01b81526001600160a01b039182166004820152602481018390526000917f00000000000000000000000000000000000000000000000000000000000000001690633e87f4939060440160408051808303816000875af1158015610d7b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d9f919061562d565b509050610dac8184615568565b9250505b5060008360a00151610dc183613365565b610dcb91906155f4565b905060007f00000000000000000000000000000000000000000000000000000000000000008213610e1d57507f0000000000000000000000000000000000000000000000000000000000000000610ebc565b610eb97f0000000000000000000000000000000000000000000000000000000000000000670de0b6b3a76400007f0000000000000000000000000000000000000000000000000000000000000000610e758387615651565b610e7f9190615671565b610e8991906156a1565b610e9391906155f4565b7f000000000000000000000000000000000000000000000000000000000000000061339b565b90505b6000610ec7826133b2565b9050838111610f1557610f046001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633836133d8565b610f0e81856156cf565b9350611013565b60006001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016637cbc237382610f5188866156cf565b6040516001600160e01b031960e085901b168152600481019290925260248201526044016020604051808303816000875af1158015610f94573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fb89190615614565b9050610ff933610fc88388615568565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691906133d8565b61100b6110068287615568565b613365565b925060009450505b61101d8284615651565b92505082156110ba57604051631c57762b60e31b815260006004820152602481018490527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063e2bbb158906044016020604051808303816000875af1158015611094573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b89190615614565b505b6110f56110ec8360ff7f00000000000000000000000000000000000000000000000000000000000000001660126134bb565b60608701510190565b6060860152600060a086015261110a856134fb565b6020808701516000908152600490915260408120611129906009612b16565b60208089015160009081526004909152604081209192509061114c90600a612b16565b60208981018051600090815260048084526040808320600984528552808320839055925182528352818120600a8252909252812081905590915061119260026007612b16565b905061119e8284615568565b6111a890826156cf565b90506111b7600260078361357c565b5050506020860151604051630852cd8d60e31b815260048101919091527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906342966c6890602401600060405180830381600087803b15801561122257600080fd5b505af1158015611236573d6000803e3d6000fd5b505087516020808a0151604080519384529183015281018590527f8c3b5c5c5dddce5cc5abfa2cc66ec5dd06f76f9e9bb21debb1a85ad88fdbdb6f9250606001905060405180910390a150506001805460ff60a01b19169055505050505050565b6040516331a9108f60e11b815260048101829052600090611342906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690636352211e90602401602060405180830381865afa158015611303573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061132791906155d7565b60008481526004602052604090208490610b07906002612b16565b905061134d81612e18565b600061135882612e5b565b90506000611365846131da565b60c084015160608086015160408051858152602081018a905280820188905292830193909352608082015290519192507fb299642ff40e6ba13eec0f77203c3ef9816a9f64212a40467d63db0eba259b4f919081900360a00190a150505050565b6000546001600160a01b031633146113f157604051634755657960e01b815260040160405180910390fd5b61141e8161140160026007612b16565b61140b6001613597565b61141591906156cf565b600191906133d8565b50565b6114806040518061014001604052806000815260200160006001600160a01b0316815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6000828152600460205260409020611499906001612b16565b815260008281526004602052604090206114b4906002612b16565b6001600160a01b03166020808301829052600091825260039052604090206114dd906001612b16565b6001600160a01b0316631e010439836040518263ffffffff1660e01b815260040161150a91815260200190565b602060405180830381865afa158015611527573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061154b9190615614565b604080830191909152600083815260046020522061156a906003612b16565b6060820152600082815260046020819052604090912061158991612b16565b608082015260008281526004602052604090206115a7906005612b16565b60a082015260008281526004602052604090206115c5906006612b16565b60c082015260008281526004602052604090206115e3906007612b16565b60e08201526000828152600460205260409020611601906009612b16565b610100820152600082815260046020526040902061162090600a612b16565b610120820152919050565b801561141e57604051637cbc237360e01b815260006004820181905260248201839052907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690637cbc2373906044016020604051808303816000875af11580156116a2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116c69190615614565b9050801561178457604051632770a7eb60e21b8152336004820152602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690639dc29fac90604401600060405180830381600087803b15801561173657600080fd5b505af115801561174a573d6000803e3d6000fd5b506117849250506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016905033836133d8565b5050565b6000546001600160a01b031633146117b357604051634755657960e01b815260040160405180910390fd5b600080546001600160a01b0319166001600160a01b0383169081179091556040519081527f71614071b88dee5e0b2ae578a9dd7b2ebbe9ae832ba419dc0242cd065a290b6c906020015b60405180910390a150565b600154600160a01b900460ff1615611833576040516325dbe6e160e21b815260040160405180910390fd5b6001805460ff60a01b1916600160a01b179055611850828261328f565b60008280602001905181019061186691906156e2565b905061187a8160200151826000015161361e565b61189181602001518260400151836060015161368f565b60006118e87f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316636352211e84602001516040518263ffffffff1660e01b8152600401610aa691815260200190565b905060006119078260c00151846080015161332690919063ffffffff16565b905061193881601260ff7f00000000000000000000000000000000000000000000000000000000000000001661332b565b8260a00181815161194991906155f4565b905250608083015160c083015260a0830151600090156119945761196c83612e18565b611991838560400151600014611986578560a0015161198a565b6000195b6000613714565b90505b61199d836134fb565b6119ab84602001513361427b565b8360a00151600003611a105783516020808601516040808801516060808a0151835196875294860193909352908401528201527f3b963caf777561b495590bdc6ec8abd33838524d0a3aafc3f6f1b7857449d0999060800160405180910390a1611a7f565b83516020808601516040808801516060808a015189840151845197885295870194909452918501528301526001600160a01b0316608082015260a081018290527fc72ca5dc9c24b437f43b8c112e7b4e24b4a7bce4b1f405e591eee1cc32d327c29060c0015b60405180910390a15b50506001805460ff60a01b1916905550505050565b600084600003611b50576040516335313c2160e11b81523360048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690636a627842906024016020604051808303816000875af1158015611b04573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b289190615614565b94508115611b4b576000858152600460205260409020611b4b90600860016142ec565b611b5a565b611b5a85336142f5565b611b63846143b5565b6000611b70338787612d03565b90506000196001600160a01b03861601611ba55734841115611ba5576040516394613e9960e01b815260040160405180910390fd5b83600003611bc6576040516394613e9960e01b815260040160405180910390fd5b6001600160a01b038516600114611bee578051611bee906001600160a01b03871690866143ff565b611bf88185614489565b611c01816134fb565b6000611c0c876131da565b60408051828152602081018a90526001600160a01b038916818301526060810188905290519192507f8eee26bd33caee967b96e711aa45ef94c734251b0457a0f2213fb392c01ef53a919081900360800190a186925050505b949350505050565b60008080611c7c600280612b16565b90506000611c8c60026003612b16565b9050611c9a60026004612b16565b600086815260046020526040812091955090611cb7906005612b16565b6000878152600460205260409020909150611cd3906006612b16565b600087815260046020526040812091955090611cf0906007612b16565b90508215611d6957600083670de0b6b3a764000080611d0f88426156cf565b611d19919061575c565b611d23919061575c565b611d2d9190615554565b9687019690508215611d6757818703670de0b6b3a7640000611d4f858361575c565b611d599190615554565b611d639088615568565b9650505b505b50505050915091565b60408051600580825260c082019092526060916020820160a08036833750506001600090815260056020527f1471eb6eb2c5e789fc3de43f8ce62938c7d1836ec861730447e2ada8fd81017b54835193945092849250611dd457611dd4615773565b6020908102919091018101919091526002600052600590527f89832631fb3c3307a103ba2c84ab569c64d6182a18893dcd163f0f1c2090733a54815182906001908110611e2357611e23615773565b6020908102919091018101919091526003600052600590527fa9bc9a3a348c357ba16b37005d7e6b3236198c0e939f4af8c5f19b8deeb8ebc054815182906002908110611e7257611e72615773565b6020908102919091018101919091526004600052600590527f3eec716f11ba9e820c81ca75eb978ffb45831ef8b7a53e5e422c26008e1ca6d554815182906003908110611ec157611ec1615773565b60200260200101818152505060056000600581526020019081526020016000205481600481518110611ef557611ef5615773565b60200260200101818152505090565b600154600160a01b900460ff1615611f2f576040516325dbe6e160e21b815260040160405180910390fd5b6001805460ff60a01b1916600160a01b179055611f4c828261328f565b600082806020019051810190611f629190615789565b9050611f768160200151826000015161361e565b6000611fcd7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316636352211e84602001516040518263ffffffff1660e01b8152600401610aa691815260200190565b90506000611fec8260c00151846060015161332690919063ffffffff16565b905061201d81601260ff7f00000000000000000000000000000000000000000000000000000000000000001661332b565b8260a00181815161202e91906155f4565b905250606083015160c083015261204482612e18565b60006120568385608001516001613714565b9050836040015161206684612e5b565b1015612085576040516341c092a960e01b815260040160405180910390fd5b61208e836134fb565b61209c84602001513361427b565b83516020808601516040808701518151948552928401919091526001600160a01b0390911690820152606081018290527f39f415ac8ef6b49b1ba40c4fafb2b911df1c5306c49eef15c4bc38a0128fca9890608001611a76565b6000546001600160a01b0316331461212157604051634755657960e01b815260040160405180910390fd5b61141e600260068361357c565b826000036121c7576040516335313c2160e11b81523360048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690636a627842906024016020604051808303816000875af115801561219c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121c09190615614565b92506121d1565b6121d18333612b49565b6121da826143b5565b60006121e7338585612d03565b6001600090815260056020527f1471eb6eb2c5e789fc3de43f8ce62938c7d1836ec861730447e2ada8fd81017b5491925090612224908690612c09565b90506000196001600160a01b0385160161223c578092505b8260000361225d576040516394613e9960e01b815260040160405180910390fd5b6001600160a01b038416600114612285578151612285906001600160a01b03861690856143ff565b61228f8284614489565b61229882612e18565b60006122a383612e5b565b90506122ae836134fb565b60006122b9876131da565b60c08086015160608088015160408051868152602081018e9052908101889052918201929092526080810191909152600060a08201529192507f0be79565c9c0f7b4144002bd9abeb6bd0e8f43023abae7066992c58bdff9397e9101610951565b6000816123af576040516331a9108f60e11b8152600481018490527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690636352211e90602401602060405180830381865afa158015612386573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123aa91906155d7565b612438565b6040516331a9108f60e11b8152600481018490527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690636352211e90602401602060405180830381865afa158015612414573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061243891906155d7565b60008481526004602052604081209192509061245590600a612b16565b905080156124af57600061246b60026007612b16565b905061247782826156cf565b9050612486600260078361357c565b6000858152600460209081526040808320600a84529091528120556124ad600184846133d8565b505b50505050565b6000546001600160a01b031633146124e057604051634755657960e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f6b70829fcbe4891157f7a7496f9870927de3c8237adbe9cd39bae09b7382c409906020016117fd565b61256e6040518060e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b61257a60026001612b16565b8152612587600280612b16565b602082015261259860026003612b16565b60408201526125a960026004612b16565b60608201526125ba60026005612b16565b60808201526125cb60026006612b16565b60a08201526125dc60026007612b16565b60c082015290565b6125ee86336142f5565b600560008190526020527f458b30c2d72bfd2c6317304a4594ecbafe5f729d3111b65fdc3a33bd48e5432d54612625908790612c09565b5083600003612647576040516394613e9960e01b815260040160405180910390fd5b6000612654338888612d03565b905061265f81612e18565b600061266a82612e5b565b905060006126788388612fa2565b9050612685606483615554565b811161268f575060005b600061269a8a6131da565b90507ecae5d4a40f5ab841867a076a796230392d8ce403d409f84f18377b49a602d5818b848760c0015188606001518d8d8d8d6040516126e299989796959493929190615830565b60405180910390a150505050505050505050565b6000546001600160a01b0316331461272157604051634755657960e01b815260040160405180910390fd5b60008281526005602090815260409182902083905581518481529081018390527f7ee33d5ad485da9f8837dbf37e57c80fabc9165cc5c74f9d760028895a34badc910160405180910390a15050565b61277a83336142f5565b600360005260056020527fa9bc9a3a348c357ba16b37005d7e6b3236198c0e939f4af8c5f19b8deeb8ebc0546127b1908490612c09565b50806000036127d3576040516394613e9960e01b815260040160405180910390fd5b60006127e0338585612d03565b90506127eb81612e18565b60006127f682612e5b565b905060006128048385612fa2565b9050612811606483615554565b811161281b575060005b6000612826876131da565b60c08086015160608088015160408051868152602081018e905290810188905291820192909252608081019190915260a081018890529192507f88ad2ade0e436c3cffc2b0d1b51fb1379e5bca5dd3aaef801b234b18aa87d2a99101610951565b6128da6040518061010001604052806000815260200160006001600160a01b0316815260200160008152602001600081526020016000815260200160001515815260200160008152602001600081525090565b60008281526004602052604090206128f3906001612b16565b8152600082815260046020526040902061290e906002612b16565b6001600160a01b0316602080830182905260009182526003905260409020612937906001612b16565b6001600160a01b0316631e010439836040518263ffffffff1660e01b815260040161296491815260200190565b602060405180830381865afa158015612981573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129a59190615614565b60408083019190915260008381526004602052206129c4906003612b16565b606082015260008281526004602081905260409091206129e391612b16565b60808201526000828152600460205260409020612a019060086146ea565b151560a08201526000828152600460205260409020612a21906009612b16565b60c08201526000828152600460205260409020612a3f90600a612b16565b60e0820152919050565b612a5384336142f5565b600460005260056020527f3eec716f11ba9e820c81ca75eb978ffb45831ef8b7a53e5e422c26008e1ca6d554612a8a908590612c09565b506000848152600460205260408120612aac9033908790610b07906002612b16565b9050612ab781612e18565b6000612ac282612e5b565b90506000612acf876131da565b90507fd8eb4847ae2a642c0c24f4336eb3ac86a4cac889da47bb554f95c4d6163d80ff8188848660c0015187606001518b8b8b60405161095198979695949392919061587f565b60ff81166000908152602083905260408120545b90505b92915050565b60ff166000908152602091909152604090205490565b806001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316636352211e846040518263ffffffff1660e01b8152600401612ba191815260200190565b602060405180830381865afa158015612bbe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612be291906155d7565b6001600160a01b0316146117845760405163a03dca6f60e01b815260040160405180910390fd5b600080612c1860026006612b16565b905082341015612c3b5760405163e6b6589360e01b815260040160405180910390fd5b6000612c4782856156cf565b9050600081612c5860026007612b16565b612c629190615568565b9050612c71600260078361357c565b6000868152600460205260408120612c8a906009612b16565b600088815260046020526040812091925090612ca790600a612b16565b9050612cb38282615568565b6000898152600460205260409020859350909150612cd39060098461357c565b6000888152600460205260409020612ced90600a8361357c565b612cf787346156cf565b98975050505050505050565b612d6d60405180610120016040528060006001600160a01b031681526020016000815260200160006001600160a01b031681526020016000815260200160006001600160a01b03168152602001600081526020016000815260200160008152602001600081525090565b6001600160a01b0380851682526020820184905282166040820152612d9460026001612b16565b60608201526001600160a01b0382166000908152600360205260409020612dbc906001612b16565b6001600160a01b031660808201526000838152600460205260409020612de3906003612b16565b60a08201526000838152600460208190526040909120612e0291612b16565b60c0820152612e118383614704565b9392505050565b6040808201516001600160a01b0316600090815260036020819052919020612e3f91612b16565b60e08201526040810151612e5290614800565b61010090910152565b600080670de0b6b3a76400008360e00151670de0b6b3a764000085610100015186608001516001600160a01b0316631e01043988602001516040518263ffffffff1660e01b8152600401612eb191815260200190565b602060405180830381865afa158015612ece573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ef29190615614565b612efc919061575c565b612f069190615554565b612f10919061575c565b612f1a9190615554565b90506000808460a001511215612f4557612f408460a00151612f3b906158c5565b6133b2565b612f48565b60005b9050808210612f9b57612f987f000000000000000000000000000000000000000000000000000000000000000060ff166012612f918760a001518661497d90919063ffffffff16565b91906149aa565b92505b5050919050565b6000826101000151600019612fb79190615554565b821015612b2d5760808301516020840151604051631e01043960e01b815260048101919091526000916001600160a01b031690631e01043990602401602060405180830381865afa158015613010573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130349190615614565b90508083106130d45760008460a0015113156130cf576000670de0b6b3a7640000856101000151838661306791906156cf565b613071919061575c565b61307b9190615554565b9050600061308c8660a001516133b2565b9050818111156130cc576130c960ff7f0000000000000000000000000000000000000000000000000000000000000000166012612f9185856156cf565b93505b50505b6131d3565b6000670de0b6b3a76400008560e00151670de0b6b3a764000087610100015187866130ff91906156cf565b613109919061575c565b6131139190615554565b61311d919061575c565b6131279190615554565b905060008560a001511261317c576131757f000000000000000000000000000000000000000000000000000000000000000060ff166012612f918860a001518561497d90919063ffffffff16565b92506131d1565b600061318f8660a00151612f3b906158c5565b9050808211156131cf576131cc60ff7f0000000000000000000000000000000000000000000000000000000000000000166012612f9184866156cf565b93505b505b505b5092915050565b6000806131e960026005612b16565b90506131f66001826158e1565b905061320e600260056001600160801b03841661357c565b6000838152600460205260408120613227906001612b16565b90506132346001826158e1565b600085815260046020526040902090915061325a9060016001600160801b03841661357c565b60006132866001600160801b0383166fffffffffffffffffffffffffffffffff19608086901b16615568565b95945050505050565b815160208301207f19457468657265756d205369676e6564204d6573736167653a0a3332000000006000908152601c91909152603c90206001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000166132fa82846149dd565b6001600160a01b03161461332157604051638baa579f60e01b815260040160405180910390fd5b505050565b900390565b6000806133398585856134bb565b90506000851280156133555750846133528285876134bb565b14155b15611c6557613286600182615651565b60006001600160ff1b0382111561339757604051632a26bd1360e01b8152600481018390526024015b60405180910390fd5b5090565b6000818313156133ab5781612b2a565b5090919050565b60008082121561339757604051632a33bb3160e01b81526004810183905260240161338e565b60006133e384613597565b90506000196001600160a01b03851601613471576000836001600160a01b03168360405160006040518083038185875af1925050503d8060008114613444576040519150601f19603f3d011682016040523d82523d6000602084013e613449565b606091505b505090508061346b5760405163e277d13760e01b815260040160405180910390fd5b50613485565b6134856001600160a01b0385168484614a01565b600061349085613597565b905061349c8382615568565b82146124ad5760405163f4d4667360e01b815260040160405180910390fd5b60008183146134f3576134cf83600a6159e5565b6134da83600a6159e5565b6134e49086615671565b6134ee91906156a1565b611c65565b509192915050565b606081015161350f9060029060019061357c565b604080820151602080840151600090815260049091529190912061353591600290614a64565b60a0810151602080830151600090815260049091526040902061355a9160039061357c565b60c081015160208083015160009081526004918290526040902061141e929091905b805b60ff909216600090815260209390935250604090912055565b60006001600160a01b038216600114613617576040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401602060405180830381865afa1580156135ee573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906136129190615614565b612b2d565b4792915050565b600082815260046020526040902081906001600160801b03821690613644906001612b16565b14613662576040516302e8145360e61b815260040160405180910390fd5b613321600161367183826158e1565b600086815260046020526040902091906001600160801b031661357c565b60008061369b85611c6d565b90925090506136ac6002804261357c565b6136b9600260038561357c565b6136c6600260048461357c565b60008581526004602052604090206136e09060058661357c565b60008581526004602052604090206136fa9060068361357c565b60008581526004602052604090206124ad9060078461357c565b81613729670de0b6b3a7640000600019615554565b8110801561373b575060008460a00151125b156137f7577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031684604001516001600160a01b03160361379f5761378e8460a00151612f3b906158c5565b6137989082615568565b90506137f7565b6064846101000151670de0b6b3a76400006137c18760a00151612f3b906158c5565b6137cb919061575c565b6137d59190615554565b6137e090606961575c565b6137ea9190615554565b6137f49082615568565b90505b60808401516020850151604051637cbc237360e01b81526001600160a01b0390921691637cbc237391613837918590600401918252602082015260400190565b6020604051808303816000875af1158015613856573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061387a9190615614565b9050600080600085841115613a8a57600061389587866156cf565b905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031689604001516001600160a01b0316036138e95750806138e281876156cf565b9550613a5e565b60016001600160a01b031689604001516001600160a01b0316036139a9576000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663f29216e2856040518263ffffffff1660e01b8152600401604080518083038185885af1158015613969573d6000803e3d6000fd5b50505050506040513d601f19601f8201168201806040525081019061398e919061562d565b90935083925090506139a081896156cf565b97505050613a5e565b6040898101519051633e87f49360e01b81526001600160a01b0391821660048201526024810184905260009182917f000000000000000000000000000000000000000000000000000000000000000090911690633e87f4939060440160408051808303816000875af1158015613a23573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613a47919061562d565b9093508392509050613a5981896156cf565b975050505b613a688186615568565b9450613a7381613365565b8960a001818151613a8491906155f4565b90525050505b600084118015613a9e575060008760a00151125b15613ccf576000613ab68860a00151612f3b906158c5565b905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031689604001516001600160a01b031603613b1a57818610613b11575080613b0a81876156cf565b9550613ca3565b50600094613ca3565b60016001600160a01b031689604001516001600160a01b031603613be7576000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316637661f4f689866040518363ffffffff1660e01b8152600401613b8a91815260200190565b604080518083038185885af1158015613ba7573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190613bcc919061562d565b9093508392509050613bde81896156cf565b97505050613ca3565b604089810151905163bf441a5960e01b81526001600160a01b039182166004820152602481018490526044810188905260009182917f00000000000000000000000000000000000000000000000000000000000000009091169063bf441a599060640160408051808303816000875af1158015613c68573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613c8c919061562d565b9093508392509050613c9e81896156cf565b975050505b613cad8186615568565b9450613cb881613365565b8960a001818151613cc991906155f4565b90525050505b60008760a001511315613edd576000613cf2670de0b6b3a7640000600019615554565b8710613d0c57613d058860a001516133b2565b9050613d77565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031688604001516001600160a01b0316148015613d5057508685105b15613d7757613d74613d658960a001516133b2565b613d6f878a6156cf565b614a73565b90505b8015613edb57600084821115613e8e5760006001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016637cbc237382613dc389876156cf565b6040516001600160e01b031960e085901b168152600481019290925260248201526044016020604051808303816000875af1158015613e06573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613e2a9190615614565b9050613e3686846156cf565b811015613e78578715613e5f5780613e4e87856156cf565b613e5891906156cf565b9350613e78565b6040516320b7831f60e11b815260040160405180910390fd5b613e828187615568565b91506000955050613e9d565b5080613e9a81866156cf565b94505b613ea78185615568565b9350613eb283613365565b613ebb82613365565b613ec591906155f4565b8960a001818151613ed69190615651565b905250505b505b8215613f7757604051631c57762b60e31b815260006004820152602481018490527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063e2bbb158906044016020604051808303816000875af1158015613f51573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613f759190615614565b505b81156141c157841561400d577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031687604001516001600160a01b031603613fd157613fca8285615568565b93506141c1565b8651614008906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690846133d8565b6141c1565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031687604001516001600160a01b03160361405457613fca8285615568565b60016001600160a01b031687604001516001600160a01b03160361411557604051632cb4d9d960e11b8152600481018390526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690635969b3b29060240160408051808303816000875af11580156140db573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140ff919061562d565b915061410d90508186615568565b9450506141c1565b604087810151905163045608af60e01b81526001600160a01b039182166004820152602481018490526000917f0000000000000000000000000000000000000000000000000000000000000000169063045608af9060440160408051808303816000875af115801561418b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141af919061562d565b91506141bd90508186615568565b9450505b83156141e457865160408801516141e4916001600160a01b0390911690866133d8565b80156142715786516040516340c10f1960e01b81526001600160a01b039182166004820152602481018390527f0000000000000000000000000000000000000000000000000000000000000000909116906340c10f1990604401600060405180830381600087803b15801561425857600080fd5b505af115801561426c573d6000803e3d6000fd5b505050505b5050509392505050565b6000828152600460205260408120614294906009612b16565b905080156133215760006142aa60026007612b16565b90506142b682826156cf565b90506142c5600260078361357c565b6000848152600460209081526040808320600984529091528120556124af600184846133d8565b61357e81614a83565b806001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316636352211e846040518263ffffffff1660e01b815260040161434d91815260200190565b602060405180830381865afa15801561436a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061438e91906155d7565b6001600160a01b03161461178457604051639289a15b60e01b815260040160405180910390fd5b6001600160a01b03811660009081526003602052604081206143d8906001612b16565b6001600160a01b03160361141e57604051634033aec960e01b815260040160405180910390fd5b6000196001600160a01b038416016144315734811461332157604051630fc9bd0f60e11b815260040160405180910390fd5b600061443c84613597565b90506144536001600160a01b038516843085614a9d565b600061445e85613597565b905061446a8383615568565b81146124ad57604051630fc9bd0f60e11b815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031682604001516001600160a01b0316036145c6576000670de0b6b3a76400006144fb7f00000000000000000000000000000000000000000000000000000000000000008461575c565b6145059190615554565b905061451181836156cf565b604051631c57762b60e31b815260006004820152602481018390529092507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063e2bbb158906044016020604051808303816000875af1158015614582573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906145a69190615614565b506145b081613365565b8360a0018181516145c191906155f4565b905250505b60016001600160a01b031682604001516001600160a01b0316036146675781608001516001600160a01b031663e2bbb158828460200151846040518463ffffffff1660e01b8152600401614624929190918252602082015260400190565b60206040518083038185885af1158015614642573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906133219190615614565b60808201516020830151604051631c57762b60e31b81526001600160a01b039092169163e2bbb158916146a7918590600401918252602082015260400190565b6020604051808303816000875af11580156146c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133219190615614565b60ff81166000908152602083905260408120541515612b2a565b600082815260046020526040812061471d906002612b16565b90506001600160a01b038116158015906147495750816001600160a01b0316816001600160a01b031614155b15613321576001600160a01b0381166000908152600360205260408120614771906001612b16565b6001600160a01b0316635d988967856040518263ffffffff1660e01b815260040161479e91815260200190565b602060405180830381865afa1580156147bb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906147df9190615614565b905080156124af57604051634033aec960e01b815260040160405180910390fd5b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b03160361484a5750670de0b6b3a7640000919050565b600061485e836001600160a01b0316614ad5565b90506149548160ff167f000000000000000000000000000000000000000000000000000000000000000060ff16612f917f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663698439406148f56002600360008c6001600160a01b03166001600160a01b03168152602001908152602001600020612b3390919063ffffffff16565b6040518263ffffffff1660e01b815260040161491391815260200190565b602060405180830381865afa158015614930573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f3b9190615614565b9150816000036149775760405163089112d560e21b815260040160405180910390fd5b50919050565b6000808212614997576149908284615568565b9050612b2d565b6149a0826158c5565b61499090846156cf565b60008183146134f3576149be83600a6159e5565b6149c983600a6159e5565b6149d3908661575c565b6134ee9190615554565b60008060006149ec8585614b52565b915091506149f981614b97565b509392505050565b6040516001600160a01b03831660248201526044810182905261332190849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152614ce1565b6001600160a01b03811661357e565b6000818311156133ab5781612b2a565b600081614a91576000614a94565b60015b60ff1692915050565b6040516001600160a01b03808516602483015283166044820152606481018290526124af9085906323b872dd60e01b90608401614a2d565b60006001600160a01b038216600114614b4a57816001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015614b26573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061361291906159f1565b601292915050565b6000808251604103614b885760208301516040840151606085015160001a614b7c87828585614db6565b94509450505050614b90565b506000905060025b9250929050565b6000816004811115614bab57614bab615a14565b03614bb35750565b6001816004811115614bc757614bc7615a14565b03614c145760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e61747572650000000000000000604482015260640161338e565b6002816004811115614c2857614c28615a14565b03614c755760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e67746800604482015260640161338e565b6003816004811115614c8957614c89615a14565b0361141e5760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b606482015260840161338e565b6000614d36826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316614e7a9092919063ffffffff16565b9050805160001480614d57575080806020019051810190614d579190615a2a565b6133215760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161338e565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0831115614ded5750600090506003614e71565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015614e41573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116614e6a57600060019250925050614e71565b9150600090505b94509492505050565b6060611c65848460008585600080866001600160a01b03168587604051614ea19190615a6b565b60006040518083038185875af1925050503d8060008114614ede576040519150601f19603f3d011682016040523d82523d6000602084013e614ee3565b606091505b5091509150614ef487838387614eff565b979650505050505050565b60608315614f6e578251600003614f67576001600160a01b0385163b614f675760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161338e565b5081611c65565b611c658383815115614f835781518083602001fd5b8060405162461bcd60e51b815260040161338e9190615a87565b6001600160a01b038116811461141e57600080fd5b600060208284031215614fc457600080fd5b8135612e1181614f9d565b600080600060608486031215614fe457600080fd5b833592506020840135614ff681614f9d565b929592945050506040919091013590565b60008083601f84011261501957600080fd5b50813567ffffffffffffffff81111561503157600080fd5b6020830191508360208260051b8501011115614b9057600080fd5b801515811461141e57600080fd5b600080600080600080600060c0888a03121561507557600080fd5b87359650602088013561508781614f9d565b95506040880135945060608801359350608088013567ffffffffffffffff8111156150b157600080fd5b6150bd8a828b01615007565b90945092505060a08801356150d18161504c565b8091505092959891949750929550565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261510857600080fd5b813567ffffffffffffffff80821115615123576151236150e1565b604051601f8301601f19908116603f0116810190828211818310171561514b5761514b6150e1565b8160405283815286602085880101111561516457600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806040838503121561519757600080fd5b823567ffffffffffffffff808211156151af57600080fd5b6151bb868387016150f7565b935060208501359150808211156151d157600080fd5b506151de858286016150f7565b9150509250929050565b6000602082840312156151fa57600080fd5b5035919050565b81518152602080830151610140830191615225908401826001600160a01b03169052565b5060408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015260e083015160e083015261010080840151818401525061012080840151818401525092915050565b6000806000806080858703121561529657600080fd5b8435935060208501356152a881614f9d565b92506040850135915060608501356152bf8161504c565b939692955090935050565b6020808252825182820181905260009190848201906040850190845b81811015615302578351835292840192918401916001016152e6565b50909695505050505050565b6000806040838503121561532157600080fd5b8235915060208301356153338161504c565b809150509250929050565b81516001600160a01b031681526101808101602083015161536a60208401826001600160a01b03169052565b50604083015161538560408401826001600160a01b03169052565b5060608301516153a060608401826001600160a01b03169052565b5060808301516153bb60808401826001600160a01b03169052565b5060a08301516153d660a08401826001600160a01b03169052565b5060c08301516153f160c08401826001600160a01b03169052565b5060e083015161540c60e08401826001600160a01b03169052565b5061010083810151908301526101208084015190830152610140808401519083015261016092830151929091019190915290565b60008060008060008060a0878903121561545957600080fd5b86359550602087013561546b81614f9d565b94506040870135935060608701359250608087013567ffffffffffffffff81111561549557600080fd5b6154a189828a01615007565b979a9699509497509295939492505050565b600080604083850312156154c657600080fd5b50508035926020909101359150565b600080600080606085870312156154eb57600080fd5b8435935060208501359250604085013567ffffffffffffffff81111561551057600080fd5b61551c87828801615007565b95989497509550505050565b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60008261556357615563615528565b500490565b80820180821115612b2d57612b2d61553e565b60006060828403121561558d57600080fd5b6040516060810181811067ffffffffffffffff821117156155b0576155b06150e1565b80604052508251815260208301516020820152604083015160408201528091505092915050565b6000602082840312156155e957600080fd5b8151612e1181614f9d565b80820182811260008312801582168215821617156131d1576131d161553e565b60006020828403121561562657600080fd5b5051919050565b6000806040838503121561564057600080fd5b505080516020909101519092909150565b81810360008312801583831316838312821617156131d3576131d361553e565b80820260008212600160ff1b8414161561568d5761568d61553e565b8181058314821517612b2d57612b2d61553e565b6000826156b0576156b0615528565b600160ff1b8214600019841416156156ca576156ca61553e565b500590565b81810381811115612b2d57612b2d61553e565b600060c082840312156156f457600080fd5b60405160c0810181811067ffffffffffffffff82111715615717576157176150e1565b8060405250825181526020830151602082015260408301516040820152606083015160608201526080830151608082015260a083015160a08201528091505092915050565b8082028115828204841417612b2d57612b2d61553e565b634e487b7160e01b600052603260045260246000fd5b600060a0828403121561579b57600080fd5b60405160a0810181811067ffffffffffffffff821117156157be576157be6150e1565b806040525082518152602083015160208201526040830151604082015260608301516060820152608083015160808201528091505092915050565b8183526000602080850194508260005b8581101561582557813587529582019590820190600101615809565b509495945050505050565b60006101008b83528a60208401528960408401528860608401528760808401528660a08401528560c08401528060e084015261586f81840185876157f9565b9c9b505050505050505050505050565b8881528760208201528660408201528560608201528460808201528360a082015260e060c082015260006158b760e0830184866157f9565b9a9950505050505050505050565b6000600160ff1b82016158da576158da61553e565b5060000390565b6001600160801b038181168382160190808211156131d3576131d361553e565b600181815b8085111561593c5781600019048211156159225761592261553e565b8085161561592f57918102915b93841c9390800290615906565b509250929050565b60008261595357506001612b2d565b8161596057506000612b2d565b816001811461597657600281146159805761599c565b6001915050612b2d565b60ff8411156159915761599161553e565b50506001821b612b2d565b5060208310610133831016604e8410600b84101617156159bf575081810a612b2d565b6159c98383615901565b80600019048211156159dd576159dd61553e565b029392505050565b6000612b2a8383615944565b600060208284031215615a0357600080fd5b815160ff81168114612e1157600080fd5b634e487b7160e01b600052602160045260246000fd5b600060208284031215615a3c57600080fd5b8151612e118161504c565b60005b83811015615a62578181015183820152602001615a4a565b50506000910152565b60008251615a7d818460208701615a47565b9190910192915050565b6020815260008251806020840152615aa6816040850160208701615a47565b601f01601f1916919091016040019291505056fea2646970667358221220a51bc7cc468f19984b4c513e11e7ea00ac0fd17e28e0446592be85d8d888e0b164736f6c63430008140033000000000000000000000000c79102c36bbba246b8bb6ae81b50ba8544e451740000000000000000000000005a9dbbc5e6bd9ecdf81d48580d861653f12ea91e0000000000000000000000003b823dc7087d1ba9778ab8161b791b59053a0941000000000000000000000000837299b1188328b5256a0215b18074bd71c852d70000000000000000000000004489f4dd67b275a1c364cb652ab163972659d67200000000000000000000000036345041077eab2b204d9496349a6208b7b15eb3000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff000000000000000000000000a51cd97f3090f6a16cf0cdc12b0cb4b0a95b38ea00000000000000000000000000000000000000000000000002c68af0bb14000000000000000000000000000000000000000000000000000006f05b59d3b20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b1ae4d6e2ef500000

Deployed Bytecode

0x60806040526004361061019c5760003560e01c8063976b4997116100ec578063db4015661161008a578063f7c60d4411610064578063f7c60d44146106ea578063f802a697146106fd578063f851a44014610784578063fa321c26146107a457600080fd5b8063db4015661461064c578063e1faff56146106b7578063ed7ebd0d146106ca57600080fd5b8063aaba0398116100c6578063aaba0398146103f6578063b77352d314610409578063c323acb614610429578063d784d4261461062c57600080fd5b8063976b4997146103945780639b1a2e94146103b6578063a67a40e9146103d657600080fd5b806360499aba11610159578063704b6c0211610133578063704b6c02146102fe57806370a1848c1461031e5780637e9459ba1461033e5780638769da2d1461035f57600080fd5b806360499aba14610291578063619cf2be146102b1578063702e6c06146102de57600080fd5b8063068689cd146101a15780630c8aeb56146101f157806316890fc2146102065780631c58d9c1146102195780633e0c7fc1146102395780635c60da1b14610259575b600080fd5b3480156101ad57600080fd5b506101c16101bc366004614fb2565b6107b7565b6040805182516001600160a01b031681526020808401519082015291810151908201526060015b60405180910390f35b6102046101ff366004614fcf565b61083e565b005b61020461021436600461505a565b610962565b34801561022557600080fd5b50610204610234366004615184565b6109ef565b34801561024557600080fd5b506102046102543660046151e8565b611297565b34801561026557600080fd5b50600154610279906001600160a01b031681565b6040516001600160a01b0390911681526020016101e8565b34801561029d57600080fd5b506102046102ac366004614fb2565b6113c6565b3480156102bd57600080fd5b506102d16102cc3660046151e8565b611421565b6040516101e89190615201565b3480156102ea57600080fd5b506102046102f93660046151e8565b61162b565b34801561030a57600080fd5b50610204610319366004614fb2565b611788565b34801561032a57600080fd5b50610204610339366004615184565b611808565b61035161034c366004615280565b611a94565b6040519081526020016101e8565b34801561036b57600080fd5b5061037f61037a3660046151e8565b611c6d565b604080519283526020830191909152016101e8565b3480156103a057600080fd5b506103a9611d72565b6040516101e891906152ca565b3480156103c257600080fd5b506102046103d1366004615184565b611f04565b3480156103e257600080fd5b506102046103f13660046151e8565b6120f6565b610204610404366004614fcf565b61212e565b34801561041557600080fd5b5061020461042436600461530e565b61231a565b34801561043557600080fd5b5060408051610180810182526001600160a01b037f000000000000000000000000c79102c36bbba246b8bb6ae81b50ba8544e45174811682527f0000000000000000000000005a9dbbc5e6bd9ecdf81d48580d861653f12ea91e811660208301527f0000000000000000000000003b823dc7087d1ba9778ab8161b791b59053a09418116828401527f000000000000000000000000837299b1188328b5256a0215b18074bd71c852d7811660608301527f0000000000000000000000004489f4dd67b275a1c364cb652ab163972659d672811660808301527f00000000000000000000000036345041077eab2b204d9496349a6208b7b15eb3811660a08301527f000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff811660c08301527f000000000000000000000000a51cd97f3090f6a16cf0cdc12b0cb4b0a95b38ea1660e08201527f00000000000000000000000000000000000000000000000002c68af0bb1400006101008201527f00000000000000000000000000000000000000000000000006f05b59d3b200006101208201527f00000000000000000000000000000000000000000000000000000000000000006101408201527f00000000000000000000000000000000000000000000001b1ae4d6e2ef50000061016082015290516101e8919061533e565b34801561063857600080fd5b50610204610647366004614fb2565b6124b5565b34801561065857600080fd5b5061066161252e565b6040516101e89190600060e082019050825182526020830151602083015260408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015292915050565b6102046106c5366004615440565b6125e4565b3480156106d657600080fd5b506102046106e53660046154b3565b6126f6565b6102046106f8366004614fcf565b612770565b34801561070957600080fd5b5061071d6107183660046151e8565b612887565b6040516101e89190815181526020808301516001600160a01b03169082015260408083015190820152606080830151908201526080808301519082015260a08083015115159082015260c0808301519082015260e091820151918101919091526101000190565b34801561079057600080fd5b50600054610279906001600160a01b031681565b6102046107b23660046154d5565b612a49565b60408051606081018252600080825260208083018290528284018290526001600160a01b038516825260039052919091206107f3906001612b16565b6001600160a01b0390811682528216600081815260036020818152604080842060028552808352908420548287015293909252908190526108349190612b16565b6040820152919050565b6108488333612b49565b600260005260056020527f89832631fb3c3307a103ba2c84ab569c64d6182a18893dcd163f0f1c2090733a5461087f908490612c09565b50806000036108a1576040516394613e9960e01b815260040160405180910390fd5b60006108ae338585612d03565b90506108b981612e18565b60006108c482612e5b565b905060006108d28385612fa2565b90506108df606483615554565b81116108e9575060005b60006108f4876131da565b60c08086015160608088015160408051868152602081018e905290810188905291820192909252608081019190915260a081018890529192507f0be79565c9c0f7b4144002bd9abeb6bd0e8f43023abae7066992c58bdff9397e91015b60405180910390a150505050505050565b6000196001600160a01b038716016109cc57600460005260056020527f3eec716f11ba9e820c81ca75eb978ffb45831ef8b7a53e5e422c26008e1ca6d554346109ab8288615568565b11156109ca576040516394613e9960e01b815260040160405180910390fd5b505b6109d887878784611a94565b96506109e687858585612a49565b50505050505050565b600154600160a01b900460ff1615610a1a576040516325dbe6e160e21b815260040160405180910390fd5b6001805460ff60a01b1916600160a01b179055610a37828261328f565b600082806020019051810190610a4d919061557b565b90506000610b0c7f0000000000000000000000005a9dbbc5e6bd9ecdf81d48580d861653f12ea91e6001600160a01b0316636352211e84602001516040518263ffffffff1660e01b8152600401610aa691815260200190565b602060405180830381865afa158015610ac3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ae791906155d7565b602080850151600081815260049092526040909120610b07906002612b16565b612d03565b90506000610b2b8260c00151846040015161332690919063ffffffff16565b9050610b5c81601260ff7f00000000000000000000000000000000000000000000000000000000000000061661332b565b8260a001818151610b6d91906155f4565b90525060408381015160c0840152608083015160208401519151637cbc237360e01b81526004810192909252600019602483015260009182916001600160a01b031690637cbc2373906044016020604051808303816000875af1158015610bd8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bfc9190615614565b90507f000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff6001600160a01b031684604001516001600160a01b031603610c4c57610c458183615568565b9150610db0565b60016001600160a01b031684604001516001600160a01b031603610d055760007f000000000000000000000000837299b1188328b5256a0215b18074bd71c852d76001600160a01b031663f29216e2836040518263ffffffff1660e01b8152600401604080518083038185885af1158015610ccb573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190610cf0919061562d565b509050610cfd8184615568565b925050610db0565b6040848101519051633e87f49360e01b81526001600160a01b039182166004820152602481018390526000917f000000000000000000000000837299b1188328b5256a0215b18074bd71c852d71690633e87f4939060440160408051808303816000875af1158015610d7b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d9f919061562d565b509050610dac8184615568565b9250505b5060008360a00151610dc183613365565b610dcb91906155f4565b905060007f00000000000000000000000000000000000000000000000000000000000000008213610e1d57507f0000000000000000000000000000000000000000000000000000000000000000610ebc565b610eb97f0000000000000000000000000000000000000000000000000000000000000000670de0b6b3a76400007f00000000000000000000000000000000000000000000000006f05b59d3b20000610e758387615651565b610e7f9190615671565b610e8991906156a1565b610e9391906155f4565b7f00000000000000000000000000000000000000000000001b1ae4d6e2ef50000061339b565b90505b6000610ec7826133b2565b9050838111610f1557610f046001600160a01b037f000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff1633836133d8565b610f0e81856156cf565b9350611013565b60006001600160a01b037f0000000000000000000000004489f4dd67b275a1c364cb652ab163972659d67216637cbc237382610f5188866156cf565b6040516001600160e01b031960e085901b168152600481019290925260248201526044016020604051808303816000875af1158015610f94573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fb89190615614565b9050610ff933610fc88388615568565b6001600160a01b037f000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff1691906133d8565b61100b6110068287615568565b613365565b925060009450505b61101d8284615651565b92505082156110ba57604051631c57762b60e31b815260006004820152602481018490527f0000000000000000000000004489f4dd67b275a1c364cb652ab163972659d6726001600160a01b03169063e2bbb158906044016020604051808303816000875af1158015611094573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b89190615614565b505b6110f56110ec8360ff7f00000000000000000000000000000000000000000000000000000000000000061660126134bb565b60608701510190565b6060860152600060a086015261110a856134fb565b6020808701516000908152600490915260408120611129906009612b16565b60208089015160009081526004909152604081209192509061114c90600a612b16565b60208981018051600090815260048084526040808320600984528552808320839055925182528352818120600a8252909252812081905590915061119260026007612b16565b905061119e8284615568565b6111a890826156cf565b90506111b7600260078361357c565b5050506020860151604051630852cd8d60e31b815260048101919091527f0000000000000000000000005a9dbbc5e6bd9ecdf81d48580d861653f12ea91e6001600160a01b0316906342966c6890602401600060405180830381600087803b15801561122257600080fd5b505af1158015611236573d6000803e3d6000fd5b505087516020808a0151604080519384529183015281018590527f8c3b5c5c5dddce5cc5abfa2cc66ec5dd06f76f9e9bb21debb1a85ad88fdbdb6f9250606001905060405180910390a150506001805460ff60a01b19169055505050505050565b6040516331a9108f60e11b815260048101829052600090611342906001600160a01b037f0000000000000000000000005a9dbbc5e6bd9ecdf81d48580d861653f12ea91e1690636352211e90602401602060405180830381865afa158015611303573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061132791906155d7565b60008481526004602052604090208490610b07906002612b16565b905061134d81612e18565b600061135882612e5b565b90506000611365846131da565b60c084015160608086015160408051858152602081018a905280820188905292830193909352608082015290519192507fb299642ff40e6ba13eec0f77203c3ef9816a9f64212a40467d63db0eba259b4f919081900360a00190a150505050565b6000546001600160a01b031633146113f157604051634755657960e01b815260040160405180910390fd5b61141e8161140160026007612b16565b61140b6001613597565b61141591906156cf565b600191906133d8565b50565b6114806040518061014001604052806000815260200160006001600160a01b0316815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6000828152600460205260409020611499906001612b16565b815260008281526004602052604090206114b4906002612b16565b6001600160a01b03166020808301829052600091825260039052604090206114dd906001612b16565b6001600160a01b0316631e010439836040518263ffffffff1660e01b815260040161150a91815260200190565b602060405180830381865afa158015611527573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061154b9190615614565b604080830191909152600083815260046020522061156a906003612b16565b6060820152600082815260046020819052604090912061158991612b16565b608082015260008281526004602052604090206115a7906005612b16565b60a082015260008281526004602052604090206115c5906006612b16565b60c082015260008281526004602052604090206115e3906007612b16565b60e08201526000828152600460205260409020611601906009612b16565b610100820152600082815260046020526040902061162090600a612b16565b610120820152919050565b801561141e57604051637cbc237360e01b815260006004820181905260248201839052907f0000000000000000000000004489f4dd67b275a1c364cb652ab163972659d6726001600160a01b031690637cbc2373906044016020604051808303816000875af11580156116a2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116c69190615614565b9050801561178457604051632770a7eb60e21b8152336004820152602481018290527f00000000000000000000000036345041077eab2b204d9496349a6208b7b15eb36001600160a01b031690639dc29fac90604401600060405180830381600087803b15801561173657600080fd5b505af115801561174a573d6000803e3d6000fd5b506117849250506001600160a01b037f000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff16905033836133d8565b5050565b6000546001600160a01b031633146117b357604051634755657960e01b815260040160405180910390fd5b600080546001600160a01b0319166001600160a01b0383169081179091556040519081527f71614071b88dee5e0b2ae578a9dd7b2ebbe9ae832ba419dc0242cd065a290b6c906020015b60405180910390a150565b600154600160a01b900460ff1615611833576040516325dbe6e160e21b815260040160405180910390fd5b6001805460ff60a01b1916600160a01b179055611850828261328f565b60008280602001905181019061186691906156e2565b905061187a8160200151826000015161361e565b61189181602001518260400151836060015161368f565b60006118e87f000000000000000000000000c79102c36bbba246b8bb6ae81b50ba8544e451746001600160a01b0316636352211e84602001516040518263ffffffff1660e01b8152600401610aa691815260200190565b905060006119078260c00151846080015161332690919063ffffffff16565b905061193881601260ff7f00000000000000000000000000000000000000000000000000000000000000061661332b565b8260a00181815161194991906155f4565b905250608083015160c083015260a0830151600090156119945761196c83612e18565b611991838560400151600014611986578560a0015161198a565b6000195b6000613714565b90505b61199d836134fb565b6119ab84602001513361427b565b8360a00151600003611a105783516020808601516040808801516060808a0151835196875294860193909352908401528201527f3b963caf777561b495590bdc6ec8abd33838524d0a3aafc3f6f1b7857449d0999060800160405180910390a1611a7f565b83516020808601516040808801516060808a015189840151845197885295870194909452918501528301526001600160a01b0316608082015260a081018290527fc72ca5dc9c24b437f43b8c112e7b4e24b4a7bce4b1f405e591eee1cc32d327c29060c0015b60405180910390a15b50506001805460ff60a01b1916905550505050565b600084600003611b50576040516335313c2160e11b81523360048201527f0000000000000000000000005a9dbbc5e6bd9ecdf81d48580d861653f12ea91e6001600160a01b031690636a627842906024016020604051808303816000875af1158015611b04573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b289190615614565b94508115611b4b576000858152600460205260409020611b4b90600860016142ec565b611b5a565b611b5a85336142f5565b611b63846143b5565b6000611b70338787612d03565b90506000196001600160a01b03861601611ba55734841115611ba5576040516394613e9960e01b815260040160405180910390fd5b83600003611bc6576040516394613e9960e01b815260040160405180910390fd5b6001600160a01b038516600114611bee578051611bee906001600160a01b03871690866143ff565b611bf88185614489565b611c01816134fb565b6000611c0c876131da565b60408051828152602081018a90526001600160a01b038916818301526060810188905290519192507f8eee26bd33caee967b96e711aa45ef94c734251b0457a0f2213fb392c01ef53a919081900360800190a186925050505b949350505050565b60008080611c7c600280612b16565b90506000611c8c60026003612b16565b9050611c9a60026004612b16565b600086815260046020526040812091955090611cb7906005612b16565b6000878152600460205260409020909150611cd3906006612b16565b600087815260046020526040812091955090611cf0906007612b16565b90508215611d6957600083670de0b6b3a764000080611d0f88426156cf565b611d19919061575c565b611d23919061575c565b611d2d9190615554565b9687019690508215611d6757818703670de0b6b3a7640000611d4f858361575c565b611d599190615554565b611d639088615568565b9650505b505b50505050915091565b60408051600580825260c082019092526060916020820160a08036833750506001600090815260056020527f1471eb6eb2c5e789fc3de43f8ce62938c7d1836ec861730447e2ada8fd81017b54835193945092849250611dd457611dd4615773565b6020908102919091018101919091526002600052600590527f89832631fb3c3307a103ba2c84ab569c64d6182a18893dcd163f0f1c2090733a54815182906001908110611e2357611e23615773565b6020908102919091018101919091526003600052600590527fa9bc9a3a348c357ba16b37005d7e6b3236198c0e939f4af8c5f19b8deeb8ebc054815182906002908110611e7257611e72615773565b6020908102919091018101919091526004600052600590527f3eec716f11ba9e820c81ca75eb978ffb45831ef8b7a53e5e422c26008e1ca6d554815182906003908110611ec157611ec1615773565b60200260200101818152505060056000600581526020019081526020016000205481600481518110611ef557611ef5615773565b60200260200101818152505090565b600154600160a01b900460ff1615611f2f576040516325dbe6e160e21b815260040160405180910390fd5b6001805460ff60a01b1916600160a01b179055611f4c828261328f565b600082806020019051810190611f629190615789565b9050611f768160200151826000015161361e565b6000611fcd7f0000000000000000000000005a9dbbc5e6bd9ecdf81d48580d861653f12ea91e6001600160a01b0316636352211e84602001516040518263ffffffff1660e01b8152600401610aa691815260200190565b90506000611fec8260c00151846060015161332690919063ffffffff16565b905061201d81601260ff7f00000000000000000000000000000000000000000000000000000000000000061661332b565b8260a00181815161202e91906155f4565b905250606083015160c083015261204482612e18565b60006120568385608001516001613714565b9050836040015161206684612e5b565b1015612085576040516341c092a960e01b815260040160405180910390fd5b61208e836134fb565b61209c84602001513361427b565b83516020808601516040808701518151948552928401919091526001600160a01b0390911690820152606081018290527f39f415ac8ef6b49b1ba40c4fafb2b911df1c5306c49eef15c4bc38a0128fca9890608001611a76565b6000546001600160a01b0316331461212157604051634755657960e01b815260040160405180910390fd5b61141e600260068361357c565b826000036121c7576040516335313c2160e11b81523360048201527f000000000000000000000000c79102c36bbba246b8bb6ae81b50ba8544e451746001600160a01b031690636a627842906024016020604051808303816000875af115801561219c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121c09190615614565b92506121d1565b6121d18333612b49565b6121da826143b5565b60006121e7338585612d03565b6001600090815260056020527f1471eb6eb2c5e789fc3de43f8ce62938c7d1836ec861730447e2ada8fd81017b5491925090612224908690612c09565b90506000196001600160a01b0385160161223c578092505b8260000361225d576040516394613e9960e01b815260040160405180910390fd5b6001600160a01b038416600114612285578151612285906001600160a01b03861690856143ff565b61228f8284614489565b61229882612e18565b60006122a383612e5b565b90506122ae836134fb565b60006122b9876131da565b60c08086015160608088015160408051868152602081018e9052908101889052918201929092526080810191909152600060a08201529192507f0be79565c9c0f7b4144002bd9abeb6bd0e8f43023abae7066992c58bdff9397e9101610951565b6000816123af576040516331a9108f60e11b8152600481018490527f0000000000000000000000005a9dbbc5e6bd9ecdf81d48580d861653f12ea91e6001600160a01b031690636352211e90602401602060405180830381865afa158015612386573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123aa91906155d7565b612438565b6040516331a9108f60e11b8152600481018490527f000000000000000000000000c79102c36bbba246b8bb6ae81b50ba8544e451746001600160a01b031690636352211e90602401602060405180830381865afa158015612414573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061243891906155d7565b60008481526004602052604081209192509061245590600a612b16565b905080156124af57600061246b60026007612b16565b905061247782826156cf565b9050612486600260078361357c565b6000858152600460209081526040808320600a84529091528120556124ad600184846133d8565b505b50505050565b6000546001600160a01b031633146124e057604051634755657960e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f6b70829fcbe4891157f7a7496f9870927de3c8237adbe9cd39bae09b7382c409906020016117fd565b61256e6040518060e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b61257a60026001612b16565b8152612587600280612b16565b602082015261259860026003612b16565b60408201526125a960026004612b16565b60608201526125ba60026005612b16565b60808201526125cb60026006612b16565b60a08201526125dc60026007612b16565b60c082015290565b6125ee86336142f5565b600560008190526020527f458b30c2d72bfd2c6317304a4594ecbafe5f729d3111b65fdc3a33bd48e5432d54612625908790612c09565b5083600003612647576040516394613e9960e01b815260040160405180910390fd5b6000612654338888612d03565b905061265f81612e18565b600061266a82612e5b565b905060006126788388612fa2565b9050612685606483615554565b811161268f575060005b600061269a8a6131da565b90507ecae5d4a40f5ab841867a076a796230392d8ce403d409f84f18377b49a602d5818b848760c0015188606001518d8d8d8d6040516126e299989796959493929190615830565b60405180910390a150505050505050505050565b6000546001600160a01b0316331461272157604051634755657960e01b815260040160405180910390fd5b60008281526005602090815260409182902083905581518481529081018390527f7ee33d5ad485da9f8837dbf37e57c80fabc9165cc5c74f9d760028895a34badc910160405180910390a15050565b61277a83336142f5565b600360005260056020527fa9bc9a3a348c357ba16b37005d7e6b3236198c0e939f4af8c5f19b8deeb8ebc0546127b1908490612c09565b50806000036127d3576040516394613e9960e01b815260040160405180910390fd5b60006127e0338585612d03565b90506127eb81612e18565b60006127f682612e5b565b905060006128048385612fa2565b9050612811606483615554565b811161281b575060005b6000612826876131da565b60c08086015160608088015160408051868152602081018e905290810188905291820192909252608081019190915260a081018890529192507f88ad2ade0e436c3cffc2b0d1b51fb1379e5bca5dd3aaef801b234b18aa87d2a99101610951565b6128da6040518061010001604052806000815260200160006001600160a01b0316815260200160008152602001600081526020016000815260200160001515815260200160008152602001600081525090565b60008281526004602052604090206128f3906001612b16565b8152600082815260046020526040902061290e906002612b16565b6001600160a01b0316602080830182905260009182526003905260409020612937906001612b16565b6001600160a01b0316631e010439836040518263ffffffff1660e01b815260040161296491815260200190565b602060405180830381865afa158015612981573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129a59190615614565b60408083019190915260008381526004602052206129c4906003612b16565b606082015260008281526004602081905260409091206129e391612b16565b60808201526000828152600460205260409020612a019060086146ea565b151560a08201526000828152600460205260409020612a21906009612b16565b60c08201526000828152600460205260409020612a3f90600a612b16565b60e0820152919050565b612a5384336142f5565b600460005260056020527f3eec716f11ba9e820c81ca75eb978ffb45831ef8b7a53e5e422c26008e1ca6d554612a8a908590612c09565b506000848152600460205260408120612aac9033908790610b07906002612b16565b9050612ab781612e18565b6000612ac282612e5b565b90506000612acf876131da565b90507fd8eb4847ae2a642c0c24f4336eb3ac86a4cac889da47bb554f95c4d6163d80ff8188848660c0015187606001518b8b8b60405161095198979695949392919061587f565b60ff81166000908152602083905260408120545b90505b92915050565b60ff166000908152602091909152604090205490565b806001600160a01b03167f000000000000000000000000c79102c36bbba246b8bb6ae81b50ba8544e451746001600160a01b0316636352211e846040518263ffffffff1660e01b8152600401612ba191815260200190565b602060405180830381865afa158015612bbe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612be291906155d7565b6001600160a01b0316146117845760405163a03dca6f60e01b815260040160405180910390fd5b600080612c1860026006612b16565b905082341015612c3b5760405163e6b6589360e01b815260040160405180910390fd5b6000612c4782856156cf565b9050600081612c5860026007612b16565b612c629190615568565b9050612c71600260078361357c565b6000868152600460205260408120612c8a906009612b16565b600088815260046020526040812091925090612ca790600a612b16565b9050612cb38282615568565b6000898152600460205260409020859350909150612cd39060098461357c565b6000888152600460205260409020612ced90600a8361357c565b612cf787346156cf565b98975050505050505050565b612d6d60405180610120016040528060006001600160a01b031681526020016000815260200160006001600160a01b031681526020016000815260200160006001600160a01b03168152602001600081526020016000815260200160008152602001600081525090565b6001600160a01b0380851682526020820184905282166040820152612d9460026001612b16565b60608201526001600160a01b0382166000908152600360205260409020612dbc906001612b16565b6001600160a01b031660808201526000838152600460205260409020612de3906003612b16565b60a08201526000838152600460208190526040909120612e0291612b16565b60c0820152612e118383614704565b9392505050565b6040808201516001600160a01b0316600090815260036020819052919020612e3f91612b16565b60e08201526040810151612e5290614800565b61010090910152565b600080670de0b6b3a76400008360e00151670de0b6b3a764000085610100015186608001516001600160a01b0316631e01043988602001516040518263ffffffff1660e01b8152600401612eb191815260200190565b602060405180830381865afa158015612ece573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ef29190615614565b612efc919061575c565b612f069190615554565b612f10919061575c565b612f1a9190615554565b90506000808460a001511215612f4557612f408460a00151612f3b906158c5565b6133b2565b612f48565b60005b9050808210612f9b57612f987f000000000000000000000000000000000000000000000000000000000000000660ff166012612f918760a001518661497d90919063ffffffff16565b91906149aa565b92505b5050919050565b6000826101000151600019612fb79190615554565b821015612b2d5760808301516020840151604051631e01043960e01b815260048101919091526000916001600160a01b031690631e01043990602401602060405180830381865afa158015613010573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130349190615614565b90508083106130d45760008460a0015113156130cf576000670de0b6b3a7640000856101000151838661306791906156cf565b613071919061575c565b61307b9190615554565b9050600061308c8660a001516133b2565b9050818111156130cc576130c960ff7f0000000000000000000000000000000000000000000000000000000000000006166012612f9185856156cf565b93505b50505b6131d3565b6000670de0b6b3a76400008560e00151670de0b6b3a764000087610100015187866130ff91906156cf565b613109919061575c565b6131139190615554565b61311d919061575c565b6131279190615554565b905060008560a001511261317c576131757f000000000000000000000000000000000000000000000000000000000000000660ff166012612f918860a001518561497d90919063ffffffff16565b92506131d1565b600061318f8660a00151612f3b906158c5565b9050808211156131cf576131cc60ff7f0000000000000000000000000000000000000000000000000000000000000006166012612f9184866156cf565b93505b505b505b5092915050565b6000806131e960026005612b16565b90506131f66001826158e1565b905061320e600260056001600160801b03841661357c565b6000838152600460205260408120613227906001612b16565b90506132346001826158e1565b600085815260046020526040902090915061325a9060016001600160801b03841661357c565b60006132866001600160801b0383166fffffffffffffffffffffffffffffffff19608086901b16615568565b95945050505050565b815160208301207f19457468657265756d205369676e6564204d6573736167653a0a3332000000006000908152601c91909152603c90206001600160a01b037f000000000000000000000000a51cd97f3090f6a16cf0cdc12b0cb4b0a95b38ea166132fa82846149dd565b6001600160a01b03161461332157604051638baa579f60e01b815260040160405180910390fd5b505050565b900390565b6000806133398585856134bb565b90506000851280156133555750846133528285876134bb565b14155b15611c6557613286600182615651565b60006001600160ff1b0382111561339757604051632a26bd1360e01b8152600481018390526024015b60405180910390fd5b5090565b6000818313156133ab5781612b2a565b5090919050565b60008082121561339757604051632a33bb3160e01b81526004810183905260240161338e565b60006133e384613597565b90506000196001600160a01b03851601613471576000836001600160a01b03168360405160006040518083038185875af1925050503d8060008114613444576040519150601f19603f3d011682016040523d82523d6000602084013e613449565b606091505b505090508061346b5760405163e277d13760e01b815260040160405180910390fd5b50613485565b6134856001600160a01b0385168484614a01565b600061349085613597565b905061349c8382615568565b82146124ad5760405163f4d4667360e01b815260040160405180910390fd5b60008183146134f3576134cf83600a6159e5565b6134da83600a6159e5565b6134e49086615671565b6134ee91906156a1565b611c65565b509192915050565b606081015161350f9060029060019061357c565b604080820151602080840151600090815260049091529190912061353591600290614a64565b60a0810151602080830151600090815260049091526040902061355a9160039061357c565b60c081015160208083015160009081526004918290526040902061141e929091905b805b60ff909216600090815260209390935250604090912055565b60006001600160a01b038216600114613617576040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401602060405180830381865afa1580156135ee573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906136129190615614565b612b2d565b4792915050565b600082815260046020526040902081906001600160801b03821690613644906001612b16565b14613662576040516302e8145360e61b815260040160405180910390fd5b613321600161367183826158e1565b600086815260046020526040902091906001600160801b031661357c565b60008061369b85611c6d565b90925090506136ac6002804261357c565b6136b9600260038561357c565b6136c6600260048461357c565b60008581526004602052604090206136e09060058661357c565b60008581526004602052604090206136fa9060068361357c565b60008581526004602052604090206124ad9060078461357c565b81613729670de0b6b3a7640000600019615554565b8110801561373b575060008460a00151125b156137f7577f000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff6001600160a01b031684604001516001600160a01b03160361379f5761378e8460a00151612f3b906158c5565b6137989082615568565b90506137f7565b6064846101000151670de0b6b3a76400006137c18760a00151612f3b906158c5565b6137cb919061575c565b6137d59190615554565b6137e090606961575c565b6137ea9190615554565b6137f49082615568565b90505b60808401516020850151604051637cbc237360e01b81526001600160a01b0390921691637cbc237391613837918590600401918252602082015260400190565b6020604051808303816000875af1158015613856573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061387a9190615614565b9050600080600085841115613a8a57600061389587866156cf565b905060007f000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff6001600160a01b031689604001516001600160a01b0316036138e95750806138e281876156cf565b9550613a5e565b60016001600160a01b031689604001516001600160a01b0316036139a9576000807f000000000000000000000000837299b1188328b5256a0215b18074bd71c852d76001600160a01b031663f29216e2856040518263ffffffff1660e01b8152600401604080518083038185885af1158015613969573d6000803e3d6000fd5b50505050506040513d601f19601f8201168201806040525081019061398e919061562d565b90935083925090506139a081896156cf565b97505050613a5e565b6040898101519051633e87f49360e01b81526001600160a01b0391821660048201526024810184905260009182917f000000000000000000000000837299b1188328b5256a0215b18074bd71c852d790911690633e87f4939060440160408051808303816000875af1158015613a23573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613a47919061562d565b9093508392509050613a5981896156cf565b975050505b613a688186615568565b9450613a7381613365565b8960a001818151613a8491906155f4565b90525050505b600084118015613a9e575060008760a00151125b15613ccf576000613ab68860a00151612f3b906158c5565b905060007f000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff6001600160a01b031689604001516001600160a01b031603613b1a57818610613b11575080613b0a81876156cf565b9550613ca3565b50600094613ca3565b60016001600160a01b031689604001516001600160a01b031603613be7576000807f000000000000000000000000837299b1188328b5256a0215b18074bd71c852d76001600160a01b0316637661f4f689866040518363ffffffff1660e01b8152600401613b8a91815260200190565b604080518083038185885af1158015613ba7573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190613bcc919061562d565b9093508392509050613bde81896156cf565b97505050613ca3565b604089810151905163bf441a5960e01b81526001600160a01b039182166004820152602481018490526044810188905260009182917f000000000000000000000000837299b1188328b5256a0215b18074bd71c852d79091169063bf441a599060640160408051808303816000875af1158015613c68573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613c8c919061562d565b9093508392509050613c9e81896156cf565b975050505b613cad8186615568565b9450613cb881613365565b8960a001818151613cc991906155f4565b90525050505b60008760a001511315613edd576000613cf2670de0b6b3a7640000600019615554565b8710613d0c57613d058860a001516133b2565b9050613d77565b7f000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff6001600160a01b031688604001516001600160a01b0316148015613d5057508685105b15613d7757613d74613d658960a001516133b2565b613d6f878a6156cf565b614a73565b90505b8015613edb57600084821115613e8e5760006001600160a01b037f0000000000000000000000004489f4dd67b275a1c364cb652ab163972659d67216637cbc237382613dc389876156cf565b6040516001600160e01b031960e085901b168152600481019290925260248201526044016020604051808303816000875af1158015613e06573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613e2a9190615614565b9050613e3686846156cf565b811015613e78578715613e5f5780613e4e87856156cf565b613e5891906156cf565b9350613e78565b6040516320b7831f60e11b815260040160405180910390fd5b613e828187615568565b91506000955050613e9d565b5080613e9a81866156cf565b94505b613ea78185615568565b9350613eb283613365565b613ebb82613365565b613ec591906155f4565b8960a001818151613ed69190615651565b905250505b505b8215613f7757604051631c57762b60e31b815260006004820152602481018490527f0000000000000000000000004489f4dd67b275a1c364cb652ab163972659d6726001600160a01b03169063e2bbb158906044016020604051808303816000875af1158015613f51573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613f759190615614565b505b81156141c157841561400d577f000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff6001600160a01b031687604001516001600160a01b031603613fd157613fca8285615568565b93506141c1565b8651614008906001600160a01b037f000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff1690846133d8565b6141c1565b7f000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff6001600160a01b031687604001516001600160a01b03160361405457613fca8285615568565b60016001600160a01b031687604001516001600160a01b03160361411557604051632cb4d9d960e11b8152600481018390526000907f000000000000000000000000837299b1188328b5256a0215b18074bd71c852d76001600160a01b031690635969b3b29060240160408051808303816000875af11580156140db573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140ff919061562d565b915061410d90508186615568565b9450506141c1565b604087810151905163045608af60e01b81526001600160a01b039182166004820152602481018490526000917f000000000000000000000000837299b1188328b5256a0215b18074bd71c852d7169063045608af9060440160408051808303816000875af115801561418b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141af919061562d565b91506141bd90508186615568565b9450505b83156141e457865160408801516141e4916001600160a01b0390911690866133d8565b80156142715786516040516340c10f1960e01b81526001600160a01b039182166004820152602481018390527f00000000000000000000000036345041077eab2b204d9496349a6208b7b15eb3909116906340c10f1990604401600060405180830381600087803b15801561425857600080fd5b505af115801561426c573d6000803e3d6000fd5b505050505b5050509392505050565b6000828152600460205260408120614294906009612b16565b905080156133215760006142aa60026007612b16565b90506142b682826156cf565b90506142c5600260078361357c565b6000848152600460209081526040808320600984529091528120556124af600184846133d8565b61357e81614a83565b806001600160a01b03167f0000000000000000000000005a9dbbc5e6bd9ecdf81d48580d861653f12ea91e6001600160a01b0316636352211e846040518263ffffffff1660e01b815260040161434d91815260200190565b602060405180830381865afa15801561436a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061438e91906155d7565b6001600160a01b03161461178457604051639289a15b60e01b815260040160405180910390fd5b6001600160a01b03811660009081526003602052604081206143d8906001612b16565b6001600160a01b03160361141e57604051634033aec960e01b815260040160405180910390fd5b6000196001600160a01b038416016144315734811461332157604051630fc9bd0f60e11b815260040160405180910390fd5b600061443c84613597565b90506144536001600160a01b038516843085614a9d565b600061445e85613597565b905061446a8383615568565b81146124ad57604051630fc9bd0f60e11b815260040160405180910390fd5b7f000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff6001600160a01b031682604001516001600160a01b0316036145c6576000670de0b6b3a76400006144fb7f00000000000000000000000000000000000000000000000002c68af0bb1400008461575c565b6145059190615554565b905061451181836156cf565b604051631c57762b60e31b815260006004820152602481018390529092507f0000000000000000000000004489f4dd67b275a1c364cb652ab163972659d6726001600160a01b03169063e2bbb158906044016020604051808303816000875af1158015614582573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906145a69190615614565b506145b081613365565b8360a0018181516145c191906155f4565b905250505b60016001600160a01b031682604001516001600160a01b0316036146675781608001516001600160a01b031663e2bbb158828460200151846040518463ffffffff1660e01b8152600401614624929190918252602082015260400190565b60206040518083038185885af1158015614642573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906133219190615614565b60808201516020830151604051631c57762b60e31b81526001600160a01b039092169163e2bbb158916146a7918590600401918252602082015260400190565b6020604051808303816000875af11580156146c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133219190615614565b60ff81166000908152602083905260408120541515612b2a565b600082815260046020526040812061471d906002612b16565b90506001600160a01b038116158015906147495750816001600160a01b0316816001600160a01b031614155b15613321576001600160a01b0381166000908152600360205260408120614771906001612b16565b6001600160a01b0316635d988967856040518263ffffffff1660e01b815260040161479e91815260200190565b602060405180830381865afa1580156147bb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906147df9190615614565b905080156124af57604051634033aec960e01b815260040160405180910390fd5b60007f000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff6001600160a01b0316826001600160a01b03160361484a5750670de0b6b3a7640000919050565b600061485e836001600160a01b0316614ad5565b90506149548160ff167f000000000000000000000000000000000000000000000000000000000000000660ff16612f917f0000000000000000000000003b823dc7087d1ba9778ab8161b791b59053a09416001600160a01b031663698439406148f56002600360008c6001600160a01b03166001600160a01b03168152602001908152602001600020612b3390919063ffffffff16565b6040518263ffffffff1660e01b815260040161491391815260200190565b602060405180830381865afa158015614930573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f3b9190615614565b9150816000036149775760405163089112d560e21b815260040160405180910390fd5b50919050565b6000808212614997576149908284615568565b9050612b2d565b6149a0826158c5565b61499090846156cf565b60008183146134f3576149be83600a6159e5565b6149c983600a6159e5565b6149d3908661575c565b6134ee9190615554565b60008060006149ec8585614b52565b915091506149f981614b97565b509392505050565b6040516001600160a01b03831660248201526044810182905261332190849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152614ce1565b6001600160a01b03811661357e565b6000818311156133ab5781612b2a565b600081614a91576000614a94565b60015b60ff1692915050565b6040516001600160a01b03808516602483015283166044820152606481018290526124af9085906323b872dd60e01b90608401614a2d565b60006001600160a01b038216600114614b4a57816001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015614b26573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061361291906159f1565b601292915050565b6000808251604103614b885760208301516040840151606085015160001a614b7c87828585614db6565b94509450505050614b90565b506000905060025b9250929050565b6000816004811115614bab57614bab615a14565b03614bb35750565b6001816004811115614bc757614bc7615a14565b03614c145760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e61747572650000000000000000604482015260640161338e565b6002816004811115614c2857614c28615a14565b03614c755760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e67746800604482015260640161338e565b6003816004811115614c8957614c89615a14565b0361141e5760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b606482015260840161338e565b6000614d36826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316614e7a9092919063ffffffff16565b9050805160001480614d57575080806020019051810190614d579190615a2a565b6133215760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161338e565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0831115614ded5750600090506003614e71565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015614e41573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116614e6a57600060019250925050614e71565b9150600090505b94509492505050565b6060611c65848460008585600080866001600160a01b03168587604051614ea19190615a6b565b60006040518083038185875af1925050503d8060008114614ede576040519150601f19603f3d011682016040523d82523d6000602084013e614ee3565b606091505b5091509150614ef487838387614eff565b979650505050505050565b60608315614f6e578251600003614f67576001600160a01b0385163b614f675760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161338e565b5081611c65565b611c658383815115614f835781518083602001fd5b8060405162461bcd60e51b815260040161338e9190615a87565b6001600160a01b038116811461141e57600080fd5b600060208284031215614fc457600080fd5b8135612e1181614f9d565b600080600060608486031215614fe457600080fd5b833592506020840135614ff681614f9d565b929592945050506040919091013590565b60008083601f84011261501957600080fd5b50813567ffffffffffffffff81111561503157600080fd5b6020830191508360208260051b8501011115614b9057600080fd5b801515811461141e57600080fd5b600080600080600080600060c0888a03121561507557600080fd5b87359650602088013561508781614f9d565b95506040880135945060608801359350608088013567ffffffffffffffff8111156150b157600080fd5b6150bd8a828b01615007565b90945092505060a08801356150d18161504c565b8091505092959891949750929550565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261510857600080fd5b813567ffffffffffffffff80821115615123576151236150e1565b604051601f8301601f19908116603f0116810190828211818310171561514b5761514b6150e1565b8160405283815286602085880101111561516457600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806040838503121561519757600080fd5b823567ffffffffffffffff808211156151af57600080fd5b6151bb868387016150f7565b935060208501359150808211156151d157600080fd5b506151de858286016150f7565b9150509250929050565b6000602082840312156151fa57600080fd5b5035919050565b81518152602080830151610140830191615225908401826001600160a01b03169052565b5060408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015260e083015160e083015261010080840151818401525061012080840151818401525092915050565b6000806000806080858703121561529657600080fd5b8435935060208501356152a881614f9d565b92506040850135915060608501356152bf8161504c565b939692955090935050565b6020808252825182820181905260009190848201906040850190845b81811015615302578351835292840192918401916001016152e6565b50909695505050505050565b6000806040838503121561532157600080fd5b8235915060208301356153338161504c565b809150509250929050565b81516001600160a01b031681526101808101602083015161536a60208401826001600160a01b03169052565b50604083015161538560408401826001600160a01b03169052565b5060608301516153a060608401826001600160a01b03169052565b5060808301516153bb60808401826001600160a01b03169052565b5060a08301516153d660a08401826001600160a01b03169052565b5060c08301516153f160c08401826001600160a01b03169052565b5060e083015161540c60e08401826001600160a01b03169052565b5061010083810151908301526101208084015190830152610140808401519083015261016092830151929091019190915290565b60008060008060008060a0878903121561545957600080fd5b86359550602087013561546b81614f9d565b94506040870135935060608701359250608087013567ffffffffffffffff81111561549557600080fd5b6154a189828a01615007565b979a9699509497509295939492505050565b600080604083850312156154c657600080fd5b50508035926020909101359150565b600080600080606085870312156154eb57600080fd5b8435935060208501359250604085013567ffffffffffffffff81111561551057600080fd5b61551c87828801615007565b95989497509550505050565b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60008261556357615563615528565b500490565b80820180821115612b2d57612b2d61553e565b60006060828403121561558d57600080fd5b6040516060810181811067ffffffffffffffff821117156155b0576155b06150e1565b80604052508251815260208301516020820152604083015160408201528091505092915050565b6000602082840312156155e957600080fd5b8151612e1181614f9d565b80820182811260008312801582168215821617156131d1576131d161553e565b60006020828403121561562657600080fd5b5051919050565b6000806040838503121561564057600080fd5b505080516020909101519092909150565b81810360008312801583831316838312821617156131d3576131d361553e565b80820260008212600160ff1b8414161561568d5761568d61553e565b8181058314821517612b2d57612b2d61553e565b6000826156b0576156b0615528565b600160ff1b8214600019841416156156ca576156ca61553e565b500590565b81810381811115612b2d57612b2d61553e565b600060c082840312156156f457600080fd5b60405160c0810181811067ffffffffffffffff82111715615717576157176150e1565b8060405250825181526020830151602082015260408301516040820152606083015160608201526080830151608082015260a083015160a08201528091505092915050565b8082028115828204841417612b2d57612b2d61553e565b634e487b7160e01b600052603260045260246000fd5b600060a0828403121561579b57600080fd5b60405160a0810181811067ffffffffffffffff821117156157be576157be6150e1565b806040525082518152602083015160208201526040830151604082015260608301516060820152608083015160808201528091505092915050565b8183526000602080850194508260005b8581101561582557813587529582019590820190600101615809565b509495945050505050565b60006101008b83528a60208401528960408401528860608401528760808401528660a08401528560c08401528060e084015261586f81840185876157f9565b9c9b505050505050505050505050565b8881528760208201528660408201528560608201528460808201528360a082015260e060c082015260006158b760e0830184866157f9565b9a9950505050505050505050565b6000600160ff1b82016158da576158da61553e565b5060000390565b6001600160801b038181168382160190808211156131d3576131d361553e565b600181815b8085111561593c5781600019048211156159225761592261553e565b8085161561592f57918102915b93841c9390800290615906565b509250929050565b60008261595357506001612b2d565b8161596057506000612b2d565b816001811461597657600281146159805761599c565b6001915050612b2d565b60ff8411156159915761599161553e565b50506001821b612b2d565b5060208310610133831016604e8410600b84101617156159bf575081810a612b2d565b6159c98383615901565b80600019048211156159dd576159dd61553e565b029392505050565b6000612b2a8383615944565b600060208284031215615a0357600080fd5b815160ff81168114612e1157600080fd5b634e487b7160e01b600052602160045260246000fd5b600060208284031215615a3c57600080fd5b8151612e118161504c565b60005b83811015615a62578181015183820152602001615a4a565b50506000910152565b60008251615a7d818460208701615a47565b9190910192915050565b6020815260008251806020840152615aa6816040850160208701615a47565b601f01601f1916919091016040019291505056fea2646970667358221220a51bc7cc468f19984b4c513e11e7ea00ac0fd17e28e0446592be85d8d888e0b164736f6c63430008140033

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

000000000000000000000000c79102c36bbba246b8bb6ae81b50ba8544e451740000000000000000000000005a9dbbc5e6bd9ecdf81d48580d861653f12ea91e0000000000000000000000003b823dc7087d1ba9778ab8161b791b59053a0941000000000000000000000000837299b1188328b5256a0215b18074bd71c852d70000000000000000000000004489f4dd67b275a1c364cb652ab163972659d67200000000000000000000000036345041077eab2b204d9496349a6208b7b15eb3000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff000000000000000000000000a51cd97f3090f6a16cf0cdc12b0cb4b0a95b38ea00000000000000000000000000000000000000000000000002c68af0bb14000000000000000000000000000000000000000000000000000006f05b59d3b20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b1ae4d6e2ef500000

-----Decoded View---------------
Arg [0] : lToken_ (address): 0xC79102C36BBbA246B8Bb6aE81B50ba8544e45174
Arg [1] : pToken_ (address): 0x5A9dBbC5E6BD9ECdf81D48580D861653f12Ea91E
Arg [2] : oracle_ (address): 0x3b823dc7087d1ba9778aB8161b791B59053a0941
Arg [3] : swapper_ (address): 0x837299B1188328B5256A0215B18074bd71c852D7
Arg [4] : vault0_ (address): 0x4489F4dD67B275a1c364cb652ab163972659D672
Arg [5] : iou_ (address): 0x36345041077eAb2b204D9496349a6208b7B15EB3
Arg [6] : tokenB0_ (address): 0x176211869cA2b568f2A7D4EE941E073a821EE1ff
Arg [7] : dChainEventSigner_ (address): 0xa51Cd97F3090f6a16Cf0cdC12B0cB4b0a95b38EA
Arg [8] : b0ReserveRatio_ (uint256): 200000000000000000
Arg [9] : liquidationRewardCutRatio_ (int256): 500000000000000000
Arg [10] : minLiquidationReward_ (int256): 0
Arg [11] : maxLiquidationReward_ (int256): 500000000000000000000

-----Encoded View---------------
12 Constructor Arguments found :
Arg [0] : 000000000000000000000000c79102c36bbba246b8bb6ae81b50ba8544e45174
Arg [1] : 0000000000000000000000005a9dbbc5e6bd9ecdf81d48580d861653f12ea91e
Arg [2] : 0000000000000000000000003b823dc7087d1ba9778ab8161b791b59053a0941
Arg [3] : 000000000000000000000000837299b1188328b5256a0215b18074bd71c852d7
Arg [4] : 0000000000000000000000004489f4dd67b275a1c364cb652ab163972659d672
Arg [5] : 00000000000000000000000036345041077eab2b204d9496349a6208b7b15eb3
Arg [6] : 000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff
Arg [7] : 000000000000000000000000a51cd97f3090f6a16cf0cdc12b0cb4b0a95b38ea
Arg [8] : 00000000000000000000000000000000000000000000000002c68af0bb140000
Arg [9] : 00000000000000000000000000000000000000000000000006f05b59d3b20000
Arg [10] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [11] : 00000000000000000000000000000000000000000000001b1ae4d6e2ef500000


Block Transaction Gas Used Reward
view all blocks sequenced

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
[ Download: CSV Export  ]

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