Contract Overview |  Hydro_HybridExchange
Balance: 0 Ether
Ether Value: $0
Transactions: 19900 txns
Misc:
Address Watch: Add To Watch List
Contract Creator: 0x967a2d1d753b1c21011b395caac96274f142b01fat txn 0x7c25a87eb6363875110974c70d2695f4711343b4518396f07dd03429fef5fe02
Token Balance:
 Latest 25 transactions from a total of 19900 transactions
(+1 PendingTxn)

TxHash Age From To Value [TxFee]
0x9a7112184256bb0592e85ea3c5d163d8d291d987c94240428b653a30be8fd720(pending)0x49497a4d914ae91d34ce80030fe620687bf333fd  IN  0x2cb4b49c0d6e9db2164d94ce48853bf77c4d883e0 Ether(Pending)
0xc387a78bd05c077a0f658321be005a3068ab2f8378cb2ed1957dd935553606cf3 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.0017854376
0x351ec94c5d2685717215b532f253c8eec4d3f1b5838302af4a8ab53a759e808a5 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.00192099
0xaeeeb8d418b7e489618e25f5990e6306989f253555a6084cded4a768f41fff9b5 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.0031219587
0x845f6d284c80fe270eab894b53f649ceacf300c41d7333e22a20c449541ae4cb6 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.0015077988
0xa4c9c97f325094615bf57c200f55e7fa60bc3236d41f77bc97376644a48043b86 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.0013417893
0x87165a56ac56259559e66bbb8af7bf5d5eda182f61a2386ddaacc7f5e69ecaf76 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.0015146352
0x6719cd8a2a9df0a0f8cad203f4e3d6d9fa44b1764861c495221bbc45bb2b20aa6 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.0159875451
0xdb25e152563c1b2e4d5c8f09f033f62ee1152482a909b2d2099124f7a275f9eb9 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.002038968
0xded8221c7fd4569cd4ca67b9e162cc2a52de9a666b90202d6e91c4acb8bdc7129 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.001482216
0xbee5f38cd484a2c6dc0a39f2023dcb68385e131d6de0126220939b6a85c9d28f9 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.001428848
0x0323ce07405318bc43407282c8ae0123558eb5953f8850a8778bc5abfbf9d5c79 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.005340824
0xadbe8872dc8523dfd76580680d4def460cd8390750429872ec1cc4cf55682b4811 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.001544272
0x907f9816cf8f8c9d38f44b965022591093c6f9bec4d911027cf53f0ee4a502cc12 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.00153348
0x0e68eb9c84ee2de92e0cbad5e91a20552cfaa75003e07475305fbeed19185fb812 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.001535528
0x34c30c95af53e913ba7e1da4f9553bb39946d7e6bf041792ec345afbf826f2b712 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.001548824
0xeb72fb0bfa76861142f858ea9b288cc8d235060ca7618d406f80468a4f43ff3214 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.001544192
0x09af8a0f8471979c35fe675d3e3d1e0db4d2966b9a4d42092a08534dade84d7c14 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.001532968
0x013ea9d6e31bd5cbe7b72fb5242e01831aa16bd3fba6e4b883da8af15d3189b914 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.001488568
0xaf7be091258a5725e963e418b62fbd612e319be375dc552f83bf934fdf4d357a16 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.001544704
0x65df176d56616f908844118d566f22d8c4c593cb3b8d9030f891011f7bcbf3e816 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.001491976
0x1407d42f03fe5edbb4c58ea532ee38921ff9e4503cc3af3fafec94ad1491d2a117 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.001481192
0x1389e00d5682a861bceec068b193bba73dd2efc671a33fd93fe78df22486ace118 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.001542736
0x3bbc16abd46ba1d4faad0b2f97ec5c810a1d75b7c63d6498938b09f3387f1fff19 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.001495976
0x1b80cda1674a4fec86596c6b802542976aad2133f601c2d8f472210bacdceab920 mins agoDDEX1.0  IN   Hydro_HybridExchange0 Ether0.001532456
[ Download CSV Export  ] 
 Internal Transactions as a result of Contract Execution
View All
ParentTxHash Block Age From To Value
Warning: The Compiled Contract might be susceptible to ExpExponentCleanup (medium/high-severity), EventStructWrongData (very low-severity) SolidityCompiler Bugs.

Contract Source Code Verified (Exact Match)
Contract Name: HybridExchange
Compiler Text: v0.4.24+commit.e67f0147
Optimization Enabled: Yes
Runs (Optimiser):  1000000



  Contract Source Code   Find Similiar Contracts

/*

    Copyright 2018 The Hydro Protocol Foundation

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

*/

pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;

/// @title Ownable
/// @dev The Ownable contract has an owner address, and provides basic authorization control
/// functions, this simplifies the implementation of "user permissions".
contract LibOwnable {
    address private _owner;

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

    /// @dev The Ownable constructor sets the original `owner` of the contract to the sender account.
    constructor() internal {
        _owner = msg.sender;
        emit OwnershipTransferred(address(0), _owner);
    }

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

    /// @dev Throws if called by any account other than the owner.
    modifier onlyOwner() {
        require(isOwner(), "NOT_OWNER");
        _;
    }

    /// @return true if `msg.sender` is the owner of the contract.
    function isOwner() public view returns(bool) {
        return msg.sender == _owner;
    }

    /// @dev Allows the current owner to relinquish control of the contract.
    /// @notice Renouncing to ownership will leave the contract without an owner.
    /// It will not be possible to call the functions with the `onlyOwner`
    /// modifier anymore.
    function renounceOwnership() public onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /// @dev Allows the current owner to transfer control of the contract to a newOwner.
    /// @param newOwner The address to transfer ownership to.
    function transferOwnership(address newOwner) public onlyOwner {
        require(newOwner != address(0), "INVALID_OWNER");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

/// @dev Math operations with safety checks that revert on error
library SafeMath {

    /// @dev Multiplies two numbers, reverts on overflow.
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "MUL_ERROR");

        return c;
    }

    /// @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0, "DIVIDING_ERROR");
        uint256 c = a / b;
        return c;
    }

    /// @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "SUB_ERROR");
        uint256 c = a - b;
        return c;
    }

    /// @dev Adds two numbers, reverts on overflow.
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "ADD_ERROR");
        return c;
    }

    /// @dev Divides two numbers and returns the remainder (unsigned integer modulo), reverts when dividing by zero.
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0, "MOD_ERROR");
        return a % b;
    }
}

/**
 * EIP712 Ethereum typed structured data hashing and signing
 */
contract EIP712 {
    string internal constant DOMAIN_NAME = "Hydro Protocol";
    string internal constant DOMAIN_VERSION = "1";

    /**
     * Hash of the EIP712 Domain Separator Schema
     */
    bytes32 public constant EIP712_DOMAIN_TYPEHASH = keccak256(
        abi.encodePacked("EIP712Domain(string name,string version,address verifyingContract)")
    );

    bytes32 public DOMAIN_SEPARATOR;

    constructor () public {
        DOMAIN_SEPARATOR = keccak256(
            abi.encodePacked(
                EIP712_DOMAIN_TYPEHASH,
                keccak256(bytes(DOMAIN_NAME)),
                keccak256(bytes(DOMAIN_VERSION)),
                bytes32(address(this))
            )
        );
    }

    /**
     * Calculates EIP712 encoding for a hash struct in this EIP712 Domain.
     *
     * @param eip712hash The EIP712 hash struct.
     * @return EIP712 hash applied to this EIP712 Domain.
     */
    function hashEIP712Message(bytes32 eip712hash) internal view returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, eip712hash));
    }
}

contract LibSignature {

    enum SignatureMethod {
        EthSign,
        EIP712
    }

    /**
     * OrderSignature struct contains typical signature data as v, r, and s with the signature
     * method encoded in as well.
     */
    struct OrderSignature {
        /**
         * Config contains the following values packed into 32 bytes
         * ╔════════════════════╤═══════════════════════════════════════════════════════════╗
         * ║                    │ length(bytes)   desc                                      ║
         * ╟────────────────────┼───────────────────────────────────────────────────────────╢
         * ║ v                  │ 1               the v parameter of a signature            ║
         * ║ signatureMethod    │ 1               SignatureMethod enum value                ║
         * ╚════════════════════╧═══════════════════════════════════════════════════════════╝
         */
        bytes32 config;
        bytes32 r;
        bytes32 s;
    }
    
    /**
     * Validate a signature given a hash calculated from the order data, the signer, and the
     * signature data passed in with the order.
     *
     * This function will revert the transaction if the signature method is invalid.
     *
     * @param hash Hash bytes calculated by taking the EIP712 hash of the passed order data
     * @param signerAddress The address of the signer
     * @param signature The signature data passed along with the order to validate against
     * @return True if the calculated signature matches the order signature data, false otherwise.
     */
    function isValidSignature(bytes32 hash, address signerAddress, OrderSignature memory signature)
        internal
        pure
        returns (bool)
    {
        uint8 method = uint8(signature.config[1]);
        address recovered;
        uint8 v = uint8(signature.config[0]);

        if (method == uint8(SignatureMethod.EthSign)) {
            recovered = ecrecover(
                keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)),
                v,
                signature.r,
                signature.s
            );
        } else if (method == uint8(SignatureMethod.EIP712)) {
            recovered = ecrecover(hash, v, signature.r, signature.s);
        } else {
            revert("INVALID_SIGN_METHOD");
        }

        return signerAddress == recovered;
    }
}

