contract dexBlue is dexBlueStorage, dexBlueEvents, dexBlueUtils, dexBlueStructs{
    // Hardcode settlement module contract:
    address constant settlementModuleAddress = 0x9e3d5C6ffACA00cAf136609680b536DC0Eb20c66;

    // Deposit functions:

    /** @notice Deposit Ether into the smart contract 
      */
    function depositEther() public payable{
        balances[address(0)][msg.sender] += msg.value;          // Add the received ETH to the users balance
        emit LogDeposit(msg.sender, address(0), msg.value);     // emit LogDeposit event
    }
    
    /** @notice Fallback function to credit ETH sent to the contract without data 
      */
    function() external payable{
        if(msg.sender != wrappedEtherContract){     // ETH sends from WETH contract are handled in the depositWrappedEther() function
            depositEther();                 // Call the deposit function to credit ETH sent in this transaction
        }
    }
    
    /** @notice Deposit Wrapped Ether (remember to set allowance in the token contract first)
      * @param  amount  The amount of WETH to deposit 
      */
    address constant wrappedEtherContract = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // We hardcode the address, to prevent misbehaviour through custom contracts (reentrancy etc)
    function depositWrappedEther(uint256 amount) external {
        
        Token(wrappedEtherContract).transferFrom(msg.sender, address(this), amount);    // Transfer WETH to this contract
        
        require(
            checkERC20TransferSuccess(),                                        // Check whether the ERC20 token transfer was successful
            "WETH deposit failed."
        );
        
        uint balanceBefore = address(this).balance;                             // Remember ETH balance before the call
        
        WETH(wrappedEtherContract).withdraw(amount);                            // Unwrap the WETH
        
        require(balanceBefore + amount == address(this).balance);               // Check whether the ETH was deposited
        
        balances[address(0)][msg.sender] += amount;                             // Credit the deposited eth to users balance
        
        emit LogDeposit(msg.sender, address(0), amount);                        // emit LogDeposit event
    }
    
    /** @notice Deposit ERC20 tokens into the smart contract (remember to set allowance in the token contract first)
      * @param  token   The address of the token to deposit
      * @param  amount  The amount of tokens to deposit 
      */
    function depositToken(address token, uint256 amount) external {
        Token(token).transferFrom(msg.sender, address(this), amount);    // Deposit ERC20
        require(
            checkERC20TransferSuccess(),                                 // Check whether the ERC20 token transfer was successful
            "ERC20 token transfer failed."
        );
        balances[token][msg.sender] += amount;                           // Credit the deposited token to users balance
        emit LogDeposit(msg.sender, token, amount);                      // emit LogDeposit event
    }
        
    // Multi-sig withdrawal functions:

    /** @notice User-submitted withdrawal with arbiters signature, which withdraws to the users address
      * @param  token   The token to withdraw (ETH is address(address(0)))
      * @param  amount  The amount of tokens to withdraw
      * @param  nonce   The nonce (to salt the hash)
      * @param  v       Multi-signature v
      * @param  r       Multi-signature r
      * @param  s       Multi-signature s 
      */
    function multiSigWithdrawal(address token, uint256 amount, uint64 nonce, uint8 v, bytes32 r, bytes32 s) external {
        multiSigSend(token, amount, nonce, v, r, s, msg.sender); // Call multiSigSend to send funds to msg.sender
    }    

    /** @notice User-submitted withdrawal with arbiters signature, which sends tokens to specified address
      * @param  token              The token to withdraw (ETH is address(address(0)))
      * @param  amount             The amount of tokens to withdraw
      * @param  nonce              The nonce (to salt the hash)
      * @param  v                  Multi-signature v
      * @param  r                  Multi-signature r
      * @param  s                  Multi-signature s
      * @param  receiving_address  The address to send the withdrawn token/ETH to
      */
    function multiSigSend(address token, uint256 amount, uint64 nonce, uint8 v, bytes32 r, bytes32 s, address payable receiving_address) public {
        bytes32 hash = keccak256(abi.encodePacked(                      // Calculate the withdrawal hash from the parameters 
            "\x19Ethereum Signed Message:\n32", 
            keccak256(abi.encodePacked(
                msg.sender,
                token,
                amount,
                nonce,
                address(this)
            ))
        ));
        if(
            !processed_withdrawals[hash]                                // Check if the withdrawal was initiated before
            && arbiters[ecrecover(hash, v,r,s)]                         // Check if the multi-sig is valid
            && balances[token][msg.sender] >= amount                    // Check if the user holds the required balance
        ){
            processed_withdrawals[hash]  = true;                        // Mark this withdrawal as processed
            balances[token][msg.sender] -= amount;                      // Substract the withdrawn balance from the users balance
            
            if(token == address(0)){                                    // Process an ETH withdrawal
                require(
                    receiving_address.send(amount),
                    "Sending of ETH failed."
                );
            }else{                                                      // Withdraw an ERC20 token
                Token(token).transfer(receiving_address, amount);       // Transfer the ERC20 token
                require(
                    checkERC20TransferSuccess(),                        // Check whether the ERC20 token transfer was successful
                    "ERC20 token transfer failed."
                );
            }

            blocked_for_single_sig_withdrawal[token][msg.sender] = 0;   // Set potential previous blocking of these funds to 0
            
            emit LogWithdrawal(msg.sender,token,amount);                // emit LogWithdrawal event
        }else{
            revert();                                                   // Revert the transaction if checks fail
        }
    }

    /** @notice User-submitted transfer with arbiters signature, which sends tokens to another addresses account in the smart contract
      * @param  token              The token to transfer (ETH is address(address(0)))
      * @param  amount             The amount of tokens to transfer
      * @param  nonce              The nonce (to salt the hash)
      * @param  v                  Multi-signature v
      * @param  r                  Multi-signature r
      * @param  s                  Multi-signature s
      * @param  receiving_address  The address to transfer the token/ETH to
      */
    function multiSigTransfer(address token, uint256 amount, uint64 nonce, uint8 v, bytes32 r, bytes32 s, address receiving_address) external {
        bytes32 hash = keccak256(abi.encodePacked(                      // Calculate the withdrawal/transfer hash from the parameters 
            "\x19Ethereum Signed Message:\n32", 
            keccak256(abi.encodePacked(
                msg.sender,
                token,
                amount,
                nonce,
                address(this)
            ))
        ));
        if(
            !processed_withdrawals[hash]                                // Check if the withdrawal was initiated before
            && arbiters[ecrecover(hash, v,r,s)]                         // Check if the multi-sig is valid
            && balances[token][msg.sender] >= amount                    // Check if the user holds the required balance
        ){
            processed_withdrawals[hash]         = true;                 // Mark this withdrawal as processed
            balances[token][msg.sender]        -= amount;               // Substract the balance from the withdrawing account
            balances[token][receiving_address] += amount;               // Add the balance to the receiving account
            
            blocked_for_single_sig_withdrawal[token][msg.sender] = 0;   // Set potential previous blocking of these funds to 0
            
            emit LogWithdrawal(msg.sender,token,amount);                // emit LogWithdrawal event
            emit LogDeposit(receiving_address,token,amount);            // emit LogDeposit event
        }else{
            revert();                                                   // Revert the transaction if checks fail
        }
    }
    
    /** @notice Arbiter submitted withdrawal with users multi-sig to users address
      * @param  packedInput1 tightly packed input arguments:
      *             amount  The amount of tokens to withdraw
      *             fee     The fee, covering the gas cost of the arbiter
      * @param  packedInput2 tightly packed input arguments:
      *             token           The token to withdraw (ETH is address(address(0)))
      *             nonce           The nonce (to salt the hash)
      *             v               Multi-signature v (either 27 or 28. To identify the different signing schemes an offset of 10 is applied for EIP712)
      *             signing_scheme  The signing scheme of the users signature
      *             burn_gas_tokens The amount of gas tokens to burn
      * @param  r       Multi-signature r
      * @param  s       Multi-signature s
      */
    function userSigWithdrawal(bytes32 packedInput1, bytes32 packedInput2, bytes32 r, bytes32 s) external {
        /* 
            BITMASK packedInput1                                               | BYTE RANGE | DESCRIPTION
            -------------------------------------------------------------------|------------|----------------------------------
            0xffffffffffffffffffffffffffffffff00000000000000000000000000000000 |  0-15      | amount
            0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff | 16-31      | gas fee
            
            BITMASK packedInput2                                               | BYTE RANGE | DESCRIPTION
            -------------------------------------------------------------------|------------|----------------------------------
            0xffff000000000000000000000000000000000000000000000000000000000000 |  0- 1      | token identifier
            0x0000ffffffffffffffff00000000000000000000000000000000000000000000 |  2- 9      | nonce
            0x00000000000000000000ff000000000000000000000000000000000000000000 | 10-10      | v
            0x0000000000000000000000ff0000000000000000000000000000000000000000 | 11-11      | signing scheme 0x00 = personal.sign, 0x01 = EIP712
            0x000000000000000000000000ff00000000000000000000000000000000000000 | 12-12      | burn_gas_tokens
        */
        // parse the packed input parameters
        uint256 amount = uint256(packedInput1 >> 128);
        uint256 fee    = uint256(packedInput1 & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff);
        address token  = tokens[uint256(packedInput2 >> 240)];
        uint64  nonce  = uint64(uint256((packedInput2 & 0x0000ffffffffffffffff00000000000000000000000000000000000000000000) >> 176));
        uint8   v      = uint8(packedInput2[10]);

        bytes32 hash;
        if(packedInput2[11] == byte(0x00)){                             // Standard signing scheme (personal.sign())
            hash = keccak256(abi.encodePacked(                          // Restore multi-sig hash
                "\x19Ethereum Signed Message:\n32",
                keccak256(abi.encodePacked(
                    token,
                    amount,
                    nonce,
                    address(this)
                ))
            ));
        }else{                                                          // EIP712 signing scheme
            hash = keccak256(abi.encodePacked(                          // Restore multi-sig hash
                "\x19\x01",
                EIP712_DOMAIN_SEPARATOR,
                keccak256(abi.encode(
                    EIP712_WITHDRAWAL_TYPEHASH,
                    token,
                    amount,
                    nonce
                ))
            ));
        }

        address payable account = address(uint160(ecrecover(hash, v, r, s)));   // Restore signing address
        
        if(
            !processed_withdrawals[hash]                                // Check if the withdrawal was initiated before
            && arbiters[msg.sender]                                     // Check if transaction comes from arbiter
            && fee <= amount / 20                                       // Check if fee is not too big
            && balances[token][account] >= amount                       // Check if the user holds the required tokens
        ){
            processed_withdrawals[hash]    = true;                      // Mark the withdrawal as processed
            balances[token][account]      -= amount;                    // Deduct the withdrawn tokens from the users balance
            balances[token][feeCollector] += fee;                       // Fee to cover gas costs for the withdrawal
            
            if(token == address(0)){                                    // Send ETH
                require(
                    account.send(amount - fee),
                    "Sending of ETH failed."
                );
            }else{
                Token(token).transfer(account, amount - fee);           // Withdraw ERC20
                require(
                    checkERC20TransferSuccess(),                        // Check if the transfer was successful
                    "ERC20 token transfer failed."
                );
            }
        
            blocked_for_single_sig_withdrawal[token][account] = 0;      // Set potential previous blocking of these funds to 0
            
            emit LogWithdrawal(account,token,amount);                   // emit LogWithdrawal event
            
            // burn gas tokens
            if(packedInput2[12] != byte(0x00)){
                spendGasTokens(uint8(packedInput2[12]));
            }
        }else{
            revert();                                                   // Revert the transaction is checks fail
        }
    }
    
    // Single-sig withdrawal functions:

    /** @notice Allows user to block funds for single-sig withdrawal after 24h waiting period 
      *         (This period is necessary to ensure all trades backed by these funds will be settled.)
      * @param  token   The address of the token to block (ETH is address(address(0)))
      * @param  amount  The amount of the token to block
      */
    function blockFundsForSingleSigWithdrawal(address token, uint256 amount) external {
        if (balances[token][msg.sender] - blocked_for_single_sig_withdrawal[token][msg.sender] >= amount){  // Check if the user holds the required funds
            blocked_for_single_sig_withdrawal[token][msg.sender] += amount;                                 // Block funds for manual withdrawal
            last_blocked_timestamp[msg.sender] = block.timestamp;                                           // Start waiting period
            emit LogBlockedForSingleSigWithdrawal(msg.sender, token, amount);                               // emit LogBlockedForSingleSigWithdrawal event
        }else{
            revert();                                                                                       // Revert the transaction if the user does not hold the required balance
        }
    }
    
    /** @notice Allows user to withdraw blocked funds without multi-sig after the waiting period
      * @param  token   The address of the token to withdraw (ETH is address(address(0)))
      * @param  amount  The amount of the token to withdraw
      */
    function initiateSingleSigWithdrawal(address token, uint256 amount) external {
        if (
            balances[token][msg.sender] >= amount                                   // Check if the user holds the funds
            && (
                (
                    blocked_for_single_sig_withdrawal[token][msg.sender] >= amount                          // Check if these funds are blocked
                    && last_blocked_timestamp[msg.sender] + single_sig_waiting_period <= block.timestamp    // Check if the waiting period has passed
                )
                || single_sig_waiting_period == 0                                                           // or the waiting period is disabled
            )
        ){
            balances[token][msg.sender] -= amount;                                  // Substract the tokens from users balance

            if(blocked_for_single_sig_withdrawal[token][msg.sender] >= amount){
                blocked_for_single_sig_withdrawal[token][msg.sender] = 0;     // Substract the tokens from users blocked balance
            }
            
            if(token == address(0)){                                                // Withdraw ETH
                require(
                    msg.sender.send(amount),
                    "Sending of ETH failed."
                );
            }else{                                                                  // Withdraw ERC20 tokens
                Token(token).transfer(msg.sender, amount);                          // Transfer the ERC20 tokens
                require(
                    checkERC20TransferSuccess(),                                    // Check if the transfer was successful
                    "ERC20 token transfer failed."
                );
            }
            
            emit LogSingleSigWithdrawal(msg.sender, token, amount);                 // emit LogSingleSigWithdrawal event
        }else{
            revert();                                                               // Revert the transaction if the required checks fail
        }
    } 

    //Trade settlement structs and function

    /** @notice Allows an arbiter to settle a trade between two orders
      * @param  makerOrderInput  The packed maker order input
      * @param  takerOrderInput  The packed taker order input
      * @param  tradeInput       The packed trade to settle between the two
      */ 
    function settleTrade(OrderInputPacked calldata makerOrderInput, OrderInputPacked calldata takerOrderInput, TradeInputPacked calldata tradeInput) external {
        require(arbiters[msg.sender] && marketActive);   // Check if msg.sender is an arbiter and the market is active

        settlementModuleAddress.delegatecall(msg.data);  // delegate the call to the settlement module
        
        // burn gas tokens
        if(tradeInput.packedInput3[28] != byte(0x00)){
            spendGasTokens(uint8(tradeInput.packedInput3[28]));
        }
    }

    /** @notice Allows an arbiter to settle a trade between an order and a reserve
      * @param  orderInput  The packed maker order input
      * @param  tradeInput  The packed trade to settle between the two
      */ 
    function settleReserveTrade(OrderInputPacked calldata orderInput, TradeInputPacked calldata tradeInput) external {
        require(arbiters[msg.sender] && marketActive);   // Check if msg.sender is an arbiter and the market is active

        settlementModuleAddress.delegatecall(msg.data);  // delegate the call to the settlement module
        
        // burn gas tokens
        if(tradeInput.packedInput3[28] != byte(0x00)){
            spendGasTokens(uint8(tradeInput.packedInput3[28]));
        }
    }

    /** @notice Allows an arbiter to settle a trade between an order and a reserve, passing some additional data to the reserve
      * @param  orderInput  The packed maker order input
      * @param  tradeInput  The packed trade to settle between the two
      * @param  data        The data to pass on to the reserve
      */ 
    function settleReserveTradeWithData(OrderInputPacked calldata orderInput, TradeInputPacked calldata tradeInput, bytes32[] calldata data) external {
        require(arbiters[msg.sender] && marketActive);      // Check if msg.sender is an arbiter and the market is active
        
        settlementModuleAddress.delegatecall(msg.data);  // delegate the call to the settlement module
        
        // burn gas tokens
        if(tradeInput.packedInput3[28] != byte(0x00)){
            spendGasTokens(uint8(tradeInput.packedInput3[28]));
        }
    }
    
    /** @notice Allows an arbiter to settle a trade between two reserves
      * @param  tradeInput  The packed trade to settle between the two
      */ 
    function settleReserveReserveTrade(TradeInputPacked calldata tradeInput) external {
        require(arbiters[msg.sender] && marketActive);          // Check if msg.sender is an arbiter and the market is active

        settlementModuleAddress.delegatecall(msg.data);  // delegate the call to the settlement module
        
        // burn gas tokens
        if(tradeInput.packedInput3[28] != byte(0x00)){
            spendGasTokens(uint8(tradeInput.packedInput3[28]));
        }
    }
    
    /** @notice Allows an arbiter to settle a trade between two reserves
      * @param  tradeInput  The packed trade to settle between the two
      * @param  makerData   The data to pass on to the maker reserve
      * @param  takerData   The data to pass on to the taker reserve
      */ 
    function settleReserveReserveTradeWithData(TradeInputPacked calldata tradeInput, bytes32[] calldata makerData, bytes32[] calldata takerData) external {
        require(arbiters[msg.sender] && marketActive);      // Check if msg.sender is an arbiter and the market is active
        
        settlementModuleAddress.delegatecall(msg.data);     // delegate the call to the settlement module
        
        // burn gas tokens
        if(tradeInput.packedInput3[28] != byte(0x00)){
            spendGasTokens(uint8(tradeInput.packedInput3[28]));
        }
    }
    

    /** @notice Allows an arbiter to settle multiple trades between multiple orders and reserves
      * @param  orderInput     Array of all orders involved in the transactions
      * @param  tradeInput     Array of the trades to be settled
      */   
    function batchSettleTrades(OrderInputPacked[] calldata orderInput, TradeInputPacked[] calldata tradeInput) external {
        require(arbiters[msg.sender] && marketActive);          // Check if msg.sender is an arbiter and the market is active
        
        settlementModuleAddress.delegatecall(msg.data);  // delegate the call to the settlement module
        
        // Loop through the trades and calc the gasToken sum
        uint256 i = tradeInput.length;        
        uint256 gasTokenSum;
        while(i-- != 0){
            gasTokenSum += uint8(tradeInput[i].packedInput3[28]);
        }
        
        // burn gas tokens
        if(gasTokenSum > 0){
            spendGasTokens(gasTokenSum);
        }
    }

    /** @notice Allow arbiters to settle a ring of order and reserve trades
      * @param  orderInput Array of OrderInputPacked structs
      * @param  tradeInput Array of RingTradeInputPacked structs
      */
    function settleRingTrade(OrderInputPacked[] calldata orderInput, RingTradeInputPacked[] calldata tradeInput) external {
        require(arbiters[msg.sender] && marketActive);      // Check if msg.sender is an arbiter and the market is active

        settlementModuleAddress.delegatecall(msg.data);
        
        // Loop through the trades and calc the gasToken sum
        uint256 i = tradeInput.length;        
        uint256 gasTokenSum;
        while(i-- != 0){
            gasTokenSum += uint8(tradeInput[i].packedInput2[24]);
        }
        
        // burn gas tokens
        if(gasTokenSum > 0){
            spendGasTokens(gasTokenSum);
        }
    }

    /** @notice Allow arbiters to settle a ring of order and reserve trades, passing on some data to the reserves
      * @param  orderInput Array of OrderInputPacked structs
      * @param  tradeInput Array of RingTradeInputPacked structs
      * @param  data       Array of data to pass along to the reserves
      */
    function settleRingTradeWithData(OrderInputPacked[] calldata orderInput, RingTradeInputPacked[] calldata tradeInput, bytes32[][] calldata data) external {
        require(arbiters[msg.sender] && marketActive);      // Check if msg.sender is an arbiter and the market is active

        settlementModuleAddress.delegatecall(msg.data);
        
        // Loop through the trades and calc the gasToken sum
        uint256 i = tradeInput.length;        
        uint256 gasTokenSum;
        while(i-- != 0){
            gasTokenSum += uint8(tradeInput[i].packedInput2[24]);
        }
        
        // burn gas tokens
        if(gasTokenSum > 0){
            spendGasTokens(gasTokenSum);
        }
    }


    /** @notice Helper function, callable only by the contract itself, to execute a trade between two reserves
      * @param  makerReserve  The maker reserve
      * @param  takerReserve  The taker reserve
      * @param  trade         The trade to settle between the two
      * @return Whether the trade succeeded or failed
      */
    function executeReserveReserveTrade(
        address             makerReserve,
        address payable     takerReserve,
        ReserveReserveTrade calldata trade
    ) external returns(bool){
        // this method is only callable from the contract itself
        // a call is used vs a jump, to be able to revert the sending of funds to the reserve without throwing the entire transaction
        require(msg.sender == address(this));                       // Check that the caller is the contract itself
        
        // Check whether the taker reserve accepts the trade
        require(
            dexBlueReserve(takerReserve).offer(                     
                trade.takerToken,                                   // The token we offer the reserve to sell
                trade.takerAmount,                                  // The amount the reserve could sell
                trade.makerToken,                                   // The token the reserve would receive
                trade.makerAmount - trade.takerFee                  // The amount the reserve would receive
            )
            && balances[trade.takerToken][takerReserve] >= trade.takerAmount    // Check whether the taker reserve deposited the collateral
        );
        
        balances[trade.takerToken][takerReserve] -= trade.takerAmount;          // Substract the deposited amount from the taker reserve
        
        if(trade.takerToken != address(0)){
            Token(trade.takerToken).transfer(makerReserve, trade.takerAmount - trade.makerFee);     // Send the taker reserves collateral to the maker reserve
            require(                                                                                // Revert if the send failed
                checkERC20TransferSuccess(),
                "ERC20 token transfer failed."
            );
        }
        
        // Check whether the maker reserve accepts the trade
        require(
            dexBlueReserve(makerReserve).trade.value(               // Execute the trade in the maker reserve
                trade.takerToken == address(0) ? 
                    trade.takerAmount - trade.makerFee              // Send the taker reserves collateral to the maker reserve
                    : 0
            )(
                trade.takerToken,                                   // The token the taker reserve is selling
                trade.takerAmount - trade.makerFee,                 // The amount of sellToken the taker reserve wants to sell
                trade.makerToken,                                   // The token the taker reserve wants in return
                trade.makerAmount                                   // The amount of token the taker reserve wants in return
            )
            && balances[trade.makerToken][makerReserve] >= trade.makerAmount  // Check whether the maker reserve deposited the collateral
        );

        balances[trade.makerToken][makerReserve] -= trade.makerAmount;                              // Substract the maker reserves's sold amount
        
        // Send the acquired amount to the taker reserve
        if(trade.makerToken == address(0)){                                                         // Is the acquired token ETH
            require(
                takerReserve.send(trade.makerAmount - trade.takerFee),                              // Send ETH
                "Sending of ETH failed."
            );
        }else{
            Token(trade.makerToken).transfer(takerReserve, trade.makerAmount - trade.takerFee);     // Transfer ERC20
            require(                                                                                // Revert if the transfer failed
                checkERC20TransferSuccess(),
                "ERC20 token transfer failed."
            );
        }

        // Notify the reserve, that the offer got executed
        dexBlueReserve(takerReserve).offerExecuted(                     
            trade.takerToken,                                   // The token the reserve sold
            trade.takerAmount,                                  // The amount the reserve sold
            trade.makerToken,                                   // The token the reserve received
            trade.makerAmount - trade.takerFee                  // The amount the reserve received
        );
        
        // Give fee to feeCollector
        balances[trade.makerToken][feeCollector] += trade.takerFee;  // Give feeColletor the taker fee
        balances[trade.takerToken][feeCollector] += trade.makerFee;  // Give feeColletor the maker fee
        
        emit LogTrade(trade.makerToken, trade.makerAmount, trade.takerToken, trade.takerAmount);
        
        emit LogDirectWithdrawal(makerReserve, trade.takerToken, trade.takerAmount - trade.makerFee);
        emit LogDirectWithdrawal(takerReserve, trade.makerToken, trade.makerAmount - trade.takerFee);
        
        return true;
    }

    /** @notice Helper function, callable only by the contract itself, to execute a trade between two reserves
      * @param  makerReserve  The maker reserve
      * @param  takerReserve  The taker reserve
      * @param  trade         The trade to settle between the two
      * @param  makerData     The data to pass on to the maker reserve
      * @param  takerData     The data to pass on to the taker reserve
      * @return Whether the trade succeeded or failed
      */
    function executeReserveReserveTradeWithData(
        address             makerReserve,
        address payable     takerReserve,
        ReserveReserveTrade calldata trade,
        bytes32[] calldata  makerData,
        bytes32[] calldata  takerData
    ) external returns(bool){
        // this method is only callable from the contract itself
        // a call is used vs a jump, to be able to revert the sending of funds to the reserve without throwing the entire transaction
        require(msg.sender == address(this));                       // Check that the caller is the contract itself
        
        // Check whether the taker reserve accepts the trade
        require(
            dexBlueReserve(takerReserve).offerWithData(                     
                trade.takerToken,                                   // The token we offer the reserve to sell
                trade.takerAmount,                                  // The amount the reserve could sell
                trade.makerToken,                                   // The token the reserve would receive
                trade.makerAmount - trade.takerFee,                 // The amount the reserve would receive
                takerData
            )
            && balances[trade.takerToken][takerReserve] >= trade.takerAmount    // Check whether the taker reserve deposited the collateral
        );
        
        balances[trade.takerToken][takerReserve] -= trade.takerAmount;          // Substract the deposited amount from the taker reserve
        
        if(trade.takerToken != address(0)){
            Token(trade.takerToken).transfer(makerReserve, trade.takerAmount - trade.makerFee);     // Send the taker reserves collateral to the maker reserve
            require(                                                                                // Revert if the send failed
                checkERC20TransferSuccess(),
                "ERC20 token transfer failed."
            );
        }
        
        // Check whether the maker reserve accepts the trade
        require(
            dexBlueReserve(makerReserve).tradeWithData.value(       // Execute the trade in the maker reserve
                trade.takerToken == address(0) ? 
                    trade.takerAmount - trade.makerFee              // Send the taker reserves collateral to the maker reserve
                    : 0
            )(
                trade.takerToken,                                   // The token the taker reserve is selling
                trade.takerAmount - trade.makerFee,                 // The amount of sellToken the taker reserve wants to sell
                trade.makerToken,                                   // The token the taker reserve wants in return
                trade.makerAmount,                                  // The amount of token the taker reserve wants in return
                makerData
            )
            && balances[trade.makerToken][makerReserve] >= trade.makerAmount  // Check whether the maker reserve deposited the collateral
        );

        balances[trade.makerToken][makerReserve] -= trade.makerAmount;                              // Substract the maker reserves's sold amount
        
        // Send the acquired amount to the taker reserve
        if(trade.makerToken == address(0)){                                                         // Is the acquired token ETH
            require(
                takerReserve.send(trade.makerAmount - trade.takerFee),                              // Send ETH
                "Sending of ETH failed."
            );
        }else{
            Token(trade.makerToken).transfer(takerReserve, trade.makerAmount - trade.takerFee);     // Transfer ERC20
            require(                                                                                // Revert if the transfer failed
                checkERC20TransferSuccess(),
                "ERC20 token transfer failed."
            );
        }

        // Notify the reserve, that the offer got executed
        dexBlueReserve(takerReserve).offerExecuted(                     
            trade.takerToken,                                   // The token the reserve sold
            trade.takerAmount,                                  // The amount the reserve sold
            trade.makerToken,                                   // The token the reserve received
            trade.makerAmount - trade.takerFee                  // The amount the reserve received
        );
        
        // Give fee to feeCollector
        balances[trade.makerToken][feeCollector] += trade.takerFee;  // Give feeColletor the taker fee
        balances[trade.takerToken][feeCollector] += trade.makerFee;  // Give feeColletor the maker fee
        
        emit LogTrade(trade.makerToken, trade.makerAmount, trade.takerToken, trade.takerAmount);
        
        emit LogDirectWithdrawal(makerReserve, trade.takerToken, trade.takerAmount - trade.makerFee);
        emit LogDirectWithdrawal(takerReserve, trade.makerToken, trade.makerAmount - trade.takerFee);
        
        return true;
    }

    /** @notice Helper function, callable only by the contract itself, to execute a trade with a reserve contract
      * @param  sellToken   The address of the token we want to sell (ETH is address(address(0)))
      * @param  sellAmount  The amount of sellToken we want to sell
      * @param  buyToken    The address of the token we want to buy (ETH is address(address(0)))
      * @param  buyAmount   The amount of buyToken we want in exchange for sellAmount
      * @param  reserve     The address of the reserve, we want to trade with
      */
    function executeReserveTrade(
        address    sellToken,
        uint256    sellAmount,
        address    buyToken,
        uint256    buyAmount,
        address    reserve
    ) external returns(bool){
        // this method is only callable from the contract itself
        // a call is used vs a jump, to be able to revert the sending of funds to the reserve without throwing the entire transaction
        require(msg.sender == address(this));                   // check that the caller is the contract itself
        
        if(sellToken == address(0)){
            require(dexBlueReserve(reserve).trade.value(        // execute the trade in the reserve
                                                                // if the reserve accepts the trade, it will deposit the buyAmount and return true
                sellAmount                                      // send collateral to reserve
            )(
                sellToken,                                      // the token we want to sell
                sellAmount,                                     // the amount of sellToken we want to exchange
                buyToken,                                       // the token we want to receive
                buyAmount                                       // the quantity of buyToken we demand in return
            ));
        }else{
            Token(sellToken).transfer(reserve, sellAmount);     // send collateral to reserve
            require(                                            // revert if the send failed
                checkERC20TransferSuccess(),
                "ERC20 token transfer failed."
            );
            
            require(dexBlueReserve(reserve).trade(              // execute the trade in the reserve
                sellToken,                                      // the token we want to sell
                sellAmount,                                     // the amount of sellToken we want to exchange
                buyToken,                                       // the token we want to receive
                buyAmount                                       // the quantity of buyToken we demand in return
            ));
        }
        
        require(balances[buyToken][reserve] >= buyAmount);      // check if the reserve delivered on the request, else revert
        
        return true;                                            // return true if all checks are passed and the trade was executed successfully
    }
    
    /** @notice private function to execute a trade with a reserve contract
      * @param  sellToken   The address of the token we want to sell (ETH is address(address(0)))
      * @param  sellAmount  The amount of sellToken we want to sell
      * @param  buyToken    The address of the token we want to buy (ETH is address(address(0)))
      * @param  buyAmount   The amount of buyToken we want in exchange for sellAmount
      * @param  reserve     The address of the reserve, we want to trade with
      * @param  data        The data passed on to the reserve
      */
    function executeReserveTradeWithData(
        address    sellToken,
        uint256    sellAmount,
        address    buyToken,
        uint256    buyAmount,
        address    reserve,
        bytes32[]  calldata data
    ) external returns(bool){
        // this method is only callable from the contract itself
        // a call is used vs a jump, to be able to revert the sending of funds to the reserve without throwing the entire transaction
        require(msg.sender == address(this));                   // check that the caller is the contract itself
        
        if(sellToken == address(0)){
            require(dexBlueReserve(reserve).tradeWithData.value(// execute the trade in the reserve
                                                                // if the reserve accepts the trade, it will deposit the buyAmount and return true
                sellAmount                                      // send collateral to reserve
            )(
                sellToken,                                      // the token we want to sell
                sellAmount,                                     // the amount of sellToken we want to exchange
                buyToken,                                       // the token we want to receive
                buyAmount,                                      // the quantity of buyToken we demand in return
                data                                            // the data passed on to the reserve
            ));
        }else{
            Token(sellToken).transfer(reserve, sellAmount);     // send collateral to reserve
            require(                                            // revert if the send failed
                checkERC20TransferSuccess(),
                "ERC20 token transfer failed."
            );
            require(dexBlueReserve(reserve).tradeWithData(      // execute the trade in the reserve
                sellToken,                                      // the token we want to sell
                sellAmount,                                     // the amount of sellToken we want to exchange
                buyToken,                                       // the token we want to receive
                buyAmount,                                      // the quantity of buyToken we demand in return
                data                                            // the data passed on to the reserve
            ));
        }
        
        require(balances[buyToken][reserve] >= buyAmount);      // check if the reserve delivered on the request, else revert
        
        return true;                                            // return true if all checks are passed and the trade was executed successfully
    }


    // Token swapping functionality

    /** @notice Queries best output for a trade currently available from the reserves
      * @param  sell_token   The token the user wants to sell (ETH is address(0))
      * @param  sell_amount  The amount of sell_token to sell
      * @param  buy_token    The token the user wants to acquire (ETH is address(0))
      * @return The output amount the reserve with the best price offers
    */
    function getSwapOutput(address sell_token, uint256 sell_amount, address buy_token) public view returns (uint256){
        (, uint256 output) = getBestReserve(sell_token, sell_amount, buy_token);
        return output;
    }

    /** @notice Queries the reserve address and output of trade, of the reserve which offers the best deal on a trade
      * @param  sell_token   The token the user wants to sell (ETH is address(0))
      * @param  sell_amount  The amount of sell_token to sell
      * @param  buy_token    The token the user wants to acquire (ETH is address(0))
      * @return The address of the reserve offering the best deal and the expected output of the trade
    */
    function getBestReserve(address sell_token, uint256 sell_amount, address buy_token) public view returns (address, uint256){
        address bestReserve;
        uint256 bestOutput = 0;
        uint256 output;
        
        for(uint256 i = 0; i < public_reserve_arr.length; i++){
            output = dexBlueReserve(public_reserve_arr[i]).getSwapOutput(sell_token, sell_amount, buy_token);
            if(output > bestOutput){
                bestOutput  = output;
                bestReserve = public_reserve_arr[i];
            }
        }
        
        return (bestReserve, bestOutput);
    }

    /** @notice Allows users to swap a token or ETH with the reserve offering the best price for his trade
      * @param  sell_token   The token the user wants to sell (ETH is address(0))
      * @param  sell_amount  The amount of sell_token to sell
      * @param  buy_token    The token the user wants to acquire (ETH is address(0))
      * @param  min_output   The minimum amount of buy_token, the trade should result in 
      * @param  deadline     The timestamp after which the transaction should not be executed
      * @return The amount of buy_token the user receives
    */
    function swap(address sell_token, uint256 sell_amount, address buy_token,  uint256 min_output, uint256 deadline) external payable returns(uint256){

        (bool success, bytes memory returnData) = settlementModuleAddress.delegatecall(msg.data);  // delegate the call to the settlement module

        require(success);

        return abi.decode(returnData, (uint256));
    }

    /** @notice Allows users to swap a token or ETH with a specified reserve
      * @param  sell_token   The token the user wants to sell (ETH is address(0))
      * @param  sell_amount  The amount of sell_token to sell
      * @param  buy_token    The token the user wants to acquire (ETH is address(0))
      * @param  min_output   The minimum amount of buy_token, the trade should result in 
      * @param  reserve      The address of the reserve to trade with
      * @param  deadline     The timestamp after which the transaction should not be executed
    */
    function swapWithReserve(address sell_token, uint256 sell_amount, address buy_token,  uint256 min_output, address reserve, uint256 deadline) public payable returns (uint256){
        
        (bool success, bytes memory returnData) = settlementModuleAddress.delegatecall(msg.data);  // delegate the call to the settlement module

        require(success);

        return abi.decode(returnData, (uint256));
    }

    
    // Order cancellation functions

    /** @notice Give the user the option to perform multiple on-chain cancellations of orders at once with arbiters multi-sig
      * @param  orderHashes Array of orderHashes of the orders to be canceled
      * @param  v           Multi-sig v
      * @param  r           Multi-sig r
      * @param  s           Multi-sig s
      */
    function multiSigOrderBatchCancel(bytes32[] calldata orderHashes, uint8 v, bytes32 r, bytes32 s) external {
        if(
            arbiters[                                               // Check if the signee is an arbiter
                ecrecover(                                          // Restore the signing address
                    keccak256(abi.encodePacked(                     // Restore the signed hash (hash of all orderHashes)
                        "\x19Ethereum Signed Message:\n32", 
                        keccak256(abi.encodePacked(orderHashes))
                    )),
                    v, r, s
                )
            ]
        ){
            uint256 len = orderHashes.length;
            for(uint256 i = 0; i < len; i++){
                matched[orderHashes[i]] = 2**256 - 1;               // Set the matched amount of all orders to the maximum
                emit LogOrderCanceled(orderHashes[i]);              // emit LogOrderCanceled event
            }
        }else{
            revert();
        }
    }
    
    
    // Gastoken functionality
    
    // This is heavily inspired and based on the work of the gastoken.io team @ initc3.org, kudos!
    // Why not use their implementation?
    // We can safe even more gas through: having a even shorter contract address (1 byte less), saving the call to their contract, their token logic, and other minor optimisations
    
    uint256 gas_token_nonce_head;
    uint256 gas_token_nonce_tail;
    
    /** @notice Get the available amount of gasTokens
      * @return The array of all indexed token addresses
      */
    function getAvailableGasTokens() view public returns (uint256 amount){
        return gas_token_nonce_head - gas_token_nonce_tail;
    }
    
    /** @notice Mint new gasTokens
      * @param  amount  The amount of gasTokens to mint
      */
    function mintGasTokens(uint amount) public {
        gas_token_nonce_head += amount;
        while(amount-- > 0){
            createChildContract();   
        }
    }
    
    /** @notice internal function to burn gasTokens
      * @param  amount  The amount of gasTokens to burn
      */
    function spendGasTokens(uint256 amount) internal {
        uint256 tail = gas_token_nonce_tail;
        
        if(amount <= gas_token_nonce_head - tail){
            
            // tail points to slot behind the last contract in the queue
            for (uint256 i = tail + 1; i <= tail + amount; i++) {
                restoreChildContractAddress(i).call("");
            }
    
            gas_token_nonce_tail = tail + amount;
        }
    }
    
    /** @notice internal helper function to create a child contract
      * @return The address of the created contract
      */
    function createChildContract() internal returns (address addr) {
        assembly {
            let solidity_free_mem_ptr := mload(0x40)
            mstore(solidity_free_mem_ptr, 0x746d541e251335090ac5b47176af4f7e3318585733ff6000526015600bf3) // Load contract bytecode into memory
            addr := create(0, add(solidity_free_mem_ptr, 2), 30)                                          // Create child contract
        }
    }
    
    /** @notice internal helper function to restore the address of a child contract for a given nonce
      * @param  nonce   The nonce of the child contract
      * @return The address of the child contract
      */
    function restoreChildContractAddress(uint256 nonce) view internal returns (address) {
        require(nonce <= 256**9 - 1);

        uint256 encoded;
        uint256 tot_bytes;

        if (nonce < 128) {
            // RLP(nonce) = nonce
            // add the encoded nonce to the encoded word
            encoded = nonce * 256**9;
            
            // [address_length(1) address(20) nonce_length(0) nonce(1)]
            tot_bytes = 22;
        } else {
            // RLP(nonce) = [num_bytes_in_nonce nonce]
            uint nonce_bytes = 1;
            // count nonce bytes
            uint mask = 256;
            while (nonce >= mask) {
                nonce_bytes += 1;
                mask        *= 256;
            }
            
            // add the encoded nonce to the word
            encoded = ((128 + nonce_bytes) * 256**9) +  // nonce length
                      (nonce * 256**(9 - nonce_bytes)); // nonce
                   
            // [address_length(1) address(20) nonce_length(1) nonce(1-9)]
            tot_bytes = 22 + nonce_bytes;
        }

        // add the prefix and encoded address to the encoded word
        encoded += ((192 + tot_bytes) * 256**31) +     // total length
                   ((128 + 20) * 256**30) +            // address length
                   (uint256(address(this)) * 256**10); // address(this)

        uint256 hash;

        assembly {
            let mem_start := mload(0x40)        // get a pointer to free memory
            mstore(0x40, add(mem_start, 0x20))  // update the pointer

            mstore(mem_start, encoded)          // store the rlp encoding
            hash := keccak256(mem_start,
                         add(tot_bytes, 1))     // hash the rlp encoding
        }

        // interpret hash as address (20 least significant bytes)
        return address(hash);
    }
    
        
    // Signature delegation

    /** @notice delegate an address to allow it to sign orders on your behalf
      * @param delegate  The address to delegate
      */
    function delegateAddress(address delegate) external {
        // set as delegate
        require(delegates[delegate] == address(0), "Address is already a delegate");
        delegates[delegate] = msg.sender;
        
        emit LogDelegateStatus(msg.sender, delegate, true);
    }
    
    /** @notice revoke the delegation of an address
      * @param  delegate  The delegated address
      * @param  v         Multi-sig v
      * @param  r         Multi-sig r
      * @param  s         Multi-sig s
      */
    function revokeDelegation(address delegate, uint8 v, bytes32 r, bytes32 s) external {
        bytes32 hash = keccak256(abi.encodePacked(              // Restore the signed hash
            "\x19Ethereum Signed Message:\n32", 
            keccak256(abi.encodePacked(
                delegate,
                msg.sender,
                address(this)
            ))
        ));

        require(
            arbiters[ecrecover(hash, v, r, s)],     // Check if signee is an arbiter
            "MultiSig is not from known arbiter"
        );
        
        delegates[delegate] = address(1);           // Set to 1 not 0 to prevent double delegation, which would make old signed orders valid for the new delegator
        
        emit LogDelegateStatus(msg.sender, delegate, false);
    }
    

    // Management functions:

    /** @notice Constructor function. Sets initial roles and creates EIP712 Domain.
      */
    constructor() public {
        owner = msg.sender;             // Nominate sender to be the contract owner
        
        // create EIP712 domain seperator
        EIP712_Domain memory eip712Domain = EIP712_Domain({
            name              : "dex.blue",
            version           : "1",
            chainId           : 1,
            verifyingContract : address(this)
        });
        EIP712_DOMAIN_SEPARATOR = keccak256(abi.encode(
            EIP712_DOMAIN_TYPEHASH,
            keccak256(bytes(eip712Domain.name)),
            keccak256(bytes(eip712Domain.version)),
            eip712Domain.chainId,
            eip712Domain.verifyingContract
        ));
    }
    
    /** @notice Allows the owner to change / disable the waiting period for a single sig withdrawal
      * @param  waiting_period The new waiting period
      */
    function changeSingleSigWaitingPeriod(uint256 waiting_period) external {
        require(
            msg.sender == owner             // only owner can set waiting period
            && waiting_period <= 86400      // max period owner can set is one day
        );
        
        single_sig_waiting_period = waiting_period;
    }
    
    /** @notice Allows the owner to handle over the ownership to another address
      * @param  new_owner The new owner address
      */
    function changeOwner(address payable new_owner) external {
        require(msg.sender == owner);
        owner = new_owner;
    }
    
    /** @notice Allows the owner to register & cache a new reserve address in the smart conract
      * @param  reserve   The address of the reserve to add
      * @param  index     The index under which the reserve should be indexed
      * @param  is_public Whether the reserve should publicly available through swap() & swapWithReserve()
      */
    function cacheReserveAddress(address payable reserve, uint256 index, bool is_public) external {
        require(arbiters[msg.sender]);
        
        reserves[index] = reserve;
        reserve_indices[reserve] = index;
        
        if(is_public){
            public_reserves[reserve] = true;
            public_reserve_arr.push(reserve);  // append the reserve to the reserve array
        }
    }
    
    /** @notice Allows the owner to remove a reserve from the array swap() and getSwapOutput() need to loop through
      * @param  reserve The address of the reserve to remove
      */
    function removePublicReserveAddress(address reserve) external {
        require(arbiters[msg.sender]);
        
        public_reserves[reserve] = false;

        for(uint256 i = 0; i < public_reserve_arr.length; i++){
            if(public_reserve_arr[i] == reserve){
                public_reserve_arr[i] = public_reserve_arr[public_reserve_arr.length - 1]; // array order does not matter, so we just move the last element in the slot of the element we are removing
                
                delete public_reserve_arr[public_reserve_arr.length-1];                    // delete the last element of the array
                public_reserve_arr.length--;                             
                
                return;
            }
        }
    }
        
    /** @notice Allows an arbiterto cache a new token address
      * @param  token   The address of the token to add
      * @param  index   The index under which the token should be indexed
      */
    function cacheTokenAddress(address token, uint256 index) external {
        require(arbiters[msg.sender]);
        
        tokens[index]        = token;
        token_indices[token] = index;
        
        token_arr.push(token);  // append the token to the array
    }

    /** @notice Allows arbiters to remove a token from the token array
      * @param  token The address of the token to remove
      */
    function removeTokenAddressFromArr(address token) external {
        require(arbiters[msg.sender]);
        
        for(uint256 i = 0; i < token_arr.length; i++){
            if(token_arr[i] == token){
                token_arr[i] = token_arr[token_arr.length - 1]; // array order does not matter, so we just move the last element in the slot of the element we are removing
                
                delete token_arr[token_arr.length-1];           // delete the last element of the array
                token_arr.length--;                             
                
                return;
            }
        }
    }
    
    /** @notice Allows the owner to nominate or denominate trade arbiting addresses
      * @param  arbiter The arbiter whose status to change
      * @param  status  whether the address should be an arbiter (true) or not (false)
      */
    function nominateArbiter(address arbiter, bool status) external {
        require(msg.sender == owner);                           // Check if sender is owner
        arbiters[arbiter] = status;                             // Update address status
    }
    
    /** @notice Allows the owner to pause / unpause the market
      * @param  state  whether the the market should be active (true) or paused (false)
      */
    function setMarketActiveState(bool state) external {
        require(msg.sender == owner);                           // Check if sender is owner
        marketActive = state;                                   // pause / unpause market
    }
    
    /** @notice Allows the owner to nominate the feeCollector address
      * @param  collector The address to nominate as feeCollector
      */
    function nominateFeeCollector(address payable collector) external {
        require(msg.sender == owner && !feeCollectorLocked);    // Check if sender is owner and feeCollector address is not locked
        feeCollector = collector;                               // Update feeCollector address
    }
    
    /** @notice Allows the owner to lock the feeCollector address
    */
    function lockFeeCollector() external {
        require(msg.sender == owner);                           // Check if sender is owner
        feeCollectorLocked = true;                              // Lock feeCollector address
    }
    
    /** @notice Get the feeCollectors address
      * @return The feeCollectors address
      */
    function getFeeCollector() public view returns (address){
        return feeCollector;
    }

    /** @notice Allows an arbiter or feeCollector to directly withdraw his own funds (would allow e.g. a fee distribution contract the withdrawal of collected fees)
      * @param  token   The token to withdraw
      * @param  amount  The amount of tokens to withdraw
    */
    function directWithdrawal(address token, uint256 amount) external returns(bool){
        if (
            (
                msg.sender == feeCollector                        // Check if the sender is the feeCollector
                || arbiters[msg.sender]                           // Check if the sender is an arbiter
            )
            && balances[token][msg.sender] >= amount              // Check if feeCollector has the sufficient balance
        ){
            balances[token][msg.sender] -= amount;                // Substract the feeCollectors balance
            
            if(token == address(0)){                              // Is the withdrawal token ETH
                require(
                    msg.sender.send(amount),                      // Withdraw ETH
                    "Sending of ETH failed."
                );
            }else{
                Token(token).transfer(msg.sender, amount);        // Withdraw ERC20
                require(                                          // Revert if the withdrawal failed
                    checkERC20TransferSuccess(),
                    "ERC20 token transfer failed."
                );
            }
            
            emit LogDirectWithdrawal(msg.sender, token, amount);     // emit LogDirectWithdrawal event
            return true;
        }else{
            return false;
        }
    }
}
function removeTokenAddressFromArr(address token) external {
        require(arbiters[msg.sender]);
        
        for(uint256 i = 0; i < token_arr.length; i++){
            if(token_arr[i] == token){
                token_arr[i] = token_arr[token_arr.length - 1]; // array order does not matter, so we just move the last element in the slot of the element we are removing
                
                delete token_arr[token_arr.length-1];           // delete the last element of the array
                token_arr.length--;                             
                
                return;
            }
        }
    }
/** @notice Allows arbiters to remove a token from the token array
 * @param  token The address of the token to remove
 */