Contract 0x2cb4b49c0d6e9db2164d94ce48853bf77c4d883e

 

TxHash Block Age From To Value [TxFee]
0xe293878591c756ce0870b5ec0479c3dc95fc8752552a495d00b7b6961c980c76740337112 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001602744
0x091d48455667329dcfb58fdb25138269d32b2995ee8c0181c2551a30acbd36c8740336913 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001481208
0xd996f5414177500fb1bbb1b50e4222ba535da98dfd10bedb05f175b7c02ee219740335915 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001538416
0xa827ad4417e6a0b951c266775a6ed2e61c1cc70ef6bfe2d4a98cc00bdc6b9892740330929 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001644616
0x104f9d90e39d10604427d251ed2a8bf2ee3c6cc7fcb860499730747873dcc23d740318257 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001526488
0xd887cf479314a218bf4c91cda5e34283523e73c83067d1fec067341c47cc865774031391 hr 6 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.00136204
0xbdb2db24e3dd9600cc44ed6d968ddc54dac465c1145da522c4a67923025a942674030671 hr 22 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.00160608
0xe5eca9bb80e7f2927df5d12cff508b9c25d456d738eb7f5ca07249cff76ea60974030561 hr 24 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001427168
0x798dcb002cab95c6591873a10761a501c8d82095e326448084875512ebe8df9674029591 hr 47 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001361616
0xc79cd83cd38cdd5c78bab60d1db5cdca47b3fb3aa9f4b32a7ac63b80212beba474029391 hr 50 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.002209072
0xa11611a77a9472f752071a040d61af17efd200ec813cffdadc5ba61287a03ba174029301 hr 51 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001480696
0x6261f46cf9430f49f67003c3f434a2f8dbe827ef95a5d8f7664376d7ac27d28474029171 hr 54 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.00153764
0xa4e6d29aa2d5f6040867d7a093e6e93bbbe17f77c4faf2a3b28290a929b8a73b74028981 hr 58 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001485024
0x8239ada40d6a7dfa45e540d6d0d0f50eea03289f63310e1dd9baf361de50bd7274028462 hrs 6 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001538344
0x3ec42c050bd521d9dfdf4e945e72f7c31c61b07bf09b4b479e43ec837ab1b43b74026052 hrs 56 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001681749
0xaf95e5c857564268bd6d1fc0639d00a62eeedfb26978464c95839cbfc123ec8674025663 hrs 8 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001704078
0x665833531d42d68a49dbeef467be33e74b181b2b42e61547f4fadb705bb82b4874024943 hrs 24 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.002585772
0x1d61c3c9fe236394bef0d7b5bb9dc7b16b69791c932897f89c98599032cc666174023563 hrs 57 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001719333
0xcc828b41a1fe33490f8f8be4b27a922008ee3f6cfc7958c806ae991d3a9b51d474021134 hrs 51 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.002441439
0xa30426c305ef9941d229a25a6ae3c7cc5d4eb63c48e620c938f296803d56edf074020884 hrs 57 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001796076
0x8e2d7106c975e5df2d200769f38a6a90c1c32fe8d998e50f1aadf9f47f58e17574020844 hrs 57 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001635372
0x0ce8fc307e2048ac036e78aabca4d18bf619fdd6b389986ae2e89d79c7d31b1b74020585 hrs 2 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001592847
0xc7612a1bb1b8091c4de0aaafe257a620f56fffbf0fca43f1433881b8f096002e74019505 hrs 26 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.002046726
0x4b7927bfe1425ca094e33602118bb55f4cba60ee6ab40e0c2ee2eb493d94aaf474019255 hrs 33 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.003973167
0x97af2cfcb94f43c7260f69c9874cdb0efdde72d4c8d45e6c75225aa66cacef6a74018675 hrs 45 mins agoDDEX 1.0 IN  Hydro: Hybrid Exchange0 Ether0.001671921
[ Download CSV Export 

Internal Transactions as a result of Contract Execution

Parent TxHash Block Age From To Value
Warning: The compiled contract might be susceptible to ExpExponentCleanup (medium/high-severity), EventStructWrongData (very low-severity) Solidity Compiler Bugs.

Contract Source Code Verified (Exact Match)

Contract Name:
HybridExchange

Compiler Version
v0.4.24+commit.e67f0147

Optimization Enabled:
Yes

Runs (Optimizer):
1000000

Contract Source Code

/*

    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

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
Block Age Transaction Difficulty GasUsed Reward
Block Age Uncle Number Difficulty GasUsed Reward
Loading
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.