// File: contracts/lib/math/SafeMath.sol

pragma solidity 0.5.10;

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "SafeMath: subtraction overflow");
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, "SafeMath: division by zero");
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0, "SafeMath: modulo by zero");
        return a % b;
    }
}

// File: contracts/lib/ownership/Ownable.sol

pragma solidity 0.5.10;

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be aplied to your functions to restrict their use to
 * the owner.
 */
contract Ownable {
    address public owner;
    address public pendingOwner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor () internal {
        owner = msg.sender;
        emit OwnershipTransferred(address(0), owner);
    }

    /**
    * @dev Modifier throws if called by any account other than the pendingOwner.
    */
    modifier onlyPendingOwner() {
        require(msg.sender == pendingOwner);
        _;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(isOwner(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Returns true if the caller is the current owner.
     */
    function isOwner() public view returns (bool) {
        return msg.sender == owner;
    }

    /**
    * @dev Allows the current owner to set the pendingOwner address.
    * @param newOwner The address to transfer ownership to.
    */
    function transferOwnership(address newOwner) public onlyOwner {
        pendingOwner = newOwner;
    }

    /**
    * @dev Allows the pendingOwner address to finalize the transfer.
    */
    function claimOwnership() public onlyPendingOwner {
        emit OwnershipTransferred(owner, pendingOwner);
        owner = pendingOwner;
        pendingOwner = address(0);
    }
}

// File: contracts/lib/utils/ReentrancyGuard.sol

pragma solidity ^0.5.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the `nonReentrant` modifier
 * available, which can be aplied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 */
contract ReentrancyGuard {
    /// @dev counter to allow mutex lock with only one SSTORE operation
    uint256 private _guardCounter;

    constructor () internal {
        // The counter starts at one to prevent changing it from zero to a non-zero
        // value, which is a more expensive operation.
        _guardCounter = 1;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _guardCounter += 1;
        uint256 localCounter = _guardCounter;
        _;
        require(localCounter == _guardCounter, "ReentrancyGuard: reentrant call");
    }
}

// File: contracts/Utils.sol

pragma solidity 0.5.10;


interface ERC20 {
    function balanceOf(address account) external view returns (uint256);
}

interface MarketDapp {
    // Returns the address to approve tokens for
    function tokenReceiver(address[] calldata assetIds, uint256[] calldata dataValues, address[] calldata addresses) external view returns(address);
    function trade(address[] calldata assetIds, uint256[] calldata dataValues, address[] calldata addresses, address payable recipient) external payable;
}

/// @title Util functions for the BrokerV2 contract for Switcheo Exchange
/// @author Switcheo Network
/// @notice Functions were moved from the BrokerV2 contract into this contract
/// so that the BrokerV2 contract would not exceed the maximum contract size of
/// 24 KB.
library Utils {
    using SafeMath for uint256;

    // The constants for EIP-712 are precompiled to reduce contract size,
    // the original values are left here for reference and verification.
    //
    // bytes32 public constant CONTRACT_NAME = keccak256("Switcheo Exchange");
    // bytes32 public constant CONTRACT_VERSION = keccak256("2");
    // uint256 public constant CHAIN_ID = 3; // TODO: update this before deployment
    // address public constant VERIFYING_CONTRACT = address(0x7CFbeEa553784500394c878D4f4f79d3B79B9d41); // TODO: pre-calculate and update this before deployment
    // bytes32 public constant SALT = keccak256("switcheo-eth-salt");
    // bytes32 public constant EIP712_DOMAIN_TYPEHASH = keccak256(abi.encodePacked(
    //     "EIP712Domain(",
    //         "string name,",
    //         "string version,",
    //         "uint256 chainId,",
    //         "address verifyingContract,",
    //         "bytes32 salt",
    //     ")"
    // ));
    // bytes32 public constant EIP712_DOMAIN_TYPEHASH = 0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472;

    // bytes32 public constant DOMAIN_SEPARATOR = keccak256(abi.encode(
    //     EIP712_DOMAIN_TYPEHASH,
    //     CONTRACT_NAME,
    //     CONTRACT_VERSION,
    //     CHAIN_ID,
    //     VERIFYING_CONTRACT,
    //     SALT
    // ));
    bytes32 public constant DOMAIN_SEPARATOR = 0x376a22e062fefdc56ac08f9a26f925278e5cc27dd2ef7880765327cadbb4fa5a;

    // bytes32 public constant OFFER_TYPEHASH = keccak256(abi.encodePacked(
    //     "Offer(",
    //         "address maker,",
    //         "address offerAssetId,",
    //         "uint256 offerAmount,",
    //         "address wantAssetId,",
    //         "uint256 wantAmount,",
    //         "address feeAssetId,",
    //         "uint256 feeAmount,",
    //         "uint256 nonce",
    //     ")"
    // ));
    bytes32 public constant OFFER_TYPEHASH = 0xf845c83a8f7964bc8dd1a092d28b83573b35be97630a5b8a3b8ae2ae79cd9260;

    // bytes32 public constant FILL_TYPEHASH = keccak256(abi.encodePacked(
    //     "Fill(",
    //         "address filler,",
    //         "address offerAssetId,",
    //         "uint256 offerAmount,",
    //         "address wantAssetId,",
    //         "uint256 wantAmount,",
    //         "address feeAssetId,",
    //         "uint256 feeAmount,",
    //         "uint256 nonce",
    //     ")"
    // ));
    bytes32 public constant FILL_TYPEHASH = 0x5f59dbc3412a4575afed909d028055a91a4250ce92235f6790c155a4b2669e99;

    // The Ether token address is set as the constant 0x00 for backwards
    // compatibility
    address private constant ETHER_ADDR = address(0);

    /// @dev Validates `BrokerV2.trade` parameters to ensure trade fairness,
    /// see `BrokerV2.trade` for param details.
    /// @param _values Values from `trade`
    /// @param _hashes Hashes from `trade`
    /// @param _addresses Addresses from `trade`
    function validateTrades(
        uint256[] memory _values,
        bytes32[] memory _hashes,
        address[] memory _addresses
    )
        public
        pure
        returns (bytes32[] memory)
    {
        _validateTradeInputLengths(_values, _hashes);
        _validateUniqueOffers(_values);
        _validateMatches(_values, _addresses);
        _validateFillAmounts(_values);
        _validateTradeData(_values, _addresses);

        // validate signatures of all fills
        _validateTradeSignatures(
            _values,
            _hashes,
            _addresses,
            FILL_TYPEHASH,
            _values[0] & ~(~uint256(0) << 8), // numOffers
            (_values[0] & ~(~uint256(0) << 8)) + ((_values[0] & ~(~uint256(0) << 16)) >> 8) // numOffers + numFills
        );

        // validate signatures of all offers
        return _validateTradeSignatures(
            _values,
            _hashes,
            _addresses,
            OFFER_TYPEHASH,
            0,
            _values[0] & ~(~uint256(0) << 8) // numOffers
        );
    }

    /// @dev Validates `BrokerV2.networkTrade` parameters to ensure trade fairness,
    /// see `BrokerV2.networkTrade` for param details.
    /// @param _values Values from `networkTrade`
    /// @param _hashes Hashes from `networkTrade`
    /// @param _addresses Addresses from `networkTrade`
    /// @param _operator Address of the `BrokerV2.operator`
    function validateNetworkTrades(
        uint256[] memory _values,
        bytes32[] memory _hashes,
        address[] memory _addresses,
        address _operator
    )
        public
        pure
        returns (bytes32[] memory)
    {
        _validateNetworkTradeInputLengths(_values, _hashes);
        _validateUniqueOffers(_values);
        _validateNetworkMatches(_values, _addresses, _operator);
        _validateOfferData(_values, _addresses, _operator);

        // validate signatures of all offers
        return _validateTradeSignatures(
            _values,
            _hashes,
            _addresses,
            OFFER_TYPEHASH,
            0,
            _values[0] & ~(~uint256(0) << 8) // numOffers
        );
    }

    /// @dev Executes trades against external markets,
    /// see `BrokerV2.networkTrade` for param details.
    /// @param _values Values from `networkTrade`
    /// @param _addresses Addresses from `networkTrade`
    /// @param _marketDapps See `BrokerV2.marketDapps`
    function performNetworkTrades(
        uint256[] memory _values,
        address[] memory _addresses,
        address[] memory _marketDapps
    )
        public
        returns (uint256[] memory)
    {
        uint256[] memory increments = new uint256[](_addresses.length / 2);
        // i = 1 + numOffers * 2
        uint256 i = 1 + (_values[0] & ~(~uint256(0) << 8)) * 2;
        uint256 end = _values.length;

        // loop matches
        for(i; i < end; i++) {
            uint256[] memory data = new uint256[](9);
            data[0] = _values[i]; // match data
            data[1] = data[0] & ~(~uint256(0) << 8); // offerIndex
            data[2] = (data[0] & ~(~uint256(0) << 24)) >> 16; // operator.surplusAssetIndex
            data[3] = _values[data[1] * 2 + 1]; // offer.dataA
            data[4] = _values[data[1] * 2 + 2]; // offer.dataB
            data[5] = ((data[3] & ~(~uint256(0) << 16)) >> 8); // maker.offerAssetIndex
            data[6] = ((data[3] & ~(~uint256(0) << 24)) >> 16); // maker.wantAssetIndex
            // amount of offerAssetId to take from offer is equal to the match.takeAmount
            data[7] = data[0] >> 128;
            // expected amount to receive is: matchData.takeAmount * offer.wantAmount / offer.offerAmount
            data[8] = data[7].mul(data[4] >> 128).div(data[4] & ~(~uint256(0) << 128));

            address[] memory assetIds = new address[](3);
            assetIds[0] = _addresses[data[5] * 2 + 1]; // offer.offerAssetId
            assetIds[1] = _addresses[data[6] * 2 + 1]; // offer.wantAssetId
            assetIds[2] = _addresses[data[2] * 2 + 1]; // surplusAssetId

            uint256[] memory dataValues = new uint256[](3);
            dataValues[0] = data[7]; // the proportion of offerAmount to offer
            dataValues[1] = data[8]; // the propotionate wantAmount of the offer
            dataValues[2] = data[0]; // match data

            increments[data[2]] = _performNetworkTrade(
                assetIds,
                dataValues,
                _marketDapps,
                _addresses
            );
        }

        return increments;
    }

    /// @notice Approves a token transfer
    /// @param _assetId The address of the token to approve
    /// @param _spender The address of the spender to approve
    /// @param _amount The number of tokens to approve
    function approveTokenTransfer(
        address _assetId,
        address _spender,
        uint256 _amount
    )
        public
    {
        _validateContractAddress(_assetId);

        // Some tokens have an `approve` which returns a boolean and some do not.
        // The ERC20 interface cannot be used here because it requires specifying
        // an explicit return value, and an EVM exception would be raised when calling
        // a token with the mismatched return value.
        bytes memory payload = abi.encodeWithSignature(
            "approve(address,uint256)",
            _spender,
            _amount
        );
        bytes memory returnData = _callContract(_assetId, payload);
        // Ensure that the asset transfer succeeded
        _validateContractCallResult(returnData);
    }

    /// @notice Transfers tokens into the contract
    /// @param _user The address to transfer the tokens from
    /// @param _assetId The address of the token to transfer
    /// @param _amount The number of tokens to transfer
    /// @param _expectedAmount The number of tokens expected to be received,
    /// this may not match `_amount`, for example, tokens which have a
    /// propotion burnt on transfer will have a different amount received.
    function transferTokensIn(
        address _user,
        address _assetId,
        uint256 _amount,
        uint256 _expectedAmount
    )
        public
    {
        _validateContractAddress(_assetId);

        uint256 initialBalance = tokenBalance(_assetId);

        // Some tokens have a `transferFrom` which returns a boolean and some do not.
        // The ERC20 interface cannot be used here because it requires specifying
        // an explicit return value, and an EVM exception would be raised when calling
        // a token with the mismatched return value.
        bytes memory payload = abi.encodeWithSignature(
            "transferFrom(address,address,uint256)",
            _user,
            address(this),
            _amount
        );
        bytes memory returnData = _callContract(_assetId, payload);
        // Ensure that the asset transfer succeeded
        _validateContractCallResult(returnData);

        uint256 finalBalance = tokenBalance(_assetId);
        uint256 transferredAmount = finalBalance.sub(initialBalance);

        require(transferredAmount == _expectedAmount, "Invalid transfer");
    }

    /// @notice Transfers tokens from the contract to a user
    /// @param _receivingAddress The address to transfer the tokens to
    /// @param _assetId The address of the token to transfer
    /// @param _amount The number of tokens to transfer
    function transferTokensOut(
        address _receivingAddress,
        address _assetId,
        uint256 _amount
    )
        public
    {
        _validateContractAddress(_assetId);

        // Some tokens have a `transfer` which returns a boolean and some do not.
        // The ERC20 interface cannot be used here because it requires specifying
        // an explicit return value, and an EVM exception would be raised when calling
        // a token with the mismatched return value.
        bytes memory payload = abi.encodeWithSignature(
                                   "transfer(address,uint256)",
                                   _receivingAddress,
                                   _amount
                               );
        bytes memory returnData = _callContract(_assetId, payload);

        // Ensure that the asset transfer succeeded
        _validateContractCallResult(returnData);
    }

    /// @notice Returns the number of tokens owned by this contract
    /// @param _assetId The address of the token to query
    function externalBalance(address _assetId) public view returns (uint256) {
        if (_assetId == ETHER_ADDR) {
            return address(this).balance;
        }
        return tokenBalance(_assetId);
    }

    /// @notice Returns the number of tokens owned by this contract.
    /// @dev This will not work for Ether tokens, use `externalBalance` for
    /// Ether tokens.
    /// @param _assetId The address of the token to query
    function tokenBalance(address _assetId) public view returns (uint256) {
        return ERC20(_assetId).balanceOf(address(this));
    }

    /// @dev Validates that the specified `_hash` was signed by the specified `_user`.
    /// This method supports the EIP712 specification, the older Ethereum
    /// signed message specification is also supported for backwards compatibility.
    /// @param _hash The original hash that was signed by the user
    /// @param _user The user who signed the hash
    /// @param _v The `v` component of the `_user`'s signature
    /// @param _r The `r` component of the `_user`'s signature
    /// @param _s The `s` component of the `_user`'s signature
    /// @param _prefixed If true, the signature will be verified
    /// against the Ethereum signed message specification instead of the
    /// EIP712 specification
    function validateSignature(
        bytes32 _hash,
        address _user,
        uint8 _v,
        bytes32 _r,
        bytes32 _s,
        bool _prefixed
    )
        public
        pure
    {
        bytes32 eip712Hash = keccak256(abi.encodePacked(
            "\x19\x01",
            DOMAIN_SEPARATOR,
            _hash
        ));

        if (_prefixed) {
            bytes32 prefixedHash = keccak256(abi.encodePacked(
                "\x19Ethereum Signed Message:\n32",
                eip712Hash
            ));
            require(_user == ecrecover(prefixedHash, _v, _r, _s), "Invalid signature");
        } else {
            require(_user == ecrecover(eip712Hash, _v, _r, _s), "Invalid signature");
        }
    }

    /// @dev Ensures that `_address` is not the zero address
    /// @param _address The address to check
    function validateAddress(address _address) public pure {
        require(_address != address(0), "Invalid address");
    }

    /// @notice Executes a trade against an external market.
    /// @dev The initial Ether or token balance is compared with the
    /// balance after the trade to ensure that the appropriate amounts of
    /// tokens were taken and an appropriate amount received.
    /// The trade will fail if the number of tokens received is less than
    /// expected. If the number of tokens received is more than expected than
    /// the excess tokens are transferred to the `BrokerV2.operator`.
    /// @param _assetIds[0] The offerAssetId of the offer
    /// @param _assetIds[2] The wantAssetId of the offer
    /// @param _assetIds[3] The surplusAssetId
    /// @param _dataValues[0] The number of tokens offerred
    /// @param _dataValues[1] The number of tokens expected to be received
    /// @param _dataValues[2] Match data
    /// @param _marketDapps See `BrokerV2.marketDapps`
    /// @param _addresses Addresses from `networkTrade`
    function _performNetworkTrade(
        address[] memory _assetIds,
        uint256[] memory _dataValues,
        address[] memory _marketDapps,
        address[] memory _addresses
    )
        private
        returns (uint256)
    {
        uint256 dappIndex = (_dataValues[2] & ~(~uint256(0) << 16)) >> 8;
        MarketDapp marketDapp = MarketDapp(_marketDapps[dappIndex]);

        uint256[] memory funds = new uint256[](6);
        funds[0] = externalBalance(_assetIds[0]); // initialOfferTokenBalance
        funds[1] = externalBalance(_assetIds[1]); // initialWantTokenBalance
        if (_assetIds[2] != _assetIds[0] && _assetIds[2] != _assetIds[1]) {
            funds[2] = externalBalance(_assetIds[2]); // initialSurplusTokenBalance
        }

        uint256 ethValue = 0;
        address tokenReceiver;

        if (_assetIds[0] != ETHER_ADDR) {
            tokenReceiver = marketDapp.tokenReceiver(_assetIds, _dataValues, _addresses);
            approveTokenTransfer(
                _assetIds[0], // offerAssetId
                tokenReceiver,
                _dataValues[0] // offerAmount
            );
        } else {
            ethValue = _dataValues[0]; // offerAmount
        }

        marketDapp.trade.value(ethValue)(
            _assetIds,
            _dataValues,
            _addresses,
            // use uint160 to cast `address` to `address payable`
            address(uint160(address(this))) // destAddress
        );

        funds[3] = externalBalance(_assetIds[0]); // finalOfferTokenBalance
        funds[4] = externalBalance(_assetIds[1]); // finalWantTokenBalance
        if (_assetIds[2] != _assetIds[0] && _assetIds[2] != _assetIds[1]) {
            funds[5] = externalBalance(_assetIds[2]); // finalSurplusTokenBalance
        }

        uint256 surplusAmount = 0;

        // validate that the appropriate offerAmount was deducted
        // surplusAssetId == offerAssetId
        if (_assetIds[2] == _assetIds[0]) {
            // surplusAmount = finalOfferTokenBalance - (initialOfferTokenBalance - offerAmount)
            surplusAmount = funds[3].sub(funds[0].sub(_dataValues[0]));
        } else {
            // finalOfferTokenBalance == initialOfferTokenBalance - offerAmount
            require(funds[3] == funds[0].sub(_dataValues[0]), "Invalid offer asset balance");
        }

        // validate that the appropriate wantAmount was credited
        // surplusAssetId == wantAssetId
        if (_assetIds[2] == _assetIds[1]) {
            // surplusAmount = finalWantTokenBalance - (initialWantTokenBalance + wantAmount)
            surplusAmount = funds[4].sub(funds[1].add(_dataValues[1]));
        } else {
            // finalWantTokenBalance == initialWantTokenBalance + wantAmount
            require(funds[4] == funds[1].add(_dataValues[1]), "Invalid want asset balance");
        }

        // surplusAssetId != offerAssetId && surplusAssetId != wantAssetId
        if (_assetIds[2] != _assetIds[0] && _assetIds[2] != _assetIds[1]) {
            // surplusAmount = finalSurplusTokenBalance - initialSurplusTokenBalance
            surplusAmount = funds[5].sub(funds[2]);
        }

        // set the approved token amount back to zero
        if (_assetIds[0] != ETHER_ADDR) {
            approveTokenTransfer(
                _assetIds[0],
                tokenReceiver,
                0
            );
        }

        return surplusAmount;
    }

    /// @dev Validates input lengths based on the expected format
    /// detailed in the `trade` method.
    /// @param _values Values from `trade`
    /// @param _hashes Hashes from `trade`
    function _validateTradeInputLengths(
        uint256[] memory _values,
        bytes32[] memory _hashes
    )
        private
        pure
    {
        uint256 numOffers = _values[0] & ~(~uint256(0) << 8);
        uint256 numFills = (_values[0] & ~(~uint256(0) << 16)) >> 8;
        uint256 numMatches = (_values[0] & ~(~uint256(0) << 24)) >> 16;

        // Validate that bits(24..256) are zero
        require(_values[0] >> 24 == 0, "Invalid trade input");

        // It is enforced by other checks that if a fill is present
        // then it must be completely filled so there must be at least one offer
        // and at least one match in this case.
        // It is possible to have one offer with no matches and no fills
        // but that is blocked by this check as there is no foreseeable use
        // case for it.
        require(
            numOffers > 0 && numFills > 0 && numMatches > 0,
            "Invalid trade input"
        );

        require(
            _values.length == 1 + numOffers * 2 + numFills * 2 + numMatches,
            "Invalid _values.length"
        );

        require(
            _hashes.length == (numOffers + numFills) * 2,
            "Invalid _hashes.length"
        );
    }

    /// @dev Validates input lengths based on the expected format
    /// detailed in the `networkTrade` method.
    /// @param _values Values from `networkTrade`
    /// @param _hashes Hashes from `networkTrade`
    function _validateNetworkTradeInputLengths(
        uint256[] memory _values,
        bytes32[] memory _hashes
    )
        private
        pure
    {
        uint256 numOffers = _values[0] & ~(~uint256(0) << 8);
        uint256 numFills = (_values[0] & ~(~uint256(0) << 16)) >> 8;
        uint256 numMatches = (_values[0] & ~(~uint256(0) << 24)) >> 16;

        // Validate that bits(24..256) are zero
        require(_values[0] >> 24 == 0, "Invalid networkTrade input");

        // Validate that numFills is zero because the offers
        // should be filled against external orders
        require(
            numOffers > 0 && numMatches > 0 && numFills == 0,
            "Invalid networkTrade input"
        );

        require(
            _values.length == 1 + numOffers * 2 + numFills * 2 + numMatches,
            "Invalid _values.length"
        );

        require(
            _hashes.length == (numOffers + numFills) * 2,
            "Invalid _hashes.length"
        );
    }

    /// @dev See the `BrokerV2.trade` method for an explanation of why offer
    /// uniquness is required.
    /// The set of offers in `_values` must be sorted such that offer nonces'
    /// are arranged in a strictly ascending order.
    /// This allows the validation of offer uniqueness to be done in O(N) time,
    /// with N being the number of offers.
    /// @param _values Values from `trade`
    function _validateUniqueOffers(uint256[] memory _values) private pure {
        uint256 numOffers = _values[0] & ~(~uint256(0) << 8);

        uint256 prevNonce;
        uint256 mask = ~(~uint256(0) << 128);

        for(uint256 i = 0; i < numOffers; i++) {
            uint256 nonce = (_values[i * 2 + 1] & mask) >> 56;

            if (i == 0) {
                // Set the value of the first nonce
                prevNonce = nonce;
                continue;
            }

            require(nonce > prevNonce, "Invalid offer nonces");
            prevNonce = nonce;
        }
    }

    /// @dev Validate that for every match:
    /// 1. offerIndexes fall within the range of offers
    /// 2. fillIndexes falls within the range of fills
    /// 3. offer.offerAssetId == fill.wantAssetId
    /// 4. offer.wantAssetId == fill.offerAssetId
    /// 5. takeAmount > 0
    /// 6. (offer.wantAmount * takeAmount) % offer.offerAmount == 0
    /// @param _values Values from `trade`
    /// @param _addresses Addresses from `trade`
    function _validateMatches(
        uint256[] memory _values,
        address[] memory _addresses
    )
        private
        pure
    {
        uint256 numOffers = _values[0] & ~(~uint256(0) << 8);
        uint256 numFills = (_values[0] & ~(~uint256(0) << 16)) >> 8;

        uint256 i = 1 + numOffers * 2 + numFills * 2;
        uint256 end = _values.length;

        // loop matches
        for (i; i < end; i++) {
            uint256 offerIndex = _values[i] & ~(~uint256(0) << 8);
            uint256 fillIndex = (_values[i] & ~(~uint256(0) << 16)) >> 8;

            require(offerIndex < numOffers, "Invalid match.offerIndex");

            require(fillIndex >= numOffers && fillIndex < numOffers + numFills, "Invalid match.fillIndex");

            uint256 makerOfferAssetIndex = (_values[1 + offerIndex * 2] & ~(~uint256(0) << 16)) >> 8;
            uint256 makerWantAssetIndex = (_values[1 + offerIndex * 2] & ~(~uint256(0) << 24)) >> 16;
            uint256 fillerOfferAssetIndex = (_values[1 + fillIndex * 2] & ~(~uint256(0) << 16)) >> 8;
            uint256 fillerWantAssetIndex = (_values[1 + fillIndex * 2] & ~(~uint256(0) << 24)) >> 16;

            require(
                _addresses[makerOfferAssetIndex * 2 + 1] == _addresses[fillerWantAssetIndex * 2 + 1],
                "offer.offerAssetId does not match fill.wantAssetId"
            );

            require(
                _addresses[makerWantAssetIndex * 2 + 1] == _addresses[fillerOfferAssetIndex * 2 + 1],
                "offer.wantAssetId does not match fill.offerAssetId"
            );

            // require that bits(16..128) are all zero for every match
            require((_values[i] & ~(~uint256(0) << 128)) >> 16 == uint256(0), "Invalid match data");

            uint256 takeAmount = _values[i] >> 128;
            require(takeAmount > 0, "Invalid match.takeAmount");

            uint256 offerDataB = _values[2 + offerIndex * 2];
            // (offer.wantAmount * takeAmount) % offer.offerAmount == 0
            require(
                (offerDataB >> 128).mul(takeAmount).mod(offerDataB & ~(~uint256(0) << 128)) == 0,
                "Invalid amounts"
            );
        }
    }

    /// @dev Validate that for every match:
    /// 1. offerIndexes fall within the range of offers
    /// 2. _addresses[surplusAssetIndexes * 2] matches the operator address
    /// 3. takeAmount > 0
    /// 4. (offer.wantAmount * takeAmount) % offer.offerAmount == 0
    /// @param _values Values from `trade`
    /// @param _addresses Addresses from `trade`
    /// @param _operator Address of the `BrokerV2.operator`
    function _validateNetworkMatches(
        uint256[] memory _values,
        address[] memory _addresses,
        address _operator
    )
        private
        pure
    {
        uint256 numOffers = _values[0] & ~(~uint256(0) << 8);

        // 1 + numOffers * 2
        uint256 i = 1 + (_values[0] & ~(~uint256(0) << 8)) * 2;
        uint256 end = _values.length;

        // loop matches
        for (i; i < end; i++) {
            uint256 offerIndex = _values[i] & ~(~uint256(0) << 8);
            uint256 surplusAssetIndex = (_values[i] & ~(~uint256(0) << 24)) >> 16;

            require(offerIndex < numOffers, "Invalid match.offerIndex");
            require(_addresses[surplusAssetIndex * 2] == _operator, "Invalid operator address");

            uint256 takeAmount = _values[i] >> 128;
            require(takeAmount > 0, "Invalid match.takeAmount");

            uint256 offerDataB = _values[2 + offerIndex * 2];
            // (offer.wantAmount * takeAmount) % offer.offerAmount == 0
            require(
                (offerDataB >> 128).mul(takeAmount).mod(offerDataB & ~(~uint256(0) << 128)) == 0,
                "Invalid amounts"
            );
        }
    }

    /// @dev Validate that all fills will be completely filled by the specified
    /// matches. See the `BrokerV2.trade` method for an explanation of why
    /// fills must be completely filled.
    /// @param _values Values from `trade`
    function _validateFillAmounts(uint256[] memory _values) private pure {
        // "filled" is used to store the sum of `takeAmount`s and `giveAmount`s.
        // While a fill's `offerAmount` and `wantAmount` are combined to share
        // a single uint256 value, each sum of `takeAmount`s and `giveAmount`s
        // for a fill is tracked with an individual uint256 value.
        // This is to prevent the verification from being vulnerable to overflow
        // issues.
        uint256[] memory filled = new uint256[](_values.length);

        uint256 i = 1;
        // i += numOffers * 2
        i += (_values[0] & ~(~uint256(0) << 8)) * 2;
        // i += numFills * 2
        i += ((_values[0] & ~(~uint256(0) << 16)) >> 8) * 2;

        uint256 end = _values.length;

        // loop matches
        for (i; i < end; i++) {
            uint256 offerIndex = _values[i] & ~(~uint256(0) << 8);
            uint256 fillIndex = (_values[i] & ~(~uint256(0) << 16)) >> 8;
            uint256 takeAmount = _values[i] >> 128;
            uint256 wantAmount = _values[2 + offerIndex * 2] >> 128;
            uint256 offerAmount = _values[2 + offerIndex * 2] & ~(~uint256(0) << 128);
            // giveAmount = takeAmount * wantAmount / offerAmount
            uint256 giveAmount = takeAmount.mul(wantAmount).div(offerAmount);

            // (1 + fillIndex * 2) would give the index of the first part
            // of the data for the fill at fillIndex within `_values`,
            // and (2 + fillIndex * 2) would give the index of the second part
            filled[1 + fillIndex * 2] = filled[1 + fillIndex * 2].add(giveAmount);
            filled[2 + fillIndex * 2] = filled[2 + fillIndex * 2].add(takeAmount);
        }

        // numOffers
        i = (_values[0] & ~(~uint256(0) << 8));
        // i + numFills
        end = i + ((_values[0] & ~(~uint256(0) << 16)) >> 8);

        // loop fills
        for(i; i < end; i++) {
            require(
                // fill.offerAmount == (sum of given amounts for fill)
                _values[i * 2 + 2] & ~(~uint256(0) << 128) == filled[i * 2 + 1] &&
                // fill.wantAmount == (sum of taken amounts for fill)
                _values[i * 2 + 2] >> 128 == filled[i * 2 + 2],
                "Invalid fills"
            );
        }
    }

    /// @dev Validates that for every offer / fill:
    /// 1. offerAssetId != wantAssetId
    /// 2. offerAmount > 0 && wantAmount > 0
    /// 3. The referenced `operator` address is the zero address
    /// @param _values Values from `trade`
    /// @param _addresses Addresses from `trade`
    function _validateTradeData(
        uint256[] memory _values,
        address[] memory _addresses
    )
        private
        pure
    {
        // numOffers + numFills
        uint256 end = (_values[0] & ~(~uint256(0) << 8)) +
                      ((_values[0] & ~(~uint256(0) << 16)) >> 8);

        for (uint256 i = 0; i < end; i++) {
            uint256 dataA = _values[i * 2 + 1];
            uint256 dataB = _values[i * 2 + 2];

            require(
                // offerAssetId != wantAssetId
                _addresses[((dataA & ~(~uint256(0) << 16)) >> 8) * 2 + 1] !=
                _addresses[((dataA & ~(~uint256(0) << 24)) >> 16) * 2 + 1],
                "Invalid trade assets"
            );

            require(
                // offerAmount > 0 && wantAmount > 0
                (dataB & ~(~uint256(0) << 128)) > 0 && (dataB >> 128) > 0,
                "Invalid trade amounts"
            );

             require(
                // _addresses[operator address index] == address(0)
                // The actual operator address will be read directly from
                // the contract's storage
                _addresses[((dataA & ~(~uint256(0) << 40)) >> 32) * 2] == address(0),
                "Invalid operator address placeholder"
            );

             require(
                // _addresses[operator fee asset ID index] == address(1)
                // address(1) is used to differentiate from the ETHER_ADDR which is address(0)
                // The actual fee asset ID will be read from the filler / maker feeAssetId
                _addresses[((dataA & ~(~uint256(0) << 40)) >> 32) * 2 + 1] == address(1),
                "Invalid operator fee asset ID placeholder"
            );
        }
    }

    /// @dev Validates that for every offer
    /// 1. offerAssetId != wantAssetId
    /// 2. offerAmount > 0 && wantAmount > 0
    /// 3. Specified `operator` address matches the expected `operator` address,
    /// 4. Specified `operator.feeAssetId` matches the offer's feeAssetId
    /// @param _values Values from `trade`
    /// @param _addresses Addresses from `trade`
    function _validateOfferData(
        uint256[] memory _values,
        address[] memory _addresses,
        address _operator
    )
        private
        pure
    {
        // numOffers
        uint256 i = (_values[0] & ~(~uint256(0) << 8));
        // numOffers + numFills
        uint256 end = (_values[0] & ~(~uint256(0) << 8)) +
                      ((_values[0] & ~(~uint256(0) << 16)) >> 8);

        for (i; i < end; i++) {
            uint256 dataA = _values[i * 2 + 1];
            uint256 dataB = _values[i * 2 + 2];
            uint256 feeAssetIndex = ((dataA & ~(~uint256(0) << 40)) >> 32) * 2;

            require(
                // offerAssetId != wantAssetId
                _addresses[((dataA & ~(~uint256(0) << 16)) >> 8) * 2 + 1] !=
                _addresses[((dataA & ~(~uint256(0) << 24)) >> 16) * 2 + 1],
                "Invalid trade assets"
            );

            require(
                // offerAmount > 0 && wantAmount > 0
                (dataB & ~(~uint256(0) << 128)) > 0 && (dataB >> 128) > 0,
                "Invalid trade amounts"
            );

             require(
                _addresses[feeAssetIndex] == _operator,
                "Invalid operator address"
            );

             require(
                _addresses[feeAssetIndex + 1] == _addresses[((dataA & ~(~uint256(0) << 32)) >> 24) * 2 + 1],
                "Invalid operator fee asset ID"
            );
        }
    }

    /// @dev Validates signatures for a set of offers or fills
    /// @param _values Values from `trade`
    /// @param _hashes Hashes from `trade`
    /// @param _addresses Addresses from `trade`
    /// @param _typehash The typehash used to construct the signed hash
    /// @param _i The starting index to verify
    /// @param _end The ending index to verify
    /// @return An array of hash keys if _i started as 0, because only
    /// the hash keys of offers are needed
    function _validateTradeSignatures(
        uint256[] memory _values,
        bytes32[] memory _hashes,
        address[] memory _addresses,
        bytes32 _typehash,
        uint256 _i,
        uint256 _end
    )
        private
        pure
        returns (bytes32[] memory)
    {
        bytes32[] memory hashKeys;
        if (_i == 0) {
            hashKeys = new bytes32[](_end - _i);
        }

        for (_i; _i < _end; _i++) {
            uint256 dataA = _values[_i * 2 + 1];
            uint256 dataB = _values[_i * 2 + 2];

            bytes32 hashKey = keccak256(abi.encode(
                _typehash,
                _addresses[(dataA & ~(~uint256(0) << 8)) * 2], // user
                _addresses[((dataA & ~(~uint256(0) << 16)) >> 8) * 2 + 1], // offerAssetId
                dataB & ~(~uint256(0) << 128), // offerAmount
                _addresses[((dataA & ~(~uint256(0) << 24)) >> 16) * 2 + 1], // wantAssetId
                dataB >> 128, // wantAmount
                _addresses[((dataA & ~(~uint256(0) << 32)) >> 24) * 2 + 1], // feeAssetId
                dataA >> 128, // feeAmount
                (dataA & ~(~uint256(0) << 128)) >> 56 // nonce
            ));

            // To reduce gas costs, each bit of _values[0] after the 24th bit
            // is used to indicate whether the Ethereum signed message prefix
            // should be prepended for signature verification of the offer / fill
            // at that index
            bool prefixedSignature = ((dataA & ~(~uint256(0) << 56)) >> 48) != 0;

            validateSignature(
                hashKey,
                _addresses[(dataA & ~(~uint256(0) << 8)) * 2], // user
                uint8((dataA & ~(~uint256(0) << 48)) >> 40), // The `v` component of the user's signature
                _hashes[_i * 2], // The `r` component of the user's signature
                _hashes[_i * 2 + 1], // The `s` component of the user's signature
                prefixedSignature
            );

            if (hashKeys.length > 0) { hashKeys[_i] = hashKey; }
        }

        return hashKeys;
    }

    /// @dev Ensure that the address is a deployed contract
    /// @param _contract The address to check
    function _validateContractAddress(address _contract) private view {
        assembly {
            if iszero(extcodesize(_contract)) { revert(0, 0) }
        }
    }

    /// @dev A thin wrapper around the native `call` function, to
    /// validate that the contract `call` must be successful.
    /// See https://solidity.readthedocs.io/en/v0.5.1/050-breaking-changes.html
    /// for details on constructing the `_payload`
    /// @param _contract Address of the contract to call
    /// @param _payload The data to call the contract with
    /// @return The data returned from the contract call
    function _callContract(
        address _contract,
        bytes memory _payload
    )
        private
        returns (bytes memory)
    {
        bool success;
        bytes memory returnData;

        (success, returnData) = _contract.call(_payload);
        require(success, "Contract call failed");

        return returnData;
    }

    /// @dev Fix for ERC-20 tokens that do not have proper return type
    /// See: https://github.com/ethereum/solidity/issues/4116
    /// https://medium.com/loopring-protocol/an-incompatibility-in-smart-contract-threatening-dapp-ecosystem-72b8ca5db4da
    /// https://github.com/sec-bit/badERC20Fix/blob/master/badERC20Fix.sol
    /// @param _data The data returned from a transfer call
    function _validateContractCallResult(bytes memory _data) private pure {
        require(
            _data.length == 0 ||
            (_data.length == 32 && _getUint256FromBytes(_data) != 0),
            "Invalid contract call result"
        );
    }

    /// @dev Converts data of type `bytes` into its corresponding `uint256` value
    /// @param _data The data in bytes
    /// @return The corresponding `uint256` value
    function _getUint256FromBytes(
        bytes memory _data
    )
        private
        pure
        returns (uint256)
    {
        uint256 parsed;
        assembly { parsed := mload(add(_data, 32)) }
        return parsed;
    }
}

// File: contracts/BrokerV2.sol

pragma solidity 0.5.10;





interface IERC1820Registry {
    function setInterfaceImplementer(address account, bytes32 interfaceHash, address implementer) external;
}

interface TokenList {
    function validateToken(address assetId) external view;
}

interface SpenderList {
    function validateSpender(address spender) external view;
    function validateSpenderAuthorization(address user, address spender) external view;
}

/// @title The BrokerV2 contract for Switcheo Exchange
/// @author Switcheo Network
/// @notice This contract faciliates Ethereum and Ethereum token trades
/// between users.
/// Users can trade with each other by making and taking offers without
/// giving up custody of their tokens.
/// Users should first deposit tokens, then communicate off-chain
/// with the exchange coordinator, in order to place orders.
/// This allows trades to be confirmed immediately by the coordinator,
/// and settled on-chain through this contract at a later time.
///
/// @dev Bit compacting is used in the contract to reduce gas costs, when
/// it is used, params are documented as bits(n..m).
/// This means that the documented value is represented by bits starting
/// from and including `n`, up to and excluding `m`.
/// For example, bits(8..16), indicates that the value is represented by bits:
/// [8, 9, 10, 11, 12, 13, 14, 15].
///
/// Bit manipulation of the form (data & ~(~uint(0) << m)) >> n is frequently
/// used to recover the value at the specified bits.
/// For example, to recover bits(2..7) from a uint8 value, we can use
/// (data & ~(~uint8(0) << 7)) >> 2.
/// Given a `data` value of `1101,0111`, bits(2..7) should give "10101".
/// ~uint8(0): "1111,1111" (8 ones)
/// (~uint8(0) << 7): "1000,0000" (1 followed by 7 zeros)
/// ~(~uint8(0) << 7): "0111,1111" (0 followed by 7 ones)
/// (data & ~(~uint8(0) << 7)): "0101,0111" (bits after the 7th bit is zeroed)
/// (data & ~(~uint8(0) << 7)) >> 2: "0001,0101" (matching the expected "10101")
///
/// Additionally, bit manipulation of the form data >> n is used to recover
/// bits(n..e), where e is equal to the number of bits in the data.
/// For example, to recover bits(4..8) from a uint8 value, we can use data >> 4.
/// Given a data value of "1111,1111", bits(4..8) should give "1111".
/// data >> 4: "0000,1111" (matching the expected "1111")
///
/// There is frequent reference and usage of asset IDs, this is a unique
/// identifier used within the contract to represent individual assets.
/// For all tokens, the asset ID is identical to the contract address
/// of the token, this is so that additional mappings are not needed to
/// identify tokens during deposits and withdrawals.
/// The only exception is the Ethereum token, which does not have a contract
/// address, for this reason, the zero address is used to represent the
/// Ethereum token's ID.
contract BrokerV2 is Ownable, ReentrancyGuard {
    using SafeMath for uint256;

    struct WithdrawalAnnouncement {
        uint256 amount;
        uint256 withdrawableAt;
    }

    // Exchange states
    enum State { Active, Inactive }
    // Exchange admin states
    enum AdminState { Normal, Escalated }

    // The constants for EIP-712 are precompiled to reduce contract size,
    // the original values are left here for reference and verification.
    // NOTE: CHAIN_ID and VERIFYING_CONTRACT values must be updated before
    // mainnet deployment.
    //
    // bytes32 public constant CONTRACT_NAME = keccak256("Switcheo Exchange");
    // bytes32 public constant CONTRACT_VERSION = keccak256("2");
    // uint256 public constant CHAIN_ID = 3; // TODO: update this before deployment
    // address public constant VERIFYING_CONTRACT = address(1); // TODO: pre-calculate and update this before deployment
    // bytes32 public constant SALT = keccak256("switcheo-eth-eip712-salt");
    // bytes32 public constant EIP712_DOMAIN_TYPEHASH = keccak256(abi.encodePacked(
    //     "EIP712Domain(",
    //         "string name,",
    //         "string version,",
    //         "uint256 chainId,",
    //         "address verifyingContract,",
    //         "bytes32 salt",
    //     ")"
    // ));
    // bytes32 public constant EIP712_DOMAIN_TYPEHASH = 0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472;

    // bytes32 public constant DOMAIN_SEPARATOR = keccak256(abi.encode(
    //     EIP712_DOMAIN_TYPEHASH,
    //     CONTRACT_NAME,
    //     CONTRACT_VERSION,
    //     CHAIN_ID,
    //     VERIFYING_CONTRACT,
    //     SALT
    // ));
    bytes32 public constant DOMAIN_SEPARATOR = 0x14f697e312cdba1c10a1eb5c87d96fa22b63aef9dc39592568387471319ea630;

    // bytes32 public constant WITHDRAW_TYPEHASH = keccak256(abi.encodePacked(
    //     "Withdraw(",
    //         "address withdrawer,",
    //         "address receivingAddress,",
    //         "address assetId,",
    //         "uint256 amount,",
    //         "address feeAssetId,",
    //         "uint256 feeAmount,",
    //         "uint256 nonce",
    //     ")"
    // ));
    bytes32 public constant WITHDRAW_TYPEHASH = 0xbe2f4292252fbb88b129dc7717b2f3f74a9afb5b13a2283cac5c056117b002eb;

    // bytes32 public constant OFFER_TYPEHASH = keccak256(abi.encodePacked(
    //     "Offer(",
    //         "address maker,",
    //         "address offerAssetId,",
    //         "uint256 offerAmount,",
    //         "address wantAssetId,",
    //         "uint256 wantAmount,",
    //         "address feeAssetId,",
    //         "uint256 feeAmount,",
    //         "uint256 nonce",
    //     ")"
    // ));
    bytes32 public constant OFFER_TYPEHASH = 0xf845c83a8f7964bc8dd1a092d28b83573b35be97630a5b8a3b8ae2ae79cd9260;

    // bytes32 public constant CANCEL_TYPEHASH = keccak256(abi.encodePacked(
    //     "Cancel(",
    //         "bytes32 offerHash,",
    //         "address feeAssetId,",
    //         "uint256 feeAmount,",
    //     ")"
    // ));
    bytes32 public constant CANCEL_TYPEHASH = 0x46f6d088b1f0ff5a05c3f232c4567f2df96958e05457e6c0e1221dcee7d69c18;

    // bytes32 public constant SWAP_TYPEHASH = keccak256(abi.encodePacked(
    //     "Swap(",
    //         "address maker,",
    //         "address taker,",
    //         "address assetId,",
    //         "uint256 amount,",
    //         "bytes32 hashedSecret,",
    //         "uint256 expiryTime,",
    //         "address feeAssetId,",
    //         "uint256 feeAmount,",
    //         "uint256 nonce",
    //     ")"
    // ));
    bytes32 public constant SWAP_TYPEHASH = 0x6ba9001457a287c210b728198a424a4222098d7fac48f8c5fb5ab10ef907d3ef;

    // The Ether token address is set as the constant 0x00 for backwards
    // compatibility
    address private constant ETHER_ADDR = address(0);

    // Reason codes are used by the off-chain coordinator to track balance changes
    uint256 private constant REASON_DEPOSIT = 0x01;

    uint256 private constant REASON_WITHDRAW = 0x09;
    uint256 private constant REASON_WITHDRAW_FEE_GIVE = 0x14;
    uint256 private constant REASON_WITHDRAW_FEE_RECEIVE = 0x15;

    uint256 private constant REASON_CANCEL = 0x08;
    uint256 private constant REASON_CANCEL_FEE_GIVE = 0x12;
    uint256 private constant REASON_CANCEL_FEE_RECEIVE = 0x13;

    uint256 private constant REASON_SWAP_GIVE = 0x30;
    uint256 private constant REASON_SWAP_RECEIVE = 0x35;
    uint256 private constant REASON_SWAP_FEE_GIVE = 0x36;
    uint256 private constant REASON_SWAP_FEE_RECEIVE = 0x37;

    uint256 private constant REASON_SWAP_CANCEL_RECEIVE = 0x38;
    uint256 private constant REASON_SWAP_CANCEL_FEE_RECEIVE = 0x3B;
    uint256 private constant REASON_SWAP_CANCEL_FEE_REFUND = 0x3D;

    // 7 days * 24 hours * 60 mins * 60 seconds: 604800
    uint256 private constant MAX_SLOW_WITHDRAW_DELAY = 604800;
    uint256 private constant MAX_SLOW_CANCEL_DELAY = 604800;

    State public state;
    AdminState public adminState;
    // All fees will be transferred to the operator address
    address public operator;
    TokenList public tokenList;
    SpenderList public spenderList;

    // The delay in seconds to complete the respective escape hatch (`slowCancel` / `slowWithdraw`).
    // This gives the off-chain service time to update the off-chain state
    // before the state is separately updated by the user.
    uint256 public slowCancelDelay;
    uint256 public slowWithdrawDelay;

    // A mapping of remaining offer amounts: offerHash => availableAmount
    mapping(bytes32 => uint256) public offers;
    // A mapping of used nonces: nonceIndex => nonceData
    // The storing of nonces is used to ensure that transactions signed by
    // the user can only be used once.
    // For space and gas cost efficiency, one nonceData is used to store the
    // state of 256 nonces.
    // This reduces the average cost of storing a new nonce from 20,000 gas
    // to 5000 + 20,000 / 256 = 5078.125 gas
    // See _markNonce and _nonceTaken for more details.
    mapping(uint256 => uint256) public usedNonces;
    // A mapping of user balances: userAddress => assetId => balance
    mapping(address => mapping(address => uint256)) public balances;
    // A mapping of atomic swap states: swapHash => isSwapActive
    mapping(bytes32 => bool) public atomicSwaps;

    // A record of admin addresses: userAddress => isAdmin
    mapping(address => bool) public adminAddresses;
    // A record of market DApp addresses
    address[] public marketDapps;
    // A mapping of cancellation announcements for the cancel escape hatch: offerHash => cancellableAt
    mapping(bytes32 => uint256) public cancellationAnnouncements;
    // A mapping of withdrawal announcements: userAddress => assetId => announcementData
    mapping(address => mapping(address => WithdrawalAnnouncement)) public withdrawalAnnouncements;

    // Emitted on positive balance state transitions
    event BalanceIncrease(
        address indexed user,
        address indexed assetId,
        uint256 amount,
        uint256 indexed reason,
        uint256 nonce
    );

    // Emitted on negative balance state transitions
    event BalanceDecrease(
        address indexed user,
        address indexed assetId,
        uint256 amount,
        uint256 indexed reason,
        uint256 nonce
    );

    // Compacted versions of the `BalanceIncrease` and `BalanceDecrease` events.
    // These are used in the `trade` method, they are compacted to save gas costs.
    event Increment(uint256 data);
    event Decrement(uint256 data);

    event TokenFallback(
        address indexed user,
        address indexed assetId,
        uint256 amount
    );

    event TokensReceived(
        address indexed user,
        address indexed assetId,
        uint256 amount
    );

    event AnnounceCancel(
        bytes32 indexed offerHash,
        uint256 cancellableAt
    );

    event SlowCancel(
        bytes32 indexed offerHash,
        uint256 amount
    );

    event AnnounceWithdraw(
        address indexed withdrawer,
        address indexed assetId,
        uint256 amount,
        uint256 withdrawableAt
    );

    event SlowWithdraw(
        address indexed withdrawer,
        address indexed assetId,
        uint256 amount
    );

    /// @notice Initializes the Broker contract
    /// @dev The coordinator, operator and owner (through Ownable) is initialized
    /// to be the address of the sender.
    /// The Broker is put into an active state, with maximum exit delays set.
    /// The Broker is also registered as an implementer of ERC777TokensRecipient
    /// through the ERC1820 registry.
    constructor(address _tokenListAddress, address _spenderListAddress) public {
        adminAddresses[msg.sender] = true;
        operator = msg.sender;
        tokenList = TokenList(_tokenListAddress);
        spenderList = SpenderList(_spenderListAddress);

        slowWithdrawDelay = MAX_SLOW_WITHDRAW_DELAY;
        slowCancelDelay = MAX_SLOW_CANCEL_DELAY;
        state = State.Active;

        IERC1820Registry erc1820 = IERC1820Registry(
            0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
        );

        erc1820.setInterfaceImplementer(
            address(this),
            keccak256("ERC777TokensRecipient"),
            address(this)
        );
    }

    modifier onlyAdmin() {
        // Error code 1: onlyAdmin, address is not an admin address
        require(adminAddresses[msg.sender], "1");
        _;
    }

    modifier onlyActiveState() {
        // Error code 2: onlyActiveState, state is not 'Active'
        require(state == State.Active, "2");
        _;
    }

    modifier onlyEscalatedAdminState() {
        // Error code 3: onlyEscalatedAdminState, adminState is not 'Escalated'
        require(adminState == AdminState.Escalated, "3");
        _;
    }

    function isAdmin(address _user) external view returns(bool) {
        return adminAddresses[_user];
    }

    /// @notice Sets tbe Broker's state.
    /// @dev The two available states are `Active` and `Inactive`.
    /// The `Active` state allows for regular exchange activity,
    /// while the `Inactive` state prevents the invokation of deposit
    /// and trading functions.
    /// The `Inactive` state is intended as a means to cease contract operation
    /// in the case of an upgrade or in an emergency.
    /// @param _state The state to transition the contract into
    function setState(State _state) external onlyOwner { state = _state; }

    /// @notice Sets the Broker's admin state.
    /// @dev The two available states are `Normal` and `Escalated`.
    /// In the `Normal` admin state, the admin methods `adminCancel` and `adminWithdraw`
    /// are not invokable.
    /// The admin state must be set to `Escalated` by the contract owner for these
    /// methods to become usable.
    /// In an `Escalated` admin state, admin addresses would be able to cancel offers
    /// and withdraw balances to the respective user's wallet on behalf of users.
    /// The escalated state is intended to be used in the case of a contract upgrade or
    /// in an emergency.
    /// It is set separately from the `Inactive` state so that it is possible
    /// to use admin functions without affecting regular operations.
    /// @param _state The admin state to transition the contract into
    function setAdminState(AdminState _state) external onlyOwner { adminState = _state; }

    /// @notice Sets the operator address.
    /// @dev All fees will be transferred to the operator address.
    /// @param _operator The address to set as the operator
    function setOperator(address _operator) external onlyOwner {
        _validateAddress(operator);
        operator = _operator;
    }

    /// @notice Sets the minimum delay between an `announceCancel` call and
    /// when the cancellation can actually be executed through `slowCancel`.
    /// @dev This gives the off-chain service time to update the off-chain state
    /// before the state is separately updated by the user.
    /// This differs from the regular `cancel` operation, which does not involve a delay.
    /// @param _delay The delay in seconds
    function setSlowCancelDelay(uint256 _delay) external onlyOwner {
        // Error code 4: setSlowCancelDelay, slow cancel delay exceeds max allowable delay
        require(_delay <= MAX_SLOW_CANCEL_DELAY, "4");
        slowCancelDelay = _delay;
    }

    /// @notice Sets the delay between an `announceWithdraw` call and
    /// when the withdrawal can actually be executed through `slowWithdraw`.
    /// @dev This gives the off-chain service time to update the off-chain state
    /// before the state is separately updated by the user.
    /// This differs from the regular `withdraw` operation, which does not involve a delay.
    /// @param _delay The delay in seconds
    function setSlowWithdrawDelay(uint256 _delay) external onlyOwner {
        // Error code 5: setSlowWithdrawDelay, slow withdraw delay exceeds max allowable delay
        require(_delay <= MAX_SLOW_WITHDRAW_DELAY, "5");
        slowWithdrawDelay = _delay;
    }

    /// @notice Gives admin permissons to the specified address.
    /// @dev Admin addresses are intended to coordinate the regular operation
    /// of the Broker contract, and to perform special functions such as
    /// `adminCancel` and `adminWithdraw`.
    /// @param _admin The address to give admin permissions to
    function addAdmin(address _admin) external onlyOwner {
        _validateAddress(_admin);
        // Error code 6: addAdmin, address is already an admin address
        require(!adminAddresses[_admin], "6");
        adminAddresses[_admin] = true;
    }

    /// @notice Removes admin permissons for the specified address.
    /// @param _admin The admin address to remove admin permissions from
    function removeAdmin(address _admin) external onlyOwner {
        _validateAddress(_admin);
        // Error code 7: removeAdmin, address is not an admin address
        require(adminAddresses[_admin], "7");
        delete adminAddresses[_admin];
    }

    /// @notice Adds a market DApp to be used in `networkTrade`
    /// @param _dapp Address of the market DApp
    function addMarketDapp(address _dapp) external onlyOwner {
        _validateAddress(_dapp);
        marketDapps.push(_dapp);
    }

    /// @notice Updates a market DApp to be used in `networkTrade`
    /// @param _index Index of the market DApp to update
    /// @param _dapp The new address of the market DApp
    function updateMarketDapp(uint256 _index, address _dapp) external onlyOwner {
        _validateAddress(_dapp);
        // Error code 8: updateMarketDapp, _index does not refer to a DApp address
        require(marketDapps[_index] != address(0), "8");
        marketDapps[_index] = _dapp;
    }

    /// @notice Removes a market DApp
    /// @param _index Index of the market DApp to remove
    function removeMarketDapp(uint256 _index) external onlyOwner {
        // Error code 9: removeMarketDapp, _index does not refer to a DApp address
        require(marketDapps[_index] != address(0), "9");
        delete marketDapps[_index];
    }

    /// @notice Performs a balance transfer from one address to another
    /// @dev This method is intended to be invoked by spender contracts.
    /// To invoke this method, a spender contract must have been
    /// previously whitelisted and also authorized by the address from which
    /// funds will be deducted.
    /// Balance events are not emitted by this method, they should be separately
    /// emitted by the spender contract.
    /// @param _from The address to deduct from
    /// @param _to The address to credit
    /// @param _assetId The asset to transfer
    /// @param _amount The amount to transfer
    function spendFrom(
        address _from,
        address _to,
        address _assetId,
        uint256 _amount
    )
        external
    {
        spenderList.validateSpenderAuthorization(_from, msg.sender);

        _validateAddress(_to);

        balances[_from][_assetId] = balances[_from][_assetId].sub(_amount);
        balances[_to][_assetId] = balances[_to][_assetId].add(_amount);
    }

    function markNonce(uint256 _nonce) external {
        spenderList.validateSpender(msg.sender);
        _markNonce(_nonce);
    }

    /// @notice Deposits ETH into the sender's contract balance
    /// @dev This operation is only usable in an `Active` state
    /// to prevent this contract from receiving ETH in the case that its
    /// operation has been terminated.
    function deposit() external payable onlyActiveState {
        // Error code 10: deposit, msg.value is 0
        require(msg.value > 0, "10");
        _increaseBalance(msg.sender, ETHER_ADDR, msg.value, REASON_DEPOSIT, 0);
    }

    /// @dev This function is needed as market DApps generally send ETH
    /// using the `<address>.transfer` method.
    /// It is left empty to avoid issues with the function call running out
    /// of gas, as some callers set a small limit on how much gas can be
    /// used by the ETH receiver.
    function() payable external {}

    /// @notice Deposits ERC20 tokens under the `_user`'s balance
    /// @dev Transfers token into the Broker contract using the
    /// token's `transferFrom` method.
    /// The user must have previously authorized the token transfer
    /// through the token's `approve` method.
    /// This method has separate `_amount` and `_expectedAmount` values
    /// to support unconventional token transfers, e.g. tokens which have a
    /// proportion burnt on transfer.
    /// Whitelisted tokens cannot use this method as it may cause a double
    /// increment for the user's balance. This is because this method does a
    /// call to the token's `transferFrom` method, and some tokens have a
    /// `transferFrom` that later on calls `tokenFallback` or `tokensReceived`.
    /// @param _user The address of the user depositing the tokens
    /// @param _assetId The address of the token contract
    /// @param _amount The value to invoke the token's `transferFrom` with
    /// @param _expectedAmount The final amount expected to be received by this contract
    /// @param _nonce An unused nonce for balance tracking
    function depositToken(
        address _user,
        address _assetId,
        uint256 _amount,
        uint256 _expectedAmount,
        uint256 _nonce
    )
        external
        onlyAdmin
        onlyActiveState
        nonReentrant
    {
        _markNonce(_nonce);

        _increaseBalance(
            _user,
            _assetId,
            _expectedAmount,
            REASON_DEPOSIT,
            _nonce
        );

        Utils.transferTokensIn(
            _user,
            _assetId,
            _amount,
            _expectedAmount
        );
    }

    /// @notice Deposits ERC223 tokens under the `_user`'s balance
    /// @dev ERC223 tokens should invoke this method when tokens are
    /// sent to the Broker contract.
    /// The invokation will fail unless the token has been previously
    /// whitelisted through the `whitelistToken` method.
    /// @param _user The address of the user sending the tokens
    /// @param _amount The amount of tokens transferred to the Broker
    function tokenFallback(
        address _user,
        uint _amount,
        bytes calldata /* _data */
    )
        external
        onlyActiveState
        nonReentrant
    {
        address assetId = msg.sender;
        tokenList.validateToken(assetId);
        _increaseBalance(_user, assetId, _amount, REASON_DEPOSIT, 0);
        emit TokenFallback(_user, assetId, _amount);
    }

    /// @notice Deposits ERC777 tokens under the `_user`'s balance
    /// @dev ERC777 tokens should invoke this method when tokens are
    /// sent to the Broker contract.
    /// The invokation will fail unless the token has been previously
    /// whitelisted through the `whitelistToken` method.
    /// @param _user The address of the user sending the tokens
    /// @param _to The address receiving the tokens
    /// @param _amount The amount of tokens transferred to the Broker
    function tokensReceived(
        address /* _operator */,
        address _user,
        address _to,
        uint _amount,
        bytes calldata /* _userData */,
        bytes calldata /* _operatorData */
    )
        external
        onlyActiveState
        nonReentrant
    {
        if (_to != address(this)) { return; }
        address assetId = msg.sender;
        tokenList.validateToken(assetId);
        _increaseBalance(_user, assetId, _amount, REASON_DEPOSIT, 0);
        emit TokensReceived(_user, assetId, _amount);
    }

    /// @notice Executes an array of offers and fills
    /// @dev This method accepts an array of "offers" and "fills" together with
    /// an array of "matches" to specify the matching between the "offers" and "fills".
    /// The data is bit compacted for ease of index referencing and to reduce gas costs,
    /// i.e. data representing different types of information is stored within one 256 bit value.
    ///
    /// For efficient balance updates, the `_addresses` array is meant to contain a
    /// unique set of user asset pairs in the form of:
    /// [
    ///     user_1_address,
    ///     asset_1_address,
    ///     user_1_address,
    ///     asset_2_address,
    ///     user_2_address,
    ///     asset_1_address,
    ///     ...
    /// ]
    /// This allows combining multiple balance updates for a user asset pair
    /// into a single update by first calculating the total balance update for
    /// a pair at a specified index, then looping through the sums to perform
    /// the balance update.
    ///
    /// The added benefit is further gas cost reduction because repeated
    /// user asset pairs do not need to be duplicated for the calldata.
    ///
    /// The operator address and operator fee asset ID is enforced to be `address(0)`,
    /// this is because while a slot is needed, the actual operator address should
    /// be read directly from contract storage, and the operator fee asset ID is
    /// identical to the maker's / filler's feeAssetId.
    /// Enforcing this reduces calldata gas costs as zero bits cost less than
    /// non-zero bits.
    ///
    /// A tradeoff of compacting the bits is that there is a lower maximum value
    /// for offer and fill data, however the limits remain generally practical.
    ///
    /// For `offerAmount`, `wantAmount`, `feeAmount` values, the maximum value
    /// is 2^128. For a token with 18 decimals, this allows support for tokens
    /// with a maximum supply of 1000 million billion billion (33 zeros).
    /// In the case where the maximum value needs to be exceeded, a single
    /// offer / fill can be split into multiple offers / fills by the off-chain
    /// service.
    ///
    /// For nonces the maximum value is 2^72, or more than a billion billion (21 zeros).
    ///
    /// Offers and fills both encompass information about how much (offerAmount)
    /// of a specified token (offerAssetId) the user wants to offer and
    /// how much (wantAmount) of another token (wantAssetId) they want
    /// in return.
    ///
    /// Each match specifies how much of the match's `offer.offerAmount` should
    /// be transferred to the filler, in return, the offer's maker receives:
    /// `offer.wantAmount * match.takeAmount / offer.offerAmount` of the
    /// `offer.wantAssetId` from the filler.
    ///
    /// A few restirctions are enforced to ensure fairness and security of trades:
    /// 1. To prevent unfairness due to rounding issues, it is required that:
    /// `offer.wantAmount * match.takeAmount % offer.offerAmount == 0`.
    ///
    /// 2. Fills can be filled by offers which do not individually match
    /// the `fill.offerAmount` and `fill.wantAmount` ratio. As such, it is
    /// required that:
    /// fill.offerAmount == total amount deducted from filler for the fill's
    /// associated matches (excluding fees)
    /// fill.wantAmount == total amount credited to filler for the fill's
    /// associated matches (excluding fees)
    ///
    /// 3. The offer array must not consist of repeated offers. For efficient
    /// balance updates, a loop through each offer in the offer array is used
    /// to deduct the offer.offerAmount from the respective maker.
    /// If an offer has not been recorded by a previos `trade` call,
    /// and it the offer is repeated in the offers array, then there would be
    /// duplicate deductions from the maker.
    /// To enforce uniqueness, it is required that offer nonces are sorted in a
    /// strictly ascending order.
    ///
    /// 4. The fill array must not consist of repeated fills, for the same
    /// reason why there cannot be repeated offers. Additionally, to prevent
    /// replay attacks, all fill nonces are required to be unused.
    ///
    /// @param _values[0] Number of offers, fills, matches
    /// bits(0..8): number of offers (numOffers)
    /// bits(8..16): number of fills (numFills)
    /// bits(16..24): number of matches (numMatches)
    /// bits(24..256): must be zero
    ///
    /// @param _values[1 + i * 2] First part of offer data for the i'th offer
    /// bits(0..8): Index of the maker's address in _addresses
    /// bits(8..16): Index of the maker offerAssetId pair in _addresses
    /// bits(16..24): Index of the maker wantAssetId pair in _addresses
    /// bits(24..32): Index of the maker feeAssetId pair in _addresses
    /// bits(32..40): Index of the operator feeAssetId pair in _addresses
    /// bits(40..48): The `v` component of the maker's signature for this offer
    /// bits(48..56): Indicates whether the Ethereum signed message
    /// prefix should be prepended during signature verification
    /// bits(56..128): The offer nonce to prevent replay attacks
    /// bits(128..256): The number of tokens to be paid to the operator as fees for this offer
    ///
    /// @param _values[2 + i * 2] Second part of offer data for the i'th offer
    /// bits(0..128): offer.offerAmount, i.e. the number of tokens to offer
    /// bits(128..256): offer.wantAmount, i.e. the number of tokens to ask for in return
    ///
    /// @param _values[1 + numOffers * 2 + i * 2] First part of fill data for the i'th fill
    /// bits(0..8): Index of the filler's address in _addresses
    /// bits(8..16): Index of the filler offerAssetId pair in _addresses
    /// bits(16..24): Index of the filler wantAssetId pair in _addresses
    /// bits(24..32): Index of the filler feeAssetId pair in _addresses
    /// bits(32..40): Index of the operator feeAssetId pair in _addresses
    /// bits(40..48): The `v` component of the filler's signature for this fill
    /// bits(48..56): Indicates whether the Ethereum signed message
    /// prefix should be prepended during signature verification
    /// bits(56..128): The fill nonce to prevent replay attacks
    /// bits(128..256): The number of tokens to be paid to the operator as fees for this fill
    ///
    /// @param _values[2 + numOffers * 2 + i * 2] Second part of fill data for the i'th fill
    /// bits(0..128): fill.offerAmount, i.e. the number of tokens to offer
    /// bits(128..256): fill.wantAmount, i.e. the number of tokens to ask for in return
    ///
    /// @param _values[1 + numOffers * 2 + numFills * 2 + i] Data for the i'th match
    /// bits(0..8): Index of the offerIndex for this match
    /// bits(8..16): Index of the fillIndex for this match
    /// bits(128..256): The number of tokens to take from the matched offer's offerAmount
    ///
    /// @param _hashes[i * 2] The `r` component of the maker's / filler's signature
    /// for the i'th offer / fill
    ///
    /// @param _hashes[i * 2 + 1] The `s` component of the maker's / filler's signature
    /// for the i'th offer / fill
    ///
    /// @param _addresses An array of user asset pairs in the form of:
    /// [
    ///     user_1_address,
    ///     asset_1_address,
    ///     user_1_address,
    ///     asset_2_address,
    ///     user_2_address,
    ///     asset_1_address,
    ///     ...
    /// ]
    function trade(
        uint256[] calldata _values,
        bytes32[] calldata _hashes,
        address[] calldata _addresses
    )
        external
        onlyAdmin
        onlyActiveState
    {
        // Cache the operator address to reduce gas costs from storage reads
        address operatorAddress = operator;

        // `validateTrades` needs to calculate the hash keys of offers and fills
        // to verify the signature of the offer / fill.
        // The calculated hash keys for each offer is return to reduce repeated
        // computation.
        bytes32[] memory hashKeys = Utils.validateTrades(
            _values,
            _hashes,
            _addresses
        );

        // Credit fillers for each fill.wantAmount, and credit the operator
        // for each fill.feeAmount.
        _creditFillBalances(_values, _addresses, operatorAddress);

        // Credit makers for each amount received through a matched fill.
        _creditMakerBalances(_values, _addresses);

        // Credit the operator for each offer.feeAmount if the offer has not
        // been recorded through a previous `trade` call.
        _creditMakerFeeBalances(_values, _addresses, operatorAddress);

        // Deduct tokens from fillers for each fill.offerAmount
        // and each fill.feeAmount.
        _deductFillBalances(_values, _addresses);

        // Deduct tokens from makers for each offer.offerAmount
        // and each offer.feeAmount if the offer has not been recorded
        // through a previous `trade` call.
        _deductMakerBalances(_values, _addresses);

        // Reduce available offer amounts of offers and store the remaining
        // offer amount in the `offers` mapping.
        // Offer nonces will also be marked as taken.
        _storeOfferData(_values, hashKeys);

        // Mark all fill nonces as taken in the `usedNonces` mapping.
        _storeFillNonces(_values);
    }

    /// @notice Executes an array of offers against external orders.
    /// @dev This method accepts an array of "offers" together with
    /// an array of "matches" to specify the matching between the "offers" and
    /// external orders.
    /// The data is bit compacted and formatted in the same way as the `trade` function.
    ///
    /// @param _values[0] Number of offers, fills, matches
    /// bits(0..8): number of offers (numOffers)
    /// bits(8..16): number of fills, must be zero
    /// bits(16..24): number of matches (numMatches)
    /// bits(24..256): must be zero
    ///
    /// @param _values[1 + i * 2] First part of offer data for the i'th offer
    /// bits(0..8): Index of the maker's address in _addresses
    /// bits(8..16): Index of the maker offerAssetId pair in _addresses
    /// bits(16..24): Index of the maker wantAssetId pair in _addresses
    /// bits(24..32): Index of the maker feeAssetId pair in _addresses
    /// bits(32..40): Index of the operator feeAssetId pair in _addresses
    /// bits(40..48): The `v` component of the maker's signature for this offer
    /// bits(48..56): Indicates whether the Ethereum signed message
    /// prefix should be prepended during signature verification
    /// bits(56..128): The offer nonce to prevent replay attacks
    /// bits(128..256): The number of tokens to be paid to the operator as fees for this offer
    ///
    /// @param _values[2 + i * 2] Second part of offer data for the i'th offer
    /// bits(0..128): offer.offerAmount, i.e. the number of tokens to offer
    /// bits(128..256): offer.wantAmount, i.e. the number of tokens to ask for in return
    ///
    /// @param _values[1 + numOffers * 2 + i] Data for the i'th match
    /// bits(0..8): Index of the offerIndex for this match
    /// bits(8..16): Index of the marketDapp for this match
    /// bits(16..24): Index of the surplus receiver and surplus asset ID for this
    /// match, for any excess tokens resulting from the trade
    /// bits(24..128): Additional DApp specific data
    /// bits(128..256): The number of tokens to take from the matched offer's offerAmount
    ///
    /// @param _hashes[i * 2] The `r` component of the maker's / filler's signature
    /// for the i'th offer / fill
    ///
    /// @param _hashes[i * 2 + 1] The `s` component of the maker's / filler's signature
    /// for the i'th offer / fill
    ///
    /// @param _addresses An array of user asset pairs in the form of:
    /// [
    ///     user_1_address,
    ///     asset_1_address,
    ///     user_1_address,
    ///     asset_2_address,
    ///     user_2_address,
    ///     asset_1_address,
    ///     ...
    /// ]
    function networkTrade(
        uint256[] calldata _values,
        bytes32[] calldata _hashes,
        address[] calldata _addresses
    )
        external
        onlyAdmin
        onlyActiveState
        nonReentrant
    {
        // Cache the operator address to reduce gas costs from storage reads
        address operatorAddress = operator;

        // `validateNetworkTrades` needs to calculate the hash keys of offers
        // to verify the signature of the offer.
        // The calculated hash keys for each offer is return to reduce repeated
        // computation.
        bytes32[] memory hashKeys = Utils.validateNetworkTrades(
            _values,
            _hashes,
            _addresses,
            operatorAddress
        );

        _creditMakerBalances(_values, _addresses);
        _creditMakerFeeBalances(_values, _addresses, operatorAddress);
        _deductMakerBalances(_values, _addresses);
        _storeOfferData(_values, hashKeys);

        // There may be excess tokens resulting from a trade
        // Any excess tokens are returned and recorded in `increments`
        uint256[] memory increments = Utils.performNetworkTrades(
            _values,
            _addresses,
            marketDapps
        );
        _incrementBalances(increments, 0, 0, increments.length - 1, _addresses);
    }

    /// @notice Cancels a perviously made offer and refunds the remaining offer
    /// amount to the offer maker.
    /// To reduce gas costs, the original parameters of the offer are not stored
    /// in the contract's storage, only the hash of the parameters is stored for
    /// verification, so the original parameters need to be re-specified here.
    ///
    /// The `_expectedavailableamount` is required to help prevent accidental
    /// cancellation of an offer ahead of time, for example, if there is
    /// a pending fill in the off-chain state.
    ///
    /// @param _values[0] The offerAmount and wantAmount of the offer
    /// bits(0..128): offer.offerAmount
    /// bits(128..256): offer.wantAmount
    ///
    /// @param _values[1] The fee amounts
    /// bits(0..128): offer.feeAmount
    /// bits(128..256): cancelFeeAmount
    ///
    /// @param _values[2] Additional offer and cancellation data
    /// bits(0..128): expectedAvailableAmount
    /// bits(128..136): prefixedSignature
    /// bits(136..144): The `v` component of the maker's signature for the cancellation
    /// bits(144..256): offer.nonce
    ///
    /// @param _hashes[0] The `r` component of the maker's signature for the cancellation
    /// @param _hashes[1] The `s` component of the maker's signature for the cancellation
    ///
    /// @param _addresses[0] offer.maker
    /// @param _addresses[1] offer.offerAssetId
    /// @param _addresses[2] offer.wantAssetId
    /// @param _addresses[3] offer.feeAssetId
    /// @param _addresses[4] offer.cancelFeeAssetId
    function cancel(
        uint256[] calldata _values,
        bytes32[] calldata _hashes,
        address[] calldata _addresses
    )
        external
        onlyAdmin
    {
        bytes32 offerHash = keccak256(abi.encode(
            OFFER_TYPEHASH,
            _addresses[0], // maker
            _addresses[1], // offerAssetId
            _values[0] & ~(~uint256(0) << 128), // offerAmount
            _addresses[2], // wantAssetId
            _values[0] >> 128, // wantAmount
            _addresses[3], // feeAssetId
            _values[1] & ~(~uint256(0) << 128), // feeAmount
            _values[2] >> 144 // offerNonce
        ));

        bytes32 cancelHash = keccak256(abi.encode(
            CANCEL_TYPEHASH,
            offerHash,
            _addresses[4],
            _values[1] >> 128
        ));

        _validateSignature(
            cancelHash,
            _addresses[0], // maker
            uint8((_values[2] & ~(~uint256(0) << 144)) >> 136), // v
            _hashes[0], // r
            _hashes[1], // s
            ((_values[2] & ~(~uint256(0) << 136)) >> 128) != 0 // prefixedSignature
        );

        _cancel(
            _addresses[0], // maker
            offerHash,
            _values[2] & ~(~uint256(0) << 128), // expectedAvailableAmount
            _addresses[1], // offerAssetId
            _values[2] >> 144, // offerNonce
            _addresses[4], // cancelFeeAssetId
            _values[1] >> 128 // cancelFeeAmount
        );
    }

    /// @notice Cancels an offer without requiring the maker's signature
    /// @dev This method is intended to be used in the case of a contract
    /// upgrade or in an emergency. It can only be invoked by an admin and only
    /// after the admin state has been set to `Escalated` by the contract owner.
    ///
    /// To reduce gas costs, the original parameters of the offer are not stored
    /// in the contract's storage, only the hash of the parameters is stored for
    /// verification, so the original parameters need to be re-specified here.
    ///
    /// The `_expectedavailableamount` is required to help prevent accidental
    /// cancellation of an offer ahead of time, for example, if there is
    /// a pending fill in the off-chain state.
    /// @param _maker The address of the offer's maker
    /// @param _offerAssetId The contract address of the offerred asset
    /// @param _offerAmount The number of tokens offerred
    /// @param _wantAssetId The contract address of the asset asked in return
    /// @param _wantAmount The number of tokens asked for in return
    /// @param _feeAssetId The contract address of the fee asset
    /// @param _feeAmount The number of tokens to pay as fees to the operator
    /// @param _offerNonce The nonce of the original offer
    /// @param _expectedAvailableAmount The offer amount remaining
    function adminCancel(
        address _maker,
        address _offerAssetId,
        uint256 _offerAmount,
        address _wantAssetId,
        uint256 _wantAmount,
        address _feeAssetId,
        uint256 _feeAmount,
        uint256 _offerNonce,
        uint256 _expectedAvailableAmount
    )
        external
        onlyAdmin
        onlyEscalatedAdminState
    {
        bytes32 offerHash = keccak256(abi.encode(
            OFFER_TYPEHASH,
            _maker,
            _offerAssetId,
            _offerAmount,
            _wantAssetId,
            _wantAmount,
            _feeAssetId,
            _feeAmount,
            _offerNonce
        ));

        _cancel(
            _maker,
            offerHash,
            _expectedAvailableAmount,
            _offerAssetId,
            _offerNonce,
            address(0),
            0
        );
    }

    /// @notice Announces a user's intention to cancel their offer
    /// @dev This method allows a user to cancel their offer without requiring
    /// admin permissions.
    /// An announcement followed by a delay is needed so that the off-chain
    /// service has time to update the off-chain state.
    ///
    /// To reduce gas costs, the original parameters of the offer are not stored
    /// in the contract's storage, only the hash of the parameters is stored for
    /// verification, so the original parameters need to be re-specified here.
    ///
    /// @param _maker The address of the offer's maker
    /// @param _offerAssetId The contract address of the offerred asset
    /// @param _offerAmount The number of tokens offerred
    /// @param _wantAssetId The contract address of the asset asked in return
    /// @param _wantAmount The number of tokens asked for in return
    /// @param _feeAssetId The contract address of the fee asset
    /// @param _feeAmount The number of tokens to pay as fees to the operator
    /// @param _offerNonce The nonce of the original offer
    function announceCancel(
        address _maker,
        address _offerAssetId,
        uint256 _offerAmount,
        address _wantAssetId,
        uint256 _wantAmount,
        address _feeAssetId,
        uint256 _feeAmount,
        uint256 _offerNonce
    )
        external
    {
        // Error code 11: announceCancel, invalid msg.sender
        require(_maker == msg.sender, "11");

        bytes32 offerHash = keccak256(abi.encode(
            OFFER_TYPEHASH,
            _maker,
            _offerAssetId,
            _offerAmount,
            _wantAssetId,
            _wantAmount,
            _feeAssetId,
            _feeAmount,
            _offerNonce
        ));

        // Error code 12: announceCancel, nothing left to cancel
        require(offers[offerHash] > 0, "12");

        uint256 cancellableAt = now.add(slowCancelDelay);
        cancellationAnnouncements[offerHash] = cancellableAt;

        emit AnnounceCancel(offerHash, cancellableAt);
    }

    /// @notice Executes an offer cancellation previously announced in `announceCancel`
    /// @dev This method allows a user to cancel their offer without requiring
    /// admin permissions.
    /// An announcement followed by a delay is needed so that the off-chain
    /// service has time to update the off-chain state.
    ///
    /// To reduce gas costs, the original parameters of the offer are not stored
    /// in the contract's storage, only the hash of the parameters is stored for
    /// verification, so the original parameters need to be re-specified here.
    ///
    /// @param _maker The address of the offer's maker
    /// @param _offerAssetId The contract address of the offerred asset
    /// @param _offerAmount The number of tokens offerred
    /// @param _wantAssetId The contract address of the asset asked in return
    /// @param _wantAmount The number of tokens asked for in return
    /// @param _feeAssetId The contract address of the fee asset
    /// @param _feeAmount The number of tokens to pay as fees to the operator
    /// @param _offerNonce The nonce of the original offer
    function slowCancel(
        address _maker,
        address _offerAssetId,
        uint256 _offerAmount,
        address _wantAssetId,
        uint256 _wantAmount,
        address _feeAssetId,
        uint256 _feeAmount,
        uint256 _offerNonce
    )
        external
    {
        bytes32 offerHash = keccak256(abi.encode(
            OFFER_TYPEHASH,
            _maker,
            _offerAssetId,
            _offerAmount,
            _wantAssetId,
            _wantAmount,
            _feeAssetId,
            _feeAmount,
            _offerNonce
        ));

        uint256 cancellableAt = cancellationAnnouncements[offerHash];
        // Error code 13: slowCancel, cancellation was not announced
        require(cancellableAt != 0, "13");
        // Error code 14: slowCancel, cancellation delay not yet reached
        require(now >= cancellableAt, "14");

        uint256 availableAmount = offers[offerHash];
        // Error code 15: slowCancel, nothing left to cancel
        require(availableAmount > 0, "15");

        delete cancellationAnnouncements[offerHash];
        _cancel(
            _maker,
            offerHash,
            availableAmount,
            _offerAssetId,
            _offerNonce,
            address(0),
            0
        );

        emit SlowCancel(offerHash, availableAmount);
    }

    /// @notice Withdraws tokens from the Broker contract to a user's wallet balance
    /// @dev The user's internal balance is decreased, and the tokens are transferred
    /// to the `_receivingAddress` signed by the user.
    /// @param _withdrawer The user address whose balance will be reduced
    /// @param _receivingAddress The address to tranfer the tokens to
    /// @param _assetId The contract address of the token to withdraw
    /// @param _amount The number of tokens to withdraw
    /// @param _feeAssetId The contract address of the fee asset
    /// @param _feeAmount The number of tokens to pay as fees to the operator
    /// @param _nonce An unused nonce to prevent replay attacks
    /// @param _v The `v` component of the `_user`'s signature
    /// @param _r The `r` component of the `_user`'s signature
    /// @param _s The `s` component of the `_user`'s signature
    /// @param _prefixedSignature Indicates whether the Ethereum signed message
    /// prefix should be prepended during signature verification
    function withdraw(
        address _withdrawer,
        address payable _receivingAddress,
        address _assetId,
        uint256 _amount,
        address _feeAssetId,
        uint256 _feeAmount,
        uint256 _nonce,
        uint8 _v,
        bytes32 _r,
        bytes32 _s,
        bool _prefixedSignature
    )
        external
        onlyAdmin
    {
        _markNonce(_nonce);

        _validateSignature(
            keccak256(abi.encode(
                WITHDRAW_TYPEHASH,
                _withdrawer,
                _receivingAddress,
                _assetId,
                _amount,
                _feeAssetId,
                _feeAmount,
                _nonce
            )),
            _withdrawer,
            _v,
            _r,
            _s,
            _prefixedSignature
        );

        _withdraw(
            _withdrawer,
            _receivingAddress,
            _assetId,
            _amount,
            _feeAssetId,
            _feeAmount,
            _nonce
        );
    }

    /// @notice Withdraws tokens without requiring the withdrawer's signature
    /// @dev This method is intended to be used in the case of a contract
    /// upgrade or in an emergency. It can only be invoked by an admin and only
    /// after the admin state has been set to `Escalated` by the contract owner.
    /// Unlike `withdraw`, tokens can only be withdrawn to the `_withdrawer`'s
    /// address.
    /// @param _withdrawer The user address whose balance will be reduced
    /// @param _assetId The contract address of the token to withdraw
    /// @param _amount The number of tokens to withdraw
    /// @param _nonce An unused nonce for balance tracking
    function adminWithdraw(
        address payable _withdrawer,
        address _assetId,
        uint256 _amount,
        uint256 _nonce
    )
        external
        onlyAdmin
        onlyEscalatedAdminState
    {
        _markNonce(_nonce);

        _withdraw(
            _withdrawer,
            _withdrawer,
            _assetId,
            _amount,
            address(0),
            0,
            _nonce
        );
    }

    /// @notice Announces a user's intention to withdraw their funds
    /// @dev This method allows a user to withdraw their funds without requiring
    /// admin permissions.
    /// An announcement followed by a delay before execution is needed so that
    /// the off-chain service has time to update the off-chain state.
    /// @param _assetId The contract address of the token to withdraw
    /// @param _amount The number of tokens to withdraw
    function announceWithdraw(
        address _assetId,
        uint256 _amount
    )
        external
    {

        // Error code 16: announceWithdraw, invalid withdrawal amount
        require(_amount > 0 && _amount <= balances[msg.sender][_assetId], "16");

        WithdrawalAnnouncement storage announcement = withdrawalAnnouncements[msg.sender][_assetId];

        announcement.withdrawableAt = now.add(slowWithdrawDelay);
        announcement.amount = _amount;

        emit AnnounceWithdraw(msg.sender, _assetId, _amount, announcement.withdrawableAt);
    }

    /// @notice Executes a withdrawal previously announced in `announceWithdraw`
    /// @dev This method allows a user to withdraw their funds without requiring
    /// admin permissions.
    /// An announcement followed by a delay before execution is needed so that
    /// the off-chain service has time to update the off-chain state.
    /// @param _withdrawer The user address whose balance will be reduced
    /// @param _assetId The contract address of the token to withdraw
    function slowWithdraw(
        address payable _withdrawer,
        address _assetId,
        uint256 _amount
    )
        external
    {
        WithdrawalAnnouncement memory announcement = withdrawalAnnouncements[_withdrawer][_assetId];

        // Error code 17: slowWithdraw, withdrawal was not announced
        require(announcement.withdrawableAt != 0, "17");
        // Error code 18: slowWithdraw, withdrawal delay not yet reached
        require(now >= announcement.withdrawableAt, "18");
        // Error code 19: slowWithdraw, withdrawal amount does not match announced amount
        require(announcement.amount == _amount, "19");

        delete withdrawalAnnouncements[_withdrawer][_assetId];
        _withdraw(
            _withdrawer,
            _withdrawer,
            _assetId,
            announcement.amount,
            address(0),
            0,
            0
        );
        emit SlowWithdraw(_withdrawer, _assetId, announcement.amount);
    }

    /// @notice Locks a user's balances for the first part of an atomic swap
    /// @param _addresses[0] maker: the address of the user to deduct the swap tokens from
    /// @param _addresses[1] taker: the address of the swap taker who will receive the swap tokens
    /// if the swap is completed through `executeSwap`
    /// @param _addresses[2] assetId: the contract address of the token to swap
    /// @param _addresses[3] feeAssetId: the contract address of the token to use as fees
    /// @param _values[0] amount: the number of tokens to lock and to transfer if the swap
    ///  is completed through `executeSwap`
    /// @param _values[1] expiryTime: the time in epoch seconds after which the swap will become cancellable
    /// @param _values[2] feeAmount: the number of tokens to be paid to the operator as fees
    /// @param _values[3] nonce: an unused nonce to prevent replay attacks
    /// @param _hashes[0] hashedSecret: the hash of the secret decided by the maker
    /// @param _hashes[1] The `r` component of the user's signature
    /// @param _hashes[2] The `s` component of the user's signature
    /// @param _v The `v` component of the user's signature
    /// @param _prefixedSignature Indicates whether the Ethereum signed message
    /// prefix should be prepended during signature verification
    function createSwap(
        address[4] calldata _addresses,
        uint256[4] calldata _values,
        bytes32[3] calldata _hashes,
        uint8 _v,
        bool _prefixedSignature
    )
        external
        onlyAdmin
        onlyActiveState
    {
        // Error code 20: createSwap, invalid swap amount
        require(_values[0] > 0, "20");
        // Error code 21: createSwap, expiry time has already passed
        require(_values[1] > now, "21");
        _validateAddress(_addresses[1]);

        bytes32 swapHash = _hashSwap(_addresses, _values, _hashes[0]);
        // Error code 22: createSwap, the swap is already active
        require(!atomicSwaps[swapHash], "22");

        _markNonce(_values[3]);

        _validateSignature(
            swapHash,
            _addresses[0], // swap.maker
            _v,
            _hashes[1], // r
            _hashes[2], // s
            _prefixedSignature
        );

        if (_addresses[3] == _addresses[2]) { // feeAssetId == assetId
            // Error code 23: createSwap, swap.feeAmount exceeds swap.amount
            require(_values[2] < _values[0], "23"); // feeAmount < amount
        } else {
            _decreaseBalance(
                _addresses[0], // maker
                _addresses[3], // feeAssetId
                _values[2], // feeAmount
                REASON_SWAP_FEE_GIVE,
                _values[3] // nonce
            );
        }

        _decreaseBalance(
            _addresses[0], // maker
            _addresses[2], // assetId
            _values[0], // amount
            REASON_SWAP_GIVE,
            _values[3] // nonce
        );

        atomicSwaps[swapHash] = true;
    }

    /// @notice Executes a swap by transferring the tokens previously locked through
    /// a `createSwap` call to the swap taker.
    ///
    /// @dev To reduce gas costs, the original parameters of the swap are not stored
    /// in the contract's storage, only the hash of the parameters is stored for
    /// verification, so the original parameters need to be re-specified here.
    ///
    /// @param _addresses[0] maker: the address of the user to deduct the swap tokens from
    /// @param _addresses[1] taker: the address of the swap taker who will receive the swap tokens
    /// @param _addresses[2] assetId: the contract address of the token to swap
    /// @param _addresses[3] feeAssetId: the contract address of the token to use as fees
    /// @param _values[0] amount: the number of tokens previously locked
    /// @param _values[1] expiryTime: the time in epoch seconds after which the swap will become cancellable
    /// @param _values[2] feeAmount: the number of tokens to be paid to the operator as fees
    /// @param _values[3] nonce: an unused nonce to prevent replay attacks
    /// @param _hashedSecret The hash of the secret decided by the maker
    /// @param _preimage The preimage of the `_hashedSecret`
    function executeSwap(
        address[4] calldata _addresses,
        uint256[4] calldata _values,
        bytes32 _hashedSecret,
        bytes calldata _preimage
    )
        external
    {
        bytes32 swapHash = _hashSwap(_addresses, _values, _hashedSecret);
        // Error code 24: executeSwap, swap is not active
        require(atomicSwaps[swapHash], "24");
        // Error code 25: executeSwap, hash of preimage does not match hashedSecret
        require(sha256(abi.encodePacked(sha256(_preimage))) == _hashedSecret, "25");

        uint256 takeAmount = _values[0];
        if (_addresses[3] == _addresses[2]) { // feeAssetId == assetId
            takeAmount = takeAmount.sub(_values[2]);
        }

        delete atomicSwaps[swapHash];

        _increaseBalance(
            _addresses[1], // taker
            _addresses[2], // assetId
            takeAmount,
            REASON_SWAP_RECEIVE,
            _values[3] // nonce
        );

        _increaseBalance(
            operator,
            _addresses[3], // feeAssetId
            _values[2], // feeAmount
            REASON_SWAP_FEE_RECEIVE,
            _values[3] // nonce
        );
    }

    /// @notice Cancels a swap and refunds the previously locked tokens to
    /// the swap maker.
    ///
    /// @dev To reduce gas costs, the original parameters of the swap are not stored
    /// in the contract's storage, only the hash of the parameters is stored for
    /// verification, so the original parameters need to be re-specified here.
    ///
    /// @param _addresses[0] maker: the address of the user to deduct the swap tokens from
    /// @param _addresses[1] taker: the address of the swap taker who will receive the swap tokens
    /// @param _addresses[2] assetId: the contract address of the token to swap
    /// @param _addresses[3] feeAssetId: the contract address of the token to use as fees
    /// @param _values[0] amount: the number of tokens previously locked
    /// @param _values[1] expiryTime: the time in epoch seconds after which the swap will become cancellable
    /// @param _values[2] feeAmount: the number of tokens to be paid to the operator as fees
    /// @param _values[3] nonce: an unused nonce to prevent replay attacks
    /// @param _hashedSecret The hash of the secret decided by the maker
    /// @param _cancelFeeAmount The number of tokens to be paid to the operator as the cancellation fee
    function cancelSwap(
        address[4] calldata _addresses,
        uint256[4] calldata _values,
        bytes32 _hashedSecret,
        uint256 _cancelFeeAmount
    )
        external
    {
        // Error code 26: cancelSwap, expiry time has not been reached
        require(_values[1] <= now, "26");
        bytes32 swapHash = _hashSwap(_addresses, _values, _hashedSecret);
        // Error code 27: cancelSwap, swap is not active
        require(atomicSwaps[swapHash], "27");

        uint256 cancelFeeAmount = _cancelFeeAmount;
        if (!adminAddresses[msg.sender]) { cancelFeeAmount = _values[2]; }

        // cancelFeeAmount < feeAmount
        // Error code 28: cancelSwap, cancelFeeAmount exceeds swap.feeAmount
        require(cancelFeeAmount <= _values[2], "28");

        uint256 refundAmount = _values[0];
        if (_addresses[3] == _addresses[2]) { // feeAssetId == assetId
            refundAmount = refundAmount.sub(cancelFeeAmount);
        }

        delete atomicSwaps[swapHash];

        _increaseBalance(
            _addresses[0], // maker
            _addresses[2], // assetId
            refundAmount,
            REASON_SWAP_CANCEL_RECEIVE,
            _values[3] // nonce
        );

        _increaseBalance(
            operator,
            _addresses[3], // feeAssetId
            cancelFeeAmount,
            REASON_SWAP_CANCEL_FEE_RECEIVE,
            _values[3] // nonce
        );

        if (_addresses[3] != _addresses[2]) { // feeAssetId != assetId
            uint256 refundFeeAmount = _values[2].sub(cancelFeeAmount);
            _increaseBalance(
                _addresses[0], // maker
                _addresses[3], // feeAssetId
                refundFeeAmount,
                REASON_SWAP_CANCEL_FEE_REFUND,
                _values[3] // nonce
            );
        }
    }

    /// @dev Credit fillers for each fill.wantAmount,and credit the operator
    /// for each fill.feeAmount. See the `trade` method for param details.
    /// @param _values Values from `trade`
    /// @param _addresses Addresses from `trade`
    /// @param _operator Address of the operator
    function _creditFillBalances(
        uint256[] memory _values,
        address[] memory _addresses,
        address _operator
    )
        private
    {
        uint256 min = _addresses.length;
        uint256 max = 0;
        uint256[] memory increments = new uint256[](_addresses.length / 2);

        // 1 + numOffers * 2
        uint256 i = 1 + (_values[0] & ~(~uint256(0) << 8)) * 2;
        // i + numFills * 2
        uint256 end = i + ((_values[0] & ~(~uint256(0) << 16)) >> 8) * 2;

        // loop fills
        for(i; i < end; i += 2) {
            // let assetIndex be filler.wantAssetIndex
            uint256 assetIndex = (_values[i] & ~(~uint256(0) << 24)) >> 16;
            uint256 wantAmount = _values[i + 1] >> 128;

            // credit fill.wantAmount to filler
            increments[assetIndex] = increments[assetIndex].add(wantAmount);
            if (min > assetIndex) { min = assetIndex; }
            if (max < assetIndex) { max = assetIndex; }

            uint256 feeAmount = _values[i] >> 128;
            if (feeAmount == 0) { continue; }

            // let assetIndex be filler.feeAssetIndex
            assetIndex = (_values[i] & ~(~uint256(0) << 32)) >> 24;
            uint256 feeAssetIndex = ((_values[i] & ~(~uint256(0) << 40)) >> 32);

            // override the operator slot with the actual operator address
            // and set the operator fee asset ID slot to be the fill's feeAssetId
            _addresses[feeAssetIndex * 2] = _operator;
            if (_addresses[feeAssetIndex * 2 + 1] == address(1)) {
                // a value of address(1) indicates that this value has not been set before
                _addresses[feeAssetIndex * 2 + 1] = _addresses[assetIndex * 2 + 1];
            } else {
                // if the value is not address(1) then it must have been previously set
                // to a value matching the make's feeAssetId
                // Error code 29: _creditFillBalances, invalid operator feeAssetId
                require(_addresses[feeAssetIndex * 2 + 1] == _addresses[assetIndex * 2 + 1], "29");
            }

            // credit fill.feeAmount to operator
            increments[feeAssetIndex] = increments[feeAssetIndex].add(feeAmount);
            if (min > feeAssetIndex) { min = feeAssetIndex; }
            if (max < feeAssetIndex) { max = feeAssetIndex; }
        }

        _incrementBalances(increments, 1, min, max, _addresses);
    }

    /// @dev Credit makers for each amount received through a matched fill.
    /// See the `trade` method for param details.
    /// @param _values Values from `trade`
    /// @param _addresses Addresses from `trade`
    function _creditMakerBalances(
        uint256[] memory _values,
        address[] memory _addresses
    )
        private
    {
        uint256 min = _addresses.length;
        uint256 max = 0;
        uint256[] memory increments = new uint256[](_addresses.length / 2);

        uint256 i = 1;
        // i += numOffers * 2
        i += (_values[0] & ~(~uint256(0) << 8)) * 2;
        // i += numFills * 2
        i += ((_values[0] & ~(~uint256(0) << 16)) >> 8) * 2;

        uint256 end = _values.length;

        // loop matches
        for(i; i < end; i++) {
            // match.offerIndex
            uint256 offerIndex = _values[i] & ~(~uint256(0) << 8);
            // offer.wantAssetIndex
            uint256 wantAssetIndex = (_values[1 + offerIndex * 2] & ~(~uint256(0) << 24)) >> 16;

            // match.takeAmount
            uint256 amount = _values[i] >> 128;
            // receiveAmount = match.takeAmount * offer.wantAmount / offer.offerAmount
            amount = amount.mul(_values[2 + offerIndex * 2] >> 128)
                           .div(_values[2 + offerIndex * 2] & ~(~uint256(0) << 128));

            // credit maker for the amount received from the match
            increments[wantAssetIndex] = increments[wantAssetIndex].add(amount);
            if (min > wantAssetIndex) { min = wantAssetIndex; }
            if (max < wantAssetIndex) { max = wantAssetIndex; }
        }

        _incrementBalances(increments, 1, min, max, _addresses);
    }

    /// @dev Credit the operator for each offer.feeAmount if the offer has not
    /// been recorded through a previous `trade` call.
    /// See the `trade` method for param details.
    /// @param _values Values from `trade`
    /// @param _addresses Addresses from `trade`
    function _creditMakerFeeBalances(
        uint256[] memory _values,
        address[] memory _addresses,
        address _operator
    )
        private
    {
        uint256 min = _addresses.length;
        uint256 max = 0;
        uint256[] memory increments = new uint256[](_addresses.length / 2);

        uint256 i = 1;
        // i + numOffers * 2
        uint256 end = i + (_values[0] & ~(~uint256(0) << 8)) * 2;

        // loop offers
        for(i; i < end; i += 2) {
            uint256 nonce = (_values[i] & ~(~uint256(0) << 128)) >> 56;
            if (_nonceTaken(nonce)) { continue; }

            uint256 feeAmount = _values[i] >> 128;
            if (feeAmount == 0) { continue; }

            // let assetIndex be maker.feeAssetIndex
            uint256 assetIndex = (_values[i] & ~(~uint256(0) << 32)) >> 24;
            uint256 feeAssetIndex = (_values[i] & ~(~uint256(0) << 40)) >> 32;

            // override the operator slot with the actual operator address
            // and set the operator fee asset ID slot to be the make's feeAssetId
            _addresses[feeAssetIndex * 2] = _operator;
            if (_addresses[feeAssetIndex * 2 + 1] == address(1)) {
                // a value of address(1) indicates that this value has not been set before
                _addresses[feeAssetIndex * 2 + 1] = _addresses[assetIndex * 2 + 1];
            } else {
                // if the value is not address(1) then it must have been previously set
                // to a value matching the make's feeAssetId
                // Error code 30: _creditMakerFeeBalances, invalid operator feeAssetId
                require(_addresses[feeAssetIndex * 2 + 1] == _addresses[assetIndex * 2 + 1], "30");
            }

            // credit make.feeAmount to operator
            increments[feeAssetIndex] = increments[feeAssetIndex].add(feeAmount);
            if (min > feeAssetIndex) { min = feeAssetIndex; }
            if (max < feeAssetIndex) { max = feeAssetIndex; }
        }

        _incrementBalances(increments, 1, min, max, _addresses);
    }

    /// @dev Deduct tokens from fillers for each fill.offerAmount
    /// and each fill.feeAmount.
    /// See the `trade` method for param details.
    /// @param _values Values from `trade`
    /// @param _addresses Addresses from `trade`
    function _deductFillBalances(
        uint256[] memory _values,
        address[] memory _addresses
    )
        private
    {
        uint256 min = _addresses.length;
        uint256 max = 0;
        uint256[] memory decrements = new uint256[](_addresses.length / 2);

        // 1 + numOffers * 2
        uint256 i = 1 + (_values[0] & ~(~uint256(0) << 8)) * 2;
        // i + numFills * 2
        uint256 end = i + ((_values[0] & ~(~uint256(0) << 16)) >> 8) * 2;

        // loop fills
        for(i; i < end; i += 2) {
            uint256 offerAssetIndex = (_values[i] & ~(~uint256(0) << 16)) >> 8;
            uint256 offerAmount = _values[i + 1] & ~(~uint256(0) << 128);

            // deduct fill.offerAmount from filler
            decrements[offerAssetIndex] = decrements[offerAssetIndex].add(offerAmount);
            if (min > offerAssetIndex) { min = offerAssetIndex; }
            if (max < offerAssetIndex) { max = offerAssetIndex; }

            uint256 feeAmount = _values[i] >> 128;
            if (feeAmount == 0) { continue; }

            // deduct fill.feeAmount from filler
            uint256 feeAssetIndex = (_values[i] & ~(~uint256(0) << 32)) >> 24;
            decrements[feeAssetIndex] = decrements[feeAssetIndex].add(feeAmount);
            if (min > feeAssetIndex) { min = feeAssetIndex; }
            if (max < feeAssetIndex) { max = feeAssetIndex; }
        }

        _decrementBalances(decrements, min, max, _addresses);
    }

    /// @dev Deduct tokens from makers for each offer.offerAmount
    /// and each offer.feeAmount if the offer has not been recorded
    /// through a previous `trade` call.
    /// See the `trade` method for param details.
    /// @param _values Values from `trade`
    /// @param _addresses Addresses from `trade`
    function _deductMakerBalances(
        uint256[] memory _values,
        address[] memory _addresses
    )
        private
    {
        uint256 min = _addresses.length;
        uint256 max = 0;
        uint256[] memory decrements = new uint256[](_addresses.length / 2);

        uint256 i = 1;
        // i + numOffers * 2
        uint256 end = i + (_values[0] & ~(~uint256(0) << 8)) * 2;

        // loop offers
        for(i; i < end; i += 2) {
            uint256 nonce = (_values[i] & ~(~uint256(0) << 128)) >> 56;
            if (_nonceTaken(nonce)) { continue; }

            uint256 offerAssetIndex = (_values[i] & ~(~uint256(0) << 16)) >> 8;
            uint256 offerAmount = _values[i + 1] & ~(~uint256(0) << 128);

            // deduct make.offerAmount from maker
            decrements[offerAssetIndex] = decrements[offerAssetIndex].add(offerAmount);
            if (min > offerAssetIndex) { min = offerAssetIndex; }
            if (max < offerAssetIndex) { max = offerAssetIndex; }

            uint256 feeAmount = _values[i] >> 128;
            if (feeAmount == 0) { continue; }

            // deduct make.feeAmount from maker
            uint256 feeAssetIndex = (_values[i] & ~(~uint256(0) << 32)) >> 24;
            decrements[feeAssetIndex] = decrements[feeAssetIndex].add(feeAmount);
            if (min > feeAssetIndex) { min = feeAssetIndex; }
            if (max < feeAssetIndex) { max = feeAssetIndex; }
        }

        _decrementBalances(decrements, min, max, _addresses);
    }

    /// @dev Reduce available offer amounts of offers and store the remaining
    /// offer amount in the `offers` mapping.
    /// Offer nonces will also be marked as taken.
    /// See the `trade` method for param details.
    /// @param _values Values from `trade`
    /// @param _hashKeys An array of offer hash keys
    function _storeOfferData(
        uint256[] memory _values,
        bytes32[] memory _hashKeys
    )
        private
    {
        // Decrements with size numOffers
        uint256[] memory decrements = new uint256[](_values[0] & ~(~uint256(0) << 8));

        uint256 i = 1;
        // i += numOffers * 2
        i += (_values[0] & ~(~uint256(0) << 8)) * 2;
        // i += numFills * 2
        i += ((_values[0] & ~(~uint256(0) << 16)) >> 8) * 2;

        uint256 end = _values.length;

        // loop matches
        for (i; i < end; i++) {
            uint256 offerIndex = _values[i] & ~(~uint256(0) << 8);
            uint256 takeAmount = _values[i] >> 128;
            decrements[offerIndex] = decrements[offerIndex].add(takeAmount);
        }

        i = 0;
        end = _values[0] & ~(~uint256(0) << 8); // numOffers

        // loop offers
        for (i; i < end; i++) {
            uint256 nonce = (_values[i * 2 + 1] & ~(~uint256(0) << 128)) >> 56;
            bool existingOffer = _nonceTaken(nonce);
            bytes32 hashKey = _hashKeys[i];

            uint256 availableAmount = existingOffer ? offers[hashKey] : (_values[i * 2 + 2] & ~(~uint256(0) << 128));
            // Error code 31: _storeOfferData, offer's available amount is zero
            require(availableAmount > 0, "31");

            uint256 remainingAmount = availableAmount.sub(decrements[i]);
            if (remainingAmount > 0) { offers[hashKey] = remainingAmount; }
            if (existingOffer && remainingAmount == 0) { delete offers[hashKey]; }

            if (!existingOffer) { _markNonce(nonce); }
        }
    }

    /// @dev Mark all fill nonces as taken in the `usedNonces` mapping.
    /// This also validates fill uniquness within the set of fills in `_values`,
    /// since fill nonces are marked one at a time with validation that the
    /// nonce to be marked has not been marked before.
    /// See the `trade` method for param details.
    /// @param _values Values from `trade`
    function _storeFillNonces(uint256[] memory _values) private {
        // 1 + numOffers * 2
        uint256 i = 1 + (_values[0] & ~(~uint256(0) << 8)) * 2;
        // i + numFills * 2
        uint256 end = i + ((_values[0] & ~(~uint256(0) << 16)) >> 8) * 2;

        // loop fills
        for(i; i < end; i += 2) {
            uint256 nonce = (_values[i] & ~(~uint256(0) << 128)) >> 56;
            _markNonce(nonce);
        }
    }

    /// @dev The actual cancellation logic shared by `cancel`, `adminCancel`,
    /// `slowCancel`.
    /// The remaining offer amount is refunded back to the offer's maker, and
    /// the specified cancellation fee will be deducted from the maker's balances.
    function _cancel(
        address _maker,
        bytes32 _offerHash,
        uint256 _expectedAvailableAmount,
        address _offerAssetId,
        uint256 _offerNonce,
        address _cancelFeeAssetId,
        uint256 _cancelFeeAmount
    )
        private
    {
        uint256 refundAmount = offers[_offerHash];
        // Error code 32: _cancel, there is no offer amount left to cancel
        require(refundAmount > 0, "32");
        // Error code 33: _cancel, the remaining offer amount does not match
        // the expectedAvailableAmount
        require(refundAmount == _expectedAvailableAmount, "33");

        delete offers[_offerHash];

        if (_cancelFeeAssetId == _offerAssetId) {
            refundAmount = refundAmount.sub(_cancelFeeAmount);
        } else {
            _decreaseBalance(
                _maker,
                _cancelFeeAssetId,
                _cancelFeeAmount,
                REASON_CANCEL_FEE_GIVE,
                _offerNonce
            );
        }

        _increaseBalance(
            _maker,
            _offerAssetId,
            refundAmount,
            REASON_CANCEL,
            _offerNonce
        );

        _increaseBalance(
            operator,
            _cancelFeeAssetId,
            _cancelFeeAmount,
            REASON_CANCEL_FEE_RECEIVE,
            _offerNonce // offer nonce
        );
    }

    /// @dev The actual withdrawal logic shared by `withdraw`, `adminWithdraw`,
    /// `slowWithdraw`. The specified amount is deducted from the `_withdrawer`'s
    /// contract balance and transferred to the external `_receivingAddress`,
    /// and the specified withdrawal fee will be deducted from the `_withdrawer`'s
    /// balance.
    function _withdraw(
        address _withdrawer,
        address payable _receivingAddress,
        address _assetId,
        uint256 _amount,
        address _feeAssetId,
        uint256 _feeAmount,
        uint256 _nonce
    )
        private
        nonReentrant
    {
        // Error code 34: _withdraw, invalid withdrawal amount
        require(_amount > 0, "34");

        _validateAddress(_receivingAddress);

        _decreaseBalance(
            _withdrawer,
            _assetId,
            _amount,
            REASON_WITHDRAW,
            _nonce
        );

        _increaseBalance(
            operator,
            _feeAssetId,
            _feeAmount,
            REASON_WITHDRAW_FEE_RECEIVE,
            _nonce
        );

        uint256 withdrawAmount;

        if (_feeAssetId == _assetId) {
            withdrawAmount = _amount.sub(_feeAmount);
        } else {
            _decreaseBalance(
                _withdrawer,
                _feeAssetId,
                _feeAmount,
                REASON_WITHDRAW_FEE_GIVE,
                _nonce
            );
            withdrawAmount = _amount;
        }

        if (_assetId == ETHER_ADDR) {
            _receivingAddress.transfer(withdrawAmount);
            return;
        }

        Utils.transferTokensOut(
            _receivingAddress,
            _assetId,
            withdrawAmount
        );
    }

    /// @dev Creates a hash key for a swap using the swap's parameters
    /// @param _addresses[0] Address of the user making the swap
    /// @param _addresses[1] Address of the user taking the swap
    /// @param _addresses[2] Contract address of the asset to swap
    /// @param _addresses[3] Contract address of the fee asset
    /// @param _values[0] The number of tokens to be transferred
    /// @param _values[1] The time in epoch seconds after which the swap will become cancellable
    /// @param _values[2] The number of tokens to pay as fees to the operator
    /// @param _values[3] The swap nonce to prevent replay attacks
    /// @param _hashedSecret The hash of the secret decided by the maker
    /// @return The hash key of the swap
    function _hashSwap(
        address[4] memory _addresses,
        uint256[4] memory _values,
        bytes32 _hashedSecret
    )
        private
        pure
        returns (bytes32)
    {
        return keccak256(abi.encode(
            SWAP_TYPEHASH,
            _addresses[0], // maker
            _addresses[1], // taker
            _addresses[2], // assetId
            _values[0], // amount
            _hashedSecret, // hashedSecret
            _values[1], // expiryTime
            _addresses[3], // feeAssetId
            _values[2], // feeAmount
            _values[3] // nonce
        ));
    }

    /// @dev Checks if the `_nonce` had been previously taken.
    /// To reduce gas costs, a single `usedNonces` value is used to
    /// store the state of 256 nonces, using the formula:
    /// nonceTaken = "usedNonces[_nonce / 256] bit (_nonce % 256)" != 0
    /// For example:
    /// nonce 0 taken: "usedNonces[0] bit 0" != 0 (0 / 256 = 0, 0 % 256 = 0)
    /// nonce 1 taken: "usedNonces[0] bit 1" != 0 (1 / 256 = 0, 1 % 256 = 1)
    /// nonce 2 taken: "usedNonces[0] bit 2" != 0 (2 / 256 = 0, 2 % 256 = 2)
    /// nonce 255 taken: "usedNonces[0] bit 255" != 0 (255 / 256 = 0, 255 % 256 = 255)
    /// nonce 256 taken: "usedNonces[1] bit 0" != 0 (256 / 256 = 1, 256 % 256 = 0)
    /// nonce 257 taken: "usedNonces[1] bit 1" != 0 (257 / 256 = 1, 257 % 256 = 1)
    /// @param _nonce The nonce to check
    /// @return Whether the nonce has been taken
    function _nonceTaken(uint256 _nonce) private view returns (bool) {
        uint256 slotData = _nonce.div(256);
        uint256 shiftedBit = uint256(1) << _nonce.mod(256);
        uint256 bits = usedNonces[slotData];

        // The check is for "!= 0" instead of "== 1" because the shiftedBit is
        // not at the zero'th position, so it would require an additional
        // shift to compare it with "== 1"
        return bits & shiftedBit != 0;
    }

    /// @dev Sets the corresponding `_nonce` bit to 1.
    /// An error will be raised if the corresponding `_nonce` bit was
    /// previously set to 1.
    /// See `_nonceTaken` for details on calculating the corresponding `_nonce` bit.
    /// @param _nonce The nonce to mark
    function _markNonce(uint256 _nonce) private {
        // Error code 35: _markNonce, nonce cannot be zero
        require(_nonce != 0, "35");

        uint256 slotData = _nonce.div(256);
        uint256 shiftedBit = 1 << _nonce.mod(256);
        uint256 bits = usedNonces[slotData];

        // Error code 36: _markNonce, nonce has already been marked
        require(bits & shiftedBit == 0, "36");

        usedNonces[slotData] = bits | shiftedBit;
    }

    /// @dev Validates that the specified `_hash` was signed by the specified `_user`.
    /// This method supports the EIP712 specification, the older Ethereum
    /// signed message specification is also supported for backwards compatibility.
    /// @param _hash The original hash that was signed by the user
    /// @param _user The user who signed the hash
    /// @param _v The `v` component of the `_user`'s signature
    /// @param _r The `r` component of the `_user`'s signature
    /// @param _s The `s` component of the `_user`'s signature
    /// @param _prefixed If true, the signature will be verified
    /// against the Ethereum signed message specification instead of the
    /// EIP712 specification
    function _validateSignature(
        bytes32 _hash,
        address _user,
        uint8 _v,
        bytes32 _r,
        bytes32 _s,
        bool _prefixed
    )
        private
        pure
    {
        Utils.validateSignature(
            _hash,
            _user,
            _v,
            _r,
            _s,
            _prefixed
        );
    }

    /// @dev A utility method to increase the balance of a user.
    /// A corressponding `BalanceIncrease` event will also be emitted.
    /// @param _user The address to increase balance for
    /// @param _assetId The asset's contract address
    /// @param _amount The number of tokens to increase the balance by
    /// @param _reasonCode The reason code for the `BalanceIncrease` event
    /// @param _nonce The nonce for the `BalanceIncrease` event
    function _increaseBalance(
        address _user,
        address _assetId,
        uint256 _amount,
        uint256 _reasonCode,
        uint256 _nonce
    )
        private
    {
        if (_amount == 0) { return; }
        balances[_user][_assetId] = balances[_user][_assetId].add(_amount);

        emit BalanceIncrease(
            _user,
            _assetId,
            _amount,
            _reasonCode,
            _nonce
        );
    }

    /// @dev A utility method to decrease the balance of a user.
    /// A corressponding `BalanceDecrease` event will also be emitted.
    /// @param _user The address to decrease balance for
    /// @param _assetId The asset's contract address
    /// @param _amount The number of tokens to decrease the balance by
    /// @param _reasonCode The reason code for the `BalanceDecrease` event
    /// @param _nonce The nonce for the `BalanceDecrease` event
    function _decreaseBalance(
        address _user,
        address _assetId,
        uint256 _amount,
        uint256 _reasonCode,
        uint256 _nonce
    )
        private
    {
        if (_amount == 0) { return; }
        balances[_user][_assetId] = balances[_user][_assetId].sub(_amount);

        emit BalanceDecrease(
            _user,
            _assetId,
            _amount,
            _reasonCode,
            _nonce
        );
    }

    /// @dev Ensures that `_address` is not the zero address
    /// @param _address The address to check
    function _validateAddress(address _address) private pure {
        Utils.validateAddress(_address);
    }

    /// @dev A utility method to increase balances of multiple addresses.
    /// A corressponding `Increment` event will also be emitted.
    /// @param _increments An array of amounts to increase a user's balance by,
    /// the corresponding user and assetId is referenced by
    /// _addresses[index * 2] and _addresses[index * 2 + 1] respectively
    /// @param _static Indicates if the amount was pre-calculated or only known
    /// at the time the transaction was executed
    /// @param _i The index to start the increment loop at (inclusive)
    /// @param _end The index to end the increment loop at (inclusive)
    /// @param _addresses An array of user asset pairs in the form of:
    /// [
    ///     user_1_address,
    ///     asset_1_address,
    ///     user_1_address,
    ///     asset_2_address,
    ///     user_2_address,
    ///     asset_1_address,
    ///     ...
    /// ]
    function _incrementBalances(
        uint256[] memory _increments,
        uint256 _static,
        uint256 _i,
        uint256 _end,
        address[] memory _addresses
    )
        private
    {
        for(_i; _i <= _end; _i++) {
            uint256 increment = _increments[_i];
            if (increment == 0) { continue; }

            balances[_addresses[_i * 2]][_addresses[_i * 2 + 1]] =
            balances[_addresses[_i * 2]][_addresses[_i * 2 + 1]].add(increment);

            emit Increment((_i << 248) | (_static << 240) | increment);
        }
    }

    /// @dev A utility method to decrease balances of multiple addresses.
    /// A corressponding `Decrement` event will also be emitted.
    /// @param _decrements An array of amounts to decrease a user's balance by,
    /// the corresponding user and assetId is referenced by
    /// _addresses[index * 2] and _addresses[index * 2 + 1] respectively
    /// @param _i The index to start the increment loop at (inclusive)
    /// @param _end The index to end the increment loop at (inclusive)
    /// @param _addresses An array of user asset pairs in the form of:
    /// [
    ///     user_1_address,
    ///     asset_1_address,
    ///     user_1_address,
    ///     asset_2_address,
    ///     user_2_address,
    ///     asset_1_address,
    ///     ...
    /// ]
    function _decrementBalances(
        uint256[] memory _decrements,
        uint256 _i,
        uint256 _end,
        address[] memory _addresses
    )
        private
    {
        for(_i; _i <= _end; _i++) {
            uint256 decrement = _decrements[_i];
            if (decrement == 0) { continue; }

            balances[_addresses[_i * 2]][_addresses[_i * 2 + 1]] =
            balances[_addresses[_i * 2]][_addresses[_i * 2 + 1]].sub(decrement);

            emit Decrement(_i << 248 | decrement);
        }
    }
}