contract LibOrder is EIP712, LibSignature {
    struct Order {
        address trader;
        address relayer;
        address baseToken;
        address quoteToken;
        uint256 baseTokenAmount;
        uint256 quoteTokenAmount;
        uint256 gasTokenAmount;

        /**
         * Data contains the following values packed into 32 bytes
         * ╔════════════════════╤═══════════════════════════════════════════════════════════╗
         * ║                    │ length(bytes)   desc                                      ║
         * ╟────────────────────┼───────────────────────────────────────────────────────────╢
         * ║ version            │ 1               order version                             ║
         * ║ side               │ 1               0: buy, 1: sell                           ║
         * ║ isMarketOrder      │ 1               0: limitOrder, 1: marketOrder             ║
         * ║ expiredAt          │ 5               order expiration time in seconds          ║
         * ║ asMakerFeeRate     │ 2               maker fee rate (base 100,000)             ║
         * ║ asTakerFeeRate     │ 2               taker fee rate (base 100,000)             ║
         * ║ makerRebateRate    │ 2               rebate rate for maker (base 100,000)      ║
         * ║ salt               │ 8               salt                                      ║
         * ║                    │ 10              reserved                                  ║
         * ╚════════════════════╧═══════════════════════════════════════════════════════════╝
         */
        bytes32 data;
    }

    enum OrderStatus {
        EXPIRED,
        CANCELLED,
        FILLABLE,
        FULLY_FILLED
    }

    bytes32 public constant EIP712_ORDER_TYPE = keccak256(
        abi.encodePacked(
            "Order(address trader,address relayer,address baseToken,address quoteToken,uint256 baseTokenAmount,uint256 quoteTokenAmount,uint256 gasTokenAmount,bytes32 data)"
        )
    );

    /**
     * Calculates the Keccak-256 EIP712 hash of the order using the Hydro Protocol domain.
     *
     * @param order The order data struct.
     * @return Fully qualified EIP712 hash of the order in the Hydro Protocol domain.
     */
    function getOrderHash(Order memory order) internal view returns (bytes32 orderHash) {
        orderHash = hashEIP712Message(hashOrder(order));
        return orderHash;
    }

    /**
     * Calculates the EIP712 hash of the order.
     *
     * @param order The order data struct.
     * @return Hash of the order.
     */
    function hashOrder(Order memory order) internal pure returns (bytes32 result) {
        /**
         * Calculate the following hash in solidity assembly to save gas.
         *
         * keccak256(
         *     abi.encodePacked(
         *         EIP712_ORDER_TYPE,
         *         bytes32(order.trader),
         *         bytes32(order.relayer),
         *         bytes32(order.baseToken),
         *         bytes32(order.quoteToken),
         *         order.baseTokenAmount,
         *         order.quoteTokenAmount,
         *         order.gasTokenAmount,
         *         order.data
         *     )
         * );
         */

        bytes32 orderType = EIP712_ORDER_TYPE;

        assembly {
            let start := sub(order, 32)
            let tmp := mload(start)

            // 288 = (1 + 8) * 32
            //
            // [0...32)   bytes: EIP712_ORDER_TYPE
            // [32...288) bytes: order
            mstore(start, orderType)
            result := keccak256(start, 288)

            mstore(start, tmp)
        }

        return result;
    }

    /* Functions to extract info from data bytes in Order struct */

    function getExpiredAtFromOrderData(bytes32 data) internal pure returns (uint256) {
        return uint256(bytes5(data << (8*3)));
    }

    function isSell(bytes32 data) internal pure returns (bool) {
        return data[1] == 1;
    }

    function isMarketOrder(bytes32 data) internal pure returns (bool) {
        return data[2] == 1;
    }

    function isMarketBuy(bytes32 data) internal pure returns (bool) {
        return !isSell(data) && isMarketOrder(data);
    }

    function getAsMakerFeeRateFromOrderData(bytes32 data) internal pure returns (uint256) {
        return uint256(bytes2(data << (8*8)));
    }

    function getAsTakerFeeRateFromOrderData(bytes32 data) internal pure returns (uint256) {
        return uint256(bytes2(data << (8*10)));
    }

    function getMakerRebateRateFromOrderData(bytes32 data) internal pure returns (uint256) {
        return uint256(bytes2(data << (8*12)));
    }
}

