ETH Price: $2,660.99 (+1.55%)

Contract Diff Checker

Contract Name:
ExchangeV3

Contract Source Code:

File 1 of 1 : ExchangeV3

// File: contracts/utils/LoggingErrors.sol

pragma solidity ^0.4.11;

/**
 * @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) {
    emit LogErrorString(_errorMessage);
    return false;
  }
}

// File: contracts/wallet/WalletConnector.sol

pragma solidity ^0.4.15;


/**
 * @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];
  }
}

// File: contracts/token/ERC20Interface.sol

pragma solidity ^0.4.11;

interface Token {
  /// @return total amount of tokens
  function totalSupply() external constant returns (uint256 supply);

  /// @param _owner The address from which the balance will be retrieved
  /// @return The balance
  function balanceOf(address _owner) external 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) external 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) external 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) external 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) external constant returns (uint256 remaining);

  event Transfer(address indexed _from, address indexed _to, uint256 _value);
  event Approval(address indexed _owner, address indexed _spender, uint256 _value);

  function decimals() external constant returns(uint);
  function name() external constant returns(string);
}

// File: contracts/wallet/WalletV3.sol

pragma solidity ^0.4.15;




/**
 * @title Wallet to hold and trade ERC20 tokens and ether
 * @dev User wallet to interact with the exchange.
 * all tokens and ether held in this wallet, 1 to 1 mapping to user EOAs.
 */
contract WalletV3 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
   */
  constructor(address _owner, address _connector, address _exchange) public {
    owner_ = _owner;
    connector_ = WalletConnector(_connector);
    exchange_ = _exchange;
    logic_ = connector_.latestLogic_();
    birthBlock_ = block.number;
  }

  function () external payable {}

  /**
  * External
  */

  /**
   * @dev Deposit ether into this wallet, default to address 0 for consistent token lookup.
   */
  function depositEther()
    external
    payable
  {
    require(
      logic_.delegatecall(abi.encodeWithSignature('deposit(address,uint256)', 0, msg.value)),
      "depositEther() failed"
    );
  }

  /**
   * @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(abi.encodeWithSignature('deposit(address,uint256)', _token, _amount)),
      "depositERC20Token() failed"
    );
    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)
  {
    if (_token == address(0)) {
      return address(this).balance;
    } else {
      return Token(_token).balanceOf(this);
    }
  }

  function walletVersion() external pure returns(uint){
    return 3;
  }
}

// File: contracts/wallet/WalletBuilderInterface.sol

pragma solidity ^0.4.15;



/**
 * @title Wallet to hold and trade ERC20 tokens and ether
 */
interface WalletBuilderInterface {

  /**
   * @dev build a new trading wallet and returns its address
   * @param _owner user EOA of the created trading wallet
   * @param _exchange exchange address
   */
  function buildWallet(address _owner, address _exchange) external returns(address);
}

// File: contracts/utils/Ownable.sol

pragma solidity ^0.4.24;

contract Ownable {
  address public owner;

  function Ownable() public {
    owner = msg.sender;
  }

  modifier onlyOwner() {
    require(msg.sender == owner, "msg.sender != owner");
    _;
  }

  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != address(0), "newOwner == 0");
    owner = newOwner;
  }

}

// File: contracts/utils/SafeMath.sol

pragma solidity ^0.4.11;


