Address Details
contract

0x3e60cE358ee58748456d17ffF72941Ec92eE1DB8

Contract Name
ExchangeProxy
Creator
0xe456f9–e60f03 at 0x5560b1–fd48ce
Balance
0 CELO ( )
Locked CELO Balance
0.00 CELO
Voting CELO Balance
0.00 CELO
Pending Unlocked Gold
0.00 CELO
Tokens
Fetching tokens...
Transactions
0 Transactions
Transfers
0 Transfers
Gas Used
Fetching gas used...
Last Balance Update
11126716
This contract has been verified via Sourcify. View contract in Sourcify repository
Contract name:
ExchangeProxy




Optimization enabled
true
Compiler version
v0.8.11+commit.d7f03943




Optimization runs
200
EVM Version
london




Verified at
2022-01-26T15:50:35.861146Z

contracts/proxies/ExchangeProxy.sol

// SPDX-License-Identifier: MIT

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity >=0.7.12;
pragma experimental ABIEncoderV2;

contract Context {
    // Empty internal constructor, to prevent people from mistakenly deploying
    // an instance of this contract, which should be used via inheritance.
    constructor () { }
    // solhint-disable-previous-line no-empty-blocks

    function _msgSender() internal view returns (address payable) {
        return payable(msg.sender);
    }

    function _msgData() internal view returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}