contract LibMath {
    using SafeMath for uint256;

    /**
     * Check the amount of precision lost by calculating multiple * (numerator / denominator). To
     * do this, we check the remainder and make sure it's proportionally less than 0.1%. So we have:
     *
     *     ((numerator * multiple) % denominator)     1
     *     -------------------------------------- < ----
     *              numerator * multiple            1000
     *
     * To avoid further division, we can move the denominators to the other sides and we get:
     *
     *     ((numerator * multiple) % denominator) * 1000 < numerator * multiple
     *
     * Since we want to return true if there IS a rounding error, we simply flip the sign and our
     * final equation becomes:
     *
     *     ((numerator * multiple) % denominator) * 1000 >= numerator * multiple
     *
     * @param numerator The numerator of the proportion
     * @param denominator The denominator of the proportion
     * @param multiple The amount we want a proportion of
     * @return Boolean indicating if there is a rounding error when calculating the proportion
     */
    function isRoundingError(uint256 numerator, uint256 denominator, uint256 multiple)
        internal
        pure
        returns (bool)
    {
        return numerator.mul(multiple).mod(denominator).mul(1000) >= numerator.mul(multiple);
    }

    /// @dev calculate "multiple * (numerator / denominator)", rounded down.
    /// revert when there is a rounding error.
    /**
     * Takes an amount (multiple) and calculates a proportion of it given a numerator/denominator
     * pair of values. The final value will be rounded down to the nearest integer value.
     *
     * This function will revert the transaction if rounding the final value down would lose more
     * than 0.1% precision.
     *
     * @param numerator The numerator of the proportion
     * @param denominator The denominator of the proportion
     * @param multiple The amount we want a proportion of
     * @return The final proportion of multiple rounded down
     */
    function getPartialAmountFloor(uint256 numerator, uint256 denominator, uint256 multiple)
        internal
        pure
        returns (uint256)
    {
        require(!isRoundingError(numerator, denominator, multiple), "ROUNDING_ERROR");
        return numerator.mul(multiple).div(denominator);
    }

    /**
     * Returns the smaller integer of the two passed in.
     *
     * @param a Unsigned integer
     * @param b Unsigned integer
     * @return The smaller of the two integers
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }
}

/**
 * @title LibRelayer provides two distinct features for relayers. 
 *
 * First, Relayers can opt into or out of the Hydro liquidity incentive system.
 *
 * Second, a relayer can register a delegate address.
 * Delegates can send matching requests on behalf of relayers.
 * The delegate scheme allows additional possibilities for smart contract interaction.
 * on behalf of the relayer.
 */
contract LibRelayer {

    /**
     * Mapping of relayerAddress => delegateAddress
     */
    mapping (address => mapping (address => bool)) public relayerDelegates;

    /**
     * Mapping of relayerAddress => whether relayer is opted out of the liquidity incentive system
     */
    mapping (address => bool) hasExited;

    event RelayerApproveDelegate(address indexed relayer, address indexed delegate);
    event RelayerRevokeDelegate(address indexed relayer, address indexed delegate);

    event RelayerExit(address indexed relayer);
    event RelayerJoin(address indexed relayer);

    /**
     * Approve an address to match orders on behalf of msg.sender
     */
    function approveDelegate(address delegate) external {
        relayerDelegates[msg.sender][delegate] = true;
        emit RelayerApproveDelegate(msg.sender, delegate);
    }

    /**
     * Revoke an existing delegate
     */
    function revokeDelegate(address delegate) external {
        relayerDelegates[msg.sender][delegate] = false;
        emit RelayerRevokeDelegate(msg.sender, delegate);
    }

    /**
     * @return true if msg.sender is allowed to match orders which belong to relayer
     */
    function canMatchOrdersFrom(address relayer) public view returns(bool) {
        return msg.sender == relayer || relayerDelegates[relayer][msg.sender] == true;
    }

    /**
     * Join the Hydro incentive system.
     */
    function joinIncentiveSystem() external {
        delete hasExited[msg.sender];
        emit RelayerJoin(msg.sender);
    }

    /**
     * Exit the Hydro incentive system.
     * For relayers that choose to opt-out, the Hydro Protocol
     * effective becomes a tokenless protocol.
     */
    function exitIncentiveSystem() external {
        hasExited[msg.sender] = true;
        emit RelayerExit(msg.sender);
    }

    /**
     * @return true if relayer is participating in the Hydro incentive system.
     */
    function isParticipant(address relayer) public view returns(bool) {
        return !hasExited[relayer];
    }
}

/**
 * Library to handle fee discount calculation
 */
contract LibDiscount is LibOwnable {
    using SafeMath for uint256;
    
    // The base discounted rate is 100% of the current rate, or no discount.
    uint256 public constant DISCOUNT_RATE_BASE = 100;

    address public hotTokenAddress;

    constructor(address _hotTokenAddress) internal {
        hotTokenAddress = _hotTokenAddress;
    }

    /**
     * Get the HOT token balance of an address.
     *
     * @param owner The address to check.
     * @return The HOT balance for the owner address.
     */
    function getHotBalance(address owner) internal view returns (uint256 result) {
        address hotToken = hotTokenAddress;

        // IERC20(hotTokenAddress).balanceOf(owner)

        /**
         * We construct calldata for the `balanceOf` ABI.
         * The layout of this calldata is in the table below.
         * 
         * ╔════════╤════════╤════════╤═══════════════════╗
         * ║ Area   │ Offset │ Length │ Contents          ║
         * ╟────────┼────────┼────────┼───────────────────╢
         * ║ Header │ 0      │ 4      │ function selector ║
         * ║ Params │ 4      │ 32     │ owner address     ║
         * ╚════════╧════════╧════════╧═══════════════════╝
         */
        assembly {
            // Keep these so we can restore stack memory upon completion
            let tmp1 := mload(0)
            let tmp2 := mload(4)

            // keccak256('balanceOf(address)') bitmasked to 4 bytes
            mstore(0, 0x70a0823100000000000000000000000000000000000000000000000000000000)
            mstore(4, owner)

            // No need to check the return value because hotToken is a trustworthy contract
            result := call(
                gas,      // Forward all gas
                hotToken, // HOT token deployment address
                0,        // Don't send any ETH
                0,        // Pointer to start of calldata
                36,       // Length of calldata
                0,        // Overwrite calldata with output
                32        // Expecting uint256 output, the token balance
            )
            result := mload(0)

            // Restore stack memory
            mstore(0, tmp1)
            mstore(4, tmp2)
        }
    }

    bytes32 public discountConfig = 0x043c000027106400004e205a000075305000009c404600000000000000000000;

    /**
     * Calculate and return the rate at which fees will be charged for an address. The discounted
     * rate depends on how much HOT token is owned by the user. Values returned will be a percentage
     * used to calculate how much of the fee is paid, so a return value of 100 means there is 0
     * discount, and a return value of 70 means a 30% rate reduction.
     *
     * The discountConfig is defined as such:
     * ╔═══════════════════╤════════════════════════════════════════════╗
     * ║                   │ length(bytes)   desc                       ║
     * ╟───────────────────┼────────────────────────────────────────────╢
     * ║ count             │ 1               the count of configs       ║
     * ║ maxDiscountedRate │ 1               the max discounted rate    ║
     * ║ config            │ 5 each                                     ║
     * ╚═══════════════════╧════════════════════════════════════════════╝
     *
     * The default discount structure as defined in code would give the following result:
     *
     * Fee discount table
     * ╔════════════════════╤══════════╗
     * ║     HOT BALANCE    │ DISCOUNT ║
     * ╠════════════════════╪══════════╣
     * ║     0 <= x < 10000 │     0%   ║
     * ╟────────────────────┼──────────╢
     * ║ 10000 <= x < 20000 │    10%   ║
     * ╟────────────────────┼──────────╢
     * ║ 20000 <= x < 30000 │    20%   ║
     * ╟────────────────────┼──────────╢
     * ║ 30000 <= x < 40000 │    30%   ║
     * ╟────────────────────┼──────────╢
     * ║ 40000 <= x         │    40%   ║
     * ╚════════════════════╧══════════╝
     *
     * Breaking down the bytes of 0x043c000027106400004e205a000075305000009c404600000000000000000000
     *
     * 0x  04           3c          0000271064  00004e205a  0000753050  00009c4046  0000000000  0000000000;
     *     ~~           ~~          ~~~~~~~~~~  ~~~~~~~~~~  ~~~~~~~~~~  ~~~~~~~~~~  ~~~~~~~~~~  ~~~~~~~~~~
     *      |            |               |           |           |           |           |           |
     *    count  maxDiscountedRate       1           2           3           4           5           6
     *
     * The first config breaks down as follows:  00002710   64
     *                                           ~~~~~~~~   ~~
     *                                               |      |
     *                                              bar    rate
     *
     * Meaning if a user has less than 10000 (0x00002710) HOT, they will pay 100%(0x64) of the
     * standard fee.
     *
     * @param user The user address to calculate a fee discount for.
     * @return The percentage of the regular fee this user will pay.
     */
    function getDiscountedRate(address user) public view returns (uint256 result) {
        uint256 hotBalance = getHotBalance(user);

        if (hotBalance == 0) {
            return DISCOUNT_RATE_BASE;
        }

        bytes32 config = discountConfig;
        uint256 count = uint256(byte(config));
        uint256 bar;

        // HOT Token has 18 decimals
        hotBalance = hotBalance.div(10**18);

        for (uint256 i = 0; i < count; i++) {
            bar = uint256(bytes4(config << (2 + i * 5) * 8));

            if (hotBalance < bar) {
                result = uint256(byte(config << (2 + i * 5 + 4) * 8));
                break;
            }
        }

        // If we haven't found a rate in the config yet, use the maximum rate.
        if (result == 0) {
            result = uint256(config[1]);
        }

        // Make sure our discount algorithm never returns a higher rate than the base.
        require(result <= DISCOUNT_RATE_BASE, "DISCOUNT_ERROR");
    }

    /**
     * Owner can modify discount configuration.
     *
     * @param newConfig A data blob representing the new discount config. Details on format above.
     */
    function changeDiscountConfig(bytes32 newConfig) external onlyOwner {
        discountConfig = newConfig;
    }
}

contract LibExchangeErrors {
    string constant INVALID_TRADER = "INVALID_TRADER";
    string constant INVALID_SENDER = "INVALID_SENDER";
    // Taker order and maker order can't be matched
    string constant INVALID_MATCH = "INVALID_MATCH";
    string constant INVALID_SIDE = "INVALID_SIDE";
    // Signature validation failed
    string constant INVALID_ORDER_SIGNATURE = "INVALID_ORDER_SIGNATURE";
    // Taker order is not valid
    string constant INVALID_TAKER_ORDER = "INVALID_TAKER_ORDER";
    string constant ORDER_IS_NOT_FILLABLE = "ORDER_IS_NOT_FILLABLE";
    string constant MAKER_ORDER_CAN_NOT_BE_MARKET_ORDER = "MAKER_ORDER_CAN_NOT_BE_MARKET_ORDER";
    string constant COMPLETE_MATCH_FAILED = "COMPLETE_MATCH_FAILED";
    // Taker sells more than expected base tokens
    string constant TAKER_SELL_BASE_EXCEEDED = "TAKER_SELL_BASE_EXCEEDED";
    // Taker used more than expected quote tokens in market buy
    string constant TAKER_MARKET_BUY_QUOTE_EXCEEDED = "TAKER_MARKET_BUY_QUOTE_EXCEEDED";
    // Taker buys more than expected base tokens
    string constant TAKER_LIMIT_BUY_BASE_EXCEEDED = "TAKER_LIMIT_BUY_BASE_EXCEEDED";
    string constant TRANSFER_FROM_FAILED = "TRANSFER_FROM_FAILED";
    string constant RECORD_ADDRESSES_ERROR = "RECORD_ADDRESSES_ERROR";
    string constant PERIOD_NOT_COMPLETED_ERROR = "PERIOD_NOT_COMPLETED_ERROR";
    string constant CLAIM_HOT_TOKEN_ERROR = "CLAIM_HOT_TOKEN_ERROR";
    string constant INVALID_PERIOD = "INVALID_PERIOD";
}

contract HybridExchange is LibOrder, LibMath, LibRelayer, LibDiscount, LibExchangeErrors {
    using SafeMath for uint256;

    uint256 public constant FEE_RATE_BASE = 100000;

    /**
     * Address of the proxy responsible for asset transfer.
     */
    address public proxyAddress;

    /**
     * Mapping of orderHash => amount
     * Generally the amount will be specified in base token units, however in the case of a market
     * buy order the amount is specified in quote token units.
     */
    mapping (bytes32 => uint256) public filled;
    /**
     * Mapping of orderHash => whether order has been cancelled.
     */
    mapping (bytes32 => bool) public cancelled;

    event Cancel(bytes32 indexed orderHash);
    event Match(
        address baseToken,
        address quoteToken,
        address relayer,
        address maker,
        address taker,
        uint256 baseTokenAmount,
        uint256 quoteTokenAmount,
        uint256 makerFee,
        uint256 takerFee,
        uint256 makerGasFee,
        uint256 makerRebate,
        uint256 takerGasFee
    );

    struct TotalMatchResult {
        uint256 baseTokenFilledAmount;
        uint256 quoteTokenFilledAmount;
    }

    struct MatchResult {
        address maker;
        address taker;
        uint256 makerFee;
        uint256 makerRebate;
        uint256 takerFee;
        uint256 makerGasFee;
        uint256 takerGasFee;
        uint256 baseTokenFilledAmount;
        uint256 quoteTokenFilledAmount;
    }

    /**
     * When orders are being matched, they will always contain the exact same base token,
     * quote token, and relayer. Since excessive call data is very expensive, we choose
     * to create a stripped down OrderParam struct containing only data that may vary between
     * Order objects, and separate out the common elements into a set of addresses that will
     * be shared among all of the OrderParam items. This is meant to eliminate redundancy in
     * the call data, reducing it's size, and hence saving gas.
     */
    struct OrderParam {
        address trader;
        uint256 baseTokenAmount;
        uint256 quoteTokenAmount;
        uint256 gasTokenAmount;
        bytes32 data;
        OrderSignature signature;
    }

    struct OrderAddressSet {
        address baseToken;
        address quoteToken;
        address relayer;
    }

    /**
     * Calculated data about an order object.
     * Generally the filledAmount is specified in base token units, however in the case of a market
     * buy order the filledAmount is specified in quote token units.
     */
    struct OrderInfo {
        bytes32 orderHash;
        uint256 filledAmount;
    }

    constructor(address _proxyAddress, address hotTokenAddress)
        LibDiscount(hotTokenAddress)
        public
    {
        proxyAddress = _proxyAddress;
    }

    /**
     * Match taker order to a list of maker orders. Common addresses are passed in
     * separately as an OrderAddressSet to reduce call size data and save gas.
     *
     * @param takerOrderParam A OrderParam object representing the order from the taker.
     * @param makerOrderParams An array of OrderParam objects representing orders from a list of makers.
     * @param orderAddressSet An object containing addresses common across each order.
     */
    function matchOrders(
        OrderParam memory takerOrderParam,
        OrderParam[] memory makerOrderParams,
        OrderAddressSet memory orderAddressSet
    ) public {
        require(canMatchOrdersFrom(orderAddressSet.relayer), INVALID_SENDER);

        bool isParticipantRelayer = isParticipant(orderAddressSet.relayer);
        uint256 takerFeeRate = getTakerFeeRate(takerOrderParam, isParticipantRelayer);
        OrderInfo memory takerOrderInfo = getOrderInfo(takerOrderParam, orderAddressSet);

        // Calculate which orders match for settlement.
        MatchResult[] memory results = new MatchResult[](makerOrderParams.length);
        TotalMatchResult memory totalMatch;
        for (uint256 i = 0; i < makerOrderParams.length; i++) {
            require(!isMarketOrder(makerOrderParams[i].data), MAKER_ORDER_CAN_NOT_BE_MARKET_ORDER);
            require(isSell(takerOrderParam.data) != isSell(makerOrderParams[i].data), INVALID_SIDE);
            validatePrice(takerOrderParam, makerOrderParams[i]);

            OrderInfo memory makerOrderInfo = getOrderInfo(makerOrderParams[i], orderAddressSet);

            results[i] = getMatchResult(
                takerOrderParam,
                takerOrderInfo,
                makerOrderParams[i],
                makerOrderInfo,
                takerFeeRate,
                isParticipantRelayer
            );

            // Update TotalMatchResult with new fill amounts
            totalMatch.baseTokenFilledAmount = totalMatch.baseTokenFilledAmount.add(
                results[i].baseTokenFilledAmount
            );
            totalMatch.quoteTokenFilledAmount = totalMatch.quoteTokenFilledAmount.add(
                results[i].quoteTokenFilledAmount
            );

            // Update amount filled for this maker order.
            filled[makerOrderInfo.orderHash] = makerOrderInfo.filledAmount.add(
                results[i].baseTokenFilledAmount
            );
        }

        validateMatchResult(takerOrderParam, totalMatch);
        settleResults(results, takerOrderParam, orderAddressSet);

        // Update amount filled for this taker order.
        filled[takerOrderInfo.orderHash] = takerOrderInfo.filledAmount;
    }

    /**
     * Cancels an order, preventing it from being matched. In practice, matching mode relayers will
     * generally handle cancellation off chain by removing the order from their system, however if
     * the trader wants to ensure the order never goes through, or they no longer trust the relayer,
     * this function may be called to block it from ever matching at the contract level.
     *
     * Emits a Cancel event on success.
     *
     * @param order The order to be cancelled.
     */
    function cancelOrder(Order memory order) public {
        require(order.trader == msg.sender, INVALID_TRADER);

        bytes32 orderHash = getOrderHash(order);
        cancelled[orderHash] = true;

        emit Cancel(orderHash);
    }

    /**
     * Calculates current state of the order. Will revert transaction if this order is not
     * fillable for any reason, or if the order signature is invalid.
     *
     * @param orderParam The OrderParam object containing Order data.
     * @param orderAddressSet An object containing addresses common across each order.
     * @return An OrderInfo object containing the hash and current amount filled
     */
    function getOrderInfo(OrderParam memory orderParam, OrderAddressSet memory orderAddressSet)
        internal
        view
        returns (OrderInfo memory orderInfo)
    {
        Order memory order = getOrderFromOrderParam(orderParam, orderAddressSet);
        orderInfo.orderHash = getOrderHash(order);
        orderInfo.filledAmount = filled[orderInfo.orderHash];
        uint8 status = uint8(OrderStatus.FILLABLE);

        if (!isMarketBuy(order.data) && orderInfo.filledAmount >= order.baseTokenAmount) {
            status = uint8(OrderStatus.FULLY_FILLED);
        } else if (isMarketBuy(order.data) && orderInfo.filledAmount >= order.quoteTokenAmount) {
            status = uint8(OrderStatus.FULLY_FILLED);
        } else if (block.timestamp >= getExpiredAtFromOrderData(order.data)) {
            status = uint8(OrderStatus.EXPIRED);
        } else if (cancelled[orderInfo.orderHash]) {
            status = uint8(OrderStatus.CANCELLED);
        }

        require(status == uint8(OrderStatus.FILLABLE), ORDER_IS_NOT_FILLABLE);
        require(
            isValidSignature(orderInfo.orderHash, orderParam.trader, orderParam.signature),
            INVALID_ORDER_SIGNATURE
        );

        return orderInfo;
    }

    /**
     * Reconstruct an Order object from the given OrderParam and OrderAddressSet objects.
     *
     * @param orderParam The OrderParam object containing the Order data.
     * @param orderAddressSet An object containing addresses common across each order.
     * @return The reconstructed Order object.
     */
    function getOrderFromOrderParam(OrderParam memory orderParam, OrderAddressSet memory orderAddressSet)
        internal
        pure
        returns (Order memory order)
    {
        order.trader = orderParam.trader;
        order.baseTokenAmount = orderParam.baseTokenAmount;
        order.quoteTokenAmount = orderParam.quoteTokenAmount;
        order.gasTokenAmount = orderParam.gasTokenAmount;
        order.data = orderParam.data;
        order.baseToken = orderAddressSet.baseToken;
        order.quoteToken = orderAddressSet.quoteToken;
        order.relayer = orderAddressSet.relayer;
    }

    /**
     * Validates that the maker and taker orders can be matched based on the listed prices.
     *
     * If the taker submitted a sell order, the matching maker order must have a price greater than
     * or equal to the price the taker is willing to sell for.
     *
     * Since the price of an order is computed by order.quoteTokenAmount / order.baseTokenAmount
     * we can establish the following formula:
     *
     *    takerOrder.quoteTokenAmount        makerOrder.quoteTokenAmount
     *   -----------------------------  <=  -----------------------------
     *     takerOrder.baseTokenAmount        makerOrder.baseTokenAmount
     *
     * To avoid precision loss from division, we modify the formula to avoid division entirely.
     * In shorthand, this becomes:
     *
     *   takerOrder.quote * makerOrder.base <= takerOrder.base * makerOrder.quote
     *
     * We can apply this same process to buy orders - if the taker submitted a buy order then
     * the matching maker order must have a price less than or equal to the price the taker is
     * willing to pay. This means we can use the same result as above, but simply flip the
     * sign of the comparison operator.
     *
     * The function will revert the transaction if the orders cannot be matched.
     *
     * @param takerOrderParam The OrderParam object representing the taker's order data
     * @param makerOrderParam The OrderParam object representing the maker's order data
     */
    function validatePrice(OrderParam memory takerOrderParam, OrderParam memory makerOrderParam)
        internal
        pure
    {
        uint256 left = takerOrderParam.quoteTokenAmount.mul(makerOrderParam.baseTokenAmount);
        uint256 right = takerOrderParam.baseTokenAmount.mul(makerOrderParam.quoteTokenAmount);
        require(isSell(takerOrderParam.data) ? left <= right : left >= right, INVALID_MATCH);
    }

    /**
     * Construct a MatchResult from matching taker and maker order data, which will be used when
     * settling the orders and transferring token.
     *
     * @param takerOrderParam The OrderParam object representing the taker's order data
     * @param takerOrderInfo The OrderInfo object representing the current taker order state
     * @param makerOrderParam The OrderParam object representing the maker's order data
     * @param makerOrderInfo The OrderInfo object representing the current maker order state
     * @param takerFeeRate The rate used to calculate the fee charged to the taker
     * @param isParticipantRelayer Whether this relayer is participating in hot discount
     * @return MatchResult object containing data that will be used during order settlement.
     */
    function getMatchResult(
        OrderParam memory takerOrderParam,
        OrderInfo memory takerOrderInfo,
        OrderParam memory makerOrderParam,
        OrderInfo memory makerOrderInfo,
        uint256 takerFeeRate,
        bool isParticipantRelayer
    )
        internal
        view
        returns (MatchResult memory result)
    {
        // This will represent the amount we will be filling in this match. In most cases this will
        // be represented in base token units, but in the market buy case this will be quote token
        // units.
        uint256 filledAmount;

        // Determine the amount of token that will be filled by this match, in both base and quote
        // token units. This is done by checking which order has the least amount of token available
        // to fill or be filled and using that as the base fill amount.
        if(!isMarketBuy(takerOrderParam.data)) {
            filledAmount = min(
                takerOrderParam.baseTokenAmount.sub(takerOrderInfo.filledAmount),
                makerOrderParam.baseTokenAmount.sub(makerOrderInfo.filledAmount)
            );
            result.quoteTokenFilledAmount = convertBaseToQuote(makerOrderParam, filledAmount);
            result.baseTokenFilledAmount = filledAmount;
        } else {
            // In the market buy order case, we have to compare the amount of quote token left in
            // the taker order with the amount of base token left in the maker order. In order to do
            // that we convert from base to quote units in our comparison.
            filledAmount = min(
                takerOrderParam.quoteTokenAmount.sub(takerOrderInfo.filledAmount),
                convertBaseToQuote(
                    makerOrderParam,
                    makerOrderParam.baseTokenAmount.sub(makerOrderInfo.filledAmount)
                )
            );
            result.baseTokenFilledAmount = convertQuoteToBase(makerOrderParam, filledAmount);
            result.quoteTokenFilledAmount = filledAmount;
        }

        // Each order only pays gas once, so only pay gas when nothing has been filled yet.
        if (takerOrderInfo.filledAmount == 0) {
            result.takerGasFee = takerOrderParam.gasTokenAmount;
        }

        if (makerOrderInfo.filledAmount == 0) {
            result.makerGasFee = makerOrderParam.gasTokenAmount;
        }

        // Update filled amount. The filledAmount variable will always be in the correct base or
        // quote unit.
        takerOrderInfo.filledAmount = takerOrderInfo.filledAmount.add(filledAmount);

        result.maker = makerOrderParam.trader;
        result.taker = takerOrderParam.trader;

        // rebateRate uses the same base as fee rates, so can be directly compared
        uint256 rebateRate = getMakerRebateRateFromOrderData(makerOrderParam.data);
        uint256 makerRawFeeRate = getAsMakerFeeRateFromOrderData(makerOrderParam.data);

        if (rebateRate > makerRawFeeRate) {
            // Cap the rebate so it will never exceed the fees paid by the taker.
            uint256 makerRebateRate = min(
                // Don't want to apply discounts to the rebase, so simply multiply by
                // DISCOUNT_RATE_BASE to get it to the correct units.
                rebateRate.sub(makerRawFeeRate).mul(DISCOUNT_RATE_BASE),
                takerFeeRate
            );
            result.makerRebate = result.quoteTokenFilledAmount.mul(makerRebateRate).div(
                FEE_RATE_BASE.mul(DISCOUNT_RATE_BASE)
            );
            // If the rebate rate is higher, maker pays no fees.
            result.makerFee = 0;
        } else {
            // maker fee will be reduced, but still >= 0
            uint256 makerFeeRate = getFinalFeeRate(
                makerOrderParam.trader,
                makerRawFeeRate.sub(rebateRate),
                isParticipantRelayer
            );
            result.makerFee = result.quoteTokenFilledAmount.mul(makerFeeRate).div(
                FEE_RATE_BASE.mul(DISCOUNT_RATE_BASE)
            );
            result.makerRebate = 0;
        }

        result.takerFee = result.quoteTokenFilledAmount.mul(takerFeeRate).div(
            FEE_RATE_BASE.mul(DISCOUNT_RATE_BASE)
        );
    }

    /**
     * Get the rate used to calculate the taker fee.
     *
     * @param orderParam The OrderParam object representing the taker order data.
     * @param isParticipantRelayer Whether this relayer is participating in hot discount.
     * @return The final potentially discounted rate to use for the taker fee.
     */
    function getTakerFeeRate(OrderParam memory orderParam, bool isParticipantRelayer)
        internal
        view
        returns(uint256)
    {
        uint256 rawRate = getAsTakerFeeRateFromOrderData(orderParam.data);
        return getFinalFeeRate(orderParam.trader, rawRate, isParticipantRelayer);
    }

    /**
     * Take a fee rate and calculate the potentially discounted rate for this trader based on
     * HOT token ownership.
     *
     * @param trader The address of the trader who made the order.
     * @param rate The raw rate which we will discount if needed.
     * @param isParticipantRelayer Whether this relayer is participating in hot discount.
     * @return The final potentially discounted rate.
     */
    function getFinalFeeRate(address trader, uint256 rate, bool isParticipantRelayer)
        internal
        view
        returns(uint256)
    {
        if (isParticipantRelayer) {
            return rate.mul(getDiscountedRate(trader));
        } else {
            return rate.mul(DISCOUNT_RATE_BASE);
        }
    }

    /**
     * Take an amount and convert it from base token units to quote token units based on the price
     * in the order param.
     *
     * @param orderParam The OrderParam object containing the Order data.
     * @param amount An amount of base token.
     * @return The converted amount in quote token units.
     */
    function convertBaseToQuote(OrderParam memory orderParam, uint256 amount)
        internal
        pure
        returns (uint256)
    {
        return getPartialAmountFloor(
            orderParam.quoteTokenAmount,
            orderParam.baseTokenAmount,
            amount
        );
    }

    /**
     * Take an amount and convert it from quote token units to base token units based on the price
     * in the order param.
     *
     * @param orderParam The OrderParam object containing the Order data.
     * @param amount An amount of quote token.
     * @return The converted amount in base token units.
     */
    function convertQuoteToBase(OrderParam memory orderParam, uint256 amount)
        internal
        pure
        returns (uint256)
    {
        return getPartialAmountFloor(
            orderParam.baseTokenAmount,
            orderParam.quoteTokenAmount,
            amount
        );
    }

    /**
     * Validates sanity of match results.
     *
     * This function will revert the transaction if the results cannot be validated.
     *
     * @param takerOrderParam The OrderParam object representing the taker's order data
     * @param totalMatch Accumlated match result data representing how much token will be filled
     */
    function validateMatchResult(OrderParam memory takerOrderParam, TotalMatchResult memory totalMatch)
        internal
        pure
    {
        if (isSell(takerOrderParam.data)) {
            // Ensure we don't attempt to sell more tokens than the taker wished to sell
            require(
                totalMatch.baseTokenFilledAmount <= takerOrderParam.baseTokenAmount,
                TAKER_SELL_BASE_EXCEEDED
            );
        } else {
            // Ensure we don't attempt to buy more tokens than the taker wished to buy
            require(
                totalMatch.quoteTokenFilledAmount <= takerOrderParam.quoteTokenAmount,
                TAKER_MARKET_BUY_QUOTE_EXCEEDED
            );

            // If this isn't a market order, there may be maker orders with a better price. Ensure
            // we use exactly the taker's price in this case (as it is a limit order) by validating
            // that the amount of base token filled also matches.
            if (!isMarketOrder(takerOrderParam.data)) {
                require(
                    totalMatch.baseTokenFilledAmount <= takerOrderParam.baseTokenAmount,
                    TAKER_LIMIT_BUY_BASE_EXCEEDED
                );
            }
        }
    }

    /**
     * Take a list of matches and settle them with the taker order, transferring tokens all tokens
     * and paying all fees necessary to complete the transaction.
     *
     * @param results List of MatchResult objects representing each individual trade to settle.
     * @param takerOrderParam The OrderParam object representing the taker order data.
     * @param orderAddressSet An object containing addresses common across each order.
     */
    function settleResults(
        MatchResult[] memory results,
        OrderParam memory takerOrderParam,
        OrderAddressSet memory orderAddressSet
    )
        internal
    {
        if (isSell(takerOrderParam.data)) {
            settleTakerSell(results, orderAddressSet);
        } else {
            settleTakerBuy(results, orderAddressSet);
        }
    }

    /**
     * Settles a sell order given a list of MatchResult objects. A naive approach would be to take
     * each result, have the taker and maker transfer the appropriate tokens, and then have them
     * each send the appropriate fees to the relayer, meaning that for n makers there would be 4n
     * transactions. Additionally the taker would have to have an allowance set for the quote token
     * in order to pay the fees to the relayer.
     *
     * Instead we do the following:
     *  - Taker transfers the required base token to each maker
     *  - Each maker sends an amount of quote token to the relayer equal to:
     *    [Amount owed to taker] + [Maker fee] + [Maker gas cost] - [Maker rebate amount]
     *  - The relayer will then take all of this quote token and in a single batch transaction
     *    send the appropriate amount to the taker, equal to:
     *    [Total amount owed to taker] - [All taker fees] - [All taker gas costs]
     *
     * Thus in the end the taker will have the full amount of quote token, sans the fee and cost of
     * their share of gas. Each maker will have their share of base token, sans the fee and cost of
     * their share of gas, and will keep their rebate in quote token. The relayer will end up with
     * the fees from the taker and each maker (sans rebate), and the gas costs will pay for the
     * transactions. In this scenario, with n makers there will be 2n + 1 transactions, which will
     * be a significant gas savings over the original method.
     *
     * @param results A list of MatchResult objects representing each individual trade to settle.
     * @param orderAddressSet An object containing addresses common across each order.
     */
    function settleTakerSell(MatchResult[] memory results, OrderAddressSet memory orderAddressSet) internal {
        uint256 totalTakerBaseTokenFilledAmount = 0;

        for (uint256 i = 0; i < results.length; i++) {
            transferFrom(
                orderAddressSet.baseToken,
                results[i].taker,
                results[i].maker,
                results[i].baseTokenFilledAmount
            );

            transferFrom(
                orderAddressSet.quoteToken,
                results[i].maker,
                orderAddressSet.relayer,
                results[i].quoteTokenFilledAmount.
                    add(results[i].makerFee).
                    add(results[i].makerGasFee).
                    sub(results[i].makerRebate)
            );

            totalTakerBaseTokenFilledAmount = totalTakerBaseTokenFilledAmount.add(
                results[i].quoteTokenFilledAmount.sub(results[i].takerFee)
            );

            emitMatchEvent(results[i], orderAddressSet);
        }

        transferFrom(
            orderAddressSet.quoteToken,
            orderAddressSet.relayer,
            results[0].taker,
            totalTakerBaseTokenFilledAmount.sub(results[0].takerGasFee)
        );
    }

    /**
     * Settles a buy order given a list of MatchResult objects. A naive approach would be to take
     * each result, have the taker and maker transfer the appropriate tokens, and then have them
     * each send the appropriate fees to the relayer, meaning that for n makers there would be 4n
     * transactions. Additionally each maker would have to have an allowance set for the quote token
     * in order to pay the fees to the relayer.
     *
     * Instead we do the following:
     *  - Each maker transfers base tokens to the taker
     *  - The taker sends an amount of quote tokens to each maker equal to:
     *    [Amount owed to maker] + [Maker rebate amount] - [Maker fee] - [Maker gas cost]
     *  - Since the taker saved all the maker fees and gas costs, it can then send them as a single
     *    batch transaction to the relayer, equal to:
     *    [All maker and taker fees] + [All maker and taker gas costs] - [All maker rebates]
     *
     * Thus in the end the taker will have the full amount of base token, sans the fee and cost of
     * their share of gas. Each maker will have their share of quote token, including their rebate,
     * but sans the fee and cost of their share of gas. The relayer will end up with the fees from
     * the taker and each maker (sans rebates), and the gas costs will pay for the transactions. In
     * this scenario, with n makers there will be 2n + 1 transactions, which will be a significant
     * gas savings over the original method.
     *
     * @param results A list of MatchResult objects representing each individual trade to settle.
     * @param orderAddressSet An object containing addresses common across each order.
     */
    function settleTakerBuy(MatchResult[] memory results, OrderAddressSet memory orderAddressSet) internal {
        uint256 totalFee = 0;

        for (uint256 i = 0; i < results.length; i++) {
            transferFrom(
                orderAddressSet.baseToken,
                results[i].maker,
                results[i].taker,
                results[i].baseTokenFilledAmount
            );

            transferFrom(
                orderAddressSet.quoteToken,
                results[i].taker,
                results[i].maker,
                results[i].quoteTokenFilledAmount.
                    sub(results[i].makerFee).
                    sub(results[i].makerGasFee).
                    add(results[i].makerRebate)
            );

            totalFee = totalFee.
                add(results[i].takerFee).
                add(results[i].makerFee).
                add(results[i].makerGasFee).
                add(results[i].takerGasFee).
                sub(results[i].makerRebate);

            emitMatchEvent(results[i], orderAddressSet);
        }

        transferFrom(
            orderAddressSet.quoteToken,
            results[0].taker,
            orderAddressSet.relayer,
            totalFee
        );
    }

    /**
     * A helper function to call the transferFrom function in Proxy.sol with solidity assembly.
     * Copying the data in order to make an external call can be expensive, but performing the
     * operations in assembly seems to reduce gas cost.
     *
     * The function will revert the transaction if the transfer fails.
     *
     * @param token The address of the ERC20 token we will be transferring, 0 for ETH.
     * @param from The address we will be transferring from.
     * @param to The address we will be transferring to.
     * @param value The amount of token we will be transferring.
     */
    function transferFrom(address token, address from, address to, uint256 value) internal {
        if (value == 0) {
            return;
        }

        address proxy = proxyAddress;
        uint256 result;

        /**
         * We construct calldata for the `Proxy.transferFrom` ABI.
         * The layout of this calldata is in the table below.
         *
         * ╔════════╤════════╤════════╤═══════════════════╗
         * ║ Area   │ Offset │ Length │ Contents          ║
         * ╟────────┼────────┼────────┼───────────────────╢
         * ║ Header │ 0      │ 4      │ function selector ║
         * ║ Params │ 4      │ 32     │ token address     ║
         * ║        │ 36     │ 32     │ from address      ║
         * ║        │ 68     │ 32     │ to address        ║
         * ║        │ 100    │ 32     │ amount of token   ║
         * ╚════════╧════════╧════════╧═══════════════════╝
         */
        assembly {
            // Keep these so we can restore stack memory upon completion
            let tmp1 := mload(0)
            let tmp2 := mload(4)
            let tmp3 := mload(36)
            let tmp4 := mload(68)
            let tmp5 := mload(100)

            // keccak256('transferFrom(address,address,address,uint256)') bitmasked to 4 bytes
            mstore(0, 0x15dacbea00000000000000000000000000000000000000000000000000000000)
            mstore(4, token)
            mstore(36, from)
            mstore(68, to)
            mstore(100, value)

            // Call Proxy contract transferFrom function using constructed calldata
            result := call(
                gas,   // Forward all gas
                proxy, // Proxy.sol deployment address
                0,     // Don't send any ETH
                0,     // Pointer to start of calldata
                132,   // Length of calldata
                0,     // Output location
                0      // We don't expect any output
            )

            // Restore stack memory
            mstore(0, tmp1)
            mstore(4, tmp2)
            mstore(36, tmp3)
            mstore(68, tmp4)
            mstore(100, tmp5)
        }

        if (result == 0) {
            revert(TRANSFER_FROM_FAILED);
        }
    }

    function emitMatchEvent(MatchResult memory result, OrderAddressSet memory orderAddressSet) internal {
        emit Match(
            orderAddressSet.baseToken,
            orderAddressSet.quoteToken,
            orderAddressSet.relayer,
            result.maker,
            result.taker,
            result.baseTokenFilledAmount,
            result.quoteTokenFilledAmount,
            result.makerFee,
            result.takerFee,
            result.makerGasFee,
            result.makerRebate,
            result.takerGasFee
        );
    }
}

    Contract ABI  
[{"constant":false,"inputs":[{"name":"delegate","type":"address"}],"name":"approveDelegate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newConfig","type":"bytes32"}],"name":"changeDiscountConfig","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"proxyAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"filled","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"cancelled","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"relayerDelegates","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"exitIncentiveSystem","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"discountConfig","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"relayer","type":"address"}],"name":"canMatchOrdersFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"user","type":"address"}],"name":"getDiscountedRate","outputs":[{"name":"result","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"EIP712_ORDER_TYPE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FEE_RATE_BASE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"DISCOUNT_RATE_BASE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"components":[{"name":"trader","type":"address"},{"name":"baseTokenAmount","type":"uint256"},{"name":"quoteTokenAmount","type":"uint256"},{"name":"gasTokenAmount","type":"uint256"},{"name":"data","type":"bytes32"},{"components":[{"name":"config","type":"bytes32"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"}],"name":"signature","type":"tuple"}],"name":"takerOrderParam","type":"tuple"},{"components":[{"name":"trader","type":"address"},{"name":"baseTokenAmount","type":"uint256"},{"name":"quoteTokenAmount","type":"uint256"},{"name":"gasTokenAmount","type":"uint256"},{"name":"data","type":"bytes32"},{"components":[{"name":"config","type":"bytes32"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"}],"name":"signature","type":"tuple"}],"name":"makerOrderParams","type":"tuple[]"},{"components":[{"name":"baseToken","type":"address"},{"name":"quoteToken","type":"address"},{"name":"relayer","type":"address"}],"name":"orderAddressSet","type":"tuple"}],"name":"matchOrders","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"relayer","type":"address"}],"name":"isParticipant","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"components":[{"name":"trader","type":"address"},{"name":"relayer","type":"address"},{"name":"baseToken","type":"address"},{"name":"quoteToken","type":"address"},{"name":"baseTokenAmount","type":"uint256"},{"name":"quoteTokenAmount","type":"uint256"},{"name":"gasTokenAmount","type":"uint256"},{"name":"data","type":"bytes32"}],"name":"order","type":"tuple"}],"name":"cancelOrder","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"joinIncentiveSystem","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"EIP712_DOMAIN_TYPEHASH","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"hotTokenAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"delegate","type":"address"}],"name":"revokeDelegate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_proxyAddress","type":"address"},{"name":"hotTokenAddress","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"orderHash","type":"bytes32"}],"name":"Cancel","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"baseToken","type":"address"},{"indexed":false,"name":"quoteToken","type":"address"},{"indexed":false,"name":"relayer","type":"address"},{"indexed":false,"name":"maker","type":"address"},{"indexed":false,"name":"taker","type":"address"},{"indexed":false,"name":"baseTokenAmount","type":"uint256"},{"indexed":false,"name":"quoteTokenAmount","type":"uint256"},{"indexed":false,"name":"makerFee","type":"uint256"},{"indexed":false,"name":"takerFee","type":"uint256"},{"indexed":false,"name":"makerGasFee","type":"uint256"},{"indexed":false,"name":"makerRebate","type":"uint256"},{"indexed":false,"name":"takerGasFee","type":"uint256"}],"name":"Match","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"relayer","type":"address"},{"indexed":true,"name":"delegate","type":"address"}],"name":"RelayerApproveDelegate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"relayer","type":"address"},{"indexed":true,"name":"delegate","type":"address"}],"name":"RelayerRevokeDelegate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"relayer","type":"address"}],"name":"RelayerExit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"relayer","type":"address"}],"name":"RelayerJoin","type":"event"}]

  Contract Creation Code Switch To Opcodes View