/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a * b;
    assert(a == 0 || c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure 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 pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

// File: contracts/UsersManager.sol

pragma solidity ^0.4.24;







interface RetrieveWalletInterface {
  function retrieveWallet(address userAccount) external returns(address walletAddress);
}

contract UsersManager is Ownable, RetrieveWalletInterface {
  mapping(address => address) public userAccountToWallet_; // User EOA to wallet addresses
  WalletBuilderInterface public walletBuilder;
  RetrieveWalletInterface public previousMapping;

  event LogUserAdded(address indexed user, address walletAddress);
  event LogWalletUpgraded(address indexed user, address oldWalletAddress, address newWalletAddress);
  event LogWalletBuilderChanged(address newWalletBuilder);

  constructor (
    address _previousMappingAddress,
    address _walletBuilder
  ) public {
    require(_walletBuilder != address (0), "WalletConnector address == 0");
    previousMapping = RetrieveWalletInterface(_previousMappingAddress);
    walletBuilder = WalletBuilderInterface(_walletBuilder);
  }

  /**
   * 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) && address(previousMapping) != address(0)) {
      // Retrieve the wallet address from the old exchange.
      walletAddress = previousMapping.retrieveWallet(userAccount);

      if (walletAddress != address(0)) {
        userAccountToWallet_[userAccount] = walletAddress;
      }
    }
  }

  /**
 * @dev Private implementation for addNewUser function. Add a new user
 * into 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.
 * @param exchangeAddress The address of the exchange smart contract.
 * @return the created trading wallet address.
 */
  function __addNewUser(address userExternalOwnedAccount, address exchangeAddress)
    private
    returns (address)
  {
    address userTradingWallet = walletBuilder.buildWallet(userExternalOwnedAccount, exchangeAddress);
    userAccountToWallet_[userExternalOwnedAccount] = userTradingWallet;
    emit LogUserAdded(userExternalOwnedAccount, userTradingWallet);
    return userTradingWallet;
  }

  /**
   * @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)
  {
    require (
      retrieveWallet(userExternalOwnedAccount) == address(0),
      "User already exists, Exchange.addNewUser()"
    );

    // Create a new wallet passing EOA for owner and msg.sender for exchange. If the caller of this function is not the
    // exchange, the trading wallet is not operable and requires an updateExchange()
    __addNewUser(userExternalOwnedAccount, msg.sender);
    return true;
  }

  /**
   * @dev Recreate the trading wallet for the user calling the function.
   */
  function upgradeWallet() external
  {
    address oldWallet = retrieveWallet(msg.sender);
    require(
      oldWallet != address(0),
      "User does not exists yet, Exchange.upgradeWallet()"
    );
    address exchange = WalletV3(oldWallet).exchange_();
    address userTradingWallet = __addNewUser(msg.sender, exchange);
    emit LogWalletUpgraded(msg.sender, oldWallet, userTradingWallet);
  }

  /**
   * @dev Administratively changes the wallet address for a specific EOA.
   */
  function adminSetWallet(address userExternalOwnedAccount, address userTradingWallet)
    onlyOwner
    external
  {
    address oldWallet = retrieveWallet(userExternalOwnedAccount);
    userAccountToWallet_[userExternalOwnedAccount] = userTradingWallet;
    emit LogUserAdded(userExternalOwnedAccount, userTradingWallet);
    if (oldWallet != address(0)) {
      emit LogWalletUpgraded(userExternalOwnedAccount, oldWallet, userTradingWallet);
    }
  }

  /**
   * @dev Add a new user to the exchange, create a wallet for them.
   * Map their account address to the wallet contract for lookup.
   * @param newWalletBuilder The address of the new wallet builder
   * @return Success of the transaction, false if error condition met.
   */
  function setWalletBuilder(address newWalletBuilder)
    public
    onlyOwner
    returns (bool)
  {
    require(newWalletBuilder != address(0), "setWalletBuilder(): newWalletBuilder == 0");
    walletBuilder = WalletBuilderInterface(newWalletBuilder);
    emit LogWalletBuilderChanged(walletBuilder);
    return true;
  }
}

// File: contracts/utils/SafeERC20.sol

pragma solidity ^0.4.11;


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 SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeERC20 {

  event LogWarningNonZeroAllowance(address token, address spender, uint256 allowance);

  /**
   * @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
  )
  internal
  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
  )
  internal
  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)
      }
    }
  }

  function checkAndApprove(
    address _token,
    address _spender,
    uint256 _value
  )
  internal
  returns (bool result)
  {
    uint currentAllowance = Token(_token).allowance(this, _spender);
    if (currentAllowance > 0) {
      emit LogWarningNonZeroAllowance(_token, _spender, currentAllowance);
      // no check required for approve because it eventually will fail in the second approve
      safeApprove(_token, _spender, 0);
    }
    return safeApprove(_token, _spender, _value);
  }
  /**
   * @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
  )
  internal
  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)
      }
    }
  }
}

// File: contracts/ExchangeV3.sol

pragma solidity ^0.4.24;






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

  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
  }

  struct Orders {
    Order makerOrder;
    Order takerOrder;
    bool isMakerBuy;
  }

  struct FeeRate {
    uint256 edoPerQuote;
    uint256 edoPerQuoteDecimals;
  }

  struct Balances {
    uint256 makerWantTokenBalance;
    uint256 makerOfferTokenBalance;
    uint256 takerWantTokenBalance;
    uint256 takerOfferTokenBalance;
  }

  struct TradingWallets {
    WalletV3 maker;
    WalletV3 taker;
  }

  struct TradingAmounts {
    uint256 toTaker;
    uint256 toMaker;
    uint256 fee;
  }

  struct OrdersHashes {
    bytes32 makerOrder;
    bytes32 takerOrder;
  }

  /**
   * Storage
   */
  address private orderBookAccount_;
  address public owner_;
  address public feeManager_;
  uint256 public birthBlock_;
  address public edoToken_;
  uint256 public dustLimit = 100;

  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 => FeeRate)) public customFee;
  // whitelist of EOA that should not pay fee
  mapping(address => bool) public feeTakersWhitelist;

  /**
   * @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
  UsersManager public users;

  /**
   * Events
   */
  event LogFeeRateSet(address indexed token, uint256 rate, uint256 decimals);
  event LogQuotePrioritySet(address indexed quoteToken, uint256 priority);
  event LogCustomFeeSet(address indexed base, address indexed quote, uint256 edoPerQuote, uint256 edoPerQuoteDecimals);
  event LogFeeTakersWhitelistSet(address takerEOA, bool value);
  event LogWalletDeposit(address indexed walletAddress, address token, uint256 amount, uint256 balance);
  event LogWalletWithdrawal(address indexed walletAddress, address token, uint256 amount, uint256 balance);
  event LogWithdraw(address recipient, address token, uint256 amount);

  event LogOrderExecutionSuccess(
    bytes32 indexed makerOrderId,
    bytes32 indexed takerOrderId,
    uint256 toMaker,
    uint256 toTaker
  );
  event LogBatchOrderExecutionFailed(
    bytes32 indexed makerOrderId,
    bytes32 indexed takerOrderId,
    uint256 position
  );
  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 _usersMapperAddress Previous exchange smart contract address.
   */
  constructor (
    address _bookAccount,
    address _edoToken,
    uint256 _edoPerWei,
    uint256 _edoPerWeiDecimals,
    address _eidooWallet,
    address _usersMapperAddress
  ) 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;
    setUsersMapper(_usersMapperAddress);
  }

  /**
   * @dev Fallback. wallets utilize to send ether in order to broker trade.
   */
  function () external payable { }

  modifier onlyOwner() {
    require (
      msg.sender == owner_,
      "msg.sender != owner"
    );
    _;
  }

  function setUsersMapper(address _userMapperAddress)
    public
    onlyOwner
    returns (bool)
  {
    require(_userMapperAddress != address(0), "_userMapperAddress == 0");
    users = UsersManager(_userMapperAddress);
    return true;
  }

  function setFeeManager(address feeManager)
    public
    onlyOwner
  {
    feeManager_ = feeManager;
  }

  function setDustLimit(uint limit)
    public
    onlyOwner
  {
    dustLimit = limit;
  }

  /**
   * @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)
    external
    returns (bool)
  {
    return users.addNewUser(userExternalOwnedAccount);
  }

  /**
   * @dev For backward compatibility.
   * @param userExternalOwnedAccount The address of the user's EOA.
   * @return The address of the trading wallet
   */
  function userAccountToWallet_(address userExternalOwnedAccount) external returns(address)
  {
    return users.retrieveWallet(userExternalOwnedAccount);
  }

  function retrieveWallet(address userExternalOwnedAccount)
    external
    returns(address)
  {
    return users.retrieveWallet(userExternalOwnedAccount);
  }

  /**
   * 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)
  {
    require(
      msg.sender == orderBookAccount_,
      "msg.sender != orderBookAccount, Exchange.batchExecuteOrder()"
    );

    for (uint256 i = 0; i < amountsExpirationsAndSalts.length; i++) {
      // TODO: the following 3 lines requires solc 0.5.0
//      bytes memory payload = abi.encodeWithSignature("executeOrder(address[4],uint256[8],uint8[2],bytes32[4])",
//        ownedExternalAddressesAndTokenAddresses[i],
//        amountsExpirationsAndSalts[i],
//        vSignatures[i],
//        rAndSsignatures[i]
//      );
//      (bool success, bytes memory returnData) = address(this).call(payload);
//      if (!success || !bool(returnData)) {
      bool success = address(this).call(abi.encodeWithSignature("executeOrder(address[4],uint256[8],uint8[2],bytes32[4])",
        ownedExternalAddressesAndTokenAddresses[i],
        amountsExpirationsAndSalts[i],
        vSignatures[i],
        rAndSsignatures[i]
      ));
      if (!success) {
        OrdersHashes memory hashes = __generateOrderHashes__(
          ownedExternalAddressesAndTokenAddresses[i],
          amountsExpirationsAndSalts[i]
        );
        emit LogBatchOrderExecutionFailed(hashes.makerOrder, hashes.takerOrder, 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 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)
  {
    // get users trading wallets and check if they exist
    TradingWallets memory wallets =
      getMakerAndTakerTradingWallets(ownedExternalAddressesAndTokenAddresses);

    // Basic pre-conditions, return if any input data is invalid
    __executeOrderInputIsValid__(
      ownedExternalAddressesAndTokenAddresses,
      amountsExpirationsAndSalts
    );

    // Verify Maker and Taker signatures
    OrdersHashes memory hashes = __generateOrderHashes__(
      ownedExternalAddressesAndTokenAddresses,
      amountsExpirationsAndSalts
    );

    // Check maker order signature
    require(
      __signatureIsValid__(
      ownedExternalAddressesAndTokenAddresses[0],
        hashes.makerOrder,
        vSignatures[0],
        rAndSsignatures[0],
        rAndSsignatures[1]
      ),
      "Maker signature is invalid, Exchange.executeOrder()"
    );

    // Check taker order signature
    require(__signatureIsValid__(
        ownedExternalAddressesAndTokenAddresses[2],
        hashes.takerOrder,
        vSignatures[1],
        rAndSsignatures[2],
        rAndSsignatures[3]
      ),
      "Taker signature is invalid, Exchange.executeOrder()"
    );

    // Exchange Order Verification and matching
    Orders memory orders = __getOrders__(ownedExternalAddressesAndTokenAddresses, amountsExpirationsAndSalts, hashes);

    // Trade amounts and fee
    TradingAmounts memory amounts = __getTradeAmounts__(orders, ownedExternalAddressesAndTokenAddresses[2]);

    require(
      amounts.toTaker > 0 && amounts.toMaker > 0,
      "Token amount < 1, price ratio is invalid! Token value < 1, Exchange.executeOrder()"
    );

    // Update orders status
    orders.makerOrder.offerTokenRemaining_ = orders.makerOrder.offerTokenRemaining_.sub(amounts.toTaker);
    orders.makerOrder.wantTokenReceived_ = orders.makerOrder.wantTokenReceived_.add(amounts.toMaker);

    orders.takerOrder.offerTokenRemaining_ = orders.takerOrder.offerTokenRemaining_.sub(amounts.toMaker);
    orders.takerOrder.wantTokenReceived_ = orders.takerOrder.wantTokenReceived_.add(amounts.toTaker);

    // Write orders status to storage. If the remaining to trade in the order is below the limit,
    // cleanup the storage marking the order as completed.
    uint limit = dustLimit;
    if ((orders.makerOrder.offerTokenRemaining_ <= limit) ||
        (orders.isMakerBuy && (orders.makerOrder.wantTokenReceived_ + limit) >= orders.makerOrder.wantTokenTotal_)
    ) {
      orders_[hashes.makerOrder].offerTokenRemaining_ = 0;
      orders_[hashes.makerOrder].wantTokenReceived_ = 0;
    } else {
      orders_[hashes.makerOrder].offerTokenRemaining_ = orders.makerOrder.offerTokenRemaining_;
      orders_[hashes.makerOrder].wantTokenReceived_ = orders.makerOrder.wantTokenReceived_;
    }

    if ((orders.takerOrder.offerTokenRemaining_ <= limit) ||
        (!orders.isMakerBuy && (orders.takerOrder.wantTokenReceived_ + limit) >= orders.takerOrder.wantTokenTotal_)
    ) {
      orders_[hashes.takerOrder].offerTokenRemaining_ = 0;
      orders_[hashes.takerOrder].wantTokenReceived_ = 0;
    } else {
      orders_[hashes.takerOrder].offerTokenRemaining_ = orders.takerOrder.offerTokenRemaining_;
      orders_[hashes.takerOrder].wantTokenReceived_ = orders.takerOrder.wantTokenReceived_;
    }

    // Transfer the external value, ether <> tokens
    __executeTokenTransfer__(
      ownedExternalAddressesAndTokenAddresses,
      amounts,
      wallets
    );

    // Log the order id(hash), amount of offer given, amount of offer remaining
    emit LogOrderFilled(hashes.makerOrder, orders.makerOrder.offerTokenRemaining_, orders.makerOrder.wantTokenReceived_);
    emit LogOrderFilled(hashes.takerOrder, orders.takerOrder.offerTokenRemaining_, orders.takerOrder.wantTokenReceived_);
    emit LogOrderExecutionSuccess(hashes.makerOrder, hashes.takerOrder, amounts.toMaker, amounts.toTaker);

    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)
  {
    require(
      msg.sender == owner_ || msg.sender == feeManager_,
      "msg.sender != owner, Exchange.setFeeRate()"
    );

    require(
      quotePriority[_quoteToken] != 0,
      "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)
  {
    require(
      msg.sender == owner_,
      "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)
  {
    require(
      msg.sender == owner_,
      "msg.sender != owner, Exchange.setOrderBookAcount()"
    );
    orderBookAccount_ = account;
    return true;
  }

  /**
   * @dev Set custom fee for a pair. If both _edoPerQuote and _edoPerQuoteDecimals are 0 there is no custom fee.
   *      If _edoPerQuote is 0 and _edoPerQuoteDecimals is greater than 0 the resulting fee rate is 0 (disable fee for the pair).
   * @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 _edoPerQuote Rate
   * @param _edoPerQuoteDecimals Rate decimals
   */
  function setCustomFee (
    address _baseTokenAddress,
    address _quoteTokenAddress,
    uint256 _edoPerQuote,
    uint256 _edoPerQuoteDecimals
  ) external
    returns(bool)
  {
    // Preserving same owner check style
    require(
      msg.sender == owner_ || msg.sender == feeManager_,
      "msg.sender != owner, Exchange.setCustomFee()"
    );
    if (_edoPerQuote == 0 && _edoPerQuoteDecimals == 0) {
      delete customFee[_baseTokenAddress][_quoteTokenAddress];
    } else {
      customFee[_baseTokenAddress][_quoteTokenAddress] = FeeRate({
        edoPerQuote: _edoPerQuote,
        edoPerQuoteDecimals: _edoPerQuoteDecimals
      });
    }
    emit LogCustomFeeSet(_baseTokenAddress, _quoteTokenAddress, _edoPerQuote, _edoPerQuoteDecimals);
    return true;
  }

  /**
   *  For backward compatibility
   */
  function mustSkipFee(address base, address quote) external view returns(bool) {
    FeeRate storage rate = customFee[base][quote];
    return rate.edoPerQuote == 0 && rate.edoPerQuoteDecimals != 0;
  }

  /**
   * @dev Set a taker EOA in the fees whitelist
   * @param _takerEOA EOA address of the taker
   * @param _value true if the take should not pay fees
   *
   */
  function setFeeTakersWhitelist(
    address _takerEOA,
    bool _value
  ) external
    returns(bool)
  {
    require(
      msg.sender == owner_,
      "msg.sender != owner, Exchange.setFeeTakersWhitelist()"
    );
    feeTakersWhitelist[_takerEOA] = _value;
    emit LogFeeTakersWhitelistSet(_takerEOA, _value);
    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)
  {
    require(
      msg.sender == owner_,
      "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 DEPRECATED!
   * 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 DEPRECATED!
   * 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
   */

  /**
   * @dev Convenient function to resolve call stack size error in ExchangeV3
   * @param ownedExternalAddressesAndTokenAddresses -
   */
  function getMakerAndTakerTradingWallets(address[4] ownedExternalAddressesAndTokenAddresses)
    private
    returns (TradingWallets wallets)
  {
    wallets = TradingWallets(
      WalletV3(users.retrieveWallet(ownedExternalAddressesAndTokenAddresses[0])), // maker
      WalletV3(users.retrieveWallet(ownedExternalAddressesAndTokenAddresses[2])) // taker
    );

    // Operating on existing tradingWallets
    require(
      wallets.maker != address(0),
      "Maker wallet does not exist, Exchange.getMakerAndTakerTradingWallets()"
    );

    require(
      wallets.taker != address(0),
      "Taker wallet does not exist, Exchange.getMakerAndTakerTradingWallets()"
    );
  }

  function calculateFee(
    address base,
    address quote,
    uint256 quoteAmount,
    address takerEOA
  ) public
    view
    returns(uint256)
  {
    require(quotePriority[quote] > quotePriority[base], "Invalid pair");
    return __calculateFee__(base, quote, quoteAmount, takerEOA);
  }

  function __calculateFee__(
    address base,
    address quote,
    uint256 quoteAmount,
    address takerEOA
  )
    internal view returns(uint256)
  {
    FeeRate memory fee;
    if (feeTakersWhitelist[takerEOA]) {
      return 0;
    }

    // weiAmount * (fee %) * (EDO/Wei) / (decimals in edo/wei) / (decimals in percentage)
      fee = customFee[base][quote];
      if (fee.edoPerQuote == 0 && fee.edoPerQuoteDecimals == 0) {
        // no custom fee
        fee.edoPerQuote = feeEdoPerQuote[quote];
        fee.edoPerQuoteDecimals = feeEdoPerQuoteDecimals[quote];
      }
      return quoteAmount.mul(fee.edoPerQuote).div(10**fee.edoPerQuoteDecimals);
  }

  /**
   * @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
  ) private view
  {
    // msg.send needs to be the orderBookAccount
    require(
      msg.sender == orderBookAccount_ || msg.sender == address(this),
      "msg.sender != orderBookAccount, Exchange.__executeOrderInputIsValid__()"
    );

    // Check expirations base on the block number
    require (
      block.number <= amountsExpirationsAndSalts[4],
      "Maker order has expired, Exchange.__executeOrderInputIsValid__()"
    );

    require(
      block.number <= amountsExpirationsAndSalts[6],
      "Taker order has expired, Exchange.__executeOrderInputIsValid__()"
    );

    require(
      quotePriority[ownedExternalAddressesAndTokenAddresses[1]] != quotePriority[ownedExternalAddressesAndTokenAddresses[3]],
      "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
      )
    {
      revert("May not execute an order where token amount == 0, Exchange.__executeOrderInputIsValid__()");
    }
  }

  function __getBalance__(address token, address owner) private view returns(uint256) {
    if (token == address(0)) {
      return owner.balance;
    } else {
      return Token(token).balanceOf(owner);
    }
  }

  /**
   * @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 amounts The amount of tokens to transfer.
   * @return Success if both wallets verify the order.
   */
  function __executeTokenTransfer__(
    address[4] ownedExternalAddressesAndTokenAddresses,
    TradingAmounts amounts,
    TradingWallets wallets
  ) private
  {

    // Get balances. Must be taken before trading wallet verifyOrder() call because it can transfer ethers
    Balances memory initialBalances;
    initialBalances.takerOfferTokenBalance = __getBalance__(ownedExternalAddressesAndTokenAddresses[3], wallets.taker);
    initialBalances.makerOfferTokenBalance = __getBalance__(ownedExternalAddressesAndTokenAddresses[1], wallets.maker);
    initialBalances.takerWantTokenBalance = __getBalance__(ownedExternalAddressesAndTokenAddresses[1], wallets.taker);
    initialBalances.makerWantTokenBalance = __getBalance__(ownedExternalAddressesAndTokenAddresses[3], wallets.maker);
    //    initialBalances.takerFeeTokenBalance = __getBalance__(edoToken_, wallets.taker);


    // Wallet Order Verification, reach out to the maker and taker wallets. Approve the tokens and transfer ethers
    // to the exchange contract
    require(
      wallets.maker.verifyOrder(
        ownedExternalAddressesAndTokenAddresses[1],
        amounts.toTaker,
        0,
        0
      ),
      "Maker wallet could not prepare the transfer, Exchange.__executeTokenTransfer__()"
    );

    require(
      wallets.taker.verifyOrder(
        ownedExternalAddressesAndTokenAddresses[3],
        amounts.toMaker,
        amounts.fee,
        edoToken_
      ),
      "Taker wallet could not prepare the transfer, Exchange.__executeTokenTransfer__()"
    );

    // Wallet mapping balances
    address makerOfferTokenAddress = ownedExternalAddressesAndTokenAddresses[1];
    address takerOfferTokenAddress = ownedExternalAddressesAndTokenAddresses[3];

    WalletV3 makerTradingWallet = wallets.maker;
    WalletV3 takerTradingWallet = wallets.taker;

    // Taker to pay fee before trading
    if(amounts.fee != 0) {
      uint256 takerInitialFeeTokenBalance = Token(edoToken_).balanceOf(takerTradingWallet);

      require(
        Token(edoToken_).transferFrom(takerTradingWallet, eidooWallet_, amounts.fee),
        "Cannot transfer fees from taker trading wallet to eidoo wallet, Exchange.__executeTokenTransfer__()"
      );
      require(
        Token(edoToken_).balanceOf(takerTradingWallet) == takerInitialFeeTokenBalance.sub(amounts.fee),
        "Wrong fee token balance after transfer, Exchange.__executeTokenTransfer__()"
      );
    }

    // Ether to the taker and tokens to the maker
    if (makerOfferTokenAddress == address(0)) {
      address(takerTradingWallet).transfer(amounts.toTaker);
    } else {
      require(
        SafeERC20.safeTransferFrom(makerOfferTokenAddress, makerTradingWallet, takerTradingWallet, amounts.toTaker),
        "Token transfership from makerTradingWallet to takerTradingWallet failed, Exchange.__executeTokenTransfer__()"
      );
    }

    if (takerOfferTokenAddress == address(0)) {
      address(makerTradingWallet).transfer(amounts.toMaker);
    } else {
      require(
        SafeERC20.safeTransferFrom(takerOfferTokenAddress, takerTradingWallet, makerTradingWallet, amounts.toMaker),
        "Token transfership from takerTradingWallet to makerTradingWallet failed, Exchange.__executeTokenTransfer__()"
      );
    }

    // Check balances
    Balances memory expected;
    if (takerTradingWallet != makerTradingWallet) {
      expected.makerWantTokenBalance = initialBalances.makerWantTokenBalance.add(amounts.toMaker);
      expected.makerOfferTokenBalance = initialBalances.makerOfferTokenBalance.sub(amounts.toTaker);
      expected.takerWantTokenBalance = edoToken_ == makerOfferTokenAddress
        ? initialBalances.takerWantTokenBalance.add(amounts.toTaker).sub(amounts.fee)
        : initialBalances.takerWantTokenBalance.add(amounts.toTaker);
      expected.takerOfferTokenBalance = edoToken_ == takerOfferTokenAddress
        ? initialBalances.takerOfferTokenBalance.sub(amounts.toMaker).sub(amounts.fee)
        : initialBalances.takerOfferTokenBalance.sub(amounts.toMaker);
    } else {
      expected.makerWantTokenBalance = expected.takerOfferTokenBalance =
        edoToken_ == takerOfferTokenAddress
        ? initialBalances.takerOfferTokenBalance.sub(amounts.fee)
        : initialBalances.takerOfferTokenBalance;
      expected.makerOfferTokenBalance = expected.takerWantTokenBalance =
        edoToken_ == makerOfferTokenAddress
        ? initialBalances.takerWantTokenBalance.sub(amounts.fee)
        : initialBalances.takerWantTokenBalance;
    }

    require(
      expected.takerOfferTokenBalance == __getBalance__(takerOfferTokenAddress, takerTradingWallet),
      "Wrong taker offer token balance after transfer, Exchange.__executeTokenTransfer__()"
    );
    require(
      expected.makerOfferTokenBalance == __getBalance__(makerOfferTokenAddress, makerTradingWallet),
      "Wrong maker offer token balance after transfer, Exchange.__executeTokenTransfer__()"
    );
    require(
      expected.takerWantTokenBalance == __getBalance__(makerOfferTokenAddress, takerTradingWallet),
      "Wrong taker want token balance after transfer, Exchange.__executeTokenTransfer__()"
    );
    require(
      expected.makerWantTokenBalance == __getBalance__(takerOfferTokenAddress, makerTradingWallet),
      "Wrong maker want token balance after transfer, Exchange.__executeTokenTransfer__()"
    );
  }

  /**
   * @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])
  {
    OrdersHashes memory hashes = __generateOrderHashes__(
      ownedExternalAddressesAndTokenAddresses,
      amountsExpirationsAndSalts
    );
    return [hashes.makerOrder, hashes.takerOrder];
  }

  function __generateOrderHashes__(
    address[4] ownedExternalAddressesAndTokenAddresses,
    uint256[8] amountsExpirationsAndSalts
  ) internal
    view
    returns (OrdersHashes)
  {
    bytes32 makerOrderHash = keccak256(abi.encodePacked(
      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(abi.encodePacked(
      address(this),
      ownedExternalAddressesAndTokenAddresses[2], // _makerEOA
      ownedExternalAddressesAndTokenAddresses[3], // offerToken
      amountsExpirationsAndSalts[2],  // offerTokenAmount
      ownedExternalAddressesAndTokenAddresses[1], // wantToken
      amountsExpirationsAndSalts[3],  // wantTokenAmount
      amountsExpirationsAndSalts[6], // expiry
      amountsExpirationsAndSalts[7] // salt
    ));

    return OrdersHashes(makerOrderHash, takerOrderHash);
  }

  function __getOrders__(
    address[4] ownedExternalAddressesAndTokenAddresses,
    uint256[8] amountsExpirationsAndSalts,
    OrdersHashes hashes
  ) private
    returns(Orders orders)
  {
    OrderStatus storage makerOrderStatus = orders_[hashes.makerOrder];
    OrderStatus storage takerOrderStatus = orders_[hashes.takerOrder];

    orders.makerOrder.offerToken_ = ownedExternalAddressesAndTokenAddresses[1];
    orders.makerOrder.offerTokenTotal_ = amountsExpirationsAndSalts[0];
    orders.makerOrder.wantToken_ = ownedExternalAddressesAndTokenAddresses[3];
    orders.makerOrder.wantTokenTotal_ = amountsExpirationsAndSalts[1];

    if (makerOrderStatus.expirationBlock_ > 0) {  // Check for existence
      // Orders still active
      require(
        makerOrderStatus.offerTokenRemaining_ != 0,
        "Maker order is inactive, Exchange.executeOrder()"
      );
      orders.makerOrder.offerTokenRemaining_ = makerOrderStatus.offerTokenRemaining_; // Amount to give
      orders.makerOrder.wantTokenReceived_ = makerOrderStatus.wantTokenReceived_; // Amount received
    } else {
      makerOrderStatus.expirationBlock_ = amountsExpirationsAndSalts[4]; // maker order expiration block, persist order on storage
      orders.makerOrder.offerTokenRemaining_ = amountsExpirationsAndSalts[0]; // Amount to give
      orders.makerOrder.wantTokenReceived_ = 0; // Amount received
    }

    orders.takerOrder.offerToken_ = ownedExternalAddressesAndTokenAddresses[3];
    orders.takerOrder.offerTokenTotal_ = amountsExpirationsAndSalts[2];
    orders.takerOrder.wantToken_ = ownedExternalAddressesAndTokenAddresses[1];
    orders.takerOrder.wantTokenTotal_ = amountsExpirationsAndSalts[3];

    if (takerOrderStatus.expirationBlock_ > 0) {  // Check for existence
      require(
        takerOrderStatus.offerTokenRemaining_ != 0,
        "Taker order is inactive, Exchange.executeOrder()"
      );
      orders.takerOrder.offerTokenRemaining_ = takerOrderStatus.offerTokenRemaining_;  // Amount to give
      orders.takerOrder.wantTokenReceived_ = takerOrderStatus.wantTokenReceived_; // Amount received
    } else {
      takerOrderStatus.expirationBlock_ = amountsExpirationsAndSalts[6]; // taker order expiration block, persist order on storage
      orders.takerOrder.offerTokenRemaining_ = amountsExpirationsAndSalts[2];  // Amount to give
      orders.takerOrder.wantTokenReceived_ = 0; // Amount received
    }

    orders.isMakerBuy = __isSell__(orders.takerOrder);
  }

  /**
   * @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 orders The maker and taker orders data structure.
   * @return The amount moving from makerOfferRemaining to takerWantRemaining and vice versa.
   */
  function __getTradeAmounts__(
    Orders memory orders,
    address takerEOA
  ) internal
    view
    returns (TradingAmounts)
  {
    Order memory makerOrder = orders.makerOrder;
    Order memory takerOrder = orders.takerOrder;
    bool isMakerBuy = orders.isMakerBuy;  // 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_);
      require(
        priceRatio >= takerOrder.wantTokenTotal_.mul(2**128).div(takerOrder.offerTokenTotal_),
        "Taker price is greater than maker price, Exchange.__getTradeAmounts__()"
      );
      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).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_);
      require(
        priceRatio <= takerOrder.offerTokenTotal_.mul(2**128).div(takerOrder.wantTokenTotal_),
        "Taker price is less than maker price, Exchange.__getTradeAmounts__()"
      );
      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).div(2**128);
      }
    }

    uint fee = isMakerBuy
      ? __calculateFee__(makerOrder.wantToken_, makerOrder.offerToken_, toTakerAmount, takerEOA)
      : __calculateFee__(makerOrder.offerToken_, makerOrder.wantToken_, toMakerAmount, takerEOA);

    return TradingAmounts(toTakerAmount, toMakerAmount, fee);
  }

  /**
   * @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 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) != WalletV3(makerTradingWallet).balanceOf(token)) {
      return false;
    }

    if (Token(token).balanceOf(takerTradingWallet) != WalletV3(takerTradingWallet).balanceOf(token)) {
      return false;
    }

    return true;
  }

  /**
 * @dev Withdraw asset.
 * @param _tokenAddress Asset to be withdrawed.
 * @return bool.
 */
  function withdraw(address _tokenAddress)
    public
    onlyOwner
  returns(bool)
  {
    uint tokenBalance;
    if (_tokenAddress == address(0)) {
      tokenBalance = address(this).balance;
      msg.sender.transfer(tokenBalance);
    } else {
      tokenBalance = Token(_tokenAddress).balanceOf(address(this));
      require(
        Token(_tokenAddress).transfer(msg.sender, tokenBalance),
        "withdraw transfer failed"
      );
    }
    emit LogWithdraw(msg.sender, _tokenAddress, tokenBalance);
    return true;
  }

}

Please enter a contract address above to load the contract details and source code.

Context size (optional):