contract dexBlueSettlementModule is dexBlueStorage, dexBlueEvents, dexBlueUtils, dexBlueStructs{
    
    /** @notice Internal helper function to settle a trade between two orders
      * @param  makerOrder  The maker order
      * @param  takerOrder  The taker order
      * @param  trade       The trade to settle between the two
      * @return Whether the trade succeeded or failed
      */
    function matchOrders(
        Order memory makerOrder,
        Order memory takerOrder,
        Trade memory trade
    ) internal returns (bool){
        // Load the orders previously matched amounts into memory
        uint makerOrderMatched = matched[makerOrder.hash];
        uint takerOrderMatched = matched[takerOrder.hash];

        if( // Check if the arbiter has matched following the conditions of the two order signees
            // Do maker and taker want to trade the same tokens with each other
               makerOrder.buyToken == takerOrder.sellToken
            && takerOrder.buyToken == makerOrder.sellToken
            
            // Are both of the orders still valid
            && makerOrder.expiry > block.timestamp
            && takerOrder.expiry > block.timestamp 
            
            // Do maker and taker hold the required balances
            && balances[makerOrder.sellToken][makerOrder.signee] >= trade.makerAmount - trade.makerRebate
            && balances[takerOrder.sellToken][takerOrder.signee] >= trade.takerAmount
            
            // Are they both matched at a rate better or equal to the one they signed
            && trade.makerAmount - trade.makerRebate <= makerOrder.sellAmount * trade.takerAmount / makerOrder.buyAmount + 1  // Check maker doesn't overpay (+ 1 to deal with rouding errors for very smal amounts)
            && trade.takerAmount                     <= takerOrder.sellAmount * trade.makerAmount / takerOrder.buyAmount + 1  // Check taker doesn't overpay (+ 1 to deal with rouding errors for very smal amounts)
            
            // Check if the order was cancelled
            && makerOrder.sellAmount > makerOrderMatched
            && takerOrder.sellAmount > takerOrderMatched

            // Check if the matched amount + previously matched trades doesn't exceed the amount specified by the order signee
            && trade.makerAmount - trade.makerRebate + makerOrderMatched <= makerOrder.sellAmount
            && trade.takerAmount                     + takerOrderMatched <= takerOrder.sellAmount
                
            // Check if the charged fee is not too high
            && trade.makerFee <= trade.takerAmount / 20
            && trade.takerFee <= trade.makerAmount / 20
            
            // Check if maker_rebate is smaller than or equal to the taker's fee which compensates it
            && trade.makerRebate <= trade.takerFee
        ){
            // Settle the trade:
            
            // Substract sold amounts
            balances[makerOrder.sellToken][makerOrder.signee] -= trade.makerAmount - trade.makerRebate;     // Substract maker's sold amount minus the makers rebate
            balances[takerOrder.sellToken][takerOrder.signee] -= trade.takerAmount;                         // Substract taker's sold amount
            
            // Add bought amounts
            balances[makerOrder.buyToken][makerOrder.signee] += trade.takerAmount - trade.makerFee;         // Give the maker his bought amount minus the fee
            balances[takerOrder.buyToken][takerOrder.signee] += trade.makerAmount - trade.takerFee;         // Give the taker his bought amount minus the fee
            
            // Save sold amounts to prevent double matching
            matched[makerOrder.hash] += trade.makerAmount - trade.makerRebate;                              // Prevent maker order from being reused
            matched[takerOrder.hash] += trade.takerAmount;                                                  // Prevent taker order from being reused
            
            // Give fee to feeCollector
            balances[takerOrder.buyToken][feeCollector] += trade.takerFee - trade.makerRebate;              // Give the feeColletor the taker fee minus the maker rebate 
            balances[makerOrder.buyToken][feeCollector] += trade.makerFee;                                  // Give the feeColletor the maker fee
            
            // Set potential previous blocking of these funds to 0
            blocked_for_single_sig_withdrawal[makerOrder.sellToken][makerOrder.signee] = 0;                 // If the maker tried to block funds which he/she used in this order we have to unblock them
            blocked_for_single_sig_withdrawal[takerOrder.sellToken][takerOrder.signee] = 0;                 // If the taker tried to block funds which he/she used in this order we have to unblock them
            
            emit LogTrade(makerOrder.sellToken, trade.makerAmount, takerOrder.sellToken, trade.takerAmount);

            return true;                                                                         
        }else{
            return false;                                                                                   
        }
    }

    /** @notice Internal helper function to settle a trade between an order and a reserve
      * @param  order    The order
      * @param  reserve  The reserve
      * @param  trade    The trade to settle between the two
      * @return Whether the trade succeeded or failed
      */
    function matchOrderWithReserve(
        Order memory order,
        address      reserve,
        ReserveTrade memory trade
    ) internal returns(bool){
        // Load the orders previously matched amount into memory
        uint orderMatched = matched[order.hash];

        if( // Check if the arbiter matched the conditions of the order signee
            // Does the order signee has the required balances deposited
            balances[order.sellToken][order.signee] >= trade.orderAmount - trade.orderRebate
            
            // Is the order still valid
            && order.expiry > block.timestamp 
            
            // Is the order matched at a rate better or equal to the one specified by the signee
            && trade.orderAmount - trade.orderRebate <= order.sellAmount * trade.reserveAmount / order.buyAmount + 1  // + 1 to deal with rouding errors
            
            // Check if the order was cancelled
            && order.sellAmount > orderMatched

            // Check if the matched amount + previously matched trades doesn't exceed the amount specified by the order signee
            && trade.orderAmount - trade.orderRebate + orderMatched <= order.sellAmount
                
            // Check if the charged fee is not too high
            && trade.orderFee   <= trade.reserveAmount / 20
            && trade.reserveFee <= trade.orderAmount   / 20
            
            // Check if the rebates can be compensated by the fees
            && trade.orderRebate   <= trade.reserveFee
            && trade.reserveRebate <= trade.orderFee
        ){
            balances[order.sellToken][order.signee] -= trade.orderAmount - trade.orderRebate;  // Substract users's sold amount minus the makers rebate
            
            (bool txSuccess, bytes memory returnData) = address(this).call.gas(
                    trade.gasLimit                                              // The gas limit for the call
                )(
                    abi.encodePacked(                                           // This encodes the function to call and the parameters we are passing to the settlement function
                        dexBlue(address(0)).executeReserveTrade.selector,       // This function executes the call to the reserve
                        abi.encode(                            
                            order.sellToken,                                    // The token the order signee wants to exchange with the reserve
                            trade.orderAmount   - trade.reserveFee,             // The reserve receives the sold amount minus the fee
                            order.buyToken,                                     // The token the order signee wants to receive from the reserve
                            trade.reserveAmount - trade.reserveRebate,          // The reserve has to return the amount the order want to receive minus
                            reserve                                             // The reserve the trade is settled with
                        )
                    )
                );
            
            if(
               txSuccess                                    // The call to the reserve did not fail
               && abi.decode(returnData, (bool))            // The call returned true (we are sure its a contract we called)
               // executeReserveTrade checks whether the reserve deposited the funds
            ){
                // Substract the deposited amount from reserves balance
                balances[order.buyToken][reserve]      -= trade.reserveAmount - trade.reserveRebate;    // Substract reserves's sold amount
                
                // The amount to the order signees balance
                balances[order.buyToken][order.signee] += trade.reserveAmount - trade.orderFee;         // Give the users his bought amount minus the fee
                
                // Save sold amounts to prevent double matching
                matched[order.hash] += trade.orderAmount - trade.orderRebate;                           // Prevent maker order from being reused
                
                // Give fee to feeCollector
                balances[order.buyToken][feeCollector]  += trade.orderFee   - trade.reserveRebate;      // Give the feeColletor the fee minus the maker rebate
                balances[order.sellToken][feeCollector] += trade.reserveFee - trade.orderRebate;        // Give the feeColletor the fee minus the maker rebate
                
                // Set potential previous blocking of these funds to 0
                blocked_for_single_sig_withdrawal[order.sellToken][order.signee] = 0;                   // If the user blocked funds which he/she used in this order we have to unblock them

                if(trade.orderIsMaker){
                    emit LogTrade(order.sellToken, trade.orderAmount, order.buyToken, trade.reserveAmount);
                }else{
                    emit LogTrade(order.buyToken, trade.reserveAmount, order.sellToken, trade.orderAmount);
                }
                emit LogDirectWithdrawal(reserve, order.sellToken, trade.orderAmount - trade.reserveFee);
                
                return true;
            }else{
                balances[order.sellToken][order.signee] += trade.orderAmount - trade.orderRebate;  // Refund substracted amount
                
                return false;
            }
        }else{
            return false;
        }
    }
    
    /** @notice Internal helper function to settle a trade between an order and a reserve, passing some additional data to the reserve
      * @param  order    The order
      * @param  reserve  The reserve
      * @param  trade    The trade to settle between the two
      * @param  data     The data to pass along to the reserve
      * @return Whether the trade succeeded or failed
      */
    function matchOrderWithReserveWithData(
        Order        memory order,
        address      reserve,
        ReserveTrade memory trade,
        bytes32[]    memory data
    ) internal returns(bool){
        // Load the orders previously matched amount into memory
        uint orderMatched = matched[order.hash];

        if( // Check if the arbiter matched the conditions of the order signee
            // Does the order signee has the required balances deposited
            balances[order.sellToken][order.signee] >= trade.orderAmount - trade.orderRebate
            
            // Is the order still valid
            && order.expiry > block.timestamp 
            
            // Is the order matched at a rate better or equal to the one specified by the signee
            && trade.orderAmount - trade.orderRebate <= order.sellAmount * trade.reserveAmount / order.buyAmount + 1  // + 1 to deal with rouding errors
            
            // Check if the order was cancelled
            && order.sellAmount > orderMatched

            // Check if the matched amount + previously matched trades doesn't exceed the amount specified by the order signee
            && trade.orderAmount - trade.orderRebate + orderMatched <= order.sellAmount
                
            // Check if the charged fee is not too high
            && trade.orderFee   <= trade.reserveAmount / 20
            && trade.reserveFee <= trade.orderAmount   / 20
            
            // Check if the rebates can be compensated by the fees
            && trade.orderRebate   <= trade.reserveFee
            && trade.reserveRebate <= trade.orderFee
        ){
            balances[order.sellToken][order.signee] -= trade.orderAmount - trade.orderRebate;  // Substract users's sold amount minus the makers rebate
            
            (bool txSuccess, bytes memory returnData) = address(this).call.gas(
                    trade.gasLimit                                                  // The gas limit for the call
                )(
                    abi.encodePacked(                                               // This encodes the function to call and the parameters we are passing to the settlement function
                        dexBlue(address(0)).executeReserveTradeWithData.selector,   // This function executes the call to the reserve
                        abi.encode(                            
                            order.sellToken,                                        // The token the order signee wants to exchange with the reserve
                            trade.orderAmount   - trade.reserveFee,                 // The reserve receives the sold amount minus the fee
                            order.buyToken,                                         // The token the order signee wants to receive from the reserve
                            trade.reserveAmount - trade.reserveRebate,              // The reserve has to return the amount the order want to receive minus
                            reserve,                                                // The reserve the trade is settled with
                            data                                                    // The data passed on to the reserve
                        )
                    )
                );
            
            if(
               txSuccess                                    // The call to the reserve did not fail
               && abi.decode(returnData, (bool))            // The call returned true (we are sure its a contract we called)
               // executeReserveTrade checks whether the reserve deposited the funds
            ){
                // substract the deposited amount from reserves balance
                balances[order.buyToken][reserve]      -= trade.reserveAmount - trade.reserveRebate;    // Substract reserves's sold amount
                
                // the amount to the order signees balance
                balances[order.buyToken][order.signee] += trade.reserveAmount - trade.orderFee;         // Give the users his bought amount minus the fee
                
                // Save sold amounts to prevent double matching
                matched[order.hash] += trade.orderAmount - trade.orderRebate;                           // Prevent maker order from being reused
                
                // Give fee to feeCollector
                balances[order.buyToken][feeCollector]  += trade.orderFee   - trade.reserveRebate;      // Give the feeColletor the fee minus the maker rebate
                balances[order.sellToken][feeCollector] += trade.reserveFee - trade.orderRebate;        // Give the feeColletor the fee minus the maker rebate
                
                // Set potential previous blocking of these funds to 0
                blocked_for_single_sig_withdrawal[order.sellToken][order.signee] = 0;                   // If the user blocked funds which he/she used in this order we have to unblock them

                if(trade.orderIsMaker){
                    emit LogTrade(order.sellToken, trade.orderAmount, order.buyToken, trade.reserveAmount);
                }else{
                    emit LogTrade(order.buyToken, trade.reserveAmount, order.sellToken, trade.orderAmount);
                }
                emit LogDirectWithdrawal(reserve, order.sellToken, trade.orderAmount - trade.reserveFee);
                
                return true;
            }else{
                balances[order.sellToken][order.signee] += trade.orderAmount - trade.orderRebate;  // Refund substracted amount
                
                return false;
            }
        }else{
            return false;
        }
    }
    
    /** @notice internal helper function to settle 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 matchReserveWithReserve(
        address             makerReserve,
        address             takerReserve,
        ReserveReserveTrade memory trade
    ) internal returns(bool){

        (bool txSuccess, bytes memory returnData) = address(this).call.gas(
            trade.gasLimit                                                      // The gas limit for the call
        )(
            abi.encodePacked(                                                   // This encodes the function to call and the parameters we are passing to the settlement function
                dexBlue(address(0)).executeReserveReserveTrade.selector,     // This function executes the call to the reserves
                abi.encode(                            
                    makerReserve,
                    takerReserve,
                    trade
                )
            )
        );

        return (
            txSuccess                                    // The call to the reserve did not fail
            && abi.decode(returnData, (bool))            // The call returned true (we are sure its a contract we called)
        );
    }

    
    /** @notice internal helper function to settle 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 matchReserveWithReserveWithData(
        address             makerReserve,
        address             takerReserve,
        ReserveReserveTrade memory trade,
        bytes32[] memory    makerData,
        bytes32[] memory    takerData
    ) internal returns(bool){

        (bool txSuccess, bytes memory returnData) = address(this).call.gas(
            trade.gasLimit                                                       // The gas limit for the call
        )(
            abi.encodePacked(                                                    // This encodes the function to call and the parameters we are passing to the settlement function
                dexBlue(address(0)).executeReserveReserveTradeWithData.selector, // This function executes the call to the reserves
                abi.encode(                            
                    makerReserve,
                    takerReserve,
                    trade,
                    makerData,
                    takerData
                )
            )
        );

        return (
            txSuccess                                    // The call to the reserve did not fail
            && abi.decode(returnData, (bool))            // The call returned true (we are sure its a contract we called)
        );
    }
    
    /** @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
        
        Order[] memory orders = new Order[](orderInput.length);
        uint256 i = orderInput.length;

        while(i-- != 0){                                // Loop through the orderInput array, to parse the infos and restore all signees
            orders[i] = orderFromInput(orderInput[i]);  // Parse this orders infos
        }
        
        uint256 makerIdentifier;
        uint256 takerIdentifier;
        
        for(i = 0; i < tradeInput.length; i++){
            makerIdentifier = uint256((tradeInput[i].packedInput3 & 0x0000000000000000000000000000000000ffff00000000000000000000000000) >> 104);
            takerIdentifier = uint256((tradeInput[i].packedInput3 & 0x00000000000000000000000000000000000000ffff0000000000000000000000) >> 88);
            
            if(tradeInput[i].packedInput3[16] == byte(0x11)){       // Both are orders
                if(!matchOrders(
                    orders[makerIdentifier],
                    orders[takerIdentifier],
                    tradeFromInput(tradeInput[i])
                )){
                    emit LogTradeFailed();      
                }
            }else if(tradeInput[i].packedInput3[16] == byte(0x10)){ // Maker is order, taker is reserve
                if(!matchOrderWithReserve(
                    orders[makerIdentifier],
                    reserves[takerIdentifier],
                    reserveTradeFromInput(tradeInput[i])
                )){
                    emit LogTradeFailed();      
                }
            }else if(tradeInput[i].packedInput3[16] == byte(0x01)){ // Taker is order, maker is reserve
                if(!matchOrderWithReserve(
                    orders[takerIdentifier],
                    reserves[makerIdentifier],
                    reserveTradeFromInput(tradeInput[i])
                )){
                    emit LogTradeFailed();      
                }
            }else{                                                  // Both are reserves
                if(!matchReserveWithReserve(
                    reserves[makerIdentifier],
                    reserves[takerIdentifier],
                    reserveReserveTradeFromInput(tradeInput[i])
                )){
                    emit LogTradeFailed();      
                }
            }
        }
    }

    /** @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
        
        if(!matchOrders(
            orderFromInput(makerOrderInput),
            orderFromInput(takerOrderInput),
            tradeFromInput(tradeInput)
        )){
            emit LogTradeFailed();      
        }
    }
        
    /** @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
        
        if(!matchOrderWithReserve(
            orderFromInput(orderInput),
            reserves[
                tradeInput.packedInput3[16] == byte(0x01) ? // is maker reserve
                    // maker is reserve
                    uint256((tradeInput.packedInput3 & 0x0000000000000000000000000000000000ffff00000000000000000000000000) >> 104) :
                    // taker is reserve
                    uint256((tradeInput.packedInput3 & 0x00000000000000000000000000000000000000ffff0000000000000000000000) >> 88)
            ],
            reserveTradeFromInput(tradeInput)
        )){
            emit LogTradeFailed();      
        }
    }

    /** @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
      * @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
        
        if(!matchOrderWithReserveWithData(
            orderFromInput(orderInput),
            reserves[
                tradeInput.packedInput3[16] == byte(0x01) ? // Is maker reserve
                    // maker is reserve
                    uint256((tradeInput.packedInput3 & 0x0000000000000000000000000000000000ffff00000000000000000000000000) >> 104) :
                    // taker is reserve
                    uint256((tradeInput.packedInput3 & 0x00000000000000000000000000000000000000ffff0000000000000000000000) >> 88)
            ],
            reserveTradeFromInput(tradeInput),
            data
        )){
            emit LogTradeFailed();      
        }
    }
    
    /** @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
        
        if(!matchReserveWithReserve(
            reserves[uint256((tradeInput.packedInput3 & 0x0000000000000000000000000000000000ffff00000000000000000000000000) >> 104)],
            reserves[uint256((tradeInput.packedInput3 & 0x00000000000000000000000000000000000000ffff0000000000000000000000) >> 88)],
            reserveReserveTradeFromInput(tradeInput)
        )){
            emit LogTradeFailed();      
        }
    }
    
    /** @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
        
        if(!matchReserveWithReserveWithData(
            reserves[uint256((tradeInput.packedInput3 & 0x0000000000000000000000000000000000ffff00000000000000000000000000) >> 104)],
            reserves[uint256((tradeInput.packedInput3 & 0x00000000000000000000000000000000000000ffff0000000000000000000000) >> 88)],
            reserveReserveTradeFromInput(tradeInput),
            makerData,
            takerData
        )){
            emit LogTradeFailed();      
        }
    }
    
    /** @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
        
        // Parse Orders from packed input
        uint256 i = orderInput.length;
        Order[] memory orders = new Order[](i);
        while(i-- != 0){
            orders[i] = orderFromInput(orderInput[i]);
        }
        
        // Parse RingTrades from packed input
        i = tradeInput.length;
        RingTrade[] memory trades = new RingTrade[](i);
        while(i-- != 0){
            trades[i] = ringTradeFromInput(tradeInput[i]);
        }
        
        uint256 prev = trades.length - 1;
        uint256 next = 1;
         // Loop through the RingTrades array and settle each participants trade
        for(i = 0; i < trades.length; i++){
            
            require(
                // Check if the charged fee is not too high
                trades[i].fee       <= trades[prev].giveAmount / 20
                
                // Check if maker_rebate is smaller than or equal to the taker's fee which compensates it
                && trades[i].rebate <= trades[next].fee
            );
            
            if(trades[i].isReserve){ // Ring element is a reserve
                address reserve = reserves[trades[i].identifier];

                if(i == 0){
                    require(
                        dexBlueReserve(reserve).offer(
                            trades[i].giveToken,                                   // The token the reserve would sell
                            trades[i].giveAmount - trades[i].rebate,               // The amount the reserve would sell
                            trades[prev].giveToken,                                // The token the reserve would receive
                            trades[prev].giveAmount - trades[i].fee                // The amount the reserve would receive
                        )
                        && balances[trades[i].giveToken][reserve] >= trades[i].giveAmount
                    );
                }else{
                    uint256 receiveAmount = trades[prev].giveAmount - trades[i].fee;

                    if(trades[prev].giveToken != address(0)){
                        Token(trades[prev].giveToken).transfer(reserve, receiveAmount);  // Send collateral to reserve
                        require(                                                         // Revert if the send failed
                            checkERC20TransferSuccess(),
                            "ERC20 token transfer failed."
                        );
                    }

                    require(
                        dexBlueReserve(reserve).trade.value(
                            trades[prev].giveToken == address(0) ? receiveAmount : 0
                        )(             
                            trades[prev].giveToken,
                            receiveAmount,                                      // Reserve gets the reserve_buy_amount minus the fee
                            trades[i].giveToken,    
                            trades[i].giveAmount - trades[i].rebate             // Reserve has to give reserve_sell_amount minus the rebate
                        )
                    );
                }

                // Substract deposited amount from reserves balance
                balances[trades[i].giveToken][reserve] -= trades[i].giveAmount - trades[i].rebate;

                emit LogDirectWithdrawal(reserve, trades[prev].giveToken, trades[prev].giveAmount - trades[i].fee);
            }else{ // Ring element is an order
                
                Order memory order = orders[trades[i].identifier];  // Cache order

                uint256 orderMatched = matched[order.hash];
                
                require(
                    // Does the order signee want to trade the last elements giveToken and this elements giveToken
                       order.buyToken  == trades[prev].giveToken
                    && order.sellToken == trades[i].giveToken
                    
                    // Is the order still valid
                    && order.expiry > block.timestamp
                    
                    // Does the order signee hold the required balances
                    && balances[order.sellToken][order.signee] >= trades[i].giveAmount - trades[i].rebate
                    
                    // Is the order matched at a rate better or equal to the one the order signee signed
                    && trades[i].giveAmount - trades[i].rebate <= order.sellAmount * trades[prev].giveAmount / order.buyAmount + 1  // Check order doesn't overpay (+ 1 to deal with rouding errors for very smal amounts)
                    
                    // Check if the order was cancelled
                    && order.sellAmount > orderMatched
                    
                    // Do the matched amount + previously matched trades not exceed the amount specified by the order signee
                    && trades[i].giveAmount - trades[i].rebate + orderMatched <= order.sellAmount
                );
                
                // Substract the sold amounts
                balances[order.sellToken       ][order.signee] -= trades[i].giveAmount - trades[i].rebate;      // Substract sold amount minus the makers rebate from order signees balance
                
                // Add bought amounts
                balances[trades[prev].giveToken][order.signee] += trades[prev].giveAmount - trades[i].fee;      // Give the order signee his bought amount minus the fee
                
                // Save sold amounts to prevent double matching
                matched[order.hash] += trades[i].giveAmount - trades[i].rebate;                                 // Prevent order from being reused
                
                // Set potential previous blocking of these funds to 0
                blocked_for_single_sig_withdrawal[order.sellToken][order.signee] = 0;                           // If the order signee tried to block funds which he/she used in this order we have to unblock them
            }

            emit LogTrade(trades[prev].giveToken, trades[prev].giveAmount, trades[i].giveToken, trades[i].giveAmount);
            
            // Give fee to feeCollector
            balances[trades[prev].giveToken][feeCollector] += trades[i].fee - trades[prev].rebate;              // Give the feeColletor the fee minus the maker rebate 
            
            prev = i;
            if(i == trades.length - 2){
                next = 0;
            }else{
                next = i + 2;
            }
        }

        if(trades[0].isReserve){
            address payable reserve = reserves[trades[0].identifier];
            prev = trades.length - 1;
            
            if(trades[prev].giveToken == address(0)){                                                       // Is the withdrawal token ETH
                require(
                    reserve.send(trades[prev].giveAmount - trades[0].fee),                                  // Withdraw ETH
                    "Sending of ETH failed."
                );
            }else{
                Token(trades[prev].giveToken).transfer(reserve, trades[prev].giveAmount - trades[0].fee);   // Withdraw ERC20
                require(                                                                                    // Revert if the withdrawal failed
                    checkERC20TransferSuccess(),
                    "ERC20 token transfer failed."
                );
            }

            // Notify the reserve, that the offer got executed
            dexBlueReserve(reserve).offerExecuted(
                trades[0].giveToken,                                   // The token the reserve sold
                trades[0].giveAmount - trades[0].rebate,               // The amount the reserve sold
                trades[prev].giveToken,                                // The token the reserve received
                trades[prev].giveAmount - trades[0].fee                // The amount the reserve received
            );
        }
    }
    
    
    /** @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
        
        // Parse Orders from packed input
        uint256 i = orderInput.length;
        Order[] memory orders = new Order[](i);
        while(i-- != 0){
            orders[i] = orderFromInput(orderInput[i]);
        }
        
        // Parse RingTrades from packed input
        i = tradeInput.length;
        RingTrade[] memory trades = new RingTrade[](i);
        while(i-- != 0){
            trades[i] = ringTradeFromInput(tradeInput[i]);
        }
        
        uint256 prev = trades.length - 1;
        uint256 next = 1;
         // Loop through the RingTrades array and settle each participants trade
        for(i = 0; i < trades.length; i++){
            
            require(
                // Check if the charged fee is not too high
                trades[i].fee       <= trades[prev].giveAmount / 20
                
                // Check if maker_rebate is smaller than or equal to the taker's fee which compensates it
                && trades[i].rebate <= trades[next].fee
            );
            
            if(trades[i].isReserve){ // ring element is a reserve
                address reserve = reserves[trades[i].identifier];

                if(i == 0){
                    require(
                        dexBlueReserve(reserve).offerWithData(
                            trades[i].giveToken,                                   // The token the reserve would sell
                            trades[i].giveAmount - trades[i].rebate,               // The amount the reserve would sell
                            trades[prev].giveToken,                                // The token the reserve would receive
                            trades[prev].giveAmount - trades[i].fee,               // The amount the reserve would receive
                            data[i]                                                // The data to pass along to the reserve
                        )
                        && balances[trades[i].giveToken][reserve] >= trades[i].giveAmount
                    );
                }else{
                    uint256 receiveAmount = trades[prev].giveAmount - trades[i].fee;

                    if(trades[prev].giveToken != address(0)){
                        Token(trades[prev].giveToken).transfer(reserve, receiveAmount);  // Send collateral to reserve
                        require(                                                         // Revert if the send failed
                            checkERC20TransferSuccess(),
                            "ERC20 token transfer failed."
                        );
                    }

                    require(
                        dexBlueReserve(reserve).tradeWithData.value(
                            trades[prev].giveToken == address(0) ? receiveAmount : 0
                        )(             
                            trades[prev].giveToken,
                            receiveAmount,                                      // Reserve gets the reserve_buy_amount minus the fee
                            trades[i].giveToken,    
                            trades[i].giveAmount - trades[i].rebate,            // Reserve has to give reserve_sell_amount minus the reserve rebate
                            data[i]                                             // The data to pass along to the reserve
                        )
                    );
                }

                // Substract deposited amount from reserves balance
                balances[trades[i].giveToken][reserve] -= trades[i].giveAmount - trades[i].rebate;

                emit LogDirectWithdrawal(reserve, trades[prev].giveToken, trades[prev].giveAmount - trades[i].fee);
            }else{ // Ring element is an order
                
                Order memory order = orders[trades[i].identifier];  // Cache order

                uint256 orderMatched = matched[order.hash];
                
                require(
                    // Does the order signee want to trade the last elements giveToken and this elements giveToken
                       order.buyToken  == trades[prev].giveToken
                    && order.sellToken == trades[i].giveToken
                    
                    // Is the order still valid
                    && order.expiry > block.timestamp
                    
                    // Does the order signee hold the required balances
                    && balances[order.sellToken][order.signee] >= trades[i].giveAmount - trades[i].rebate
                    
                    // Is the order matched at a rate better or equal to the one the order signee signed
                    && trades[i].giveAmount - trades[i].rebate <= order.sellAmount * trades[prev].giveAmount / order.buyAmount + 1  // Check order doesn't overpay (+ 1 to deal with rouding errors for very smal amounts)
                    
                    // Check if the order was cancelled
                    && order.sellAmount > orderMatched
                    
                    // Do the matched amount + previously matched trades not exceed the amount specified by the order signee
                    && trades[i].giveAmount - trades[i].rebate + orderMatched <= order.sellAmount
                );
                
                // Substract the sold amounts
                balances[order.sellToken       ][order.signee] -= trades[i].giveAmount - trades[i].rebate;      // Substract sold amount minus the makers rebate from order signees balance
                
                // Add bought amounts
                balances[trades[prev].giveToken][order.signee] += trades[prev].giveAmount - trades[i].fee;      // Give the order signee his bought amount minus the fee
                
                // Save sold amounts to prevent double matching
                matched[order.hash] += trades[i].giveAmount - trades[i].rebate;                                 // Prevent order from being reused
                
                // Set potential previous blocking of these funds to 0
                blocked_for_single_sig_withdrawal[order.sellToken][order.signee] = 0;                           // If the order signee tried to block funds which he/she used in this order we have to unblock them
            }

            emit LogTrade(trades[prev].giveToken, trades[prev].giveAmount, trades[i].giveToken, trades[i].giveAmount);
            
            // Give fee to feeCollector
            balances[trades[prev].giveToken][feeCollector] += trades[i].fee - trades[prev].rebate;              // Give the feeColletor the fee minus the maker rebate 
            
            prev = i;
            if(i == trades.length - 2){
                next = 0;
            }else{
                next = i + 2;
            }
        }

        if(trades[0].isReserve){
            address payable reserve = reserves[trades[0].identifier];
            prev = trades.length - 1;
            
            if(trades[prev].giveToken == address(0)){                                                       // Is the withdrawal token ETH
                require(
                    reserve.send(trades[prev].giveAmount - trades[0].fee),                                  // Withdraw ETH
                    "Sending of ETH failed."
                );
            }else{
                Token(trades[prev].giveToken).transfer(reserve, trades[prev].giveAmount - trades[0].fee);   // Withdraw ERC20
                require(                                                                                    // Revert if the withdrawal failed
                    checkERC20TransferSuccess(),
                    "ERC20 token transfer failed."
                );
            }

            // Notify the reserve, that the offer got executed
            dexBlueReserve(reserve).offerExecuted(
                trades[0].giveToken,                                   // The token the reserve sold
                trades[0].giveAmount - trades[0].rebate,               // The amount the reserve sold
                trades[prev].giveToken,                                // The token the reserve received
                trades[prev].giveAmount - trades[0].fee                // The amount the reserve received
            );
        }
    }
    
    
    // Swapping functions
    
    /** @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){        
        require(
            (
                deadline == 0                               // No deadline is set         
                || deadline > block.timestamp               // Deadline is met
            ),                                              // Check whether the deadline is met
            "Call deadline exceeded."
        );
        
        (address reserve, uint256 amount) = getBestReserve(sell_token, sell_amount, buy_token);     // Check which reserve offers the best deal on the trade
        
        require(
            amount >= min_output,                                                                   // Check whether the best reserves deal is good enough
            "Too much slippage"
        );
        
        return swapWithReserve(sell_token, sell_amount, buy_token,  min_output, reserve, deadline); // Execute the swap with the best reserve
    }
    
    /** @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){
        require(
            (
                deadline == 0                               // No deadline is set         
                || deadline > block.timestamp               // Deadline is met
            ),
            "Call deadline exceeded."
        );
        
        require(
            public_reserves[reserve],                       // Check whether the reserve is registered
            "Unknown reserve."
        );
        
        if(sell_token == address(0)){                       // Caller wants to swap ETH
            require(
                msg.value == sell_amount,                   // Check whether the caller sent the required ETH
                "ETH amount not sent with the call."
            );
        }else{                                              // Caller wants to swap a token
            require(
                msg.value == 0,                             // Check the caller hasn't sent any ETH with the call
                "Don't send ETH when swapping a token."
            );
            
            Token(sell_token).transferFrom(msg.sender, reserve, sell_amount);   // Deposit ERC20 into the reserve
            
            require(
                checkERC20TransferSuccess(),                // Check whether the ERC20 token transfer was successful
                "ERC20 token transfer failed."
            );
        }
        
        // Execute the swap with the reserve
        uint256 output = dexBlueReserve(reserve).swap.value(msg.value)(
            sell_token,
            sell_amount,
            buy_token,
            min_output
        );
        
        if(
            output >= min_output                                // Check whether the output amount is sufficient 
            && balances[buy_token][reserve] >= output           // Check whether the reserve deposited the output amount
        ){
            balances[buy_token][reserve] -= output;             // Substract the amount from the reserves balance
            
            if(buy_token == address(0)){                        // Is the bought asset ETH
                require(
                    msg.sender.send(output),                    // Send the output ETH of the swap to msg.sender
                    "Sending of ETH failed."
                );
            }else{
                Token(buy_token).transfer(msg.sender, output);  // Transfer the output token of the swap msg.sender
                require(                                        // Revert if the transfer failed
                    checkERC20TransferSuccess(),
                    "ERC20 token transfer failed."
                );
            }

            emit LogSwap(sell_token, sell_amount, buy_token, output);
            
            return output;
        }else{
            revert("Too much slippage.");
        }
    }
}
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
        
    // Parse Orders from packed input
    uint256 i = orderInput.length;
    Order[] memory orders = new Order[](i);
    while(i-- != 0){
        orders[i] = orderFromInput(orderInput[i]);
    }
        
    // Parse RingTrades from packed input
    i = tradeInput.length;
    RingTrade[] memory trades = new RingTrade[](i);
    while(i-- != 0){
        trades[i] = ringTradeFromInput(tradeInput[i]);
    }
        
    uint256 prev = trades.length - 1;
    uint256 next = 1;
     // Loop through the RingTrades array and settle each participants trade
    for(i = 0; i < trades.length; i++){
            
        require(
            // Check if the charged fee is not too high
            trades[i].fee       <= trades[prev].giveAmount / 20
                
            // Check if maker_rebate is smaller than or equal to the taker's fee which compensates it
            && trades[i].rebate <= trades[next].fee
        );
            
        if(trades[i].isReserve){ // ring element is a reserve
            address reserve = reserves[trades[i].identifier];

            if(i == 0){
                require(
                    dexBlueReserve(reserve).offerWithData(
                        trades[i].giveToken,                                   // The token the reserve would sell
                        trades[i].giveAmount - trades[i].rebate,               // The amount the reserve would sell
                        trades[prev].giveToken,                                // The token the reserve would receive
                        trades[prev].giveAmount - trades[i].fee,               // The amount the reserve would receive
                        data[i]                                                // The data to pass along to the reserve
                    )
                    && balances[trades[i].giveToken][reserve] >= trades[i].giveAmount
                );
            }else{
                uint256 receiveAmount = trades[prev].giveAmount - trades[i].fee;

                if(trades[prev].giveToken != address(0)){
                    Token(trades[prev].giveToken).transfer(reserve, receiveAmount);  // Send collateral to reserve
                    require(                                                         // Revert if the send failed
                        checkERC20TransferSuccess(),
                        "ERC20 token transfer failed."
                    );
                }

                require(
                    dexBlueReserve(reserve).tradeWithData.value(
                        trades[prev].giveToken == address(0) ? receiveAmount : 0
                    )(             
                        trades[prev].giveToken,
                        receiveAmount,                                      // Reserve gets the reserve_buy_amount minus the fee
                        trades[i].giveToken,    
                        trades[i].giveAmount - trades[i].rebate,            // Reserve has to give reserve_sell_amount minus the reserve rebate
                        data[i]                                             // The data to pass along to the reserve
                    )
                );
            }

            // Substract deposited amount from reserves balance
            balances[trades[i].giveToken][reserve] -= trades[i].giveAmount - trades[i].rebate;

            emit LogDirectWithdrawal(reserve, trades[prev].giveToken, trades[prev].giveAmount - trades[i].fee);
        }else{ // Ring element is an order
                
            Order memory order = orders[trades[i].identifier];  // Cache order

            uint256 orderMatched = matched[order.hash];
                
            require(
                // Does the order signee want to trade the last elements giveToken and this elements giveToken
                   order.buyToken  == trades[prev].giveToken
                && order.sellToken == trades[i].giveToken
                    
                // Is the order still valid
                && order.expiry > block.timestamp
                    
                // Does the order signee hold the required balances
                && balances[order.sellToken][order.signee] >= trades[i].giveAmount - trades[i].rebate
                    
                // Is the order matched at a rate better or equal to the one the order signee signed
                && trades[i].giveAmount - trades[i].rebate <= order.sellAmount * trades[prev].giveAmount / order.buyAmount + 1  // Check order doesn't overpay (+ 1 to deal with rouding errors for very smal amounts)
                    
                // Check if the order was cancelled
                && order.sellAmount > orderMatched
                    
                // Do the matched amount + previously matched trades not exceed the amount specified by the order signee
                && trades[i].giveAmount - trades[i].rebate + orderMatched <= order.sellAmount
            );
                
            // Substract the sold amounts
            balances[order.sellToken       ][order.signee] -= trades[i].giveAmount - trades[i].rebate;      // Substract sold amount minus the makers rebate from order signees balance
                
            // Add bought amounts
            balances[trades[prev].giveToken][order.signee] += trades[prev].giveAmount - trades[i].fee;      // Give the order signee his bought amount minus the fee
                
            // Save sold amounts to prevent double matching
            matched[order.hash] += trades[i].giveAmount - trades[i].rebate;                                 // Prevent order from being reused
                
            // Set potential previous blocking of these funds to 0
            blocked_for_single_sig_withdrawal[order.sellToken][order.signee] = 0;                           // If the order signee tried to block funds which he/she used in this order we have to unblock them
        }

        emit LogTrade(trades[prev].giveToken, trades[prev].giveAmount, trades[i].giveToken, trades[i].giveAmount);
            
        // Give fee to feeCollector
        balances[trades[prev].giveToken][feeCollector] += trades[i].fee - trades[prev].rebate;              // Give the feeColletor the fee minus the maker rebate 
            
        prev = i;
        if(i == trades.length - 2){
            next = 0;
        }else{
            next = i + 2;
        }
    }

    if(trades[0].isReserve){
        address payable reserve = reserves[trades[0].identifier];
        prev = trades.length - 1;
            
        if(trades[prev].giveToken == address(0)){                                                       // Is the withdrawal token ETH
            require(
                reserve.send(trades[prev].giveAmount - trades[0].fee),                                  // Withdraw ETH
                "Sending of ETH failed."
            );
        }else{
            Token(trades[prev].giveToken).transfer(reserve, trades[prev].giveAmount - trades[0].fee);   // Withdraw ERC20
            require(                                                                                    // Revert if the withdrawal failed
                checkERC20TransferSuccess(),
                "ERC20 token transfer failed."
            );
        }

        // Notify the reserve, that the offer got executed
        dexBlueReserve(reserve).offerExecuted(
            trades[0].giveToken,                                   // The token the reserve sold
            trades[0].giveAmount - trades[0].rebate,               // The amount the reserve sold
            trades[prev].giveToken,                                // The token the reserve received
            trades[prev].giveAmount - trades[0].fee                // The amount the reserve received
        );
    }
}

/** @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
 */