Transaction Hash:
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 | ||
---|---|---|---|---|---|
0x342653de...9d69a74bb | |||||
0x5A0b54D5...D3E029c4c
Miner
| (Spark Pool) | 3,594.299397026337073253 Eth | 3,594.299862067137073253 Eth | 0.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 )
-
depositERC20Token[WalletV2 (ln:200)]
error[WalletV2 (ln:208)]
delegatecall[WalletV2 (ln:210)]
sha3[WalletV2 (ln:210)]
File 1 of 4: WalletV2
File 2 of 4: EidooToken
File 3 of 4: ExchangeV2
File 4 of 4: WalletLogic3
/** *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) } } } }