contract Ownable is Context {
    address private _owner;

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

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor () {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

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

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

    /**
     * @dev Returns true if the caller is the current owner.
     */
    function isOwner() public view returns (bool) {
        return _msgSender() == _owner;
    }

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

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public onlyOwner {
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     */
    function _transferOwnership(address newOwner) internal {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     *
     * _Available since v2.4.0._
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

interface PoolInterface {
    function swapExactAmountIn(address, uint, address, uint, uint) external returns (uint, uint);
    function swapExactAmountOut(address, uint, address, uint, uint) external returns (uint, uint);
    function calcInGivenOut(uint, uint, uint, uint, uint, uint) external pure returns (uint);
    function calcOutGivenIn(uint, uint, uint, uint, uint, uint) external pure returns (uint);
    function getDenormalizedWeight(address) external view returns (uint);
    function getBalance(address) external view returns (uint);
    function getSwapFee() external view returns (uint);
}

interface TokenInterface {
    function balanceOf(address) external view returns (uint);
    function allowance(address, address) external view returns (uint);
    function approve(address, uint) external returns (bool);
    function transfer(address, uint) external returns (bool);
    function transferFrom(address, address, uint) external returns (bool);
    function deposit() external payable;
    function withdraw(uint) external;
}

interface RegistryInterface {
    function getBestPoolsWithLimit(address, address, uint) external view returns (address[] memory);
}

contract ExchangeProxy is Ownable {

    using SafeMath for uint256;

    struct Pool {
        address pool;
        uint    tokenBalanceIn;
        uint    tokenWeightIn;
        uint    tokenBalanceOut;
        uint    tokenWeightOut;
        uint    swapFee;
        uint    effectiveLiquidity;
    }

    struct Swap {
        address pool;
        address tokenIn;
        address tokenOut;
        uint    swapAmount; // tokenInAmount / tokenOutAmount
        uint    limitReturnAmount; // minAmountOut / maxAmountIn
        uint    maxPrice;
    }

    TokenInterface weth;
    RegistryInterface registry;
    address private constant ETH_ADDRESS = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
    uint private constant BONE = 10**18;

    constructor(address _weth) {
        weth = TokenInterface(_weth);
    }

    function setRegistry(address _registry) external onlyOwner {
        registry = RegistryInterface(_registry);
    }

    function batchSwapExactIn(
        Swap[] memory swaps,
        TokenInterface tokenIn,
        TokenInterface tokenOut,
        uint totalAmountIn,
        uint minTotalAmountOut
    )
        public payable
        returns (uint totalAmountOut)
    {
        transferFromAll(tokenIn, totalAmountIn);

        for (uint i = 0; i < swaps.length; i++) {
            Swap memory swap = swaps[i];
            TokenInterface SwapTokenIn = TokenInterface(swap.tokenIn);
            PoolInterface pool = PoolInterface(swap.pool);

            if (SwapTokenIn.allowance(address(this), swap.pool) > 0) {
                SwapTokenIn.approve(swap.pool, 0);
            }
            SwapTokenIn.approve(swap.pool, swap.swapAmount);

            (uint tokenAmountOut,) = pool.swapExactAmountIn(
                                        swap.tokenIn,
                                        swap.swapAmount,
                                        swap.tokenOut,
                                        swap.limitReturnAmount,
                                        swap.maxPrice
                                    );
            totalAmountOut = tokenAmountOut.add(totalAmountOut);
        }

        require(totalAmountOut >= minTotalAmountOut, "ERR_LIMIT_OUT");

        transferAll(tokenOut, totalAmountOut);
        transferAll(tokenIn, getBalance(tokenIn));
    }

    function batchSwapExactOut(
        Swap[] memory swaps,
        TokenInterface tokenIn,
        TokenInterface tokenOut,
        uint maxTotalAmountIn
    )
        public payable
        returns (uint totalAmountIn)
    {
        transferFromAll(tokenIn, maxTotalAmountIn);

        for (uint i = 0; i < swaps.length; i++) {
            Swap memory swap = swaps[i];
            TokenInterface SwapTokenIn = TokenInterface(swap.tokenIn);
            PoolInterface pool = PoolInterface(swap.pool);

            if (SwapTokenIn.allowance(address(this), swap.pool) > 0) {
                SwapTokenIn.approve(swap.pool, 0);
            }
            SwapTokenIn.approve(swap.pool, swap.limitReturnAmount);

            (uint tokenAmountIn,) = pool.swapExactAmountOut(
                                        swap.tokenIn,
                                        swap.limitReturnAmount,
                                        swap.tokenOut,
                                        swap.swapAmount,
                                        swap.maxPrice
                                    );
            totalAmountIn = tokenAmountIn.add(totalAmountIn);
        }
        require(totalAmountIn <= maxTotalAmountIn, "ERR_LIMIT_IN");

        transferAll(tokenOut, getBalance(tokenOut));
        transferAll(tokenIn, getBalance(tokenIn));

    }

    function multihopBatchSwapExactIn(
        Swap[][] memory swapSequences,
        TokenInterface tokenIn,
        TokenInterface tokenOut,
        uint totalAmountIn,
        uint minTotalAmountOut
    )
        public payable
        returns (uint totalAmountOut)
    {

        transferFromAll(tokenIn, totalAmountIn);

        for (uint i = 0; i < swapSequences.length; i++) {
            uint tokenAmountOut;
            for (uint k = 0; k < swapSequences[i].length; k++) {
                Swap memory swap = swapSequences[i][k];
                TokenInterface SwapTokenIn = TokenInterface(swap.tokenIn);
                if (k == 1) {
                    // Makes sure that on the second swap the output of the first was used
                    // so there is not intermediate token leftover
                    swap.swapAmount = tokenAmountOut;
                }

                PoolInterface pool = PoolInterface(swap.pool);
                if (SwapTokenIn.allowance(address(this), swap.pool) > 0) {
                    SwapTokenIn.approve(swap.pool, 0);
                }
                SwapTokenIn.approve(swap.pool, swap.swapAmount);
                (tokenAmountOut,) = pool.swapExactAmountIn(
                                            swap.tokenIn,
                                            swap.swapAmount,
                                            swap.tokenOut,
                                            swap.limitReturnAmount,
                                            swap.maxPrice
                                        );
            }
            // This takes the amountOut of the last swap
            totalAmountOut = tokenAmountOut.add(totalAmountOut);
        }

        require(totalAmountOut >= minTotalAmountOut, "ERR_LIMIT_OUT");

        transferAll(tokenOut, totalAmountOut);
        transferAll(tokenIn, getBalance(tokenIn));

    }

    function multihopBatchSwapExactOut(
        Swap[][] memory swapSequences,
        TokenInterface tokenIn,
        TokenInterface tokenOut,
        uint maxTotalAmountIn
    )
        public payable
        returns (uint totalAmountIn)
    {

        transferFromAll(tokenIn, maxTotalAmountIn);

        for (uint i = 0; i < swapSequences.length; i++) {
            uint tokenAmountInFirstSwap;
            // Specific code for a simple swap and a multihop (2 swaps in sequence)
            if (swapSequences[i].length == 1) {
                Swap memory swap = swapSequences[i][0];
                TokenInterface SwapTokenIn = TokenInterface(swap.tokenIn);

                PoolInterface pool = PoolInterface(swap.pool);
                if (SwapTokenIn.allowance(address(this), swap.pool) > 0) {
                    SwapTokenIn.approve(swap.pool, 0);
                }
                SwapTokenIn.approve(swap.pool, swap.limitReturnAmount);

                (tokenAmountInFirstSwap,) = pool.swapExactAmountOut(
                                        swap.tokenIn,
                                        swap.limitReturnAmount,
                                        swap.tokenOut,
                                        swap.swapAmount,
                                        swap.maxPrice
                                    );
            } else {
                // Consider we are swapping A -> B and B -> C. The goal is to buy a given amount
                // of token C. But first we need to buy B with A so we can then buy C with B
                // To get the exact amount of C we then first need to calculate how much B we'll need:
                uint intermediateTokenAmount; // This would be token B as described above
                Swap memory secondSwap = swapSequences[i][1];
                PoolInterface poolSecondSwap = PoolInterface(secondSwap.pool);
                intermediateTokenAmount = poolSecondSwap.calcInGivenOut(
                                        poolSecondSwap.getBalance(secondSwap.tokenIn),
                                        poolSecondSwap.getDenormalizedWeight(secondSwap.tokenIn),
                                        poolSecondSwap.getBalance(secondSwap.tokenOut),
                                        poolSecondSwap.getDenormalizedWeight(secondSwap.tokenOut),
                                        secondSwap.swapAmount,
                                        poolSecondSwap.getSwapFee()
                                    );

                //// Buy intermediateTokenAmount of token B with A in the first pool
                Swap memory firstSwap = swapSequences[i][0];
                TokenInterface FirstSwapTokenIn = TokenInterface(firstSwap.tokenIn);
                PoolInterface poolFirstSwap = PoolInterface(firstSwap.pool);
                if (FirstSwapTokenIn.allowance(address(this), firstSwap.pool) < type(uint256).max) {
                    FirstSwapTokenIn.approve(firstSwap.pool, type(uint256).max);
                }

                (tokenAmountInFirstSwap,) = poolFirstSwap.swapExactAmountOut(
                                        firstSwap.tokenIn,
                                        firstSwap.limitReturnAmount,
                                        firstSwap.tokenOut,
                                        intermediateTokenAmount, // This is the amount of token B we need
                                        firstSwap.maxPrice
                                    );

                //// Buy the final amount of token C desired
                TokenInterface SecondSwapTokenIn = TokenInterface(secondSwap.tokenIn);
                if (SecondSwapTokenIn.allowance(address(this), secondSwap.pool) < type(uint256).max) {
                    SecondSwapTokenIn.approve(secondSwap.pool, type(uint256).max);
                }

                poolSecondSwap.swapExactAmountOut(
                                        secondSwap.tokenIn,
                                        secondSwap.limitReturnAmount,
                                        secondSwap.tokenOut,
                                        secondSwap.swapAmount,
                                        secondSwap.maxPrice
                                    );
            }
            totalAmountIn = tokenAmountInFirstSwap.add(totalAmountIn);
        }

        require(totalAmountIn <= maxTotalAmountIn, "ERR_LIMIT_IN");

        transferAll(tokenOut, getBalance(tokenOut));
        transferAll(tokenIn, getBalance(tokenIn));

    }

    function smartSwapExactIn(
        TokenInterface tokenIn,
        TokenInterface tokenOut,
        uint totalAmountIn,
        uint minTotalAmountOut,
        uint nPools
    )
        public payable
        returns (uint totalAmountOut)
    {
        Swap[] memory swaps;
        if (isETH(tokenIn)) {
          (swaps,) = viewSplitExactIn(address(weth), address(tokenOut), totalAmountIn, nPools);
        } else if (isETH(tokenOut)){
          (swaps,) = viewSplitExactIn(address(tokenIn), address(weth), totalAmountIn, nPools);
        } else {
          (swaps,) = viewSplitExactIn(address(tokenIn), address(tokenOut), totalAmountIn, nPools);
        }

        totalAmountOut = batchSwapExactIn(swaps, tokenIn, tokenOut, totalAmountIn, minTotalAmountOut);
    }

    function smartSwapExactOut(
        TokenInterface tokenIn,
        TokenInterface tokenOut,
        uint totalAmountOut,
        uint maxTotalAmountIn,
        uint nPools
    )
        public payable
        returns (uint totalAmountIn)
    {
        Swap[] memory swaps;
        if (isETH(tokenIn)) {
          (swaps,) = viewSplitExactOut(address(weth), address(tokenOut), totalAmountOut, nPools);
        } else if (isETH(tokenOut)){
          (swaps,) = viewSplitExactOut(address(tokenIn), address(weth), totalAmountOut, nPools);
        } else {
          (swaps,) = viewSplitExactOut(address(tokenIn), address(tokenOut), totalAmountOut, nPools);
        }

        totalAmountIn = batchSwapExactOut(swaps, tokenIn, tokenOut, maxTotalAmountIn);
    }

    function viewSplitExactIn(
        address tokenIn,
        address tokenOut,
        uint swapAmount,
        uint nPools
    )
        public view
        returns (Swap[] memory swaps, uint totalOutput)
    {
        address[] memory poolAddresses = registry.getBestPoolsWithLimit(tokenIn, tokenOut, nPools);

        Pool[] memory pools = new Pool[](poolAddresses.length);
        uint sumEffectiveLiquidity;
        for (uint i = 0; i < poolAddresses.length; i++) {
            pools[i] = getPoolData(tokenIn, tokenOut, poolAddresses[i]);
            sumEffectiveLiquidity = sumEffectiveLiquidity.add(pools[i].effectiveLiquidity);
        }

        uint[] memory bestInputAmounts = new uint[](pools.length);
        uint totalInputAmount;
        for (uint i = 0; i < pools.length; i++) {
            bestInputAmounts[i] = swapAmount.mul(pools[i].effectiveLiquidity).div(sumEffectiveLiquidity);
            totalInputAmount = totalInputAmount.add(bestInputAmounts[i]);
        }

        if (totalInputAmount < swapAmount) {
            bestInputAmounts[0] = bestInputAmounts[0].add(swapAmount.sub(totalInputAmount));
        } else {
            bestInputAmounts[0] = bestInputAmounts[0].sub(totalInputAmount.sub(swapAmount));
        }

        swaps = new Swap[](pools.length);

        for (uint i = 0; i < pools.length; i++) {
            swaps[i] = Swap({
                        pool: pools[i].pool,
                        tokenIn: tokenIn,
                        tokenOut: tokenOut,
                        swapAmount: bestInputAmounts[i],
                        limitReturnAmount: 0,
                        maxPrice: type(uint256).max
                    });
        }

        totalOutput = calcTotalOutExactIn(bestInputAmounts, pools);

        return (swaps, totalOutput);
    }

    function viewSplitExactOut(
        address tokenIn,
        address tokenOut,
        uint swapAmount,
        uint nPools
    )
        public view
        returns (Swap[] memory swaps, uint totalOutput)
    {
        address[] memory poolAddresses = registry.getBestPoolsWithLimit(tokenIn, tokenOut, nPools);

        Pool[] memory pools = new Pool[](poolAddresses.length);
        uint sumEffectiveLiquidity;
        for (uint i = 0; i < poolAddresses.length; i++) {
            pools[i] = getPoolData(tokenIn, tokenOut, poolAddresses[i]);
            sumEffectiveLiquidity = sumEffectiveLiquidity.add(pools[i].effectiveLiquidity);
        }

        uint[] memory bestInputAmounts = new uint[](pools.length);
        uint totalInputAmount;
        for (uint i = 0; i < pools.length; i++) {
            bestInputAmounts[i] = swapAmount.mul(pools[i].effectiveLiquidity).div(sumEffectiveLiquidity);
            totalInputAmount = totalInputAmount.add(bestInputAmounts[i]);
        }

         if (totalInputAmount < swapAmount) {
            bestInputAmounts[0] = bestInputAmounts[0].add(swapAmount.sub(totalInputAmount));
        } else {
            bestInputAmounts[0] = bestInputAmounts[0].sub(totalInputAmount.sub(swapAmount));
        }

        swaps = new Swap[](pools.length);

        for (uint i = 0; i < pools.length; i++) {
            swaps[i] = Swap({
                        pool: pools[i].pool,
                        tokenIn: tokenIn,
                        tokenOut: tokenOut,
                        swapAmount: bestInputAmounts[i],
                        limitReturnAmount: type(uint256).max,
                        maxPrice: type(uint256).max
                    });
        }

        totalOutput = calcTotalOutExactOut(bestInputAmounts, pools);

        return (swaps, totalOutput);
    }

    function getPoolData(
        address tokenIn,
        address tokenOut,
        address poolAddress
    )
        internal view
        returns (Pool memory)
    {
        PoolInterface pool = PoolInterface(poolAddress);
        uint tokenBalanceIn = pool.getBalance(tokenIn);
        uint tokenBalanceOut = pool.getBalance(tokenOut);
        uint tokenWeightIn = pool.getDenormalizedWeight(tokenIn);
        uint tokenWeightOut = pool.getDenormalizedWeight(tokenOut);
        uint swapFee = pool.getSwapFee();

        uint effectiveLiquidity = calcEffectiveLiquidity(
                                            tokenWeightIn,
                                            tokenBalanceOut,
                                            tokenWeightOut
                                        );
        Pool memory returnPool = Pool({
            pool: poolAddress,
            tokenBalanceIn: tokenBalanceIn,
            tokenWeightIn: tokenWeightIn,
            tokenBalanceOut: tokenBalanceOut,
            tokenWeightOut: tokenWeightOut,
            swapFee: swapFee,
            effectiveLiquidity: effectiveLiquidity
        });

        return returnPool;
    }

    function calcEffectiveLiquidity(
        uint tokenWeightIn,
        uint tokenBalanceOut,
        uint tokenWeightOut
    )
        internal pure
        returns (uint effectiveLiquidity)
    {

        // Bo * wi/(wi+wo)
        effectiveLiquidity =
            tokenWeightIn.mul(BONE).div(
                tokenWeightOut.add(tokenWeightIn)
            ).mul(tokenBalanceOut).div(BONE);

        return effectiveLiquidity;
    }

    function calcTotalOutExactIn(
        uint[] memory bestInputAmounts,
        Pool[] memory bestPools
    )
        internal pure
        returns (uint totalOutput)
    {
        totalOutput = 0;
        for (uint i = 0; i < bestInputAmounts.length; i++) {
            uint output = PoolInterface(bestPools[i].pool).calcOutGivenIn(
                                bestPools[i].tokenBalanceIn,
                                bestPools[i].tokenWeightIn,
                                bestPools[i].tokenBalanceOut,
                                bestPools[i].tokenWeightOut,
                                bestInputAmounts[i],
                                bestPools[i].swapFee
                            );

            totalOutput = totalOutput.add(output);
        }
        return totalOutput;
    }

    function calcTotalOutExactOut(
        uint[] memory bestInputAmounts,
        Pool[] memory bestPools
    )
        internal pure
        returns (uint totalOutput)
    {
        totalOutput = 0;
        for (uint i = 0; i < bestInputAmounts.length; i++) {
            uint output = PoolInterface(bestPools[i].pool).calcInGivenOut(
                                bestPools[i].tokenBalanceIn,
                                bestPools[i].tokenWeightIn,
                                bestPools[i].tokenBalanceOut,
                                bestPools[i].tokenWeightOut,
                                bestInputAmounts[i],
                                bestPools[i].swapFee
                            );

            totalOutput = totalOutput.add(output);
        }
        return totalOutput;
    }

    function transferFromAll(TokenInterface token, uint amount) internal returns(bool) {
        if (isETH(token)) {
            weth.deposit{value: msg.value}();
        } else {
            require(token.transferFrom(msg.sender, address(this), amount), "ERR_TRANSFER_FAILED");
        }
        return true;
    }

    function getBalance(TokenInterface token) internal view returns (uint) {
        if (isETH(token)) {
            return weth.balanceOf(address(this));
        } else {
            return token.balanceOf(address(this));
        }
    }

    function transferAll(TokenInterface token, uint amount) internal returns(bool) {
        if (amount == 0) {
            return true;
        }

        if (isETH(token)) {
            weth.withdraw(amount);
            (bool xfer,) = msg.sender.call{value: amount}("");
            require(xfer, "ERR_ETH_FAILED");
        } else {
            require(token.transfer(msg.sender, amount), "ERR_TRANSFER_FAILED");
        }
        return true;
    }

    function isETH(TokenInterface token) internal pure returns(bool) {
        return (address(token) == ETH_ADDRESS);
    }

     receive() external payable {}
}
        

Contract ABI

[{"type":"constructor","stateMutability":"nonpayable","inputs":[{"type":"address","name":"_weth","internalType":"address"}]},{"type":"event","name":"OwnershipTransferred","inputs":[{"type":"address","name":"previousOwner","internalType":"address","indexed":true},{"type":"address","name":"newOwner","internalType":"address","indexed":true}],"anonymous":false},{"type":"function","stateMutability":"payable","outputs":[{"type":"uint256","name":"totalAmountOut","internalType":"uint256"}],"name":"batchSwapExactIn","inputs":[{"type":"tuple[]","name":"swaps","internalType":"struct ExchangeProxy.Swap[]","components":[{"type":"address","name":"pool","internalType":"address"},{"type":"address","name":"tokenIn","internalType":"address"},{"type":"address","name":"tokenOut","internalType":"address"},{"type":"uint256","name":"swapAmount","internalType":"uint256"},{"type":"uint256","name":"limitReturnAmount","internalType":"uint256"},{"type":"uint256","name":"maxPrice","internalType":"uint256"}]},{"type":"address","name":"tokenIn","internalType":"contract TokenInterface"},{"type":"address","name":"tokenOut","internalType":"contract TokenInterface"},{"type":"uint256","name":"totalAmountIn","internalType":"uint256"},{"type":"uint256","name":"minTotalAmountOut","internalType":"uint256"}]},{"type":"function","stateMutability":"payable","outputs":[{"type":"uint256","name":"totalAmountIn","internalType":"uint256"}],"name":"batchSwapExactOut","inputs":[{"type":"tuple[]","name":"swaps","internalType":"struct ExchangeProxy.Swap[]","components":[{"type":"address","name":"pool","internalType":"address"},{"type":"address","name":"tokenIn","internalType":"address"},{"type":"address","name":"tokenOut","internalType":"address"},{"type":"uint256","name":"swapAmount","internalType":"uint256"},{"type":"uint256","name":"limitReturnAmount","internalType":"uint256"},{"type":"uint256","name":"maxPrice","internalType":"uint256"}]},{"type":"address","name":"tokenIn","internalType":"contract TokenInterface"},{"type":"address","name":"tokenOut","internalType":"contract TokenInterface"},{"type":"uint256","name":"maxTotalAmountIn","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"isOwner","inputs":[]},{"type":"function","stateMutability":"payable","outputs":[{"type":"uint256","name":"totalAmountOut","internalType":"uint256"}],"name":"multihopBatchSwapExactIn","inputs":[{"type":"tuple[][]","name":"swapSequences","internalType":"struct ExchangeProxy.Swap[][]","components":[{"type":"address","name":"pool","internalType":"address"},{"type":"address","name":"tokenIn","internalType":"address"},{"type":"address","name":"tokenOut","internalType":"address"},{"type":"uint256","name":"swapAmount","internalType":"uint256"},{"type":"uint256","name":"limitReturnAmount","internalType":"uint256"},{"type":"uint256","name":"maxPrice","internalType":"uint256"}]},{"type":"address","name":"tokenIn","internalType":"contract TokenInterface"},{"type":"address","name":"tokenOut","internalType":"contract TokenInterface"},{"type":"uint256","name":"totalAmountIn","internalType":"uint256"},{"type":"uint256","name":"minTotalAmountOut","internalType":"uint256"}]},{"type":"function","stateMutability":"payable","outputs":[{"type":"uint256","name":"totalAmountIn","internalType":"uint256"}],"name":"multihopBatchSwapExactOut","inputs":[{"type":"tuple[][]","name":"swapSequences","internalType":"struct ExchangeProxy.Swap[][]","components":[{"type":"address","name":"pool","internalType":"address"},{"type":"address","name":"tokenIn","internalType":"address"},{"type":"address","name":"tokenOut","internalType":"address"},{"type":"uint256","name":"swapAmount","internalType":"uint256"},{"type":"uint256","name":"limitReturnAmount","internalType":"uint256"},{"type":"uint256","name":"maxPrice","internalType":"uint256"}]},{"type":"address","name":"tokenIn","internalType":"contract TokenInterface"},{"type":"address","name":"tokenOut","internalType":"contract TokenInterface"},{"type":"uint256","name":"maxTotalAmountIn","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"owner","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"renounceOwnership","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setRegistry","inputs":[{"type":"address","name":"_registry","internalType":"address"}]},{"type":"function","stateMutability":"payable","outputs":[{"type":"uint256","name":"totalAmountOut","internalType":"uint256"}],"name":"smartSwapExactIn","inputs":[{"type":"address","name":"tokenIn","internalType":"contract TokenInterface"},{"type":"address","name":"tokenOut","internalType":"contract TokenInterface"},{"type":"uint256","name":"totalAmountIn","internalType":"uint256"},{"type":"uint256","name":"minTotalAmountOut","internalType":"uint256"},{"type":"uint256","name":"nPools","internalType":"uint256"}]},{"type":"function","stateMutability":"payable","outputs":[{"type":"uint256","name":"totalAmountIn","internalType":"uint256"}],"name":"smartSwapExactOut","inputs":[{"type":"address","name":"tokenIn","internalType":"contract TokenInterface"},{"type":"address","name":"tokenOut","internalType":"contract TokenInterface"},{"type":"uint256","name":"totalAmountOut","internalType":"uint256"},{"type":"uint256","name":"maxTotalAmountIn","internalType":"uint256"},{"type":"uint256","name":"nPools","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"transferOwnership","inputs":[{"type":"address","name":"newOwner","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple[]","name":"swaps","internalType":"struct ExchangeProxy.Swap[]","components":[{"type":"address","name":"pool","internalType":"address"},{"type":"address","name":"tokenIn","internalType":"address"},{"type":"address","name":"tokenOut","internalType":"address"},{"type":"uint256","name":"swapAmount","internalType":"uint256"},{"type":"uint256","name":"limitReturnAmount","internalType":"uint256"},{"type":"uint256","name":"maxPrice","internalType":"uint256"}]},{"type":"uint256","name":"totalOutput","internalType":"uint256"}],"name":"viewSplitExactIn","inputs":[{"type":"address","name":"tokenIn","internalType":"address"},{"type":"address","name":"tokenOut","internalType":"address"},{"type":"uint256","name":"swapAmount","internalType":"uint256"},{"type":"uint256","name":"nPools","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple[]","name":"swaps","internalType":"struct ExchangeProxy.Swap[]","components":[{"type":"address","name":"pool","internalType":"address"},{"type":"address","name":"tokenIn","internalType":"address"},{"type":"address","name":"tokenOut","internalType":"address"},{"type":"uint256","name":"swapAmount","internalType":"uint256"},{"type":"uint256","name":"limitReturnAmount","internalType":"uint256"},{"type":"uint256","name":"maxPrice","internalType":"uint256"}]},{"type":"uint256","name":"totalOutput","internalType":"uint256"}],"name":"viewSplitExactOut","inputs":[{"type":"address","name":"tokenIn","internalType":"address"},{"type":"address","name":"tokenOut","internalType":"address"},{"type":"uint256","name":"swapAmount","internalType":"uint256"},{"type":"uint256","name":"nPools","internalType":"uint256"}]},{"type":"receive","stateMutability":"payable"}]
              

Contract Creation Code

0x60806040523480156200001157600080fd5b50604051620031993803806200319983398101604081905262000034916200009b565b600080546001600160a01b031916339081178255604051909182917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a350600180546001600160a01b0319166001600160a01b0392909216919091179055620000cd565b600060208284031215620000ae57600080fd5b81516001600160a01b0381168114620000c657600080fd5b9392505050565b6130bc80620000dd6000396000f3fe6080604052600436106100c65760003560e01c80638743ad581161007f578063a91ee0dc11610059578063a91ee0dc146101e9578063b40f39ee14610209578063e2b397461461021c578063f2fde38b1461022f57600080fd5b80638743ad58146101835780638da5cb5b146101965780638f32d59b146101be57600080fd5b806321b0eb85146100d25780632db58134146100f8578063368bb1fc1461010b5780634b0f93fb14610139578063715018a61461015957806386b2ecc41461017057600080fd5b366100cd57005b600080fd5b6100e56100e036600461298f565b61024f565b6040519081526020015b60405180910390f35b6100e5610106366004612b43565b6102d0565b34801561011757600080fd5b5061012b610126366004612bac565b610599565b6040516100ef929190612bf2565b34801561014557600080fd5b5061012b610154366004612bac565b6109e1565b34801561016557600080fd5b5061016e610d66565b005b6100e561017e366004612d09565b610dda565b6100e5610191366004612d41565b6116b4565b3480156101a257600080fd5b506000546040516001600160a01b0390911681526020016100ef565b3480156101ca57600080fd5b506000546001600160a01b0316331460405190151581526020016100ef565b3480156101f557600080fd5b5061016e610204366004612db4565b611972565b6100e561021736600461298f565b6119be565b6100e561022a366004612dd1565b611a33565b34801561023b57600080fd5b5061016e61024a366004612db4565b611cef565b6000606061025c87611d25565b1561028157600154610279906001600160a01b03168787866109e1565b5090506102b8565b61028a86611d25565b156102a8576001546102799088906001600160a01b031687866109e1565b6102b4878787866109e1565b5090505b6102c581888888886116b4565b979650505050505050565b60006102dc8483611d47565b5060005b855181101561052b5760008682815181106102fd576102fd612e0b565b602090810291909101810151908101518151604051636eb1769f60e11b81523060048201526001600160a01b0380831660248301529394509192909160009184169063dd62ed3e90604401602060405180830381865afa158015610365573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103899190612e21565b111561040457825160405163095ea7b360e01b81526001600160a01b0384169163095ea7b3916103bf9190600090600401612e3a565b6020604051808303816000875af11580156103de573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104029190612e53565b505b8251608084015160405163095ea7b360e01b81526001600160a01b0385169263095ea7b39261043592600401612e3a565b6020604051808303816000875af1158015610454573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104789190612e53565b5060208301516080840151604080860151606087015160a08801519251631f17a7a960e21b81526000956001600160a01b03881695637c5e9ea4956104c39592949193600401612e75565b60408051808303816000875af11580156104e1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105059190612ea9565b5090506105128187611e89565b955050505050808061052390612ee3565b9150506102e0565b50818111156105705760405162461bcd60e51b815260206004820152600c60248201526b22a9292fa624a6a4aa2fa4a760a11b60448201526064015b60405180910390fd5b6105828361057d85611eef565b611f98565b506105908461057d86611eef565b50949350505050565b60025460405163bfdbfc4360e01b81526001600160a01b03868116600483015285811660248301526044820184905260609260009283929091169063bfdbfc4390606401600060405180830381865afa1580156105fa573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526106229190810190612efe565b9050600081516001600160401b0381111561063f5761063f6129e0565b60405190808252806020026020018201604052801561067857816020015b6106656128e3565b81526020019060019003908161065d5790505b5090506000805b835181101561070d576106ac8a8a86848151811061069f5761069f612e0b565b60200260200101516120d0565b8382815181106106be576106be612e0b565b60200260200101819052506106f98382815181106106de576106de612e0b565b602002602001015160c0015183611e8990919063ffffffff16565b91508061070581612ee3565b91505061067f565b50600082516001600160401b03811115610729576107296129e0565b604051908082528060200260200182016040528015610752578160200160208202803683370190505b5090506000805b84518110156107fa5761079c8461079687848151811061077b5761077b612e0b565b602002602001015160c001518d61235490919063ffffffff16565b906123d3565b8382815181106107ae576107ae612e0b565b6020026020010181815250506107e68382815181106107cf576107cf612e0b565b602002602001015183611e8990919063ffffffff16565b9150806107f281612ee3565b915050610759565b508881101561085d576108396108108a83612415565b8360008151811061082357610823612e0b565b6020026020010151611e8990919063ffffffff16565b8260008151811061084c5761084c612e0b565b6020026020010181815250506108b3565b61089361086a828b612415565b8360008151811061087d5761087d612e0b565b602002602001015161241590919063ffffffff16565b826000815181106108a6576108a6612e0b565b6020026020010181815250505b83516001600160401b038111156108cc576108cc6129e0565b60405190808252806020026020018201604052801561090557816020015b6108f2612929565b8152602001906001900390816108ea5790505b50965060005b84518110156109c6576040518060c0016040528086838151811061093157610931612e0b565b6020026020010151600001516001600160a01b031681526020018d6001600160a01b031681526020018c6001600160a01b0316815260200184838151811061097b5761097b612e0b565b6020026020010151815260200160001981526020016000198152508882815181106109a8576109a8612e0b565b602002602001018190525080806109be90612ee3565b91505061090b565b506109d18285612457565b9550505050505094509492505050565b60025460405163bfdbfc4360e01b81526001600160a01b03868116600483015285811660248301526044820184905260609260009283929091169063bfdbfc4390606401600060405180830381865afa158015610a42573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610a6a9190810190612efe565b9050600081516001600160401b03811115610a8757610a876129e0565b604051908082528060200260200182016040528015610ac057816020015b610aad6128e3565b815260200190600190039081610aa55790505b5090506000805b8351811015610b2d57610ae78a8a86848151811061069f5761069f612e0b565b838281518110610af957610af9612e0b565b6020026020010181905250610b198382815181106106de576106de612e0b565b915080610b2581612ee3565b915050610ac7565b50600082516001600160401b03811115610b4957610b496129e0565b604051908082528060200260200182016040528015610b72578160200160208202803683370190505b5090506000805b8451811015610be257610b9b8461079687848151811061077b5761077b612e0b565b838281518110610bad57610bad612e0b565b602002602001018181525050610bce8382815181106107cf576107cf612e0b565b915080610bda81612ee3565b915050610b79565b5088811015610c1c57610bf86108108a83612415565b82600081518110610c0b57610c0b612e0b565b602002602001018181525050610c49565b610c2961086a828b612415565b82600081518110610c3c57610c3c612e0b565b6020026020010181815250505b83516001600160401b03811115610c6257610c626129e0565b604051908082528060200260200182016040528015610c9b57816020015b610c88612929565b815260200190600190039081610c805790505b50965060005b8451811015610d5b576040518060c00160405280868381518110610cc757610cc7612e0b565b6020026020010151600001516001600160a01b031681526020018d6001600160a01b031681526020018c6001600160a01b03168152602001848381518110610d1157610d11612e0b565b6020026020010151815260200160008152602001600019815250888281518110610d3d57610d3d612e0b565b60200260200101819052508080610d5390612ee3565b915050610ca1565b506109d182856125ed565b6000546001600160a01b03163314610d905760405162461bcd60e51b815260040161056790612f8c565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6000610de68483611d47565b5060005b855181101561052b576000868281518110610e0757610e07612e0b565b60200260200101515160011415611058576000878381518110610e2c57610e2c612e0b565b6020026020010151600081518110610e4657610e46612e0b565b602090810291909101810151908101518151604051636eb1769f60e11b81523060048201526001600160a01b0380831660248301529394509192909160009184169063dd62ed3e90604401602060405180830381865afa158015610eae573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ed29190612e21565b1115610f4d57825160405163095ea7b360e01b81526001600160a01b0384169163095ea7b391610f089190600090600401612e3a565b6020604051808303816000875af1158015610f27573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4b9190612e53565b505b8251608084015160405163095ea7b360e01b81526001600160a01b0385169263095ea7b392610f7e92600401612e3a565b6020604051808303816000875af1158015610f9d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fc19190612e53565b50806001600160a01b0316637c5e9ea484602001518560800151866040015187606001518860a001516040518663ffffffff1660e01b815260040161100a959493929190612e75565b60408051808303816000875af1158015611028573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061104c9190612ea9565b50935061169492505050565b60008088848151811061106d5761106d612e0b565b602002602001015160018151811061108757611087612e0b565b60209081029190910181015180519181015160405163f8b2cb4f60e01b81526001600160a01b03918216600482015291935082169063f8d6aed490829063f8b2cb4f90602401602060405180830381865afa1580156110ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061110e9190612e21565b6020850151604051634a46c67360e11b81526001600160a01b0391821660048201529085169063948d8ce690602401602060405180830381865afa15801561115a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061117e9190612e21565b604086810151905163f8b2cb4f60e01b81526001600160a01b0391821660048201529086169063f8b2cb4f90602401602060405180830381865afa1580156111ca573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111ee9190612e21565b6040878101519051634a46c67360e11b81526001600160a01b0391821660048201529087169063948d8ce690602401602060405180830381865afa15801561123a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061125e9190612e21565b8760600151876001600160a01b031663d4cadf686040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112a1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112c59190612e21565b6040516001600160e01b031960e089901b1681526004810196909652602486019490945260448501929092526064840152608483015260a482015260c401602060405180830381865afa158015611320573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113449190612e21565b925060008a868151811061135a5761135a612e0b565b602002602001015160008151811061137457611374612e0b565b602090810291909101810151908101518151604051636eb1769f60e11b81523060048201526001600160a01b038083166024830152939450919290916000199184169063dd62ed3e90604401602060405180830381865afa1580156113dd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114019190612e21565b101561147d57825160405163095ea7b360e01b81526001600160a01b0384169163095ea7b391611438919060001990600401612e3a565b6020604051808303816000875af1158015611457573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061147b9190612e53565b505b806001600160a01b0316637c5e9ea48460200151856080015186604001518a8860a001516040518663ffffffff1660e01b81526004016114c1959493929190612e75565b60408051808303816000875af11580156114df573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115039190612ea9565b5060208601518651604051636eb1769f60e11b81523060048201526001600160a01b03918216602482015292995090916000199183169063dd62ed3e90604401602060405180830381865afa158015611560573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115849190612e21565b101561160057855160405163095ea7b360e01b81526001600160a01b0383169163095ea7b3916115bb919060001990600401612e3a565b6020604051808303816000875af11580156115da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115fe9190612e53565b505b846001600160a01b0316637c5e9ea48760200151886080015189604001518a606001518b60a001516040518663ffffffff1660e01b8152600401611648959493929190612e75565b60408051808303816000875af1158015611666573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061168a9190612ea9565b5050505050505050505b61169e8184611e89565b92505080806116ac90612ee3565b915050610dea565b60006116c08584611d47565b5060005b865181101561190f5760008782815181106116e1576116e1612e0b565b602090810291909101810151908101518151604051636eb1769f60e11b81523060048201526001600160a01b0380831660248301529394509192909160009184169063dd62ed3e90604401602060405180830381865afa158015611749573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061176d9190612e21565b11156117e857825160405163095ea7b360e01b81526001600160a01b0384169163095ea7b3916117a39190600090600401612e3a565b6020604051808303816000875af11580156117c2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117e69190612e53565b505b8251606084015160405163095ea7b360e01b81526001600160a01b0385169263095ea7b39261181992600401612e3a565b6020604051808303816000875af1158015611838573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061185c9190612e53565b5060208301516060840151604080860151608087015160a08801519251638201aa3f60e01b81526000956001600160a01b03881695638201aa3f956118a79592949193600401612e75565b60408051808303816000875af11580156118c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118e99190612ea9565b5090506118f68187611e89565b955050505050808061190790612ee3565b9150506116c4565b50818110156119505760405162461bcd60e51b815260206004820152600d60248201526c11549497d31253525517d3d555609a1b6044820152606401610567565b61195a8482611f98565b506119688561057d87611eef565b5095945050505050565b6000546001600160a01b0316331461199c5760405162461bcd60e51b815260040161056790612f8c565b600280546001600160a01b0319166001600160a01b0392909216919091179055565b600060606119cb87611d25565b156119f0576001546119e8906001600160a01b0316878786610599565b509050611a27565b6119f986611d25565b15611a17576001546119e89088906001600160a01b03168786610599565b611a2387878786610599565b5090505b6102c5818888876102d0565b6000611a3f8584611d47565b5060005b865181101561190f576000805b888381518110611a6257611a62612e0b565b602002602001015151811015611cce576000898481518110611a8657611a86612e0b565b60200260200101518281518110611a9f57611a9f612e0b565b602002602001015190506000816020015190508260011415611ac357606082018490525b8151604051636eb1769f60e11b81523060048201526001600160a01b0380831660248301526000919084169063dd62ed3e90604401602060405180830381865afa158015611b15573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b399190612e21565b1115611bb457825160405163095ea7b360e01b81526001600160a01b0384169163095ea7b391611b6f9190600090600401612e3a565b6020604051808303816000875af1158015611b8e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611bb29190612e53565b505b8251606084015160405163095ea7b360e01b81526001600160a01b0385169263095ea7b392611be592600401612e3a565b6020604051808303816000875af1158015611c04573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c289190612e53565b50806001600160a01b0316638201aa3f84602001518560600151866040015187608001518860a001516040518663ffffffff1660e01b8152600401611c71959493929190612e75565b60408051808303816000875af1158015611c8f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cb39190612ea9565b50809550505050508080611cc690612ee3565b915050611a50565b50611cd98184611e89565b9250508080611ce790612ee3565b915050611a43565b6000546001600160a01b03163314611d195760405162461bcd60e51b815260040161056790612f8c565b611d228161277c565b50565b6001600160a01b031673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1490565b6000611d5283611d25565b15611dc557600160009054906101000a90046001600160a01b03166001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b158015611da757600080fd5b505af1158015611dbb573d6000803e3d6000fd5b5050505050611e7f565b6040516323b872dd60e01b8152336004820152306024820152604481018390526001600160a01b038416906323b872dd906064015b6020604051808303816000875af1158015611e19573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e3d9190612e53565b611e7f5760405162461bcd60e51b815260206004820152601360248201527211549497d514905394d1915497d19052531151606a1b6044820152606401610567565b5060015b92915050565b600080611e968385612fc1565b905083811015611ee85760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f7700000000006044820152606401610567565b9392505050565b6000611efa82611d25565b15611f6c576001546040516370a0823160e01b81523060048201526001600160a01b03909116906370a08231906024015b602060405180830381865afa158015611f48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e839190612e21565b6040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401611f2b565b600081611fa757506001611e83565b611fb083611d25565b156120a257600154604051632e1a7d4d60e01b8152600481018490526001600160a01b0390911690632e1a7d4d90602401600060405180830381600087803b158015611ffb57600080fd5b505af115801561200f573d6000803e3d6000fd5b50506040516000925033915084908381818185875af1925050503d8060008114612055576040519150601f19603f3d011682016040523d82523d6000602084013e61205a565b606091505b505090508061209c5760405162461bcd60e51b815260206004820152600e60248201526d11549497d1551217d1905253115160921b6044820152606401610567565b50611e7f565b60405163a9059cbb60e01b81526001600160a01b0384169063a9059cbb90611dfa9033908690600401612e3a565b6120d86128e3565b60405163f8b2cb4f60e01b81526001600160a01b038581166004830152839160009183169063f8b2cb4f90602401602060405180830381865afa158015612123573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121479190612e21565b60405163f8b2cb4f60e01b81526001600160a01b03878116600483015291925060009184169063f8b2cb4f90602401602060405180830381865afa158015612193573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121b79190612e21565b604051634a46c67360e11b81526001600160a01b03898116600483015291925060009185169063948d8ce690602401602060405180830381865afa158015612203573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122279190612e21565b604051634a46c67360e11b81526001600160a01b03898116600483015291925060009186169063948d8ce690602401602060405180830381865afa158015612273573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122979190612e21565b90506000856001600160a01b031663d4cadf686040518163ffffffff1660e01b8152600401602060405180830381865afa1580156122d9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122fd9190612e21565b9050600061230c84868561283c565b6040805160e0810182526001600160a01b039b909b168b5260208b019790975295890193909352606088019390935260808701525060a085015260c084015250909392505050565b60008261236357506000611e83565b600061236f8385612fd9565b90508261237c8583612ff8565b14611ee85760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b6064820152608401610567565b6000611ee883836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f00000000000081525061287b565b6000611ee883836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f7700008152506128b2565b6000805b83518110156125e657600083828151811061247857612478612e0b565b6020026020010151600001516001600160a01b031663f8d6aed48584815181106124a4576124a4612e0b565b6020026020010151602001518685815181106124c2576124c2612e0b565b6020026020010151604001518786815181106124e0576124e0612e0b565b6020026020010151606001518887815181106124fe576124fe612e0b565b6020026020010151608001518a888151811061251c5761251c612e0b565b60200260200101518a898151811061253657612536612e0b565b602090810291909101015160a001516040516001600160e01b031960e089901b1681526004810196909652602486019490945260448501929092526064840152608483015260a482015260c401602060405180830381865afa1580156125a0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125c49190612e21565b90506125d08382611e89565b92505080806125de90612ee3565b91505061245b565b5092915050565b6000805b83518110156125e657600083828151811061260e5761260e612e0b565b6020026020010151600001516001600160a01b031663ba9530a685848151811061263a5761263a612e0b565b60200260200101516020015186858151811061265857612658612e0b565b60200260200101516040015187868151811061267657612676612e0b565b60200260200101516060015188878151811061269457612694612e0b565b6020026020010151608001518a88815181106126b2576126b2612e0b565b60200260200101518a89815181106126cc576126cc612e0b565b602090810291909101015160a001516040516001600160e01b031960e089901b1681526004810196909652602486019490945260448501929092526064840152608483015260a482015260c401602060405180830381865afa158015612736573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061275a9190612e21565b90506127668382611e89565b925050808061277490612ee3565b9150506125f1565b6001600160a01b0381166127e15760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610567565b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000612873670de0b6b3a76400006107968561286d61285b878a611e89565b6107968a670de0b6b3a7640000612354565b90612354565b949350505050565b6000818361289c5760405162461bcd60e51b8152600401610567919061301a565b5060006128a98486612ff8565b95945050505050565b600081848411156128d65760405162461bcd60e51b8152600401610567919061301a565b5060006128a9848661306f565b6040518060e0016040528060006001600160a01b031681526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040518060c0016040528060006001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b031681526020016000815260200160008152602001600081525090565b6001600160a01b0381168114611d2257600080fd5b600080600080600060a086880312156129a757600080fd5b85356129b28161297a565b945060208601356129c28161297a565b94979496505050506040830135926060810135926080909101359150565b634e487b7160e01b600052604160045260246000fd5b60405160c081016001600160401b0381118282101715612a1857612a186129e0565b60405290565b604051601f8201601f191681016001600160401b0381118282101715612a4657612a466129e0565b604052919050565b60006001600160401b03821115612a6757612a676129e0565b5060051b60200190565b600082601f830112612a8257600080fd5b81356020612a97612a9283612a4e565b612a1e565b82815260c09283028501820192828201919087851115612ab657600080fd5b8387015b85811015612b365781818a031215612ad25760008081fd5b612ada6129f6565b8135612ae58161297a565b815281860135612af48161297a565b81870152604082810135612b078161297a565b90820152606082810135908201526080808301359082015260a080830135908201528452928401928101612aba565b5090979650505050505050565b60008060008060808587031215612b5957600080fd5b84356001600160401b03811115612b6f57600080fd5b612b7b87828801612a71565b9450506020850135612b8c8161297a565b92506040850135612b9c8161297a565b9396929550929360600135925050565b60008060008060808587031215612bc257600080fd5b8435612bcd8161297a565b93506020850135612bdd8161297a565b93969395505050506040820135916060013590565b6040808252835182820181905260009190606090818501906020808901865b83811015612c6b57815180516001600160a01b0390811687528482015181168588015288820151168887015286810151878701526080808201519087015260a0908101519086015260c09094019390820190600101612c11565b505095909501959095525092949350505050565b600082601f830112612c9057600080fd5b81356020612ca0612a9283612a4e565b82815260059290921b84018101918181019086841115612cbf57600080fd5b8286015b84811015612cfe5780356001600160401b03811115612ce25760008081fd5b612cf08986838b0101612a71565b845250918301918301612cc3565b509695505050505050565b60008060008060808587031215612d1f57600080fd5b84356001600160401b03811115612d3557600080fd5b612b7b87828801612c7f565b600080600080600060a08688031215612d5957600080fd5b85356001600160401b03811115612d6f57600080fd5b612d7b88828901612a71565b9550506020860135612d8c8161297a565b93506040860135612d9c8161297a565b94979396509394606081013594506080013592915050565b600060208284031215612dc657600080fd5b8135611ee88161297a565b600080600080600060a08688031215612de957600080fd5b85356001600160401b03811115612dff57600080fd5b612d7b88828901612c7f565b634e487b7160e01b600052603260045260246000fd5b600060208284031215612e3357600080fd5b5051919050565b6001600160a01b03929092168252602082015260400190565b600060208284031215612e6557600080fd5b81518015158114611ee857600080fd5b6001600160a01b03958616815260208101949094529190931660408301526060820192909252608081019190915260a00190565b60008060408385031215612ebc57600080fd5b505080516020909101519092909150565b634e487b7160e01b600052601160045260246000fd5b6000600019821415612ef757612ef7612ecd565b5060010190565b60006020808385031215612f1157600080fd5b82516001600160401b03811115612f2757600080fd5b8301601f81018513612f3857600080fd5b8051612f46612a9282612a4e565b81815260059190911b82018301908381019087831115612f6557600080fd5b928401925b828410156102c5578351612f7d8161297a565b82529284019290840190612f6a565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60008219821115612fd457612fd4612ecd565b500190565b6000816000190483118215151615612ff357612ff3612ecd565b500290565b60008261301557634e487b7160e01b600052601260045260246000fd5b500490565b600060208083528351808285015260005b818110156130475785810183015185820160400152820161302b565b81811115613059576000604083870101525b50601f01601f1916929092016040019392505050565b60008282101561308157613081612ecd565b50039056fea2646970667358221220ac8b35a3e54b0109c84a98a87d685aaf7f320bd4fd1f95e588b93f4ff553059064736f6c634300080b00330000000000000000000000005cb6a52db2a37540e65c52ed0256211ab3b61d08

Deployed ByteCode

0x6080604052600436106100c65760003560e01c80638743ad581161007f578063a91ee0dc11610059578063a91ee0dc146101e9578063b40f39ee14610209578063e2b397461461021c578063f2fde38b1461022f57600080fd5b80638743ad58146101835780638da5cb5b146101965780638f32d59b146101be57600080fd5b806321b0eb85146100d25780632db58134146100f8578063368bb1fc1461010b5780634b0f93fb14610139578063715018a61461015957806386b2ecc41461017057600080fd5b366100cd57005b600080fd5b6100e56100e036600461298f565b61024f565b6040519081526020015b60405180910390f35b6100e5610106366004612b43565b6102d0565b34801561011757600080fd5b5061012b610126366004612bac565b610599565b6040516100ef929190612bf2565b34801561014557600080fd5b5061012b610154366004612bac565b6109e1565b34801561016557600080fd5b5061016e610d66565b005b6100e561017e366004612d09565b610dda565b6100e5610191366004612d41565b6116b4565b3480156101a257600080fd5b506000546040516001600160a01b0390911681526020016100ef565b3480156101ca57600080fd5b506000546001600160a01b0316331460405190151581526020016100ef565b3480156101f557600080fd5b5061016e610204366004612db4565b611972565b6100e561021736600461298f565b6119be565b6100e561022a366004612dd1565b611a33565b34801561023b57600080fd5b5061016e61024a366004612db4565b611cef565b6000606061025c87611d25565b1561028157600154610279906001600160a01b03168787866109e1565b5090506102b8565b61028a86611d25565b156102a8576001546102799088906001600160a01b031687866109e1565b6102b4878787866109e1565b5090505b6102c581888888886116b4565b979650505050505050565b60006102dc8483611d47565b5060005b855181101561052b5760008682815181106102fd576102fd612e0b565b602090810291909101810151908101518151604051636eb1769f60e11b81523060048201526001600160a01b0380831660248301529394509192909160009184169063dd62ed3e90604401602060405180830381865afa158015610365573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103899190612e21565b111561040457825160405163095ea7b360e01b81526001600160a01b0384169163095ea7b3916103bf9190600090600401612e3a565b6020604051808303816000875af11580156103de573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104029190612e53565b505b8251608084015160405163095ea7b360e01b81526001600160a01b0385169263095ea7b39261043592600401612e3a565b6020604051808303816000875af1158015610454573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104789190612e53565b5060208301516080840151604080860151606087015160a08801519251631f17a7a960e21b81526000956001600160a01b03881695637c5e9ea4956104c39592949193600401612e75565b60408051808303816000875af11580156104e1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105059190612ea9565b5090506105128187611e89565b955050505050808061052390612ee3565b9150506102e0565b50818111156105705760405162461bcd60e51b815260206004820152600c60248201526b22a9292fa624a6a4aa2fa4a760a11b60448201526064015b60405180910390fd5b6105828361057d85611eef565b611f98565b506105908461057d86611eef565b50949350505050565b60025460405163bfdbfc4360e01b81526001600160a01b03868116600483015285811660248301526044820184905260609260009283929091169063bfdbfc4390606401600060405180830381865afa1580156105fa573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526106229190810190612efe565b9050600081516001600160401b0381111561063f5761063f6129e0565b60405190808252806020026020018201604052801561067857816020015b6106656128e3565b81526020019060019003908161065d5790505b5090506000805b835181101561070d576106ac8a8a86848151811061069f5761069f612e0b565b60200260200101516120d0565b8382815181106106be576106be612e0b565b60200260200101819052506106f98382815181106106de576106de612e0b565b602002602001015160c0015183611e8990919063ffffffff16565b91508061070581612ee3565b91505061067f565b50600082516001600160401b03811115610729576107296129e0565b604051908082528060200260200182016040528015610752578160200160208202803683370190505b5090506000805b84518110156107fa5761079c8461079687848151811061077b5761077b612e0b565b602002602001015160c001518d61235490919063ffffffff16565b906123d3565b8382815181106107ae576107ae612e0b565b6020026020010181815250506107e68382815181106107cf576107cf612e0b565b602002602001015183611e8990919063ffffffff16565b9150806107f281612ee3565b915050610759565b508881101561085d576108396108108a83612415565b8360008151811061082357610823612e0b565b6020026020010151611e8990919063ffffffff16565b8260008151811061084c5761084c612e0b565b6020026020010181815250506108b3565b61089361086a828b612415565b8360008151811061087d5761087d612e0b565b602002602001015161241590919063ffffffff16565b826000815181106108a6576108a6612e0b565b6020026020010181815250505b83516001600160401b038111156108cc576108cc6129e0565b60405190808252806020026020018201604052801561090557816020015b6108f2612929565b8152602001906001900390816108ea5790505b50965060005b84518110156109c6576040518060c0016040528086838151811061093157610931612e0b565b6020026020010151600001516001600160a01b031681526020018d6001600160a01b031681526020018c6001600160a01b0316815260200184838151811061097b5761097b612e0b565b6020026020010151815260200160001981526020016000198152508882815181106109a8576109a8612e0b565b602002602001018190525080806109be90612ee3565b91505061090b565b506109d18285612457565b9550505050505094509492505050565b60025460405163bfdbfc4360e01b81526001600160a01b03868116600483015285811660248301526044820184905260609260009283929091169063bfdbfc4390606401600060405180830381865afa158015610a42573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610a6a9190810190612efe565b9050600081516001600160401b03811115610a8757610a876129e0565b604051908082528060200260200182016040528015610ac057816020015b610aad6128e3565b815260200190600190039081610aa55790505b5090506000805b8351811015610b2d57610ae78a8a86848151811061069f5761069f612e0b565b838281518110610af957610af9612e0b565b6020026020010181905250610b198382815181106106de576106de612e0b565b915080610b2581612ee3565b915050610ac7565b50600082516001600160401b03811115610b4957610b496129e0565b604051908082528060200260200182016040528015610b72578160200160208202803683370190505b5090506000805b8451811015610be257610b9b8461079687848151811061077b5761077b612e0b565b838281518110610bad57610bad612e0b565b602002602001018181525050610bce8382815181106107cf576107cf612e0b565b915080610bda81612ee3565b915050610b79565b5088811015610c1c57610bf86108108a83612415565b82600081518110610c0b57610c0b612e0b565b602002602001018181525050610c49565b610c2961086a828b612415565b82600081518110610c3c57610c3c612e0b565b6020026020010181815250505b83516001600160401b03811115610c6257610c626129e0565b604051908082528060200260200182016040528015610c9b57816020015b610c88612929565b815260200190600190039081610c805790505b50965060005b8451811015610d5b576040518060c00160405280868381518110610cc757610cc7612e0b565b6020026020010151600001516001600160a01b031681526020018d6001600160a01b031681526020018c6001600160a01b03168152602001848381518110610d1157610d11612e0b565b6020026020010151815260200160008152602001600019815250888281518110610d3d57610d3d612e0b565b60200260200101819052508080610d5390612ee3565b915050610ca1565b506109d182856125ed565b6000546001600160a01b03163314610d905760405162461bcd60e51b815260040161056790612f8c565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6000610de68483611d47565b5060005b855181101561052b576000868281518110610e0757610e07612e0b565b60200260200101515160011415611058576000878381518110610e2c57610e2c612e0b565b6020026020010151600081518110610e4657610e46612e0b565b602090810291909101810151908101518151604051636eb1769f60e11b81523060048201526001600160a01b0380831660248301529394509192909160009184169063dd62ed3e90604401602060405180830381865afa158015610eae573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ed29190612e21565b1115610f4d57825160405163095ea7b360e01b81526001600160a01b0384169163095ea7b391610f089190600090600401612e3a565b6020604051808303816000875af1158015610f27573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4b9190612e53565b505b8251608084015160405163095ea7b360e01b81526001600160a01b0385169263095ea7b392610f7e92600401612e3a565b6020604051808303816000875af1158015610f9d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fc19190612e53565b50806001600160a01b0316637c5e9ea484602001518560800151866040015187606001518860a001516040518663ffffffff1660e01b815260040161100a959493929190612e75565b60408051808303816000875af1158015611028573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061104c9190612ea9565b50935061169492505050565b60008088848151811061106d5761106d612e0b565b602002602001015160018151811061108757611087612e0b565b60209081029190910181015180519181015160405163f8b2cb4f60e01b81526001600160a01b03918216600482015291935082169063f8d6aed490829063f8b2cb4f90602401602060405180830381865afa1580156110ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061110e9190612e21565b6020850151604051634a46c67360e11b81526001600160a01b0391821660048201529085169063948d8ce690602401602060405180830381865afa15801561115a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061117e9190612e21565b604086810151905163f8b2cb4f60e01b81526001600160a01b0391821660048201529086169063f8b2cb4f90602401602060405180830381865afa1580156111ca573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111ee9190612e21565b6040878101519051634a46c67360e11b81526001600160a01b0391821660048201529087169063948d8ce690602401602060405180830381865afa15801561123a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061125e9190612e21565b8760600151876001600160a01b031663d4cadf686040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112a1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112c59190612e21565b6040516001600160e01b031960e089901b1681526004810196909652602486019490945260448501929092526064840152608483015260a482015260c401602060405180830381865afa158015611320573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113449190612e21565b925060008a868151811061135a5761135a612e0b565b602002602001015160008151811061137457611374612e0b565b602090810291909101810151908101518151604051636eb1769f60e11b81523060048201526001600160a01b038083166024830152939450919290916000199184169063dd62ed3e90604401602060405180830381865afa1580156113dd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114019190612e21565b101561147d57825160405163095ea7b360e01b81526001600160a01b0384169163095ea7b391611438919060001990600401612e3a565b6020604051808303816000875af1158015611457573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061147b9190612e53565b505b806001600160a01b0316637c5e9ea48460200151856080015186604001518a8860a001516040518663ffffffff1660e01b81526004016114c1959493929190612e75565b60408051808303816000875af11580156114df573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115039190612ea9565b5060208601518651604051636eb1769f60e11b81523060048201526001600160a01b03918216602482015292995090916000199183169063dd62ed3e90604401602060405180830381865afa158015611560573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115849190612e21565b101561160057855160405163095ea7b360e01b81526001600160a01b0383169163095ea7b3916115bb919060001990600401612e3a565b6020604051808303816000875af11580156115da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115fe9190612e53565b505b846001600160a01b0316637c5e9ea48760200151886080015189604001518a606001518b60a001516040518663ffffffff1660e01b8152600401611648959493929190612e75565b60408051808303816000875af1158015611666573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061168a9190612ea9565b5050505050505050505b61169e8184611e89565b92505080806116ac90612ee3565b915050610dea565b60006116c08584611d47565b5060005b865181101561190f5760008782815181106116e1576116e1612e0b565b602090810291909101810151908101518151604051636eb1769f60e11b81523060048201526001600160a01b0380831660248301529394509192909160009184169063dd62ed3e90604401602060405180830381865afa158015611749573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061176d9190612e21565b11156117e857825160405163095ea7b360e01b81526001600160a01b0384169163095ea7b3916117a39190600090600401612e3a565b6020604051808303816000875af11580156117c2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117e69190612e53565b505b8251606084015160405163095ea7b360e01b81526001600160a01b0385169263095ea7b39261181992600401612e3a565b6020604051808303816000875af1158015611838573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061185c9190612e53565b5060208301516060840151604080860151608087015160a08801519251638201aa3f60e01b81526000956001600160a01b03881695638201aa3f956118a79592949193600401612e75565b60408051808303816000875af11580156118c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118e99190612ea9565b5090506118f68187611e89565b955050505050808061190790612ee3565b9150506116c4565b50818110156119505760405162461bcd60e51b815260206004820152600d60248201526c11549497d31253525517d3d555609a1b6044820152606401610567565b61195a8482611f98565b506119688561057d87611eef565b5095945050505050565b6000546001600160a01b0316331461199c5760405162461bcd60e51b815260040161056790612f8c565b600280546001600160a01b0319166001600160a01b0392909216919091179055565b600060606119cb87611d25565b156119f0576001546119e8906001600160a01b0316878786610599565b509050611a27565b6119f986611d25565b15611a17576001546119e89088906001600160a01b03168786610599565b611a2387878786610599565b5090505b6102c5818888876102d0565b6000611a3f8584611d47565b5060005b865181101561190f576000805b888381518110611a6257611a62612e0b565b602002602001015151811015611cce576000898481518110611a8657611a86612e0b565b60200260200101518281518110611a9f57611a9f612e0b565b602002602001015190506000816020015190508260011415611ac357606082018490525b8151604051636eb1769f60e11b81523060048201526001600160a01b0380831660248301526000919084169063dd62ed3e90604401602060405180830381865afa158015611b15573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b399190612e21565b1115611bb457825160405163095ea7b360e01b81526001600160a01b0384169163095ea7b391611b6f9190600090600401612e3a565b6020604051808303816000875af1158015611b8e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611bb29190612e53565b505b8251606084015160405163095ea7b360e01b81526001600160a01b0385169263095ea7b392611be592600401612e3a565b6020604051808303816000875af1158015611c04573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c289190612e53565b50806001600160a01b0316638201aa3f84602001518560600151866040015187608001518860a001516040518663ffffffff1660e01b8152600401611c71959493929190612e75565b60408051808303816000875af1158015611c8f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cb39190612ea9565b50809550505050508080611cc690612ee3565b915050611a50565b50611cd98184611e89565b9250508080611ce790612ee3565b915050611a43565b6000546001600160a01b03163314611d195760405162461bcd60e51b815260040161056790612f8c565b611d228161277c565b50565b6001600160a01b031673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1490565b6000611d5283611d25565b15611dc557600160009054906101000a90046001600160a01b03166001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b158015611da757600080fd5b505af1158015611dbb573d6000803e3d6000fd5b5050505050611e7f565b6040516323b872dd60e01b8152336004820152306024820152604481018390526001600160a01b038416906323b872dd906064015b6020604051808303816000875af1158015611e19573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e3d9190612e53565b611e7f5760405162461bcd60e51b815260206004820152601360248201527211549497d514905394d1915497d19052531151606a1b6044820152606401610567565b5060015b92915050565b600080611e968385612fc1565b905083811015611ee85760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f7700000000006044820152606401610567565b9392505050565b6000611efa82611d25565b15611f6c576001546040516370a0823160e01b81523060048201526001600160a01b03909116906370a08231906024015b602060405180830381865afa158015611f48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e839190612e21565b6040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401611f2b565b600081611fa757506001611e83565b611fb083611d25565b156120a257600154604051632e1a7d4d60e01b8152600481018490526001600160a01b0390911690632e1a7d4d90602401600060405180830381600087803b158015611ffb57600080fd5b505af115801561200f573d6000803e3d6000fd5b50506040516000925033915084908381818185875af1925050503d8060008114612055576040519150601f19603f3d011682016040523d82523d6000602084013e61205a565b606091505b505090508061209c5760405162461bcd60e51b815260206004820152600e60248201526d11549497d1551217d1905253115160921b6044820152606401610567565b50611e7f565b60405163a9059cbb60e01b81526001600160a01b0384169063a9059cbb90611dfa9033908690600401612e3a565b6120d86128e3565b60405163f8b2cb4f60e01b81526001600160a01b038581166004830152839160009183169063f8b2cb4f90602401602060405180830381865afa158015612123573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121479190612e21565b60405163f8b2cb4f60e01b81526001600160a01b03878116600483015291925060009184169063f8b2cb4f90602401602060405180830381865afa158015612193573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121b79190612e21565b604051634a46c67360e11b81526001600160a01b03898116600483015291925060009185169063948d8ce690602401602060405180830381865afa158015612203573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122279190612e21565b604051634a46c67360e11b81526001600160a01b03898116600483015291925060009186169063948d8ce690602401602060405180830381865afa158015612273573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122979190612e21565b90506000856001600160a01b031663d4cadf686040518163ffffffff1660e01b8152600401602060405180830381865afa1580156122d9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122fd9190612e21565b9050600061230c84868561283c565b6040805160e0810182526001600160a01b039b909b168b5260208b019790975295890193909352606088019390935260808701525060a085015260c084015250909392505050565b60008261236357506000611e83565b600061236f8385612fd9565b90508261237c8583612ff8565b14611ee85760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b6064820152608401610567565b6000611ee883836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f00000000000081525061287b565b6000611ee883836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f7700008152506128b2565b6000805b83518110156125e657600083828151811061247857612478612e0b565b6020026020010151600001516001600160a01b031663f8d6aed48584815181106124a4576124a4612e0b565b6020026020010151602001518685815181106124c2576124c2612e0b565b6020026020010151604001518786815181106124e0576124e0612e0b565b6020026020010151606001518887815181106124fe576124fe612e0b565b6020026020010151608001518a888151811061251c5761251c612e0b565b60200260200101518a898151811061253657612536612e0b565b602090810291909101015160a001516040516001600160e01b031960e089901b1681526004810196909652602486019490945260448501929092526064840152608483015260a482015260c401602060405180830381865afa1580156125a0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125c49190612e21565b90506125d08382611e89565b92505080806125de90612ee3565b91505061245b565b5092915050565b6000805b83518110156125e657600083828151811061260e5761260e612e0b565b6020026020010151600001516001600160a01b031663ba9530a685848151811061263a5761263a612e0b565b60200260200101516020015186858151811061265857612658612e0b565b60200260200101516040015187868151811061267657612676612e0b565b60200260200101516060015188878151811061269457612694612e0b565b6020026020010151608001518a88815181106126b2576126b2612e0b565b60200260200101518a89815181106126cc576126cc612e0b565b602090810291909101015160a001516040516001600160e01b031960e089901b1681526004810196909652602486019490945260448501929092526064840152608483015260a482015260c401602060405180830381865afa158015612736573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061275a9190612e21565b90506127668382611e89565b925050808061277490612ee3565b9150506125f1565b6001600160a01b0381166127e15760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610567565b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000612873670de0b6b3a76400006107968561286d61285b878a611e89565b6107968a670de0b6b3a7640000612354565b90612354565b949350505050565b6000818361289c5760405162461bcd60e51b8152600401610567919061301a565b5060006128a98486612ff8565b95945050505050565b600081848411156128d65760405162461bcd60e51b8152600401610567919061301a565b5060006128a9848661306f565b6040518060e0016040528060006001600160a01b031681526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040518060c0016040528060006001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b031681526020016000815260200160008152602001600081525090565b6001600160a01b0381168114611d2257600080fd5b600080600080600060a086880312156129a757600080fd5b85356129b28161297a565b945060208601356129c28161297a565b94979496505050506040830135926060810135926080909101359150565b634e487b7160e01b600052604160045260246000fd5b60405160c081016001600160401b0381118282101715612a1857612a186129e0565b60405290565b604051601f8201601f191681016001600160401b0381118282101715612a4657612a466129e0565b604052919050565b60006001600160401b03821115612a6757612a676129e0565b5060051b60200190565b600082601f830112612a8257600080fd5b81356020612a97612a9283612a4e565b612a1e565b82815260c09283028501820192828201919087851115612ab657600080fd5b8387015b85811015612b365781818a031215612ad25760008081fd5b612ada6129f6565b8135612ae58161297a565b815281860135612af48161297a565b81870152604082810135612b078161297a565b90820152606082810135908201526080808301359082015260a080830135908201528452928401928101612aba565b5090979650505050505050565b60008060008060808587031215612b5957600080fd5b84356001600160401b03811115612b6f57600080fd5b612b7b87828801612a71565b9450506020850135612b8c8161297a565b92506040850135612b9c8161297a565b9396929550929360600135925050565b60008060008060808587031215612bc257600080fd5b8435612bcd8161297a565b93506020850135612bdd8161297a565b93969395505050506040820135916060013590565b6040808252835182820181905260009190606090818501906020808901865b83811015612c6b57815180516001600160a01b0390811687528482015181168588015288820151168887015286810151878701526080808201519087015260a0908101519086015260c09094019390820190600101612c11565b505095909501959095525092949350505050565b600082601f830112612c9057600080fd5b81356020612ca0612a9283612a4e565b82815260059290921b84018101918181019086841115612cbf57600080fd5b8286015b84811015612cfe5780356001600160401b03811115612ce25760008081fd5b612cf08986838b0101612a71565b845250918301918301612cc3565b509695505050505050565b60008060008060808587031215612d1f57600080fd5b84356001600160401b03811115612d3557600080fd5b612b7b87828801612c7f565b600080600080600060a08688031215612d5957600080fd5b85356001600160401b03811115612d6f57600080fd5b612d7b88828901612a71565b9550506020860135612d8c8161297a565b93506040860135612d9c8161297a565b94979396509394606081013594506080013592915050565b600060208284031215612dc657600080fd5b8135611ee88161297a565b600080600080600060a08688031215612de957600080fd5b85356001600160401b03811115612dff57600080fd5b612d7b88828901612c7f565b634e487b7160e01b600052603260045260246000fd5b600060208284031215612e3357600080fd5b5051919050565b6001600160a01b03929092168252602082015260400190565b600060208284031215612e6557600080fd5b81518015158114611ee857600080fd5b6001600160a01b03958616815260208101949094529190931660408301526060820192909252608081019190915260a00190565b60008060408385031215612ebc57600080fd5b505080516020909101519092909150565b634e487b7160e01b600052601160045260246000fd5b6000600019821415612ef757612ef7612ecd565b5060010190565b60006020808385031215612f1157600080fd5b82516001600160401b03811115612f2757600080fd5b8301601f81018513612f3857600080fd5b8051612f46612a9282612a4e565b81815260059190911b82018301908381019087831115612f6557600080fd5b928401925b828410156102c5578351612f7d8161297a565b82529284019290840190612f6a565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60008219821115612fd457612fd4612ecd565b500190565b6000816000190483118215151615612ff357612ff3612ecd565b500290565b60008261301557634e487b7160e01b600052601260045260246000fd5b500490565b600060208083528351808285015260005b818110156130475785810183015185820160400152820161302b565b81811115613059576000604083870101525b50601f01601f1916929092016040019392505050565b60008282101561308157613081612ecd565b50039056fea2646970667358221220ac8b35a3e54b0109c84a98a87d685aaf7f320bd4fd1f95e588b93f4ff553059064736f6c634300080b0033