ETH Price: $2,639.68 (+3.42%)

Transaction Decoder

Block:
7721933 at May-08-2019 07:33:33 PM +UTC
Transaction Fee:
0.0004650408 ETH $1.23
Gas Used:
73,816 Gas / 6.3 Gwei

Emitted Events:

106 EidooToken.Transfer( from=[Sender] 0x83cd41970635d7e4aa0cb493cd7a5ed925826882, to=[Receiver] WalletV2, value=100000000000000000000 )
107 WalletV2.LogDeposit( token=EidooToken, amount=100000000000000000000, balance=100000000000000000000 )
108 ExchangeV2.LogWalletDeposit( walletAddress=[Receiver] WalletV2, token=EidooToken, amount=100000000000000000000, balance=100000000000000000000 )

Account State Difference:

  Address   Before After State Difference Code
0x342653de...9d69a74bb
(Spark Pool)
3,594.299397026337073253 Eth3,594.299862067137073253 Eth0.0004650408
0x83CD4197...925826882
15.1356479581 Eth
Nonce: 48
15.1351829173 Eth
Nonce: 49
0.0004650408
0xCeD4E931...D49eb388E

Execution Trace

WalletV2.depositERC20Token( _token=0xCeD4E93198734dDaFf8492d525Bd258D49eb388E, _amount=100000000000000000000 ) => ( True )
  • WalletLogic3.deposit( _token=0xCeD4E93198734dDaFf8492d525Bd258D49eb388E, _amount=100000000000000000000 ) => ( True )
    • EidooToken.transferFrom( _from=0x83CD41970635d7e4aA0cb493cD7a5eD925826882, _to=0x342653deeFD59701DB595AD9F4104b39d69a74bb, _value=100000000000000000000 ) => ( True )
    • ExchangeV2.walletDeposit( tokenAddress=0xCeD4E93198734dDaFf8492d525Bd258D49eb388E, amount=100000000000000000000, tradingWalletBalance=100000000000000000000 )
      File 1 of 4: WalletV2
      /**
       *Submitted for verification at Etherscan.io on 2019-01-15
      */
      
      pragma solidity ^0.4.24;
      
      /**
       * @title Log Various Error Types
       * @author Adam Lemmon <[email protected]>
       * @dev Inherit this contract and your may now log errors easily
       * To support various error types, params, etc.
       */
      contract LoggingErrors {
        /**
        * Events
        */
        event LogErrorString(string errorString);
      
        /**
        * Error cases
        */
      
        /**
         * @dev Default error to simply log the error message and return
         * @param _errorMessage The error message to log
         * @return ALWAYS false
         */
        function error(string _errorMessage) internal returns(bool) {
          LogErrorString(_errorMessage);
          return false;
        }
      }
      
      /**
       * @title Wallet Connector
       * @dev Connect the wallet contract to the correct Wallet Logic version
       */
      contract WalletConnector is LoggingErrors {
        /**
         * Storage
         */
        address public owner_;
        address public latestLogic_;
        uint256 public latestVersion_;
        mapping(uint256 => address) public logicVersions_;
        uint256 public birthBlock_;
      
        /**
         * Events
         */
        event LogLogicVersionAdded(uint256 version);
        event LogLogicVersionRemoved(uint256 version);
      
        /**
         * @dev Constructor to set the latest logic address
         * @param _latestVersion Latest version of the wallet logic
         * @param _latestLogic Latest address of the wallet logic contract
         */
        function WalletConnector (
          uint256 _latestVersion,
          address _latestLogic
        ) public {
          owner_ = msg.sender;
          latestLogic_ = _latestLogic;
          latestVersion_ = _latestVersion;
          logicVersions_[_latestVersion] = _latestLogic;
          birthBlock_ = block.number;
        }
      
        /**
         * Add a new version of the logic contract
         * @param _version The version to be associated with the new contract.
         * @param _logic New logic contract.
         * @return Success of the transaction.
         */
        function addLogicVersion (
          uint256 _version,
          address _logic
        ) external
          returns(bool)
        {
          if (msg.sender != owner_)
            return error('msg.sender != owner, WalletConnector.addLogicVersion()');
      
          if (logicVersions_[_version] != 0)
            return error('Version already exists, WalletConnector.addLogicVersion()');
      
          // Update latest if this is the latest version
          if (_version > latestVersion_) {
            latestLogic_ = _logic;
            latestVersion_ = _version;
          }
      
          logicVersions_[_version] = _logic;
          LogLogicVersionAdded(_version);
      
          return true;
        }
      
        /**
         * @dev Remove a version. Cannot remove the latest version.
         * @param  _version The version to remove.
         */
        function removeLogicVersion(uint256 _version) external {
          require(msg.sender == owner_);
          require(_version != latestVersion_);
          delete logicVersions_[_version];
          LogLogicVersionRemoved(_version);
        }
      
        /**
         * Constants
         */
      
        /**
         * Called from user wallets in order to upgrade their logic.
         * @param _version The version to upgrade to. NOTE pass in 0 to upgrade to latest.
         * @return The address of the logic contract to upgrade to.
         */
        function getLogic(uint256 _version)
          external
          constant
          returns(address)
        {
          if (_version == 0)
            return latestLogic_;
          else
            return logicVersions_[_version];
        }
      }
      
      /**
       * @title Wallet to hold and trade ERC20 tokens and ether
       * @author Adam Lemmon <[email protected]>
       * @dev User wallet to interact with the exchange.
       * all tokens and ether held in this wallet, 1 to 1 mapping to user EOAs.
       */
      contract WalletV2 is LoggingErrors {
        /**
         * Storage
         */
        // Vars included in wallet logic "lib", the order must match between Wallet and Logic
        address public owner_;
        address public exchange_;
        mapping(address => uint256) public tokenBalances_;
      
        address public logic_; // storage location 0x3 loaded for delegatecalls so this var must remain at index 3
        uint256 public birthBlock_;
      
        WalletConnector private connector_;
      
        /**
         * Events
         */
        event LogDeposit(address token, uint256 amount, uint256 balance);
        event LogWithdrawal(address token, uint256 amount, uint256 balance);
      
        /**
         * @dev Contract constructor. Set user as owner and connector address.
         * @param _owner The address of the user's EOA, wallets created from the exchange
         * so must past in the owner address, msg.sender == exchange.
         * @param _connector The wallet connector to be used to retrieve the wallet logic
         */
        function WalletV2(address _owner, address _connector) public {
          owner_ = _owner;
          connector_ = WalletConnector(_connector);
          exchange_ = msg.sender;
          logic_ = connector_.latestLogic_();
          birthBlock_ = block.number;
        }
      
        /**
         * @dev Fallback - Only enable funds to be sent from the exchange.
         * Ensures balances will be consistent.
         */
        function () external payable {
          require(msg.sender == exchange_);
        }
      
        /**
        * External
        */
      
        /**
         * @dev Deposit ether into this wallet, default to address 0 for consistent token lookup.
         */
        function depositEther()
          external
          payable
        {
          require(logic_.delegatecall(bytes4(sha3('deposit(address,uint256)')), 0, msg.value));
        }
      
        /**
         * @dev Deposit any ERC20 token into this wallet.
         * @param _token The address of the existing token contract.
         * @param _amount The amount of tokens to deposit.
         * @return Bool if the deposit was successful.
         */
        function depositERC20Token (
          address _token,
          uint256 _amount
        ) external
          returns(bool)
        {
          // ether
          if (_token == 0)
            return error('Cannot deposit ether via depositERC20, Wallet.depositERC20Token()');
      
          require(logic_.delegatecall(bytes4(sha3('deposit(address,uint256)')), _token, _amount));
          return true;
        }
      
        /**
         * @dev The result of an order, update the balance of this wallet.
         * @param _token The address of the token balance to update.
         * @param _amount The amount to update the balance by.
         * @param _subtractionFlag If true then subtract the token amount else add.
         * @return Bool if the update was successful.
         */
        function updateBalance (
          address _token,
          uint256 _amount,
          bool _subtractionFlag
        ) external
          returns(bool)
        {
          assembly {
            calldatacopy(0x40, 0, calldatasize)
            delegatecall(gas, sload(0x3), 0x40, calldatasize, 0, 32)
            return(0, 32)
            pop
          }
        }
      
        /**
         * User may update to the latest version of the exchange contract.
         * Note that multiple versions are NOT supported at this time and therefore if a
         * user does not wish to update they will no longer be able to use the exchange.
         * @param _exchange The new exchange.
         * @return Success of this transaction.
         */
        function updateExchange(address _exchange)
          external
          returns(bool)
        {
          if (msg.sender != owner_)
            return error('msg.sender != owner_, Wallet.updateExchange()');
      
          // If subsequent messages are not sent from this address all orders will fail
          exchange_ = _exchange;
      
          return true;
        }
      
        /**
         * User may update to a new or older version of the logic contract.
         * @param _version The versin to update to.
         * @return Success of this transaction.
         */
        function updateLogic(uint256 _version)
          external
          returns(bool)
        {
          if (msg.sender != owner_)
            return error('msg.sender != owner_, Wallet.updateLogic()');
      
          address newVersion = connector_.getLogic(_version);
      
          // Invalid version as defined by connector
          if (newVersion == 0)
            return error('Invalid version, Wallet.updateLogic()');
      
          logic_ = newVersion;
          return true;
        }
      
        /**
         * @dev Verify an order that the Exchange has received involving this wallet.
         * Internal checks and then authorize the exchange to move the tokens.
         * If sending ether will transfer to the exchange to broker the trade.
         * @param _token The address of the token contract being sold.
         * @param _amount The amount of tokens the order is for.
         * @param _fee The fee for the current trade.
         * @param _feeToken The token of which the fee is to be paid in.
         * @return If the order was verified or not.
         */
        function verifyOrder (
          address _token,
          uint256 _amount,
          uint256 _fee,
          address _feeToken
        ) external
          returns(bool)
        {
          assembly {
            calldatacopy(0x40, 0, calldatasize)
            delegatecall(gas, sload(0x3), 0x40, calldatasize, 0, 32)
            return(0, 32)
            pop
          }
        }
      
        /**
         * @dev Withdraw any token, including ether from this wallet to an EOA.
         * @param _token The address of the token to withdraw.
         * @param _amount The amount to withdraw.
         * @return Success of the withdrawal.
         */
        function withdraw(address _token, uint256 _amount)
          external
          returns(bool)
        {
          if(msg.sender != owner_)
            return error('msg.sender != owner, Wallet.withdraw()');
      
          assembly {
            calldatacopy(0x40, 0, calldatasize)
            delegatecall(gas, sload(0x3), 0x40, calldatasize, 0, 32)
            return(0, 32)
            pop
          }
        }
      
        /**
         * Constants
         */
      
        /**
         * @dev Get the balance for a specific token.
         * @param _token The address of the token contract to retrieve the balance of.
         * @return The current balance within this contract.
         */
        function balanceOf(address _token)
          public
          view
          returns(uint)
        {
          return tokenBalances_[_token];
        }
      }
      
      /**
       * @title SafeMath
       * @dev Math operations with safety checks that throw on error
       */
      library SafeMath {
        function mul(uint256 a, uint256 b) internal constant returns (uint256) {
          uint256 c = a * b;
          assert(a == 0 || c / a == b);
          return c;
        }
      
        function div(uint256 a, uint256 b) internal constant returns (uint256) {
          // assert(b > 0); // Solidity automatically throws when dividing by 0
          uint256 c = a / b;
          // assert(a == b * c + a % b); // There is no case in which this doesn't hold
          return c;
        }
      
        function sub(uint256 a, uint256 b) internal constant returns (uint256) {
          assert(b <= a);
          return a - b;
        }
      
        function add(uint256 a, uint256 b) internal constant returns (uint256) {
          uint256 c = a + b;
          assert(c >= a);
          return c;
        }
      }
      
      contract Token {
        /// @return total amount of tokens
        function totalSupply() constant returns (uint256 supply) {}
      
        /// @param _owner The address from which the balance will be retrieved
        /// @return The balance
        function balanceOf(address _owner) constant returns (uint256 balance) {}
      
        /// @notice send `_value` token to `_to` from `msg.sender`
        /// @param _to The address of the recipient
        /// @param _value The amount of token to be transferred
        /// @return Whether the transfer was successful or not
        function transfer(address _to, uint256 _value) returns (bool success) {}
      
        /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
        /// @param _from The address of the sender
        /// @param _to The address of the recipient
        /// @param _value The amount of token to be transferred
        /// @return Whether the transfer was successful or not
        function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {}
      
        /// @notice `msg.sender` approves `_addr` to spend `_value` tokens
        /// @param _spender The address of the account able to transfer the tokens
        /// @param _value The amount of wei to be approved for transfer
        /// @return Whether the approval was successful or not
        function approve(address _spender, uint256 _value) returns (bool success) {}
      
        /// @param _owner The address of the account owning tokens
        /// @param _spender The address of the account able to transfer the tokens
        /// @return Amount of remaining tokens allowed to spent
        function allowance(address _owner, address _spender) constant returns (uint256 remaining) {}
      
        event Transfer(address indexed _from, address indexed _to, uint256 _value);
        event Approval(address indexed _owner, address indexed _spender, uint256 _value);
      
        uint public decimals;
        string public name;
      }
      
      interface ExchangeV1 {
        function userAccountToWallet_(address) external returns(address);
      }
      
      interface BadERC20 {
        function transfer(address to, uint value) external;
        function transferFrom(address from, address to, uint256 value) external;
      }
      
      /**
       * @title Decentralized exchange for ether and ERC20 tokens.
       * @author Eidoo SAGL.
       * @dev All trades brokered by this contract.
       * Orders submitted by off chain order book and this contract handles
       * verification and execution of orders.
       * All value between parties is transferred via this exchange.
       * Methods arranged by visibility; external, public, internal, private and alphabatized within.
       *
       * New Exchange SC with eventually no fee and ERC20 tokens as quote
       */
      contract ExchangeV2 is LoggingErrors {
      
        using SafeMath for uint256;
      
        /**
         * Data Structures
         */
        struct Order {
          address offerToken_;
          uint256 offerTokenTotal_;
          uint256 offerTokenRemaining_;  // Amount left to give
          address wantToken_;
          uint256 wantTokenTotal_;
          uint256 wantTokenReceived_;  // Amount received, note this may exceed want total
        }
      
        struct OrderStatus {
          uint256 expirationBlock_;
          uint256 wantTokenReceived_;    // Amount received, note this may exceed want total
          uint256 offerTokenRemaining_;  // Amount left to give
        }
      
        /**
         * Storage
         */
        address public previousExchangeAddress_;
        address private orderBookAccount_;
        address public owner_;
        uint256 public birthBlock_;
        address public edoToken_;
        address public walletConnector;
      
        mapping (address => uint256) public feeEdoPerQuote;
        mapping (address => uint256) public feeEdoPerQuoteDecimals;
      
        address public eidooWallet_;
      
        // Define if fee calculation must be skipped for a given trade. By default (false) fee must not be skipped.
        mapping(address => mapping(address => bool)) public mustSkipFee;
      
        /**
         * @dev Define in a trade who is the quote using a priority system:
         * values example
         *   0: not used as quote
         *  >0: used as quote
         *  if wanted and offered tokens have value > 0 the quote is the token with the bigger value
         */
        mapping(address => uint256) public quotePriority;
      
        mapping(bytes32 => OrderStatus) public orders_; // Map order hashes to order data struct
        mapping(address => address) public userAccountToWallet_; // User EOA to wallet addresses
      
        /**
         * Events
         */
        event LogFeeRateSet(address indexed token, uint256 rate, uint256 decimals);
        event LogQuotePrioritySet(address indexed quoteToken, uint256 priority);
        event LogMustSkipFeeSet(address indexed base, address indexed quote, bool mustSkipFee);
        event LogUserAdded(address indexed user, address walletAddress);
        event LogWalletDeposit(address indexed walletAddress, address token, uint256 amount, uint256 balance);
        event LogWalletWithdrawal(address indexed walletAddress, address token, uint256 amount, uint256 balance);
      
        event LogOrderExecutionSuccess(
          bytes32 indexed makerOrderId,
          bytes32 indexed takerOrderId,
          uint256 toMaker,
          uint256 toTaker
        );
        event LogOrderFilled(bytes32 indexed orderId, uint256 totalOfferRemaining, uint256 totalWantReceived);
      
        /**
         * @dev Contract constructor - CONFIRM matches contract name.  Set owner and addr of order book.
         * @param _bookAccount The EOA address for the order book, will submit ALL orders.
         * @param _edoToken Deployed edo token.
         * @param _edoPerWei Rate of edo tokens per wei.
         * @param _edoPerWeiDecimals Decimlas carried in edo rate.
         * @param _eidooWallet Wallet to pay fees to.
         * @param _previousExchangeAddress Previous exchange smart contract address.
         */
        constructor (
          address _bookAccount,
          address _edoToken,
          uint256 _edoPerWei,
          uint256 _edoPerWeiDecimals,
          address _eidooWallet,
          address _previousExchangeAddress,
          address _walletConnector
        ) public {
          orderBookAccount_ = _bookAccount;
          owner_ = msg.sender;
          birthBlock_ = block.number;
          edoToken_ = _edoToken;
          feeEdoPerQuote[address(0)] = _edoPerWei;
          feeEdoPerQuoteDecimals[address(0)] = _edoPerWeiDecimals;
          eidooWallet_ = _eidooWallet;
          quotePriority[address(0)] = 10;
          previousExchangeAddress_ = _previousExchangeAddress;
          require(_walletConnector != address (0), "WalletConnector address == 0");
          walletConnector = _walletConnector;
        }
      
        /**
         * @dev Fallback. wallets utilize to send ether in order to broker trade.
         */
        function () external payable { }
      
        /**
         * External
         */
      
        /**
         * @dev Returns the Wallet contract address associated to a user account. If the user account is not known, try to
         * migrate the wallet address from the old exchange instance. This function is equivalent to getWallet(), in addition
         * it stores the wallet address fetched from old the exchange instance.
         * @param userAccount The user account address
         * @return The address of the Wallet instance associated to the user account
         */
        function retrieveWallet(address userAccount)
          public
          returns(address walletAddress)
        {
          walletAddress = userAccountToWallet_[userAccount];
          if (walletAddress == address(0) && previousExchangeAddress_ != 0) {
            // Retrieve the wallet address from the old exchange.
            walletAddress = ExchangeV1(previousExchangeAddress_).userAccountToWallet_(userAccount);
            // TODO: in the future versions of the exchange the above line must be replaced with the following one
            //walletAddress = ExchangeV2(previousExchangeAddress_).retrieveWallet(userAccount);
      
            if (walletAddress != address(0)) {
              userAccountToWallet_[userAccount] = walletAddress;
            }
          }
        }
      
        /**
         * @dev Add a new user to the exchange, create a wallet for them.
         * Map their account address to the wallet contract for lookup.
         * @param userExternalOwnedAccount The address of the user"s EOA.
         * @return Success of the transaction, false if error condition met.
         */
        function addNewUser(address userExternalOwnedAccount)
          public
          returns (bool)
        {
          if (retrieveWallet(userExternalOwnedAccount) != address(0)) {
            return error("User already exists, Exchange.addNewUser()");
          }
      
          // Pass the userAccount address to wallet constructor so owner is not the exchange contract
          address userTradingWallet = new WalletV2(userExternalOwnedAccount, walletConnector);
          userAccountToWallet_[userExternalOwnedAccount] = userTradingWallet;
          emit LogUserAdded(userExternalOwnedAccount, userTradingWallet);
          return true;
        }
      
        /**
         * Execute orders in batches.
         * @param ownedExternalAddressesAndTokenAddresses Tokan and user addresses.
         * @param amountsExpirationsAndSalts Offer and want token amount and expiration and salt values.
         * @param vSignatures All order signature v values.
         * @param rAndSsignatures All order signature r and r values.
         * @return The success of this transaction.
         */
        function batchExecuteOrder(
          address[4][] ownedExternalAddressesAndTokenAddresses,
          uint256[8][] amountsExpirationsAndSalts, // Packing to save stack size
          uint8[2][] vSignatures,
          bytes32[4][] rAndSsignatures
        ) external
          returns(bool)
        {
          for (uint256 i = 0; i < amountsExpirationsAndSalts.length; i++) {
            require(
              executeOrder(
                ownedExternalAddressesAndTokenAddresses[i],
                amountsExpirationsAndSalts[i],
                vSignatures[i],
                rAndSsignatures[i]
              ),
              "Cannot execute order, Exchange.batchExecuteOrder()"
            );
          }
      
          return true;
        }
      
        /**
         * @dev Execute an order that was submitted by the external order book server.
         * The order book server believes it to be a match.
         * There are components for both orders, maker and taker, 2 signatures as well.
         * @param ownedExternalAddressesAndTokenAddresses The maker and taker external owned accounts addresses and offered tokens contracts.
         * [
         *   makerEOA
         *   makerOfferToken
         *   takerEOA
         *   takerOfferToken
         * ]
         * @param amountsExpirationsAndSalts The amount of tokens and the block number at which this order expires and a random number to mitigate replay.
         * [
         *   makerOffer
         *   makerWant
         *   takerOffer
         *   takerWant
         *   makerExpiry
         *   makerSalt
         *   takerExpiry
         *   takerSalt
         * ]
         * @param vSignatures ECDSA signature parameter.
         * [
         *   maker V
         *   taker V
         * ]
         * @param rAndSsignatures ECDSA signature parameters r ans s, maker 0, 1 and taker 2, 3.
         * [
         *   maker R
         *   maker S
         *   taker R
         *   taker S
         * ]
         * @return Success of the transaction, false if error condition met.
         * Like types grouped to eliminate stack depth error.
         */
        function executeOrder (
          address[4] ownedExternalAddressesAndTokenAddresses,
          uint256[8] amountsExpirationsAndSalts, // Packing to save stack size
          uint8[2] vSignatures,
          bytes32[4] rAndSsignatures
        ) public
          returns(bool)
        {
          // Only read wallet addresses from storage once
          // Need one more stack slot so squashing into array
          WalletV2[2] memory makerAndTakerTradingWallets = [
            WalletV2(retrieveWallet(ownedExternalAddressesAndTokenAddresses[0])), // maker
            WalletV2(retrieveWallet(ownedExternalAddressesAndTokenAddresses[2])) // taker
          ];
      
          // Basic pre-conditions, return if any input data is invalid
          if(!__executeOrderInputIsValid__(
            ownedExternalAddressesAndTokenAddresses,
            amountsExpirationsAndSalts,
            makerAndTakerTradingWallets[0], // maker
            makerAndTakerTradingWallets[1] // taker
          )) {
            return error("Input is invalid, Exchange.executeOrder()");
          }
      
          // Verify Maker and Taker signatures
          bytes32[2] memory makerAndTakerOrderHash = generateOrderHashes(
            ownedExternalAddressesAndTokenAddresses,
            amountsExpirationsAndSalts
          );
      
          // Check maker order signature
          if (!__signatureIsValid__(
            ownedExternalAddressesAndTokenAddresses[0],
            makerAndTakerOrderHash[0],
            vSignatures[0],
            rAndSsignatures[0],
            rAndSsignatures[1]
          )) {
            return error("Maker signature is invalid, Exchange.executeOrder()");
          }
      
          // Check taker order signature
          if (!__signatureIsValid__(
            ownedExternalAddressesAndTokenAddresses[2],
            makerAndTakerOrderHash[1],
            vSignatures[1],
            rAndSsignatures[2],
            rAndSsignatures[3]
          )) {
            return error("Taker signature is invalid, Exchange.executeOrder()");
          }
      
          // Exchange Order Verification and matching
          OrderStatus memory makerOrderStatus = orders_[makerAndTakerOrderHash[0]];
          OrderStatus memory takerOrderStatus = orders_[makerAndTakerOrderHash[1]];
          Order memory makerOrder;
          Order memory takerOrder;
      
          makerOrder.offerToken_ = ownedExternalAddressesAndTokenAddresses[1];
          makerOrder.offerTokenTotal_ = amountsExpirationsAndSalts[0];
          makerOrder.wantToken_ = ownedExternalAddressesAndTokenAddresses[3];
          makerOrder.wantTokenTotal_ = amountsExpirationsAndSalts[1];
      
          if (makerOrderStatus.expirationBlock_ > 0) {  // Check for existence
            // Orders still active
            if (makerOrderStatus.offerTokenRemaining_ == 0) {
              return error("Maker order is inactive, Exchange.executeOrder()");
            }
            makerOrder.offerTokenRemaining_ = makerOrderStatus.offerTokenRemaining_; // Amount to give
            makerOrder.wantTokenReceived_ = makerOrderStatus.wantTokenReceived_; // Amount received
          } else {
            makerOrder.offerTokenRemaining_ = amountsExpirationsAndSalts[0]; // Amount to give
            makerOrder.wantTokenReceived_ = 0; // Amount received
            makerOrderStatus.expirationBlock_ = amountsExpirationsAndSalts[4]; // maker order expiration block
          }
      
          takerOrder.offerToken_ = ownedExternalAddressesAndTokenAddresses[3];
          takerOrder.offerTokenTotal_ = amountsExpirationsAndSalts[2];
          takerOrder.wantToken_ = ownedExternalAddressesAndTokenAddresses[1];
          takerOrder.wantTokenTotal_ = amountsExpirationsAndSalts[3];
      
          if (takerOrderStatus.expirationBlock_ > 0) {  // Check for existence
            if (takerOrderStatus.offerTokenRemaining_ == 0) {
              return error("Taker order is inactive, Exchange.executeOrder()");
            }
            takerOrder.offerTokenRemaining_ = takerOrderStatus.offerTokenRemaining_;  // Amount to give
            takerOrder.wantTokenReceived_ = takerOrderStatus.wantTokenReceived_; // Amount received
      
          } else {
            takerOrder.offerTokenRemaining_ = amountsExpirationsAndSalts[2];  // Amount to give
            takerOrder.wantTokenReceived_ = 0; // Amount received
            takerOrderStatus.expirationBlock_ = amountsExpirationsAndSalts[6]; // taker order expiration block
          }
      
          // Check if orders are matching and are valid
          if (!__ordersMatch_and_AreVaild__(makerOrder, takerOrder)) {
            return error("Orders do not match, Exchange.executeOrder()");
          }
      
          // Trade amounts
          // [0] => toTakerAmount
          // [1] => toMakerAmount
          uint[2] memory toTakerAndToMakerAmount;
          toTakerAndToMakerAmount = __getTradeAmounts__(makerOrder, takerOrder);
      
          // TODO consider removing. Can this condition be met?
          if (toTakerAndToMakerAmount[0] < 1 || toTakerAndToMakerAmount[1] < 1) {
            return error("Token amount < 1, price ratio is invalid! Token value < 1, Exchange.executeOrder()");
          }
      
          uint calculatedFee = __calculateFee__(makerOrder, toTakerAndToMakerAmount[0], toTakerAndToMakerAmount[1]);
      
          // Check taker has sufficent EDO token balance to pay the fee
          if (
            takerOrder.offerToken_ == edoToken_ &&
            Token(edoToken_).balanceOf(makerAndTakerTradingWallets[1]) < calculatedFee.add(toTakerAndToMakerAmount[1])
          ) {
            return error("Taker has an insufficient EDO token balance to cover the fee AND the offer, Exchange.executeOrder()");
          } else if (Token(edoToken_).balanceOf(makerAndTakerTradingWallets[1]) < calculatedFee) {
            return error("Taker has an insufficient EDO token balance to cover the fee, Exchange.executeOrder()");
          }
      
          // Wallet Order Verification, reach out to the maker and taker wallets.
          if (
            !__ordersVerifiedByWallets__(
              ownedExternalAddressesAndTokenAddresses,
              toTakerAndToMakerAmount[1],
              toTakerAndToMakerAmount[0],
              makerAndTakerTradingWallets[0],
              makerAndTakerTradingWallets[1],
              calculatedFee
          )) {
            return error("Order could not be verified by wallets, Exchange.executeOrder()");
          }
      
          // Write to storage then external calls
          makerOrderStatus.offerTokenRemaining_ = makerOrder.offerTokenRemaining_.sub(toTakerAndToMakerAmount[0]);
          makerOrderStatus.wantTokenReceived_ = makerOrder.wantTokenReceived_.add(toTakerAndToMakerAmount[1]);
      
          takerOrderStatus.offerTokenRemaining_ = takerOrder.offerTokenRemaining_.sub(toTakerAndToMakerAmount[1]);
          takerOrderStatus.wantTokenReceived_ = takerOrder.wantTokenReceived_.add(toTakerAndToMakerAmount[0]);
      
          // Finally write orders to storage
          orders_[makerAndTakerOrderHash[0]] = makerOrderStatus;
          orders_[makerAndTakerOrderHash[1]] = takerOrderStatus;
      
          // Transfer the external value, ether <> tokens
          require(
            __executeTokenTransfer__(
              ownedExternalAddressesAndTokenAddresses,
              toTakerAndToMakerAmount[0],
              toTakerAndToMakerAmount[1],
              calculatedFee,
              makerAndTakerTradingWallets[0],
              makerAndTakerTradingWallets[1]
            ),
            "Cannot execute token transfer, Exchange.__executeTokenTransfer__()"
          );
      
          // Log the order id(hash), amount of offer given, amount of offer remaining
          emit LogOrderFilled(makerAndTakerOrderHash[0], makerOrderStatus.offerTokenRemaining_, makerOrderStatus.wantTokenReceived_);
          emit LogOrderFilled(makerAndTakerOrderHash[1], takerOrderStatus.offerTokenRemaining_, takerOrderStatus.wantTokenReceived_);
          emit LogOrderExecutionSuccess(makerAndTakerOrderHash[0], makerAndTakerOrderHash[1], toTakerAndToMakerAmount[1], toTakerAndToMakerAmount[0]);
      
          return true;
        }
      
        /**
         * @dev Set the fee rate for a specific quote
         * @param _quoteToken Quote token.
         * @param _edoPerQuote EdoPerQuote.
         * @param _edoPerQuoteDecimals EdoPerQuoteDecimals.
         * @return Success of the transaction.
         */
        function setFeeRate(
          address _quoteToken,
          uint256 _edoPerQuote,
          uint256 _edoPerQuoteDecimals
        ) external
          returns(bool)
        {
          if (msg.sender != owner_) {
            return error("msg.sender != owner, Exchange.setFeeRate()");
          }
      
          if (quotePriority[_quoteToken] == 0) {
            return error("quotePriority[_quoteToken] == 0, Exchange.setFeeRate()");
          }
      
          feeEdoPerQuote[_quoteToken] = _edoPerQuote;
          feeEdoPerQuoteDecimals[_quoteToken] = _edoPerQuoteDecimals;
      
          emit LogFeeRateSet(_quoteToken, _edoPerQuote, _edoPerQuoteDecimals);
      
          return true;
        }
      
        /**
         * @dev Set the wallet for fees to be paid to.
         * @param eidooWallet Wallet to pay fees to.
         * @return Success of the transaction.
         */
        function setEidooWallet(
          address eidooWallet
        ) external
          returns(bool)
        {
          if (msg.sender != owner_) {
            return error("msg.sender != owner, Exchange.setEidooWallet()");
          }
          eidooWallet_ = eidooWallet;
          return true;
        }
      
        /**
         * @dev Set a new order book account.
         * @param account The new order book account.
         */
        function setOrderBookAcount (
          address account
        ) external
          returns(bool)
        {
          if (msg.sender != owner_) {
            return error("msg.sender != owner, Exchange.setOrderBookAcount()");
          }
          orderBookAccount_ = account;
          return true;
        }
      
        /**
         * @dev Set if a base must skip fee calculation.
         * @param _baseTokenAddress The trade base token address that must skip fee calculation.
         * @param _quoteTokenAddress The trade quote token address that must skip fee calculation.
         * @param _mustSkipFee The trade base token address that must skip fee calculation.
         */
        function setMustSkipFee (
          address _baseTokenAddress,
          address _quoteTokenAddress,
          bool _mustSkipFee
        ) external
          returns(bool)
        {
          // Preserving same owner check style
          if (msg.sender != owner_) {
            return error("msg.sender != owner, Exchange.setMustSkipFee()");
          }
          mustSkipFee[_baseTokenAddress][_quoteTokenAddress] = _mustSkipFee;
          emit LogMustSkipFeeSet(_baseTokenAddress, _quoteTokenAddress, _mustSkipFee);
          return true;
        }
      
        /**
         * @dev Set quote priority token.
         * Set the sorting of token quote based on a priority.
         * @param _token The address of the token that was deposited.
         * @param _priority The amount of the token that was deposited.
         * @return Operation success.
         */
      
        function setQuotePriority(address _token, uint256 _priority)
          external
          returns(bool)
        {
          if (msg.sender != owner_) {
            return error("msg.sender != owner, Exchange.setQuotePriority()");
          }
          quotePriority[_token] = _priority;
          emit LogQuotePrioritySet(_token, _priority);
          return true;
        }
      
        /*
         Methods to catch events from external contracts, user wallets primarily
         */
      
        /**
         * @dev Simply log the event to track wallet interaction off-chain.
         * @param tokenAddress The address of the token that was deposited.
         * @param amount The amount of the token that was deposited.
         * @param tradingWalletBalance The updated balance of the wallet after deposit.
         */
        function walletDeposit(
          address tokenAddress,
          uint256 amount,
          uint256 tradingWalletBalance
        ) external
        {
          emit LogWalletDeposit(msg.sender, tokenAddress, amount, tradingWalletBalance);
        }
      
        /**
         * @dev Simply log the event to track wallet interaction off-chain.
         * @param tokenAddress The address of the token that was deposited.
         * @param amount The amount of the token that was deposited.
         * @param tradingWalletBalance The updated balance of the wallet after deposit.
         */
        function walletWithdrawal(
          address tokenAddress,
          uint256 amount,
          uint256 tradingWalletBalance
        ) external
        {
          emit LogWalletWithdrawal(msg.sender, tokenAddress, amount, tradingWalletBalance);
        }
      
        /**
         * Private
         */
      
        /**
         * Calculate the fee for the given trade. Calculated as the set % of the wei amount
         * converted into EDO tokens using the manually set conversion ratio.
         * @param makerOrder The maker order object.
         * @param toTakerAmount The amount of tokens going to the taker.
         * @param toMakerAmount The amount of tokens going to the maker.
         * @return The total fee to be paid in EDO tokens.
         */
        function __calculateFee__(
          Order makerOrder,
          uint256 toTakerAmount,
          uint256 toMakerAmount
        ) private
          view
          returns(uint256)
        {
          // weiAmount * (fee %) * (EDO/Wei) / (decimals in edo/wei) / (decimals in percentage)
          if (!__isSell__(makerOrder)) {
            // buy -> the quote is the offered token by the maker
            return mustSkipFee[makerOrder.wantToken_][makerOrder.offerToken_]
              ? 0
              : toTakerAmount.mul(feeEdoPerQuote[makerOrder.offerToken_]).div(10**feeEdoPerQuoteDecimals[makerOrder.offerToken_]);
          } else {
            // sell -> the quote is the wanted token by the maker
            return mustSkipFee[makerOrder.offerToken_][makerOrder.wantToken_]
              ? 0
              : toMakerAmount.mul(feeEdoPerQuote[makerOrder.wantToken_]).div(10**feeEdoPerQuoteDecimals[makerOrder.wantToken_]);
          }
        }
      
        /**
         * @dev Verify the input to order execution is valid.
         * @param ownedExternalAddressesAndTokenAddresses The maker and taker external owned accounts addresses and offered tokens contracts.
         * [
         *   makerEOA
         *   makerOfferToken
         *   takerEOA
         *   takerOfferToken
         * ]
         * @param amountsExpirationsAndSalts The amount of tokens and the block number at which this order expires and a random number to mitigate replay.
         * [
         *   makerOffer
         *   makerWant
         *   takerOffer
         *   takerWant
         *   makerExpiry
         *   makerSalt
         *   takerExpiry
         *   takerSalt
         * ]
         * @return Success if all checks pass.
         */
        function __executeOrderInputIsValid__(
          address[4] ownedExternalAddressesAndTokenAddresses,
          uint256[8] amountsExpirationsAndSalts,
          address makerTradingWallet,
          address takerTradingWallet
        ) private
          returns(bool)
        {
          // msg.send needs to be the orderBookAccount
          if (msg.sender != orderBookAccount_) {
            return error("msg.sender != orderBookAccount, Exchange.__executeOrderInputIsValid__()");
          }
      
          // Check expirations base on the block number
          if (block.number > amountsExpirationsAndSalts[4]) {
            return error("Maker order has expired, Exchange.__executeOrderInputIsValid__()");
          }
      
          if (block.number > amountsExpirationsAndSalts[6]) {
            return error("Taker order has expired, Exchange.__executeOrderInputIsValid__()");
          }
      
          // Operating on existing tradingWallets
          if (makerTradingWallet == address(0)) {
            return error("Maker wallet does not exist, Exchange.__executeOrderInputIsValid__()");
          }
      
          if (takerTradingWallet == address(0)) {
            return error("Taker wallet does not exist, Exchange.__executeOrderInputIsValid__()");
          }
      
          if (quotePriority[ownedExternalAddressesAndTokenAddresses[1]] == quotePriority[ownedExternalAddressesAndTokenAddresses[3]]) {
            return error("Quote token is omitted! Is not offered by either the Taker or Maker, Exchange.__executeOrderInputIsValid__()");
          }
      
          // Check that none of the amounts is = to 0
          if (
              amountsExpirationsAndSalts[0] == 0 ||
              amountsExpirationsAndSalts[1] == 0 ||
              amountsExpirationsAndSalts[2] == 0 ||
              amountsExpirationsAndSalts[3] == 0
            )
            return error("May not execute an order where token amount == 0, Exchange.__executeOrderInputIsValid__()");
      
          // // Confirm order ether amount >= min amount
          //  // Maker
          //  uint256 minOrderEthAmount = minOrderEthAmount_; // Single storage read
          //  if (_token_and_EOA_Addresses[1] == 0 && _amountsExpirationAndSalt[0] < minOrderEthAmount)
          //    return error('Maker order does not meet the minOrderEthAmount_ of ether, Exchange.__executeOrderInputIsValid__()');
      
          //  // Taker
          //  if (_token_and_EOA_Addresses[3] == 0 && _amountsExpirationAndSalt[2] < minOrderEthAmount)
          //    return error('Taker order does not meet the minOrderEthAmount_ of ether, Exchange.__executeOrderInputIsValid__()');
      
          return true;
        }
      
        /**
         * @dev Execute the external transfer of tokens.
         * @param ownedExternalAddressesAndTokenAddresses The maker and taker external owned accounts addresses and offered tokens contracts.
         * [
         *   makerEOA
         *   makerOfferToken
         *   takerEOA
         *   takerOfferToken
         * ]
         * @param toTakerAmount The amount of tokens to transfer to the taker.
         * @param toMakerAmount The amount of tokens to transfer to the maker.
         * @return Success if both wallets verify the order.
         */
        function __executeTokenTransfer__(
          address[4] ownedExternalAddressesAndTokenAddresses,
          uint256 toTakerAmount,
          uint256 toMakerAmount,
          uint256 fee,
          WalletV2 makerTradingWallet,
          WalletV2 takerTradingWallet
        ) private
          returns (bool)
        {
          // Wallet mapping balances
          address makerOfferTokenAddress = ownedExternalAddressesAndTokenAddresses[1];
          address takerOfferTokenAddress = ownedExternalAddressesAndTokenAddresses[3];
      
          // Taker to pay fee before trading
          if(fee != 0) {
            require(
              takerTradingWallet.updateBalance(edoToken_, fee, true),
              "Taker trading wallet cannot update balance with fee, Exchange.__executeTokenTransfer__()"
            );
      
            require(
              Token(edoToken_).transferFrom(takerTradingWallet, eidooWallet_, fee),
              "Cannot transfer fees from taker trading wallet to eidoo wallet, Exchange.__executeTokenTransfer__()"
            );
          }
      
          // Updating makerTradingWallet balance by the toTaker
          require(
            makerTradingWallet.updateBalance(makerOfferTokenAddress, toTakerAmount, true),
            "Maker trading wallet cannot update balance subtracting toTakerAmount, Exchange.__executeTokenTransfer__()"
          ); // return error("Unable to subtract maker token from maker wallet, Exchange.__executeTokenTransfer__()");
      
          // Updating takerTradingWallet balance by the toTaker
          require(
            takerTradingWallet.updateBalance(makerOfferTokenAddress, toTakerAmount, false),
            "Taker trading wallet cannot update balance adding toTakerAmount, Exchange.__executeTokenTransfer__()"
          ); // return error("Unable to add maker token to taker wallet, Exchange.__executeTokenTransfer__()");
      
          // Updating takerTradingWallet balance by the toMaker amount
          require(
            takerTradingWallet.updateBalance(takerOfferTokenAddress, toMakerAmount, true),
            "Taker trading wallet cannot update balance subtracting toMakerAmount, Exchange.__executeTokenTransfer__()"
          ); // return error("Unable to subtract taker token from taker wallet, Exchange.__executeTokenTransfer__()");
      
          // Updating makerTradingWallet balance by the toMaker amount
          require(
            makerTradingWallet.updateBalance(takerOfferTokenAddress, toMakerAmount, false),
            "Maker trading wallet cannot update balance adding toMakerAmount, Exchange.__executeTokenTransfer__()"
          ); // return error("Unable to add taker token to maker wallet, Exchange.__executeTokenTransfer__()");
      
          // Ether to the taker and tokens to the maker
          if (makerOfferTokenAddress == address(0)) {
            address(takerTradingWallet).transfer(toTakerAmount);
          } else {
            require(
              safeTransferFrom(makerOfferTokenAddress, makerTradingWallet, takerTradingWallet, toTakerAmount),
              "Token transfership from makerTradingWallet to takerTradingWallet failed, Exchange.__executeTokenTransfer__()"
            );
            assert(
              __tokenAndWalletBalancesMatch__(
                makerTradingWallet,
                takerTradingWallet,
                makerOfferTokenAddress
              )
            );
          }
      
          if (takerOfferTokenAddress == address(0)) {
            address(makerTradingWallet).transfer(toMakerAmount);
          } else {
            require(
              safeTransferFrom(takerOfferTokenAddress, takerTradingWallet, makerTradingWallet, toMakerAmount),
              "Token transfership from takerTradingWallet to makerTradingWallet failed, Exchange.__executeTokenTransfer__()"
            );
            assert(
              __tokenAndWalletBalancesMatch__(
                makerTradingWallet,
                takerTradingWallet,
                takerOfferTokenAddress
              )
            );
          }
      
          return true;
        }
      
        /**
         * @dev Calculates Keccak-256 hash of order with specified parameters.
         * @param ownedExternalAddressesAndTokenAddresses The orders maker EOA and current exchange address.
         * @param amountsExpirationsAndSalts The orders offer and want amounts and expirations with salts.
         * @return Keccak-256 hash of the passed order.
         */
      
        function generateOrderHashes(
          address[4] ownedExternalAddressesAndTokenAddresses,
          uint256[8] amountsExpirationsAndSalts
        ) public
          view
          returns (bytes32[2])
        {
          bytes32 makerOrderHash = keccak256(
            address(this),
            ownedExternalAddressesAndTokenAddresses[0], // _makerEOA
            ownedExternalAddressesAndTokenAddresses[1], // offerToken
            amountsExpirationsAndSalts[0],  // offerTokenAmount
            ownedExternalAddressesAndTokenAddresses[3], // wantToken
            amountsExpirationsAndSalts[1],  // wantTokenAmount
            amountsExpirationsAndSalts[4], // expiry
            amountsExpirationsAndSalts[5] // salt
          );
      
          bytes32 takerOrderHash = keccak256(
            address(this),
            ownedExternalAddressesAndTokenAddresses[2], // _makerEOA
            ownedExternalAddressesAndTokenAddresses[3], // offerToken
            amountsExpirationsAndSalts[2],  // offerTokenAmount
            ownedExternalAddressesAndTokenAddresses[1], // wantToken
            amountsExpirationsAndSalts[3],  // wantTokenAmount
            amountsExpirationsAndSalts[6], // expiry
            amountsExpirationsAndSalts[7] // salt
          );
      
          return [makerOrderHash, takerOrderHash];
        }
      
        /**
         * @dev Returns a bool representing a SELL or BUY order based on quotePriority.
         * @param _order The maker order data structure.
         * @return The bool indicating if the order is a SELL or BUY.
         */
        function __isSell__(Order _order) internal view returns (bool) {
          return quotePriority[_order.offerToken_] < quotePriority[_order.wantToken_];
        }
      
        /**
         * @dev Compute the tradeable amounts of the two verified orders.
         * Token amount is the __min__ remaining between want and offer of the two orders that isn"t ether.
         * Ether amount is then: etherAmount = tokenAmount * priceRatio, as ratio = eth / token.
         * @param makerOrder The maker order data structure.
         * @param takerOrder The taker order data structure.
         * @return The amount moving from makerOfferRemaining to takerWantRemaining and vice versa.
         */
        function __getTradeAmounts__(
          Order makerOrder,
          Order takerOrder
        ) internal
          view
          returns (uint256[2])
        {
          bool isMakerBuy = __isSell__(takerOrder);  // maker buy = taker sell
          uint256 priceRatio;
          uint256 makerAmountLeftToReceive;
          uint256 takerAmountLeftToReceive;
      
          uint toTakerAmount;
          uint toMakerAmount;
      
          if (makerOrder.offerTokenTotal_ >= makerOrder.wantTokenTotal_) {
            priceRatio = makerOrder.offerTokenTotal_.mul(2**128).div(makerOrder.wantTokenTotal_);
            if (isMakerBuy) {
              // MP > 1
              makerAmountLeftToReceive = makerOrder.wantTokenTotal_.sub(makerOrder.wantTokenReceived_);
              toMakerAmount = __min__(takerOrder.offerTokenRemaining_, makerAmountLeftToReceive);
              // add 2**128-1 in order to obtain a round up
              toTakerAmount = toMakerAmount.mul(priceRatio).add(2**128-1).div(2**128);
            } else {
              // MP < 1
              takerAmountLeftToReceive = takerOrder.wantTokenTotal_.sub(takerOrder.wantTokenReceived_);
              toTakerAmount = __min__(makerOrder.offerTokenRemaining_, takerAmountLeftToReceive);
              toMakerAmount = toTakerAmount.mul(2**128).div(priceRatio);
            }
          } else {
            priceRatio = makerOrder.wantTokenTotal_.mul(2**128).div(makerOrder.offerTokenTotal_);
            if (isMakerBuy) {
              // MP < 1
              makerAmountLeftToReceive = makerOrder.wantTokenTotal_.sub(makerOrder.wantTokenReceived_);
              toMakerAmount = __min__(takerOrder.offerTokenRemaining_, makerAmountLeftToReceive);
              toTakerAmount = toMakerAmount.mul(2**128).div(priceRatio);
            } else {
              // MP > 1
              takerAmountLeftToReceive = takerOrder.wantTokenTotal_.sub(takerOrder.wantTokenReceived_);
              toTakerAmount = __min__(makerOrder.offerTokenRemaining_, takerAmountLeftToReceive);
              // add 2**128-1 in order to obtain a round up
              toMakerAmount = toTakerAmount.mul(priceRatio).add(2**128-1).div(2**128);
            }
          }
          return [toTakerAmount, toMakerAmount];
        }
      
        /**
         * @dev Return the maximum of two uints
         * @param a Uint 1
         * @param b Uint 2
         * @return The grater value or a if equal
         */
        function __max__(uint256 a, uint256 b)
          private
          pure
          returns (uint256)
        {
          return a < b
            ? b
            : a;
        }
      
        /**
         * @dev Return the minimum of two uints
         * @param a Uint 1
         * @param b Uint 2
         * @return The smallest value or b if equal
         */
        function __min__(uint256 a, uint256 b)
          private
          pure
          returns (uint256)
        {
          return a < b
            ? a
            : b;
        }
      
        /**
         * @dev Confirm that the orders do match and are valid.
         * @param makerOrder The maker order data structure.
         * @param takerOrder The taker order data structure.
         * @return Bool if the orders passes all checks.
         */
        function __ordersMatch_and_AreVaild__(
          Order makerOrder,
          Order takerOrder
        ) private
          returns (bool)
        {
          // Confirm tokens match
          // NOTE potentially omit as matching handled upstream?
          if (makerOrder.wantToken_ != takerOrder.offerToken_) {
            return error("Maker wanted token does not match taker offer token, Exchange.__ordersMatch_and_AreVaild__()");
          }
      
          if (makerOrder.offerToken_ != takerOrder.wantToken_) {
            return error("Maker offer token does not match taker wanted token, Exchange.__ordersMatch_and_AreVaild__()");
          }
      
          // Price Ratios, to x decimal places hence * decimals, dependent on the size of the denominator.
          // Ratios are relative to eth, amount of ether for a single token, ie. ETH / GNO == 0.2 Ether per 1 Gnosis
      
          uint256 orderPrice;   // The price the maker is willing to accept
          uint256 offeredPrice; // The offer the taker has given
      
          // Ratio = larger amount / smaller amount
          if (makerOrder.offerTokenTotal_ >= makerOrder.wantTokenTotal_) {
            orderPrice = makerOrder.offerTokenTotal_.mul(2**128).div(makerOrder.wantTokenTotal_);
            offeredPrice = takerOrder.wantTokenTotal_.mul(2**128).div(takerOrder.offerTokenTotal_);
      
            // ie. Maker is offering 10 ETH for 100 GNO but taker is offering 100 GNO for 20 ETH, no match!
            // The taker wants more ether than the maker is offering.
            if (orderPrice < offeredPrice) {
              return error("Taker price is greater than maker price, Exchange.__ordersMatch_and_AreVaild__()");
            }
          } else {
            orderPrice = makerOrder.wantTokenTotal_.mul(2**128).div(makerOrder.offerTokenTotal_);
            offeredPrice = takerOrder.offerTokenTotal_.mul(2**128).div(takerOrder.wantTokenTotal_);
      
            // ie. Maker is offering 100 GNO for 10 ETH but taker is offering 5 ETH for 100 GNO, no match!
            // The taker is not offering enough ether for the maker
            if (orderPrice > offeredPrice) {
              return error("Taker price is less than maker price, Exchange.__ordersMatch_and_AreVaild__()");
            }
          }
      
          return true;
        }
      
        /**
         * @dev Ask each wallet to verify this order.
         * @param ownedExternalAddressesAndTokenAddresses The maker and taker external owned accounts addresses and offered tokens contracts.
         * [
         *   makerEOA
         *   makerOfferToken
         *   takerEOA
         *   takerOfferToken
         * ]
         * @param toMakerAmount The amount of tokens to be sent to the maker.
         * @param toTakerAmount The amount of tokens to be sent to the taker.
         * @param makerTradingWallet The maker trading wallet contract.
         * @param takerTradingWallet The taker trading wallet contract.
         * @param fee The fee to be paid for this trade, paid in full by taker.
         * @return Success if both wallets verify the order.
         */
        function __ordersVerifiedByWallets__(
          address[4] ownedExternalAddressesAndTokenAddresses,
          uint256 toMakerAmount,
          uint256 toTakerAmount,
          WalletV2 makerTradingWallet,
          WalletV2 takerTradingWallet,
          uint256 fee
        ) private
          returns (bool)
        {
          // Have the transaction verified by both maker and taker wallets
          // confirm sufficient balance to transfer, offerToken and offerTokenAmount
          if(!makerTradingWallet.verifyOrder(ownedExternalAddressesAndTokenAddresses[1], toTakerAmount, 0, 0)) {
            return error("Maker wallet could not verify the order, Exchange.____ordersVerifiedByWallets____()");
          }
      
          if(!takerTradingWallet.verifyOrder(ownedExternalAddressesAndTokenAddresses[3], toMakerAmount, fee, edoToken_)) {
            return error("Taker wallet could not verify the order, Exchange.____ordersVerifiedByWallets____()");
          }
      
          return true;
        }
      
        /**
         * @dev On chain verification of an ECDSA ethereum signature.
         * @param signer The EOA address of the account that supposedly signed the message.
         * @param orderHash The on-chain generated hash for the order.
         * @param v ECDSA signature parameter v.
         * @param r ECDSA signature parameter r.
         * @param s ECDSA signature parameter s.
         * @return Bool if the signature is valid or not.
         */
        function __signatureIsValid__(
          address signer,
          bytes32 orderHash,
          uint8 v,
          bytes32 r,
          bytes32 s
        ) private
          pure
          returns (bool)
        {
          address recoveredAddr = ecrecover(
            keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", orderHash)),
            v,
            r,
            s
          );
      
          return recoveredAddr == signer;
        }
      
        /**
         * @dev Confirm wallet local balances and token balances match.
         * @param makerTradingWallet  Maker wallet address.
         * @param takerTradingWallet  Taker wallet address.
         * @param token  Token address to confirm balances match.
         * @return If the balances do match.
         */
        function __tokenAndWalletBalancesMatch__(
          address makerTradingWallet,
          address takerTradingWallet,
          address token
        ) private
          view
          returns(bool)
        {
          if (Token(token).balanceOf(makerTradingWallet) != WalletV2(makerTradingWallet).balanceOf(token)) {
            return false;
          }
      
          if (Token(token).balanceOf(takerTradingWallet) != WalletV2(takerTradingWallet).balanceOf(token)) {
            return false;
          }
      
          return true;
        }
      
        /**
         * @dev Wrapping the ERC20 transfer function to avoid missing returns.
         * @param _token The address of bad formed ERC20 token.
         * @param _from Transfer sender.
         * @param _to Transfer receiver.
         * @param _value Amount to be transfered.
         * @return Success of the safeTransfer.
         */
        function safeTransferFrom(
          address _token,
          address _from,
          address _to,
          uint256 _value
        )
          public
          returns (bool result)
        {
          BadERC20(_token).transferFrom(_from, _to, _value);
      
          assembly {
            switch returndatasize()
            case 0 {                      // This is our BadToken
              result := not(0)            // result is true
            }
            case 32 {                     // This is our GoodToken
              returndatacopy(0, 0, 32)
              result := mload(0)          // result == returndata of external call
            }
            default {                     // This is not an ERC20 token
              revert(0, 0)
            }
          }
        }
      }

      File 2 of 4: EidooToken
      pragma solidity ^0.4.13;
      
      /**
       * @title Ownable
       * @dev The Ownable contract has an owner address, and provides basic authorization control
       * functions, this simplifies the implementation of "user permissions".
       */
      contract Ownable {
        address public owner;
      
      
        /**
         * @dev The Ownable constructor sets the original `owner` of the contract to the sender
         * account.
         */
        function Ownable() {
          owner = msg.sender;
        }
      
      
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
          require(msg.sender == owner);
          _;
        }
      
      
        /**
         * @dev Allows the current owner to transfer control of the contract to a newOwner.
         * @param newOwner The address to transfer ownership to.
         */
        function transferOwnership(address newOwner) onlyOwner {
          if (newOwner != address(0)) {
            owner = newOwner;
          }
        }
      
      }
      
      /**
       * @title SafeMath
       * @dev Math operations with safety checks that throw on error
       */
      library SafeMath {
        function mul(uint256 a, uint256 b) internal constant returns (uint256) {
          uint256 c = a * b;
          assert(a == 0 || c / a == b);
          return c;
        }
      
        function div(uint256 a, uint256 b) internal constant returns (uint256) {
          // assert(b > 0); // Solidity automatically throws when dividing by 0
          uint256 c = a / b;
          // assert(a == b * c + a % b); // There is no case in which this doesn't hold
          return c;
        }
      
        function sub(uint256 a, uint256 b) internal constant returns (uint256) {
          assert(b <= a);
          return a - b;
        }
      
        function add(uint256 a, uint256 b) internal constant returns (uint256) {
          uint256 c = a + b;
          assert(c >= a);
          return c;
        }
      }
      
      contract MintableInterface {
        function mint(address _to, uint256 _amount) returns (bool);
        function mintLocked(address _to, uint256 _amount) returns (bool);
      }
      
      /**
       * This is the Crowdsale contract from OpenZeppelin version 1.2.0
       * The only changes are:
       *   - the type of token field is changed from MintableToken to MintableInterface
       *   - the createTokenContract() method is removed, the token field must be initialized in the derived contracts constuctor
       **/
      
      
      
      
      
      
      /**
       * @title Crowdsale 
       * @dev Crowdsale is a base contract for managing a token crowdsale.
       * Crowdsales have a start and end block, where investors can make
       * token purchases and the crowdsale will assign them tokens based
       * on a token per ETH rate. Funds collected are forwarded to a wallet 
       * as they arrive.
       */
      contract Crowdsale {
        using SafeMath for uint256;
      
        // The token being sold
        MintableInterface public token;
      
        // start and end block where investments are allowed (both inclusive)
        uint256 public startBlock;
        uint256 public endBlock;
      
        // address where funds are collected
        address public wallet;
      
        // how many token units a buyer gets per wei
        uint256 public rate;
      
        // amount of raised money in wei
        uint256 public weiRaised;
      
        /**
         * event for token purchase logging
         * @param purchaser who paid for the tokens
         * @param beneficiary who got the tokens
         * @param value weis paid for purchase
         * @param amount amount of tokens purchased
         */ 
        event TokenPurchase(address indexed purchaser, address indexed beneficiary, uint256 value, uint256 amount);
      
      
        function Crowdsale(uint256 _startBlock, uint256 _endBlock, uint256 _rate, address _wallet) {
          require(_startBlock >= block.number);
          require(_endBlock >= _startBlock);
          require(_rate > 0);
          require(_wallet != 0x0);
      
          startBlock = _startBlock;
          endBlock = _endBlock;
          rate = _rate;
          wallet = _wallet;
        }
      
        // fallback function can be used to buy tokens
        function () payable {
          buyTokens(msg.sender);
        }
      
        // low level token purchase function
        function buyTokens(address beneficiary) payable {
          require(beneficiary != 0x0);
          require(validPurchase());
      
          uint256 weiAmount = msg.value;
      
          // calculate token amount to be created
          uint256 tokens = weiAmount.mul(rate);
      
          // update state
          weiRaised = weiRaised.add(weiAmount);
      
          token.mint(beneficiary, tokens);
          TokenPurchase(msg.sender, beneficiary, weiAmount, tokens);
      
          forwardFunds();
        }
      
        // send ether to the fund collection wallet
        // override to create custom fund forwarding mechanisms
        function forwardFunds() internal {
          wallet.transfer(msg.value);
        }
      
        // @return true if the transaction can buy tokens
        function validPurchase() internal constant returns (bool) {
          uint256 current = block.number;
          bool withinPeriod = current >= startBlock && current <= endBlock;
          bool nonZeroPurchase = msg.value != 0;
          return withinPeriod && nonZeroPurchase;
        }
      
        // @return true if crowdsale event has ended
        function hasEnded() public constant returns (bool) {
          return block.number > endBlock;
        }
      
      
      }
      
      /**
       * @title CappedCrowdsale
       * @dev Extension of Crowsdale with a max amount of funds raised
       */
      contract TokenCappedCrowdsale is Crowdsale {
        using SafeMath for uint256;
      
        // tokenCap should be initialized in derived contract
        uint256 public tokenCap;
      
        uint256 public soldTokens;
      
        // overriding Crowdsale#hasEnded to add tokenCap logic
        // @return true if crowdsale event has ended
        function hasEnded() public constant returns (bool) {
          bool capReached = soldTokens >= tokenCap;
          return super.hasEnded() || capReached;
        }
      
        // overriding Crowdsale#buyTokens to add extra tokenCap logic
        function buyTokens(address beneficiary) payable {
          // calculate token amount to be created
          uint256 tokens = msg.value.mul(rate);
          uint256 newTotalSold = soldTokens.add(tokens);
          require(newTotalSold <= tokenCap);
          soldTokens = newTotalSold;
          super.buyTokens(beneficiary);
        }
      }
      
      /**
       * @title ERC20Basic
       * @dev Simpler version of ERC20 interface
       * @dev see https://github.com/ethereum/EIPs/issues/179
       */
      contract ERC20Basic {
        uint256 public totalSupply;
        function balanceOf(address who) constant returns (uint256);
        function transfer(address to, uint256 value) returns (bool);
        event Transfer(address indexed from, address indexed to, uint256 value);
      }
      
      /**
       * This is the TokenTimelock contract from OpenZeppelin version 1.2.0
       * The only changes are:
       *   - all contract fields are declared as public
       *   - removed deprecated claim() method
       **/
      
      
      
      
      
      /**
       * @title TokenTimelock
       * @dev TokenTimelock is a token holder contract that will allow a 
       * beneficiary to extract the tokens after a given release time
       */
      contract TokenTimelock {
        
        // ERC20 basic token contract being held
        ERC20Basic public token;
      
        // beneficiary of tokens after they are released
        address public beneficiary;
      
        // timestamp when token release is enabled
        uint public releaseTime;
      
        function TokenTimelock(ERC20Basic _token, address _beneficiary, uint _releaseTime) {
          require(_releaseTime > now);
          token = _token;
          beneficiary = _beneficiary;
          releaseTime = _releaseTime;
        }
      
        /**
         * @notice Transfers tokens held by timelock to beneficiary.
         */
        function release() {
          require(now >= releaseTime);
      
          uint amount = token.balanceOf(this);
          require(amount > 0);
      
          token.transfer(beneficiary, amount);
        }
      }
      
      /**
       * @title Basic token
       * @dev Basic version of StandardToken, with no allowances. 
       */
      contract BasicToken is ERC20Basic {
        using SafeMath for uint256;
      
        mapping(address => uint256) balances;
      
        /**
        * @dev transfer token for a specified address
        * @param _to The address to transfer to.
        * @param _value The amount to be transferred.
        */
        function transfer(address _to, uint256 _value) returns (bool) {
          balances[msg.sender] = balances[msg.sender].sub(_value);
          balances[_to] = balances[_to].add(_value);
          Transfer(msg.sender, _to, _value);
          return true;
        }
      
        /**
        * @dev Gets the balance of the specified address.
        * @param _owner The address to query the the balance of. 
        * @return An uint256 representing the amount owned by the passed address.
        */
        function balanceOf(address _owner) constant returns (uint256 balance) {
          return balances[_owner];
        }
      
      }
      
      /**
       * @title ERC20 interface
       * @dev see https://github.com/ethereum/EIPs/issues/20
       */
      contract ERC20 is ERC20Basic {
        function allowance(address owner, address spender) constant returns (uint256);
        function transferFrom(address from, address to, uint256 value) returns (bool);
        function approve(address spender, uint256 value) returns (bool);
        event Approval(address indexed owner, address indexed spender, uint256 value);
      }
      
      /**
       * @title Standard ERC20 token
       *
       * @dev Implementation of the basic standard token.
       * @dev https://github.com/ethereum/EIPs/issues/20
       * @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
       */
      contract StandardToken is ERC20, BasicToken {
      
        mapping (address => mapping (address => uint256)) allowed;
      
      
        /**
         * @dev Transfer tokens from one address to another
         * @param _from address The address which you want to send tokens from
         * @param _to address The address which you want to transfer to
         * @param _value uint256 the amout of tokens to be transfered
         */
        function transferFrom(address _from, address _to, uint256 _value) returns (bool) {
          var _allowance = allowed[_from][msg.sender];
      
          // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
          // require (_value <= _allowance);
      
          balances[_to] = balances[_to].add(_value);
          balances[_from] = balances[_from].sub(_value);
          allowed[_from][msg.sender] = _allowance.sub(_value);
          Transfer(_from, _to, _value);
          return true;
        }
      
        /**
         * @dev Aprove the passed address to spend the specified amount of tokens on behalf of msg.sender.
         * @param _spender The address which will spend the funds.
         * @param _value The amount of tokens to be spent.
         */
        function approve(address _spender, uint256 _value) returns (bool) {
      
          // To change the approve amount you first have to reduce the addresses`
          //  allowance to zero by calling `approve(_spender, 0)` if it is not
          //  already 0 to mitigate the race condition described here:
          //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
          require((_value == 0) || (allowed[msg.sender][_spender] == 0));
      
          allowed[msg.sender][_spender] = _value;
          Approval(msg.sender, _spender, _value);
          return true;
        }
      
        /**
         * @dev Function to check the amount of tokens that an owner allowed to a spender.
         * @param _owner address The address which owns the funds.
         * @param _spender address The address which will spend the funds.
         * @return A uint256 specifing the amount of tokens still avaible for the spender.
         */
        function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
          return allowed[_owner][_spender];
        }
      
      }
      
      contract EidooToken is MintableInterface, Ownable, StandardToken {
        using SafeMath for uint256;
      
        string public name = "Eidoo Token";
        string public symbol = "EDO";
        uint256 public decimals = 18;
      
        uint256 public transferableFromBlock;
        uint256 public lockEndBlock;
        mapping (address => uint256) public initiallyLockedAmount;
      
        function EidooToken(uint256 _transferableFromBlock, uint256 _lockEndBlock) {
          require(_lockEndBlock > _transferableFromBlock);
          transferableFromBlock = _transferableFromBlock;
          lockEndBlock = _lockEndBlock;
        }
      
        modifier canTransfer(address _from, uint _value) {
          if (block.number < lockEndBlock) {
            require(block.number >= transferableFromBlock);
            uint256 locked = lockedBalanceOf(_from);
            if (locked > 0) {
              uint256 newBalance = balanceOf(_from).sub(_value);
              require(newBalance >= locked);
            }
          }
         _;
        }
      
        function lockedBalanceOf(address _to) constant returns(uint256) {
          uint256 locked = initiallyLockedAmount[_to];
          if (block.number >= lockEndBlock ) return 0;
          else if (block.number <= transferableFromBlock) return locked;
      
          uint256 releaseForBlock = locked.div(lockEndBlock.sub(transferableFromBlock));
          uint256 released = block.number.sub(transferableFromBlock).mul(releaseForBlock);
          return locked.sub(released);
        }
      
        function transfer(address _to, uint _value) canTransfer(msg.sender, _value) returns (bool) {
          return super.transfer(_to, _value);
        }
      
        function transferFrom(address _from, address _to, uint _value) canTransfer(_from, _value) returns (bool) {
          return super.transferFrom(_from, _to, _value);
        }
      
        // --------------- Minting methods
      
        modifier canMint() {
          require(!mintingFinished());
          _;
        }
      
        function mintingFinished() constant returns(bool) {
          return block.number >= transferableFromBlock;
        }
      
        /**
         * @dev Function to mint tokens, implements MintableInterface
         * @param _to The address that will recieve the minted tokens.
         * @param _amount The amount of tokens to mint.
         * @return A boolean that indicates if the operation was successful.
         */
        function mint(address _to, uint256 _amount) onlyOwner canMint returns (bool) {
          totalSupply = totalSupply.add(_amount);
          balances[_to] = balances[_to].add(_amount);
          Transfer(address(0), _to, _amount);
          return true;
        }
      
        function mintLocked(address _to, uint256 _amount) onlyOwner canMint returns (bool) {
          initiallyLockedAmount[_to] = initiallyLockedAmount[_to].add(_amount);
          return mint(_to, _amount);
        }
      
        function burn(uint256 _amount) returns (bool) {
          balances[msg.sender] = balances[msg.sender].sub(_amount);
          totalSupply = totalSupply.sub(_amount);
          Transfer(msg.sender, address(0), _amount);
          return true;
        }
      }
      
      contract EidooTokenSale is Ownable, TokenCappedCrowdsale {
        using SafeMath for uint256;
        uint256 public MAXIMUM_SUPPLY = 100000000 * 10**18;
        uint256 [] public LOCKED = [     20000000 * 10**18,
                                         15000000 * 10**18,
                                          6000000 * 10**18,
                                          6000000 * 10**18 ];
        uint256 public POST_ICO =        21000000 * 10**18;
        uint256 [] public LOCK_END = [
          1570190400, // 4 October 2019 12:00:00 GMT
          1538654400, // 4 October 2018 12:00:00 GMT
          1522843200, // 4 April 2018 12:00:00 GMT
          1515067200  // 4 January 2018 12:00:00 GMT
        ];
      
        mapping (address => bool) public claimed;
        TokenTimelock [4] public timeLocks;
      
        event ClaimTokens(address indexed to, uint amount);
      
        modifier beforeStart() {
          require(block.number < startBlock);
          _;
        }
      
        function EidooTokenSale(
          uint256 _startBlock,
          uint256 _endBlock,
          uint256 _rate,
          uint _tokenStartBlock,
          uint _tokenLockEndBlock,
          address _wallet
        )
          Crowdsale(_startBlock, _endBlock, _rate, _wallet)
        {
          token = new EidooToken(_tokenStartBlock, _tokenLockEndBlock);
      
          // create timelocks for tokens
          timeLocks[0] = new TokenTimelock(EidooToken(token), _wallet, LOCK_END[0]);
          timeLocks[1] = new TokenTimelock(EidooToken(token), _wallet, LOCK_END[1]);
          timeLocks[2] = new TokenTimelock(EidooToken(token), _wallet, LOCK_END[2]);
          timeLocks[3] = new TokenTimelock(EidooToken(token), _wallet, LOCK_END[3]);
          token.mint(address(timeLocks[0]), LOCKED[0]);
          token.mint(address(timeLocks[1]), LOCKED[1]);
          token.mint(address(timeLocks[2]), LOCKED[2]);
          token.mint(address(timeLocks[3]), LOCKED[3]);
      
          token.mint(_wallet, POST_ICO);
      
          // initialize maximum number of tokens that can be sold
          tokenCap = MAXIMUM_SUPPLY.sub(EidooToken(token).totalSupply());
        }
      
        function claimTokens(address [] buyers, uint [] amounts) onlyOwner beforeStart public {
          require(buyers.length == amounts.length);
          uint len = buyers.length;
          for (uint i = 0; i < len; i++) {
            address to = buyers[i];
            uint256 amount = amounts[i];
            if (amount > 0 && !claimed[to]) {
              claimed[to] = true;
              if (to == 0x32Be343B94f860124dC4fEe278FDCBD38C102D88) {
                // replace Poloniex Wallet address
                to = 0x2274bebe2b47Ec99D50BB9b12005c921F28B83bB;
              }
              tokenCap = tokenCap.sub(amount);
              uint256 unlockedAmount = amount.div(10).mul(3);
              token.mint(to, unlockedAmount);
              token.mintLocked(to, amount.sub(unlockedAmount));
              ClaimTokens(to, amount);
            }
          }
        }
      
      }

      File 3 of 4: ExchangeV2
      pragma solidity ^0.4.24;
      
      /**
       * @title Log Various Error Types
       * @author Adam Lemmon <[email protected]>
       * @dev Inherit this contract and your may now log errors easily
       * To support various error types, params, etc.
       */
      contract LoggingErrors {
        /**
        * Events
        */
        event LogErrorString(string errorString);
      
        /**
        * Error cases
        */
      
        /**
         * @dev Default error to simply log the error message and return
         * @param _errorMessage The error message to log
         * @return ALWAYS false
         */
        function error(string _errorMessage) internal returns(bool) {
          LogErrorString(_errorMessage);
          return false;
        }
      }
      
      /**
       * @title Wallet Connector
       * @dev Connect the wallet contract to the correct Wallet Logic version
       */
      contract WalletConnector is LoggingErrors {
        /**
         * Storage
         */
        address public owner_;
        address public latestLogic_;
        uint256 public latestVersion_;
        mapping(uint256 => address) public logicVersions_;
        uint256 public birthBlock_;
      
        /**
         * Events
         */
        event LogLogicVersionAdded(uint256 version);
        event LogLogicVersionRemoved(uint256 version);
      
        /**
         * @dev Constructor to set the latest logic address
         * @param _latestVersion Latest version of the wallet logic
         * @param _latestLogic Latest address of the wallet logic contract
         */
        function WalletConnector (
          uint256 _latestVersion,
          address _latestLogic
        ) public {
          owner_ = msg.sender;
          latestLogic_ = _latestLogic;
          latestVersion_ = _latestVersion;
          logicVersions_[_latestVersion] = _latestLogic;
          birthBlock_ = block.number;
        }
      
        /**
         * Add a new version of the logic contract
         * @param _version The version to be associated with the new contract.
         * @param _logic New logic contract.
         * @return Success of the transaction.
         */
        function addLogicVersion (
          uint256 _version,
          address _logic
        ) external
          returns(bool)
        {
          if (msg.sender != owner_)
            return error('msg.sender != owner, WalletConnector.addLogicVersion()');
      
          if (logicVersions_[_version] != 0)
            return error('Version already exists, WalletConnector.addLogicVersion()');
      
          // Update latest if this is the latest version
          if (_version > latestVersion_) {
            latestLogic_ = _logic;
            latestVersion_ = _version;
          }
      
          logicVersions_[_version] = _logic;
          LogLogicVersionAdded(_version);
      
          return true;
        }
      
        /**
         * @dev Remove a version. Cannot remove the latest version.
         * @param  _version The version to remove.
         */
        function removeLogicVersion(uint256 _version) external {
          require(msg.sender == owner_);
          require(_version != latestVersion_);
          delete logicVersions_[_version];
          LogLogicVersionRemoved(_version);
        }
      
        /**
         * Constants
         */
      
        /**
         * Called from user wallets in order to upgrade their logic.
         * @param _version The version to upgrade to. NOTE pass in 0 to upgrade to latest.
         * @return The address of the logic contract to upgrade to.
         */
        function getLogic(uint256 _version)
          external
          constant
          returns(address)
        {
          if (_version == 0)
            return latestLogic_;
          else
            return logicVersions_[_version];
        }
      }
      
      /**
       * @title Wallet to hold and trade ERC20 tokens and ether
       * @author Adam Lemmon <[email protected]>
       * @dev User wallet to interact with the exchange.
       * all tokens and ether held in this wallet, 1 to 1 mapping to user EOAs.
       */
      contract WalletV2 is LoggingErrors {
        /**
         * Storage
         */
        // Vars included in wallet logic "lib", the order must match between Wallet and Logic
        address public owner_;
        address public exchange_;
        mapping(address => uint256) public tokenBalances_;
      
        address public logic_; // storage location 0x3 loaded for delegatecalls so this var must remain at index 3
        uint256 public birthBlock_;
      
        WalletConnector private connector_;
      
        /**
         * Events
         */
        event LogDeposit(address token, uint256 amount, uint256 balance);
        event LogWithdrawal(address token, uint256 amount, uint256 balance);
      
        /**
         * @dev Contract constructor. Set user as owner and connector address.
         * @param _owner The address of the user's EOA, wallets created from the exchange
         * so must past in the owner address, msg.sender == exchange.
         * @param _connector The wallet connector to be used to retrieve the wallet logic
         */
        function WalletV2(address _owner, address _connector) public {
          owner_ = _owner;
          connector_ = WalletConnector(_connector);
          exchange_ = msg.sender;
          logic_ = connector_.latestLogic_();
          birthBlock_ = block.number;
        }
      
        /**
         * @dev Fallback - Only enable funds to be sent from the exchange.
         * Ensures balances will be consistent.
         */
        function () external payable {
          require(msg.sender == exchange_);
        }
      
        /**
        * External
        */
      
        /**
         * @dev Deposit ether into this wallet, default to address 0 for consistent token lookup.
         */
        function depositEther()
          external
          payable
        {
          require(logic_.delegatecall(bytes4(sha3('deposit(address,uint256)')), 0, msg.value));
        }
      
        /**
         * @dev Deposit any ERC20 token into this wallet.
         * @param _token The address of the existing token contract.
         * @param _amount The amount of tokens to deposit.
         * @return Bool if the deposit was successful.
         */
        function depositERC20Token (
          address _token,
          uint256 _amount
        ) external
          returns(bool)
        {
          // ether
          if (_token == 0)
            return error('Cannot deposit ether via depositERC20, Wallet.depositERC20Token()');
      
          require(logic_.delegatecall(bytes4(sha3('deposit(address,uint256)')), _token, _amount));
          return true;
        }
      
        /**
         * @dev The result of an order, update the balance of this wallet.
         * @param _token The address of the token balance to update.
         * @param _amount The amount to update the balance by.
         * @param _subtractionFlag If true then subtract the token amount else add.
         * @return Bool if the update was successful.
         */
        function updateBalance (
          address _token,
          uint256 _amount,
          bool _subtractionFlag
        ) external
          returns(bool)
        {
          assembly {
            calldatacopy(0x40, 0, calldatasize)
            delegatecall(gas, sload(0x3), 0x40, calldatasize, 0, 32)
            return(0, 32)
            pop
          }
        }
      
        /**
         * User may update to the latest version of the exchange contract.
         * Note that multiple versions are NOT supported at this time and therefore if a
         * user does not wish to update they will no longer be able to use the exchange.
         * @param _exchange The new exchange.
         * @return Success of this transaction.
         */
        function updateExchange(address _exchange)
          external
          returns(bool)
        {
          if (msg.sender != owner_)
            return error('msg.sender != owner_, Wallet.updateExchange()');
      
          // If subsequent messages are not sent from this address all orders will fail
          exchange_ = _exchange;
      
          return true;
        }
      
        /**
         * User may update to a new or older version of the logic contract.
         * @param _version The versin to update to.
         * @return Success of this transaction.
         */
        function updateLogic(uint256 _version)
          external
          returns(bool)
        {
          if (msg.sender != owner_)
            return error('msg.sender != owner_, Wallet.updateLogic()');
      
          address newVersion = connector_.getLogic(_version);
      
          // Invalid version as defined by connector
          if (newVersion == 0)
            return error('Invalid version, Wallet.updateLogic()');
      
          logic_ = newVersion;
          return true;
        }
      
        /**
         * @dev Verify an order that the Exchange has received involving this wallet.
         * Internal checks and then authorize the exchange to move the tokens.
         * If sending ether will transfer to the exchange to broker the trade.
         * @param _token The address of the token contract being sold.
         * @param _amount The amount of tokens the order is for.
         * @param _fee The fee for the current trade.
         * @param _feeToken The token of which the fee is to be paid in.
         * @return If the order was verified or not.
         */
        function verifyOrder (
          address _token,
          uint256 _amount,
          uint256 _fee,
          address _feeToken
        ) external
          returns(bool)
        {
          assembly {
            calldatacopy(0x40, 0, calldatasize)
            delegatecall(gas, sload(0x3), 0x40, calldatasize, 0, 32)
            return(0, 32)
            pop
          }
        }
      
        /**
         * @dev Withdraw any token, including ether from this wallet to an EOA.
         * @param _token The address of the token to withdraw.
         * @param _amount The amount to withdraw.
         * @return Success of the withdrawal.
         */
        function withdraw(address _token, uint256 _amount)
          external
          returns(bool)
        {
          if(msg.sender != owner_)
            return error('msg.sender != owner, Wallet.withdraw()');
      
          assembly {
            calldatacopy(0x40, 0, calldatasize)
            delegatecall(gas, sload(0x3), 0x40, calldatasize, 0, 32)
            return(0, 32)
            pop
          }
        }
      
        /**
         * Constants
         */
      
        /**
         * @dev Get the balance for a specific token.
         * @param _token The address of the token contract to retrieve the balance of.
         * @return The current balance within this contract.
         */
        function balanceOf(address _token)
          public
          view
          returns(uint)
        {
          return tokenBalances_[_token];
        }
      }
      
      /**
       * @title SafeMath
       * @dev Math operations with safety checks that throw on error
       */
      library SafeMath {
        function mul(uint256 a, uint256 b) internal constant returns (uint256) {
          uint256 c = a * b;
          assert(a == 0 || c / a == b);
          return c;
        }
      
        function div(uint256 a, uint256 b) internal constant returns (uint256) {
          // assert(b > 0); // Solidity automatically throws when dividing by 0
          uint256 c = a / b;
          // assert(a == b * c + a % b); // There is no case in which this doesn't hold
          return c;
        }
      
        function sub(uint256 a, uint256 b) internal constant returns (uint256) {
          assert(b <= a);
          return a - b;
        }
      
        function add(uint256 a, uint256 b) internal constant returns (uint256) {
          uint256 c = a + b;
          assert(c >= a);
          return c;
        }
      }
      
      contract Token {
        /// @return total amount of tokens
        function totalSupply() constant returns (uint256 supply) {}
      
        /// @param _owner The address from which the balance will be retrieved
        /// @return The balance
        function balanceOf(address _owner) constant returns (uint256 balance) {}
      
        /// @notice send `_value` token to `_to` from `msg.sender`
        /// @param _to The address of the recipient
        /// @param _value The amount of token to be transferred
        /// @return Whether the transfer was successful or not
        function transfer(address _to, uint256 _value) returns (bool success) {}
      
        /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
        /// @param _from The address of the sender
        /// @param _to The address of the recipient
        /// @param _value The amount of token to be transferred
        /// @return Whether the transfer was successful or not
        function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {}
      
        /// @notice `msg.sender` approves `_addr` to spend `_value` tokens
        /// @param _spender The address of the account able to transfer the tokens
        /// @param _value The amount of wei to be approved for transfer
        /// @return Whether the approval was successful or not
        function approve(address _spender, uint256 _value) returns (bool success) {}
      
        /// @param _owner The address of the account owning tokens
        /// @param _spender The address of the account able to transfer the tokens
        /// @return Amount of remaining tokens allowed to spent
        function allowance(address _owner, address _spender) constant returns (uint256 remaining) {}
      
        event Transfer(address indexed _from, address indexed _to, uint256 _value);
        event Approval(address indexed _owner, address indexed _spender, uint256 _value);
      
        uint public decimals;
        string public name;
      }
      
      interface ExchangeV1 {
        function userAccountToWallet_(address) external returns(address);
      }
      
      interface BadERC20 {
        function transfer(address to, uint value) external;
        function transferFrom(address from, address to, uint256 value) external;
      }
      
      /**
       * @title Decentralized exchange for ether and ERC20 tokens.
       * @author Eidoo SAGL.
       * @dev All trades brokered by this contract.
       * Orders submitted by off chain order book and this contract handles
       * verification and execution of orders.
       * All value between parties is transferred via this exchange.
       * Methods arranged by visibility; external, public, internal, private and alphabatized within.
       *
       * New Exchange SC with eventually no fee and ERC20 tokens as quote
       */
      contract ExchangeV2 is LoggingErrors {
      
        using SafeMath for uint256;
      
        /**
         * Data Structures
         */
        struct Order {
          address offerToken_;
          uint256 offerTokenTotal_;
          uint256 offerTokenRemaining_;  // Amount left to give
          address wantToken_;
          uint256 wantTokenTotal_;
          uint256 wantTokenReceived_;  // Amount received, note this may exceed want total
        }
      
        struct OrderStatus {
          uint256 expirationBlock_;
          uint256 wantTokenReceived_;    // Amount received, note this may exceed want total
          uint256 offerTokenRemaining_;  // Amount left to give
        }
      
        /**
         * Storage
         */
        address public previousExchangeAddress_;
        address private orderBookAccount_;
        address public owner_;
        uint256 public birthBlock_;
        address public edoToken_;
        address public walletConnector;
      
        mapping (address => uint256) public feeEdoPerQuote;
        mapping (address => uint256) public feeEdoPerQuoteDecimals;
      
        address public eidooWallet_;
      
        // Define if fee calculation must be skipped for a given trade. By default (false) fee must not be skipped.
        mapping(address => mapping(address => bool)) public mustSkipFee;
      
        /**
         * @dev Define in a trade who is the quote using a priority system:
         * values example
         *   0: not used as quote
         *  >0: used as quote
         *  if wanted and offered tokens have value > 0 the quote is the token with the bigger value
         */
        mapping(address => uint256) public quotePriority;
      
        mapping(bytes32 => OrderStatus) public orders_; // Map order hashes to order data struct
        mapping(address => address) public userAccountToWallet_; // User EOA to wallet addresses
      
        /**
         * Events
         */
        event LogFeeRateSet(address indexed token, uint256 rate, uint256 decimals);
        event LogQuotePrioritySet(address indexed quoteToken, uint256 priority);
        event LogMustSkipFeeSet(address indexed base, address indexed quote, bool mustSkipFee);
        event LogUserAdded(address indexed user, address walletAddress);
        event LogWalletDeposit(address indexed walletAddress, address token, uint256 amount, uint256 balance);
        event LogWalletWithdrawal(address indexed walletAddress, address token, uint256 amount, uint256 balance);
      
        event LogOrderExecutionSuccess(
          bytes32 indexed makerOrderId,
          bytes32 indexed takerOrderId,
          uint256 toMaker,
          uint256 toTaker
        );
        event LogOrderFilled(bytes32 indexed orderId, uint256 totalOfferRemaining, uint256 totalWantReceived);
      
        /**
         * @dev Contract constructor - CONFIRM matches contract name.  Set owner and addr of order book.
         * @param _bookAccount The EOA address for the order book, will submit ALL orders.
         * @param _edoToken Deployed edo token.
         * @param _edoPerWei Rate of edo tokens per wei.
         * @param _edoPerWeiDecimals Decimlas carried in edo rate.
         * @param _eidooWallet Wallet to pay fees to.
         * @param _previousExchangeAddress Previous exchange smart contract address.
         */
        constructor (
          address _bookAccount,
          address _edoToken,
          uint256 _edoPerWei,
          uint256 _edoPerWeiDecimals,
          address _eidooWallet,
          address _previousExchangeAddress,
          address _walletConnector
        ) public {
          orderBookAccount_ = _bookAccount;
          owner_ = msg.sender;
          birthBlock_ = block.number;
          edoToken_ = _edoToken;
          feeEdoPerQuote[address(0)] = _edoPerWei;
          feeEdoPerQuoteDecimals[address(0)] = _edoPerWeiDecimals;
          eidooWallet_ = _eidooWallet;
          quotePriority[address(0)] = 10;
          previousExchangeAddress_ = _previousExchangeAddress;
          require(_walletConnector != address (0), "WalletConnector address == 0");
          walletConnector = _walletConnector;
        }
      
        /**
         * @dev Fallback. wallets utilize to send ether in order to broker trade.
         */
        function () external payable { }
      
        /**
         * External
         */
      
        /**
         * @dev Returns the Wallet contract address associated to a user account. If the user account is not known, try to
         * migrate the wallet address from the old exchange instance. This function is equivalent to getWallet(), in addition
         * it stores the wallet address fetched from old the exchange instance.
         * @param userAccount The user account address
         * @return The address of the Wallet instance associated to the user account
         */
        function retrieveWallet(address userAccount)
          public
          returns(address walletAddress)
        {
          walletAddress = userAccountToWallet_[userAccount];
          if (walletAddress == address(0) && previousExchangeAddress_ != 0) {
            // Retrieve the wallet address from the old exchange.
            walletAddress = ExchangeV1(previousExchangeAddress_).userAccountToWallet_(userAccount);
            // TODO: in the future versions of the exchange the above line must be replaced with the following one
            //walletAddress = ExchangeV2(previousExchangeAddress_).retrieveWallet(userAccount);
      
            if (walletAddress != address(0)) {
              userAccountToWallet_[userAccount] = walletAddress;
            }
          }
        }
      
        /**
         * @dev Add a new user to the exchange, create a wallet for them.
         * Map their account address to the wallet contract for lookup.
         * @param userExternalOwnedAccount The address of the user"s EOA.
         * @return Success of the transaction, false if error condition met.
         */
        function addNewUser(address userExternalOwnedAccount)
          public
          returns (bool)
        {
          if (retrieveWallet(userExternalOwnedAccount) != address(0)) {
            return error("User already exists, Exchange.addNewUser()");
          }
      
          // Pass the userAccount address to wallet constructor so owner is not the exchange contract
          address userTradingWallet = new WalletV2(userExternalOwnedAccount, walletConnector);
          userAccountToWallet_[userExternalOwnedAccount] = userTradingWallet;
          emit LogUserAdded(userExternalOwnedAccount, userTradingWallet);
          return true;
        }
      
        /**
         * Execute orders in batches.
         * @param ownedExternalAddressesAndTokenAddresses Tokan and user addresses.
         * @param amountsExpirationsAndSalts Offer and want token amount and expiration and salt values.
         * @param vSignatures All order signature v values.
         * @param rAndSsignatures All order signature r and r values.
         * @return The success of this transaction.
         */
        function batchExecuteOrder(
          address[4][] ownedExternalAddressesAndTokenAddresses,
          uint256[8][] amountsExpirationsAndSalts, // Packing to save stack size
          uint8[2][] vSignatures,
          bytes32[4][] rAndSsignatures
        ) external
          returns(bool)
        {
          for (uint256 i = 0; i < amountsExpirationsAndSalts.length; i++) {
            require(
              executeOrder(
                ownedExternalAddressesAndTokenAddresses[i],
                amountsExpirationsAndSalts[i],
                vSignatures[i],
                rAndSsignatures[i]
              ),
              "Cannot execute order, Exchange.batchExecuteOrder()"
            );
          }
      
          return true;
        }
      
        /**
         * @dev Execute an order that was submitted by the external order book server.
         * The order book server believes it to be a match.
         * There are components for both orders, maker and taker, 2 signatures as well.
         * @param ownedExternalAddressesAndTokenAddresses The maker and taker external owned accounts addresses and offered tokens contracts.
         * [
         *   makerEOA
         *   makerOfferToken
         *   takerEOA
         *   takerOfferToken
         * ]
         * @param amountsExpirationsAndSalts The amount of tokens and the block number at which this order expires and a random number to mitigate replay.
         * [
         *   makerOffer
         *   makerWant
         *   takerOffer
         *   takerWant
         *   makerExpiry
         *   makerSalt
         *   takerExpiry
         *   takerSalt
         * ]
         * @param vSignatures ECDSA signature parameter.
         * [
         *   maker V
         *   taker V
         * ]
         * @param rAndSsignatures ECDSA signature parameters r ans s, maker 0, 1 and taker 2, 3.
         * [
         *   maker R
         *   maker S
         *   taker R
         *   taker S
         * ]
         * @return Success of the transaction, false if error condition met.
         * Like types grouped to eliminate stack depth error.
         */
        function executeOrder (
          address[4] ownedExternalAddressesAndTokenAddresses,
          uint256[8] amountsExpirationsAndSalts, // Packing to save stack size
          uint8[2] vSignatures,
          bytes32[4] rAndSsignatures
        ) public
          returns(bool)
        {
          // Only read wallet addresses from storage once
          // Need one more stack slot so squashing into array
          WalletV2[2] memory makerAndTakerTradingWallets = [
            WalletV2(retrieveWallet(ownedExternalAddressesAndTokenAddresses[0])), // maker
            WalletV2(retrieveWallet(ownedExternalAddressesAndTokenAddresses[2])) // taker
          ];
      
          // Basic pre-conditions, return if any input data is invalid
          if(!__executeOrderInputIsValid__(
            ownedExternalAddressesAndTokenAddresses,
            amountsExpirationsAndSalts,
            makerAndTakerTradingWallets[0], // maker
            makerAndTakerTradingWallets[1] // taker
          )) {
            return error("Input is invalid, Exchange.executeOrder()");
          }
      
          // Verify Maker and Taker signatures
          bytes32[2] memory makerAndTakerOrderHash = generateOrderHashes(
            ownedExternalAddressesAndTokenAddresses,
            amountsExpirationsAndSalts
          );
      
          // Check maker order signature
          if (!__signatureIsValid__(
            ownedExternalAddressesAndTokenAddresses[0],
            makerAndTakerOrderHash[0],
            vSignatures[0],
            rAndSsignatures[0],
            rAndSsignatures[1]
          )) {
            return error("Maker signature is invalid, Exchange.executeOrder()");
          }
      
          // Check taker order signature
          if (!__signatureIsValid__(
            ownedExternalAddressesAndTokenAddresses[2],
            makerAndTakerOrderHash[1],
            vSignatures[1],
            rAndSsignatures[2],
            rAndSsignatures[3]
          )) {
            return error("Taker signature is invalid, Exchange.executeOrder()");
          }
      
          // Exchange Order Verification and matching
          OrderStatus memory makerOrderStatus = orders_[makerAndTakerOrderHash[0]];
          OrderStatus memory takerOrderStatus = orders_[makerAndTakerOrderHash[1]];
          Order memory makerOrder;
          Order memory takerOrder;
      
          makerOrder.offerToken_ = ownedExternalAddressesAndTokenAddresses[1];
          makerOrder.offerTokenTotal_ = amountsExpirationsAndSalts[0];
          makerOrder.wantToken_ = ownedExternalAddressesAndTokenAddresses[3];
          makerOrder.wantTokenTotal_ = amountsExpirationsAndSalts[1];
      
          if (makerOrderStatus.expirationBlock_ > 0) {  // Check for existence
            // Orders still active
            if (makerOrderStatus.offerTokenRemaining_ == 0) {
              return error("Maker order is inactive, Exchange.executeOrder()");
            }
            makerOrder.offerTokenRemaining_ = makerOrderStatus.offerTokenRemaining_; // Amount to give
            makerOrder.wantTokenReceived_ = makerOrderStatus.wantTokenReceived_; // Amount received
          } else {
            makerOrder.offerTokenRemaining_ = amountsExpirationsAndSalts[0]; // Amount to give
            makerOrder.wantTokenReceived_ = 0; // Amount received
            makerOrderStatus.expirationBlock_ = amountsExpirationsAndSalts[4]; // maker order expiration block
          }
      
          takerOrder.offerToken_ = ownedExternalAddressesAndTokenAddresses[3];
          takerOrder.offerTokenTotal_ = amountsExpirationsAndSalts[2];
          takerOrder.wantToken_ = ownedExternalAddressesAndTokenAddresses[1];
          takerOrder.wantTokenTotal_ = amountsExpirationsAndSalts[3];
      
          if (takerOrderStatus.expirationBlock_ > 0) {  // Check for existence
            if (takerOrderStatus.offerTokenRemaining_ == 0) {
              return error("Taker order is inactive, Exchange.executeOrder()");
            }
            takerOrder.offerTokenRemaining_ = takerOrderStatus.offerTokenRemaining_;  // Amount to give
            takerOrder.wantTokenReceived_ = takerOrderStatus.wantTokenReceived_; // Amount received
      
          } else {
            takerOrder.offerTokenRemaining_ = amountsExpirationsAndSalts[2];  // Amount to give
            takerOrder.wantTokenReceived_ = 0; // Amount received
            takerOrderStatus.expirationBlock_ = amountsExpirationsAndSalts[6]; // taker order expiration block
          }
      
          // Check if orders are matching and are valid
          if (!__ordersMatch_and_AreVaild__(makerOrder, takerOrder)) {
            return error("Orders do not match, Exchange.executeOrder()");
          }
      
          // Trade amounts
          // [0] => toTakerAmount
          // [1] => toMakerAmount
          uint[2] memory toTakerAndToMakerAmount;
          toTakerAndToMakerAmount = __getTradeAmounts__(makerOrder, takerOrder);
      
          // TODO consider removing. Can this condition be met?
          if (toTakerAndToMakerAmount[0] < 1 || toTakerAndToMakerAmount[1] < 1) {
            return error("Token amount < 1, price ratio is invalid! Token value < 1, Exchange.executeOrder()");
          }
      
          uint calculatedFee = __calculateFee__(makerOrder, toTakerAndToMakerAmount[0], toTakerAndToMakerAmount[1]);
      
          // Check taker has sufficent EDO token balance to pay the fee
          if (
            takerOrder.offerToken_ == edoToken_ &&
            Token(edoToken_).balanceOf(makerAndTakerTradingWallets[1]) < calculatedFee.add(toTakerAndToMakerAmount[1])
          ) {
            return error("Taker has an insufficient EDO token balance to cover the fee AND the offer, Exchange.executeOrder()");
          } else if (Token(edoToken_).balanceOf(makerAndTakerTradingWallets[1]) < calculatedFee) {
            return error("Taker has an insufficient EDO token balance to cover the fee, Exchange.executeOrder()");
          }
      
          // Wallet Order Verification, reach out to the maker and taker wallets.
          if (
            !__ordersVerifiedByWallets__(
              ownedExternalAddressesAndTokenAddresses,
              toTakerAndToMakerAmount[1],
              toTakerAndToMakerAmount[0],
              makerAndTakerTradingWallets[0],
              makerAndTakerTradingWallets[1],
              calculatedFee
          )) {
            return error("Order could not be verified by wallets, Exchange.executeOrder()");
          }
      
          // Write to storage then external calls
          makerOrderStatus.offerTokenRemaining_ = makerOrder.offerTokenRemaining_.sub(toTakerAndToMakerAmount[0]);
          makerOrderStatus.wantTokenReceived_ = makerOrder.wantTokenReceived_.add(toTakerAndToMakerAmount[1]);
      
          takerOrderStatus.offerTokenRemaining_ = takerOrder.offerTokenRemaining_.sub(toTakerAndToMakerAmount[1]);
          takerOrderStatus.wantTokenReceived_ = takerOrder.wantTokenReceived_.add(toTakerAndToMakerAmount[0]);
      
          // Finally write orders to storage
          orders_[makerAndTakerOrderHash[0]] = makerOrderStatus;
          orders_[makerAndTakerOrderHash[1]] = takerOrderStatus;
      
          // Transfer the external value, ether <> tokens
          require(
            __executeTokenTransfer__(
              ownedExternalAddressesAndTokenAddresses,
              toTakerAndToMakerAmount[0],
              toTakerAndToMakerAmount[1],
              calculatedFee,
              makerAndTakerTradingWallets[0],
              makerAndTakerTradingWallets[1]
            ),
            "Cannot execute token transfer, Exchange.__executeTokenTransfer__()"
          );
      
          // Log the order id(hash), amount of offer given, amount of offer remaining
          emit LogOrderFilled(makerAndTakerOrderHash[0], makerOrderStatus.offerTokenRemaining_, makerOrderStatus.wantTokenReceived_);
          emit LogOrderFilled(makerAndTakerOrderHash[1], takerOrderStatus.offerTokenRemaining_, takerOrderStatus.wantTokenReceived_);
          emit LogOrderExecutionSuccess(makerAndTakerOrderHash[0], makerAndTakerOrderHash[1], toTakerAndToMakerAmount[1], toTakerAndToMakerAmount[0]);
      
          return true;
        }
      
        /**
         * @dev Set the fee rate for a specific quote
         * @param _quoteToken Quote token.
         * @param _edoPerQuote EdoPerQuote.
         * @param _edoPerQuoteDecimals EdoPerQuoteDecimals.
         * @return Success of the transaction.
         */
        function setFeeRate(
          address _quoteToken,
          uint256 _edoPerQuote,
          uint256 _edoPerQuoteDecimals
        ) external
          returns(bool)
        {
          if (msg.sender != owner_) {
            return error("msg.sender != owner, Exchange.setFeeRate()");
          }
      
          if (quotePriority[_quoteToken] == 0) {
            return error("quotePriority[_quoteToken] == 0, Exchange.setFeeRate()");
          }
      
          feeEdoPerQuote[_quoteToken] = _edoPerQuote;
          feeEdoPerQuoteDecimals[_quoteToken] = _edoPerQuoteDecimals;
      
          emit LogFeeRateSet(_quoteToken, _edoPerQuote, _edoPerQuoteDecimals);
      
          return true;
        }
      
        /**
         * @dev Set the wallet for fees to be paid to.
         * @param eidooWallet Wallet to pay fees to.
         * @return Success of the transaction.
         */
        function setEidooWallet(
          address eidooWallet
        ) external
          returns(bool)
        {
          if (msg.sender != owner_) {
            return error("msg.sender != owner, Exchange.setEidooWallet()");
          }
          eidooWallet_ = eidooWallet;
          return true;
        }
      
        /**
         * @dev Set a new order book account.
         * @param account The new order book account.
         */
        function setOrderBookAcount (
          address account
        ) external
          returns(bool)
        {
          if (msg.sender != owner_) {
            return error("msg.sender != owner, Exchange.setOrderBookAcount()");
          }
          orderBookAccount_ = account;
          return true;
        }
      
        /**
         * @dev Set if a base must skip fee calculation.
         * @param _baseTokenAddress The trade base token address that must skip fee calculation.
         * @param _quoteTokenAddress The trade quote token address that must skip fee calculation.
         * @param _mustSkipFee The trade base token address that must skip fee calculation.
         */
        function setMustSkipFee (
          address _baseTokenAddress,
          address _quoteTokenAddress,
          bool _mustSkipFee
        ) external
          returns(bool)
        {
          // Preserving same owner check style
          if (msg.sender != owner_) {
            return error("msg.sender != owner, Exchange.setMustSkipFee()");
          }
          mustSkipFee[_baseTokenAddress][_quoteTokenAddress] = _mustSkipFee;
          emit LogMustSkipFeeSet(_baseTokenAddress, _quoteTokenAddress, _mustSkipFee);
          return true;
        }
      
        /**
         * @dev Set quote priority token.
         * Set the sorting of token quote based on a priority.
         * @param _token The address of the token that was deposited.
         * @param _priority The amount of the token that was deposited.
         * @return Operation success.
         */
      
        function setQuotePriority(address _token, uint256 _priority)
          external
          returns(bool)
        {
          if (msg.sender != owner_) {
            return error("msg.sender != owner, Exchange.setQuotePriority()");
          }
          quotePriority[_token] = _priority;
          emit LogQuotePrioritySet(_token, _priority);
          return true;
        }
      
        /*
         Methods to catch events from external contracts, user wallets primarily
         */
      
        /**
         * @dev Simply log the event to track wallet interaction off-chain.
         * @param tokenAddress The address of the token that was deposited.
         * @param amount The amount of the token that was deposited.
         * @param tradingWalletBalance The updated balance of the wallet after deposit.
         */
        function walletDeposit(
          address tokenAddress,
          uint256 amount,
          uint256 tradingWalletBalance
        ) external
        {
          emit LogWalletDeposit(msg.sender, tokenAddress, amount, tradingWalletBalance);
        }
      
        /**
         * @dev Simply log the event to track wallet interaction off-chain.
         * @param tokenAddress The address of the token that was deposited.
         * @param amount The amount of the token that was deposited.
         * @param tradingWalletBalance The updated balance of the wallet after deposit.
         */
        function walletWithdrawal(
          address tokenAddress,
          uint256 amount,
          uint256 tradingWalletBalance
        ) external
        {
          emit LogWalletWithdrawal(msg.sender, tokenAddress, amount, tradingWalletBalance);
        }
      
        /**
         * Private
         */
      
        /**
         * Calculate the fee for the given trade. Calculated as the set % of the wei amount
         * converted into EDO tokens using the manually set conversion ratio.
         * @param makerOrder The maker order object.
         * @param toTakerAmount The amount of tokens going to the taker.
         * @param toMakerAmount The amount of tokens going to the maker.
         * @return The total fee to be paid in EDO tokens.
         */
        function __calculateFee__(
          Order makerOrder,
          uint256 toTakerAmount,
          uint256 toMakerAmount
        ) private
          view
          returns(uint256)
        {
          // weiAmount * (fee %) * (EDO/Wei) / (decimals in edo/wei) / (decimals in percentage)
          if (!__isSell__(makerOrder)) {
            // buy -> the quote is the offered token by the maker
            return mustSkipFee[makerOrder.wantToken_][makerOrder.offerToken_]
              ? 0
              : toTakerAmount.mul(feeEdoPerQuote[makerOrder.offerToken_]).div(10**feeEdoPerQuoteDecimals[makerOrder.offerToken_]);
          } else {
            // sell -> the quote is the wanted token by the maker
            return mustSkipFee[makerOrder.offerToken_][makerOrder.wantToken_]
              ? 0
              : toMakerAmount.mul(feeEdoPerQuote[makerOrder.wantToken_]).div(10**feeEdoPerQuoteDecimals[makerOrder.wantToken_]);
          }
        }
      
        /**
         * @dev Verify the input to order execution is valid.
         * @param ownedExternalAddressesAndTokenAddresses The maker and taker external owned accounts addresses and offered tokens contracts.
         * [
         *   makerEOA
         *   makerOfferToken
         *   takerEOA
         *   takerOfferToken
         * ]
         * @param amountsExpirationsAndSalts The amount of tokens and the block number at which this order expires and a random number to mitigate replay.
         * [
         *   makerOffer
         *   makerWant
         *   takerOffer
         *   takerWant
         *   makerExpiry
         *   makerSalt
         *   takerExpiry
         *   takerSalt
         * ]
         * @return Success if all checks pass.
         */
        function __executeOrderInputIsValid__(
          address[4] ownedExternalAddressesAndTokenAddresses,
          uint256[8] amountsExpirationsAndSalts,
          address makerTradingWallet,
          address takerTradingWallet
        ) private
          returns(bool)
        {
          // msg.send needs to be the orderBookAccount
          if (msg.sender != orderBookAccount_) {
            return error("msg.sender != orderBookAccount, Exchange.__executeOrderInputIsValid__()");
          }
      
          // Check expirations base on the block number
          if (block.number > amountsExpirationsAndSalts[4]) {
            return error("Maker order has expired, Exchange.__executeOrderInputIsValid__()");
          }
      
          if (block.number > amountsExpirationsAndSalts[6]) {
            return error("Taker order has expired, Exchange.__executeOrderInputIsValid__()");
          }
      
          // Operating on existing tradingWallets
          if (makerTradingWallet == address(0)) {
            return error("Maker wallet does not exist, Exchange.__executeOrderInputIsValid__()");
          }
      
          if (takerTradingWallet == address(0)) {
            return error("Taker wallet does not exist, Exchange.__executeOrderInputIsValid__()");
          }
      
          if (quotePriority[ownedExternalAddressesAndTokenAddresses[1]] == quotePriority[ownedExternalAddressesAndTokenAddresses[3]]) {
            return error("Quote token is omitted! Is not offered by either the Taker or Maker, Exchange.__executeOrderInputIsValid__()");
          }
      
          // Check that none of the amounts is = to 0
          if (
              amountsExpirationsAndSalts[0] == 0 ||
              amountsExpirationsAndSalts[1] == 0 ||
              amountsExpirationsAndSalts[2] == 0 ||
              amountsExpirationsAndSalts[3] == 0
            )
            return error("May not execute an order where token amount == 0, Exchange.__executeOrderInputIsValid__()");
      
          // // Confirm order ether amount >= min amount
          //  // Maker
          //  uint256 minOrderEthAmount = minOrderEthAmount_; // Single storage read
          //  if (_token_and_EOA_Addresses[1] == 0 && _amountsExpirationAndSalt[0] < minOrderEthAmount)
          //    return error('Maker order does not meet the minOrderEthAmount_ of ether, Exchange.__executeOrderInputIsValid__()');
      
          //  // Taker
          //  if (_token_and_EOA_Addresses[3] == 0 && _amountsExpirationAndSalt[2] < minOrderEthAmount)
          //    return error('Taker order does not meet the minOrderEthAmount_ of ether, Exchange.__executeOrderInputIsValid__()');
      
          return true;
        }
      
        /**
         * @dev Execute the external transfer of tokens.
         * @param ownedExternalAddressesAndTokenAddresses The maker and taker external owned accounts addresses and offered tokens contracts.
         * [
         *   makerEOA
         *   makerOfferToken
         *   takerEOA
         *   takerOfferToken
         * ]
         * @param toTakerAmount The amount of tokens to transfer to the taker.
         * @param toMakerAmount The amount of tokens to transfer to the maker.
         * @return Success if both wallets verify the order.
         */
        function __executeTokenTransfer__(
          address[4] ownedExternalAddressesAndTokenAddresses,
          uint256 toTakerAmount,
          uint256 toMakerAmount,
          uint256 fee,
          WalletV2 makerTradingWallet,
          WalletV2 takerTradingWallet
        ) private
          returns (bool)
        {
          // Wallet mapping balances
          address makerOfferTokenAddress = ownedExternalAddressesAndTokenAddresses[1];
          address takerOfferTokenAddress = ownedExternalAddressesAndTokenAddresses[3];
      
          // Taker to pay fee before trading
          if(fee != 0) {
            require(
              takerTradingWallet.updateBalance(edoToken_, fee, true),
              "Taker trading wallet cannot update balance with fee, Exchange.__executeTokenTransfer__()"
            );
      
            require(
              Token(edoToken_).transferFrom(takerTradingWallet, eidooWallet_, fee),
              "Cannot transfer fees from taker trading wallet to eidoo wallet, Exchange.__executeTokenTransfer__()"
            );
          }
      
          // Updating makerTradingWallet balance by the toTaker
          require(
            makerTradingWallet.updateBalance(makerOfferTokenAddress, toTakerAmount, true),
            "Maker trading wallet cannot update balance subtracting toTakerAmount, Exchange.__executeTokenTransfer__()"
          ); // return error("Unable to subtract maker token from maker wallet, Exchange.__executeTokenTransfer__()");
      
          // Updating takerTradingWallet balance by the toTaker
          require(
            takerTradingWallet.updateBalance(makerOfferTokenAddress, toTakerAmount, false),
            "Taker trading wallet cannot update balance adding toTakerAmount, Exchange.__executeTokenTransfer__()"
          ); // return error("Unable to add maker token to taker wallet, Exchange.__executeTokenTransfer__()");
      
          // Updating takerTradingWallet balance by the toMaker amount
          require(
            takerTradingWallet.updateBalance(takerOfferTokenAddress, toMakerAmount, true),
            "Taker trading wallet cannot update balance subtracting toMakerAmount, Exchange.__executeTokenTransfer__()"
          ); // return error("Unable to subtract taker token from taker wallet, Exchange.__executeTokenTransfer__()");
      
          // Updating makerTradingWallet balance by the toMaker amount
          require(
            makerTradingWallet.updateBalance(takerOfferTokenAddress, toMakerAmount, false),
            "Maker trading wallet cannot update balance adding toMakerAmount, Exchange.__executeTokenTransfer__()"
          ); // return error("Unable to add taker token to maker wallet, Exchange.__executeTokenTransfer__()");
      
          // Ether to the taker and tokens to the maker
          if (makerOfferTokenAddress == address(0)) {
            address(takerTradingWallet).transfer(toTakerAmount);
          } else {
            require(
              safeTransferFrom(makerOfferTokenAddress, makerTradingWallet, takerTradingWallet, toTakerAmount),
              "Token transfership from makerTradingWallet to takerTradingWallet failed, Exchange.__executeTokenTransfer__()"
            );
            assert(
              __tokenAndWalletBalancesMatch__(
                makerTradingWallet,
                takerTradingWallet,
                makerOfferTokenAddress
              )
            );
          }
      
          if (takerOfferTokenAddress == address(0)) {
            address(makerTradingWallet).transfer(toMakerAmount);
          } else {
            require(
              safeTransferFrom(takerOfferTokenAddress, takerTradingWallet, makerTradingWallet, toMakerAmount),
              "Token transfership from takerTradingWallet to makerTradingWallet failed, Exchange.__executeTokenTransfer__()"
            );
            assert(
              __tokenAndWalletBalancesMatch__(
                makerTradingWallet,
                takerTradingWallet,
                takerOfferTokenAddress
              )
            );
          }
      
          return true;
        }
      
        /**
         * @dev Calculates Keccak-256 hash of order with specified parameters.
         * @param ownedExternalAddressesAndTokenAddresses The orders maker EOA and current exchange address.
         * @param amountsExpirationsAndSalts The orders offer and want amounts and expirations with salts.
         * @return Keccak-256 hash of the passed order.
         */
      
        function generateOrderHashes(
          address[4] ownedExternalAddressesAndTokenAddresses,
          uint256[8] amountsExpirationsAndSalts
        ) public
          view
          returns (bytes32[2])
        {
          bytes32 makerOrderHash = keccak256(
            address(this),
            ownedExternalAddressesAndTokenAddresses[0], // _makerEOA
            ownedExternalAddressesAndTokenAddresses[1], // offerToken
            amountsExpirationsAndSalts[0],  // offerTokenAmount
            ownedExternalAddressesAndTokenAddresses[3], // wantToken
            amountsExpirationsAndSalts[1],  // wantTokenAmount
            amountsExpirationsAndSalts[4], // expiry
            amountsExpirationsAndSalts[5] // salt
          );
      
          bytes32 takerOrderHash = keccak256(
            address(this),
            ownedExternalAddressesAndTokenAddresses[2], // _makerEOA
            ownedExternalAddressesAndTokenAddresses[3], // offerToken
            amountsExpirationsAndSalts[2],  // offerTokenAmount
            ownedExternalAddressesAndTokenAddresses[1], // wantToken
            amountsExpirationsAndSalts[3],  // wantTokenAmount
            amountsExpirationsAndSalts[6], // expiry
            amountsExpirationsAndSalts[7] // salt
          );
      
          return [makerOrderHash, takerOrderHash];
        }
      
        /**
         * @dev Returns a bool representing a SELL or BUY order based on quotePriority.
         * @param _order The maker order data structure.
         * @return The bool indicating if the order is a SELL or BUY.
         */
        function __isSell__(Order _order) internal view returns (bool) {
          return quotePriority[_order.offerToken_] < quotePriority[_order.wantToken_];
        }
      
        /**
         * @dev Compute the tradeable amounts of the two verified orders.
         * Token amount is the __min__ remaining between want and offer of the two orders that isn"t ether.
         * Ether amount is then: etherAmount = tokenAmount * priceRatio, as ratio = eth / token.
         * @param makerOrder The maker order data structure.
         * @param takerOrder The taker order data structure.
         * @return The amount moving from makerOfferRemaining to takerWantRemaining and vice versa.
         */
        function __getTradeAmounts__(
          Order makerOrder,
          Order takerOrder
        ) internal
          view
          returns (uint256[2])
        {
          bool isMakerBuy = __isSell__(takerOrder);  // maker buy = taker sell
          uint256 priceRatio;
          uint256 makerAmountLeftToReceive;
          uint256 takerAmountLeftToReceive;
      
          uint toTakerAmount;
          uint toMakerAmount;
      
          if (makerOrder.offerTokenTotal_ >= makerOrder.wantTokenTotal_) {
            priceRatio = makerOrder.offerTokenTotal_.mul(2**128).div(makerOrder.wantTokenTotal_);
            if (isMakerBuy) {
              // MP > 1
              makerAmountLeftToReceive = makerOrder.wantTokenTotal_.sub(makerOrder.wantTokenReceived_);
              toMakerAmount = __min__(takerOrder.offerTokenRemaining_, makerAmountLeftToReceive);
              // add 2**128-1 in order to obtain a round up
              toTakerAmount = toMakerAmount.mul(priceRatio).add(2**128-1).div(2**128);
            } else {
              // MP < 1
              takerAmountLeftToReceive = takerOrder.wantTokenTotal_.sub(takerOrder.wantTokenReceived_);
              toTakerAmount = __min__(makerOrder.offerTokenRemaining_, takerAmountLeftToReceive);
              toMakerAmount = toTakerAmount.mul(2**128).div(priceRatio);
            }
          } else {
            priceRatio = makerOrder.wantTokenTotal_.mul(2**128).div(makerOrder.offerTokenTotal_);
            if (isMakerBuy) {
              // MP < 1
              makerAmountLeftToReceive = makerOrder.wantTokenTotal_.sub(makerOrder.wantTokenReceived_);
              toMakerAmount = __min__(takerOrder.offerTokenRemaining_, makerAmountLeftToReceive);
              toTakerAmount = toMakerAmount.mul(2**128).div(priceRatio);
            } else {
              // MP > 1
              takerAmountLeftToReceive = takerOrder.wantTokenTotal_.sub(takerOrder.wantTokenReceived_);
              toTakerAmount = __min__(makerOrder.offerTokenRemaining_, takerAmountLeftToReceive);
              // add 2**128-1 in order to obtain a round up
              toMakerAmount = toTakerAmount.mul(priceRatio).add(2**128-1).div(2**128);
            }
          }
          return [toTakerAmount, toMakerAmount];
        }
      
        /**
         * @dev Return the maximum of two uints
         * @param a Uint 1
         * @param b Uint 2
         * @return The grater value or a if equal
         */
        function __max__(uint256 a, uint256 b)
          private
          pure
          returns (uint256)
        {
          return a < b
            ? b
            : a;
        }
      
        /**
         * @dev Return the minimum of two uints
         * @param a Uint 1
         * @param b Uint 2
         * @return The smallest value or b if equal
         */
        function __min__(uint256 a, uint256 b)
          private
          pure
          returns (uint256)
        {
          return a < b
            ? a
            : b;
        }
      
        /**
         * @dev Confirm that the orders do match and are valid.
         * @param makerOrder The maker order data structure.
         * @param takerOrder The taker order data structure.
         * @return Bool if the orders passes all checks.
         */
        function __ordersMatch_and_AreVaild__(
          Order makerOrder,
          Order takerOrder
        ) private
          returns (bool)
        {
          // Confirm tokens match
          // NOTE potentially omit as matching handled upstream?
          if (makerOrder.wantToken_ != takerOrder.offerToken_) {
            return error("Maker wanted token does not match taker offer token, Exchange.__ordersMatch_and_AreVaild__()");
          }
      
          if (makerOrder.offerToken_ != takerOrder.wantToken_) {
            return error("Maker offer token does not match taker wanted token, Exchange.__ordersMatch_and_AreVaild__()");
          }
      
          // Price Ratios, to x decimal places hence * decimals, dependent on the size of the denominator.
          // Ratios are relative to eth, amount of ether for a single token, ie. ETH / GNO == 0.2 Ether per 1 Gnosis
      
          uint256 orderPrice;   // The price the maker is willing to accept
          uint256 offeredPrice; // The offer the taker has given
      
          // Ratio = larger amount / smaller amount
          if (makerOrder.offerTokenTotal_ >= makerOrder.wantTokenTotal_) {
            orderPrice = makerOrder.offerTokenTotal_.mul(2**128).div(makerOrder.wantTokenTotal_);
            offeredPrice = takerOrder.wantTokenTotal_.mul(2**128).div(takerOrder.offerTokenTotal_);
      
            // ie. Maker is offering 10 ETH for 100 GNO but taker is offering 100 GNO for 20 ETH, no match!
            // The taker wants more ether than the maker is offering.
            if (orderPrice < offeredPrice) {
              return error("Taker price is greater than maker price, Exchange.__ordersMatch_and_AreVaild__()");
            }
          } else {
            orderPrice = makerOrder.wantTokenTotal_.mul(2**128).div(makerOrder.offerTokenTotal_);
            offeredPrice = takerOrder.offerTokenTotal_.mul(2**128).div(takerOrder.wantTokenTotal_);
      
            // ie. Maker is offering 100 GNO for 10 ETH but taker is offering 5 ETH for 100 GNO, no match!
            // The taker is not offering enough ether for the maker
            if (orderPrice > offeredPrice) {
              return error("Taker price is less than maker price, Exchange.__ordersMatch_and_AreVaild__()");
            }
          }
      
          return true;
        }
      
        /**
         * @dev Ask each wallet to verify this order.
         * @param ownedExternalAddressesAndTokenAddresses The maker and taker external owned accounts addresses and offered tokens contracts.
         * [
         *   makerEOA
         *   makerOfferToken
         *   takerEOA
         *   takerOfferToken
         * ]
         * @param toMakerAmount The amount of tokens to be sent to the maker.
         * @param toTakerAmount The amount of tokens to be sent to the taker.
         * @param makerTradingWallet The maker trading wallet contract.
         * @param takerTradingWallet The taker trading wallet contract.
         * @param fee The fee to be paid for this trade, paid in full by taker.
         * @return Success if both wallets verify the order.
         */
        function __ordersVerifiedByWallets__(
          address[4] ownedExternalAddressesAndTokenAddresses,
          uint256 toMakerAmount,
          uint256 toTakerAmount,
          WalletV2 makerTradingWallet,
          WalletV2 takerTradingWallet,
          uint256 fee
        ) private
          returns (bool)
        {
          // Have the transaction verified by both maker and taker wallets
          // confirm sufficient balance to transfer, offerToken and offerTokenAmount
          if(!makerTradingWallet.verifyOrder(ownedExternalAddressesAndTokenAddresses[1], toTakerAmount, 0, 0)) {
            return error("Maker wallet could not verify the order, Exchange.____ordersVerifiedByWallets____()");
          }
      
          if(!takerTradingWallet.verifyOrder(ownedExternalAddressesAndTokenAddresses[3], toMakerAmount, fee, edoToken_)) {
            return error("Taker wallet could not verify the order, Exchange.____ordersVerifiedByWallets____()");
          }
      
          return true;
        }
      
        /**
         * @dev On chain verification of an ECDSA ethereum signature.
         * @param signer The EOA address of the account that supposedly signed the message.
         * @param orderHash The on-chain generated hash for the order.
         * @param v ECDSA signature parameter v.
         * @param r ECDSA signature parameter r.
         * @param s ECDSA signature parameter s.
         * @return Bool if the signature is valid or not.
         */
        function __signatureIsValid__(
          address signer,
          bytes32 orderHash,
          uint8 v,
          bytes32 r,
          bytes32 s
        ) private
          pure
          returns (bool)
        {
          address recoveredAddr = ecrecover(
            keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", orderHash)),
            v,
            r,
            s
          );
      
          return recoveredAddr == signer;
        }
      
        /**
         * @dev Confirm wallet local balances and token balances match.
         * @param makerTradingWallet  Maker wallet address.
         * @param takerTradingWallet  Taker wallet address.
         * @param token  Token address to confirm balances match.
         * @return If the balances do match.
         */
        function __tokenAndWalletBalancesMatch__(
          address makerTradingWallet,
          address takerTradingWallet,
          address token
        ) private
          view
          returns(bool)
        {
          if (Token(token).balanceOf(makerTradingWallet) != WalletV2(makerTradingWallet).balanceOf(token)) {
            return false;
          }
      
          if (Token(token).balanceOf(takerTradingWallet) != WalletV2(takerTradingWallet).balanceOf(token)) {
            return false;
          }
      
          return true;
        }
      
        /**
         * @dev Wrapping the ERC20 transfer function to avoid missing returns.
         * @param _token The address of bad formed ERC20 token.
         * @param _from Transfer sender.
         * @param _to Transfer receiver.
         * @param _value Amount to be transfered.
         * @return Success of the safeTransfer.
         */
        function safeTransferFrom(
          address _token,
          address _from,
          address _to,
          uint256 _value
        )
          public
          returns (bool result)
        {
          BadERC20(_token).transferFrom(_from, _to, _value);
      
          assembly {
            switch returndatasize()
            case 0 {                      // This is our BadToken
              result := not(0)            // result is true
            }
            case 32 {                     // This is our GoodToken
              returndatacopy(0, 0, 32)
              result := mload(0)          // result == returndata of external call
            }
            default {                     // This is not an ERC20 token
              revert(0, 0)
            }
          }
        }
      }

      File 4 of 4: WalletLogic3
      pragma solidity ^0.4.24;
      
      
      /**
       * @title Log Various Error Types
       * @author Adam Lemmon <[email protected]>
       * @dev Inherit this contract and your may now log errors easily
       * To support various error types, params, etc.
       */
      contract LoggingErrors {
        /**
        * Events
        */
        event LogErrorString(string errorString);
      
        /**
        * Error cases
        */
      
        /**
         * @dev Default error to simply log the error message and return
         * @param _errorMessage The error message to log
         * @return ALWAYS false
         */
        function error(string _errorMessage) internal returns(bool) {
          LogErrorString(_errorMessage);
          return false;
        }
      }
      
      
      
      /**
       * @title Wallet Connector
       * @dev Connect the wallet contract to the correct Wallet Logic version
       */
      contract WalletConnector is LoggingErrors {
        /**
         * Storage
         */
        address public owner_;
        address public latestLogic_;
        uint256 public latestVersion_;
        mapping(uint256 => address) public logicVersions_;
        uint256 public birthBlock_;
      
        /**
         * Events
         */
        event LogLogicVersionAdded(uint256 version);
        event LogLogicVersionRemoved(uint256 version);
      
        /**
         * @dev Constructor to set the latest logic address
         * @param _latestVersion Latest version of the wallet logic
         * @param _latestLogic Latest address of the wallet logic contract
         */
        function WalletConnector (
          uint256 _latestVersion,
          address _latestLogic
        ) public {
          owner_ = msg.sender;
          latestLogic_ = _latestLogic;
          latestVersion_ = _latestVersion;
          logicVersions_[_latestVersion] = _latestLogic;
          birthBlock_ = block.number;
        }
      
        /**
         * Add a new version of the logic contract
         * @param _version The version to be associated with the new contract.
         * @param _logic New logic contract.
         * @return Success of the transaction.
         */
        function addLogicVersion (
          uint256 _version,
          address _logic
        ) external
          returns(bool)
        {
          if (msg.sender != owner_)
            return error('msg.sender != owner, WalletConnector.addLogicVersion()');
      
          if (logicVersions_[_version] != 0)
            return error('Version already exists, WalletConnector.addLogicVersion()');
      
          // Update latest if this is the latest version
          if (_version > latestVersion_) {
            latestLogic_ = _logic;
            latestVersion_ = _version;
          }
      
          logicVersions_[_version] = _logic;
          LogLogicVersionAdded(_version);
      
          return true;
        }
      
        /**
         * @dev Remove a version. Cannot remove the latest version.
         * @param  _version The version to remove.
         */
        function removeLogicVersion(uint256 _version) external {
          require(msg.sender == owner_);
          require(_version != latestVersion_);
          delete logicVersions_[_version];
          LogLogicVersionRemoved(_version);
        }
      
        /**
         * Constants
         */
      
        /**
         * Called from user wallets in order to upgrade their logic.
         * @param _version The version to upgrade to. NOTE pass in 0 to upgrade to latest.
         * @return The address of the logic contract to upgrade to.
         */
        function getLogic(uint256 _version)
          external
          constant
          returns(address)
        {
          if (_version == 0)
            return latestLogic_;
          else
            return logicVersions_[_version];
        }
      }
      
      /**
       * @title Wallet to hold and trade ERC20 tokens and ether
       * @author Adam Lemmon <[email protected]>
       * @dev User wallet to interact with the exchange.
       * all tokens and ether held in this wallet, 1 to 1 mapping to user EOAs.
       */
      contract Wallet is LoggingErrors {
        /**
         * Storage
         */
        // Vars included in wallet logic "lib", the order must match between Wallet and Logic
        address public owner_;
        address public exchange_;
        mapping(address => uint256) public tokenBalances_;
      
        address public logic_; // storage location 0x3 loaded for delegatecalls so this var must remain at index 3
        uint256 public birthBlock_;
      
        // Address updated at deploy time
        WalletConnector private connector_ = WalletConnector(0x03d6e7b2f48120fd57a89ff0bbd56e9ec39af21c);
      
        /**
         * Events
         */
        event LogDeposit(address token, uint256 amount, uint256 balance);
        event LogWithdrawal(address token, uint256 amount, uint256 balance);
      
        /**
         * @dev Contract consturtor. Set user as owner and connector address.
         * @param _owner The address of the user's EOA, wallets created from the exchange
         * so must past in the owner address, msg.sender == exchange.
         */
        function Wallet(address _owner) public {
          owner_ = _owner;
          exchange_ = msg.sender;
          logic_ = connector_.latestLogic_();
          birthBlock_ = block.number;
        }
      
        /**
         * @dev Fallback - Only enable funds to be sent from the exchange.
         * Ensures balances will be consistent.
         */
        function () external payable {
          require(msg.sender == exchange_);
        }
      
        /**
        * External
        */
      
        /**
         * @dev Deposit ether into this wallet, default to address 0 for consistent token lookup.
         */
        function depositEther()
          external
          payable
        {
          require(logic_.delegatecall(bytes4(sha3('deposit(address,uint256)')), 0, msg.value));
        }
      
        /**
         * @dev Deposit any ERC20 token into this wallet.
         * @param _token The address of the existing token contract.
         * @param _amount The amount of tokens to deposit.
         * @return Bool if the deposit was successful.
         */
        function depositERC20Token (
          address _token,
          uint256 _amount
        ) external
          returns(bool)
        {
          // ether
          if (_token == 0)
            return error('Cannot deposit ether via depositERC20, Wallet.depositERC20Token()');
      
          require(logic_.delegatecall(bytes4(sha3('deposit(address,uint256)')), _token, _amount));
          return true;
        }
      
        /**
         * @dev The result of an order, update the balance of this wallet.
         * @param _token The address of the token balance to update.
         * @param _amount The amount to update the balance by.
         * @param _subtractionFlag If true then subtract the token amount else add.
         * @return Bool if the update was successful.
         */
        function updateBalance (
          address _token,
          uint256 _amount,
          bool _subtractionFlag
        ) external
          returns(bool)
        {
          assembly {
            calldatacopy(0x40, 0, calldatasize)
            delegatecall(gas, sload(0x3), 0x40, calldatasize, 0, 32)
            return(0, 32)
            pop
          }
        }
      
        /**
         * User may update to the latest version of the exchange contract.
         * Note that multiple versions are NOT supported at this time and therefore if a
         * user does not wish to update they will no longer be able to use the exchange.
         * @param _exchange The new exchange.
         * @return Success of this transaction.
         */
        function updateExchange(address _exchange)
          external
          returns(bool)
        {
          if (msg.sender != owner_)
            return error('msg.sender != owner_, Wallet.updateExchange()');
      
          // If subsequent messages are not sent from this address all orders will fail
          exchange_ = _exchange;
      
          return true;
        }
      
        /**
         * User may update to a new or older version of the logic contract.
         * @param _version The versin to update to.
         * @return Success of this transaction.
         */
        function updateLogic(uint256 _version)
          external
          returns(bool)
        {
          if (msg.sender != owner_)
            return error('msg.sender != owner_, Wallet.updateLogic()');
      
          address newVersion = connector_.getLogic(_version);
      
          // Invalid version as defined by connector
          if (newVersion == 0)
            return error('Invalid version, Wallet.updateLogic()');
      
          logic_ = newVersion;
          return true;
        }
      
        /**
         * @dev Verify an order that the Exchange has received involving this wallet.
         * Internal checks and then authorize the exchange to move the tokens.
         * If sending ether will transfer to the exchange to broker the trade.
         * @param _token The address of the token contract being sold.
         * @param _amount The amount of tokens the order is for.
         * @param _fee The fee for the current trade.
         * @param _feeToken The token of which the fee is to be paid in.
         * @return If the order was verified or not.
         */
        function verifyOrder (
          address _token,
          uint256 _amount,
          uint256 _fee,
          address _feeToken
        ) external
          returns(bool)
        {
          assembly {
            calldatacopy(0x40, 0, calldatasize)
            delegatecall(gas, sload(0x3), 0x40, calldatasize, 0, 32)
            return(0, 32)
            pop
          }
        }
      
        /**
         * @dev Withdraw any token, including ether from this wallet to an EOA.
         * @param _token The address of the token to withdraw.
         * @param _amount The amount to withdraw.
         * @return Success of the withdrawal.
         */
        function withdraw(address _token, uint256 _amount)
          external
          returns(bool)
        {
          if(msg.sender != owner_)
            return error('msg.sender != owner, Wallet.withdraw()');
      
          assembly {
            calldatacopy(0x40, 0, calldatasize)
            delegatecall(gas, sload(0x3), 0x40, calldatasize, 0, 32)
            return(0, 32)
            pop
          }
        }
      
        /**
         * Constants
         */
      
        /**
         * @dev Get the balance for a specific token.
         * @param _token The address of the token contract to retrieve the balance of.
         * @return The current balance within this contract.
         */
        function balanceOf(address _token)
          public
          constant
          returns(uint)
        {
          return tokenBalances_[_token];
        }
      }
      
      /**
       * @title SafeMath
       * @dev Math operations with safety checks that throw on error
       */
      library SafeMath {
        function mul(uint256 a, uint256 b) internal constant returns (uint256) {
          uint256 c = a * b;
          assert(a == 0 || c / a == b);
          return c;
        }
      
        function div(uint256 a, uint256 b) internal constant returns (uint256) {
          // assert(b > 0); // Solidity automatically throws when dividing by 0
          uint256 c = a / b;
          // assert(a == b * c + a % b); // There is no case in which this doesn't hold
          return c;
        }
      
        function sub(uint256 a, uint256 b) internal constant returns (uint256) {
          assert(b <= a);
          return a - b;
        }
      
        function add(uint256 a, uint256 b) internal constant returns (uint256) {
          uint256 c = a + b;
          assert(c >= a);
          return c;
        }
      }
      
      
      contract Token {
        /// @return total amount of tokens
        function totalSupply() constant returns (uint256 supply) {}
      
        /// @param _owner The address from which the balance will be retrieved
        /// @return The balance
        function balanceOf(address _owner) constant returns (uint256 balance) {}
      
        /// @notice send `_value` token to `_to` from `msg.sender`
        /// @param _to The address of the recipient
        /// @param _value The amount of token to be transferred
        /// @return Whether the transfer was successful or not
        function transfer(address _to, uint256 _value) returns (bool success) {}
      
        /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
        /// @param _from The address of the sender
        /// @param _to The address of the recipient
        /// @param _value The amount of token to be transferred
        /// @return Whether the transfer was successful or not
        function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {}
      
        /// @notice `msg.sender` approves `_addr` to spend `_value` tokens
        /// @param _spender The address of the account able to transfer the tokens
        /// @param _value The amount of wei to be approved for transfer
        /// @return Whether the approval was successful or not
        function approve(address _spender, uint256 _value) returns (bool success) {}
      
        /// @param _owner The address of the account owning tokens
        /// @param _spender The address of the account able to transfer the tokens
        /// @return Amount of remaining tokens allowed to spent
        function allowance(address _owner, address _spender) constant returns (uint256 remaining) {}
      
        event Transfer(address indexed _from, address indexed _to, uint256 _value);
        event Approval(address indexed _owner, address indexed _spender, uint256 _value);
      
        uint public decimals;
        string public name;
      }
      
      
      /**
       * @title Decentralized exchange for ether and ERC20 tokens.
       * @author Adam Lemmon <[email protected]>
       * @dev All trades brokered by this contract.
       * Orders submitted by off chain order book and this contract handles
       * verification and execution of orders.
       * All value between parties is transferred via this exchange.
       * Methods arranged by visibility; external, public, internal, private and alphabatized within.
       */
      contract Exchange is LoggingErrors {
      
        using SafeMath for uint256;
      
        /**
         * Data Structures
         */
        struct Order {
          bool active_;  // True: active, False: filled or cancelled
          address offerToken_;
          uint256 offerTokenTotal_;
          uint256 offerTokenRemaining_;  // Amount left to give
          address wantToken_;
          uint256 wantTokenTotal_;
          uint256 wantTokenReceived_;  // Amount received, note this may exceed want total
        }
      
        /**
         * Storage
         */
        address private orderBookAccount_;
        address private owner_;
        uint256 public minOrderEthAmount_;
        uint256 public birthBlock_;
        address public edoToken_;
        uint256 public edoPerWei_;
        uint256 public edoPerWeiDecimals_;
        address public eidooWallet_;
        mapping(bytes32 => Order) public orders_; // Map order hashes to order data struct
        mapping(address => address) public userAccountToWallet_; // User EOA to wallet addresses
      
        /**
         * Events
         */
        event LogEdoRateSet(uint256 rate);
        event LogOrderExecutionSuccess();
        event LogOrderFilled(bytes32 indexed orderId, uint256 fillAmount, uint256 fillRemaining);
        event LogUserAdded(address indexed user, address walletAddress);
        event LogWalletDeposit(address indexed walletAddress, address token, uint256 amount, uint256 balance);
        event LogWalletWithdrawal(address indexed walletAddress, address token, uint256 amount, uint256 balance);
      
        /**
         * @dev Contract constructor - CONFIRM matches contract name.  Set owner and addr of order book.
         * @param _bookAccount The EOA address for the order book, will submit ALL orders.
         * @param _minOrderEthAmount Minimum amount of ether that each order must contain.
         * @param _edoToken Deployed edo token.
         * @param _edoPerWei Rate of edo tokens per wei.
         * @param _edoPerWeiDecimals Decimlas carried in edo rate.
         * @param _eidooWallet Wallet to pay fees to.
         */
        function Exchange(
          address _bookAccount,
          uint256 _minOrderEthAmount,
          address _edoToken,
          uint256 _edoPerWei,
          uint256 _edoPerWeiDecimals,
          address _eidooWallet
        ) public {
          orderBookAccount_ = _bookAccount;
          minOrderEthAmount_ = _minOrderEthAmount;
          owner_ = msg.sender;
          birthBlock_ = block.number;
          edoToken_ = _edoToken;
          edoPerWei_ = _edoPerWei;
          edoPerWeiDecimals_ = _edoPerWeiDecimals;
          eidooWallet_ = _eidooWallet;
        }
      
        /**
         * @dev Fallback. wallets utilize to send ether in order to broker trade.
         */
        function () external payable { }
      
        /**
         * External
         */
      
        /**
         * @dev Add a new user to the exchange, create a wallet for them.
         * Map their account address to the wallet contract for lookup.
         * @param _userAccount The address of the user's EOA.
         * @return Success of the transaction, false if error condition met.
         */
        function addNewUser(address _userAccount)
          external
          returns (bool)
        {
          if (userAccountToWallet_[_userAccount] != address(0))
            return error('User already exists, Exchange.addNewUser()');
      
          // Pass the userAccount address to wallet constructor so owner is not the exchange contract
          address userWallet = new Wallet(_userAccount);
      
          userAccountToWallet_[_userAccount] = userWallet;
      
          LogUserAdded(_userAccount, userWallet);
      
          return true;
        }
      
        /**
         * Execute orders in batches.
         * @param  _token_and_EOA_Addresses Tokan and user addresses.
         * @param  _amountsExpirationAndSalt Offer and want token amount and expiration and salt values.
         * @param _sig_v All order signature v values.
         * @param _sig_r_and_s All order signature r and r values.
         * @return The success of this transaction.
         */
        function batchExecuteOrder(
          address[4][] _token_and_EOA_Addresses,
          uint256[8][] _amountsExpirationAndSalt, // Packing to save stack size
          uint8[2][] _sig_v,
          bytes32[4][] _sig_r_and_s
        ) external
          returns(bool)
        {
          for (uint256 i = 0; i < _amountsExpirationAndSalt.length; i++) {
            require(executeOrder(
              _token_and_EOA_Addresses[i],
              _amountsExpirationAndSalt[i],
              _sig_v[i],
              _sig_r_and_s[i]
            ));
          }
      
          return true;
        }
      
        /**
         * @dev Execute an order that was submitted by the external order book server.
         * The order book server believes it to be a match.
         * There are components for both orders, maker and taker, 2 signatures as well.
         * @param _token_and_EOA_Addresses The addresses of the maker and taker EOAs and offered token contracts.
         * [makerEOA, makerOfferToken, takerEOA, takerOfferToken]
         * @param _amountsExpirationAndSalt The amount of tokens, [makerOffer, makerWant, takerOffer, takerWant].
         * and the block number at which this order expires
         * and a random number to mitigate replay. [makerExpiry, makerSalt, takerExpiry, takerSalt]
         * @param _sig_v ECDSA signature parameter v, maker 0 and taker 1.
         * @param _sig_r_and_s ECDSA signature parameters r ans s, maker 0, 1 and taker 2, 3.
         * @return Success of the transaction, false if error condition met.
         * Like types grouped to eliminate stack depth error
         */
        function executeOrder (
          address[4] _token_and_EOA_Addresses,
          uint256[8] _amountsExpirationAndSalt, // Packing to save stack size
          uint8[2] _sig_v,
          bytes32[4] _sig_r_and_s
        ) public
          returns(bool)
        {
          // Only read wallet addresses from storage once
          // Need one more stack slot so squashing into array
          Wallet[2] memory wallets = [
            Wallet(userAccountToWallet_[_token_and_EOA_Addresses[0]]), // maker
            Wallet(userAccountToWallet_[_token_and_EOA_Addresses[2]]) // taker
          ];
      
          // Basic pre-conditions, return if any input data is invalid
          if(!__executeOrderInputIsValid__(
            _token_and_EOA_Addresses,
            _amountsExpirationAndSalt,
            wallets[0],
            wallets[1]
          ))
            return error('Input is invalid, Exchange.executeOrder()');
      
          // Verify Maker and Taker signatures
          bytes32 makerOrderHash;
          bytes32 takerOrderHash;
          (makerOrderHash, takerOrderHash) = __generateOrderHashes__(_token_and_EOA_Addresses, _amountsExpirationAndSalt);
      
          if (!__signatureIsValid__(
            _token_and_EOA_Addresses[0],
            makerOrderHash,
            _sig_v[0],
            _sig_r_and_s[0],
            _sig_r_and_s[1]
          ))
            return error('Maker signature is invalid, Exchange.executeOrder()');
      
          if (!__signatureIsValid__(
            _token_and_EOA_Addresses[2],
            takerOrderHash,
            _sig_v[1],
            _sig_r_and_s[2],
            _sig_r_and_s[3]
          ))
            return error('Taker signature is invalid, Exchange.executeOrder()');
      
          // Exchange Order Verification and matching.
          Order memory makerOrder = orders_[makerOrderHash];
          Order memory takerOrder = orders_[takerOrderHash];
      
          if (makerOrder.wantTokenTotal_ == 0) {  // Check for existence
            makerOrder.active_ = true;
            makerOrder.offerToken_ = _token_and_EOA_Addresses[1];
            makerOrder.offerTokenTotal_ = _amountsExpirationAndSalt[0];
            makerOrder.offerTokenRemaining_ = _amountsExpirationAndSalt[0]; // Amount to give
            makerOrder.wantToken_ = _token_and_EOA_Addresses[3];
            makerOrder.wantTokenTotal_ = _amountsExpirationAndSalt[1];
            makerOrder.wantTokenReceived_ = 0; // Amount received
          }
      
          if (takerOrder.wantTokenTotal_ == 0) {  // Check for existence
            takerOrder.active_ = true;
            takerOrder.offerToken_ = _token_and_EOA_Addresses[3];
            takerOrder.offerTokenTotal_ = _amountsExpirationAndSalt[2];
            takerOrder.offerTokenRemaining_ = _amountsExpirationAndSalt[2];  // Amount to give
            takerOrder.wantToken_ = _token_and_EOA_Addresses[1];
            takerOrder.wantTokenTotal_ = _amountsExpirationAndSalt[3];
            takerOrder.wantTokenReceived_ = 0; // Amount received
          }
      
          if (!__ordersMatch_and_AreVaild__(makerOrder, takerOrder))
            return error('Orders do not match, Exchange.executeOrder()');
      
          // Trade amounts
          uint256 toTakerAmount;
          uint256 toMakerAmount;
          (toTakerAmount, toMakerAmount) = __getTradeAmounts__(makerOrder, takerOrder);
      
          // TODO consider removing. Can this condition be met?
          if (toTakerAmount < 1 || toMakerAmount < 1)
            return error('Token amount < 1, price ratio is invalid! Token value < 1, Exchange.executeOrder()');
      
          // Taker is offering edo tokens so ensure sufficient balance in order to offer edo and pay fee in edo
          if (
              takerOrder.offerToken_ == edoToken_ &&
              Token(edoToken_).balanceOf(wallets[1]) < __calculateFee__(makerOrder, toTakerAmount, toMakerAmount).add(toMakerAmount)
            ) {
              return error('Taker has an insufficient EDO token balance to cover the fee AND the offer, Exchange.executeOrder()');
          // Taker has sufficent EDO token balance to pay the fee
          } else if (Token(edoToken_).balanceOf(wallets[1]) < __calculateFee__(makerOrder, toTakerAmount, toMakerAmount))
            return error('Taker has an insufficient EDO token balance to cover the fee, Exchange.executeOrder()');
      
          // Wallet Order Verification, reach out to the maker and taker wallets.
          if (!__ordersVerifiedByWallets__(
              _token_and_EOA_Addresses,
              toMakerAmount,
              toTakerAmount,
              wallets[0],
              wallets[1],
              __calculateFee__(makerOrder, toTakerAmount, toMakerAmount)
            ))
            return error('Order could not be verified by wallets, Exchange.executeOrder()');
      
          // Order Execution, Order Fully Verified by this point, time to execute!
          // Local order structs
          __updateOrders__(makerOrder, takerOrder, toTakerAmount, toMakerAmount);
      
          // Write to storage then external calls
          //  Update orders active flag if filled
          if (makerOrder.offerTokenRemaining_ == 0)
            makerOrder.active_ = false;
      
          if (takerOrder.offerTokenRemaining_ == 0)
            takerOrder.active_ = false;
      
          // Finally write orders to storage
          orders_[makerOrderHash] = makerOrder;
          orders_[takerOrderHash] = takerOrder;
      
          // Transfer the external value, ether <> tokens
          require(
            __executeTokenTransfer__(
              _token_and_EOA_Addresses,
              toTakerAmount,
              toMakerAmount,
              __calculateFee__(makerOrder, toTakerAmount, toMakerAmount),
              wallets[0],
              wallets[1]
            )
          );
      
          // Log the order id(hash), amount of offer given, amount of offer remaining
          LogOrderFilled(makerOrderHash, toTakerAmount, makerOrder.offerTokenRemaining_);
          LogOrderFilled(takerOrderHash, toMakerAmount, takerOrder.offerTokenRemaining_);
      
          LogOrderExecutionSuccess();
      
          return true;
        }
      
        /**
         * @dev Set the rate of wei per edo token in or to calculate edo fee
         * @param _edoPerWei Rate of edo tokens per wei.
         * @return Success of the transaction.
         */
        function setEdoRate(
          uint256 _edoPerWei
        ) external
          returns(bool)
        {
          if (msg.sender != owner_)
            return error('msg.sender != owner, Exchange.setEdoRate()');
      
          edoPerWei_ = _edoPerWei;
      
          LogEdoRateSet(edoPerWei_);
      
          return true;
        }
      
        /**
         * @dev Set the wallet for fees to be paid to.
         * @param _eidooWallet Wallet to pay fees to.
         * @return Success of the transaction.
         */
        function setEidooWallet(
          address _eidooWallet
        ) external
          returns(bool)
        {
          if (msg.sender != owner_)
            return error('msg.sender != owner, Exchange.setEidooWallet()');
      
          eidooWallet_ = _eidooWallet;
      
          return true;
        }
      
        /**
         * @dev Set the minimum amount of ether required per order.
         * @param _minOrderEthAmount Min amount of ether required per order.
         * @return Success of the transaction.
         */
        function setMinOrderEthAmount (
          uint256 _minOrderEthAmount
        ) external
          returns(bool)
        {
          if (msg.sender != owner_)
            return error('msg.sender != owner, Exchange.setMinOrderEtherAmount()');
      
          minOrderEthAmount_ = _minOrderEthAmount;
      
          return true;
        }
      
        /**
         * @dev Set a new order book account.
         * @param _account The new order book account.
         */
        function setOrderBookAcount (
          address _account
        ) external
          returns(bool)
        {
          if (msg.sender != owner_)
            return error('msg.sender != owner, Exchange.setOrderBookAcount()');
      
          orderBookAccount_ = _account;
          return true;
        }
      
        /*
         Methods to catch events from external contracts, user wallets primarily
         */
      
        /**
         * @dev Simply log the event to track wallet interaction off-chain
         * @param _token The address of the token that was deposited.
         * @param _amount The amount of the token that was deposited.
         * @param _walletBalance The updated balance of the wallet after deposit.
         */
        function walletDeposit(
          address _token,
          uint256 _amount,
          uint256 _walletBalance
        ) external
        {
          LogWalletDeposit(msg.sender, _token, _amount, _walletBalance);
        }
      
        /**
         * @dev Simply log the event to track wallet interaction off-chain
         * @param _token The address of the token that was deposited.
         * @param _amount The amount of the token that was deposited.
         * @param _walletBalance The updated balance of the wallet after deposit.
         */
        function walletWithdrawal(
          address _token,
          uint256 _amount,
          uint256 _walletBalance
        ) external
        {
          LogWalletWithdrawal(msg.sender, _token, _amount, _walletBalance);
        }
      
        /**
         * Private
         */
      
        /**
         * Calculate the fee for the given trade. Calculated as the set % of the wei amount
         * converted into EDO tokens using the manually set conversion ratio.
         * @param _makerOrder The maker order object.
         * @param _toTaker The amount of tokens going to the taker.
         * @param _toMaker The amount of tokens going to the maker.
         * @return The total fee to be paid in EDO tokens.
         */
        function __calculateFee__(
          Order _makerOrder,
          uint256 _toTaker,
          uint256 _toMaker
        ) private
          constant
          returns(uint256)
        {
          // weiAmount * (fee %) * (EDO/Wei) / (decimals in edo/wei) / (decimals in percentage)
          if (_makerOrder.offerToken_ == address(0)) {
            return _toTaker.mul(edoPerWei_).div(10**edoPerWeiDecimals_);
          } else {
            return _toMaker.mul(edoPerWei_).div(10**edoPerWeiDecimals_);
          }
        }
      
        /**
         * @dev Verify the input to order execution is valid.
         * @param _token_and_EOA_Addresses The addresses of the maker and taker EOAs and offered token contracts.
         * [makerEOA, makerOfferToken, takerEOA, takerOfferToken]
         * @param _amountsExpirationAndSalt The amount of tokens, [makerOffer, makerWant, takerOffer, takerWant].
         * as well as The block number at which this order expires, maker[4] and taker[6].
         * @return Success if all checks pass.
         */
        function __executeOrderInputIsValid__(
          address[4] _token_and_EOA_Addresses,
          uint256[8] _amountsExpirationAndSalt,
          address _makerWallet,
          address _takerWallet
        ) private
          constant
          returns(bool)
        {
          if (msg.sender != orderBookAccount_)
            return error('msg.sender != orderBookAccount, Exchange.__executeOrderInputIsValid__()');
      
          if (block.number > _amountsExpirationAndSalt[4])
            return error('Maker order has expired, Exchange.__executeOrderInputIsValid__()');
      
          if (block.number > _amountsExpirationAndSalt[6])
            return error('Taker order has expired, Exchange.__executeOrderInputIsValid__()');
      
          // Wallets
          if (_makerWallet == address(0))
            return error('Maker wallet does not exist, Exchange.__executeOrderInputIsValid__()');
      
          if (_takerWallet == address(0))
            return error('Taker wallet does not exist, Exchange.__executeOrderInputIsValid__()');
      
          // Tokens, addresses and amounts, ether exists
          if (_token_and_EOA_Addresses[1] != address(0) && _token_and_EOA_Addresses[3] != address(0))
            return error('Ether omitted! Is not offered by either the Taker or Maker, Exchange.__executeOrderInputIsValid__()');
      
          if (_token_and_EOA_Addresses[1] == address(0) && _token_and_EOA_Addresses[3] == address(0))
            return error('Taker and Maker offer token are both ether, Exchange.__executeOrderInputIsValid__()');
      
          if (
              _amountsExpirationAndSalt[0] == 0 ||
              _amountsExpirationAndSalt[1] == 0 ||
              _amountsExpirationAndSalt[2] == 0 ||
              _amountsExpirationAndSalt[3] == 0
            )
            return error('May not execute an order where token amount == 0, Exchange.__executeOrderInputIsValid__()');
      
          // Confirm order ether amount >= min amount
          // Maker
          uint256 minOrderEthAmount = minOrderEthAmount_; // Single storage read
          if (_token_and_EOA_Addresses[1] == 0 && _amountsExpirationAndSalt[0] < minOrderEthAmount)
            return error('Maker order does not meet the minOrderEthAmount_ of ether, Exchange.__executeOrderInputIsValid__()');
      
          // Taker
          if (_token_and_EOA_Addresses[3] == 0 && _amountsExpirationAndSalt[2] < minOrderEthAmount)
            return error('Taker order does not meet the minOrderEthAmount_ of ether, Exchange.__executeOrderInputIsValid__()');
      
          return true;
        }
      
        /**
         * @dev Execute the external transfer of tokens.
         * @param _token_and_EOA_Addresses The addresses of the maker and taker EOAs and offered token contracts.
         * [makerEOA, makerOfferToken, takerEOA, takerOfferToken]
         * @param _toTakerAmount The amount of tokens to transfer to the taker.
         * @param _toMakerAmount The amount of tokens to transfer to the maker.
         * @return Success if both wallets verify the order.
         */
        function __executeTokenTransfer__(
          address[4] _token_and_EOA_Addresses,
          uint256 _toTakerAmount,
          uint256 _toMakerAmount,
          uint256 _fee,
          Wallet _makerWallet,
          Wallet _takerWallet
        ) private
          returns (bool)
        {
          // Wallet mapping balances
          address makerOfferToken = _token_and_EOA_Addresses[1];
          address takerOfferToken = _token_and_EOA_Addresses[3];
      
          // Taker to pay fee before trading
          require(_takerWallet.updateBalance(edoToken_, _fee, true));  // Subtraction flag
          require(Token(edoToken_).transferFrom(_takerWallet, eidooWallet_, _fee));
      
          // Move the toTakerAmount from the maker to the taker
          require(_makerWallet.updateBalance(makerOfferToken, _toTakerAmount, true));  // Subtraction flag
            /*return error('Unable to subtract maker token from maker wallet, Exchange.__executeTokenTransfer__()');*/
      
          require(_takerWallet.updateBalance(makerOfferToken, _toTakerAmount, false));
            /*return error('Unable to add maker token to taker wallet, Exchange.__executeTokenTransfer__()');*/
      
          // Move the toMakerAmount from the taker to the maker
          require(_takerWallet.updateBalance(takerOfferToken, _toMakerAmount, true));  // Subtraction flag
            /*return error('Unable to subtract taker token from taker wallet, Exchange.__executeTokenTransfer__()');*/
      
          require(_makerWallet.updateBalance(takerOfferToken, _toMakerAmount, false));
            /*return error('Unable to add taker token to maker wallet, Exchange.__executeTokenTransfer__()');*/
      
          // Contract ether balances and token contract balances
          // Ether to the taker and tokens to the maker
          if (makerOfferToken == address(0)) {
            _takerWallet.transfer(_toTakerAmount);
            require(
              Token(takerOfferToken).transferFrom(_takerWallet, _makerWallet, _toMakerAmount)
            );
            assert(
              __tokenAndWalletBalancesMatch__(_makerWallet, _takerWallet, takerOfferToken)
            );
      
          // Ether to the maker and tokens to the taker
          } else if (takerOfferToken == address(0)) {
            _makerWallet.transfer(_toMakerAmount);
            require(
              Token(makerOfferToken).transferFrom(_makerWallet, _takerWallet, _toTakerAmount)
            );
            assert(
              __tokenAndWalletBalancesMatch__(_makerWallet, _takerWallet, makerOfferToken)
            );
      
          // Something went wrong one had to have been ether
          } else revert();
      
          return true;
        }
      
        /**
         * @dev compute the log10 of a given number, takes the floor, ie. 2.5 = 2
         * @param _number The number to compute the log 10 of.
         * @return The floored log 10.
         */
        function __flooredLog10__(uint _number)
          public
          constant
          returns (uint256)
        {
          uint unit = 0;
          while (_number / (10**unit) >= 10)
            unit++;
          return unit;
        }
      
        /**
         * @dev Calculates Keccak-256 hash of order with specified parameters.
         * @param _token_and_EOA_Addresses The addresses of the order, [makerEOA, makerOfferToken, makerWantToken].
         * @param _amountsExpirationAndSalt The amount of tokens as well as
         * the block number at which this order expires and random salt number.
         * @return Keccak-256 hash of each order.
         */
        function __generateOrderHashes__(
          address[4] _token_and_EOA_Addresses,
          uint256[8] _amountsExpirationAndSalt
        ) private
          constant
          returns (bytes32, bytes32)
        {
          bytes32 makerOrderHash = keccak256(
            address(this),
            _token_and_EOA_Addresses[0], // _makerEOA
            _token_and_EOA_Addresses[1], // offerToken
            _amountsExpirationAndSalt[0],  // offerTokenAmount
            _token_and_EOA_Addresses[3], // wantToken
            _amountsExpirationAndSalt[1],  // wantTokenAmount
            _amountsExpirationAndSalt[4], // expiry
            _amountsExpirationAndSalt[5] // salt
          );
      
      
          bytes32 takerOrderHash = keccak256(
            address(this),
            _token_and_EOA_Addresses[2], // _makerEOA
            _token_and_EOA_Addresses[3], // offerToken
            _amountsExpirationAndSalt[2],  // offerTokenAmount
            _token_and_EOA_Addresses[1], // wantToken
            _amountsExpirationAndSalt[3],  // wantTokenAmount
            _amountsExpirationAndSalt[6], // expiry
            _amountsExpirationAndSalt[7] // salt
          );
      
          return (makerOrderHash, takerOrderHash);
        }
      
        /**
         * @dev Returns the price ratio for this order.
         * The ratio is calculated with the largest value as the numerator, this aids
         * to significantly reduce rounding errors.
         * @param _makerOrder The maker order data structure.
         * @return The ratio to `_decimals` decimal places.
         */
        function __getOrderPriceRatio__(Order _makerOrder, uint256 _decimals)
          private
          constant
          returns (uint256 orderPriceRatio)
        {
          if (_makerOrder.offerTokenTotal_ >= _makerOrder.wantTokenTotal_) {
            orderPriceRatio = _makerOrder.offerTokenTotal_.mul(10**_decimals).div(_makerOrder.wantTokenTotal_);
          } else {
            orderPriceRatio = _makerOrder.wantTokenTotal_.mul(10**_decimals).div(_makerOrder.offerTokenTotal_);
          }
        }
      
        /**
         * @dev Compute the tradeable amounts of the two verified orders.
         * Token amount is the min remaining between want and offer of the two orders that isn't ether.
         * Ether amount is then: etherAmount = tokenAmount * priceRatio, as ratio = eth / token.
         * @param _makerOrder The maker order data structure.
         * @param _takerOrder The taker order data structure.
         * @return The amount moving from makerOfferRemaining to takerWantRemaining and vice versa.
         * TODO: consider rounding errors, etc
         */
        function __getTradeAmounts__(
          Order _makerOrder,
          Order _takerOrder
        ) private
          constant
          returns (uint256 toTakerAmount, uint256 toMakerAmount)
        {
          bool ratioIsWeiPerTok = __ratioIsWeiPerTok__(_makerOrder);
          uint256 decimals = __flooredLog10__(__max__(_makerOrder.offerTokenTotal_, _makerOrder.wantTokenTotal_)) + 1;
          uint256 priceRatio = __getOrderPriceRatio__(_makerOrder, decimals);
      
          // Amount left for order to receive
          uint256 makerAmountLeftToReceive = _makerOrder.wantTokenTotal_.sub(_makerOrder.wantTokenReceived_);
          uint256 takerAmountLeftToReceive = _takerOrder.wantTokenTotal_.sub(_takerOrder.wantTokenReceived_);
      
          // wei/tok and taker receiving wei or tok/wei and taker receiving tok
          if (
              ratioIsWeiPerTok && _takerOrder.wantToken_ == address(0) ||
              !ratioIsWeiPerTok && _takerOrder.wantToken_ != address(0)
          ) {
            // In the case that the maker is offering more than the taker wants for the same quantity being offered
            // For example: maker offer 20 wei for 10 tokens but taker offers 10 tokens for 10 wei
            // Taker receives 20 wei for the 10 tokens, both orders filled
            if (
              _makerOrder.offerTokenRemaining_ > takerAmountLeftToReceive &&
              makerAmountLeftToReceive <= _takerOrder.offerTokenRemaining_
            ) {
              toTakerAmount = __max__(_makerOrder.offerTokenRemaining_, takerAmountLeftToReceive);
            } else {
              toTakerAmount = __min__(_makerOrder.offerTokenRemaining_, takerAmountLeftToReceive);
            }
      
            toMakerAmount = toTakerAmount.mul(10**decimals).div(priceRatio);
      
          // wei/tok and maker receiving wei or tok/wei and maker receiving tok
          } else {
            toMakerAmount = __min__(_takerOrder.offerTokenRemaining_, makerAmountLeftToReceive);
            toTakerAmount = toMakerAmount.mul(10**decimals).div(priceRatio);
          }
        }
      
        /**
         * @dev Return the maximum of two uints
         * @param _a Uint 1
         * @param _b Uint 2
         * @return The grater value or a if equal
         */
        function __max__(uint256 _a, uint256 _b)
          private
          constant
          returns (uint256)
        {
          return _a < _b ? _b : _a;
        }
      
        /**
         * @dev Return the minimum of two uints
         * @param _a Uint 1
         * @param _b Uint 2
         * @return The smallest value or b if equal
         */
        function __min__(uint256 _a, uint256 _b)
          private
          constant
          returns (uint256)
        {
          return _a < _b ? _a : _b;
        }
      
        /**
         * @dev Define if the ratio to be used is wei/tok to tok/wei. Largest uint will
         * always act as the numerator.
         * @param _makerOrder The maker order object.
         * @return If the ratio is wei/tok or not.
         */
        function __ratioIsWeiPerTok__(Order _makerOrder)
          private
          constant
          returns (bool)
        {
          bool offerIsWei = _makerOrder.offerToken_ == address(0) ? true : false;
      
          // wei/tok
          if (offerIsWei && _makerOrder.offerTokenTotal_ >= _makerOrder.wantTokenTotal_) {
            return true;
      
          } else if (!offerIsWei && _makerOrder.wantTokenTotal_ >= _makerOrder.offerTokenTotal_) {
            return true;
      
          // tok/wei. otherwise wanting wei && offer > want, OR offer wei && want > offer
          } else {
            return false;
          }
        }
      
        /**
         * @dev Confirm that the orders do match and are valid.
         * @param _makerOrder The maker order data structure.
         * @param _takerOrder The taker order data structure.
         * @return Bool if the orders passes all checks.
         */
        function __ordersMatch_and_AreVaild__(
          Order _makerOrder,
          Order _takerOrder
        ) private
          constant
          returns (bool)
        {
          // Orders still active
          if (!_makerOrder.active_)
            return error('Maker order is inactive, Exchange.__ordersMatch_and_AreVaild__()');
      
          if (!_takerOrder.active_)
            return error('Taker order is inactive, Exchange.__ordersMatch_and_AreVaild__()');
      
          // Confirm tokens match
          // NOTE potentially omit as matching handled upstream?
          if (_makerOrder.wantToken_ != _takerOrder.offerToken_)
            return error('Maker wanted token does not match taker offer token, Exchange.__ordersMatch_and_AreVaild__()');
      
          if (_makerOrder.offerToken_ != _takerOrder.wantToken_)
            return error('Maker offer token does not match taker wanted token, Exchange.__ordersMatch_and_AreVaild__()');
      
          // Price Ratios, to x decimal places hence * decimals, dependent on the size of the denominator.
          // Ratios are relative to eth, amount of ether for a single token, ie. ETH / GNO == 0.2 Ether per 1 Gnosis
          uint256 orderPrice;  // The price the maker is willing to accept
          uint256 offeredPrice; // The offer the taker has given
          uint256 decimals = _makerOrder.offerToken_ == address(0) ? __flooredLog10__(_makerOrder.wantTokenTotal_) : __flooredLog10__(_makerOrder.offerTokenTotal_);
      
          // Ratio = larger amount / smaller amount
          if (_makerOrder.offerTokenTotal_ >= _makerOrder.wantTokenTotal_) {
            orderPrice = _makerOrder.offerTokenTotal_.mul(10**decimals).div(_makerOrder.wantTokenTotal_);
            offeredPrice = _takerOrder.wantTokenTotal_.mul(10**decimals).div(_takerOrder.offerTokenTotal_);
      
            // ie. Maker is offering 10 ETH for 100 GNO but taker is offering 100 GNO for 20 ETH, no match!
            // The taker wants more ether than the maker is offering.
            if (orderPrice < offeredPrice)
              return error('Taker price is greater than maker price, Exchange.__ordersMatch_and_AreVaild__()');
      
          } else {
            orderPrice = _makerOrder.wantTokenTotal_.mul(10**decimals).div(_makerOrder.offerTokenTotal_);
            offeredPrice = _takerOrder.offerTokenTotal_.mul(10**decimals).div(_takerOrder.wantTokenTotal_);
      
            // ie. Maker is offering 100 GNO for 10 ETH but taker is offering 5 ETH for 100 GNO, no match!
            // The taker is not offering enough ether for the maker
            if (orderPrice > offeredPrice)
              return error('Taker price is less than maker price, Exchange.__ordersMatch_and_AreVaild__()');
      
          }
      
          return true;
        }
      
        /**
         * @dev Ask each wallet to verify this order.
         * @param _token_and_EOA_Addresses The addresses of the maker and taker EOAs and offered token contracts.
         * [makerEOA, makerOfferToken, takerEOA, takerOfferToken]
         * @param _toMakerAmount The amount of tokens to be sent to the maker.
         * @param _toTakerAmount The amount of tokens to be sent to the taker.
         * @param _makerWallet The maker's wallet contract.
         * @param _takerWallet The taker's wallet contract.
         * @param _fee The fee to be paid for this trade, paid in full by taker.
         * @return Success if both wallets verify the order.
         */
        function __ordersVerifiedByWallets__(
          address[4] _token_and_EOA_Addresses,
          uint256 _toMakerAmount,
          uint256 _toTakerAmount,
          Wallet _makerWallet,
          Wallet _takerWallet,
          uint256 _fee
        ) private
          constant
          returns (bool)
        {
          // Have the transaction verified by both maker and taker wallets
          // confirm sufficient balance to transfer, offerToken and offerTokenAmount
          if(!_makerWallet.verifyOrder(_token_and_EOA_Addresses[1], _toTakerAmount, 0, 0))
            return error('Maker wallet could not verify the order, Exchange.__ordersVerifiedByWallets__()');
      
          if(!_takerWallet.verifyOrder(_token_and_EOA_Addresses[3], _toMakerAmount, _fee, edoToken_))
            return error('Taker wallet could not verify the order, Exchange.__ordersVerifiedByWallets__()');
      
          return true;
        }
      
        /**
         * @dev On chain verification of an ECDSA ethereum signature.
         * @param _signer The EOA address of the account that supposedly signed the message.
         * @param _orderHash The on-chain generated hash for the order.
         * @param _v ECDSA signature parameter v.
         * @param _r ECDSA signature parameter r.
         * @param _s ECDSA signature parameter s.
         * @return Bool if the signature is valid or not.
         */
        function __signatureIsValid__(
          address _signer,
          bytes32 _orderHash,
          uint8 _v,
          bytes32 _r,
          bytes32 _s
        ) private
          constant
          returns (bool)
        {
          address recoveredAddr = ecrecover(
            keccak256('\x19Ethereum Signed Message:\n32', _orderHash),
            _v, _r, _s
          );
      
          return recoveredAddr == _signer;
        }
      
        /**
         * @dev Confirm wallet local balances and token balances match.
         * @param _makerWallet  Maker wallet address.
         * @param _takerWallet  Taker wallet address.
         * @param _token  Token address to confirm balances match.
         * @return If the balances do match.
         */
        function __tokenAndWalletBalancesMatch__(
          address _makerWallet,
          address _takerWallet,
          address _token
        ) private
          constant
          returns(bool)
        {
          if (Token(_token).balanceOf(_makerWallet) != Wallet(_makerWallet).balanceOf(_token))
            return false;
      
          if (Token(_token).balanceOf(_takerWallet) != Wallet(_takerWallet).balanceOf(_token))
            return false;
      
          return true;
        }
      
        /**
         * @dev Update the order structs.
         * @param _makerOrder The maker order data structure.
         * @param _takerOrder The taker order data structure.
         * @param _toTakerAmount The amount of tokens to be moved to the taker.
         * @param _toTakerAmount The amount of tokens to be moved to the maker.
         * @return Success if the update succeeds.
         */
        function __updateOrders__(
          Order _makerOrder,
          Order _takerOrder,
          uint256 _toTakerAmount,
          uint256 _toMakerAmount
        ) private
        {
          // taker => maker
          _makerOrder.wantTokenReceived_ = _makerOrder.wantTokenReceived_.add(_toMakerAmount);
          _takerOrder.offerTokenRemaining_ = _takerOrder.offerTokenRemaining_.sub(_toMakerAmount);
      
          // maker => taker
          _takerOrder.wantTokenReceived_ = _takerOrder.wantTokenReceived_.add(_toTakerAmount);
          _makerOrder.offerTokenRemaining_ = _makerOrder.offerTokenRemaining_.sub(_toTakerAmount);
        }
      }
      
      interface BadERC20 {
        function transfer(address to, uint value) external;
        function transferFrom(address from, address to, uint256 value) external;
        function approve(address spender, uint value) external;
      }
      
      /**
       * @title Wallet logic library
       * @dev Standard logic universally used by wallets.
       */
      contract WalletLogic3 is LoggingErrors {
        using SafeMath for uint256;
      
        /**
         * Storage
         */
        // NOTE order of storage vars must match Wallet contract do not revise in later versions
        address public owner_;
        address public exchange_;
        mapping(address => uint256) public tokenBalances_;
      
        /**
         * Events
         */
        event LogDeposit(address token, uint256 amount, uint256 balance);
        event LogWithdrawal(address token, uint256 amount, uint256 balance);
      
        /**
         * Core library methods
         */
      
        /**
         * @dev Deposit ether or any ERC20 token into this wallet.
         * @param _token The address of the existing token contract, including ether.
         * @param _amount The amount of tokens to deposit.
         * @return If the funcion was successful.
         */
        function deposit (
          address _token,
          uint256 _amount
        ) external
          payable
          returns(bool)
        {
          // Ensure not depositing ERC20 and mistakenly sending ether along
          if (_token != 0 && msg.value > 0) {
            return error("ERC20 deposited but msg.value > 0, WalletLogic.deposit()");
          }
      
          if (_amount <= 0) {
            return error("Token amount must be greater than 0, WalletLogic.deposit()");
          }
      
          // NOT depositing ether, must transfer the tokens to the calling contract
          if (_token != address(0)) {
            require(
              safeTransferFrom(_token, msg.sender, this, _amount),
              "Cannot deposit token, WalletLogic.deposit()"
            );
          }
      
          tokenBalances_[_token] = tokenBalances_[_token].add(_amount);
          emit LogDeposit(_token, _amount, tokenBalances_[_token]);
      
          // Notify the exchange of the deposit
          Exchange(exchange_).walletDeposit(_token, _amount, tokenBalances_[_token]);
      
          return true;
        }
      
        /**
         * @dev Update the token balance within this wallet, increment or decrement.
         * @param _token The address of the token balance to update.
         * @param _amount The amount to update the balance by.
         * @param _subtractionFlag If true then subtract the token amount else add.
         * @return If the funcion was successful.
         */
        function updateBalance (
          address _token,
          uint256 _amount,
          bool _subtractionFlag
        ) external
          returns (bool)
        {
          if (msg.sender != exchange_) {
            return error("msg.sender != exchange, potential Exchange version mis-match, wallet must upgrade. WalletLogic.updateBalance()");
          }
      
          if (_amount == 0) {
            return error("Cannot update by 0, WalletLogic.updateBalance()");
          }
      
          // If sub then check the balance is sufficient in order to log the error
          if (_subtractionFlag) {
            if (tokenBalances_[_token] < _amount) {
              return error("Insufficient balance to subtract, WalletLogic.updateBalance()");
            }
      
            tokenBalances_[_token] = tokenBalances_[_token].sub(_amount);
          } else {
            tokenBalances_[_token] = tokenBalances_[_token].add(_amount);
          }
      
          return true;
        }
      
        /**
         * @dev Verify an order that the Exchange has received involving this wallet.
         * Internal checks and then authorize the exchange to move the tokens.
         * If sending ether then will transfer to the exchange to broker the trade.
         * @param _token The address of the token contract being bought / sold
         * ether is buying, always selling ERC20 tokens.
         * @param _amount The amount of tokens the order is for.
         * @param _fee The fee for the current trade.
         * @param _feeToken The token of which the fee is to be paid in.
         * @return If the funcion was successful.
         */
        function verifyOrder (
          address _token,
          uint256 _amount,
          uint256 _fee,
          address _feeToken
        ) external
          returns(bool)
        {
          if (msg.sender != exchange_) {
            return error("msg.sender != exchange, potential Exchange version mis-match, wallet must upgrade. WalletLogic.verifyOrder()");
          }
      
          if (tokenBalances_[_token] < _amount) {
            return error("Insufficient funds in local mapping, WalletLogic.verifyOrder()");
          }
      
          if (_fee > 0 && _token != _feeToken) {
            // If there is a fee, approve the exchange to `transferFrom` this wallet the fee amount of EDO tokens
            require(
              Token(_feeToken).approve(msg.sender, _fee),
              "Cannot approve the feeToken transfer when _token != _feeToken, WalletLogic.verifyOrder()"
            );
          }
      
          if (_fee > 0 && _token == _feeToken) {
            // Fee and amount offered are the same token so approve a summed amount
            require(
              Token(_feeToken).approve(msg.sender, _fee.add(_amount)),
              "Cannot approve the feeToken transfer when _token == _feeToken, WalletLogic.verifyOrder()"
            );
          } else if (_token != address(0)) {
            // Not ether or EDO, Approve the exchange to `transferFrom` this wallet
            if (Token(_token).balanceOf(this) < _amount) {
              // token.balanceOf and tokenBalances do not match!!
              return error("CRITICAL: Mismatch between balances, Insufficient funds in token contract, WalletLogic.verifyOrder()");
            }
            require(
              safeApprove(_token, msg.sender, _amount),
              "Cannot approve the token to be traded transfer, WalletLogic.verifyOrder()"
            );
          } else {
            // If ether this wallet must have suficient balance
            // If so, will send the ether to the exchange to broker the trade
            if (address(this).balance < _amount) {
              // this.balance and tokenBalances do not match!!
              return error("CRITICAL: Mismatch between balances, Insufficient ether balance in wallet contract, WalletLogic.verifyOrder()");
            }
      
            exchange_.transfer(_amount);
          }
      
          return true;
        }
      
        /**
         * @dev Withdraw any token, including ether from this wallet to an EOA.
         * @param _token The address of the token to withdraw.
         * @param _amount The amount to withdraw.
         * @return Success of the withdrawal.
         */
        function withdraw (
          address _token,
          uint256 _amount
        ) external
          returns(bool)
        {
          if(msg.sender != owner_) {
            return error("msg.sender != owner, WalletLogic.withdraw()");
          }
      
          if(_amount == 0) {
            return error("May not withdraw an amount of 0, WalletLogic.withdraw()");
          }
      
          if (_amount < tokenBalances_[_token]) {
            tokenBalances_[_token] = tokenBalances_[_token].sub(_amount);
          } else {
            tokenBalances_[_token] = 0;
          }
      
          if (_token == address(0)) {
            if (address(this).balance < _amount) {
              return error("Insufficient funds in wallet, WalletLogic.withdraw()");
            }
            msg.sender.transfer(_amount);
            // non required but can protect from balance misalignment
            tokenBalances_[address(0)] = address(this).balance;
          } else {
            if (Token(_token).balanceOf(this) < _amount) {
              return error("Insufficient funds in wallet, WalletLogic.withdraw()");
            }
            require(
              safeTransfer(_token, msg.sender, _amount),
              "safeTransfer failed, WalletLogic.withdraw()"
            );
            // non required but can protect from balance misalignment
            tokenBalances_[_token] = Token(_token).balanceOf(this);
          }
      
          emit LogWithdrawal(_token, _amount, tokenBalances_[_token]);
      
          // Notify the exchange of the withdrawal
          Exchange(exchange_).walletWithdrawal(_token, _amount, tokenBalances_[_token]);
      
          return true;
        }
      
        /**
         * @dev Wrapping the ERC20 transfer function to avoid missing returns.
         * @param _token The address of bad formed ERC20 token.
         * @param _to Transfer receiver.
         * @param _amount Amount to be transfered.
         * @return Success of the safeTransfer.
         */
      
        function safeTransfer(
          address _token,
          address _to,
          uint _amount
        )
          public
          returns (bool result)
        {
          BadERC20(_token).transfer(_to, _amount);
      
          assembly {
            switch returndatasize()
            case 0 {                      // This is our BadToken
              result := not(0)            // result is true
            }
            case 32 {                     // This is our GoodToken
              returndatacopy(0, 0, 32)
              result := mload(0)          // result == returndata of external call
            }
            default {                     // This is not an ERC20 token
              revert(0, 0)
            }
          }
        }
      
      
        /**
         * @dev Wrapping the ERC20 transferFrom function to avoid missing returns.
         * @param _token The address of bad formed ERC20 token.
         * @param _from Transfer sender.
         * @param _to Transfer receiver.
         * @param _value Amount to be transfered.
         * @return Success of the safeTransferFrom.
         */
      
        function safeTransferFrom(
          address _token,
          address _from,
          address _to,
          uint256 _value
        )
          public
          returns (bool result)
        {
          BadERC20(_token).transferFrom(_from, _to, _value);
      
          assembly {
            switch returndatasize()
            case 0 {                      // This is our BadToken
              result := not(0)            // result is true
            }
            case 32 {                     // This is our GoodToken
              returndatacopy(0, 0, 32)
              result := mload(0)          // result == returndata of external call
            }
            default {                     // This is not an ERC20 token
              revert(0, 0)
            }
          }
        }
      
        /**
         * @dev Wrapping the ERC20 approve function to avoid missing returns.
         * @param _token The address of bad formed ERC20 token.
         * @param _spender Spender address.
         * @param _value Amount allowed to be spent.
         * @return Success of the safeApprove.
         */
      
        function safeApprove(
          address _token,
          address _spender,
          uint256 _value
        )
          public
          returns (bool result)
        {
          BadERC20(_token).approve(_spender, _value);
      
          assembly {
            switch returndatasize()
            case 0 {                      // This is our BadToken
              result := not(0)            // result is true
            }
            case 32 {                     // This is our GoodToken
              returndatacopy(0, 0, 32)
              result := mload(0)          // result == returndata of external call
            }
            default {                     // This is not an ERC20 token
              revert(0, 0)
            }
          }
        }
      }