contract Broker is Claimable {
    using SafeMath for uint256;

    struct Offer {
        address maker;
        address offerAsset;
        address wantAsset;
        uint64 nonce;
        uint256 offerAmount;
        uint256 wantAmount;
        uint256 availableAmount; // the remaining offer amount
    }

    struct AnnouncedWithdrawal {
        uint256 amount;
        uint256 canWithdrawAt;
    }

    // Exchange states
    enum State { Active, Inactive }
    State public state;

    // The maximum announce delay in seconds
    // (7 days * 60 mins * 60 seconds)
    uint32 constant maxAnnounceDelay = 604800;
    // Ether token "address" is set as the constant 0x00
    address constant etherAddr = address(0);

    // deposits
    uint8 constant ReasonDeposit = 0x01;
    // making an offer
    uint8 constant ReasonMakerGive = 0x02;
    uint8 constant ReasonMakerFeeGive = 0x10;
    uint8 constant ReasonMakerFeeReceive = 0x11;
    // filling an offer
    uint8 constant ReasonFillerGive = 0x03;
    uint8 constant ReasonFillerFeeGive = 0x04;
    uint8 constant ReasonFillerReceive = 0x05;
    uint8 constant ReasonMakerReceive = 0x06;
    uint8 constant ReasonFillerFeeReceive = 0x07;
    // cancelling an offer
    uint8 constant ReasonCancel = 0x08;
    uint8 constant ReasonCancelFeeGive = 0x12;
    uint8 constant ReasonCancelFeeReceive = 0x13;
    // withdrawals
    uint8 constant ReasonWithdraw = 0x09;
    uint8 constant ReasonWithdrawFeeGive = 0x14;
    uint8 constant ReasonWithdrawFeeReceive = 0x15;

    // The coordinator sends trades (balance transitions) to the exchange
    address public coordinator;
    // The operator receives fees
    address public operator;
    // The time required to wait after a cancellation is announced
    // to let the operator detect it in non-byzantine conditions
    uint32 public cancelAnnounceDelay;
    // The time required to wait after a withdrawal is announced
    // to let the operator detect it in non-byzantine conditions
    uint32 public withdrawAnnounceDelay;

    // User balances by: userAddress => assetHash => balance
    mapping(address => mapping(address => uint256)) public balances;
    // Offers by the creation transaction hash: transactionHash => offer
    mapping(bytes32 => Offer) public offers;
    // A record of which hashes have been used before
    mapping(bytes32 => bool) public usedHashes;
    // Set of whitelisted spender addresses allowed by the owner
    mapping(address => bool) public whitelistedSpenders;
    // Spenders which have been approved by individual user as: userAddress => spenderAddress => true
    mapping(address => mapping(address => bool)) public approvedSpenders;
    // Announced withdrawals by: userAddress => assetHash => data
    mapping(address => mapping(address => AnnouncedWithdrawal)) public announcedWithdrawals;
    // Announced cancellations by: offerHash => data
    mapping(bytes32 => uint256) public announcedCancellations;

    // Emitted when new offers made
    event Make(address indexed maker, bytes32 indexed offerHash);
    // Emitted when offers are filled
    event Fill(address indexed filler, bytes32 indexed offerHash, uint256 amountFilled, uint256 amountTaken, address indexed maker);
    // Emitted when offers are cancelled
    event Cancel(address indexed maker, bytes32 indexed offerHash);
    // Emitted on any balance state transition (+ve)
    event BalanceIncrease(address indexed user, address indexed token, uint256 amount, uint8 indexed reason);
    // Emitted on any balance state transition (-ve)
    event BalanceDecrease(address indexed user, address indexed token, uint256 amount, uint8 indexed reason);
    // Emitted when a withdrawal is annnounced
    event WithdrawAnnounce(address indexed user, address indexed token, uint256 amount, uint256 canWithdrawAt);
    // Emitted when a cancellation is annnounced
    event CancelAnnounce(address indexed user, bytes32 indexed offerHash, uint256 canCancelAt);
    // Emitted when a user approved a spender
    event SpenderApprove(address indexed user, address indexed spender);
    // Emitted when a user rescinds approval for a spender
    event SpenderRescind(address indexed user, address indexed spender);

    /// @notice Initializes the Broker contract
    /// @dev The coordinator and operator is initialized
    /// to be the address of the sender. The Broker is immediately
    /// put into an active state, with maximum exit delays set.
    constructor()
        public
    {
        coordinator = msg.sender;
        operator = msg.sender;
        cancelAnnounceDelay = maxAnnounceDelay;
        withdrawAnnounceDelay = maxAnnounceDelay;
        state = State.Active;
    }

    modifier onlyCoordinator() {
        require(
            msg.sender == coordinator,
            "Invalid sender"
        );
        _;
    }

    modifier onlyActiveState() {
        require(
            state == State.Active,
            "Invalid state"
        );
        _;
    }

    modifier onlyInactiveState() {
        require(
            state == State.Inactive,
            "Invalid state"
        );
        _;
    }

    modifier notMoreThanMaxDelay(uint32 _delay) {
        require(
            _delay <= maxAnnounceDelay,
            "Invalid delay"
        );
        _;
    }

    modifier unusedReasonCode(uint8 _reasonCode) {
        require(
            _reasonCode > ReasonWithdrawFeeReceive,
            "Invalid reason code"
        );
        _;
    }

    /// @notice Sets the Broker contract state
    /// @dev There are only two states - Active & Inactive.
    ///
    /// The Active state is the normal operating state for the contract -
    /// deposits, trading and withdrawals can be carried out.
    ///
    /// In the Inactive state, the coordinator can invoke additional
    /// emergency methods such as emergencyCancel and emergencyWithdraw,
    /// without the cooperation of users. However, deposits and trading
    /// methods cannot be invoked at that time. This state is meant
    /// primarily to terminate and upgrade the contract, or to be used
    /// in the event that the contract is considered no longer viable
    /// to continue operation, and held tokens should be immediately
    /// withdrawn to their respective owners.
    /// @param _state The state to transition the contract into
    function setState(State _state) external onlyOwner { state = _state; }

    /// @notice Sets the coordinator address.
    /// @dev All standard operations (except `depositEther`)
    /// must be invoked by the coordinator.
    /// @param _coordinator The address to set as the coordinator
    function setCoordinator(address _coordinator) external onlyOwner {
        _validateAddress(_coordinator);
        coordinator = _coordinator;
    }

    /// @notice Sets the operator address.
    /// @dev All fees are paid to the operator.
    /// @param _operator The address to set as the operator
    function setOperator(address _operator) external onlyOwner {
        _validateAddress(operator);
        operator = _operator;
    }

    /// @notice Sets the delay between when a cancel
    /// intention must be announced, and when the cancellation
    /// can actually be executed on-chain
    /// @dev This delay exists so that the coordinator has time to
    /// respond when a user is attempting to bypass it and cancel
    /// offers directly on-chain.
    /// Note that this is an direct on-chain cancellation
    /// is an atypical operation - see `slowCancel`
    /// for more details.
    /// @param _delay The delay in seconds
    function setCancelAnnounceDelay(uint32 _delay)
        external
        onlyOwner
        notMoreThanMaxDelay(_delay)
    {
        cancelAnnounceDelay = _delay;
    }

    /// @notice Sets the delay (in seconds) between when a withdrawal
    /// intention must be announced, and when the withdrawal
    /// can actually be executed on-chain.
    /// @dev This delay exists so that the coordinator has time to
    /// respond when a user is attempting to bypass it and cancel
    /// offers directly on-chain. See `announceWithdraw` and
    /// `slowWithdraw` for more details.
    /// @param _delay The delay in seconds
    function setWithdrawAnnounceDelay(uint32 _delay)
        external
        onlyOwner
        notMoreThanMaxDelay(_delay)
    {
        withdrawAnnounceDelay = _delay;
    }

    /// @notice Adds an address to the set of allowed spenders.
    /// @dev Spenders are meant to be additional EVM contracts that
    /// will allow adding or upgrading of trading functionality, without
    /// having to cancel all offers and withdraw all tokens for all users.
    /// This whitelist ensures that all approved spenders are contracts
    /// that have been verified by the owner. Note that each user also
    /// has to invoke `approveSpender` to actually allow the `_spender`
    /// to spend his/her balance, so that they can examine / verify
    /// the new spender contract first.
    /// @param _spender The address to add as a whitelisted spender
    function addSpender(address _spender)
        external
        onlyOwner
    {
        _validateAddress(_spender);
        whitelistedSpenders[_spender] = true;
    }

    /// @notice Removes an address from the set of allowed spenders.
    /// @dev Note that removing a spender from the whitelist will not
    /// prevent already approved spenders from spending a user's balance.
    /// This is to ensure that the spender contracts can be certain that once
    /// an approval is done, the owner cannot rescient spending priviledges,
    /// and cause tokens to be withheld or locked in the spender contract.
    /// Users must instead manually rescind approvals using `rescindApproval`
    /// after the `_spender` has been removed from the whitelist.
    /// @param _spender The address to remove as a whitelisted spender
    function removeSpender(address _spender)
        external
        onlyOwner
    {
        _validateAddress(_spender);
        delete whitelistedSpenders[_spender];
    }

    /// @notice Deposits Ethereum tokens under the `msg.sender`'s balance
    /// @dev Allows sending ETH to the contract, and increasing
    /// the user's contract balance by the amount sent in.
    /// This operation is only usable in an Active state to prevent
    /// a terminated contract from receiving tokens.
    function depositEther()
        external
        payable
        onlyActiveState
    {
        require(
            msg.value > 0,
            'Invalid value'
        );
        balances[msg.sender][etherAddr] = balances[msg.sender][etherAddr].add(msg.value);
        emit BalanceIncrease(msg.sender, etherAddr, msg.value, ReasonDeposit);
    }

    /// @notice Deposits ERC20 tokens under the `_user`'s balance
    /// @dev Allows sending ERC20 tokens to the contract, and increasing
    /// the user's contract balance by the amount sent in. This operation
    /// can only be used after an ERC20 `approve` operation for a
    /// sufficient amount has been carried out.
    ///
    /// Note that this operation does not require user signatures as
    /// a valid ERC20 `approve` call is considered as intent to deposit
    /// the tokens. This is as there is no other ERC20 methods that this
    /// contract can call.
    ///
    /// This operation can only be called by the coordinator,
    /// and should be autoamtically done so whenever an `approve` event
    /// from a ERC20 token (that the coordinator deems valid)
    /// approving this contract to spend tokens on behalf of a user is seen.
    ///
    /// This operation is only usable in an Active state to prevent
    /// a terminated contract from receiving tokens.
    /// @param _user The address of the user that is depositing tokens
    /// @param _token The address of the ERC20 token to deposit
    /// @param _amount The (approved) amount to deposit
    function depositERC20(
        address _user,
        address _token,
        uint256 _amount
    )
        external
        onlyCoordinator
        onlyActiveState
    {
        require(
            _amount > 0,
            'Invalid value'
        );
        balances[_user][_token] = balances[_user][_token].add(_amount);

        ERC20(_token).transferFrom(_user, address(this), _amount);
        require(
            _getSanitizedReturnValue(),
            "transferFrom failed."
        );

        emit BalanceIncrease(_user, _token, _amount, ReasonDeposit);
    }

    /// @notice Withdraws `_amount` worth of `_token`s to the `_withdrawer`
    /// @dev This is the standard withdraw operation. Tokens can only be
    /// withdrawn directly to the token balance owner's address.
    /// Fees can be paid to cover network costs, as the operation must
    /// be invoked by the coordinator. The hash of all parameters, prefixed
    /// with the operation name "withdraw" must be signed by the withdrawer
    /// to validate the withdrawal request. A nonce that is issued by the
    /// coordinator is used to prevent replay attacks.
    /// See `slowWithdraw` for withdrawing without requiring the coordinator's
    /// involvement.
    /// @param _withdrawer The address of the user that is withdrawing tokens
    /// @param _token The address of the token to withdraw
    /// @param _amount The number of tokens to withdraw
    /// @param _feeAsset The address of the token to use for fee payment
    /// @param _feeAmount The amount of tokens to pay as fees to the operator
    /// @param _nonce The nonce to prevent replay attacks
    /// @param _v The `v` component of the `_withdrawer`'s signature
    /// @param _r The `r` component of the `_withdrawer`'s signature
    /// @param _s The `s` component of the `_withdrawer`'s signature
    function withdraw(
        address _withdrawer,
        address _token,
        uint256 _amount,
        address _feeAsset,
        uint256 _feeAmount,
        uint64 _nonce,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    )
        external
        onlyCoordinator
    {
        bytes32 msgHash = keccak256(abi.encodePacked(
            "withdraw",
            _withdrawer,
            _token,
            _amount,
            _feeAsset,
            _feeAmount,
            _nonce
        ));

        require(
            _recoverAddress(msgHash, _v, _r, _s) == _withdrawer,
            "Invalid signature"
        );

        _validateAndAddHash(msgHash);

        _withdraw(_withdrawer, _token, _amount, _feeAsset, _feeAmount);
    }

    /// @notice Announces intent to withdraw tokens using `slowWithdraw`
    /// @dev Allows a user to invoke `slowWithdraw` after a minimum of
    /// `withdrawAnnounceDelay` seconds has passed.
    /// This announcement and delay is necessary so that the operator has time
    /// to respond if a user attempts to invoke a `slowWithdraw` even though
    /// the exchange is operating normally. In that case, the coordinator would respond
    /// by not allowing the announced amount of tokens to be used in future trades
    /// the moment a `WithdrawAnnounce` is seen.
    /// @param _token The address of the token to withdraw after the required exit delay
    /// @param _amount The number of tokens to withdraw after the required exit delay
    function announceWithdraw(
        address _token,
        uint256 _amount
    )
        external
    {
        require(
            _amount <= balances[msg.sender][_token],
            "Amount too high"
        );

        AnnouncedWithdrawal storage announcement = announcedWithdrawals[msg.sender][_token];
        uint256 canWithdrawAt = now + withdrawAnnounceDelay;

        announcement.canWithdrawAt = canWithdrawAt;
        announcement.amount = _amount;

        emit WithdrawAnnounce(msg.sender, _token, _amount, canWithdrawAt);
    }

    /// @notice Withdraw tokens without requiring the coordinator
    /// @dev This operation is meant to be used if the operator becomes "byzantine",
    /// so that users can still exit tokens locked in this contract.
    /// The `announceWithdraw` operation has to be invoked first, and a minimum time of
    /// `withdrawAnnounceDelay` seconds have to pass, before this operation can be carried out.
    /// Note that this direct on-chain withdrawal is an atypical operation, and
    /// the normal `withdraw` operation should be used in non-byzantine states.
    /// @param _withdrawer The address of the user that is withdrawing tokens
    /// @param _token The address of the token to withdraw
    /// @param _amount The number of tokens to withdraw
    function slowWithdraw(
        address _withdrawer,
        address _token,
        uint256 _amount
    )
        external
    {
        AnnouncedWithdrawal memory announcement = announcedWithdrawals[_withdrawer][_token];

        require(
            announcement.canWithdrawAt != 0 && announcement.canWithdrawAt <= now,
            "Insufficient delay"
        );

        require(
            announcement.amount == _amount,
            "Invalid amount"
        );

        delete announcedWithdrawals[_withdrawer][_token];

        _withdraw(_withdrawer, _token, _amount, etherAddr, 0);
    }

    /// @notice Withdraws tokens to the owner without requiring the owner's signature
    /// @dev Can only be invoked in an Inactive state by the coordinator.
    /// This operation is meant to be used in emergencies only.
    /// @param _withdrawer The address of the user that should have tokens withdrawn
    /// @param _token The address of the token to withdraw
    /// @param _amount The number of tokens to withdraw
    function emergencyWithdraw(
        address _withdrawer,
        address _token,
        uint256 _amount
    )
        external
        onlyCoordinator
        onlyInactiveState
    {
        _withdraw(_withdrawer, _token, _amount, etherAddr, 0);
    }

    /// @notice Makes an offer which can be filled by other users.
    /// @dev Makes an offer for `_offerAmount` of `offerAsset` tokens
    /// for `wantAmount` of `wantAsset` tokens, that can be filled later
    /// by one or more counterparties using `fillOffer` or `fillOffers`.
    /// The offer can be later cancelled using `cancel` or `slowCancel` as long
    /// as it has not completely been filled.
    /// A fee of `_feeAmount` of `_feeAsset` tokens can be paid to the operator
    /// to cover orderbook maintenance and network costs.
    /// The hash of all parameters, prefixed with the operation name "makeOffer"
    /// must be signed by the `_maker` to validate the offer request.
    /// A nonce that is issued by the coordinator is used to prevent replay attacks.
    /// This operation can only be invoked by the coordinator in an Active state.
    /// @param _maker The address of the user that is making the offer
    /// @param _offerAsset The address of the token being offered
    /// @param _wantAsset The address of the token asked in return
    /// @param _offerAmount The number of tokens being offered
    /// @param _wantAmount The number of tokens asked for in return
    /// @param _feeAsset The address of the token to use for fee payment
    /// @param _feeAmount The amount of tokens to pay as fees to the operator
    /// @param _nonce The nonce to prevent replay attacks
    /// @param _v The `v` component of the `_maker`'s signature
    /// @param _r The `r` component of the `_maker`'s signature
    /// @param _s The `s` component of the `_maker`'s signature
    function makeOffer(
        address _maker,
        address _offerAsset,
        address _wantAsset,
        uint256 _offerAmount,
        uint256 _wantAmount,
        address _feeAsset,
        uint256 _feeAmount,
        uint64 _nonce,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    )
        external
        onlyCoordinator
        onlyActiveState
    {
        require(
            _offerAmount > 0 && _wantAmount > 0,
            "Invalid amounts"
        );

        require(
            _offerAsset != _wantAsset,
            "Invalid assets"
        );

        bytes32 offerHash = keccak256(abi.encodePacked(
            "makeOffer",
            _maker,
            _offerAsset,
            _wantAsset,
            _offerAmount,
            _wantAmount,
            _feeAsset,
            _feeAmount,
            _nonce
        ));

        require(
            _recoverAddress(offerHash, _v, _r, _s) == _maker,
            "Invalid signature"
        );

        _validateAndAddHash(offerHash);

        // Reduce maker's balance
        _decreaseBalanceAndPayFees(
            _maker,
            _offerAsset,
            _offerAmount,
            _feeAsset,
            _feeAmount,
            ReasonMakerGive,
            ReasonMakerFeeGive,
            ReasonMakerFeeReceive
        );

        // Store the offer
        Offer storage offer = offers[offerHash];
        offer.maker = _maker;
        offer.offerAsset = _offerAsset;
        offer.wantAsset = _wantAsset;
        offer.offerAmount = _offerAmount;
        offer.wantAmount = _wantAmount;
        offer.availableAmount = _offerAmount;
        offer.nonce = _nonce;

        emit Make(_maker, offerHash);
    }

    /// @notice Fills a offer that has been previously made using `makeOffer`.
    /// @dev Fill an offer with `_offerHash` by giving `_amountToTake` of
    /// the offers' `wantAsset` tokens.
    /// A fee of `_feeAmount` of `_feeAsset` tokens can be paid to the operator
    /// to cover orderbook maintenance and network costs.
    /// The hash of all parameters, prefixed with the operation name "fillOffer"
    /// must be signed by the `_filler` to validate the fill request.
    /// A nonce that is issued by the coordinator is used to prevent replay attacks.
    /// This operation can only be invoked by the coordinator in an Active state.
    /// @param _filler The address of the user that is filling the offer
    /// @param _offerHash The hash of the offer to fill
    /// @param _amountToTake The number of tokens to take from the offer
    /// @param _feeAsset The address of the token to use for fee payment
    /// @param _feeAmount The amount of tokens to pay as fees to the operator
    /// @param _nonce The nonce to prevent replay attacks
    /// @param _v The `v` component of the `_filler`'s signature
    /// @param _r The `r` component of the `_filler`'s signature
    /// @param _s The `s` component of the `_filler`'s signature
    function fillOffer(
        address _filler,
        bytes32 _offerHash,
        uint256 _amountToTake,
        address _feeAsset,
        uint256 _feeAmount,
        uint64 _nonce,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    )
        external
        onlyCoordinator
        onlyActiveState
    {
        bytes32 msgHash = keccak256(
            abi.encodePacked(
                "fillOffer",
                _filler,
                _offerHash,
                _amountToTake,
                _feeAsset,
                _feeAmount,
                _nonce
            )
        );

        require(
            _recoverAddress(msgHash, _v, _r, _s) == _filler,
            "Invalid signature"
        );

        _validateAndAddHash(msgHash);

        _fill(_filler, _offerHash, _amountToTake, _feeAsset, _feeAmount);
    }

    /// @notice Fills multiple offers that have been previously made using `makeOffer`.
    /// @dev Fills multiple offers with hashes in `_offerHashes` for amounts in
    /// `_amountsToTake`. This method allows conserving of the base gas cost.
    /// A fee of `_feeAmount` of `_feeAsset`  tokens can be paid to the operator
    /// to cover orderbook maintenance and network costs.
    /// The hash of all parameters, prefixed with the operation name "fillOffers"
    /// must be signed by the maker to validate the fill request.
    /// A nonce that is issued by the coordinator is used to prevent replay attacks.
    /// This operation can only be invoked by the coordinator in an Active state.
    /// @param _filler The address of the user that is filling the offer
    /// @param _offerHashes The hashes of the offers to fill
    /// @param _amountsToTake The number of tokens to take for each offer
    /// (each index corresponds to the entry with the same index in _offerHashes)
    /// @param _feeAsset The address of the token to use for fee payment
    /// @param _feeAmount The amount of tokens to pay as fees to the operator
    /// @param _nonce The nonce to prevent replay attacks
    /// @param _v The `v` component of the `_filler`'s signature
    /// @param _r The `r` component of the `_filler`'s signature
    /// @param _s The `s` component of the `_filler`'s signature
    function fillOffers(
        address _filler,
        bytes32[] _offerHashes,
        uint256[] _amountsToTake,
        address _feeAsset,
        uint256 _feeAmount,
        uint64 _nonce,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    )
        external
        onlyCoordinator
        onlyActiveState
    {
        require(
            _offerHashes.length > 0,
            'Invalid input'
        );
        require(
            _offerHashes.length == _amountsToTake.length,
            'Invalid inputs'
        );

        bytes32 msgHash = keccak256(
            abi.encodePacked(
                "fillOffers",
                _filler,
                _offerHashes,
                _amountsToTake,
                _feeAsset,
                _feeAmount,
                _nonce
            )
        );

        require(
            _recoverAddress(msgHash, _v, _r, _s) == _filler,
            "Invalid signature"
        );

        _validateAndAddHash(msgHash);

        for (uint32 i = 0; i < _offerHashes.length; i++) {
            _fill(_filler, _offerHashes[i], _amountsToTake[i], etherAddr, 0);
        }

        _paySeparateFees(
            _filler,
            _feeAsset,
            _feeAmount,
            ReasonFillerFeeGive,
            ReasonFillerFeeReceive
        );
    }

    /// @notice Cancels an offer that was preivously made using `makeOffer`.
    /// @dev Cancels the offer with `_offerHash`. An `_expectedAvailableAmount`
    /// is provided to allow the coordinator to ensure that the offer is not accidentally
    /// cancelled ahead of time (where there is a pending fill that has not been settled).
    /// The hash of the _offerHash, _feeAsset, `_feeAmount` prefixed with the
    /// operation name "cancel" must be signed by the offer maker to validate
    /// the cancellation request. Only the coordinator can invoke this operation.
    /// See `slowCancel` for cancellation without requiring the coordinator's
    /// involvement.
    /// @param _offerHash The hash of the offer to cancel
    /// @param _expectedAvailableAmount The number of tokens that should be present when cancelling
    /// @param _feeAsset The address of the token to use for fee payment
    /// @param _feeAmount The amount of tokens to pay as fees to the operator
    /// @param _v The `v` component of the offer maker's signature
    /// @param _r The `r` component of the offer maker's signature
    /// @param _s The `s` component of the offer maker's signature
    function cancel(
        bytes32 _offerHash,
        uint256 _expectedAvailableAmount,
        address _feeAsset,
        uint256 _feeAmount,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    )
        external
        onlyCoordinator
    {
        require(
            _recoverAddress(keccak256(abi.encodePacked(
                "cancel",
                _offerHash,
                _feeAsset,
                _feeAmount
            )), _v, _r, _s) == offers[_offerHash].maker,
            "Invalid signature"
        );

        _cancel(_offerHash, _expectedAvailableAmount, _feeAsset, _feeAmount);
    }

    /// @notice Announces intent to cancel tokens using `slowCancel`
    /// @dev Allows a user to invoke `slowCancel` after a minimum of
    /// `cancelAnnounceDelay` seconds has passed.
    /// This announcement and delay is necessary so that the operator has time
    /// to respond if a user attempts to invoke a `slowCancel` even though
    /// the exchange is operating normally.
    /// In that case, the coordinator would simply stop matching the offer to
    /// viable counterparties the moment the `CancelAnnounce` is seen.
    /// @param _offerHash The hash of the offer that will be cancelled
    function announceCancel(bytes32 _offerHash)
        external
    {
        Offer memory offer = offers[_offerHash];

        require(
            offer.maker == msg.sender,
            "Invalid sender"
        );

        require(
            offer.availableAmount > 0,
            "Offer already cancelled"
        );

        uint256 canCancelAt = now + cancelAnnounceDelay;
        announcedCancellations[_offerHash] = canCancelAt;

        emit CancelAnnounce(offer.maker, _offerHash, canCancelAt);
    }

    /// @notice Cancel an offer without requiring the coordinator
    /// @dev This operation is meant to be used if the operator becomes "byzantine",
    /// so that users can still cancel offers in this contract, and withdraw tokens
    /// using `slowWithdraw`.
    /// The `announceCancel` operation has to be invoked first, and a minimum time of
    /// `cancelAnnounceDelay` seconds have to pass, before this operation can be carried out.
    /// Note that this direct on-chain cancellation is an atypical operation, and
    /// the normal `cancel` operation should be used in non-byzantine states.
    /// @param _offerHash The hash of the offer to cancel
    function slowCancel(bytes32 _offerHash)
        external
    {
        require(
            announcedCancellations[_offerHash] != 0 && announcedCancellations[_offerHash] <= now,
            "Insufficient delay"
        );

        delete announcedCancellations[_offerHash];

        Offer memory offer = offers[_offerHash];
        _cancel(_offerHash, offer.availableAmount, etherAddr, 0);
    }

    /// @notice Cancels an offer immediately once cancellation intent
    /// has been announced.
    /// @dev Can only be invoked by the coordinator. This allows
    /// the coordinator to quickly remove offers that it has already
    /// acknowledged, and move its offer book into a consistent state.
    function fastCancel(bytes32 _offerHash, uint256 _expectedAvailableAmount)
        external
        onlyCoordinator
    {
        require(
            announcedCancellations[_offerHash] != 0,
            "Missing annoncement"
        );

        delete announcedCancellations[_offerHash];

        _cancel(_offerHash, _expectedAvailableAmount, etherAddr, 0);
    }

    /// @notice Cancels an offer requiring the owner's signature,
    /// so that the tokens can be withdrawn using `emergencyWithdraw`.
    /// @dev Can only be invoked in an Inactive state by the coordinator.
    /// This operation is meant to be used in emergencies only.
    function emergencyCancel(bytes32 _offerHash, uint256 _expectedAvailableAmount)
        external
        onlyCoordinator
        onlyInactiveState
    {
        _cancel(_offerHash, _expectedAvailableAmount, etherAddr, 0);
    }

    /// @notice Approve an address for spending any amount of
    /// any token from the `msg.sender`'s balances
    /// @dev Analogous to ERC-20 `approve`, with the following differences:
    ///     - `_spender` must be whitelisted by owner
    ///     - approval can be rescinded at a later time by the user
    ///       iff it has been removed from the whitelist
    ///     - spending amount is unlimited
    /// @param _spender The address to approve spending
    function approveSpender(address _spender)
        external
    {
        require(
            whitelistedSpenders[_spender],
            "Spender is not whitelisted"
        );

        approvedSpenders[msg.sender][_spender] = true;
        emit SpenderApprove(msg.sender, _spender);
    }

    /// @notice Rescinds a previous approval for spending the `msg.sender`'s contract balance.
    /// @dev Rescinds approval for a spender, after it has been removed from
    /// the `whitelistedSpenders` set. This allows an approval to be removed
    /// if both the owner and user agrees that the previously approved spender
    /// contract should no longer be used.
    /// @param _spender The address to rescind spending approval
    function rescindApproval(address _spender)
        external
    {
        require(
            approvedSpenders[msg.sender][_spender],
            "Spender has not been approved"
        );

        require(
            whitelistedSpenders[_spender] != true,
            "Spender must be removed from the whitelist"
        );

        delete approvedSpenders[msg.sender][_spender];
        emit SpenderRescind(msg.sender, _spender);
    }

    /// @notice Transfers tokens from one address to another
    /// @dev Analogous to ERC-20 `transferFrom`, with the following differences:
    ///     - the address of the token to transfer must be specified
    ///     - any amount of token can be transferred, as long as it is less or equal
    ///       to `_from`'s balance
    ///     - reason codes can be attached and they must not use reasons specified in
    ///       this contract
    /// @param _from The address to transfer tokens from
    /// @param _to The address to transfer tokens to
    /// @param _amount The number of tokens to transfer
    /// @param _token The address of the token to transfer
    /// @param _decreaseReason A reason code to emit in the `BalanceDecrease` event
    /// @param _increaseReason A reason code to emit in the `BalanceIncrease` event
    function spendFrom(
        address _from,
        address _to,
        uint256 _amount,
        address _token,
        uint8 _decreaseReason,
        uint8 _increaseReason
    )
        external
        unusedReasonCode(_decreaseReason)
        unusedReasonCode(_increaseReason)
    {
        require(
            approvedSpenders[_from][msg.sender],
            "Spender has not been approved"
        );

        _validateAddress(_to);

        balances[_from][_token] = balances[_from][_token].sub(_amount);
        emit BalanceDecrease(_from, _token, _amount, _decreaseReason);

        balances[_to][_token] = balances[_to][_token].add(_amount);
        emit BalanceIncrease(_to, _token, _amount, _increaseReason);
    }

    /// @dev Overrides ability to renounce ownership as this contract is
    /// meant to always have an owner.
    function renounceOwnership() public { require(false, "Cannot have no owner"); }

    /// @dev The actual withdraw logic that is used internally by multiple operations.
    function _withdraw(
        address _withdrawer,
        address _token,
        uint256 _amount,
        address _feeAsset,
        uint256 _feeAmount
    )
        private
    {
        // SafeMath.sub checks that balance is sufficient already
        _decreaseBalanceAndPayFees(
            _withdrawer,
            _token,
            _amount,
            _feeAsset,
            _feeAmount,
            ReasonWithdraw,
            ReasonWithdrawFeeGive,
            ReasonWithdrawFeeReceive
        );

        if (_token == etherAddr) // ether
        {
            _withdrawer.transfer(_amount);
        }
        else
        {
            ERC20(_token).transfer(_withdrawer, _amount);
            require(
                _getSanitizedReturnValue(),
                "transfer failed"
            );
        }
    }

    /// @dev The actual fill logic that is used internally by multiple operations.
    function _fill(
        address _filler,
        bytes32 _offerHash,
        uint256 _amountToTake,
        address _feeAsset,
        uint256 _feeAmount
    )
        private
    {
        require(
            _amountToTake > 0,
            "Invalid input"
        );

        Offer storage offer = offers[_offerHash];
        require(
            offer.maker != _filler,
            "Invalid filler"
        );

        require(
            offer.availableAmount != 0,
            "Offer already filled"
        );

        uint256 amountToFill = (_amountToTake.mul(offer.wantAmount)).div(offer.offerAmount);

        // transfer amountToFill in fillAsset from filler to maker
        balances[_filler][offer.wantAsset] = balances[_filler][offer.wantAsset].sub(amountToFill);
        emit BalanceDecrease(_filler, offer.wantAsset, amountToFill, ReasonFillerGive);

        balances[offer.maker][offer.wantAsset] = balances[offer.maker][offer.wantAsset].add(amountToFill);
        emit BalanceIncrease(offer.maker, offer.wantAsset, amountToFill, ReasonMakerReceive);

        // deduct amountToTake in takeAsset from offer
        offer.availableAmount = offer.availableAmount.sub(_amountToTake);
        _increaseBalanceAndPayFees(
            _filler,
            offer.offerAsset,
            _amountToTake,
            _feeAsset,
            _feeAmount,
            ReasonFillerReceive,
            ReasonFillerFeeGive,
            ReasonFillerFeeReceive
        );
        emit Fill(_filler, _offerHash, amountToFill, _amountToTake, offer.maker);

        if (offer.availableAmount == 0)
        {
            delete offers[_offerHash];
        }
    }

    /// @dev The actual cancellation logic that is used internally by multiple operations.
    function _cancel(
        bytes32 _offerHash,
        uint256 _expectedAvailableAmount,
        address _feeAsset,
        uint256 _feeAmount
    )
        private
    {
        Offer memory offer = offers[_offerHash];

        require(
            offer.availableAmount > 0,
            "Offer already cancelled"
        );

        require(
            offer.availableAmount == _expectedAvailableAmount,
            "Invalid input"
        );

        delete offers[_offerHash];

        _increaseBalanceAndPayFees(
            offer.maker,
            offer.offerAsset,
            offer.availableAmount,
            _feeAsset,
            _feeAmount,
            ReasonCancel,
            ReasonCancelFeeGive,
            ReasonCancelFeeReceive
        );

        emit Cancel(offer.maker, _offerHash);
    }

    /// @dev Performs an `ecrecover` operation for signed message hashes
    /// in accordance to EIP-191.
    function _recoverAddress(bytes32 _hash, uint8 _v, bytes32 _r, bytes32 _s)
        private
        pure
        returns (address)
    {
        bytes memory prefix = "\x19Ethereum Signed Message:\n32";
        bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, _hash));
        return ecrecover(prefixedHash, _v, _r, _s);
    }

    /// @dev Decreases a user's balance while adding a cut from the decrement
    /// to be paid as fees to the operator. Reason codes should be provided
    /// to be emitted with events for tracking.
    function _decreaseBalanceAndPayFees(
        address _user,
        address _token,
        uint256 _amount,
        address _feeAsset,
        uint256 _feeAmount,
        uint8 _reason,
        uint8 _feeGiveReason,
        uint8 _feeReceiveReason
    )
        private
    {
        uint256 totalAmount = _amount;

        if (_feeAsset == _token) {
            totalAmount = _amount.add(_feeAmount);
        }

        balances[_user][_token] = balances[_user][_token].sub(totalAmount);
        emit BalanceDecrease(_user, _token, totalAmount, _reason);

        _payFees(_user, _token, _feeAsset, _feeAmount, _feeGiveReason, _feeReceiveReason);
    }

    /// @dev Increases a user's balance while deducting a cut from the increment
    /// to be paid as fees to the operator. Reason codes should be provided
    /// to be emitted with events for tracking.
    function _increaseBalanceAndPayFees(
        address _user,
        address _token,
        uint256 _amount,
        address _feeAsset,
        uint256 _feeAmount,
        uint8 _reason,
        uint8 _feeGiveReason,
        uint8 _feeReceiveReason
    )
        private
    {
        uint256 totalAmount = _amount;

        if (_feeAsset == _token) {
            totalAmount = _amount.sub(_feeAmount);
        }

        balances[_user][_token] = balances[_user][_token].add(totalAmount);
        emit BalanceIncrease(_user, _token, totalAmount, _reason);

        _payFees(_user, _token, _feeAsset, _feeAmount, _feeGiveReason, _feeReceiveReason);
    }

    /// @dev Pays fees to the operator, attaching the specified reason codes
    /// to the emitted event, only deducting from the `_user` balance if the
    /// `_token` does not match `_feeAsset`.
    /// IMPORTANT: In the event that the `_token` matches `_feeAsset`,
    /// there should a reduction in balance increment carried out separately,
    /// to ensure balance consistency.
    function _payFees(
        address _user,
        address _token,
        address _feeAsset,
        uint256 _feeAmount,
        uint8 _feeGiveReason,
        uint8 _feeReceiveReason
    )
        private
    {
        if (_feeAmount == 0) {
            return;
        }

        // if the feeAsset does not match the token then the feeAmount needs to be separately deducted
        if (_feeAsset != _token) {
            balances[_user][_feeAsset] = balances[_user][_feeAsset].sub(_feeAmount);
            emit BalanceDecrease(_user, _feeAsset, _feeAmount, _feeGiveReason);
        }

        balances[operator][_feeAsset] = balances[operator][_feeAsset].add(_feeAmount);
        emit BalanceIncrease(operator, _feeAsset, _feeAmount, _feeReceiveReason);
    }

    /// @dev Pays fees to the operator, attaching the specified reason codes to the emitted event.
    function _paySeparateFees(
        address _user,
        address _feeAsset,
        uint256 _feeAmount,
        uint8 _feeGiveReason,
        uint8 _feeReceiveReason
    )
        private
    {
        if (_feeAmount == 0) {
            return;
        }

        balances[_user][_feeAsset] = balances[_user][_feeAsset].sub(_feeAmount);
        emit BalanceDecrease(_user, _feeAsset, _feeAmount, _feeGiveReason);

        balances[operator][_feeAsset] = balances[operator][_feeAsset].add(_feeAmount);
        emit BalanceIncrease(operator, _feeAsset, _feeAmount, _feeReceiveReason);
    }

    /// @dev Ensures that the address is a valid user address.
    function _validateAddress(address _address)
        private
        pure
    {
        require(
            _address != address(0),
            'Invalid address'
        );
    }

    /// @dev Ensures a hash hasn't been already used, which would mean
    /// a repeated set of arguments and nonce was used. This prevents
    /// replay attacks.
    function _validateAndAddHash(bytes32 _hash)
        private
    {
        require(
            usedHashes[_hash] != true,
            "hash already used"
        );

        usedHashes[_hash] = true;
    }

    /// @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
    function _getSanitizedReturnValue()
        private
        pure
        returns (bool)
    {
        uint256 result = 0;
        assembly {
            switch returndatasize
            case 0 {    // this is an non-standard ERC-20 token
                result := 1 // assume success on no revert
            }
            case 32 {   // this is a standard ERC-20 token
                returndatacopy(0, 0, 32)
                result := mload(0)
            }
            default {   // this is not an ERC-20 token
                revert(0, 0) // revert for safety
            }
        }
        return result != 0;
    }
}
/// @title The Broker + Vault contract for Switcheo Exchange
/// @author Switcheo Network
/// @notice This contract faciliates Ethereum and ERC-20 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
/// (make / take offers). This allows trades to be confirmed
/// immediately by the coordinator, and settled on-chain through
/// this contract at a later time.
function fillOffer(
    address _filler,
    bytes32 _offerHash,
    uint256 _amountToTake,
    address _feeAsset,
    uint256 _feeAmount,
    uint64 _nonce,
    uint8 _v,
    bytes32 _r,
    bytes32 _s
)
    external
    onlyCoordinator
    onlyActiveState
{
    bytes32 msgHash = keccak256(
        abi.encodePacked(
            "fillOffer",
            _filler,
            _offerHash,
            _amountToTake,
            _feeAsset,
            _feeAmount,
            _nonce
        )
    );

    require(
        _recoverAddress(msgHash, _v, _r, _s) == _filler,
        "Invalid signature"
    );

    _validateAndAddHash(msgHash);

    _fill(_filler, _offerHash, _amountToTake, _feeAsset, _feeAmount);
}

/// @notice Fills a offer that has been previously made using `makeOffer`.
/// @dev Fill an offer with `_offerHash` by giving `_amountToTake` of
/// the offers' `wantAsset` tokens.
/// A fee of `_feeAmount` of `_feeAsset` tokens can be paid to the operator
/// to cover orderbook maintenance and network costs.
/// The hash of all parameters, prefixed with the operation name "fillOffer"
/// must be signed by the `_filler` to validate the fill request.
/// A nonce that is issued by the coordinator is used to prevent replay attacks.
/// This operation can only be invoked by the coordinator in an Active state.
/// @param _filler The address of the user that is filling the offer
/// @param _offerHash The hash of the offer to fill
/// @param _amountToTake The number of tokens to take from the offer
/// @param _feeAsset The address of the token to use for fee payment
/// @param _feeAmount The amount of tokens to pay as fees to the operator
/// @param _nonce The nonce to prevent replay attacks
/// @param _v The `v` component of the `_filler`'s signature
/// @param _r The `r` component of the `_filler`'s signature
/// @param _s The `s` component of the `_filler`'s signature