60806040527f043c000027106400004e205a000075305000009c4046000000000000000000006005553480156200003557600080fd5b5060405160408062003482833981018060405262000057919081019062000376565b8060405160200180807f454950373132446f6d61696e28737472696e67206e616d652c737472696e672081526020017f76657273696f6e2c6164647265737320766572696679696e67436f6e7472616381526020017f742900000000000000000000000000000000000000000000000000000000000081525060420190506040516020818303038152906040526040518082805190602001908083835b60208310620001155780518252601f199092019160209182019101620000f4565b51815160209384036101000a6000190180199092169116179052604080519290940182900382208285018552600e8084527f487964726f2050726f746f636f6c000000000000000000000000000000000000928401928352945190965091945090928392508083835b602083106200019f5780518252601f1990920191602091820191016200017e565b51815160209384036101000a600019018019909216911617905260408051929094018290038220828501855260018084527f3100000000000000000000000000000000000000000000000000000000000000928401928352945190965091945090928392508083835b60208310620002295780518252601f19909201916020918201910162000208565b51815160209384036101000a6000190180199092169116179052604080519290940182900382208282019890985281840196909652606081019690965250306080808701919091528151808703909101815260a09095019081905284519093849350850191508083835b60208310620002b45780518252601f19909201916020918201910162000293565b5181516000196020949094036101000a9390930192831692191691909117905260405192018290038220600090815560038054600160a060020a031916331790819055600160a060020a0316945092507f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091839150a360048054600160a060020a03928316600160a060020a031991821617909155600680549490921693169290921790915550620003c1565b60006200036f8251620003b5565b9392505050565b600080604083850312156200038a57600080fd5b600062000398858562000361565b9250506020620003ab8582860162000361565b9150509250929050565b600160a060020a031690565b6130b180620003d16000396000f3006080604052600436106101485763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306df453e811461014d578063156066e41461016f57806323f5c02d1461018f578063288cdc91146101ba5780632ac12622146101e757806331d48447146102145780633560c5cd146102345780633644e5151461024957806337e607511461025e5780633e5893be146102735780634376abf1146102935780635373a191146102b357806361c3efb1146102c8578063715018a6146102dd5780637add89fb146102f25780638d10883d146103075780638da5cb5b146103275780638f32d59b1461033c578063929066f514610351578063b6b3618e14610371578063b875bdf114610391578063c7977be7146103a6578063cdfcc984146103bb578063f2fde38b146103d0578063fa352c00146103f0575b600080fd5b34801561015957600080fd5b5061016d610168366004612a09565b610410565b005b34801561017b57600080fd5b5061016d61018a366004612a61565b610496565b34801561019b57600080fd5b506101a46104e4565b6040516101b19190612d45565b60405180910390f35b3480156101c657600080fd5b506101da6101d5366004612a61565b610500565b6040516101b19190612e14565b3480156101f357600080fd5b50610207610202366004612a61565b610512565b6040516101b19190612e06565b34801561022057600080fd5b5061020761022f366004612a27565b610527565b34801561024057600080fd5b5061016d610547565b34801561025557600080fd5b506101da6105a7565b34801561026a57600080fd5b506101da6105ad565b34801561027f57600080fd5b5061020761028e366004612a09565b6105b3565b34801561029f57600080fd5b506101da6102ae366004612a09565b610613565b3480156102bf57600080fd5b506101da610799565b3480156102d457600080fd5b506101da610838565b3480156102e957600080fd5b5061016d61083f565b3480156102fe57600080fd5b506101da6108ee565b34801561031357600080fd5b5061016d610322366004612a7f565b6108f3565b34801561033357600080fd5b506101a4610cd0565b34801561034857600080fd5b50610207610cec565b34801561035d57600080fd5b5061020761036c366004612a09565b610d0a565b34801561037d57600080fd5b5061016d61038c366004612ae7565b610d36565b34801561039d57600080fd5b5061016d610e2e565b3480156103b257600080fd5b506101da610e8b565b3480156103c757600080fd5b506101a4610f63565b3480156103dc57600080fd5b5061016d6103eb366004612a09565b610f7f565b3480156103fc57600080fd5b5061016d61040b366004612a09565b61109c565b33600081815260016020818152604080842073ffffffffffffffffffffffffffffffffffffffff8716808652925280842080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690931790925590519092917f7fa92f6e23fcdb0b7a7001ea137560a8ebee9b8302d16e3b37c64ae7116b69ad91a350565b61049e610cec565b15156104df576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d690612ed1565b60405180910390fd5b600555565b60065473ffffffffffffffffffffffffffffffffffffffff1681565b60076020526000908152604090205481565b60086020526000908152604090205460ff1681565b600160209081526000928352604080842090915290825290205460ff1681565b3360008181526002602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055517f9bdfcd96a99ab6dad403b4ef4562bb3471fdeaab5e699160abf7c3f2cbe668059190a2565b60005481565b60055481565b60003373ffffffffffffffffffffffffffffffffffffffff8316148061060d575073ffffffffffffffffffffffffffffffffffffffff821660009081526001602081815260408084203385529091529091205460ff161515145b92915050565b6000806000806000806106258761111d565b9450841515610637576064955061078f565b60055493507f01000000000000000000000000000000000000000000000000000000000000008404925061067985670de0b6b3a764000063ffffffff61118516565b9450600090505b82811015610701577c0100000000000000000000000000000000000000000000000000000000600582026002908101600802900a8502049150818510156106f9577f01000000000000000000000000000000000000000000000000000000000000006008600660058402010260020a8502049550610701565b600101610680565b851515610754578360011a7f0100000000000000000000000000000000000000000000000000000000000000027f0100000000000000000000000000000000000000000000000000000000000000900495505b606486111561078f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d690612e71565b5050505050919050565b60405160200180609f612fd98239609f0190506040516020818303038152906040526040518082805190602001908083835b6020831061080857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107cb565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902081565b620186a081565b610847610cec565b151561087f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d690612ed1565b60035460405160009173ffffffffffffffffffffffffffffffffffffffff16907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600380547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055565b606481565b6000806108fe612697565b6060610908612697565b6000610912612697565b61091f88604001516105b3565b60408051808201909152600e81527f494e56414c49445f53454e444552000000000000000000000000000000000000602082015290151561098d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d69190612e60565b5061099b8860400151610d0a565b96506109a78a886111da565b95506109b38a89611203565b945088516040519080825280602002602001820160405280156109f057816020015b6109dd6126ae565b8152602001906001900390816109d55790505b509350600091505b8851821015610c9957610a258983815181101515610a1257fe5b90602001906020020151608001516113da565b60408051606081018252602381527f4d414b45525f4f524445525f43414e5f4e4f545f42455f4d41524b45545f4f5260208201527f4445520000000000000000000000000000000000000000000000000000000000918101919091529015610aba576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d69190612e60565b50610adf8983815181101515610acc57fe5b906020019060200201516080015161142e565b1515610aee8b6080015161142e565b60408051808201909152600c81527f494e56414c49445f5349444500000000000000000000000000000000000000006020820152919015151415610b5f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d69190612e60565b50610b818a8a84815181101515610b7257fe5b90602001906020020151611438565b610ba28983815181101515610b9257fe5b9060200190602002015189611203565b9050610bc98a868b85815181101515610bb757fe5b90602001906020020151848a8c611509565b8483815181101515610bd757fe5b602090810290910101528351610c0f90859084908110610bf357fe5b6020908102909101015160e0015184519063ffffffff61178616565b83528351610c4590859084908110610c2357fe5b906020019060200201516101000151846020015161178690919063ffffffff16565b60208401528351610c7d90859084908110610c5c57fe5b9060200190602002015160e00151826020015161178690919063ffffffff16565b81516000908152600760205260409020556001909101906109f8565b610ca38a846117cc565b610cae848b8a611968565b5050505060208181015191516000908152600790915260409020555050505050565b60035473ffffffffffffffffffffffffffffffffffffffff1690565b60035473ffffffffffffffffffffffffffffffffffffffff16331490565b73ffffffffffffffffffffffffffffffffffffffff1660009081526002602052604090205460ff161590565b805160408051808201909152600e81527f494e56414c49445f545241444552000000000000000000000000000000000000602082015260009173ffffffffffffffffffffffffffffffffffffffff163314610dbe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d69190612e60565b50610dc882611993565b60008181526008602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555191925082917fe8d9861dbc9c663ed3accd261bbe2fe01e0d3d9e5f51fa38523b265c7757a93a9190a25050565b3360008181526002602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055517fc3dd8edec722273de2ffd31b47b4f106689ae891ac1bfddf7ae6190589ab0f679190a2565b60405160200180807f454950373132446f6d61696e28737472696e67206e616d652c737472696e672081526020017f76657273696f6e2c6164647265737320766572696679696e67436f6e7472616381526020017f742900000000000000000000000000000000000000000000000000000000000081525060420190506040516020818303038152906040526040518082805190602001908083836020831061080857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107cb565b60045473ffffffffffffffffffffffffffffffffffffffff1681565b610f87610cec565b1515610fbf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d690612ed1565b73ffffffffffffffffffffffffffffffffffffffff8116151561100e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d690612ec1565b60035460405173ffffffffffffffffffffffffffffffffffffffff8084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600380547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff8616808552925280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055519092917fa6568d7ca1ae4c87043ca12f90308b8ef94330ee3a047c2101e1a40812d26c9891a350565b600480546000805183517f70a0823100000000000000000000000000000000000000000000000000000000835293859052909273ffffffffffffffffffffffffffffffffffffffff9092169160208460248180875af150600080519290526004529392505050565b6000808083116111c1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d690612e91565b82848115156111cc57fe5b0490508091505b5092915050565b6000806111ea84608001516119a6565b90506111fb846000015182856119da565b949350505050565b61120b612697565b611213612727565b600061121f8585611a13565b915061122a82611993565b80845260009081526007602090815260409091205490840152600290506112548260e00151611a87565b15801561126957508160800151836020015110155b156112785760035b90506112dc565b6112858260e00151611a87565b801561129957508160a00151836020015110155b156112a5576003611271565b6112b28260e00151611aa3565b42106112bf576000611271565b825160009081526008602052604090205460ff16156112dc575060015b60408051808201909152601581527f4f524445525f49535f4e4f545f46494c4c41424c450000000000000000000000602082015260ff821660021461134e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d69190612e60565b508251855160a0870151611363929190611acd565b60408051808201909152601781527f494e56414c49445f4f524445525f5349474e415455524500000000000000000060208201529015156113d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d69190612e60565b50505092915050565b60008160025b1a7f01000000000000000000000000000000000000000000000000000000000000009081027fff00000000000000000000000000000000000000000000000000000000000000161492915050565b60008160016113e0565b60008061145683602001518560400151611ce990919063ffffffff16565b915061147383604001518560200151611ce990919063ffffffff16565b9050611482846080015161142e565b61148f5780821015611494565b808211155b60408051808201909152600d81527f494e56414c49445f4d41544348000000000000000000000000000000000000006020820152901515611502576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d69190612e60565b5050505050565b6115116126ae565b60008060008060006115268c60800151611a87565b15156115865761156861154a8c602001518e60200151611d4490919063ffffffff16565b6020808c0151908d01516115639163ffffffff611d4416565b611d88565b94506115748a86611d9e565b61010087015260e086018590526115e2565b6115c86115a48c602001518e60400151611d4490919063ffffffff16565b6115638c6115c38d602001518f60200151611d4490919063ffffffff16565b611d9e565b94506115d48a86611db3565b60e087015261010086018590525b60208b015115156115f85760608c015160c08701525b6020890151151561160e5760608a015160a08701525b60208b0151611623908663ffffffff61178616565b6020808d01919091528a5173ffffffffffffffffffffffffffffffffffffffff90811688528d51169087015260808a015161165d90611dc8565b935061166c8a60800151611dfe565b9250828411156116ec576116a061169a606461168e878763ffffffff611d4416565b9063ffffffff611ce916565b89611d88565b91506116db6116b9620186a0606463ffffffff611ce916565b6101008801516116cf908563ffffffff611ce916565b9063ffffffff61118516565b606087015260006040870152611744565b895161170890611702858763ffffffff611d4416565b896119da565b9050611737611721620186a0606463ffffffff611ce916565b6101008801516116cf908463ffffffff611ce916565b6040870152600060608701525b61177161175b620186a0606463ffffffff611ce916565b6101008801516116cf908b63ffffffff611ce916565b608087015250939a9950505050505050505050565b6000828201838110156117c5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d690612ee1565b9392505050565b6117d9826080015161142e565b1561185b57602080830151825160408051808201909152601881527f54414b45525f53454c4c5f424153455f45584345454445440000000000000000938101939093521115611855576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d69190612e60565b50611964565b8160400151816020015111156040805190810160405280601f81526020017f54414b45525f4d41524b45545f4255595f51554f54455f4558434545444544008152509015156118d7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d69190612e60565b506118e582608001516113da565b151561196457602080830151825160408051808201909152601d81527f54414b45525f4c494d49545f4255595f424153455f4558434545444544000000938101939093521115611962576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d69190612e60565b505b5050565b611975826080015161142e565b15611989576119848382611e30565b611962565b6119628382612047565b600061060d6119a18361223d565b612333565b7e010000000000000000000000000000000000000000000000000000000000006a0100000000000000000000820204919050565b60008115611a02576119fb6119ee85610613565b849063ffffffff611ce916565b90506117c5565b6119fb83606463ffffffff611ce916565b611a1b612727565b825173ffffffffffffffffffffffffffffffffffffffff908116825260208085015160808085019190915260408087015160a086015260608088015160c0870152919096015160e085015284518316868501528482015183169084015292909301519092169082015290565b6000611a928261142e565b15801561060d575061060d826113da565b7b010000000000000000000000000000000000000000000000000000006301000000820204919050565b80516000907f0100000000000000000000000000000000000000000000000000000000000000600182901a810281900491839190821a81020460ff83161515611c4557604080517f19457468657265756d205369676e6564204d6573736167653a0a333200000000602080830191909152603c8083018b905283518084039091018152605c909201928390528151600193918291908401908083835b60208310611ba657805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611b69565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0180199092169116179052604080519290940182900382208c8201518d860151600085529390920194859052611c10965094508793909250612e22565b60206040516020810390808403906000865af1158015611c34573d6000803e3d6000fd5b505050602060405103519150611cae565b60ff831660011415611c7c57600187828760200151886040015160405160008152602001604052604051611c109493929190612e22565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d690612e81565b8173ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161493505050509392505050565b600080831515611cfc57600091506111d3565b50828202828482811515611d0c57fe5b04146117c5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d690612ef1565b60008083831115611d81576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d690612eb1565b5050900390565b6000818310611d9757816117c5565b5090919050565b60006117c58360400151846020015184612423565b60006117c58360200151846040015184612423565b7e010000000000000000000000000000000000000000000000000000000000006c01000000000000000000000000820204919050565b7e0100000000000000000000000000000000000000000000000000000000000068010000000000000000820204919050565b6000805b8351811015611fea57611e9b83600001518583815181101515611e5357fe5b90602001906020020151602001518684815181101515611e6f57fe5b60209081029091010151518751889086908110611e8857fe5b9060200190602002015160e0015161247b565b611f6783602001518583815181101515611eb157fe5b90602001906020020151600001518560400151611f628886815181101515611ed557fe5b9060200190602002015160600151611f568a88815181101515611ef457fe5b9060200190602002015160a00151611f4a8c8a815181101515611f1357fe5b90602001906020020151604001518d8b815181101515611f2f57fe5b6020908102909101015161010001519063ffffffff61178616565b9063ffffffff61178616565b9063ffffffff611d4416565b61247b565b611fbf611fb28583815181101515611f7b57fe5b90602001906020020151608001518684815181101515611f9757fe5b6020908102909101015161010001519063ffffffff611d4416565b839063ffffffff61178616565b9150611fe28482815181101515611fd257fe5b906020019060200201518461258b565b600101611e34565b6120418360200151846040015186600081518110151561200657fe5b9060200190602002015160200151611f6288600081518110151561202657fe5b6020908102909101015160c00151879063ffffffff611d4416565b50505050565b6000805b835181101561220d5761209f8360000151858381518110151561206a57fe5b6020908102909101015151865187908590811061208357fe5b90602001906020020151602001518785815181101515611e8857fe5b61214a836020015185838151811015156120b557fe5b906020019060200201516020015186848151811015156120d157fe5b9060200190602002015160000151611f6288868151811015156120f057fe5b9060200190602002015160600151611f4a8a8881518110151561210f57fe5b9060200190602002015160a00151611f568c8a81518110151561212e57fe5b90602001906020020151604001518d8b815181101515611f9757fe5b6121f2848281518110151561215b57fe5b9060200190602002015160600151611f56868481518110151561217a57fe5b9060200190602002015160c00151611f4a888681518110151561219957fe5b9060200190602002015160a00151611f4a8a888151811015156121b857fe5b9060200190602002015160400151611f4a8c8a8151811015156121d757fe5b60209081029091010151608001518b9063ffffffff61178616565b91506122058482815181101515611fd257fe5b60010161204b565b612041836020015185600081518110151561222457fe5b906020019060200201516020015185604001518561247b565b6000806040516020018080612fd9609f9139609f0190506040516020818303038152906040526040518082805190602001908083835b602083106122b057805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101612273565b5181516020939093036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909116921691909117905260405192018290039091207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe096909601805196815261012081209690525093949350505050565b60008054604080517f19010000000000000000000000000000000000000000000000000000000000006020808301919091526022820193909352604280820186905282518083039091018152606290910191829052805190928291908401908083835b602083106123d357805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101612396565b5181516020939093036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0180199091169216919091179052604051920182900390912095945050505050565b6000612430848484612607565b15612467576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d690612ea1565b6111fb836116cf868563ffffffff611ce916565b60008082151561248a57612583565b600660009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1691506000516004516024516044516064517f15dacbea000000000000000000000000000000000000000000000000000000006000528a60045289602452886044528760645260008060846000808b5af1600095909552600493909352602491909152604452606452905080151561258357604080518082018252601481527f5452414e534645525f46524f4d5f4641494c4544000000000000000000000000602082015290517f08c379a00000000000000000000000000000000000000000000000000000000081526104d69190600401612e60565b505050505050565b805160208083015160408085015186519387015160e08801516101008901518985015160808b015160a08c015160608d015160c08e015198517fdcc6682c66bde605a9e21caeb0cb8f1f6fbd5bbfb2250c3b8d1f43bb9b06df3f9c6125fb9c909b9a909897969594939291612d53565b60405180910390a15050565b6000612619848363ffffffff611ce916565b61263f6103e861168e86612633898863ffffffff611ce916565b9063ffffffff61264916565b1015949350505050565b6000811515612684576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d690612f01565b818381151561268f57fe5b069392505050565b604080518082019091526000808252602082015290565b61012060405190810160405280600073ffffffffffffffffffffffffffffffffffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081019190915290565b60006117c58235612f5d565b6000601f8201831361278857600080fd5b813561279b61279682612f38565b612f11565b91508181835260208401935060208101905083856101008402820111156127c157600080fd5b60005b838110156127f057816127d78882612861565b84525060209092019161010091909101906001016127c4565b5050505092915050565b60006117c58235612f7b565b60006060828403121561281857600080fd5b6128226060612f11565b90506000612830848461276b565b82525060206128418484830161276b565b60208301525060406128558482850161276b565b60408301525092915050565b6000610100828403121561287457600080fd5b61287e60c0612f11565b9050600061288c848461276b565b825250602061289d848483016127fa565b60208301525060406128b1848285016127fa565b60408301525060606128c5848285016127fa565b60608301525060806128d9848285016127fa565b60808301525060a06128ed848285016128f9565b60a08301525092915050565b60006060828403121561290b57600080fd5b6129156060612f11565b9050600061292384846127fa565b8252506020612934848483016127fa565b6020830152506040612855848285016127fa565b6000610100828403121561295b57600080fd5b612966610100612f11565b90506000612974848461276b565b82525060206129858484830161276b565b60208301525060406129998482850161276b565b60408301525060606129ad8482850161276b565b60608301525060806129c1848285016127fa565b60808301525060a06129d5848285016127fa565b60a08301525060c06129e9848285016127fa565b60c08301525060e06129fd848285016127fa565b60e08301525092915050565b600060208284031215612a1b57600080fd5b60006111fb848461276b565b60008060408385031215612a3a57600080fd5b6000612a46858561276b565b9250506020612a578582860161276b565b9150509250929050565b600060208284031215612a7357600080fd5b60006111fb84846127fa565b60008060006101808486031215612a9557600080fd5b6000612aa18686612861565b93505061010084013567ffffffffffffffff811115612abf57600080fd5b612acb86828701612777565b925050610120612add86828701612806565b9150509250925092565b60006101008284031215612afa57600080fd5b60006111fb8484612948565b612b0f81612f5d565b82525050565b612b0f81612f76565b612b0f81612f7b565b6000612b3282612f59565b808452612b46816020860160208601612f84565b612b4f81612fb0565b9093016020019392505050565b600e81527f444953434f554e545f4552524f52000000000000000000000000000000000000602082015260400190565b601381527f494e56414c49445f5349474e5f4d4554484f4400000000000000000000000000602082015260400190565b600e81527f4449564944494e475f4552524f52000000000000000000000000000000000000602082015260400190565b600e81527f524f554e44494e475f4552524f52000000000000000000000000000000000000602082015260400190565b600981527f5355425f4552524f520000000000000000000000000000000000000000000000602082015260400190565b600d81527f494e56414c49445f4f574e455200000000000000000000000000000000000000602082015260400190565b600981527f4e4f545f4f574e45520000000000000000000000000000000000000000000000602082015260400190565b600981527f4144445f4552524f520000000000000000000000000000000000000000000000602082015260400190565b600981527f4d554c5f4552524f520000000000000000000000000000000000000000000000602082015260400190565b600981527f4d4f445f4552524f520000000000000000000000000000000000000000000000602082015260400190565b612b0f81612f7e565b6020810161060d8284612b06565b6101808101612d62828f612b06565b612d6f602083018e612b06565b612d7c604083018d612b06565b612d89606083018c612b06565b612d96608083018b612b06565b612da360a083018a612b1e565b612db060c0830189612b1e565b612dbd60e0830188612b1e565b612dcb610100830187612b1e565b612dd9610120830186612b1e565b612de7610140830185612b1e565b612df5610160830184612b1e565b9d9c50505050505050505050505050565b6020810161060d8284612b15565b6020810161060d8284612b1e565b60808101612e308287612b1e565b612e3d6020830186612d3c565b612e4a6040830185612b1e565b612e576060830184612b1e565b95945050505050565b602080825281016117c58184612b27565b6020808252810161060d81612b5c565b6020808252810161060d81612b8c565b6020808252810161060d81612bbc565b6020808252810161060d81612bec565b6020808252810161060d81612c1c565b6020808252810161060d81612c4c565b6020808252810161060d81612c7c565b6020808252810161060d81612cac565b6020808252810161060d81612cdc565b6020808252810161060d81612d0c565b60405181810167ffffffffffffffff81118282101715612f3057600080fd5b604052919050565b600067ffffffffffffffff821115612f4f57600080fd5b5060209081020190565b5190565b73ffffffffffffffffffffffffffffffffffffffff1690565b151590565b90565b60ff1690565b60005b83811015612f9f578181015183820152602001612f87565b838111156120415750506000910152565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169056004f726465722861646472657373207472616465722c616464726573732072656c617965722c616464726573732062617365546f6b656e2c616464726573732071756f7465546f6b656e2c75696e743235362062617365546f6b656e416d6f756e742c75696e743235362071756f7465546f6b656e416d6f756e742c75696e7432353620676173546f6b656e416d6f756e742c62797465733332206461746129a265627a7a723058201e0aec110d55e4485b21af254e011a7ac22297f6396db3a7e903c5c863798b136c6578706572696d656e74616cf5003700000000000000000000000074622073a4821dbfd046e9aa2ccf691341a076e10000000000000000000000009af839687f6c94542ac5ece2e317daae355493a1

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

-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 00000000000000000000000074622073a4821dbfd046e9aa2ccf691341a076e1
Arg [1] : 0000000000000000000000009af839687f6c94542ac5ece2e317daae355493a1


   Swarm Source:
bzzr://1e0aec110d55e4485b21af254e011a7ac22297f6396db3a7e903c5c863798b13

 

View All
Block Age transaction Difficulty GasUsed Reward
View All
Block Age UncleNumber Difficulty GasUsed Reward
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.