diff options
author | Alex Browne <stephenalexbrowne@gmail.com> | 2018-08-14 09:42:09 +0800 |
---|---|---|
committer | Alex Browne <stephenalexbrowne@gmail.com> | 2018-08-14 09:42:09 +0800 |
commit | 88766a02c7e6688e72d5c4c69ce68028b322f154 (patch) | |
tree | fa06552a80249e7998691b64df6b3b2827f9f947 /packages/contracts/src | |
parent | 8162394797342cef268cc8072fc860326974e269 (diff) | |
parent | fadd292ecf367e42154856509d0ea0c20b23f2f1 (diff) | |
download | dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.gz dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.bz2 dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.lz dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.xz dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.zst dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.zip |
Merge branch 'development'
Diffstat (limited to 'packages/contracts/src')
100 files changed, 8483 insertions, 232 deletions
diff --git a/packages/contracts/src/contracts/current/tutorials/Arbitrage/Arbitrage.sol b/packages/contracts/src/1.0.0/Arbitrage/Arbitrage.sol index a9f3c22e6..5054afc2f 100644 --- a/packages/contracts/src/contracts/current/tutorials/Arbitrage/Arbitrage.sol +++ b/packages/contracts/src/1.0.0/Arbitrage/Arbitrage.sol @@ -1,9 +1,9 @@ pragma solidity ^0.4.19; -import { Exchange } from "../../protocol/Exchange/Exchange.sol"; +import { IExchange_v1 as Exchange } from "../Exchange/IExchange_v1.sol"; import { EtherDelta } from "../EtherDelta/EtherDelta.sol"; -import { Ownable } from "../../utils/Ownable/Ownable.sol"; -import { Token } from "../../tokens/Token/Token.sol"; +import { Ownable_v1 as Ownable } from "../Ownable/Ownable_v1.sol"; +import { IToken_v1 as Token } from "../Token/IToken_v1.sol"; /// @title Arbitrage - Facilitates atomic arbitrage of ERC20 tokens between EtherDelta and 0x Exchange contract. /// @author Leonid Logvinov - <leo@0xProject.com> diff --git a/packages/contracts/src/contracts/previous/ERC20Token/ERC20Token_v1.sol b/packages/contracts/src/1.0.0/ERC20Token/ERC20Token_v1.sol index e05ee2d5e..e05ee2d5e 100644 --- a/packages/contracts/src/contracts/previous/ERC20Token/ERC20Token_v1.sol +++ b/packages/contracts/src/1.0.0/ERC20Token/ERC20Token_v1.sol diff --git a/packages/contracts/src/contracts/current/tutorials/EtherDelta/AccountLevels.sol b/packages/contracts/src/1.0.0/EtherDelta/AccountLevels.sol index 8d7a930d3..8d7a930d3 100644 --- a/packages/contracts/src/contracts/current/tutorials/EtherDelta/AccountLevels.sol +++ b/packages/contracts/src/1.0.0/EtherDelta/AccountLevels.sol diff --git a/packages/contracts/src/contracts/current/tutorials/EtherDelta/EtherDelta.sol b/packages/contracts/src/1.0.0/EtherDelta/EtherDelta.sol index 49847ab48..fe599ca0a 100644 --- a/packages/contracts/src/contracts/current/tutorials/EtherDelta/EtherDelta.sol +++ b/packages/contracts/src/1.0.0/EtherDelta/EtherDelta.sol @@ -1,8 +1,8 @@ pragma solidity ^0.4.19; -import { SafeMath } from "../../utils/SafeMath/SafeMath.sol"; +import { SafeMath } from "../SafeMath/SafeMath_v1.sol"; import { AccountLevels } from "./AccountLevels.sol"; -import { Token } from "../../tokens/Token/Token.sol"; +import { Token } from "../Token/Token_v1.sol"; contract EtherDelta is SafeMath { address public admin; //the admin address diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol b/packages/contracts/src/1.0.0/Exchange/Exchange_v1.sol index 8dacf797c..3f8e7368d 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol +++ b/packages/contracts/src/1.0.0/Exchange/Exchange_v1.sol @@ -1,6 +1,6 @@ /* - Copyright 2017 ZeroEx Intl. + Copyright 2018 ZeroEx Intl. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,13 +18,13 @@ pragma solidity ^0.4.14; -import { TokenTransferProxy } from "../TokenTransferProxy/TokenTransferProxy.sol"; -import { Token_v1 as Token } from "../../../previous/Token/Token_v1.sol"; -import { SafeMath_v1 as SafeMath } from "../../../previous/SafeMath/SafeMath_v1.sol"; +import { TokenTransferProxy_v1 as TokenTransferProxy } from "../TokenTransferProxy/TokenTransferProxy_v1.sol"; +import { Token_v1 as Token } from "../Token/Token_v1.sol"; +import { SafeMath_v1 as SafeMath } from "../SafeMath/SafeMath_v1.sol"; /// @title Exchange - Facilitates exchange of ERC20 tokens. /// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com> -contract Exchange is SafeMath { +contract Exchange_v1 is SafeMath { // Error Codes enum Errors { @@ -85,7 +85,7 @@ contract Exchange is SafeMath { bytes32 orderHash; } - function Exchange(address _zrxToken, address _tokenTransferProxy) { + function Exchange_v1(address _zrxToken, address _tokenTransferProxy) { ZRX_TOKEN_CONTRACT = _zrxToken; TOKEN_TRANSFER_PROXY_CONTRACT = _tokenTransferProxy; } diff --git a/packages/contracts/src/1.0.0/Exchange/IExchange_v1.sol b/packages/contracts/src/1.0.0/Exchange/IExchange_v1.sol new file mode 100644 index 000000000..ec4bce7b0 --- /dev/null +++ b/packages/contracts/src/1.0.0/Exchange/IExchange_v1.sol @@ -0,0 +1,226 @@ +pragma solidity ^0.4.19; + +contract IExchange_v1 { + + // Error Codes + enum Errors { + ORDER_EXPIRED, // Order has already expired + ORDER_FULLY_FILLED_OR_CANCELLED, // Order has already been fully filled or cancelled + ROUNDING_ERROR_TOO_LARGE, // Rounding error too large + INSUFFICIENT_BALANCE_OR_ALLOWANCE // Insufficient balance or allowance for token transfer + } + + event LogError(uint8 indexed errorId, bytes32 indexed orderHash); + + event LogFill( + address indexed maker, + address taker, + address indexed feeRecipient, + address makerToken, + address takerToken, + uint filledMakerTokenAmount, + uint filledTakerTokenAmount, + uint paidMakerFee, + uint paidTakerFee, + bytes32 indexed tokens, // keccak256(makerToken, takerToken), allows subscribing to a token pair + bytes32 orderHash + ); + + event LogCancel( + address indexed maker, + address indexed feeRecipient, + address makerToken, + address takerToken, + uint cancelledMakerTokenAmount, + uint cancelledTakerTokenAmount, + bytes32 indexed tokens, + bytes32 orderHash + ); + + function ZRX_TOKEN_CONTRACT() + public view + returns (address); + + function TOKEN_TRANSFER_PROXY_CONTRACT() + public view + returns (address); + + function EXTERNAL_QUERY_GAS_LIMIT() + public view + returns (uint16); + + function VERSION() + public view + returns (string); + + function filled(bytes32) + public view + returns (uint256); + + function cancelled(bytes32) + public view + returns (uint256); + + /// @dev Calculates the sum of values already filled and cancelled for a given order. + /// @param orderHash The Keccak-256 hash of the given order. + /// @return Sum of values already filled and cancelled. + function getUnavailableTakerTokenAmount(bytes32 orderHash) + public constant + returns (uint); + + /// @dev Calculates partial value given a numerator and denominator. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to calculate partial of. + /// @return Partial value of target. + function getPartialAmount(uint numerator, uint denominator, uint target) + public constant + returns (uint); + + /// @dev Checks if rounding error > 0.1%. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to multiply with numerator/denominator. + /// @return Rounding error is present. + function isRoundingError(uint numerator, uint denominator, uint target) + public constant + returns (bool); + + /// @dev Calculates Keccak-256 hash of order with specified parameters. + /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient. + /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt. + /// @return Keccak-256 hash of order. + function getOrderHash(address[5] orderAddresses, uint[6] orderValues) + public + constant + returns (bytes32); + + /// @dev Verifies that an order signature is valid. + /// @param signer address of signer. + /// @param hash Signed Keccak-256 hash. + /// @param v ECDSA signature parameter v. + /// @param r ECDSA signature parameters r. + /// @param s ECDSA signature parameters s. + /// @return Validity of order signature. + function isValidSignature( + address signer, + bytes32 hash, + uint8 v, + bytes32 r, + bytes32 s) + public constant + returns (bool); + + /// @dev Fills the input order. + /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient. + /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt. + /// @param fillTakerTokenAmount Desired amount of takerToken to fill. + /// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfer will fail before attempting. + /// @param v ECDSA signature parameter v. + /// @param r ECDSA signature parameters r. + /// @param s ECDSA signature parameters s. + /// @return Total amount of takerToken filled in trade. + function fillOrder( + address[5] orderAddresses, + uint[6] orderValues, + uint fillTakerTokenAmount, + bool shouldThrowOnInsufficientBalanceOrAllowance, + uint8 v, + bytes32 r, + bytes32 s) + public + returns (uint filledTakerTokenAmount); + + /// @dev Cancels the input order. + /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient. + /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt. + /// @param cancelTakerTokenAmount Desired amount of takerToken to cancel in order. + /// @return Amount of takerToken cancelled. + function cancelOrder( + address[5] orderAddresses, + uint[6] orderValues, + uint cancelTakerTokenAmount) + public + returns (uint); + + + /// @dev Fills an order with specified parameters and ECDSA signature, throws if specified amount not filled entirely. + /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient. + /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt. + /// @param fillTakerTokenAmount Desired amount of takerToken to fill. + /// @param v ECDSA signature parameter v. + /// @param r ECDSA signature parameters r. + /// @param s ECDSA signature parameters s. + function fillOrKillOrder( + address[5] orderAddresses, + uint[6] orderValues, + uint fillTakerTokenAmount, + uint8 v, + bytes32 r, + bytes32 s) + public; + + /// @dev Synchronously executes multiple fill orders in a single transaction. + /// @param orderAddresses Array of address arrays containing individual order addresses. + /// @param orderValues Array of uint arrays containing individual order values. + /// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders. + /// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting. + /// @param v Array ECDSA signature v parameters. + /// @param r Array of ECDSA signature r parameters. + /// @param s Array of ECDSA signature s parameters. + function batchFillOrders( + address[5][] orderAddresses, + uint[6][] orderValues, + uint[] fillTakerTokenAmounts, + bool shouldThrowOnInsufficientBalanceOrAllowance, + uint8[] v, + bytes32[] r, + bytes32[] s) + public; + + /// @dev Synchronously executes multiple fillOrKill orders in a single transaction. + /// @param orderAddresses Array of address arrays containing individual order addresses. + /// @param orderValues Array of uint arrays containing individual order values. + /// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders. + /// @param v Array ECDSA signature v parameters. + /// @param r Array of ECDSA signature r parameters. + /// @param s Array of ECDSA signature s parameters. + function batchFillOrKillOrders( + address[5][] orderAddresses, + uint[6][] orderValues, + uint[] fillTakerTokenAmounts, + uint8[] v, + bytes32[] r, + bytes32[] s) + public; + + /// @dev Synchronously executes multiple fill orders in a single transaction until total fillTakerTokenAmount filled. + /// @param orderAddresses Array of address arrays containing individual order addresses. + /// @param orderValues Array of uint arrays containing individual order values. + /// @param fillTakerTokenAmount Desired total amount of takerToken to fill in orders. + /// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting. + /// @param v Array ECDSA signature v parameters. + /// @param r Array of ECDSA signature r parameters. + /// @param s Array of ECDSA signature s parameters. + /// @return Total amount of fillTakerTokenAmount filled in orders. + function fillOrdersUpTo( + address[5][] orderAddresses, + uint[6][] orderValues, + uint fillTakerTokenAmount, + bool shouldThrowOnInsufficientBalanceOrAllowance, + uint8[] v, + bytes32[] r, + bytes32[] s) + public + returns (uint); + + /// @dev Synchronously cancels multiple orders in a single transaction. + /// @param orderAddresses Array of address arrays containing individual order addresses. + /// @param orderValues Array of uint arrays containing individual order values. + /// @param cancelTakerTokenAmounts Array of desired amounts of takerToken to cancel in orders. + function batchCancelOrders( + address[5][] orderAddresses, + uint[6][] orderValues, + uint[] cancelTakerTokenAmounts) + public; +} diff --git a/packages/contracts/src/contracts/current/multisig/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.sol b/packages/contracts/src/1.0.0/MultiSigWalletWithTImeLockExceptRemoveAuthorizedAddress/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.sol index 3c6a3d2ef..aee722c53 100644 --- a/packages/contracts/src/contracts/current/multisig/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.sol +++ b/packages/contracts/src/1.0.0/MultiSigWalletWithTImeLockExceptRemoveAuthorizedAddress/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.sol @@ -1,6 +1,6 @@ /* - Copyright 2017 ZeroEx Intl. + Copyright 2018 ZeroEx Intl. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ pragma solidity ^0.4.10; -import { MultiSigWalletWithTimeLock } from "../MultiSigWalletWithTimeLock/MultiSigWalletWithTimeLock.sol"; +import "../../2.0.0/multisig/MultiSigWalletWithTimeLock.sol"; contract MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress is MultiSigWalletWithTimeLock { diff --git a/packages/contracts/src/1.0.0/Ownable/IOwnable_v1.sol b/packages/contracts/src/1.0.0/Ownable/IOwnable_v1.sol new file mode 100644 index 000000000..7e22d544d --- /dev/null +++ b/packages/contracts/src/1.0.0/Ownable/IOwnable_v1.sol @@ -0,0 +1,18 @@ +pragma solidity ^0.4.19; + +/* + * Ownable + * + * Base contract with an owner. + * Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner. + */ + +contract IOwnable_v1 { + + function owner() + public view + returns (address); + + function transferOwnership(address newOwner) + public; +} diff --git a/packages/contracts/src/contracts/previous/Ownable/Ownable_v1.sol b/packages/contracts/src/1.0.0/Ownable/Ownable_v1.sol index c87438fa4..c87438fa4 100644 --- a/packages/contracts/src/contracts/previous/Ownable/Ownable_v1.sol +++ b/packages/contracts/src/1.0.0/Ownable/Ownable_v1.sol diff --git a/packages/contracts/src/contracts/previous/SafeMath/SafeMath_v1.sol b/packages/contracts/src/1.0.0/SafeMath/SafeMath_v1.sol index 341d611ec..341d611ec 100644 --- a/packages/contracts/src/contracts/previous/SafeMath/SafeMath_v1.sol +++ b/packages/contracts/src/1.0.0/SafeMath/SafeMath_v1.sol diff --git a/packages/contracts/src/contracts/previous/Token/Token_v1.sol b/packages/contracts/src/1.0.0/Token/Token_v1.sol index de619fb7e..de619fb7e 100644 --- a/packages/contracts/src/contracts/previous/Token/Token_v1.sol +++ b/packages/contracts/src/1.0.0/Token/Token_v1.sol diff --git a/packages/contracts/src/1.0.0/TokenRegistry/ITokenRegistery.sol b/packages/contracts/src/1.0.0/TokenRegistry/ITokenRegistery.sol new file mode 100644 index 000000000..b8bdaf3b9 --- /dev/null +++ b/packages/contracts/src/1.0.0/TokenRegistry/ITokenRegistery.sol @@ -0,0 +1,195 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.21; + +import { IOwnable_v1 as IOwnable } from "../Ownable/IOwnable_v1.sol"; + +/// @title Token Registry - Stores metadata associated with ERC20 tokens. See ERC22 https://github.com/ethereum/EIPs/issues/22 +/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com> +contract ITokenRegistery is IOwnable { + + event LogAddToken( + address indexed token, + string name, + string symbol, + uint8 decimals, + bytes ipfsHash, + bytes swarmHash + ); + + event LogRemoveToken( + address indexed token, + string name, + string symbol, + uint8 decimals, + bytes ipfsHash, + bytes swarmHash + ); + + event LogTokenNameChange( + address indexed token, + string oldName, + string newName + ); + + event LogTokenSymbolChange( + address indexed token, + string oldSymbol, + string newSymbol + ); + + event LogTokenIpfsHashChange( + address indexed token, + bytes oldIpfsHash, + bytes newIpfsHash + ); + + event LogTokenSwarmHashChange( + address indexed token, + bytes oldSwarmHash, + bytes newSwarmHash + ); + + function tokens(address tokenAddress) + public view + returns ( + address token, + string name, + string symbol, + uint8 decimals, + bytes ipfsHash, + bytes swarmHash + ); + + function tokenAddresses(uint256 index) + public view + returns (address); + + + /// @dev Allows owner to add a new token to the registry. + /// @param _token Address of new token. + /// @param _name Name of new token. + /// @param _symbol Symbol for new token. + /// @param _decimals Number of decimals, divisibility of new token. + /// @param _ipfsHash IPFS hash of token icon. + /// @param _swarmHash Swarm hash of token icon. + function addToken( + address _token, + string _name, + string _symbol, + uint8 _decimals, + bytes _ipfsHash, + bytes _swarmHash) + public; + + /// @dev Allows owner to remove an existing token from the registry. + /// @param _token Address of existing token. + function removeToken(address _token, uint _index) + public; + + /// @dev Allows owner to modify an existing token's name. + /// @param _token Address of existing token. + /// @param _name New name. + function setTokenName(address _token, string _name) + public; + + /// @dev Allows owner to modify an existing token's symbol. + /// @param _token Address of existing token. + /// @param _symbol New symbol. + function setTokenSymbol(address _token, string _symbol) + public; + + /// @dev Allows owner to modify an existing token's IPFS hash. + /// @param _token Address of existing token. + /// @param _ipfsHash New IPFS hash. + function setTokenIpfsHash(address _token, bytes _ipfsHash) + public; + + /// @dev Allows owner to modify an existing token's Swarm hash. + /// @param _token Address of existing token. + /// @param _swarmHash New Swarm hash. + function setTokenSwarmHash(address _token, bytes _swarmHash) + public; + + /* + * Web3 call functions + */ + + /// @dev Provides a registered token's address when given the token symbol. + /// @param _symbol Symbol of registered token. + /// @return Token's address. + function getTokenAddressBySymbol(string _symbol) + public constant + returns (address); + + /// @dev Provides a registered token's address when given the token name. + /// @param _name Name of registered token. + /// @return Token's address. + function getTokenAddressByName(string _name) + public constant + returns (address); + + /// @dev Provides a registered token's metadata, looked up by address. + /// @param _token Address of registered token. + /// @return Token metadata. + function getTokenMetaData(address _token) + public constant + returns ( + address, //tokenAddress + string, //name + string, //symbol + uint8, //decimals + bytes, //ipfsHash + bytes //swarmHash + ); + + /// @dev Provides a registered token's metadata, looked up by name. + /// @param _name Name of registered token. + /// @return Token metadata. + function getTokenByName(string _name) + public constant + returns ( + address, //tokenAddress + string, //name + string, //symbol + uint8, //decimals + bytes, //ipfsHash + bytes //swarmHash + ); + + /// @dev Provides a registered token's metadata, looked up by symbol. + /// @param _symbol Symbol of registered token. + /// @return Token metadata. + function getTokenBySymbol(string _symbol) + public constant + returns ( + address, //tokenAddress + string, //name + string, //symbol + uint8, //decimals + bytes, //ipfsHash + bytes //swarmHash + ); + + /// @dev Returns an array containing all token addresses. + /// @return Array of token addresses. + function getTokenAddresses() + public constant + returns (address[]); +} diff --git a/packages/contracts/src/contracts/current/protocol/TokenRegistry/TokenRegistry.sol b/packages/contracts/src/1.0.0/TokenRegistry/TokenRegistry.sol index 3bd2fbfaf..7417a10a3 100644 --- a/packages/contracts/src/contracts/current/protocol/TokenRegistry/TokenRegistry.sol +++ b/packages/contracts/src/1.0.0/TokenRegistry/TokenRegistry.sol @@ -1,6 +1,6 @@ /* - Copyright 2017 ZeroEx Intl. + Copyright 2018 ZeroEx Intl. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ pragma solidity ^0.4.11; -import { Ownable_v1 as Ownable } from "../../../previous/Ownable/Ownable_v1.sol"; +import { Ownable_v1 as Ownable } from "../Ownable/Ownable_v1.sol"; /// @title Token Registry - Stores metadata associated with ERC20 tokens. See ERC22 https://github.com/ethereum/EIPs/issues/22 /// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com> diff --git a/packages/contracts/src/contracts/current/protocol/TokenTransferProxy/TokenTransferProxy.sol b/packages/contracts/src/1.0.0/TokenTransferProxy/TokenTransferProxy_v1.sol index 1ce949fa6..e3659d8ba 100644 --- a/packages/contracts/src/contracts/current/protocol/TokenTransferProxy/TokenTransferProxy.sol +++ b/packages/contracts/src/1.0.0/TokenTransferProxy/TokenTransferProxy_v1.sol @@ -1,6 +1,6 @@ /* - Copyright 2017 ZeroEx Intl. + Copyright 2018 ZeroEx Intl. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,12 +18,12 @@ pragma solidity ^0.4.11; -import { Token_v1 as Token } from "../../../previous/Token/Token_v1.sol"; -import { Ownable_v1 as Ownable } from "../../../previous/Ownable/Ownable_v1.sol"; +import { Token_v1 as Token } from "../Token/Token_v1.sol"; +import { Ownable_v1 as Ownable } from "../Ownable/Ownable_v1.sol"; /// @title TokenTransferProxy - Transfers tokens on behalf of contracts that have been approved via decentralized governance. /// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com> -contract TokenTransferProxy is Ownable { +contract TokenTransferProxy_v1 is Ownable { /// @dev Only authorized addresses can invoke functions with this modifier. modifier onlyAuthorized { diff --git a/packages/contracts/src/contracts/previous/UnlimitedAllowanceToken/UnlimitedAllowanceToken_v1.sol b/packages/contracts/src/1.0.0/UnlimitedAllowanceToken/UnlimitedAllowanceToken_v1.sol index 6376f3f2c..46379c43d 100644 --- a/packages/contracts/src/contracts/previous/UnlimitedAllowanceToken/UnlimitedAllowanceToken_v1.sol +++ b/packages/contracts/src/1.0.0/UnlimitedAllowanceToken/UnlimitedAllowanceToken_v1.sol @@ -1,6 +1,6 @@ /* - Copyright 2017 ZeroEx Intl. + Copyright 2018 ZeroEx Intl. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/packages/contracts/src/2.0.0/examples/ExchangeWrapper/ExchangeWrapper.sol b/packages/contracts/src/2.0.0/examples/ExchangeWrapper/ExchangeWrapper.sol new file mode 100644 index 000000000..2fa0e3c5e --- /dev/null +++ b/packages/contracts/src/2.0.0/examples/ExchangeWrapper/ExchangeWrapper.sol @@ -0,0 +1,100 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/Exchange/interfaces/IExchange.sol"; +import "../../protocol/Exchange/libs/LibOrder.sol"; + + +contract ExchangeWrapper { + + // Exchange contract. + // solhint-disable-next-line var-name-mixedcase + IExchange internal EXCHANGE; + + constructor (address _exchange) + public + { + EXCHANGE = IExchange(_exchange); + } + + /// @dev Cancels all orders created by sender with a salt less than or equal to the targetOrderEpoch + /// and senderAddress equal to this contract. + /// @param targetOrderEpoch Orders created with a salt less or equal to this value will be cancelled. + /// @param salt Arbitrary value to gaurantee uniqueness of 0x transaction hash. + /// @param makerSignature Proof that maker wishes to call this function with given params. + function cancelOrdersUpTo( + uint256 targetOrderEpoch, + uint256 salt, + bytes makerSignature + ) + external + { + address makerAddress = msg.sender; + + // Encode arguments into byte array. + bytes memory data = abi.encodeWithSelector( + EXCHANGE.cancelOrdersUpTo.selector, + targetOrderEpoch + ); + + // Call `cancelOrdersUpTo` via `executeTransaction`. + EXCHANGE.executeTransaction( + salt, + makerAddress, + data, + makerSignature + ); + } + + /// @dev Fills an order using `msg.sender` as the taker. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param salt Arbitrary value to gaurantee uniqueness of 0x transaction hash. + /// @param orderSignature Proof that order has been created by maker. + /// @param takerSignature Proof that taker wishes to call this function with given params. + function fillOrder( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + uint256 salt, + bytes memory orderSignature, + bytes memory takerSignature + ) + public + { + address takerAddress = msg.sender; + + // Encode arguments into byte array. + bytes memory data = abi.encodeWithSelector( + EXCHANGE.fillOrder.selector, + order, + takerAssetFillAmount, + orderSignature + ); + + // Call `fillOrder` via `executeTransaction`. + EXCHANGE.executeTransaction( + salt, + takerAddress, + data, + takerSignature + ); + } +} diff --git a/packages/contracts/src/2.0.0/examples/Validator/Validator.sol b/packages/contracts/src/2.0.0/examples/Validator/Validator.sol new file mode 100644 index 000000000..72ed528ba --- /dev/null +++ b/packages/contracts/src/2.0.0/examples/Validator/Validator.sol @@ -0,0 +1,56 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../protocol/Exchange/interfaces/IValidator.sol"; + + +contract Validator is + IValidator +{ + + // The single valid signer for this wallet. + // solhint-disable-next-line var-name-mixedcase + address internal VALID_SIGNER; + + /// @dev constructs a new `Validator` with a single valid signer. + /// @param validSigner The sole, valid signer. + constructor (address validSigner) public { + VALID_SIGNER = validSigner; + } + + /// @dev Verifies that a signature is valid. `signer` must match `VALID_SIGNER`. + /// @param hash Message hash that is signed. + /// @param signerAddress Address that should have signed the given hash. + /// @param signature Proof of signing. + /// @return Validity of signature. + // solhint-disable no-unused-vars + function isValidSignature( + bytes32 hash, + address signerAddress, + bytes signature + ) + external + view + returns (bool isValid) + { + return (signerAddress == VALID_SIGNER); + } + // solhint-enable no-unused-vars +} diff --git a/packages/contracts/src/2.0.0/examples/Wallet/Wallet.sol b/packages/contracts/src/2.0.0/examples/Wallet/Wallet.sol new file mode 100644 index 000000000..b75021a31 --- /dev/null +++ b/packages/contracts/src/2.0.0/examples/Wallet/Wallet.sol @@ -0,0 +1,65 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../protocol/Exchange/interfaces/IWallet.sol"; +import "../../utils/LibBytes/LibBytes.sol"; + + +contract Wallet is + IWallet +{ + using LibBytes for bytes; + + // The owner of this wallet. + // solhint-disable-next-line var-name-mixedcase + address internal WALLET_OWNER; + + /// @dev constructs a new `Wallet` with a single owner. + /// @param walletOwner The owner of this wallet. + constructor (address walletOwner) public { + WALLET_OWNER = walletOwner; + } + + /// @dev Validates an EIP712 signature. + /// The signer must match the owner of this wallet. + /// @param hash Message hash that is signed. + /// @param eip712Signature Proof of signing. + /// @return Validity of signature. + function isValidSignature( + bytes32 hash, + bytes eip712Signature + ) + external + view + returns (bool isValid) + { + require( + eip712Signature.length == 65, + "LENGTH_65_REQUIRED" + ); + + uint8 v = uint8(eip712Signature[0]); + bytes32 r = eip712Signature.readBytes32(1); + bytes32 s = eip712Signature.readBytes32(33); + address recoveredAddress = ecrecover(hash, v, r, s); + isValid = WALLET_OWNER == recoveredAddress; + return isValid; + } +} diff --git a/packages/contracts/src/2.0.0/examples/Whitelist/Whitelist.sol b/packages/contracts/src/2.0.0/examples/Whitelist/Whitelist.sol new file mode 100644 index 000000000..60cac26ea --- /dev/null +++ b/packages/contracts/src/2.0.0/examples/Whitelist/Whitelist.sol @@ -0,0 +1,136 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/Exchange/interfaces/IExchange.sol"; +import "../../protocol/Exchange/libs/LibOrder.sol"; +import "../../utils/Ownable/Ownable.sol"; + + +contract Whitelist is + Ownable +{ + + // Mapping of address => whitelist status. + mapping (address => bool) public isWhitelisted; + + // Exchange contract. + // solhint-disable var-name-mixedcase + IExchange internal EXCHANGE; + bytes internal TX_ORIGIN_SIGNATURE; + // solhint-enable var-name-mixedcase + + byte constant internal VALIDATOR_SIGNATURE_BYTE = "\x06"; + + constructor (address _exchange) + public + { + EXCHANGE = IExchange(_exchange); + TX_ORIGIN_SIGNATURE = abi.encodePacked(address(this), VALIDATOR_SIGNATURE_BYTE); + } + + /// @dev Adds or removes an address from the whitelist. + /// @param target Address to add or remove from whitelist. + /// @param isApproved Whitelist status to assign to address. + function updateWhitelistStatus( + address target, + bool isApproved + ) + external + onlyOwner + { + isWhitelisted[target] = isApproved; + } + + /// @dev Verifies signer is same as signer of current Ethereum transaction. + /// NOTE: This function can currently be used to validate signatures coming from outside of this contract. + /// Extra safety checks can be added for a production contract. + /// @param signerAddress Address that should have signed the given hash. + /// @param signature Proof of signing. + /// @return Validity of order signature. + // solhint-disable no-unused-vars + function isValidSignature( + bytes32 hash, + address signerAddress, + bytes signature + ) + external + view + returns (bool isValid) + { + // solhint-disable-next-line avoid-tx-origin + return signerAddress == tx.origin; + } + // solhint-enable no-unused-vars + + /// @dev Fills an order using `msg.sender` as the taker. + /// The transaction will revert if both the maker and taker are not whitelisted. + /// Orders should specify this contract as the `senderAddress` in order to gaurantee + /// that both maker and taker have been whitelisted. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param salt Arbitrary value to gaurantee uniqueness of 0x transaction hash. + /// @param orderSignature Proof that order has been created by maker. + function fillOrderIfWhitelisted( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + uint256 salt, + bytes memory orderSignature + ) + public + { + address takerAddress = msg.sender; + + // This contract must be the entry point for the transaction. + require( + // solhint-disable-next-line avoid-tx-origin + takerAddress == tx.origin, + "INVALID_SENDER" + ); + + // Check if maker is on the whitelist. + require( + isWhitelisted[order.makerAddress], + "MAKER_NOT_WHITELISTED" + ); + + // Check if taker is on the whitelist. + require( + isWhitelisted[takerAddress], + "TAKER_NOT_WHITELISTED" + ); + + // Encode arguments into byte array. + bytes memory data = abi.encodeWithSelector( + EXCHANGE.fillOrder.selector, + order, + takerAssetFillAmount, + orderSignature + ); + + // Call `fillOrder` via `executeTransaction`. + EXCHANGE.executeTransaction( + salt, + takerAddress, + data, + TX_ORIGIN_SIGNATURE + ); + } +} diff --git a/packages/contracts/src/2.0.0/forwarder/Forwarder.sol b/packages/contracts/src/2.0.0/forwarder/Forwarder.sol new file mode 100644 index 000000000..5b88b05b1 --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/Forwarder.sol @@ -0,0 +1,55 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "./MixinWeth.sol"; +import "./MixinForwarderCore.sol"; +import "./libs/LibConstants.sol"; +import "./MixinAssets.sol"; +import "./MixinExchangeWrapper.sol"; + + +// solhint-disable no-empty-blocks +contract Forwarder is + LibConstants, + MixinWeth, + MixinAssets, + MixinExchangeWrapper, + MixinForwarderCore +{ + + constructor ( + address _exchange, + address _etherToken, + address _zrxToken, + bytes memory _zrxAssetData, + bytes memory _wethAssetData + ) + public + LibConstants( + _exchange, + _etherToken, + _zrxToken, + _zrxAssetData, + _wethAssetData + ) + MixinForwarderCore() + {} +} diff --git a/packages/contracts/src/2.0.0/forwarder/MixinAssets.sol b/packages/contracts/src/2.0.0/forwarder/MixinAssets.sol new file mode 100644 index 000000000..e06f9a8e3 --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/MixinAssets.sol @@ -0,0 +1,144 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../utils/LibBytes/LibBytes.sol"; +import "../utils/Ownable/Ownable.sol"; +import "../tokens/ERC20Token/IERC20Token.sol"; +import "../tokens/ERC721Token/IERC721Token.sol"; +import "./libs/LibConstants.sol"; +import "./mixins/MAssets.sol"; + + +contract MixinAssets is + Ownable, + LibConstants, + MAssets +{ + + using LibBytes for bytes; + + bytes4 constant internal ERC20_TRANSFER_SELECTOR = bytes4(keccak256("transfer(address,uint256)")); + + /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to + /// function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be + /// used to withdraw assets that were accidentally sent to this contract. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param amount Amount of ERC20 token to withdraw. + function withdrawAsset( + bytes assetData, + uint256 amount + ) + external + onlyOwner + { + transferAssetToSender(assetData, amount); + } + + /// @dev Transfers given amount of asset to sender. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param amount Amount of asset to transfer to sender. + function transferAssetToSender( + bytes memory assetData, + uint256 amount + ) + internal + { + bytes4 proxyId = assetData.readBytes4(0); + + if (proxyId == ERC20_DATA_ID) { + transferERC20Token(assetData, amount); + } else if (proxyId == ERC721_DATA_ID) { + transferERC721Token(assetData, amount); + } else { + revert("UNSUPPORTED_TOKEN_PROXY"); + } + } + + /// @dev Decodes ERC20 assetData and transfers given amount to sender. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param amount Amount of asset to transfer to sender. + function transferERC20Token( + bytes memory assetData, + uint256 amount + ) + internal + { + address token = assetData.readAddress(16); + + // Transfer tokens. + // We do a raw call so we can check the success separate + // from the return data. + bool success = token.call(abi.encodeWithSelector( + ERC20_TRANSFER_SELECTOR, + msg.sender, + amount + )); + require( + success, + "TRANSFER_FAILED" + ); + + // Check return data. + // If there is no return data, we assume the token incorrectly + // does not return a bool. In this case we expect it to revert + // on failure, which was handled above. + // If the token does return data, we require that it is a single + // value that evaluates to true. + assembly { + if returndatasize { + success := 0 + if eq(returndatasize, 32) { + // First 64 bytes of memory are reserved scratch space + returndatacopy(0, 0, 32) + success := mload(0) + } + } + } + require( + success, + "TRANSFER_FAILED" + ); + } + + /// @dev Decodes ERC721 assetData and transfers given amount to sender. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param amount Amount of asset to transfer to sender. + function transferERC721Token( + bytes memory assetData, + uint256 amount + ) + internal + { + require( + amount == 1, + "INVALID_AMOUNT" + ); + // Decode asset data. + address token = assetData.readAddress(16); + uint256 tokenId = assetData.readUint256(36); + + // Perform transfer. + IERC721Token(token).transferFrom( + address(this), + msg.sender, + tokenId + ); + } +} diff --git a/packages/contracts/src/2.0.0/forwarder/MixinExchangeWrapper.sol b/packages/contracts/src/2.0.0/forwarder/MixinExchangeWrapper.sol new file mode 100644 index 000000000..4584bb840 --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/MixinExchangeWrapper.sol @@ -0,0 +1,263 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "./libs/LibConstants.sol"; +import "./mixins/MExchangeWrapper.sol"; +import "../protocol/Exchange/libs/LibAbiEncoder.sol"; +import "../protocol/Exchange/libs/LibOrder.sol"; +import "../protocol/Exchange/libs/LibFillResults.sol"; +import "../protocol/Exchange/libs/LibMath.sol"; + + +contract MixinExchangeWrapper is + LibAbiEncoder, + LibFillResults, + LibMath, + LibConstants, + MExchangeWrapper +{ + + /// @dev Fills the input order. + /// Returns false if the transaction would otherwise revert. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function fillOrderNoThrow( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + internal + returns (FillResults memory fillResults) + { + // ABI encode calldata for `fillOrder` + bytes memory fillOrderCalldata = abiEncodeFillOrder( + order, + takerAssetFillAmount, + signature + ); + + address exchange = address(EXCHANGE); + + // Call `fillOrder` and handle any exceptions gracefully + assembly { + let success := call( + gas, // forward all gas, TODO: look into gas consumption of assert/throw + exchange, // call address of Exchange contract + 0, // transfer 0 wei + add(fillOrderCalldata, 32), // pointer to start of input (skip array length in first 32 bytes) + mload(fillOrderCalldata), // length of input + fillOrderCalldata, // write output over input + 128 // output size is 128 bytes + ) + switch success + case 0 { + mstore(fillResults, 0) + mstore(add(fillResults, 32), 0) + mstore(add(fillResults, 64), 0) + mstore(add(fillResults, 96), 0) + } + case 1 { + mstore(fillResults, mload(fillOrderCalldata)) + mstore(add(fillResults, 32), mload(add(fillOrderCalldata, 32))) + mstore(add(fillResults, 64), mload(add(fillOrderCalldata, 64))) + mstore(add(fillResults, 96), mload(add(fillOrderCalldata, 96))) + } + } + return fillResults; + } + + /// @dev Synchronously executes multiple calls of fillOrder until total amount of WETH has been sold by taker. + /// Returns false if the transaction would otherwise revert. + /// @param orders Array of order specifications. + /// @param wethSellAmount Desired amount of WETH to sell. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketSellWeth( + LibOrder.Order[] memory orders, + uint256 wethSellAmount, + bytes[] memory signatures + ) + internal + returns (FillResults memory totalFillResults) + { + bytes memory makerAssetData = orders[0].makerAssetData; + bytes memory wethAssetData = WETH_ASSET_DATA; + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + + // We assume that asset being bought by taker is the same for each order. + // We assume that asset being sold by taker is WETH for each order. + orders[i].makerAssetData = makerAssetData; + orders[i].takerAssetData = wethAssetData; + + // Calculate the remaining amount of WETH to sell + uint256 remainingTakerAssetFillAmount = safeSub(wethSellAmount, totalFillResults.takerAssetFilledAmount); + + // Attempt to sell the remaining amount of WETH + FillResults memory singleFillResults = fillOrderNoThrow( + orders[i], + remainingTakerAssetFillAmount, + signatures[i] + ); + + // Update amounts filled and fees paid by maker and taker + addFillResults(totalFillResults, singleFillResults); + + // Stop execution if the entire amount of takerAsset has been sold + if (totalFillResults.takerAssetFilledAmount >= wethSellAmount) { + break; + } + } + return totalFillResults; + } + + /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker. + /// Returns false if the transaction would otherwise revert. + /// The asset being sold by taker must always be WETH. + /// @param orders Array of order specifications. + /// @param makerAssetFillAmount Desired amount of makerAsset to buy. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketBuyExactAmountWithWeth( + LibOrder.Order[] memory orders, + uint256 makerAssetFillAmount, + bytes[] memory signatures + ) + internal + returns (FillResults memory totalFillResults) + { + bytes memory makerAssetData = orders[0].makerAssetData; + bytes memory wethAssetData = WETH_ASSET_DATA; + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + + // We assume that asset being bought by taker is the same for each order. + // We assume that asset being sold by taker is WETH for each order. + orders[i].makerAssetData = makerAssetData; + orders[i].takerAssetData = wethAssetData; + + // Calculate the remaining amount of makerAsset to buy + uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount); + + // Convert the remaining amount of makerAsset to buy into remaining amount + // of takerAsset to sell, assuming entire amount can be sold in the current order + uint256 remainingTakerAssetFillAmount = getPartialAmount( + orders[i].takerAssetAmount, + orders[i].makerAssetAmount, + remainingMakerAssetFillAmount + ); + + // Attempt to sell the remaining amount of takerAsset + FillResults memory singleFillResults = fillOrderNoThrow( + orders[i], + remainingTakerAssetFillAmount, + signatures[i] + ); + + // Update amounts filled and fees paid by maker and taker + addFillResults(totalFillResults, singleFillResults); + + // Stop execution if the entire amount of makerAsset has been bought + uint256 makerAssetFilledAmount = totalFillResults.makerAssetFilledAmount; + if (makerAssetFilledAmount >= makerAssetFillAmount) { + break; + } + } + + require( + makerAssetFilledAmount >= makerAssetFillAmount, + "COMPLETE_FILL_FAILED" + ); + return totalFillResults; + } + + /// @dev Buys zrxBuyAmount of ZRX fee tokens, taking into account ZRX fees for each order. This will guarantee + /// that at least zrxBuyAmount of ZRX is purchased (sometimes slightly over due to rounding issues). + /// It is possible that a request to buy 200 ZRX will require purchasing 202 ZRX + /// as 2 ZRX is required to purchase the 200 ZRX fee tokens. This guarantees at least 200 ZRX for future purchases. + /// The asset being sold by taker must always be WETH. + /// @param orders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. + /// @param zrxBuyAmount Desired amount of ZRX to buy. + /// @param signatures Proofs that orders have been created by makers. + /// @return totalFillResults Amounts filled and fees paid by maker and taker. + function marketBuyExactZrxWithWeth( + LibOrder.Order[] memory orders, + uint256 zrxBuyAmount, + bytes[] memory signatures + ) + internal + returns (FillResults memory totalFillResults) + { + // Do nothing if zrxBuyAmount == 0 + if (zrxBuyAmount == 0) { + return totalFillResults; + } + + bytes memory zrxAssetData = ZRX_ASSET_DATA; + bytes memory wethAssetData = WETH_ASSET_DATA; + uint256 zrxPurchased = 0; + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + + // All of these are ZRX/WETH, so we can drop the respective assetData from calldata. + orders[i].makerAssetData = zrxAssetData; + orders[i].takerAssetData = wethAssetData; + + // Calculate the remaining amount of ZRX to buy. + uint256 remainingZrxBuyAmount = safeSub(zrxBuyAmount, zrxPurchased); + + // Convert the remaining amount of ZRX to buy into remaining amount + // of WETH to sell, assuming entire amount can be sold in the current order. + uint256 remainingWethSellAmount = getPartialAmount( + orders[i].takerAssetAmount, + safeSub(orders[i].makerAssetAmount, orders[i].takerFee), // our exchange rate after fees + remainingZrxBuyAmount + ); + + // Attempt to sell the remaining amount of WETH. + FillResults memory singleFillResult = fillOrderNoThrow( + orders[i], + safeAdd(remainingWethSellAmount, 1), // we add 1 wei to the fill amount to make up for rounding errors + signatures[i] + ); + + // Update amounts filled and fees paid by maker and taker. + addFillResults(totalFillResults, singleFillResult); + zrxPurchased = safeSub(totalFillResults.makerAssetFilledAmount, totalFillResults.takerFeePaid); + + // Stop execution if the entire amount of ZRX has been bought. + if (zrxPurchased >= zrxBuyAmount) { + break; + } + } + + require( + zrxPurchased >= zrxBuyAmount, + "COMPLETE_FILL_FAILED" + ); + return totalFillResults; + } +} diff --git a/packages/contracts/src/2.0.0/forwarder/MixinForwarderCore.sol b/packages/contracts/src/2.0.0/forwarder/MixinForwarderCore.sol new file mode 100644 index 000000000..93cbf79be --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/MixinForwarderCore.sol @@ -0,0 +1,213 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "./libs/LibConstants.sol"; +import "./mixins/MWeth.sol"; +import "./mixins/MAssets.sol"; +import "./mixins/MExchangeWrapper.sol"; +import "./interfaces/IForwarderCore.sol"; +import "../utils/LibBytes/LibBytes.sol"; +import "../protocol/Exchange/libs/LibOrder.sol"; +import "../protocol/Exchange/libs/LibFillResults.sol"; +import "../protocol/Exchange/libs/LibMath.sol"; + + +contract MixinForwarderCore is + LibFillResults, + LibMath, + LibConstants, + MWeth, + MAssets, + MExchangeWrapper, + IForwarderCore +{ + + using LibBytes for bytes; + + /// @dev Constructor approves ERC20 proxy to transfer ZRX and WETH on this contract's behalf. + constructor () + public + { + address proxyAddress = EXCHANGE.getAssetProxy(ERC20_DATA_ID); + if (proxyAddress != address(0)) { + ETHER_TOKEN.approve(proxyAddress, MAX_UINT); + ZRX_TOKEN.approve(proxyAddress, MAX_UINT); + } + } + + /// @dev Purchases as much of orders' makerAssets as possible by selling up to 95% of transaction's ETH value. + /// Any ZRX required to pay fees for primary orders will automatically be purchased by this contract. + /// 5% of ETH value is reserved for paying fees to order feeRecipients (in ZRX) and forwarding contract feeRecipient (in ETH). + /// Any ETH not spent will be refunded to sender. + /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. + /// @param signatures Proofs that orders have been created by makers. + /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees. + /// @param feeSignatures Proofs that feeOrders have been created by makers. + /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. + /// @param feeRecipient Address that will receive ETH when orders are filled. + /// @return Amounts filled and fees paid by maker and taker for both sets of orders. + function marketSellOrdersWithEth( + LibOrder.Order[] memory orders, + bytes[] memory signatures, + LibOrder.Order[] memory feeOrders, + bytes[] memory feeSignatures, + uint256 feePercentage, + address feeRecipient + ) + public + payable + returns ( + FillResults memory orderFillResults, + FillResults memory feeOrderFillResults + ) + { + // Convert ETH to WETH. + convertEthToWeth(); + + uint256 wethSellAmount; + uint256 zrxBuyAmount; + uint256 makerAssetAmountPurchased; + if (orders[0].makerAssetData.equals(ZRX_ASSET_DATA)) { + // Calculate amount of WETH that won't be spent on ETH fees. + wethSellAmount = getPartialAmount( + PERCENTAGE_DENOMINATOR, + safeAdd(PERCENTAGE_DENOMINATOR, feePercentage), + msg.value + ); + // Market sell available WETH. + // ZRX fees are paid with this contract's balance. + orderFillResults = marketSellWeth( + orders, + wethSellAmount, + signatures + ); + // The fee amount must be deducted from the amount transfered back to sender. + makerAssetAmountPurchased = safeSub(orderFillResults.makerAssetFilledAmount, orderFillResults.takerFeePaid); + } else { + // 5% of WETH is reserved for filling feeOrders and paying feeRecipient. + wethSellAmount = getPartialAmount( + MAX_WETH_FILL_PERCENTAGE, + PERCENTAGE_DENOMINATOR, + msg.value + ); + // Market sell 95% of WETH. + // ZRX fees are payed with this contract's balance. + orderFillResults = marketSellWeth( + orders, + wethSellAmount, + signatures + ); + // Buy back all ZRX spent on fees. + zrxBuyAmount = orderFillResults.takerFeePaid; + feeOrderFillResults = marketBuyExactZrxWithWeth( + feeOrders, + zrxBuyAmount, + feeSignatures + ); + makerAssetAmountPurchased = orderFillResults.makerAssetFilledAmount; + } + + // Transfer feePercentage of total ETH spent on primary orders to feeRecipient. + // Refund remaining ETH to msg.sender. + transferEthFeeAndRefund( + orderFillResults.takerAssetFilledAmount, + feeOrderFillResults.takerAssetFilledAmount, + feePercentage, + feeRecipient + ); + + // Transfer purchased assets to msg.sender. + transferAssetToSender(orders[0].makerAssetData, makerAssetAmountPurchased); + } + + /// @dev Attempt to purchase makerAssetFillAmount of makerAsset by selling ETH provided with transaction. + /// Any ZRX required to pay fees for primary orders will automatically be purchased by this contract. + /// Any ETH not spent will be refunded to sender. + /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. + /// @param makerAssetFillAmount Desired amount of makerAsset to purchase. + /// @param signatures Proofs that orders have been created by makers. + /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees. + /// @param feeSignatures Proofs that feeOrders have been created by makers. + /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. + /// @param feeRecipient Address that will receive ETH when orders are filled. + /// @return Amounts filled and fees paid by maker and taker for both sets of orders. + function marketBuyOrdersWithEth( + LibOrder.Order[] memory orders, + uint256 makerAssetFillAmount, + bytes[] memory signatures, + LibOrder.Order[] memory feeOrders, + bytes[] memory feeSignatures, + uint256 feePercentage, + address feeRecipient + ) + public + payable + returns ( + FillResults memory orderFillResults, + FillResults memory feeOrderFillResults + ) + { + // Convert ETH to WETH. + convertEthToWeth(); + + uint256 zrxBuyAmount; + uint256 makerAssetAmountPurchased; + if (orders[0].makerAssetData.equals(ZRX_ASSET_DATA)) { + // If the makerAsset is ZRX, it is not necessary to pay fees out of this + // contracts's ZRX balance because fees are factored into the price of the order. + orderFillResults = marketBuyExactZrxWithWeth( + orders, + makerAssetFillAmount, + signatures + ); + // The fee amount must be deducted from the amount transfered back to sender. + makerAssetAmountPurchased = safeSub(orderFillResults.makerAssetFilledAmount, orderFillResults.takerFeePaid); + } else { + // Attemp to purchase desired amount of makerAsset. + // ZRX fees are payed with this contract's balance. + orderFillResults = marketBuyExactAmountWithWeth( + orders, + makerAssetFillAmount, + signatures + ); + // Buy back all ZRX spent on fees. + zrxBuyAmount = orderFillResults.takerFeePaid; + feeOrderFillResults = marketBuyExactZrxWithWeth( + feeOrders, + zrxBuyAmount, + feeSignatures + ); + makerAssetAmountPurchased = orderFillResults.makerAssetFilledAmount; + } + + // Transfer feePercentage of total ETH spent on primary orders to feeRecipient. + // Refund remaining ETH to msg.sender. + transferEthFeeAndRefund( + orderFillResults.takerAssetFilledAmount, + feeOrderFillResults.takerAssetFilledAmount, + feePercentage, + feeRecipient + ); + + // Transfer purchased assets to msg.sender. + transferAssetToSender(orders[0].makerAssetData, makerAssetAmountPurchased); + } +} diff --git a/packages/contracts/src/2.0.0/forwarder/MixinWeth.sol b/packages/contracts/src/2.0.0/forwarder/MixinWeth.sol new file mode 100644 index 000000000..e07940776 --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/MixinWeth.sol @@ -0,0 +1,114 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../protocol/Exchange/libs/LibMath.sol"; +import "./libs/LibConstants.sol"; +import "./mixins/MWeth.sol"; + + +contract MixinWeth is + LibMath, + LibConstants, + MWeth +{ + + /// @dev Default payabale function, this allows us to withdraw WETH + function () + public + payable + { + require( + msg.sender == address(ETHER_TOKEN), + "DEFAULT_FUNCTION_WETH_CONTRACT_ONLY" + ); + } + + /// @dev Converts message call's ETH value into WETH. + function convertEthToWeth() + internal + { + require( + msg.value > 0, + "INVALID_MSG_VALUE" + ); + ETHER_TOKEN.deposit.value(msg.value)(); + } + + /// @dev Transfers feePercentage of WETH spent on primary orders to feeRecipient. + /// Refunds any excess ETH to msg.sender. + /// @param wethSoldExcludingFeeOrders Amount of WETH sold when filling primary orders. + /// @param wethSoldForZrx Amount of WETH sold when purchasing ZRX required for primary order fees. + /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. + /// @param feeRecipient Address that will receive ETH when orders are filled. + function transferEthFeeAndRefund( + uint256 wethSoldExcludingFeeOrders, + uint256 wethSoldForZrx, + uint256 feePercentage, + address feeRecipient + ) + internal + { + // Ensure feePercentage is less than 5%. + require( + feePercentage <= MAX_FEE_PERCENTAGE, + "FEE_PERCENTAGE_TOO_LARGE" + ); + + // Ensure that no extra WETH owned by this contract has been sold. + uint256 wethSold = safeAdd(wethSoldExcludingFeeOrders, wethSoldForZrx); + require( + wethSold <= msg.value, + "OVERSOLD_WETH" + ); + + // Calculate amount of WETH that hasn't been sold. + uint256 wethRemaining = safeSub(msg.value, wethSold); + + // Calculate ETH fee to pay to feeRecipient. + uint256 ethFee = getPartialAmount( + feePercentage, + PERCENTAGE_DENOMINATOR, + wethSoldExcludingFeeOrders + ); + + // Ensure fee is less than amount of WETH remaining. + require( + ethFee <= wethRemaining, + "INSUFFICIENT_ETH_REMAINING" + ); + + // Do nothing if no WETH remaining + if (wethRemaining > 0) { + // Convert remaining WETH to ETH + ETHER_TOKEN.withdraw(wethRemaining); + + // Pay ETH to feeRecipient + if (ethFee > 0) { + feeRecipient.transfer(ethFee); + } + + // Refund remaining ETH to msg.sender. + uint256 ethRefund = safeSub(wethRemaining, ethFee); + if (ethRefund > 0) { + msg.sender.transfer(ethRefund); + } + } + } +} diff --git a/packages/contracts/src/2.0.0/forwarder/interfaces/IAssets.sol b/packages/contracts/src/2.0.0/forwarder/interfaces/IAssets.sol new file mode 100644 index 000000000..1e034c003 --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/interfaces/IAssets.sol @@ -0,0 +1,34 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + + +contract IAssets { + + /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to + /// function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be + /// used to withdraw assets that were accidentally sent to this contract. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param amount Amount of ERC20 token to withdraw. + function withdrawAsset( + bytes assetData, + uint256 amount + ) + external; +} diff --git a/packages/contracts/src/2.0.0/forwarder/interfaces/IForwarder.sol b/packages/contracts/src/2.0.0/forwarder/interfaces/IForwarder.sol new file mode 100644 index 000000000..f5a26e2ba --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/interfaces/IForwarder.sol @@ -0,0 +1,30 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "./IForwarderCore.sol"; +import "./IAssets.sol"; + + +// solhint-disable no-empty-blocks +contract IForwarder is + IForwarderCore, + IAssets +{} diff --git a/packages/contracts/src/2.0.0/forwarder/interfaces/IForwarderCore.sol b/packages/contracts/src/2.0.0/forwarder/interfaces/IForwarderCore.sol new file mode 100644 index 000000000..3ecbb133b --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/interfaces/IForwarderCore.sol @@ -0,0 +1,80 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/Exchange/libs/LibOrder.sol"; +import "../../protocol/Exchange/libs/LibFillResults.sol"; + + +contract IForwarderCore { + + /// @dev Purchases as much of orders' makerAssets as possible by selling up to 95% of transaction's ETH value. + /// Any ZRX required to pay fees for primary orders will automatically be purchased by this contract. + /// 5% of ETH value is reserved for paying fees to order feeRecipients (in ZRX) and forwarding contract feeRecipient (in ETH). + /// Any ETH not spent will be refunded to sender. + /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. + /// @param signatures Proofs that orders have been created by makers. + /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees. + /// @param feeSignatures Proofs that feeOrders have been created by makers. + /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. + /// @param feeRecipient Address that will receive ETH when orders are filled. + /// @return Amounts filled and fees paid by maker and taker for both sets of orders. + function marketSellOrdersWithEth( + LibOrder.Order[] memory orders, + bytes[] memory signatures, + LibOrder.Order[] memory feeOrders, + bytes[] memory feeSignatures, + uint256 feePercentage, + address feeRecipient + ) + public + payable + returns ( + LibFillResults.FillResults memory orderFillResults, + LibFillResults.FillResults memory feeOrderFillResults + ); + + /// @dev Attempt to purchase makerAssetFillAmount of makerAsset by selling ETH provided with transaction. + /// Any ZRX required to pay fees for primary orders will automatically be purchased by this contract. + /// Any ETH not spent will be refunded to sender. + /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. + /// @param makerAssetFillAmount Desired amount of makerAsset to purchase. + /// @param signatures Proofs that orders have been created by makers. + /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees. + /// @param feeSignatures Proofs that feeOrders have been created by makers. + /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. + /// @param feeRecipient Address that will receive ETH when orders are filled. + /// @return Amounts filled and fees paid by maker and taker for both sets of orders. + function marketBuyOrdersWithEth( + LibOrder.Order[] memory orders, + uint256 makerAssetFillAmount, + bytes[] memory signatures, + LibOrder.Order[] memory feeOrders, + bytes[] memory feeSignatures, + uint256 feePercentage, + address feeRecipient + ) + public + payable + returns ( + LibFillResults.FillResults memory orderFillResults, + LibFillResults.FillResults memory feeOrderFillResults + ); +} diff --git a/packages/contracts/src/2.0.0/forwarder/libs/LibConstants.sol b/packages/contracts/src/2.0.0/forwarder/libs/LibConstants.sol new file mode 100644 index 000000000..c26d7902c --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/libs/LibConstants.sol @@ -0,0 +1,58 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../protocol/Exchange/interfaces/IExchange.sol"; +import "../../tokens/EtherToken/IEtherToken.sol"; +import "../../tokens/ERC20Token/IERC20Token.sol"; + + +contract LibConstants { + + bytes4 constant internal ERC20_DATA_ID = bytes4(keccak256("ERC20Token(address)")); + bytes4 constant internal ERC721_DATA_ID = bytes4(keccak256("ERC721Token(address,uint256)")); + uint256 constant internal MAX_UINT = 2**256 - 1; + uint256 constant internal PERCENTAGE_DENOMINATOR = 10**18; + uint256 constant internal MAX_FEE_PERCENTAGE = 5 * PERCENTAGE_DENOMINATOR / 100; // 5% + uint256 constant internal MAX_WETH_FILL_PERCENTAGE = 95 * PERCENTAGE_DENOMINATOR / 100; // 95% + + // solhint-disable var-name-mixedcase + IExchange internal EXCHANGE; + IEtherToken internal ETHER_TOKEN; + IERC20Token internal ZRX_TOKEN; + bytes internal ZRX_ASSET_DATA; + bytes internal WETH_ASSET_DATA; + // solhint-enable var-name-mixedcase + + constructor ( + address _exchange, + address _etherToken, + address _zrxToken, + bytes memory _zrxAssetData, + bytes memory _wethAssetData + ) + public + { + EXCHANGE = IExchange(_exchange); + ETHER_TOKEN = IEtherToken(_etherToken); + ZRX_TOKEN = IERC20Token(_zrxToken); + ZRX_ASSET_DATA = _zrxAssetData; + WETH_ASSET_DATA = _wethAssetData; + } +} diff --git a/packages/contracts/src/2.0.0/forwarder/libs/LibForwarderErrors.sol b/packages/contracts/src/2.0.0/forwarder/libs/LibForwarderErrors.sol new file mode 100644 index 000000000..cdfb77a0b --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/libs/LibForwarderErrors.sol @@ -0,0 +1,34 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +// solhint-disable +pragma solidity 0.4.24; + + +/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons. +contract LibForwarderErrors { + string constant FEE_PERCENTAGE_TOO_LARGE = "FEE_PROPORTION_TOO_LARGE"; // Provided fee percentage greater than 5%. + string constant INSUFFICIENT_ETH_REMAINING = "INSUFFICIENT_ETH_REMAINING"; // Not enough ETH remaining to pay feeRecipient. + string constant OVERSOLD_WETH = "OVERSOLD_WETH"; // More WETH sold than provided with current message call. + string constant COMPLETE_FILL_FAILED = "COMPLETE_FILL_FAILED"; // Desired purchase amount not completely filled (required for ZRX fees only). + string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Asset transfer failed. + string constant UNSUPPORTED_TOKEN_PROXY = "UNSUPPORTED_TOKEN_PROXY"; // Proxy in assetData not supported. + string constant DEFAULT_FUNCTION_WETH_CONTRACT_ONLY = "DEFAULT_FUNCTION_WETH_CONTRACT_ONLY"; // Fallback function may only be used for WETH withdrawals. + string constant INVALID_MSG_VALUE = "INVALID_MSG_VALUE"; // msg.value must be greater than 0. + string constant INVALID_AMOUNT = "INVALID_AMOUNT"; // Amount must equal 1. +} diff --git a/packages/contracts/src/2.0.0/forwarder/mixins/MAssets.sol b/packages/contracts/src/2.0.0/forwarder/mixins/MAssets.sol new file mode 100644 index 000000000..83636432a --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/mixins/MAssets.sol @@ -0,0 +1,54 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../interfaces/IAssets.sol"; + + +contract MAssets is + IAssets +{ + + /// @dev Transfers given amount of asset to sender. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param amount Amount of asset to transfer to sender. + function transferAssetToSender( + bytes memory assetData, + uint256 amount + ) + internal; + + /// @dev Decodes ERC20 assetData and transfers given amount to sender. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param amount Amount of asset to transfer to sender. + function transferERC20Token( + bytes memory assetData, + uint256 amount + ) + internal; + + /// @dev Decodes ERC721 assetData and transfers given amount to sender. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param amount Amount of asset to transfer to sender. + function transferERC721Token( + bytes memory assetData, + uint256 amount + ) + internal; +} diff --git a/packages/contracts/src/2.0.0/forwarder/mixins/MExchangeWrapper.sol b/packages/contracts/src/2.0.0/forwarder/mixins/MExchangeWrapper.sol new file mode 100644 index 000000000..360dea0e4 --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/mixins/MExchangeWrapper.sol @@ -0,0 +1,87 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/Exchange/libs/LibOrder.sol"; +import "../../protocol/Exchange/libs/LibFillResults.sol"; + + +contract MExchangeWrapper { + + /// @dev Fills the input order. + /// Returns false if the transaction would otherwise revert. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function fillOrderNoThrow( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + internal + returns (LibFillResults.FillResults memory fillResults); + + /// @dev Synchronously executes multiple calls of fillOrder until total amount of WETH has been sold by taker. + /// Returns false if the transaction would otherwise revert. + /// @param orders Array of order specifications. + /// @param wethSellAmount Desired amount of WETH to sell. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketSellWeth( + LibOrder.Order[] memory orders, + uint256 wethSellAmount, + bytes[] memory signatures + ) + internal + returns (LibFillResults.FillResults memory totalFillResults); + + /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker. + /// Returns false if the transaction would otherwise revert. + /// The asset being sold by taker must always be WETH. + /// @param orders Array of order specifications. + /// @param makerAssetFillAmount Desired amount of makerAsset to buy. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketBuyExactAmountWithWeth( + LibOrder.Order[] memory orders, + uint256 makerAssetFillAmount, + bytes[] memory signatures + ) + internal + returns (LibFillResults.FillResults memory totalFillResults); + + /// @dev Buys zrxBuyAmount of ZRX fee tokens, taking into account ZRX fees for each order. This will guarantee + /// that at least zrxBuyAmount of ZRX is purchased (sometimes slightly over due to rounding issues). + /// It is possible that a request to buy 200 ZRX will require purchasing 202 ZRX + /// as 2 ZRX is required to purchase the 200 ZRX fee tokens. This guarantees at least 200 ZRX for future purchases. + /// The asset being sold by taker must always be WETH. + /// @param orders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. + /// @param zrxBuyAmount Desired amount of ZRX to buy. + /// @param signatures Proofs that orders have been created by makers. + /// @return totalFillResults Amounts filled and fees paid by maker and taker. + function marketBuyExactZrxWithWeth( + LibOrder.Order[] memory orders, + uint256 zrxBuyAmount, + bytes[] memory signatures + ) + internal + returns (LibFillResults.FillResults memory totalFillResults); +} diff --git a/packages/contracts/src/2.0.0/forwarder/mixins/MWeth.sol b/packages/contracts/src/2.0.0/forwarder/mixins/MWeth.sol new file mode 100644 index 000000000..88e77be4e --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/mixins/MWeth.sol @@ -0,0 +1,41 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + + +contract MWeth { + + /// @dev Converts message call's ETH value into WETH. + function convertEthToWeth() + internal; + + /// @dev Transfers feePercentage of WETH spent on primary orders to feeRecipient. + /// Refunds any excess ETH to msg.sender. + /// @param wethSoldExcludingFeeOrders Amount of WETH sold when filling primary orders. + /// @param wethSoldForZrx Amount of WETH sold when purchasing ZRX required for primary order fees. + /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. + /// @param feeRecipient Address that will receive ETH when orders are filled. + function transferEthFeeAndRefund( + uint256 wethSoldExcludingFeeOrders, + uint256 wethSoldForZrx, + uint256 feePercentage, + address feeRecipient + ) + internal; +} diff --git a/packages/contracts/src/contracts/current/multisig/MultiSigWallet/MultiSigWallet.sol b/packages/contracts/src/2.0.0/multisig/MultiSigWallet.sol index 79fd92029..eb54fe047 100644 --- a/packages/contracts/src/contracts/current/multisig/MultiSigWallet/MultiSigWallet.sol +++ b/packages/contracts/src/2.0.0/multisig/MultiSigWallet.sol @@ -1,5 +1,7 @@ +// solhint-disable pragma solidity ^0.4.10; + /// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution. /// @author Stefan George - <stefan.george@consensys.net> contract MultiSigWallet { diff --git a/packages/contracts/src/contracts/current/multisig/MultiSigWalletWithTimeLock/MultiSigWalletWithTimeLock.sol b/packages/contracts/src/2.0.0/multisig/MultiSigWalletWithTimeLock.sol index a545d9813..8c5e6e1e6 100644 --- a/packages/contracts/src/contracts/current/multisig/MultiSigWalletWithTimeLock/MultiSigWalletWithTimeLock.sol +++ b/packages/contracts/src/2.0.0/multisig/MultiSigWalletWithTimeLock.sol @@ -1,6 +1,6 @@ /* - Copyright 2017 ZeroEx Intl. + Copyright 2018 ZeroEx Intl. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,9 +16,11 @@ */ +// solhint-disable pragma solidity ^0.4.10; -import { MultiSigWallet } from "../MultiSigWallet/MultiSigWallet.sol"; +import "./MultiSigWallet.sol"; + /// @title Multisignature wallet with time lock- Allows multiple parties to execute a transaction after a time lock has passed. /// @author Amir Bandeali - <amir@0xProject.com> diff --git a/packages/contracts/src/2.0.0/protocol/AssetProxy/ERC20Proxy.sol b/packages/contracts/src/2.0.0/protocol/AssetProxy/ERC20Proxy.sol new file mode 100644 index 000000000..b5cec6b64 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/AssetProxy/ERC20Proxy.sol @@ -0,0 +1,133 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../utils/LibBytes/LibBytes.sol"; +import "./MixinAuthorizable.sol"; + + +contract ERC20Proxy is + MixinAuthorizable +{ + // Id of this proxy. + bytes4 constant internal PROXY_ID = bytes4(keccak256("ERC20Token(address)")); + + // solhint-disable-next-line payable-fallback + function () + external + { + assembly { + // The first 4 bytes of calldata holds the function selector + let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000) + + // `transferFrom` will be called with the following parameters: + // assetData Encoded byte array. + // from Address to transfer asset from. + // to Address to transfer asset to. + // amount Amount of asset to transfer. + // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4 + if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) { + + // To lookup a value in a mapping, we load from the storage location keccak256(k, p), + // where k is the key left padded to 32 bytes and p is the storage slot + let start := mload(64) + mstore(start, and(caller, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(start, 32), authorized_slot) + + // Revert if authorized[msg.sender] == false + if iszero(sload(keccak256(start, 64))) { + // Revert with `Error("SENDER_NOT_AUTHORIZED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001553454e4445525f4e4f545f415554484f52495a454400000000000000) + mstore(96, 0) + revert(0, 100) + } + + /////// Token contract address /////// + // The token address is found as follows: + // * It is stored at offset 4 in `assetData` contents. + // * This is stored at offset 32 from `assetData`. + // * The offset to `assetData` from Params is stored at offset + // 4 in calldata. + // * The offset of Params in calldata is 4. + // So we read location 4 and add 32 + 4 + 4 to it. + let token := calldataload(add(calldataload(4), 40)) + + /////// Setup Header Area /////// + // This area holds the 4-byte `transferFrom` selector. + // Any trailing data in transferFromSelector will be + // overwritten in the next `mstore` call. + mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000) + + /////// Setup Params Area /////// + // We copy the fields `from`, `to` and `amount` in bulk + // from our own calldata to the new calldata. + calldatacopy(4, 36, 96) + + /////// Call `token.transferFrom` using the calldata /////// + let success := call( + gas, // forward all gas + token, // call address of token contract + 0, // don't send any ETH + 0, // pointer to start of input + 100, // length of input + 0, // write output over input + 32 // output size should be 32 bytes + ) + + /////// Check return data. /////// + // If there is no return data, we assume the token incorrectly + // does not return a bool. In this case we expect it to revert + // on failure, which was handled above. + // If the token does return data, we require that it is a single + // nonzero 32 bytes value. + // So the transfer succeeded if the call succeeded and either + // returned nothing, or returned a non-zero 32 byte value. + success := and(success, or( + iszero(returndatasize), + and( + eq(returndatasize, 32), + gt(mload(0), 0) + ) + )) + if success { + return(0, 0) + } + + // Revert with `Error("TRANSFER_FAILED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000000f5452414e534645525f4641494c454400000000000000000000000000) + mstore(96, 0) + revert(0, 100) + } + } + } + + /// @dev Gets the proxy id associated with the proxy address. + /// @return Proxy id. + function getProxyId() + external + pure + returns (bytes4) + { + return PROXY_ID; + } +} diff --git a/packages/contracts/src/2.0.0/protocol/AssetProxy/ERC721Proxy.sol b/packages/contracts/src/2.0.0/protocol/AssetProxy/ERC721Proxy.sol new file mode 100644 index 000000000..6a70c9f60 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/AssetProxy/ERC721Proxy.sol @@ -0,0 +1,167 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../utils/LibBytes/LibBytes.sol"; +import "./MixinAuthorizable.sol"; + + +contract ERC721Proxy is + MixinAuthorizable +{ + // Id of this proxy. + bytes4 constant internal PROXY_ID = bytes4(keccak256("ERC721Token(address,uint256)")); + + // solhint-disable-next-line payable-fallback + function () + external + { + assembly { + // The first 4 bytes of calldata holds the function selector + let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000) + + // `transferFrom` will be called with the following parameters: + // assetData Encoded byte array. + // from Address to transfer asset from. + // to Address to transfer asset to. + // amount Amount of asset to transfer. + // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4 + if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) { + + // To lookup a value in a mapping, we load from the storage location keccak256(k, p), + // where k is the key left padded to 32 bytes and p is the storage slot + let start := mload(64) + mstore(start, and(caller, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(start, 32), authorized_slot) + + // Revert if authorized[msg.sender] == false + if iszero(sload(keccak256(start, 64))) { + // Revert with `Error("SENDER_NOT_AUTHORIZED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001553454e4445525f4e4f545f415554484f52495a454400000000000000) + mstore(96, 0) + revert(0, 100) + } + + // `transferFrom`. + // The function is marked `external`, so no abi decodeding is done for + // us. Instead, we expect the `calldata` memory to contain the + // following: + // + // | Area | Offset | Length | Contents | + // |----------|--------|---------|-------------------------------------| + // | Header | 0 | 4 | function selector | + // | Params | | 4 * 32 | function parameters: | + // | | 4 | | 1. offset to assetData (*) | + // | | 36 | | 2. from | + // | | 68 | | 3. to | + // | | 100 | | 4. amount | + // | Data | | | assetData: | + // | | 132 | 32 | assetData Length | + // | | 164 | ** | assetData Contents | + // + // (*): offset is computed from start of function parameters, so offset + // by an additional 4 bytes in the calldata. + // + // WARNING: The ABIv2 specification allows additional padding between + // the Params and Data section. This will result in a larger + // offset to assetData. + + // Asset data itself is encoded as follows: + // + // | Area | Offset | Length | Contents | + // |----------|--------|---------|-------------------------------------| + // | Header | 0 | 4 | function selector | + // | Params | | 2 * 32 | function parameters: | + // | | 4 | 12 + 20 | 1. token address | + // | | 36 | | 2. tokenId | + + // We construct calldata for the `token.transferFrom` ABI. + // The layout of this calldata is in the table below. + // + // | Area | Offset | Length | Contents | + // |----------|--------|---------|-------------------------------------| + // | Header | 0 | 4 | function selector | + // | Params | | 3 * 32 | function parameters: | + // | | 4 | | 1. from | + // | | 36 | | 2. to | + // | | 68 | | 3. tokenId | + + // There exists only 1 of each token. + // require(amount == 1, "INVALID_AMOUNT") + if sub(calldataload(100), 1) { + // Revert with `Error("INVALID_AMOUNT")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000000e494e56414c49445f414d4f554e540000000000000000000000000000) + mstore(96, 0) + revert(0, 100) + } + + /////// Setup Header Area /////// + // This area holds the 4-byte `transferFrom` selector. + // Any trailing data in transferFromSelector will be + // overwritten in the next `mstore` call. + mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000) + + /////// Setup Params Area /////// + // We copy the fields `from` and `to` in bulk + // from our own calldata to the new calldata. + calldatacopy(4, 36, 64) + + // Copy `tokenId` field from our own calldata to the new calldata. + let assetDataOffset := calldataload(4) + calldatacopy(68, add(assetDataOffset, 72), 32) + + /////// Call `token.transferFrom` using the calldata /////// + let token := calldataload(add(assetDataOffset, 40)) + let success := call( + gas, // forward all gas + token, // call address of token contract + 0, // don't send any ETH + 0, // pointer to start of input + 100, // length of input + 0, // write output to null + 0 // output size is 0 bytes + ) + if success { + return(0, 0) + } + + // Revert with `Error("TRANSFER_FAILED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000000f5452414e534645525f4641494c454400000000000000000000000000) + mstore(96, 0) + revert(0, 100) + } + } + } + + /// @dev Gets the proxy id associated with the proxy address. + /// @return Proxy id. + function getProxyId() + external + pure + returns (bytes4) + { + return PROXY_ID; + } +} diff --git a/packages/contracts/src/2.0.0/protocol/AssetProxy/MixinAuthorizable.sol b/packages/contracts/src/2.0.0/protocol/AssetProxy/MixinAuthorizable.sol new file mode 100644 index 000000000..ff4660a31 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/AssetProxy/MixinAuthorizable.sol @@ -0,0 +1,118 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../utils/Ownable/Ownable.sol"; +import "./mixins/MAuthorizable.sol"; + + +contract MixinAuthorizable is + Ownable, + MAuthorizable +{ + + /// @dev Only authorized addresses can invoke functions with this modifier. + modifier onlyAuthorized { + require( + authorized[msg.sender], + "SENDER_NOT_AUTHORIZED" + ); + _; + } + + mapping (address => bool) public authorized; + address[] public authorities; + + /// @dev Authorizes an address. + /// @param target Address to authorize. + function addAuthorizedAddress(address target) + external + onlyOwner + { + require( + !authorized[target], + "TARGET_ALREADY_AUTHORIZED" + ); + + authorized[target] = true; + authorities.push(target); + emit AuthorizedAddressAdded(target, msg.sender); + } + + /// @dev Removes authorizion of an address. + /// @param target Address to remove authorization from. + function removeAuthorizedAddress(address target) + external + onlyOwner + { + require( + authorized[target], + "TARGET_NOT_AUTHORIZED" + ); + + delete authorized[target]; + for (uint256 i = 0; i < authorities.length; i++) { + if (authorities[i] == target) { + authorities[i] = authorities[authorities.length - 1]; + authorities.length -= 1; + break; + } + } + emit AuthorizedAddressRemoved(target, msg.sender); + } + + /// @dev Removes authorizion of an address. + /// @param target Address to remove authorization from. + /// @param index Index of target in authorities array. + function removeAuthorizedAddressAtIndex( + address target, + uint256 index + ) + external + onlyOwner + { + require( + authorized[target], + "TARGET_NOT_AUTHORIZED" + ); + require( + index < authorities.length, + "INDEX_OUT_OF_BOUNDS" + ); + require( + authorities[index] == target, + "AUTHORIZED_ADDRESS_MISMATCH" + ); + + delete authorized[target]; + authorities[index] = authorities[authorities.length - 1]; + authorities.length -= 1; + emit AuthorizedAddressRemoved(target, msg.sender); + } + + /// @dev Gets all authorized addresses. + /// @return Array of authorized addresses. + function getAuthorizedAddresses() + external + view + returns (address[] memory) + { + return authorities; + } +} diff --git a/packages/contracts/src/2.0.0/protocol/AssetProxy/interfaces/IAssetData.sol b/packages/contracts/src/2.0.0/protocol/AssetProxy/interfaces/IAssetData.sol new file mode 100644 index 000000000..3e76e38dd --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/AssetProxy/interfaces/IAssetData.sol @@ -0,0 +1,40 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +// solhint-disable +pragma solidity 0.4.24; + + +// @dev Interface of the asset proxy's assetData. +// The asset proxies take an ABI encoded `bytes assetData` as argument. +// This argument is ABI encoded as one of the methods of this interface. +interface IAssetData { + + function ERC20Token(address tokenContract) + external + pure; + + function ERC721Token( + address tokenContract, + uint256 tokenId, + bytes receiverData + ) + external + pure; + +} diff --git a/packages/contracts/src/2.0.0/protocol/AssetProxy/interfaces/IAssetProxy.sol b/packages/contracts/src/2.0.0/protocol/AssetProxy/interfaces/IAssetProxy.sol new file mode 100644 index 000000000..3651dd694 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/AssetProxy/interfaces/IAssetProxy.sol @@ -0,0 +1,47 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "./IAuthorizable.sol"; + + +contract IAssetProxy is + IAuthorizable +{ + + /// @dev Transfers assets. Either succeeds or throws. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param from Address to transfer asset from. + /// @param to Address to transfer asset to. + /// @param amount Amount of asset to transfer. + function transferFrom( + bytes assetData, + address from, + address to, + uint256 amount + ) + external; + + /// @dev Gets the proxy id associated with the proxy address. + /// @return Proxy id. + function getProxyId() + external + pure + returns (bytes4); +} diff --git a/packages/contracts/src/2.0.0/protocol/AssetProxy/interfaces/IAuthorizable.sol b/packages/contracts/src/2.0.0/protocol/AssetProxy/interfaces/IAuthorizable.sol new file mode 100644 index 000000000..8fac43a47 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/AssetProxy/interfaces/IAuthorizable.sol @@ -0,0 +1,53 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../../utils/Ownable/IOwnable.sol"; + + +contract IAuthorizable is + IOwnable +{ + + /// @dev Authorizes an address. + /// @param target Address to authorize. + function addAuthorizedAddress(address target) + external; + + /// @dev Removes authorizion of an address. + /// @param target Address to remove authorization from. + function removeAuthorizedAddress(address target) + external; + + /// @dev Removes authorizion of an address. + /// @param target Address to remove authorization from. + /// @param index Index of target in authorities array. + function removeAuthorizedAddressAtIndex( + address target, + uint256 index + ) + external; + + /// @dev Gets all authorized addresses. + /// @return Array of authorized addresses. + function getAuthorizedAddresses() + external + view + returns (address[] memory); +} diff --git a/packages/contracts/src/2.0.0/protocol/AssetProxy/libs/LibAssetProxyErrors.sol b/packages/contracts/src/2.0.0/protocol/AssetProxy/libs/LibAssetProxyErrors.sol new file mode 100644 index 000000000..1d9a70cc1 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/AssetProxy/libs/LibAssetProxyErrors.sol @@ -0,0 +1,38 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +// solhint-disable +pragma solidity 0.4.24; + + +/// @dev This contract documents the revert reasons used in the AssetProxy contracts. +/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons. +contract LibAssetProxyErrors { + + /// Authorizable errors /// + string constant SENDER_NOT_AUTHORIZED = "SENDER_NOT_AUTHORIZED"; // Sender not authorized to call this method. + string constant TARGET_NOT_AUTHORIZED = "TARGET_NOT_AUTHORIZED"; // Target address not authorized to call this method. + string constant TARGET_ALREADY_AUTHORIZED = "TARGET_ALREADY_AUTHORIZED"; // Target address must not already be authorized. + string constant INDEX_OUT_OF_BOUNDS = "INDEX_OUT_OF_BOUNDS"; // Specified array index is out of bounds. + string constant AUTHORIZED_ADDRESS_MISMATCH = "AUTHORIZED_ADDRESS_MISMATCH"; // Address at index does not match given target address. + + /// Transfer errors /// + string constant INVALID_AMOUNT = "INVALID_AMOUNT"; // Transfer amount must equal 1. + string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Transfer failed. + string constant LENGTH_GREATER_THAN_131_REQUIRED = "LENGTH_GREATER_THAN_131_REQUIRED"; // Byte array must have a length greater than 0. +} diff --git a/packages/contracts/src/2.0.0/protocol/AssetProxy/mixins/MAuthorizable.sol b/packages/contracts/src/2.0.0/protocol/AssetProxy/mixins/MAuthorizable.sol new file mode 100644 index 000000000..8afc8c8d8 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/AssetProxy/mixins/MAuthorizable.sol @@ -0,0 +1,42 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../interfaces/IAuthorizable.sol"; + + +contract MAuthorizable is + IAuthorizable +{ + + // Event logged when a new address is authorized. + event AuthorizedAddressAdded( + address indexed target, + address indexed caller + ); + + // Event logged when a currently authorized address is unauthorized. + event AuthorizedAddressRemoved( + address indexed target, + address indexed caller + ); + + /// @dev Only authorized addresses can invoke functions with this modifier. + modifier onlyAuthorized { revert(); _; } +} diff --git a/packages/contracts/src/2.0.0/protocol/AssetProxyOwner/AssetProxyOwner.sol b/packages/contracts/src/2.0.0/protocol/AssetProxyOwner/AssetProxyOwner.sol new file mode 100644 index 000000000..8b7333646 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/AssetProxyOwner/AssetProxyOwner.sol @@ -0,0 +1,119 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.10; + +import "../../multisig/MultiSigWalletWithTimeLock.sol"; + + +contract AssetProxyOwner is + MultiSigWalletWithTimeLock +{ + + event AssetProxyRegistration(address assetProxyContract, bool isRegistered); + + // Mapping of AssetProxy contract address => + // if this contract is allowed to call the AssetProxy's `removeAuthorizedAddressAtIndex` method without a time lock. + mapping (address => bool) public isAssetProxyRegistered; + + bytes4 constant internal REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR = bytes4(keccak256("removeAuthorizedAddressAtIndex(address,uint256)")); + + /// @dev Function will revert if the transaction does not call `removeAuthorizedAddressAtIndex` + /// on an approved AssetProxy contract. + modifier validRemoveAuthorizedAddressAtIndexTx(uint256 transactionId) { + Transaction storage tx = transactions[transactionId]; + require(isAssetProxyRegistered[tx.destination]); + require(readBytes4(tx.data, 0) == REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR); + _; + } + + /// @dev Contract constructor sets initial owners, required number of confirmations, + /// time lock, and list of AssetProxy addresses. + /// @param _owners List of initial owners. + /// @param _assetProxyContracts Array of AssetProxy contract addresses. + /// @param _required Number of required confirmations. + /// @param _secondsTimeLocked Duration needed after a transaction is confirmed and before it becomes executable, in seconds. + function AssetProxyOwner( + address[] memory _owners, + address[] memory _assetProxyContracts, + uint256 _required, + uint256 _secondsTimeLocked + ) + public + MultiSigWalletWithTimeLock(_owners, _required, _secondsTimeLocked) + { + for (uint256 i = 0; i < _assetProxyContracts.length; i++) { + address assetProxy = _assetProxyContracts[i]; + require(assetProxy != address(0)); + isAssetProxyRegistered[assetProxy] = true; + } + } + + /// @dev Registers or deregisters an AssetProxy to be able to execute + /// `removeAuthorizedAddressAtIndex` without a timelock. + /// @param assetProxyContract Address of AssetProxy contract. + /// @param isRegistered Status of approval for AssetProxy contract. + function registerAssetProxy(address assetProxyContract, bool isRegistered) + public + onlyWallet + notNull(assetProxyContract) + { + isAssetProxyRegistered[assetProxyContract] = isRegistered; + AssetProxyRegistration(assetProxyContract, isRegistered); + } + + /// @dev Allows execution of `removeAuthorizedAddressAtIndex` without time lock. + /// @param transactionId Transaction ID. + function executeRemoveAuthorizedAddressAtIndex(uint256 transactionId) + public + notExecuted(transactionId) + fullyConfirmed(transactionId) + validRemoveAuthorizedAddressAtIndexTx(transactionId) + { + Transaction storage tx = transactions[transactionId]; + tx.executed = true; + // solhint-disable-next-line avoid-call-value + if (tx.destination.call.value(tx.value)(tx.data)) + Execution(transactionId); + else { + ExecutionFailure(transactionId); + tx.executed = false; + } + } + + /// @dev Reads an unpadded bytes4 value from a position in a byte array. + /// @param b Byte array containing a bytes4 value. + /// @param index Index in byte array of bytes4 value. + /// @return bytes4 value from byte array. + function readBytes4( + bytes memory b, + uint256 index + ) + internal + returns (bytes4 result) + { + require(b.length >= index + 4); + assembly { + result := mload(add(b, 32)) + // Solidity does not require us to clean the trailing bytes. + // We do it anyway + result := and(result, 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000) + } + return result; + } +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/Exchange.sol b/packages/contracts/src/2.0.0/protocol/Exchange/Exchange.sol new file mode 100644 index 000000000..7507d3da1 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/Exchange.sol @@ -0,0 +1,54 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "./libs/LibConstants.sol"; +import "./MixinExchangeCore.sol"; +import "./MixinSignatureValidator.sol"; +import "./MixinWrapperFunctions.sol"; +import "./MixinAssetProxyDispatcher.sol"; +import "./MixinTransactions.sol"; +import "./MixinMatchOrders.sol"; + + +// solhint-disable no-empty-blocks +contract Exchange is + MixinExchangeCore, + MixinMatchOrders, + MixinSignatureValidator, + MixinTransactions, + MixinAssetProxyDispatcher, + MixinWrapperFunctions +{ + + string constant public VERSION = "2.0.1-alpha"; + + // Mixins are instantiated in the order they are inherited + constructor (bytes memory _zrxAssetData) + public + LibConstants(_zrxAssetData) // @TODO: Remove when we deploy. + MixinExchangeCore() + MixinMatchOrders() + MixinSignatureValidator() + MixinTransactions() + MixinAssetProxyDispatcher() + MixinWrapperFunctions() + {} +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinAssetProxyDispatcher.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinAssetProxyDispatcher.sol new file mode 100644 index 000000000..e9f882194 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinAssetProxyDispatcher.sol @@ -0,0 +1,177 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../utils/Ownable/Ownable.sol"; +import "../../utils/LibBytes/LibBytes.sol"; +import "./mixins/MAssetProxyDispatcher.sol"; +import "../AssetProxy/interfaces/IAssetProxy.sol"; + + +contract MixinAssetProxyDispatcher is + Ownable, + MAssetProxyDispatcher +{ + using LibBytes for bytes; + + // Mapping from Asset Proxy Id's to their respective Asset Proxy + mapping (bytes4 => IAssetProxy) public assetProxies; + + /// @dev Registers an asset proxy to its asset proxy id. + /// Once an asset proxy is registered, it cannot be unregistered. + /// @param assetProxy Address of new asset proxy to register. + function registerAssetProxy(address assetProxy) + external + onlyOwner + { + IAssetProxy assetProxyContract = IAssetProxy(assetProxy); + + // Ensure that no asset proxy exists with current id. + bytes4 assetProxyId = assetProxyContract.getProxyId(); + address currentAssetProxy = assetProxies[assetProxyId]; + require( + currentAssetProxy == address(0), + "ASSET_PROXY_ALREADY_EXISTS" + ); + + // Add asset proxy and log registration. + assetProxies[assetProxyId] = assetProxyContract; + emit AssetProxyRegistered( + assetProxyId, + assetProxy + ); + } + + /// @dev Gets an asset proxy. + /// @param assetProxyId Id of the asset proxy. + /// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered. + function getAssetProxy(bytes4 assetProxyId) + external + view + returns (address) + { + return assetProxies[assetProxyId]; + } + + /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws. + /// @param assetData Byte array encoded for the asset. + /// @param from Address to transfer token from. + /// @param to Address to transfer token to. + /// @param amount Amount of token to transfer. + function dispatchTransferFrom( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + internal + { + // Do nothing if no amount should be transferred. + if (amount > 0) { + // Ensure assetData length is valid + require( + assetData.length > 3, + "LENGTH_GREATER_THAN_3_REQUIRED" + ); + + // Lookup assetProxy + bytes4 assetProxyId; + assembly { + assetProxyId := and(mload( + add(assetData, 32)), + 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 + ) + } + address assetProxy = assetProxies[assetProxyId]; + + // Ensure that assetProxy exists + require( + assetProxy != address(0), + "ASSET_PROXY_DOES_NOT_EXIST" + ); + + // We construct calldata for the `assetProxy.transferFrom` ABI. + // The layout of this calldata is in the table below. + // + // | Area | Offset | Length | Contents | + // | -------- |--------|---------|-------------------------------------------- | + // | Header | 0 | 4 | function selector | + // | Params | | 4 * 32 | function parameters: | + // | | 4 | | 1. offset to assetData (*) | + // | | 36 | | 2. from | + // | | 68 | | 3. to | + // | | 100 | | 4. amount | + // | Data | | | assetData: | + // | | 132 | 32 | assetData Length | + // | | 164 | ** | assetData Contents | + + assembly { + /////// Setup State /////// + // `cdStart` is the start of the calldata for `assetProxy.transferFrom` (equal to free memory ptr). + let cdStart := mload(64) + // `dataAreaLength` is the total number of words needed to store `assetData` + // As-per the ABI spec, this value is padded up to the nearest multiple of 32, + // and includes 32-bytes for length. + let dataAreaLength := and(add(mload(assetData), 63), 0xFFFFFFFFFFFE0) + // `cdEnd` is the end of the calldata for `assetProxy.transferFrom`. + let cdEnd := add(cdStart, add(132, dataAreaLength)) + + + /////// Setup Header Area /////// + // This area holds the 4-byte `transferFromSelector`. + // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4 + mstore(cdStart, 0xa85e59e400000000000000000000000000000000000000000000000000000000) + + /////// Setup Params Area /////// + // Each parameter is padded to 32-bytes. The entire Params Area is 128 bytes. + // Notes: + // 1. The offset to `assetData` is the length of the Params Area (128 bytes). + // 2. A 20-byte mask is applied to addresses to zero-out the unused bytes. + mstore(add(cdStart, 4), 128) + mstore(add(cdStart, 36), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(cdStart, 68), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(cdStart, 100), amount) + + /////// Setup Data Area /////// + // This area holds `assetData`. + let dataArea := add(cdStart, 132) + // solhint-disable-next-line no-empty-blocks + for {} lt(dataArea, cdEnd) {} { + mstore(dataArea, mload(assetData)) + dataArea := add(dataArea, 32) + assetData := add(assetData, 32) + } + + /////// Call `assetProxy.transferFrom` using the constructed calldata /////// + let success := call( + gas, // forward all gas + assetProxy, // call address of asset proxy + 0, // don't send any ETH + cdStart, // pointer to start of input + sub(cdEnd, cdStart), // length of input + cdStart, // write output over input + 512 // reserve 512 bytes for output + ) + if iszero(success) { + revert(cdStart, returndatasize()) + } + } + } + } +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinExchangeCore.sol new file mode 100644 index 000000000..ab5c6e507 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinExchangeCore.sol @@ -0,0 +1,435 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "./libs/LibConstants.sol"; +import "./libs/LibFillResults.sol"; +import "./libs/LibOrder.sol"; +import "./libs/LibMath.sol"; +import "./mixins/MExchangeCore.sol"; +import "./mixins/MSignatureValidator.sol"; +import "./mixins/MTransactions.sol"; +import "./mixins/MAssetProxyDispatcher.sol"; + + +contract MixinExchangeCore is + LibConstants, + LibMath, + LibOrder, + LibFillResults, + MAssetProxyDispatcher, + MExchangeCore, + MSignatureValidator, + MTransactions +{ + // Mapping of orderHash => amount of takerAsset already bought by maker + mapping (bytes32 => uint256) public filled; + + // Mapping of orderHash => cancelled + mapping (bytes32 => bool) public cancelled; + + // Mapping of makerAddress => senderAddress => lowest salt an order can have in order to be fillable + // Orders with specified senderAddress and with a salt less than their epoch are considered cancelled + mapping (address => mapping (address => uint256)) public orderEpoch; + + /// @dev Cancels all orders created by makerAddress with a salt less than or equal to the targetOrderEpoch + /// and senderAddress equal to msg.sender (or null address if msg.sender == makerAddress). + /// @param targetOrderEpoch Orders created with a salt less or equal to this value will be cancelled. + function cancelOrdersUpTo(uint256 targetOrderEpoch) + external + { + address makerAddress = getCurrentContextAddress(); + // If this function is called via `executeTransaction`, we only update the orderEpoch for the makerAddress/msg.sender combination. + // This allows external filter contracts to add rules to how orders are cancelled via this function. + address senderAddress = makerAddress == msg.sender ? address(0) : msg.sender; + + // orderEpoch is initialized to 0, so to cancelUpTo we need salt + 1 + uint256 newOrderEpoch = targetOrderEpoch + 1; + uint256 oldOrderEpoch = orderEpoch[makerAddress][senderAddress]; + + // Ensure orderEpoch is monotonically increasing + require( + newOrderEpoch > oldOrderEpoch, + "INVALID_NEW_ORDER_EPOCH" + ); + + // Update orderEpoch + orderEpoch[makerAddress][senderAddress] = newOrderEpoch; + emit CancelUpTo(makerAddress, senderAddress, newOrderEpoch); + } + + /// @dev Fills the input order. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function fillOrder( + Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + public + returns (FillResults memory fillResults) + { + // Fetch order info + OrderInfo memory orderInfo = getOrderInfo(order); + + // Fetch taker address + address takerAddress = getCurrentContextAddress(); + + // Get amount of takerAsset to fill + uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, orderInfo.orderTakerAssetFilledAmount); + uint256 takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount); + + // Validate context + assertValidFill( + order, + orderInfo, + takerAddress, + takerAssetFillAmount, + takerAssetFilledAmount, + signature + ); + + // Compute proportional fill amounts + fillResults = calculateFillResults(order, takerAssetFilledAmount); + + // Update exchange internal state + updateFilledState( + order, + takerAddress, + orderInfo.orderHash, + orderInfo.orderTakerAssetFilledAmount, + fillResults + ); + + // Settle order + settleOrder(order, takerAddress, fillResults); + + return fillResults; + } + + /// @dev After calling, the order can not be filled anymore. + /// Throws if order is invalid or sender does not have permission to cancel. + /// @param order Order to cancel. Order must be OrderStatus.FILLABLE. + function cancelOrder(Order memory order) + public + { + // Fetch current order status + OrderInfo memory orderInfo = getOrderInfo(order); + + // Validate context + assertValidCancel(order, orderInfo); + + // Perform cancel + updateCancelledState(order, orderInfo.orderHash); + } + + /// @dev Gets information about an order: status, hash, and amount filled. + /// @param order Order to gather information on. + /// @return OrderInfo Information about the order and its state. + /// See LibOrder.OrderInfo for a complete description. + function getOrderInfo(Order memory order) + public + view + returns (OrderInfo memory orderInfo) + { + // Compute the order hash + orderInfo.orderHash = getOrderHash(order); + + // Fetch filled amount + orderInfo.orderTakerAssetFilledAmount = filled[orderInfo.orderHash]; + + // If order.makerAssetAmount is zero, we also reject the order. + // While the Exchange contract handles them correctly, they create + // edge cases in the supporting infrastructure because they have + // an 'infinite' price when computed by a simple division. + if (order.makerAssetAmount == 0) { + orderInfo.orderStatus = uint8(OrderStatus.INVALID_MAKER_ASSET_AMOUNT); + return orderInfo; + } + + // If order.takerAssetAmount is zero, then the order will always + // be considered filled because 0 == takerAssetAmount == orderTakerAssetFilledAmount + // Instead of distinguishing between unfilled and filled zero taker + // amount orders, we choose not to support them. + if (order.takerAssetAmount == 0) { + orderInfo.orderStatus = uint8(OrderStatus.INVALID_TAKER_ASSET_AMOUNT); + return orderInfo; + } + + // Validate order availability + if (orderInfo.orderTakerAssetFilledAmount >= order.takerAssetAmount) { + orderInfo.orderStatus = uint8(OrderStatus.FULLY_FILLED); + return orderInfo; + } + + // Validate order expiration + // solhint-disable-next-line not-rely-on-time + if (block.timestamp >= order.expirationTimeSeconds) { + orderInfo.orderStatus = uint8(OrderStatus.EXPIRED); + return orderInfo; + } + + // Check if order has been cancelled + if (cancelled[orderInfo.orderHash]) { + orderInfo.orderStatus = uint8(OrderStatus.CANCELLED); + return orderInfo; + } + if (orderEpoch[order.makerAddress][order.senderAddress] > order.salt) { + orderInfo.orderStatus = uint8(OrderStatus.CANCELLED); + return orderInfo; + } + + // All other statuses are ruled out: order is Fillable + orderInfo.orderStatus = uint8(OrderStatus.FILLABLE); + return orderInfo; + } + + /// @dev Updates state with results of a fill order. + /// @param order that was filled. + /// @param takerAddress Address of taker who filled the order. + /// @param orderTakerAssetFilledAmount Amount of order already filled. + function updateFilledState( + Order memory order, + address takerAddress, + bytes32 orderHash, + uint256 orderTakerAssetFilledAmount, + FillResults memory fillResults + ) + internal + { + // Update state + filled[orderHash] = safeAdd(orderTakerAssetFilledAmount, fillResults.takerAssetFilledAmount); + + // Log order + emit Fill( + order.makerAddress, + order.feeRecipientAddress, + takerAddress, + msg.sender, + fillResults.makerAssetFilledAmount, + fillResults.takerAssetFilledAmount, + fillResults.makerFeePaid, + fillResults.takerFeePaid, + orderHash, + order.makerAssetData, + order.takerAssetData + ); + } + + /// @dev Updates state with results of cancelling an order. + /// State is only updated if the order is currently fillable. + /// Otherwise, updating state would have no effect. + /// @param order that was cancelled. + /// @param orderHash Hash of order that was cancelled. + function updateCancelledState( + Order memory order, + bytes32 orderHash + ) + internal + { + // Perform cancel + cancelled[orderHash] = true; + + // Log cancel + emit Cancel( + order.makerAddress, + order.feeRecipientAddress, + msg.sender, + orderHash, + order.makerAssetData, + order.takerAssetData + ); + } + + /// @dev Validates context for fillOrder. Succeeds or throws. + /// @param order to be filled. + /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. + /// @param takerAddress Address of order taker. + /// @param takerAssetFillAmount Desired amount of order to fill by taker. + /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. + /// @param signature Proof that the orders was created by its maker. + function assertValidFill( + Order memory order, + OrderInfo memory orderInfo, + address takerAddress, + uint256 takerAssetFillAmount, + uint256 takerAssetFilledAmount, + bytes memory signature + ) + internal + view + { + // An order can only be filled if its status is FILLABLE. + require( + orderInfo.orderStatus == uint8(OrderStatus.FILLABLE), + "ORDER_UNFILLABLE" + ); + + // Revert if fill amount is invalid + require( + takerAssetFillAmount != 0, + "INVALID_TAKER_AMOUNT" + ); + + // Validate sender is allowed to fill this order + if (order.senderAddress != address(0)) { + require( + order.senderAddress == msg.sender, + "INVALID_SENDER" + ); + } + + // Validate taker is allowed to fill this order + if (order.takerAddress != address(0)) { + require( + order.takerAddress == takerAddress, + "INVALID_TAKER" + ); + } + + // Validate Maker signature (check only if first time seen) + if (orderInfo.orderTakerAssetFilledAmount == 0) { + require( + isValidSignature( + orderInfo.orderHash, + order.makerAddress, + signature + ), + "INVALID_ORDER_SIGNATURE" + ); + } + + // Validate fill order rounding + require( + !isRoundingError( + takerAssetFilledAmount, + order.takerAssetAmount, + order.makerAssetAmount + ), + "ROUNDING_ERROR" + ); + } + + /// @dev Validates context for cancelOrder. Succeeds or throws. + /// @param order to be cancelled. + /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. + function assertValidCancel( + Order memory order, + OrderInfo memory orderInfo + ) + internal + view + { + // Ensure order is valid + // An order can only be cancelled if its status is FILLABLE. + require( + orderInfo.orderStatus == uint8(OrderStatus.FILLABLE), + "ORDER_UNFILLABLE" + ); + + // Validate sender is allowed to cancel this order + if (order.senderAddress != address(0)) { + require( + order.senderAddress == msg.sender, + "INVALID_SENDER" + ); + } + + // Validate transaction signed by maker + address makerAddress = getCurrentContextAddress(); + require( + order.makerAddress == makerAddress, + "INVALID_MAKER" + ); + } + + /// @dev Calculates amounts filled and fees paid by maker and taker. + /// @param order to be filled. + /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. + /// @return fillResults Amounts filled and fees paid by maker and taker. + function calculateFillResults( + Order memory order, + uint256 takerAssetFilledAmount + ) + internal + pure + returns (FillResults memory fillResults) + { + // Compute proportional transfer amounts + fillResults.takerAssetFilledAmount = takerAssetFilledAmount; + fillResults.makerAssetFilledAmount = getPartialAmount( + takerAssetFilledAmount, + order.takerAssetAmount, + order.makerAssetAmount + ); + fillResults.makerFeePaid = getPartialAmount( + takerAssetFilledAmount, + order.takerAssetAmount, + order.makerFee + ); + fillResults.takerFeePaid = getPartialAmount( + takerAssetFilledAmount, + order.takerAssetAmount, + order.takerFee + ); + + return fillResults; + } + + /// @dev Settles an order by transferring assets between counterparties. + /// @param order Order struct containing order specifications. + /// @param takerAddress Address selling takerAsset and buying makerAsset. + /// @param fillResults Amounts to be filled and fees paid by maker and taker. + function settleOrder( + LibOrder.Order memory order, + address takerAddress, + LibFillResults.FillResults memory fillResults + ) + private + { + bytes memory zrxAssetData = ZRX_ASSET_DATA; + dispatchTransferFrom( + order.makerAssetData, + order.makerAddress, + takerAddress, + fillResults.makerAssetFilledAmount + ); + dispatchTransferFrom( + order.takerAssetData, + takerAddress, + order.makerAddress, + fillResults.takerAssetFilledAmount + ); + dispatchTransferFrom( + zrxAssetData, + order.makerAddress, + order.feeRecipientAddress, + fillResults.makerFeePaid + ); + dispatchTransferFrom( + zrxAssetData, + takerAddress, + order.feeRecipientAddress, + fillResults.takerFeePaid + ); + } +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinMatchOrders.sol new file mode 100644 index 000000000..56b309a1b --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinMatchOrders.sol @@ -0,0 +1,299 @@ +/* + Copyright 2018 ZeroEx Intl. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "./libs/LibConstants.sol"; +import "./libs/LibMath.sol"; +import "./libs/LibOrder.sol"; +import "./libs/LibFillResults.sol"; +import "./mixins/MExchangeCore.sol"; +import "./mixins/MMatchOrders.sol"; +import "./mixins/MTransactions.sol"; +import "./mixins/MAssetProxyDispatcher.sol"; + + +contract MixinMatchOrders is + LibConstants, + LibMath, + MAssetProxyDispatcher, + MExchangeCore, + MMatchOrders, + MTransactions +{ + /// @dev Match two complementary orders that have a profitable spread. + /// Each order is filled at their respective price point. However, the calculations are + /// carried out as though the orders are both being filled at the right order's price point. + /// The profit made by the left order goes to the taker (who matched the two orders). + /// @param leftOrder First order to match. + /// @param rightOrder Second order to match. + /// @param leftSignature Proof that order was created by the left maker. + /// @param rightSignature Proof that order was created by the right maker. + /// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders. + function matchOrders( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + bytes memory leftSignature, + bytes memory rightSignature + ) + public + returns (LibFillResults.MatchedFillResults memory matchedFillResults) + { + // We assume that rightOrder.takerAssetData == leftOrder.makerAssetData and rightOrder.makerAssetData == leftOrder.takerAssetData. + // If this assumption isn't true, the match will fail at signature validation. + rightOrder.makerAssetData = leftOrder.takerAssetData; + rightOrder.takerAssetData = leftOrder.makerAssetData; + + // Get left & right order info + LibOrder.OrderInfo memory leftOrderInfo = getOrderInfo(leftOrder); + LibOrder.OrderInfo memory rightOrderInfo = getOrderInfo(rightOrder); + + // Fetch taker address + address takerAddress = getCurrentContextAddress(); + + // Either our context is valid or we revert + assertValidMatch(leftOrder, rightOrder); + + // Compute proportional fill amounts + matchedFillResults = calculateMatchedFillResults( + leftOrder, + rightOrder, + leftOrderInfo.orderTakerAssetFilledAmount, + rightOrderInfo.orderTakerAssetFilledAmount + ); + + // Validate fill contexts + assertValidFill( + leftOrder, + leftOrderInfo, + takerAddress, + matchedFillResults.left.takerAssetFilledAmount, + matchedFillResults.left.takerAssetFilledAmount, + leftSignature + ); + assertValidFill( + rightOrder, + rightOrderInfo, + takerAddress, + matchedFillResults.right.takerAssetFilledAmount, + matchedFillResults.right.takerAssetFilledAmount, + rightSignature + ); + + // Update exchange state + updateFilledState( + leftOrder, + takerAddress, + leftOrderInfo.orderHash, + leftOrderInfo.orderTakerAssetFilledAmount, + matchedFillResults.left + ); + updateFilledState( + rightOrder, + takerAddress, + rightOrderInfo.orderHash, + rightOrderInfo.orderTakerAssetFilledAmount, + matchedFillResults.right + ); + + // Settle matched orders. Succeeds or throws. + settleMatchedOrders( + leftOrder, + rightOrder, + takerAddress, + matchedFillResults + ); + + return matchedFillResults; + } + + /// @dev Validates context for matchOrders. Succeeds or throws. + /// @param leftOrder First order to match. + /// @param rightOrder Second order to match. + function assertValidMatch( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder + ) + internal + pure + { + // Make sure there is a profitable spread. + // There is a profitable spread iff the cost per unit bought (OrderA.MakerAmount/OrderA.TakerAmount) for each order is greater + // than the profit per unit sold of the matched order (OrderB.TakerAmount/OrderB.MakerAmount). + // This is satisfied by the equations below: + // <leftOrder.makerAssetAmount> / <leftOrder.takerAssetAmount> >= <rightOrder.takerAssetAmount> / <rightOrder.makerAssetAmount> + // AND + // <rightOrder.makerAssetAmount> / <rightOrder.takerAssetAmount> >= <leftOrder.takerAssetAmount> / <leftOrder.makerAssetAmount> + // These equations can be combined to get the following: + require( + safeMul(leftOrder.makerAssetAmount, rightOrder.makerAssetAmount) >= + safeMul(leftOrder.takerAssetAmount, rightOrder.takerAssetAmount), + "NEGATIVE_SPREAD_REQUIRED" + ); + } + + /// @dev Calculates fill amounts for the matched orders. + /// Each order is filled at their respective price point. However, the calculations are + /// carried out as though the orders are both being filled at the right order's price point. + /// The profit made by the leftOrder order goes to the taker (who matched the two orders). + /// @param leftOrder First order to match. + /// @param rightOrder Second order to match. + /// @param leftOrderTakerAssetFilledAmount Amount of left order already filled. + /// @param rightOrderTakerAssetFilledAmount Amount of right order already filled. + /// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders. + function calculateMatchedFillResults( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + uint256 leftOrderTakerAssetFilledAmount, + uint256 rightOrderTakerAssetFilledAmount + ) + internal + pure + returns (LibFillResults.MatchedFillResults memory matchedFillResults) + { + // We settle orders at the exchange rate of the right order. + // The amount saved by the left maker goes to the taker. + // Either the left or right order will be fully filled; possibly both. + // The left order is fully filled iff the right order can sell more than left can buy. + // That is: the amount required to fill the left order is less than or equal to + // the amount we can spend from the right order: + // <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightMakerToTakerRatio> + // <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount> / <rightOrder.takerAssetAmount> + // <leftTakerAssetAmountRemaining> * <rightOrder.takerAssetAmount> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount> + uint256 leftTakerAssetAmountRemaining = safeSub(leftOrder.takerAssetAmount, leftOrderTakerAssetFilledAmount); + uint256 rightTakerAssetAmountRemaining = safeSub(rightOrder.takerAssetAmount, rightOrderTakerAssetFilledAmount); + uint256 leftTakerAssetFilledAmount; + uint256 rightTakerAssetFilledAmount; + if ( + safeMul(leftTakerAssetAmountRemaining, rightOrder.takerAssetAmount) <= + safeMul(rightTakerAssetAmountRemaining, rightOrder.makerAssetAmount) + ) { + // Left order will be fully filled: maximally fill left + leftTakerAssetFilledAmount = leftTakerAssetAmountRemaining; + + // The right order receives an amount proportional to how much was spent. + rightTakerAssetFilledAmount = getPartialAmount( + rightOrder.takerAssetAmount, + rightOrder.makerAssetAmount, + leftTakerAssetFilledAmount + ); + } else { + // Right order will be fully filled: maximally fill right + rightTakerAssetFilledAmount = rightTakerAssetAmountRemaining; + + // The left order receives an amount proportional to how much was spent. + leftTakerAssetFilledAmount = getPartialAmount( + rightOrder.makerAssetAmount, + rightOrder.takerAssetAmount, + rightTakerAssetFilledAmount + ); + } + + // Calculate fill results for left order + matchedFillResults.left = calculateFillResults( + leftOrder, + leftTakerAssetFilledAmount + ); + + // Calculate fill results for right order + matchedFillResults.right = calculateFillResults( + rightOrder, + rightTakerAssetFilledAmount + ); + + // Calculate amount given to taker + matchedFillResults.leftMakerAssetSpreadAmount = safeSub( + matchedFillResults.left.makerAssetFilledAmount, + matchedFillResults.right.takerAssetFilledAmount + ); + + // Return fill results + return matchedFillResults; + } + + /// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient. + /// @param leftOrder First matched order. + /// @param rightOrder Second matched order. + /// @param takerAddress Address that matched the orders. The taker receives the spread between orders as profit. + /// @param matchedFillResults Struct holding amounts to transfer between makers, taker, and fee recipients. + function settleMatchedOrders( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + address takerAddress, + LibFillResults.MatchedFillResults memory matchedFillResults + ) + private + { + bytes memory zrxAssetData = ZRX_ASSET_DATA; + // Order makers and taker + dispatchTransferFrom( + leftOrder.makerAssetData, + leftOrder.makerAddress, + rightOrder.makerAddress, + matchedFillResults.right.takerAssetFilledAmount + ); + dispatchTransferFrom( + rightOrder.makerAssetData, + rightOrder.makerAddress, + leftOrder.makerAddress, + matchedFillResults.left.takerAssetFilledAmount + ); + dispatchTransferFrom( + leftOrder.makerAssetData, + leftOrder.makerAddress, + takerAddress, + matchedFillResults.leftMakerAssetSpreadAmount + ); + + // Maker fees + dispatchTransferFrom( + zrxAssetData, + leftOrder.makerAddress, + leftOrder.feeRecipientAddress, + matchedFillResults.left.makerFeePaid + ); + dispatchTransferFrom( + zrxAssetData, + rightOrder.makerAddress, + rightOrder.feeRecipientAddress, + matchedFillResults.right.makerFeePaid + ); + + // Taker fees + if (leftOrder.feeRecipientAddress == rightOrder.feeRecipientAddress) { + dispatchTransferFrom( + zrxAssetData, + takerAddress, + leftOrder.feeRecipientAddress, + safeAdd( + matchedFillResults.left.takerFeePaid, + matchedFillResults.right.takerFeePaid + ) + ); + } else { + dispatchTransferFrom( + zrxAssetData, + takerAddress, + leftOrder.feeRecipientAddress, + matchedFillResults.left.takerFeePaid + ); + dispatchTransferFrom( + zrxAssetData, + takerAddress, + rightOrder.feeRecipientAddress, + matchedFillResults.right.takerFeePaid + ); + } + } +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol new file mode 100644 index 000000000..44de54817 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol @@ -0,0 +1,260 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../utils/LibBytes/LibBytes.sol"; +import "./mixins/MSignatureValidator.sol"; +import "./mixins/MTransactions.sol"; +import "./interfaces/IWallet.sol"; +import "./interfaces/IValidator.sol"; + + +contract MixinSignatureValidator is + MSignatureValidator, + MTransactions +{ + using LibBytes for bytes; + + // Mapping of hash => signer => signed + mapping (bytes32 => mapping (address => bool)) public preSigned; + + // Mapping of signer => validator => approved + mapping (address => mapping (address => bool)) public allowedValidators; + + /// @dev Approves a hash on-chain using any valid signature type. + /// After presigning a hash, the preSign signature type will become valid for that hash and signer. + /// @param signerAddress Address that should have signed the given hash. + /// @param signature Proof that the hash has been signed by signer. + function preSign( + bytes32 hash, + address signerAddress, + bytes signature + ) + external + { + require( + isValidSignature( + hash, + signerAddress, + signature + ), + "INVALID_SIGNATURE" + ); + preSigned[hash][signerAddress] = true; + } + + /// @dev Approves/unnapproves a Validator contract to verify signatures on signer's behalf. + /// @param validatorAddress Address of Validator contract. + /// @param approval Approval or disapproval of Validator contract. + function setSignatureValidatorApproval( + address validatorAddress, + bool approval + ) + external + { + address signerAddress = getCurrentContextAddress(); + allowedValidators[signerAddress][validatorAddress] = approval; + emit SignatureValidatorApproval( + signerAddress, + validatorAddress, + approval + ); + } + + /// @dev Verifies that a hash has been signed by the given signer. + /// @param hash Any 32 byte hash. + /// @param signerAddress Address that should have signed the given hash. + /// @param signature Proof that the hash has been signed by signer. + /// @return True if the address recovered from the provided signature matches the input signer address. + function isValidSignature( + bytes32 hash, + address signerAddress, + bytes memory signature + ) + public + view + returns (bool isValid) + { + require( + signature.length > 0, + "LENGTH_GREATER_THAN_0_REQUIRED" + ); + + // Pop last byte off of signature byte array. + uint8 signatureTypeRaw = uint8(signature.popLastByte()); + + // Ensure signature is supported + require( + signatureTypeRaw < uint8(SignatureType.NSignatureTypes), + "SIGNATURE_UNSUPPORTED" + ); + + SignatureType signatureType = SignatureType(signatureTypeRaw); + + // Variables are not scoped in Solidity. + uint8 v; + bytes32 r; + bytes32 s; + address recovered; + + // Always illegal signature. + // This is always an implicit option since a signer can create a + // signature array with invalid type or length. We may as well make + // it an explicit option. This aids testing and analysis. It is + // also the initialization value for the enum type. + if (signatureType == SignatureType.Illegal) { + revert("SIGNATURE_ILLEGAL"); + + // Always invalid signature. + // Like Illegal, this is always implicitly available and therefore + // offered explicitly. It can be implicitly created by providing + // a correctly formatted but incorrect signature. + } else if (signatureType == SignatureType.Invalid) { + require( + signature.length == 0, + "LENGTH_0_REQUIRED" + ); + isValid = false; + return isValid; + + // Signature using EIP712 + } else if (signatureType == SignatureType.EIP712) { + require( + signature.length == 65, + "LENGTH_65_REQUIRED" + ); + v = uint8(signature[0]); + r = signature.readBytes32(1); + s = signature.readBytes32(33); + recovered = ecrecover( + hash, + v, + r, + s + ); + isValid = signerAddress == recovered; + return isValid; + + // Signed using web3.eth_sign + } else if (signatureType == SignatureType.EthSign) { + require( + signature.length == 65, + "LENGTH_65_REQUIRED" + ); + v = uint8(signature[0]); + r = signature.readBytes32(1); + s = signature.readBytes32(33); + recovered = ecrecover( + keccak256(abi.encodePacked( + "\x19Ethereum Signed Message:\n32", + hash + )), + v, + r, + s + ); + isValid = signerAddress == recovered; + return isValid; + + // Implicitly signed by caller. + // The signer has initiated the call. In the case of non-contract + // accounts it means the transaction itself was signed. + // Example: let's say for a particular operation three signatures + // A, B and C are required. To submit the transaction, A and B can + // give a signature to C, who can then submit the transaction using + // `Caller` for his own signature. Or A and C can sign and B can + // submit using `Caller`. Having `Caller` allows this flexibility. + } else if (signatureType == SignatureType.Caller) { + require( + signature.length == 0, + "LENGTH_0_REQUIRED" + ); + isValid = signerAddress == msg.sender; + return isValid; + + // Signature verified by wallet contract. + // If used with an order, the maker of the order is the wallet contract. + } else if (signatureType == SignatureType.Wallet) { + isValid = IWallet(signerAddress).isValidSignature(hash, signature); + return isValid; + + // Signature verified by validator contract. + // If used with an order, the maker of the order can still be an EOA. + // A signature using this type should be encoded as: + // | Offset | Length | Contents | + // | 0x00 | x | Signature to validate | + // | 0x00 + x | 20 | Address of validator contract | + // | 0x14 + x | 1 | Signature type is always "\x06" | + } else if (signatureType == SignatureType.Validator) { + // Pop last 20 bytes off of signature byte array. + address validatorAddress = signature.popLast20Bytes(); + + // Ensure signer has approved validator. + if (!allowedValidators[signerAddress][validatorAddress]) { + return false; + } + isValid = IValidator(validatorAddress).isValidSignature( + hash, + signerAddress, + signature + ); + return isValid; + + // Signer signed hash previously using the preSign function. + } else if (signatureType == SignatureType.PreSigned) { + isValid = preSigned[hash][signerAddress]; + return isValid; + + // Signature from Trezor hardware wallet. + // It differs from web3.eth_sign in the encoding of message length + // (Bitcoin varint encoding vs ascii-decimal, the latter is not + // self-terminating which leads to ambiguities). + // See also: + // https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer + // https://github.com/trezor/trezor-mcu/blob/master/firmware/ethereum.c#L602 + // https://github.com/trezor/trezor-mcu/blob/master/firmware/crypto.c#L36 + } else if (signatureType == SignatureType.Trezor) { + require( + signature.length == 65, + "LENGTH_65_REQUIRED" + ); + v = uint8(signature[0]); + r = signature.readBytes32(1); + s = signature.readBytes32(33); + recovered = ecrecover( + keccak256(abi.encodePacked( + "\x19Ethereum Signed Message:\n\x20", + hash + )), + v, + r, + s + ); + isValid = signerAddress == recovered; + return isValid; + } + + // Anything else is illegal (We do not return false because + // the signature may actually be valid, just not in a format + // that we currently support. In this case returning false + // may lead the caller to incorrectly believe that the + // signature was invalid.) + revert("SIGNATURE_UNSUPPORTED"); + } +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinTransactions.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinTransactions.sol new file mode 100644 index 000000000..821d30279 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinTransactions.sol @@ -0,0 +1,161 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +pragma solidity 0.4.24; + +import "./libs/LibExchangeErrors.sol"; +import "./mixins/MSignatureValidator.sol"; +import "./mixins/MTransactions.sol"; +import "./libs/LibEIP712.sol"; + + +contract MixinTransactions is + LibEIP712, + MSignatureValidator, + MTransactions +{ + + // Mapping of transaction hash => executed + // This prevents transactions from being executed more than once. + mapping (bytes32 => bool) public transactions; + + // Address of current transaction signer + address public currentContextAddress; + + // Hash for the EIP712 ZeroEx Transaction Schema + bytes32 constant internal EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = keccak256(abi.encodePacked( + "ZeroExTransaction(", + "uint256 salt,", + "address signerAddress,", + "bytes data", + ")" + )); + + /// @dev Executes an exchange method call in the context of signer. + /// @param salt Arbitrary number to ensure uniqueness of transaction hash. + /// @param signerAddress Address of transaction signer. + /// @param data AbiV2 encoded calldata. + /// @param signature Proof of signer transaction by signer. + function executeTransaction( + uint256 salt, + address signerAddress, + bytes data, + bytes signature + ) + external + { + // Prevent reentrancy + require( + currentContextAddress == address(0), + "REENTRANCY_ILLEGAL" + ); + + bytes32 transactionHash = hashEIP712Message(hashZeroExTransaction( + salt, + signerAddress, + data + )); + + // Validate transaction has not been executed + require( + !transactions[transactionHash], + "INVALID_TX_HASH" + ); + + // Transaction always valid if signer is sender of transaction + if (signerAddress != msg.sender) { + // Validate signature + require( + isValidSignature( + transactionHash, + signerAddress, + signature + ), + "INVALID_TX_SIGNATURE" + ); + + // Set the current transaction signer + currentContextAddress = signerAddress; + } + + // Execute transaction + transactions[transactionHash] = true; + require( + address(this).delegatecall(data), + "FAILED_EXECUTION" + ); + + // Reset current transaction signer if it was previously updated + if (signerAddress != msg.sender) { + currentContextAddress = address(0); + } + } + + /// @dev Calculates EIP712 hash of the Transaction. + /// @param salt Arbitrary number to ensure uniqueness of transaction hash. + /// @param signerAddress Address of transaction signer. + /// @param data AbiV2 encoded calldata. + /// @return EIP712 hash of the Transaction. + function hashZeroExTransaction( + uint256 salt, + address signerAddress, + bytes memory data + ) + internal + pure + returns (bytes32 result) + { + bytes32 schemaHash = EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH; + bytes32 dataHash = keccak256(data); + + // Assembly for more efficiently computing: + // keccak256(abi.encodePacked( + // EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH, + // salt, + // bytes32(signerAddress), + // keccak256(data) + // )); + + assembly { + // Load free memory pointer + let memPtr := mload(64) + + mstore(memPtr, schemaHash) // hash of schema + mstore(add(memPtr, 32), salt) // salt + mstore(add(memPtr, 64), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff)) // signerAddress + mstore(add(memPtr, 96), dataHash) // hash of data + + // Compute hash + result := keccak256(memPtr, 128) + } + return result; + } + + /// @dev The current function will be called in the context of this address (either 0x transaction signer or `msg.sender`). + /// If calling a fill function, this address will represent the taker. + /// If calling a cancel function, this address will represent the maker. + /// @return Signer of 0x transaction if entry point is `executeTransaction`. + /// `msg.sender` if entry point is any other function. + function getCurrentContextAddress() + internal + view + returns (address) + { + address contextAddress = currentContextAddress == address(0) ? msg.sender : currentContextAddress; + return contextAddress; + } +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol new file mode 100644 index 000000000..86194f461 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol @@ -0,0 +1,403 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "./libs/LibMath.sol"; +import "./libs/LibOrder.sol"; +import "./libs/LibFillResults.sol"; +import "./libs/LibAbiEncoder.sol"; +import "./mixins/MExchangeCore.sol"; + + +contract MixinWrapperFunctions is + LibMath, + LibFillResults, + LibAbiEncoder, + MExchangeCore +{ + + /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + function fillOrKillOrder( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + public + returns (FillResults memory fillResults) + { + fillResults = fillOrder( + order, + takerAssetFillAmount, + signature + ); + require( + fillResults.takerAssetFilledAmount == takerAssetFillAmount, + "COMPLETE_FILL_FAILED" + ); + return fillResults; + } + + /// @dev Fills the input order. + /// Returns false if the transaction would otherwise revert. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function fillOrderNoThrow( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + public + returns (FillResults memory fillResults) + { + // ABI encode calldata for `fillOrder` + bytes memory fillOrderCalldata = abiEncodeFillOrder( + order, + takerAssetFillAmount, + signature + ); + + // Delegate to `fillOrder` and handle any exceptions gracefully + assembly { + let success := delegatecall( + gas, // forward all gas, TODO: look into gas consumption of assert/throw + address, // call address of this contract + add(fillOrderCalldata, 32), // pointer to start of input (skip array length in first 32 bytes) + mload(fillOrderCalldata), // length of input + fillOrderCalldata, // write output over input + 128 // output size is 128 bytes + ) + switch success + case 0 { + mstore(fillResults, 0) + mstore(add(fillResults, 32), 0) + mstore(add(fillResults, 64), 0) + mstore(add(fillResults, 96), 0) + } + case 1 { + mstore(fillResults, mload(fillOrderCalldata)) + mstore(add(fillResults, 32), mload(add(fillOrderCalldata, 32))) + mstore(add(fillResults, 64), mload(add(fillOrderCalldata, 64))) + mstore(add(fillResults, 96), mload(add(fillOrderCalldata, 96))) + } + } + return fillResults; + } + + /// @dev Synchronously executes multiple calls of fillOrder. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders. + /// @param signatures Proofs that orders have been created by makers. + /// @return Amounts filled and fees paid by makers and taker. + /// NOTE: makerAssetFilledAmount and takerAssetFilledAmount may include amounts filled of different assets. + function batchFillOrders( + LibOrder.Order[] memory orders, + uint256[] memory takerAssetFillAmounts, + bytes[] memory signatures + ) + public + returns (FillResults memory totalFillResults) + { + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + FillResults memory singleFillResults = fillOrder( + orders[i], + takerAssetFillAmounts[i], + signatures[i] + ); + addFillResults(totalFillResults, singleFillResults); + } + return totalFillResults; + } + + /// @dev Synchronously executes multiple calls of fillOrKill. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders. + /// @param signatures Proofs that orders have been created by makers. + /// @return Amounts filled and fees paid by makers and taker. + /// NOTE: makerAssetFilledAmount and takerAssetFilledAmount may include amounts filled of different assets. + function batchFillOrKillOrders( + LibOrder.Order[] memory orders, + uint256[] memory takerAssetFillAmounts, + bytes[] memory signatures + ) + public + returns (FillResults memory totalFillResults) + { + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + FillResults memory singleFillResults = fillOrKillOrder( + orders[i], + takerAssetFillAmounts[i], + signatures[i] + ); + addFillResults(totalFillResults, singleFillResults); + } + return totalFillResults; + } + + /// @dev Fills an order with specified parameters and ECDSA signature. + /// Returns false if the transaction would otherwise revert. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders. + /// @param signatures Proofs that orders have been created by makers. + /// @return Amounts filled and fees paid by makers and taker. + /// NOTE: makerAssetFilledAmount and takerAssetFilledAmount may include amounts filled of different assets. + function batchFillOrdersNoThrow( + LibOrder.Order[] memory orders, + uint256[] memory takerAssetFillAmounts, + bytes[] memory signatures + ) + public + returns (FillResults memory totalFillResults) + { + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + FillResults memory singleFillResults = fillOrderNoThrow( + orders[i], + takerAssetFillAmounts[i], + signatures[i] + ); + addFillResults(totalFillResults, singleFillResults); + } + return totalFillResults; + } + + /// @dev Synchronously executes multiple calls of fillOrder until total amount of takerAsset is sold by taker. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signatures Proofs that orders have been created by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketSellOrders( + LibOrder.Order[] memory orders, + uint256 takerAssetFillAmount, + bytes[] memory signatures + ) + public + returns (FillResults memory totalFillResults) + { + bytes memory takerAssetData = orders[0].takerAssetData; + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + + // We assume that asset being sold by taker is the same for each order. + // Rather than passing this in as calldata, we use the takerAssetData from the first order in all later orders. + orders[i].takerAssetData = takerAssetData; + + // Calculate the remaining amount of takerAsset to sell + uint256 remainingTakerAssetFillAmount = safeSub(takerAssetFillAmount, totalFillResults.takerAssetFilledAmount); + + // Attempt to sell the remaining amount of takerAsset + FillResults memory singleFillResults = fillOrder( + orders[i], + remainingTakerAssetFillAmount, + signatures[i] + ); + + // Update amounts filled and fees paid by maker and taker + addFillResults(totalFillResults, singleFillResults); + + // Stop execution if the entire amount of takerAsset has been sold + if (totalFillResults.takerAssetFilledAmount >= takerAssetFillAmount) { + break; + } + } + return totalFillResults; + } + + /// @dev Synchronously executes multiple calls of fillOrder until total amount of takerAsset is sold by taker. + /// Returns false if the transaction would otherwise revert. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketSellOrdersNoThrow( + LibOrder.Order[] memory orders, + uint256 takerAssetFillAmount, + bytes[] memory signatures + ) + public + returns (FillResults memory totalFillResults) + { + bytes memory takerAssetData = orders[0].takerAssetData; + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + + // We assume that asset being sold by taker is the same for each order. + // Rather than passing this in as calldata, we use the takerAssetData from the first order in all later orders. + orders[i].takerAssetData = takerAssetData; + + // Calculate the remaining amount of takerAsset to sell + uint256 remainingTakerAssetFillAmount = safeSub(takerAssetFillAmount, totalFillResults.takerAssetFilledAmount); + + // Attempt to sell the remaining amount of takerAsset + FillResults memory singleFillResults = fillOrderNoThrow( + orders[i], + remainingTakerAssetFillAmount, + signatures[i] + ); + + // Update amounts filled and fees paid by maker and taker + addFillResults(totalFillResults, singleFillResults); + + // Stop execution if the entire amount of takerAsset has been sold + if (totalFillResults.takerAssetFilledAmount >= takerAssetFillAmount) { + break; + } + } + return totalFillResults; + } + + /// @dev Synchronously executes multiple calls of fillOrder until total amount of makerAsset is bought by taker. + /// @param orders Array of order specifications. + /// @param makerAssetFillAmount Desired amount of makerAsset to buy. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketBuyOrders( + LibOrder.Order[] memory orders, + uint256 makerAssetFillAmount, + bytes[] memory signatures + ) + public + returns (FillResults memory totalFillResults) + { + bytes memory makerAssetData = orders[0].makerAssetData; + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + + // We assume that asset being bought by taker is the same for each order. + // Rather than passing this in as calldata, we copy the makerAssetData from the first order onto all later orders. + orders[i].makerAssetData = makerAssetData; + + // Calculate the remaining amount of makerAsset to buy + uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount); + + // Convert the remaining amount of makerAsset to buy into remaining amount + // of takerAsset to sell, assuming entire amount can be sold in the current order + uint256 remainingTakerAssetFillAmount = getPartialAmount( + orders[i].takerAssetAmount, + orders[i].makerAssetAmount, + remainingMakerAssetFillAmount + ); + + // Attempt to sell the remaining amount of takerAsset + FillResults memory singleFillResults = fillOrder( + orders[i], + remainingTakerAssetFillAmount, + signatures[i] + ); + + // Update amounts filled and fees paid by maker and taker + addFillResults(totalFillResults, singleFillResults); + + // Stop execution if the entire amount of makerAsset has been bought + if (totalFillResults.makerAssetFilledAmount >= makerAssetFillAmount) { + break; + } + } + return totalFillResults; + } + + /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker. + /// Returns false if the transaction would otherwise revert. + /// @param orders Array of order specifications. + /// @param makerAssetFillAmount Desired amount of makerAsset to buy. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketBuyOrdersNoThrow( + LibOrder.Order[] memory orders, + uint256 makerAssetFillAmount, + bytes[] memory signatures + ) + public + returns (FillResults memory totalFillResults) + { + bytes memory makerAssetData = orders[0].makerAssetData; + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + + // We assume that asset being bought by taker is the same for each order. + // Rather than passing this in as calldata, we copy the makerAssetData from the first order onto all later orders. + orders[i].makerAssetData = makerAssetData; + + // Calculate the remaining amount of makerAsset to buy + uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount); + + // Convert the remaining amount of makerAsset to buy into remaining amount + // of takerAsset to sell, assuming entire amount can be sold in the current order + uint256 remainingTakerAssetFillAmount = getPartialAmount( + orders[i].takerAssetAmount, + orders[i].makerAssetAmount, + remainingMakerAssetFillAmount + ); + + // Attempt to sell the remaining amount of takerAsset + FillResults memory singleFillResults = fillOrderNoThrow( + orders[i], + remainingTakerAssetFillAmount, + signatures[i] + ); + + // Update amounts filled and fees paid by maker and taker + addFillResults(totalFillResults, singleFillResults); + + // Stop execution if the entire amount of makerAsset has been bought + if (totalFillResults.makerAssetFilledAmount >= makerAssetFillAmount) { + break; + } + } + return totalFillResults; + } + + /// @dev Synchronously cancels multiple orders in a single transaction. + /// @param orders Array of order specifications. + function batchCancelOrders(LibOrder.Order[] memory orders) + public + { + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + cancelOrder(orders[i]); + } + } + + /// @dev Fetches information for all passed in orders. + /// @param orders Array of order specifications. + /// @return Array of OrderInfo instances that correspond to each order. + function getOrdersInfo(LibOrder.Order[] memory orders) + public + view + returns (LibOrder.OrderInfo[] memory) + { + uint256 ordersLength = orders.length; + LibOrder.OrderInfo[] memory ordersInfo = new LibOrder.OrderInfo[](ordersLength); + for (uint256 i = 0; i != ordersLength; i++) { + ordersInfo[i] = getOrderInfo(orders[i]); + } + return ordersInfo; + } +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol new file mode 100644 index 000000000..8db8d6f6c --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol @@ -0,0 +1,37 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + + +contract IAssetProxyDispatcher { + + /// @dev Registers an asset proxy to its asset proxy id. + /// Once an asset proxy is registered, it cannot be unregistered. + /// @param assetProxy Address of new asset proxy to register. + function registerAssetProxy(address assetProxy) + external; + + /// @dev Gets an asset proxy. + /// @param assetProxyId Id of the asset proxy. + /// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered. + function getAssetProxy(bytes4 assetProxyId) + external + view + returns (address); +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IExchange.sol b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IExchange.sol new file mode 100644 index 000000000..b92abba04 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IExchange.sol @@ -0,0 +1,38 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "./IExchangeCore.sol"; +import "./IMatchOrders.sol"; +import "./ISignatureValidator.sol"; +import "./ITransactions.sol"; +import "./IAssetProxyDispatcher.sol"; +import "./IWrapperFunctions.sol"; + + +// solhint-disable no-empty-blocks +contract IExchange is + IExchangeCore, + IMatchOrders, + ISignatureValidator, + ITransactions, + IAssetProxyDispatcher, + IWrapperFunctions +{} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IExchangeCore.sol b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IExchangeCore.sol new file mode 100644 index 000000000..9995e0385 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IExchangeCore.sol @@ -0,0 +1,60 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../libs/LibOrder.sol"; +import "../libs/LibFillResults.sol"; + + +contract IExchangeCore { + + /// @dev Cancels all orders created by makerAddress with a salt less than or equal to the targetOrderEpoch + /// and senderAddress equal to msg.sender (or null address if msg.sender == makerAddress). + /// @param targetOrderEpoch Orders created with a salt less or equal to this value will be cancelled. + function cancelOrdersUpTo(uint256 targetOrderEpoch) + external; + + /// @dev Fills the input order. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function fillOrder( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + public + returns (LibFillResults.FillResults memory fillResults); + + /// @dev After calling, the order can not be filled anymore. + /// @param order Order struct containing order specifications. + function cancelOrder(LibOrder.Order memory order) + public; + + /// @dev Gets information about an order: status, hash, and amount filled. + /// @param order Order to gather information on. + /// @return OrderInfo Information about the order and its state. + /// See LibOrder.OrderInfo for a complete description. + function getOrderInfo(LibOrder.Order memory order) + public + view + returns (LibOrder.OrderInfo memory orderInfo); +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IMatchOrders.sol b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IMatchOrders.sol new file mode 100644 index 000000000..73447f3ae --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IMatchOrders.sol @@ -0,0 +1,44 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../libs/LibOrder.sol"; +import "../libs/LibFillResults.sol"; + + +contract IMatchOrders { + + /// @dev Match two complementary orders that have a profitable spread. + /// Each order is filled at their respective price point. However, the calculations are + /// carried out as though the orders are both being filled at the right order's price point. + /// The profit made by the left order goes to the taker (who matched the two orders). + /// @param leftOrder First order to match. + /// @param rightOrder Second order to match. + /// @param leftSignature Proof that order was created by the left maker. + /// @param rightSignature Proof that order was created by the right maker. + /// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders. + function matchOrders( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + bytes memory leftSignature, + bytes memory rightSignature + ) + public + returns (LibFillResults.MatchedFillResults memory matchedFillResults); +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/ISignatureValidator.sol b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/ISignatureValidator.sol new file mode 100644 index 000000000..1fd0eccf0 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/ISignatureValidator.sol @@ -0,0 +1,57 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + + +contract ISignatureValidator { + + /// @dev Approves a hash on-chain using any valid signature type. + /// After presigning a hash, the preSign signature type will become valid for that hash and signer. + /// @param signerAddress Address that should have signed the given hash. + /// @param signature Proof that the hash has been signed by signer. + function preSign( + bytes32 hash, + address signerAddress, + bytes signature + ) + external; + + /// @dev Approves/unnapproves a Validator contract to verify signatures on signer's behalf. + /// @param validatorAddress Address of Validator contract. + /// @param approval Approval or disapproval of Validator contract. + function setSignatureValidatorApproval( + address validatorAddress, + bool approval + ) + external; + + /// @dev Verifies that a signature is valid. + /// @param hash Message hash that is signed. + /// @param signerAddress Address of signer. + /// @param signature Proof of signing. + /// @return Validity of order signature. + function isValidSignature( + bytes32 hash, + address signerAddress, + bytes memory signature + ) + public + view + returns (bool isValid); +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/ITransactions.sol b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/ITransactions.sol new file mode 100644 index 000000000..4446c55ce --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/ITransactions.sol @@ -0,0 +1,35 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +pragma solidity 0.4.24; + + +contract ITransactions { + + /// @dev Executes an exchange method call in the context of signer. + /// @param salt Arbitrary number to ensure uniqueness of transaction hash. + /// @param signerAddress Address of transaction signer. + /// @param data AbiV2 encoded calldata. + /// @param signature Proof of signer transaction by signer. + function executeTransaction( + uint256 salt, + address signerAddress, + bytes data, + bytes signature + ) + external; +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IValidator.sol b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IValidator.sol new file mode 100644 index 000000000..2dd69100c --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IValidator.sol @@ -0,0 +1,37 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + + +contract IValidator { + + /// @dev Verifies that a signature is valid. + /// @param hash Message hash that is signed. + /// @param signerAddress Address that should have signed the given hash. + /// @param signature Proof of signing. + /// @return Validity of order signature. + function isValidSignature( + bytes32 hash, + address signerAddress, + bytes signature + ) + external + view + returns (bool isValid); +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IWallet.sol b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IWallet.sol new file mode 100644 index 000000000..c97161ca6 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IWallet.sol @@ -0,0 +1,35 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + + +contract IWallet { + + /// @dev Verifies that a signature is valid. + /// @param hash Message hash that is signed. + /// @param signature Proof of signing. + /// @return Validity of order signature. + function isValidSignature( + bytes32 hash, + bytes signature + ) + external + view + returns (bool isValid); +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IWrapperFunctions.sol b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IWrapperFunctions.sol new file mode 100644 index 000000000..56a533646 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/interfaces/IWrapperFunctions.sol @@ -0,0 +1,160 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../libs/LibOrder.sol"; +import "../libs/LibFillResults.sol"; + + +contract IWrapperFunctions { + + /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled. + /// @param order LibOrder.Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + function fillOrKillOrder( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + public + returns (LibFillResults.FillResults memory fillResults); + + /// @dev Fills an order with specified parameters and ECDSA signature. + /// Returns false if the transaction would otherwise revert. + /// @param order LibOrder.Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function fillOrderNoThrow( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + public + returns (LibFillResults.FillResults memory fillResults); + + /// @dev Synchronously executes multiple calls of fillOrder. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders. + /// @param signatures Proofs that orders have been created by makers. + /// @return Amounts filled and fees paid by makers and taker. + function batchFillOrders( + LibOrder.Order[] memory orders, + uint256[] memory takerAssetFillAmounts, + bytes[] memory signatures + ) + public + returns (LibFillResults.FillResults memory totalFillResults); + + /// @dev Synchronously executes multiple calls of fillOrKill. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders. + /// @param signatures Proofs that orders have been created by makers. + /// @return Amounts filled and fees paid by makers and taker. + function batchFillOrKillOrders( + LibOrder.Order[] memory orders, + uint256[] memory takerAssetFillAmounts, + bytes[] memory signatures + ) + public + returns (LibFillResults.FillResults memory totalFillResults); + + /// @dev Fills an order with specified parameters and ECDSA signature. + /// Returns false if the transaction would otherwise revert. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders. + /// @param signatures Proofs that orders have been created by makers. + /// @return Amounts filled and fees paid by makers and taker. + function batchFillOrdersNoThrow( + LibOrder.Order[] memory orders, + uint256[] memory takerAssetFillAmounts, + bytes[] memory signatures + ) + public + returns (LibFillResults.FillResults memory totalFillResults); + + /// @dev Synchronously executes multiple calls of fillOrder until total amount of takerAsset is sold by taker. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signatures Proofs that orders have been created by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketSellOrders( + LibOrder.Order[] memory orders, + uint256 takerAssetFillAmount, + bytes[] memory signatures + ) + public + returns (LibFillResults.FillResults memory totalFillResults); + + /// @dev Synchronously executes multiple calls of fillOrder until total amount of takerAsset is sold by taker. + /// Returns false if the transaction would otherwise revert. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketSellOrdersNoThrow( + LibOrder.Order[] memory orders, + uint256 takerAssetFillAmount, + bytes[] memory signatures + ) + public + returns (LibFillResults.FillResults memory totalFillResults); + + /// @dev Synchronously executes multiple calls of fillOrder until total amount of makerAsset is bought by taker. + /// @param orders Array of order specifications. + /// @param makerAssetFillAmount Desired amount of makerAsset to buy. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketBuyOrders( + LibOrder.Order[] memory orders, + uint256 makerAssetFillAmount, + bytes[] memory signatures + ) + public + returns (LibFillResults.FillResults memory totalFillResults); + + /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker. + /// Returns false if the transaction would otherwise revert. + /// @param orders Array of order specifications. + /// @param makerAssetFillAmount Desired amount of makerAsset to buy. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketBuyOrdersNoThrow( + LibOrder.Order[] memory orders, + uint256 makerAssetFillAmount, + bytes[] memory signatures + ) + public + returns (LibFillResults.FillResults memory totalFillResults); + + /// @dev Synchronously cancels multiple orders in a single transaction. + /// @param orders Array of order specifications. + function batchCancelOrders(LibOrder.Order[] memory orders) + public; + + /// @dev Fetches information for all passed in orders + /// @param orders Array of order specifications. + /// @return Array of OrderInfo instances that correspond to each order. + function getOrdersInfo(LibOrder.Order[] memory orders) + public + view + returns (LibOrder.OrderInfo[] memory); +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibAbiEncoder.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibAbiEncoder.sol new file mode 100644 index 000000000..4aad37709 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibAbiEncoder.sol @@ -0,0 +1,215 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "./LibOrder.sol"; + + +contract LibAbiEncoder { + + /// @dev ABI encodes calldata for `fillOrder`. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return ABI encoded calldata for `fillOrder`. + function abiEncodeFillOrder( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + internal + pure + returns (bytes memory fillOrderCalldata) + { + // We need to call MExchangeCore.fillOrder using a delegatecall in + // assembly so that we can intercept a call that throws. For this, we + // need the input encoded in memory in the Ethereum ABIv2 format [1]. + + // | Area | Offset | Length | Contents | + // | -------- |--------|---------|-------------------------------------------- | + // | Header | 0x00 | 4 | function selector | + // | Params | | 3 * 32 | function parameters: | + // | | 0x00 | | 1. offset to order (*) | + // | | 0x20 | | 2. takerAssetFillAmount | + // | | 0x40 | | 3. offset to signature (*) | + // | Data | | 12 * 32 | order: | + // | | 0x000 | | 1. senderAddress | + // | | 0x020 | | 2. makerAddress | + // | | 0x040 | | 3. takerAddress | + // | | 0x060 | | 4. feeRecipientAddress | + // | | 0x080 | | 5. makerAssetAmount | + // | | 0x0A0 | | 6. takerAssetAmount | + // | | 0x0C0 | | 7. makerFeeAmount | + // | | 0x0E0 | | 8. takerFeeAmount | + // | | 0x100 | | 9. expirationTimeSeconds | + // | | 0x120 | | 10. salt | + // | | 0x140 | | 11. Offset to makerAssetData (*) | + // | | 0x160 | | 12. Offset to takerAssetData (*) | + // | | 0x180 | 32 | makerAssetData Length | + // | | 0x1A0 | ** | makerAssetData Contents | + // | | 0x1C0 | 32 | takerAssetData Length | + // | | 0x1E0 | ** | takerAssetData Contents | + // | | 0x200 | 32 | signature Length | + // | | 0x220 | ** | signature Contents | + + // * Offsets are calculated from the beginning of the current area: Header, Params, Data: + // An offset stored in the Params area is calculated from the beginning of the Params section. + // An offset stored in the Data area is calculated from the beginning of the Data section. + + // ** The length of dynamic array contents are stored in the field immediately preceeding the contents. + + // [1]: https://solidity.readthedocs.io/en/develop/abi-spec.html + + assembly { + + // Areas below may use the following variables: + // 1. <area>Start -- Start of this area in memory + // 2. <area>End -- End of this area in memory. This value may + // be precomputed (before writing contents), + // or it may be computed as contents are written. + // 3. <area>Offset -- Current offset into area. If an area's End + // is precomputed, this variable tracks the + // offsets of contents as they are written. + + /////// Setup Header Area /////// + // Load free memory pointer + fillOrderCalldata := mload(0x40) + // bytes4(keccak256("fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)")) + // = 0xb4be83d5 + // Leave 0x20 bytes to store the length + mstore(add(fillOrderCalldata, 0x20), 0xb4be83d500000000000000000000000000000000000000000000000000000000) + let headerAreaEnd := add(fillOrderCalldata, 0x24) + + /////// Setup Params Area /////// + // This area is preallocated and written to later. + // This is because we need to fill in offsets that have not yet been calculated. + let paramsAreaStart := headerAreaEnd + let paramsAreaEnd := add(paramsAreaStart, 0x60) + let paramsAreaOffset := paramsAreaStart + + /////// Setup Data Area /////// + let dataAreaStart := paramsAreaEnd + let dataAreaEnd := dataAreaStart + + // Offset from the source data we're reading from + let sourceOffset := order + // arrayLenBytes and arrayLenWords track the length of a dynamically-allocated bytes array. + let arrayLenBytes := 0 + let arrayLenWords := 0 + + /////// Write order Struct /////// + // Write memory location of Order, relative to the start of the + // parameter list, then increment the paramsAreaOffset respectively. + mstore(paramsAreaOffset, sub(dataAreaEnd, paramsAreaStart)) + paramsAreaOffset := add(paramsAreaOffset, 0x20) + + // Write values for each field in the order + // It would be nice to use a loop, but we save on gas by writing + // the stores sequentially. + mstore(dataAreaEnd, mload(sourceOffset)) // makerAddress + mstore(add(dataAreaEnd, 0x20), mload(add(sourceOffset, 0x20))) // takerAddress + mstore(add(dataAreaEnd, 0x40), mload(add(sourceOffset, 0x40))) // feeRecipientAddress + mstore(add(dataAreaEnd, 0x60), mload(add(sourceOffset, 0x60))) // senderAddress + mstore(add(dataAreaEnd, 0x80), mload(add(sourceOffset, 0x80))) // makerAssetAmount + mstore(add(dataAreaEnd, 0xA0), mload(add(sourceOffset, 0xA0))) // takerAssetAmount + mstore(add(dataAreaEnd, 0xC0), mload(add(sourceOffset, 0xC0))) // makerFeeAmount + mstore(add(dataAreaEnd, 0xE0), mload(add(sourceOffset, 0xE0))) // takerFeeAmount + mstore(add(dataAreaEnd, 0x100), mload(add(sourceOffset, 0x100))) // expirationTimeSeconds + mstore(add(dataAreaEnd, 0x120), mload(add(sourceOffset, 0x120))) // salt + mstore(add(dataAreaEnd, 0x140), mload(add(sourceOffset, 0x140))) // Offset to makerAssetData + mstore(add(dataAreaEnd, 0x160), mload(add(sourceOffset, 0x160))) // Offset to takerAssetData + dataAreaEnd := add(dataAreaEnd, 0x180) + sourceOffset := add(sourceOffset, 0x180) + + // Write offset to <order.makerAssetData> + mstore(add(dataAreaStart, mul(10, 0x20)), sub(dataAreaEnd, dataAreaStart)) + + // Calculate length of <order.makerAssetData> + sourceOffset := mload(add(order, 0x140)) // makerAssetData + arrayLenBytes := mload(sourceOffset) + sourceOffset := add(sourceOffset, 0x20) + arrayLenWords := div(add(arrayLenBytes, 0x1F), 0x20) + + // Write length of <order.makerAssetData> + mstore(dataAreaEnd, arrayLenBytes) + dataAreaEnd := add(dataAreaEnd, 0x20) + + // Write contents of <order.makerAssetData> + for {let i := 0} lt(i, arrayLenWords) {i := add(i, 1)} { + mstore(dataAreaEnd, mload(sourceOffset)) + dataAreaEnd := add(dataAreaEnd, 0x20) + sourceOffset := add(sourceOffset, 0x20) + } + + // Write offset to <order.takerAssetData> + mstore(add(dataAreaStart, mul(11, 0x20)), sub(dataAreaEnd, dataAreaStart)) + + // Calculate length of <order.takerAssetData> + sourceOffset := mload(add(order, 0x160)) // takerAssetData + arrayLenBytes := mload(sourceOffset) + sourceOffset := add(sourceOffset, 0x20) + arrayLenWords := div(add(arrayLenBytes, 0x1F), 0x20) + + // Write length of <order.takerAssetData> + mstore(dataAreaEnd, arrayLenBytes) + dataAreaEnd := add(dataAreaEnd, 0x20) + + // Write contents of <order.takerAssetData> + for {let i := 0} lt(i, arrayLenWords) {i := add(i, 1)} { + mstore(dataAreaEnd, mload(sourceOffset)) + dataAreaEnd := add(dataAreaEnd, 0x20) + sourceOffset := add(sourceOffset, 0x20) + } + + /////// Write takerAssetFillAmount /////// + mstore(paramsAreaOffset, takerAssetFillAmount) + paramsAreaOffset := add(paramsAreaOffset, 0x20) + + /////// Write signature /////// + // Write offset to paramsArea + mstore(paramsAreaOffset, sub(dataAreaEnd, paramsAreaStart)) + + // Calculate length of signature + sourceOffset := signature + arrayLenBytes := mload(sourceOffset) + sourceOffset := add(sourceOffset, 0x20) + arrayLenWords := div(add(arrayLenBytes, 0x1F), 0x20) + + // Write length of signature + mstore(dataAreaEnd, arrayLenBytes) + dataAreaEnd := add(dataAreaEnd, 0x20) + + // Write contents of signature + for {let i := 0} lt(i, arrayLenWords) {i := add(i, 1)} { + mstore(dataAreaEnd, mload(sourceOffset)) + dataAreaEnd := add(dataAreaEnd, 0x20) + sourceOffset := add(sourceOffset, 0x20) + } + + // Set length of calldata + mstore(fillOrderCalldata, sub(dataAreaEnd, add(fillOrderCalldata, 0x20))) + + // Increment free memory pointer + mstore(0x40, dataAreaEnd) + } + + return fillOrderCalldata; + } +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibConstants.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibConstants.sol new file mode 100644 index 000000000..8d2732cd3 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibConstants.sol @@ -0,0 +1,49 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + + +// solhint-disable max-line-length +contract LibConstants { + + // Asset data for ZRX token. Used for fee transfers. + // @TODO: Hardcode constant when we deploy. Currently + // not constant to make testing easier. + + // The proxyId for ZRX_ASSET_DATA is bytes4(keccak256("ERC20Token(address)")) = 0xf47261b0 + + // Kovan ZRX address is 0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570. + // The ABI encoded proxyId and address is 0xf47261b00000000000000000000000006ff6c0ff1d68b964901f986d4c9fa3ac68346570 + // bytes constant public ZRX_ASSET_DATA = "\xf4\x72\x61\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x6f\xf6\xc0\xff\x1d\x68\xb9\x64\x90\x1f\x98\x6d\x4c\x9f\xa3\xac\x68\x34\x65\x70"; + + // Mainnet ZRX address is 0xe41d2489571d322189246dafa5ebde1f4699f498. + // The ABI encoded proxyId and address is 0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498 + // bytes constant public ZRX_ASSET_DATA = "\xf4\x72\x61\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe4\x1d\x24\x89\x57\x1d\x32\x21\x89\x24\x6d\xaf\xa5\xeb\xde\x1f\x46\x99\xf4\x98"; + + // solhint-disable-next-line var-name-mixedcase + bytes public ZRX_ASSET_DATA; + + // @TODO: Remove when we deploy. + constructor (bytes memory zrxAssetData) + public + { + ZRX_ASSET_DATA = zrxAssetData; + } +} +// solhint-enable max-line-length diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibEIP712.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibEIP712.sol new file mode 100644 index 000000000..b02f7632e --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibEIP712.sol @@ -0,0 +1,86 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + + +contract LibEIP712 { + // EIP191 header for EIP712 prefix + string constant internal EIP191_HEADER = "\x19\x01"; + + // EIP712 Domain Name value + string constant internal EIP712_DOMAIN_NAME = "0x Protocol"; + + // EIP712 Domain Version value + string constant internal EIP712_DOMAIN_VERSION = "2"; + + // Hash of the EIP712 Domain Separator Schema + bytes32 constant internal EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked( + "EIP712Domain(", + "string name,", + "string version,", + "address verifyingContract", + ")" + )); + + // Hash of the EIP712 Domain Separator data + // solhint-disable-next-line var-name-mixedcase + bytes32 public EIP712_DOMAIN_HASH; + + constructor () + public + { + EIP712_DOMAIN_HASH = keccak256(abi.encodePacked( + EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH, + keccak256(bytes(EIP712_DOMAIN_NAME)), + keccak256(bytes(EIP712_DOMAIN_VERSION)), + bytes32(address(this)) + )); + } + + /// @dev Calculates EIP712 encoding for a hash struct in this EIP712 Domain. + /// @param hashStruct The EIP712 hash struct. + /// @return EIP712 hash applied to this EIP712 Domain. + function hashEIP712Message(bytes32 hashStruct) + internal + view + returns (bytes32 result) + { + bytes32 eip712DomainHash = EIP712_DOMAIN_HASH; + + // Assembly for more efficient computing: + // keccak256(abi.encodePacked( + // EIP191_HEADER, + // EIP712_DOMAIN_HASH, + // hashStruct + // )); + + assembly { + // Load free memory pointer + let memPtr := mload(64) + + mstore(memPtr, 0x1901000000000000000000000000000000000000000000000000000000000000) // EIP191 header + mstore(add(memPtr, 2), eip712DomainHash) // EIP712 domain hash + mstore(add(memPtr, 34), hashStruct) // Hash of struct + + // Compute hash + result := keccak256(memPtr, 66) + } + return result; + } +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibExchangeErrors.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibExchangeErrors.sol new file mode 100644 index 000000000..a0f75bc06 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibExchangeErrors.sol @@ -0,0 +1,70 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +// solhint-disable +pragma solidity 0.4.24; + + +/// @dev This contract documents the revert reasons used in the Exchange contract. +/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons. +contract LibExchangeErrors { + + /// Order validation errors /// + string constant ORDER_UNFILLABLE = "ORDER_UNFILLABLE"; // Order cannot be filled. + string constant INVALID_MAKER = "INVALID_MAKER"; // Invalid makerAddress. + string constant INVALID_TAKER = "INVALID_TAKER"; // Invalid takerAddress. + string constant INVALID_SENDER = "INVALID_SENDER"; // Invalid `msg.sender`. + string constant INVALID_ORDER_SIGNATURE = "INVALID_ORDER_SIGNATURE"; // Signature validation failed. + + /// fillOrder validation errors /// + string constant INVALID_TAKER_AMOUNT = "INVALID_TAKER_AMOUNT"; // takerAssetFillAmount cannot equal 0. + string constant ROUNDING_ERROR = "ROUNDING_ERROR"; // Rounding error greater than 0.1% of takerAssetFillAmount. + + /// Signature validation errors /// + string constant INVALID_SIGNATURE = "INVALID_SIGNATURE"; // Signature validation failed. + string constant SIGNATURE_ILLEGAL = "SIGNATURE_ILLEGAL"; // Signature type is illegal. + string constant SIGNATURE_UNSUPPORTED = "SIGNATURE_UNSUPPORTED"; // Signature type unsupported. + + /// cancelOrdersUptTo errors /// + string constant INVALID_NEW_ORDER_EPOCH = "INVALID_NEW_ORDER_EPOCH"; // Specified salt must be greater than or equal to existing orderEpoch. + + /// fillOrKillOrder errors /// + string constant COMPLETE_FILL_FAILED = "COMPLETE_FILL_FAILED"; // Desired takerAssetFillAmount could not be completely filled. + + /// matchOrders errors /// + string constant NEGATIVE_SPREAD_REQUIRED = "NEGATIVE_SPREAD_REQUIRED"; // Matched orders must have a negative spread. + + /// Transaction errors /// + string constant REENTRANCY_ILLEGAL = "REENTRANCY_ILLEGAL"; // Recursive reentrancy is not allowed. + string constant INVALID_TX_HASH = "INVALID_TX_HASH"; // Transaction has already been executed. + string constant INVALID_TX_SIGNATURE = "INVALID_TX_SIGNATURE"; // Signature validation failed. + string constant FAILED_EXECUTION = "FAILED_EXECUTION"; // Transaction execution failed. + + /// registerAssetProxy errors /// + string constant ASSET_PROXY_ALREADY_EXISTS = "ASSET_PROXY_ALREADY_EXISTS"; // AssetProxy with same id already exists. + + /// dispatchTransferFrom errors /// + string constant ASSET_PROXY_DOES_NOT_EXIST = "ASSET_PROXY_DOES_NOT_EXIST"; // No assetProxy registered at given id. + string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Asset transfer unsuccesful. + + /// Length validation errors /// + string constant LENGTH_GREATER_THAN_0_REQUIRED = "LENGTH_GREATER_THAN_0_REQUIRED"; // Byte array must have a length greater than 0. + string constant LENGTH_GREATER_THAN_3_REQUIRED = "LENGTH_GREATER_THAN_3_REQUIRED"; // Byte array must have a length greater than 3. + string constant LENGTH_0_REQUIRED = "LENGTH_0_REQUIRED"; // Byte array must have a length of 0. + string constant LENGTH_65_REQUIRED = "LENGTH_65_REQUIRED"; // Byte array must have a length of 65. +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibFillResults.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibFillResults.sol new file mode 100644 index 000000000..1b4181d94 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibFillResults.sol @@ -0,0 +1,54 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../../utils/SafeMath/SafeMath.sol"; + + +contract LibFillResults is + SafeMath +{ + + struct FillResults { + uint256 makerAssetFilledAmount; // Total amount of makerAsset(s) filled. + uint256 takerAssetFilledAmount; // Total amount of takerAsset(s) filled. + uint256 makerFeePaid; // Total amount of ZRX paid by maker(s) to feeRecipient(s). + uint256 takerFeePaid; // Total amount of ZRX paid by taker to feeRecipients(s). + } + + struct MatchedFillResults { + FillResults left; // Amounts filled and fees paid of left order. + FillResults right; // Amounts filled and fees paid of right order. + uint256 leftMakerAssetSpreadAmount; // Spread between price of left and right order, denominated in the left order's makerAsset, paid to taker. + } + + /// @dev Adds properties of both FillResults instances. + /// Modifies the first FillResults instance specified. + /// @param totalFillResults Fill results instance that will be added onto. + /// @param singleFillResults Fill results instance that will be added to totalFillResults. + function addFillResults(FillResults memory totalFillResults, FillResults memory singleFillResults) + internal + pure + { + totalFillResults.makerAssetFilledAmount = safeAdd(totalFillResults.makerAssetFilledAmount, singleFillResults.makerAssetFilledAmount); + totalFillResults.takerAssetFilledAmount = safeAdd(totalFillResults.takerAssetFilledAmount, singleFillResults.takerAssetFilledAmount); + totalFillResults.makerFeePaid = safeAdd(totalFillResults.makerFeePaid, singleFillResults.makerFeePaid); + totalFillResults.takerFeePaid = safeAdd(totalFillResults.takerFeePaid, singleFillResults.takerFeePaid); + } +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol new file mode 100644 index 000000000..fa09da6ac --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol @@ -0,0 +1,75 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../../utils/SafeMath/SafeMath.sol"; + + +contract LibMath is + SafeMath +{ + + /// @dev Calculates partial value given a numerator and denominator. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to calculate partial of. + /// @return Partial value of target. + function getPartialAmount( + uint256 numerator, + uint256 denominator, + uint256 target + ) + internal + pure + returns (uint256 partialAmount) + { + partialAmount = safeDiv( + safeMul(numerator, target), + denominator + ); + return partialAmount; + } + + /// @dev Checks if rounding error > 0.1%. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to multiply with numerator/denominator. + /// @return Rounding error is present. + function isRoundingError( + uint256 numerator, + uint256 denominator, + uint256 target + ) + internal + pure + returns (bool isError) + { + uint256 remainder = mulmod(target, numerator, denominator); + if (remainder == 0) { + return false; // No rounding error. + } + + uint256 errPercentageTimes1000000 = safeDiv( + safeMul(remainder, 1000000), + safeMul(numerator, target) + ); + isError = errPercentageTimes1000000 > 1000; + return isError; + } +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibOrder.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibOrder.sol new file mode 100644 index 000000000..68f4f5f1b --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibOrder.sol @@ -0,0 +1,146 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "./LibEIP712.sol"; + + +contract LibOrder is + LibEIP712 +{ + + // Hash for the EIP712 Order Schema + bytes32 constant internal EIP712_ORDER_SCHEMA_HASH = keccak256(abi.encodePacked( + "Order(", + "address makerAddress,", + "address takerAddress,", + "address feeRecipientAddress,", + "address senderAddress,", + "uint256 makerAssetAmount,", + "uint256 takerAssetAmount,", + "uint256 makerFee,", + "uint256 takerFee,", + "uint256 expirationTimeSeconds,", + "uint256 salt,", + "bytes makerAssetData,", + "bytes takerAssetData", + ")" + )); + + // A valid order remains fillable until it is expired, fully filled, or cancelled. + // An order's state is unaffected by external factors, like account balances. + enum OrderStatus { + INVALID, // Default value + INVALID_MAKER_ASSET_AMOUNT, // Order does not have a valid maker asset amount + INVALID_TAKER_ASSET_AMOUNT, // Order does not have a valid taker asset amount + FILLABLE, // Order is fillable + EXPIRED, // Order has already expired + FULLY_FILLED, // Order is fully filled + CANCELLED // Order has been cancelled + } + + // solhint-disable max-line-length + struct Order { + address makerAddress; // Address that created the order. + address takerAddress; // Address that is allowed to fill the order. If set to 0, any address is allowed to fill the order. + address feeRecipientAddress; // Address that will recieve fees when order is filled. + address senderAddress; // Address that is allowed to call Exchange contract methods that affect this order. If set to 0, any address is allowed to call these methods. + uint256 makerAssetAmount; // Amount of makerAsset being offered by maker. Must be greater than 0. + uint256 takerAssetAmount; // Amount of takerAsset being bid on by maker. Must be greater than 0. + uint256 makerFee; // Amount of ZRX paid to feeRecipient by maker when order is filled. If set to 0, no transfer of ZRX from maker to feeRecipient will be attempted. + uint256 takerFee; // Amount of ZRX paid to feeRecipient by taker when order is filled. If set to 0, no transfer of ZRX from taker to feeRecipient will be attempted. + uint256 expirationTimeSeconds; // Timestamp in seconds at which order expires. + uint256 salt; // Arbitrary number to facilitate uniqueness of the order's hash. + bytes makerAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring makerAsset. The last byte references the id of this proxy. + bytes takerAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring takerAsset. The last byte references the id of this proxy. + } + // solhint-enable max-line-length + + struct OrderInfo { + uint8 orderStatus; // Status that describes order's validity and fillability. + bytes32 orderHash; // EIP712 hash of the order (see LibOrder.getOrderHash). + uint256 orderTakerAssetFilledAmount; // Amount of order that has already been filled. + } + + /// @dev Calculates Keccak-256 hash of the order. + /// @param order The order structure. + /// @return Keccak-256 EIP712 hash of the order. + function getOrderHash(Order memory order) + internal + view + returns (bytes32 orderHash) + { + orderHash = hashEIP712Message(hashOrder(order)); + return orderHash; + } + + /// @dev Calculates EIP712 hash of the order. + /// @param order The order structure. + /// @return EIP712 hash of the order. + function hashOrder(Order memory order) + internal + pure + returns (bytes32 result) + { + bytes32 schemaHash = EIP712_ORDER_SCHEMA_HASH; + bytes32 makerAssetDataHash = keccak256(order.makerAssetData); + bytes32 takerAssetDataHash = keccak256(order.takerAssetData); + + // Assembly for more efficiently computing: + // keccak256(abi.encodePacked( + // EIP712_ORDER_SCHEMA_HASH, + // bytes32(order.makerAddress), + // bytes32(order.takerAddress), + // bytes32(order.feeRecipientAddress), + // bytes32(order.senderAddress), + // order.makerAssetAmount, + // order.takerAssetAmount, + // order.makerFee, + // order.takerFee, + // order.expirationTimeSeconds, + // order.salt, + // keccak256(order.makerAssetData), + // keccak256(order.takerAssetData) + // )); + + assembly { + // Calculate memory addresses that will be swapped out before hashing + let pos1 := sub(order, 32) + let pos2 := add(order, 320) + let pos3 := add(order, 352) + + // Backup + let temp1 := mload(pos1) + let temp2 := mload(pos2) + let temp3 := mload(pos3) + + // Hash in place + mstore(pos1, schemaHash) + mstore(pos2, makerAssetDataHash) + mstore(pos3, takerAssetDataHash) + result := keccak256(pos1, 416) + + // Restore + mstore(pos1, temp1) + mstore(pos2, temp2) + mstore(pos3, temp3) + } + return result; + } +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MAssetProxyDispatcher.sol b/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MAssetProxyDispatcher.sol new file mode 100644 index 000000000..c6904300a --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MAssetProxyDispatcher.sol @@ -0,0 +1,46 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../interfaces/IAssetProxyDispatcher.sol"; + + +contract MAssetProxyDispatcher is + IAssetProxyDispatcher +{ + + // Logs registration of new asset proxy + event AssetProxyRegistered( + bytes4 id, // Id of new registered AssetProxy. + address assetProxy // Address of new registered AssetProxy. + ); + + /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws. + /// @param assetData Byte array encoded for the asset. + /// @param from Address to transfer token from. + /// @param to Address to transfer token to. + /// @param amount Amount of token to transfer. + function dispatchTransferFrom( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + internal; +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MExchangeCore.sol b/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MExchangeCore.sol new file mode 100644 index 000000000..c165b647c --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MExchangeCore.sol @@ -0,0 +1,127 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../libs/LibOrder.sol"; +import "../libs/LibFillResults.sol"; +import "../interfaces/IExchangeCore.sol"; + + +contract MExchangeCore is + IExchangeCore +{ + // Fill event is emitted whenever an order is filled. + event Fill( + address indexed makerAddress, // Address that created the order. + address indexed feeRecipientAddress, // Address that received fees. + address takerAddress, // Address that filled the order. + address senderAddress, // Address that called the Exchange contract (msg.sender). + uint256 makerAssetFilledAmount, // Amount of makerAsset sold by maker and bought by taker. + uint256 takerAssetFilledAmount, // Amount of takerAsset sold by taker and bought by maker. + uint256 makerFeePaid, // Amount of ZRX paid to feeRecipient by maker. + uint256 takerFeePaid, // Amount of ZRX paid to feeRecipient by taker. + bytes32 indexed orderHash, // EIP712 hash of order (see LibOrder.getOrderHash). + bytes makerAssetData, // Encoded data specific to makerAsset. + bytes takerAssetData // Encoded data specific to takerAsset. + ); + + // Cancel event is emitted whenever an individual order is cancelled. + event Cancel( + address indexed makerAddress, // Address that created the order. + address indexed feeRecipientAddress, // Address that would have recieved fees if order was filled. + address senderAddress, // Address that called the Exchange contract (msg.sender). + bytes32 indexed orderHash, // EIP712 hash of order (see LibOrder.getOrderHash). + bytes makerAssetData, // Encoded data specific to makerAsset. + bytes takerAssetData // Encoded data specific to takerAsset. + ); + + // CancelUpTo event is emitted whenever `cancelOrdersUpTo` is executed succesfully. + event CancelUpTo( + address indexed makerAddress, // Orders cancelled must have been created by this address. + address indexed senderAddress, // Orders cancelled must have a `senderAddress` equal to this address. + uint256 orderEpoch // Orders with specified makerAddress and senderAddress with a salt less than this value are considered cancelled. + ); + + /// @dev Updates state with results of a fill order. + /// @param order that was filled. + /// @param takerAddress Address of taker who filled the order. + /// @param orderTakerAssetFilledAmount Amount of order already filled. + /// @return fillResults Amounts filled and fees paid by maker and taker. + function updateFilledState( + LibOrder.Order memory order, + address takerAddress, + bytes32 orderHash, + uint256 orderTakerAssetFilledAmount, + LibFillResults.FillResults memory fillResults + ) + internal; + + /// @dev Updates state with results of cancelling an order. + /// State is only updated if the order is currently fillable. + /// Otherwise, updating state would have no effect. + /// @param order that was cancelled. + /// @param orderHash Hash of order that was cancelled. + function updateCancelledState( + LibOrder.Order memory order, + bytes32 orderHash + ) + internal; + + /// @dev Validates context for fillOrder. Succeeds or throws. + /// @param order to be filled. + /// @param orderInfo Status, orderHash, and amount already filled of order. + /// @param takerAddress Address of order taker. + /// @param takerAssetFillAmount Desired amount of order to fill by taker. + /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. + /// @param signature Proof that the orders was created by its maker. + function assertValidFill( + LibOrder.Order memory order, + LibOrder.OrderInfo memory orderInfo, + address takerAddress, + uint256 takerAssetFillAmount, + uint256 takerAssetFilledAmount, + bytes memory signature + ) + internal + view; + + /// @dev Validates context for cancelOrder. Succeeds or throws. + /// @param order to be cancelled. + /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. + function assertValidCancel( + LibOrder.Order memory order, + LibOrder.OrderInfo memory orderInfo + ) + internal + view; + + /// @dev Calculates amounts filled and fees paid by maker and taker. + /// @param order to be filled. + /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. + /// @return fillResults Amounts filled and fees paid by maker and taker. + function calculateFillResults( + LibOrder.Order memory order, + uint256 takerAssetFilledAmount + ) + internal + pure + returns (LibFillResults.FillResults memory fillResults); + +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MMatchOrders.sol b/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MMatchOrders.sol new file mode 100644 index 000000000..a31ec1585 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MMatchOrders.sol @@ -0,0 +1,59 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../libs/LibOrder.sol"; +import "../libs/LibFillResults.sol"; +import "../interfaces/IMatchOrders.sol"; + + +contract MMatchOrders is + IMatchOrders +{ + + /// @dev Validates context for matchOrders. Succeeds or throws. + /// @param leftOrder First order to match. + /// @param rightOrder Second order to match. + function assertValidMatch( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder + ) + internal + pure; + + /// @dev Calculates fill amounts for the matched orders. + /// Each order is filled at their respective price point. However, the calculations are + /// carried out as though the orders are both being filled at the right order's price point. + /// The profit made by the leftOrder order goes to the taker (who matched the two orders). + /// @param leftOrder First order to match. + /// @param rightOrder Second order to match. + /// @param leftOrderTakerAssetFilledAmount Amount of left order already filled. + /// @param rightOrderTakerAssetFilledAmount Amount of right order already filled. + /// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders. + function calculateMatchedFillResults( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + uint256 leftOrderTakerAssetFilledAmount, + uint256 rightOrderTakerAssetFilledAmount + ) + internal + pure + returns (LibFillResults.MatchedFillResults memory matchedFillResults); + +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MSignatureValidator.sol b/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MSignatureValidator.sol new file mode 100644 index 000000000..f14f2ba00 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MSignatureValidator.sol @@ -0,0 +1,46 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../interfaces/ISignatureValidator.sol"; + + +contract MSignatureValidator is + ISignatureValidator +{ + event SignatureValidatorApproval( + address indexed signerAddress, // Address that approves or disapproves a contract to verify signatures. + address indexed validatorAddress, // Address of signature validator contract. + bool approved // Approval or disapproval of validator contract. + ); + + // Allowed signature types. + enum SignatureType { + Illegal, // 0x00, default value + Invalid, // 0x01 + EIP712, // 0x02 + EthSign, // 0x03 + Caller, // 0x04 + Wallet, // 0x05 + Validator, // 0x06 + PreSigned, // 0x07 + Trezor, // 0x08 + NSignatureTypes // 0x09, number of signature types. Always leave at end. + } +} diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MTransactions.sol b/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MTransactions.sol new file mode 100644 index 000000000..f2b5e4b16 --- /dev/null +++ b/packages/contracts/src/2.0.0/protocol/Exchange/mixins/MTransactions.sol @@ -0,0 +1,36 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +pragma solidity 0.4.24; + +import "../interfaces/ITransactions.sol"; + + +contract MTransactions is + ITransactions +{ + + /// @dev The current function will be called in the context of this address (either 0x transaction signer or `msg.sender`). + /// If calling a fill function, this address will represent the taker. + /// If calling a cancel function, this address will represent the maker. + /// @return Signer of 0x transaction if entry point is `executeTransaction`. + /// `msg.sender` if entry point is any other function. + function getCurrentContextAddress() + internal + view + returns (address); +} diff --git a/packages/contracts/src/2.0.0/test/DummyERC20Token/DummyERC20Token.sol b/packages/contracts/src/2.0.0/test/DummyERC20Token/DummyERC20Token.sol new file mode 100644 index 000000000..9272b18a8 --- /dev/null +++ b/packages/contracts/src/2.0.0/test/DummyERC20Token/DummyERC20Token.sol @@ -0,0 +1,60 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../Mintable/Mintable.sol"; +import "../../utils/Ownable/Ownable.sol"; + + +contract DummyERC20Token is + Mintable, + Ownable +{ + string public name; + string public symbol; + uint256 public decimals; + + constructor ( + string _name, + string _symbol, + uint256 _decimals, + uint256 _totalSupply + ) + public + { + name = _name; + symbol = _symbol; + decimals = _decimals; + totalSupply = _totalSupply; + balances[msg.sender] = _totalSupply; + } + + function setBalance(address _target, uint256 _value) + public + onlyOwner + { + uint256 currBalance = balanceOf(_target); + if (_value < currBalance) { + totalSupply = safeSub(totalSupply, safeSub(currBalance, _value)); + } else { + totalSupply = safeAdd(totalSupply, safeSub(_value, currBalance)); + } + balances[_target] = _value; + } +} diff --git a/packages/contracts/src/2.0.0/test/DummyERC721Receiver/DummyERC721Receiver.sol b/packages/contracts/src/2.0.0/test/DummyERC721Receiver/DummyERC721Receiver.sol new file mode 100644 index 000000000..5dce74a14 --- /dev/null +++ b/packages/contracts/src/2.0.0/test/DummyERC721Receiver/DummyERC721Receiver.sol @@ -0,0 +1,64 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 Smart Contract Solutions, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +pragma solidity 0.4.24; + +import "../../tokens/ERC721Token/IERC721Receiver.sol"; + + +contract DummyERC721Receiver is + IERC721Receiver +{ + + event TokenReceived( + address from, + uint256 tokenId, + bytes data + ); + + /** + * @notice Handle the receipt of an NFT + * @dev The ERC721 smart contract calls this function on the recipient + * after a `safetransfer`. This function MAY throw to revert and reject the + * transfer. This function MUST use 50,000 gas or less. Return of other + * than the magic value MUST result in the transaction being reverted. + * Note: the contract address is always the message sender. + * @param _from The sending address + * @param _tokenId The NFT identifier which is being transfered + * @param _data Additional data with no specified format + * @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))` + */ + function onERC721Received( + address _from, + uint256 _tokenId, + bytes _data + ) + public + returns (bytes4) + { + emit TokenReceived(_from, _tokenId, _data); + return ERC721_RECEIVED; + } +} diff --git a/packages/contracts/src/2.0.0/test/DummyERC721Token/DummyERC721Token.sol b/packages/contracts/src/2.0.0/test/DummyERC721Token/DummyERC721Token.sol new file mode 100644 index 000000000..627746a52 --- /dev/null +++ b/packages/contracts/src/2.0.0/test/DummyERC721Token/DummyERC721Token.sol @@ -0,0 +1,76 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../tokens/ERC721Token/ERC721Token.sol"; +import "../../utils/Ownable/Ownable.sol"; + + +// solhint-disable no-empty-blocks +contract DummyERC721Token is + Ownable, + ERC721Token +{ + + /** + * @dev Constructor passes its arguments to the base ERC721Token constructor + * @param name of token + * @param symbol of token + */ + constructor ( + string name, + string symbol + ) + public + ERC721Token(name, symbol) + {} + + /** + * @dev Function to mint a new token + * @dev Reverts if the given token ID already exists + * @param to address the beneficiary that will own the minted token + * @param tokenId uint256 ID of the token to be minted by the msg.sender + */ + function mint(address to, uint256 tokenId) + public + onlyOwner + { + require( + !exists(tokenId), + "Token with tokenId already exists." + ); + _mint(to, tokenId); + } + + /** + * @dev Function to burn a token + * @dev Reverts if the given token ID doesn't exist + * @param tokenId uint256 ID of the token to be minted by the msg.sender + */ + function burn(address owner, uint256 tokenId) + public + onlyOwner + { + require( + exists(tokenId), + "Token with tokenId does not exist." + ); + _burn(owner, tokenId); + } +} diff --git a/packages/contracts/src/2.0.0/test/Mintable/Mintable.sol b/packages/contracts/src/2.0.0/test/Mintable/Mintable.sol new file mode 100644 index 000000000..767cc8d25 --- /dev/null +++ b/packages/contracts/src/2.0.0/test/Mintable/Mintable.sol @@ -0,0 +1,43 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol"; +import "../../utils/SafeMath/SafeMath.sol"; + + +/* + * Mintable + * Base contract that creates a mintable UnlimitedAllowanceToken + */ +contract Mintable is + UnlimitedAllowanceToken, + SafeMath +{ + function mint(uint256 _value) + public + { + require( + _value <= 100000000000000000000, + "Minting more than 100000000000000000000 is not allowed." + ); + balances[msg.sender] = safeAdd(_value, balances[msg.sender]); + totalSupply = safeAdd(totalSupply, _value); + } +} diff --git a/packages/contracts/src/2.0.0/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol b/packages/contracts/src/2.0.0/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol new file mode 100644 index 000000000..ad71fc9a1 --- /dev/null +++ b/packages/contracts/src/2.0.0/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol @@ -0,0 +1,37 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../protocol/Exchange/MixinAssetProxyDispatcher.sol"; + + +contract TestAssetProxyDispatcher is + MixinAssetProxyDispatcher +{ + function publicDispatchTransferFrom( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + public + { + dispatchTransferFrom(assetData, from, to, amount); + } +} diff --git a/packages/contracts/src/2.0.0/test/TestAssetProxyOwner/TestAssetProxyOwner.sol b/packages/contracts/src/2.0.0/test/TestAssetProxyOwner/TestAssetProxyOwner.sol new file mode 100644 index 000000000..75e782d43 --- /dev/null +++ b/packages/contracts/src/2.0.0/test/TestAssetProxyOwner/TestAssetProxyOwner.sol @@ -0,0 +1,72 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.10; + +import "../../protocol/AssetProxyOwner/AssetProxyOwner.sol"; + + +// solhint-disable no-empty-blocks +contract TestAssetProxyOwner is + AssetProxyOwner +{ + + function TestAssetProxyOwner( + address[] memory _owners, + address[] memory _assetProxyContracts, + uint256 _required, + uint256 _secondsTimeLocked + ) + public + AssetProxyOwner(_owners, _assetProxyContracts, _required, _secondsTimeLocked) + {} + + function testValidRemoveAuthorizedAddressAtIndexTx(uint256 id) + public + validRemoveAuthorizedAddressAtIndexTx(id) + returns (bool) + { + // Do nothing. We expect reverts through the modifier + return true; + } + + /// @dev Compares first 4 bytes of byte array to `removeAuthorizedAddressAtIndex` function selector. + /// @param data Transaction data. + /// @return Successful if data is a call to `removeAuthorizedAddressAtIndex`. + function isFunctionRemoveAuthorizedAddressAtIndex(bytes memory data) + public + returns (bool) + { + return readBytes4(data, 0) == REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR; + } + + /// @dev Reads an unpadded bytes4 value from a position in a byte array. + /// @param b Byte array containing a bytes4 value. + /// @param index Index in byte array of bytes4 value. + /// @return bytes4 value from byte array. + function publicReadBytes4( + bytes memory b, + uint256 index + ) + public + returns (bytes4 result) + { + result = readBytes4(b, index); + return result; + } +} diff --git a/packages/contracts/src/2.0.0/test/TestConstants/TestConstants.sol b/packages/contracts/src/2.0.0/test/TestConstants/TestConstants.sol new file mode 100644 index 000000000..1275d007b --- /dev/null +++ b/packages/contracts/src/2.0.0/test/TestConstants/TestConstants.sol @@ -0,0 +1,57 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../utils/LibBytes/LibBytes.sol"; + + +// solhint-disable max-line-length +contract TestConstants { + + using LibBytes for bytes; + + bytes4 constant internal ERC20_PROXY_ID = bytes4(keccak256("ERC20Token(address)")); + + address constant internal KOVAN_ZRX_ADDRESS = 0x6Ff6C0Ff1d68b964901F986d4C9FA3ac68346570; + bytes constant internal KOVAN_ZRX_ASSET_DATA = "\xf4\x72\x61\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x6f\xf6\xc0\xff\x1d\x68\xb9\x64\x90\x1f\x98\x6d\x4c\x9f\xa3\xac\x68\x34\x65\x70"; + + address constant internal MAINNET_ZRX_ADDRESS = 0xE41d2489571d322189246DaFA5ebDe1F4699F498; + bytes constant public MAINNET_ZRX_ASSET_DATA = "\xf4\x72\x61\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe4\x1d\x24\x89\x57\x1d\x32\x21\x89\x24\x6d\xaf\xa5\xeb\xde\x1f\x46\x99\xf4\x98"; + + function assertValidZrxAssetData() + public + pure + returns (bool) + { + bytes memory kovanZrxAssetData = abi.encodeWithSelector(ERC20_PROXY_ID, KOVAN_ZRX_ADDRESS); + require( + kovanZrxAssetData.equals(KOVAN_ZRX_ASSET_DATA), + "INVALID_KOVAN_ZRX_ASSET_DATA" + ); + + bytes memory mainetZrxAssetData = abi.encodeWithSelector(ERC20_PROXY_ID, MAINNET_ZRX_ADDRESS); + require( + mainetZrxAssetData.equals(MAINNET_ZRX_ASSET_DATA), + "INVALID_MAINNET_ZRX_ASSET_DATA" + ); + + return true; + } +} +// solhint-enable max-line-length
\ No newline at end of file diff --git a/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol b/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol new file mode 100644 index 000000000..d9cec9edc --- /dev/null +++ b/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol @@ -0,0 +1,121 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/Exchange/Exchange.sol"; + + +// solhint-disable no-empty-blocks +contract TestExchangeInternals is + Exchange +{ + constructor () + public + Exchange("") + {} + + /// @dev Adds properties of both FillResults instances. + /// Modifies the first FillResults instance specified. + /// Note that this function has been modified from the original + // internal version to return the FillResults. + /// @param totalFillResults Fill results instance that will be added onto. + /// @param singleFillResults Fill results instance that will be added to totalFillResults. + /// @return newTotalFillResults The result of adding singleFillResults to totalFilResults. + function publicAddFillResults(FillResults memory totalFillResults, FillResults memory singleFillResults) + public + pure + returns (FillResults memory) + { + addFillResults(totalFillResults, singleFillResults); + return totalFillResults; + } + + /// @dev Calculates amounts filled and fees paid by maker and taker. + /// @param order to be filled. + /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. + /// @return fillResults Amounts filled and fees paid by maker and taker. + function publicCalculateFillResults( + Order memory order, + uint256 takerAssetFilledAmount + ) + public + pure + returns (FillResults memory fillResults) + { + return calculateFillResults(order, takerAssetFilledAmount); + } + + /// @dev Calculates partial value given a numerator and denominator. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to calculate partial of. + /// @return Partial value of target. + function publicGetPartialAmount( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (uint256 partialAmount) + { + return getPartialAmount(numerator, denominator, target); + } + + /// @dev Checks if rounding error > 0.1%. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to multiply with numerator/denominator. + /// @return Rounding error is present. + function publicIsRoundingError( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (bool isError) + { + return isRoundingError(numerator, denominator, target); + } + + /// @dev Updates state with results of a fill order. + /// @param order that was filled. + /// @param takerAddress Address of taker who filled the order. + /// @param orderTakerAssetFilledAmount Amount of order already filled. + /// @return fillResults Amounts filled and fees paid by maker and taker. + function publicUpdateFilledState( + Order memory order, + address takerAddress, + bytes32 orderHash, + uint256 orderTakerAssetFilledAmount, + FillResults memory fillResults + ) + public + { + updateFilledState( + order, + takerAddress, + orderHash, + orderTakerAssetFilledAmount, + fillResults + ); + } +} diff --git a/packages/contracts/src/2.0.0/test/TestLibBytes/TestLibBytes.sol b/packages/contracts/src/2.0.0/test/TestLibBytes/TestLibBytes.sol new file mode 100644 index 000000000..00d861e61 --- /dev/null +++ b/packages/contracts/src/2.0.0/test/TestLibBytes/TestLibBytes.sol @@ -0,0 +1,269 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../utils/LibBytes/LibBytes.sol"; + + +contract TestLibBytes { + + using LibBytes for bytes; + + /// @dev Pops the last byte off of a byte array by modifying its length. + /// @param b Byte array that will be modified. + /// @return The byte that was popped off. + function publicPopLastByte(bytes memory b) + public + pure + returns (bytes memory, bytes1 result) + { + result = b.popLastByte(); + return (b, result); + } + + /// @dev Pops the last 20 bytes off of a byte array by modifying its length. + /// @param b Byte array that will be modified. + /// @return The 20 byte address that was popped off. + function publicPopLast20Bytes(bytes memory b) + public + pure + returns (bytes memory, address result) + { + result = b.popLast20Bytes(); + return (b, result); + } + + /// @dev Tests equality of two byte arrays. + /// @param lhs First byte array to compare. + /// @param rhs Second byte array to compare. + /// @return True if arrays are the same. False otherwise. + function publicEquals(bytes memory lhs, bytes memory rhs) + public + pure + returns (bool equal) + { + equal = lhs.equals(rhs); + return equal; + } + + function publicEqualsPop1(bytes memory lhs, bytes memory rhs) + public + pure + returns (bool equal) + { + lhs.popLastByte(); + rhs.popLastByte(); + equal = lhs.equals(rhs); + return equal; + } + + /// @dev Performs a deep copy of a byte array onto another byte array of greater than or equal length. + /// @param dest Byte array that will be overwritten with source bytes. + /// @param source Byte array to copy onto dest bytes. + function publicDeepCopyBytes( + bytes memory dest, + bytes memory source + ) + public + pure + returns (bytes memory) + { + LibBytes.deepCopyBytes(dest, source); + return dest; + } + + /// @dev Reads an address from a position in a byte array. + /// @param b Byte array containing an address. + /// @param index Index in byte array of address. + /// @return address from byte array. + function publicReadAddress( + bytes memory b, + uint256 index + ) + public + pure + returns (address result) + { + result = b.readAddress(index); + return result; + } + + /// @dev Writes an address into a specific position in a byte array. + /// @param b Byte array to insert address into. + /// @param index Index in byte array of address. + /// @param input Address to put into byte array. + function publicWriteAddress( + bytes memory b, + uint256 index, + address input + ) + public + pure + returns (bytes memory) + { + b.writeAddress(index, input); + return b; + } + + /// @dev Reads a bytes32 value from a position in a byte array. + /// @param b Byte array containing a bytes32 value. + /// @param index Index in byte array of bytes32 value. + /// @return bytes32 value from byte array. + function publicReadBytes32( + bytes memory b, + uint256 index + ) + public + pure + returns (bytes32 result) + { + result = b.readBytes32(index); + return result; + } + + /// @dev Writes a bytes32 into a specific position in a byte array. + /// @param b Byte array to insert <input> into. + /// @param index Index in byte array of <input>. + /// @param input bytes32 to put into byte array. + function publicWriteBytes32( + bytes memory b, + uint256 index, + bytes32 input + ) + public + pure + returns (bytes memory) + { + b.writeBytes32(index, input); + return b; + } + + /// @dev Reads a uint256 value from a position in a byte array. + /// @param b Byte array containing a uint256 value. + /// @param index Index in byte array of uint256 value. + /// @return uint256 value from byte array. + function publicReadUint256( + bytes memory b, + uint256 index + ) + public + pure + returns (uint256 result) + { + result = b.readUint256(index); + return result; + } + + /// @dev Writes a uint256 into a specific position in a byte array. + /// @param b Byte array to insert <input> into. + /// @param index Index in byte array of <input>. + /// @param input uint256 to put into byte array. + function publicWriteUint256( + bytes memory b, + uint256 index, + uint256 input + ) + public + pure + returns (bytes memory) + { + b.writeUint256(index, input); + return b; + } + + /// @dev Reads an unpadded bytes4 value from a position in a byte array. + /// @param b Byte array containing a bytes4 value. + /// @param index Index in byte array of bytes4 value. + /// @return bytes4 value from byte array. + function publicReadBytes4( + bytes memory b, + uint256 index + ) + public + pure + returns (bytes4 result) + { + result = b.readBytes4(index); + return result; + } + + /// @dev Reads nested bytes from a specific position. + /// @param b Byte array containing nested bytes. + /// @param index Index of nested bytes. + /// @return result Nested bytes. + function publicReadBytesWithLength( + bytes memory b, + uint256 index + ) + public + pure + returns (bytes memory result) + { + result = b.readBytesWithLength(index); + return result; + } + + /// @dev Inserts bytes at a specific position in a byte array. + /// @param b Byte array to insert <input> into. + /// @param index Index in byte array of <input>. + /// @param input bytes to insert. + /// @return b Updated input byte array + function publicWriteBytesWithLength( + bytes memory b, + uint256 index, + bytes memory input + ) + public + pure + returns (bytes memory) + { + b.writeBytesWithLength(index, input); + return b; + } + + /// @dev Copies a block of memory from one location to another. + /// @param mem Memory contents we want to apply memCopy to + /// @param dest Destination offset into <mem>. + /// @param source Source offset into <mem>. + /// @param length Length of bytes to copy from <source> to <dest> + /// @return mem Memory contents after calling memCopy. + function testMemcpy( + bytes mem, + uint256 dest, + uint256 source, + uint256 length + ) + public // not external, we need input in memory + pure + returns (bytes) + { + // Sanity check. Overflows are not checked. + require(source + length <= mem.length); + require(dest + length <= mem.length); + + // Get pointer to memory contents + uint256 offset = mem.contentAddress(); + + // Execute memCopy adjusted for memory array location + LibBytes.memCopy(offset + dest, offset + source, length); + + // Return modified memory contents + return mem; + } +} diff --git a/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol b/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol new file mode 100644 index 000000000..4a99dd9c1 --- /dev/null +++ b/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol @@ -0,0 +1,119 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/Exchange/libs/LibMath.sol"; +import "../../protocol/Exchange/libs/LibOrder.sol"; +import "../../protocol/Exchange/libs/LibFillResults.sol"; +import "../../protocol/Exchange/libs/LibAbiEncoder.sol"; + + +contract TestLibs is + LibMath, + LibOrder, + LibFillResults, + LibAbiEncoder +{ + + function publicAbiEncodeFillOrder( + Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + public + pure + returns (bytes memory fillOrderCalldata) + { + fillOrderCalldata = abiEncodeFillOrder( + order, + takerAssetFillAmount, + signature + ); + return fillOrderCalldata; + } + + function publicGetPartialAmount( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (uint256 partialAmount) + { + partialAmount = getPartialAmount( + numerator, + denominator, + target + ); + return partialAmount; + } + + function publicIsRoundingError( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (bool isError) + { + isError = isRoundingError( + numerator, + denominator, + target + ); + return isError; + } + + function publicGetOrderHash(Order memory order) + public + view + returns (bytes32 orderHash) + { + orderHash = getOrderHash(order); + return orderHash; + } + + function getOrderSchemaHash() + public + pure + returns (bytes32) + { + return EIP712_ORDER_SCHEMA_HASH; + } + + function getDomainSeparatorSchemaHash() + public + pure + returns (bytes32) + { + return EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH; + } + + function publicAddFillResults(FillResults memory totalFillResults, FillResults memory singleFillResults) + public + pure + returns (FillResults memory) + { + addFillResults(totalFillResults, singleFillResults); + return totalFillResults; + } +} diff --git a/packages/contracts/src/2.0.0/test/TestSignatureValidator/TestSignatureValidator.sol b/packages/contracts/src/2.0.0/test/TestSignatureValidator/TestSignatureValidator.sol new file mode 100644 index 000000000..e1a610469 --- /dev/null +++ b/packages/contracts/src/2.0.0/test/TestSignatureValidator/TestSignatureValidator.sol @@ -0,0 +1,46 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../protocol/Exchange/MixinSignatureValidator.sol"; +import "../../protocol/Exchange/MixinTransactions.sol"; + + +contract TestSignatureValidator is + MixinSignatureValidator, + MixinTransactions +{ + + function publicIsValidSignature( + bytes32 hash, + address signer, + bytes memory signature + ) + public + view + returns (bool isValid) + { + isValid = isValidSignature( + hash, + signer, + signature + ); + return isValid; + } +} diff --git a/packages/contracts/src/2.0.0/tokens/ERC20Token/ERC20Token.sol b/packages/contracts/src/2.0.0/tokens/ERC20Token/ERC20Token.sol new file mode 100644 index 000000000..d9950145d --- /dev/null +++ b/packages/contracts/src/2.0.0/tokens/ERC20Token/ERC20Token.sol @@ -0,0 +1,96 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "./IERC20Token.sol"; + + +contract ERC20Token is IERC20Token { + + mapping (address => uint256) internal balances; + mapping (address => mapping (address => uint256)) internal allowed; + + uint256 public totalSupply; + + function transfer(address _to, uint256 _value) + public + returns (bool) + { + require( + balances[msg.sender] >= _value, + "ERC20_INSUFFICIENT_BALANCE" + ); + require( + balances[_to] + _value >= balances[_to], + "OVERFLOW" + ); + balances[msg.sender] -= _value; + balances[_to] += _value; + emit Transfer(msg.sender, _to, _value); + return true; + } + + function transferFrom(address _from, address _to, uint256 _value) + public + returns (bool) + { + require( + balances[_from] >= _value, + "ERC20_INSUFFICIENT_BALANCE" + ); + require( + allowed[_from][msg.sender] >= _value, + "ERC20_INSUFFICIENT_ALLOWANCE" + ); + require( + balances[_to] + _value >= balances[_to], + "OVERFLOW" + ); + balances[_to] += _value; + balances[_from] -= _value; + allowed[_from][msg.sender] -= _value; + emit Transfer(_from, _to, _value); + return true; + } + + function approve(address _spender, uint256 _value) + public + returns (bool) + { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + + function balanceOf(address _owner) + public + view + returns (uint256) + { + return balances[_owner]; + } + + function allowance(address _owner, address _spender) + public + view + returns (uint256) + { + return allowed[_owner][_spender]; + } +} diff --git a/packages/contracts/src/2.0.0/tokens/ERC20Token/IERC20Token.sol b/packages/contracts/src/2.0.0/tokens/ERC20Token/IERC20Token.sol new file mode 100644 index 000000000..5ee5e1011 --- /dev/null +++ b/packages/contracts/src/2.0.0/tokens/ERC20Token/IERC20Token.sol @@ -0,0 +1,74 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + + +contract IERC20Token { + + /// @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) + public + returns (bool); + + /// @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) + public + returns (bool); + + /// @notice `msg.sender` approves `_spender` 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) + public + returns (bool); + + /// @param _owner The address from which the balance will be retrieved + /// @return The balance + function balanceOf(address _owner) + public view + returns (uint256); + + /// @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) + public view + returns (uint256); + + // solhint-disable-next-line no-simple-event-func-name + event Transfer( + address indexed _from, + address indexed _to, + uint256 _value + ); + + event Approval( + address indexed _owner, + address indexed _spender, + uint256 _value + ); +} diff --git a/packages/contracts/src/2.0.0/tokens/ERC721Token/ERC721Token.sol b/packages/contracts/src/2.0.0/tokens/ERC721Token/ERC721Token.sol new file mode 100644 index 000000000..60603aa19 --- /dev/null +++ b/packages/contracts/src/2.0.0/tokens/ERC721Token/ERC721Token.sol @@ -0,0 +1,407 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 Smart Contract Solutions, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +pragma solidity 0.4.24; + +import "./IERC721Token.sol"; +import "./IERC721Receiver.sol"; +import "../../utils/SafeMath/SafeMath.sol"; + + +/** + * @title ERC721 Non-Fungible Token Standard basic implementation + * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + * Modified from https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC721/ERC721BasicToken.sol + */ +contract ERC721Token is + IERC721Token, + SafeMath +{ + // Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))` + // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` + bytes4 constant internal ERC721_RECEIVED = 0xf0b9e5ba; + + // Mapping from token ID to owner + mapping (uint256 => address) internal tokenOwner; + + // Mapping from token ID to approved address + mapping (uint256 => address) internal tokenApprovals; + + // Mapping from owner to number of owned token + mapping (address => uint256) internal ownedTokensCount; + + // Mapping from owner to operator approvals + mapping (address => mapping (address => bool)) internal operatorApprovals; + + /** + * @dev Guarantees msg.sender is owner of the given token + * @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender + */ + modifier onlyOwnerOf(uint256 _tokenId) { + require(ownerOf(_tokenId) == msg.sender); + _; + } + + /** + * @dev Checks msg.sender can transfer a token, by being owner, approved, or operator + * @param _tokenId uint256 ID of the token to validate + */ + modifier canTransfer(uint256 _tokenId) { + require(isApprovedOrOwner(msg.sender, _tokenId)); + _; + } + + constructor ( + string _name, + string _symbol) + public + { + name_ = _name; + symbol_ = _symbol; + } + + /** + * @dev Gets the token name + * @return string representing the token name + */ + function name() + public + view + returns (string) + { + return name_; + } + + /** + * @dev Gets the token symbol + * @return string representing the token symbol + */ + function symbol() + public + view + returns (string) + { + return symbol_; + } + + /** + * @dev Gets the balance of the specified address + * @param _owner address to query the balance of + * @return uint256 representing the amount owned by the passed address + */ + function balanceOf(address _owner) + public + view + returns (uint256) + { + require(_owner != address(0)); + return ownedTokensCount[_owner]; + } + + /** + * @dev Gets the owner of the specified token ID + * @param _tokenId uint256 ID of the token to query the owner of + * @return owner address currently marked as the owner of the given token ID + */ + function ownerOf(uint256 _tokenId) + public + view + returns (address) + { + address owner = tokenOwner[_tokenId]; + require(owner != address(0)); + return owner; + } + + /** + * @dev Returns whether the specified token exists + * @param _tokenId uint256 ID of the token to query the existance of + * @return whether the token exists + */ + function exists(uint256 _tokenId) + public + view + returns (bool) + { + address owner = tokenOwner[_tokenId]; + return owner != address(0); + } + + /** + * @dev Approves another address to transfer the given token ID + * @dev The zero address indicates there is no approved address. + * @dev There can only be one approved address per token at a given time. + * @dev Can only be called by the token owner or an approved operator. + * @param _to address to be approved for the given token ID + * @param _tokenId uint256 ID of the token to be approved + */ + function approve(address _to, uint256 _tokenId) + public + { + address owner = ownerOf(_tokenId); + require(_to != owner); + require(msg.sender == owner || isApprovedForAll(owner, msg.sender)); + + if (getApproved(_tokenId) != address(0) || _to != address(0)) { + tokenApprovals[_tokenId] = _to; + emit Approval(owner, _to, _tokenId); + } + } + + /** + * @dev Gets the approved address for a token ID, or zero if no address set + * @param _tokenId uint256 ID of the token to query the approval of + * @return address currently approved for a the given token ID + */ + function getApproved(uint256 _tokenId) + public + view + returns (address) + { + return tokenApprovals[_tokenId]; + } + + /** + * @dev Sets or unsets the approval of a given operator + * @dev An operator is allowed to transfer all tokens of the sender on their behalf + * @param _to operator address to set the approval + * @param _approved representing the status of the approval to be set + */ + function setApprovalForAll(address _to, bool _approved) + public + { + require(_to != msg.sender); + operatorApprovals[msg.sender][_to] = _approved; + emit ApprovalForAll(msg.sender, _to, _approved); + } + + /** + * @dev Tells whether an operator is approved by a given owner + * @param _owner owner address which you want to query the approval of + * @param _operator operator address which you want to query the approval of + * @return bool whether the given operator is approved by the given owner + */ + function isApprovedForAll(address _owner, address _operator) + public + view + returns (bool) + { + return operatorApprovals[_owner][_operator]; + } + + /** + * @dev Transfers the ownership of a given token ID to another address + * @dev Usage of this method is discouraged, use `safeTransferFrom` whenever possible + * @dev Requires the msg sender to be the owner, approved, or operator + * @param _from current owner of the token + * @param _to address to receive the ownership of the given token ID + * @param _tokenId uint256 ID of the token to be transferred + */ + function transferFrom(address _from, address _to, uint256 _tokenId) + public + canTransfer(_tokenId) + { + require(_from != address(0)); + require(_to != address(0)); + + clearApproval(_from, _tokenId); + removeTokenFrom(_from, _tokenId); + addTokenTo(_to, _tokenId); + + emit Transfer(_from, _to, _tokenId); + } + + /** + * @dev Safely transfers the ownership of a given token ID to another address + * @dev If the target address is a contract, it must implement `onERC721Received`, + * which is called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`; otherwise, + * the transfer is reverted. + * @dev Requires the msg sender to be the owner, approved, or operator + * @param _from current owner of the token + * @param _to address to receive the ownership of the given token ID + * @param _tokenId uint256 ID of the token to be transferred + */ + function safeTransferFrom( + address _from, + address _to, + uint256 _tokenId) + public + canTransfer(_tokenId) + { + // solium-disable-next-line arg-overflow + safeTransferFrom(_from, _to, _tokenId, ""); + } + + /** + * @dev Safely transfers the ownership of a given token ID to another address + * @dev If the target address is a contract, it must implement `onERC721Received`, + * which is called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`; otherwise, + * the transfer is reverted. + * @dev Requires the msg sender to be the owner, approved, or operator + * @param _from current owner of the token + * @param _to address to receive the ownership of the given token ID + * @param _tokenId uint256 ID of the token to be transferred + * @param _data bytes data to send along with a safe transfer check + */ + function safeTransferFrom( + address _from, + address _to, + uint256 _tokenId, + bytes _data) + public + canTransfer(_tokenId) + { + transferFrom(_from, _to, _tokenId); + // solium-disable-next-line arg-overflow + require(checkAndCallSafeTransfer(_from, _to, _tokenId, _data)); + } + + /** + * @dev Returns whether the given spender can transfer a given token ID + * @param _spender address of the spender to query + * @param _tokenId uint256 ID of the token to be transferred + * @return bool whether the msg.sender is approved for the given token ID, + * is an operator of the owner, or is the owner of the token + */ + function isApprovedOrOwner(address _spender, uint256 _tokenId) + internal + view + returns (bool) + { + address owner = ownerOf(_tokenId); + return _spender == owner || getApproved(_tokenId) == _spender || isApprovedForAll(owner, _spender); + } + + /** + * @dev Internal function to mint a new token + * @dev Reverts if the given token ID already exists + * @param _to The address that will own the minted token + * @param _tokenId uint256 ID of the token to be minted by the msg.sender + */ + function _mint(address _to, uint256 _tokenId) + internal + { + require(_to != address(0)); + addTokenTo(_to, _tokenId); + emit Transfer(address(0), _to, _tokenId); + } + + /** + * @dev Internal function to burn a specific token + * @dev Reverts if the token does not exist + * @param _tokenId uint256 ID of the token being burned by the msg.sender + */ + function _burn(address _owner, uint256 _tokenId) + internal + { + clearApproval(_owner, _tokenId); + removeTokenFrom(_owner, _tokenId); + emit Transfer(_owner, address(0), _tokenId); + } + + /** + * @dev Internal function to clear current approval of a given token ID + * @dev Reverts if the given address is not indeed the owner of the token + * @param _owner owner of the token + * @param _tokenId uint256 ID of the token to be transferred + */ + function clearApproval(address _owner, uint256 _tokenId) + internal + { + require(ownerOf(_tokenId) == _owner); + if (tokenApprovals[_tokenId] != address(0)) { + tokenApprovals[_tokenId] = address(0); + emit Approval(_owner, address(0), _tokenId); + } + } + + /** + * @dev Internal function to add a token ID to the list of a given address + * @param _to address representing the new owner of the given token ID + * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address + */ + function addTokenTo(address _to, uint256 _tokenId) + internal + { + require(tokenOwner[_tokenId] == address(0)); + tokenOwner[_tokenId] = _to; + ownedTokensCount[_to] = safeAdd(ownedTokensCount[_to], 1); + } + + /** + * @dev Internal function to remove a token ID from the list of a given address + * @param _from address representing the previous owner of the given token ID + * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function removeTokenFrom(address _from, uint256 _tokenId) + internal + { + require(ownerOf(_tokenId) == _from); + ownedTokensCount[_from] = safeSub(ownedTokensCount[_from], 1); + tokenOwner[_tokenId] = address(0); + } + + /** + * @dev Internal function to invoke `onERC721Received` on a target address + * @dev The call is not executed if the target address is not a contract + * @param _from address representing the previous owner of the given token ID + * @param _to target address that will receive the tokens + * @param _tokenId uint256 ID of the token to be transferred + * @param _data bytes optional data to send along with the call + * @return whether the call correctly returned the expected magic value + */ + function checkAndCallSafeTransfer( + address _from, + address _to, + uint256 _tokenId, + bytes _data) + internal + returns (bool) + { + if (!isContract(_to)) { + return true; + } + bytes4 retval = IERC721Receiver(_to).onERC721Received(_from, _tokenId, _data); + return (retval == ERC721_RECEIVED); + } + + function isContract(address addr) + internal + view + returns (bool) + { + uint256 size; + // XXX Currently there is no better way to check if there is a contract in an address + // than to check the size of the code at that address. + // See https://ethereum.stackexchange.com/a/14016/36603 + // for more details about how this works. + // TODO Check this again before the Serenity release, because all addresses will be + // contracts then. + assembly { size := extcodesize(addr) } // solium-disable-line security/no-inline-assembly + return size > 0; + } +} diff --git a/packages/contracts/src/2.0.0/tokens/ERC721Token/IERC721Receiver.sol b/packages/contracts/src/2.0.0/tokens/ERC721Token/IERC721Receiver.sol new file mode 100644 index 000000000..f2e8f3c88 --- /dev/null +++ b/packages/contracts/src/2.0.0/tokens/ERC721Token/IERC721Receiver.sol @@ -0,0 +1,61 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 Smart Contract Solutions, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +pragma solidity 0.4.24; + + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * rom ERC721 asset contracts. + * Modified from https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC721/ERC721Receiver.sol + */ +contract IERC721Receiver { + /** + * @dev Magic value to be returned upon successful reception of an NFT + * Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`, + * which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` + */ + bytes4 constant internal ERC721_RECEIVED = 0xf0b9e5ba; + + /** + * @notice Handle the receipt of an NFT + * @dev The ERC721 smart contract calls this function on the recipient + * after a `safetransfer`. This function MAY throw to revert and reject the + * transfer. This function MUST use 50,000 gas or less. Return of other + * than the magic value MUST result in the transaction being reverted. + * Note: the contract address is always the message sender. + * @param _from The sending address + * @param _tokenId The NFT identifier which is being transfered + * @param _data Additional data with no specified format + * @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))` + */ + function onERC721Received( + address _from, + uint256 _tokenId, + bytes _data) + public + returns (bytes4); +} diff --git a/packages/contracts/src/2.0.0/tokens/ERC721Token/IERC721Token.sol b/packages/contracts/src/2.0.0/tokens/ERC721Token/IERC721Token.sol new file mode 100644 index 000000000..4d57ece38 --- /dev/null +++ b/packages/contracts/src/2.0.0/tokens/ERC721Token/IERC721Token.sol @@ -0,0 +1,118 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 Smart Contract Solutions, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +pragma solidity 0.4.24; + + +/** + * @title ERC721 Non-Fungible Token Standard basic interface + * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + * Modified from https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC721/ERC721Basic.sol + */ +contract IERC721Token { + string internal name_; + string internal symbol_; + + event Transfer( + address indexed _from, + address indexed _to, + uint256 _tokenId + ); + + event Approval( + address indexed _owner, + address indexed _approved, + uint256 _tokenId + ); + + event ApprovalForAll( + address indexed _owner, + address indexed _operator, + bool _approved + ); + + function name() + public + view + returns (string); + + function symbol() + public + view + returns (string); + + function balanceOf(address _owner) + public + view + returns (uint256 _balance); + + function ownerOf(uint256 _tokenId) + public + view + returns (address _owner); + + function exists(uint256 _tokenId) + public + view + returns (bool _exists); + + function approve(address _to, uint256 _tokenId) + public; + + function getApproved(uint256 _tokenId) + public + view + returns (address _operator); + + function setApprovalForAll(address _operator, bool _approved) + public; + + function isApprovedForAll(address _owner, address _operator) + public + view + returns (bool); + + function transferFrom( + address _from, + address _to, + uint256 _tokenId + ) + public; + + function safeTransferFrom( + address _from, + address _to, + uint256 _tokenId + ) + public; + + function safeTransferFrom( + address _from, + address _to, + uint256 _tokenId, + bytes _data + ) + public; +} diff --git a/packages/contracts/src/2.0.0/tokens/EtherToken/IEtherToken.sol b/packages/contracts/src/2.0.0/tokens/EtherToken/IEtherToken.sol new file mode 100644 index 000000000..9e2e68766 --- /dev/null +++ b/packages/contracts/src/2.0.0/tokens/EtherToken/IEtherToken.sol @@ -0,0 +1,33 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../ERC20Token/IERC20Token.sol"; + + +contract IEtherToken is + IERC20Token +{ + function deposit() + public + payable; + + function withdraw(uint256 amount) + public; +} diff --git a/packages/contracts/src/contracts/current/tokens/WETH9/WETH9.sol b/packages/contracts/src/2.0.0/tokens/EtherToken/WETH9.sol index 733ca414b..17876b86d 100644 --- a/packages/contracts/src/contracts/current/tokens/WETH9/WETH9.sol +++ b/packages/contracts/src/2.0.0/tokens/EtherToken/WETH9.sol @@ -13,17 +13,19 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. +// solhint-disable pragma solidity ^0.4.18; + contract WETH9 { string public name = "Wrapped Ether"; string public symbol = "WETH"; uint8 public decimals = 18; - event Approval(address indexed src, address indexed guy, uint wad); - event Transfer(address indexed src, address indexed dst, uint wad); - event Deposit(address indexed dst, uint wad); - event Withdrawal(address indexed src, uint wad); + event Approval(address indexed _owner, address indexed _spender, uint _value); + event Transfer(address indexed _from, address indexed _to, uint _value); + event Deposit(address indexed _owner, uint _value); + event Withdrawal(address indexed _owner, uint _value); mapping (address => uint) public balanceOf; mapping (address => mapping (address => uint)) public allowance; diff --git a/packages/contracts/src/contracts/current/tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol b/packages/contracts/src/2.0.0/tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol index 699f535d2..9feb5c914 100644 --- a/packages/contracts/src/contracts/current/tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol +++ b/packages/contracts/src/2.0.0/tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol @@ -1,6 +1,6 @@ /* - Copyright 2017 ZeroEx Intl. + Copyright 2018 ZeroEx Intl. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,31 +16,43 @@ */ -pragma solidity ^0.4.18; +pragma solidity 0.4.24; + +import "../ERC20Token/ERC20Token.sol"; -import { ERC20Token } from "../ERC20Token/ERC20Token.sol"; contract UnlimitedAllowanceToken is ERC20Token { - uint constant MAX_UINT = 2**256 - 1; + uint256 constant internal MAX_UINT = 2**256 - 1; /// @dev ERC20 transferFrom, modified such that an allowance of MAX_UINT represents an unlimited allowance. See https://github.com/ethereum/EIPs/issues/717 /// @param _from Address to transfer from. /// @param _to Address to transfer to. /// @param _value Amount to transfer. /// @return Success of transfer. - function transferFrom(address _from, address _to, uint _value) + function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { - uint allowance = allowed[_from][msg.sender]; - require(balances[_from] >= _value && allowance >= _value && balances[_to] + _value >= balances[_to]); + uint256 allowance = allowed[_from][msg.sender]; + require( + balances[_from] >= _value, + "ERC20_INSUFFICIENT_BALANCE" + ); + require( + allowance >= _value, + "ERC20_INSUFFICIENT_ALLOWANCE" + ); + require( + balances[_to] + _value >= balances[_to], + "OVERFLOW" + ); balances[_to] += _value; balances[_from] -= _value; if (allowance < MAX_UINT) { allowed[_from][msg.sender] -= _value; } - Transfer(_from, _to, _value); + emit Transfer(_from, _to, _value); return true; } } diff --git a/packages/contracts/src/contracts/current/tokens/ZRXToken/ZRXToken.sol b/packages/contracts/src/2.0.0/tokens/ZRXToken/ZRXToken.sol index 7f5e1f849..28c0b2fb3 100644 --- a/packages/contracts/src/contracts/current/tokens/ZRXToken/ZRXToken.sol +++ b/packages/contracts/src/2.0.0/tokens/ZRXToken/ZRXToken.sol @@ -1,6 +1,6 @@ /* - Copyright 2017 ZeroEx Intl. + Copyright 2018 ZeroEx Intl. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,18 +16,24 @@ */ -pragma solidity ^0.4.11; +pragma solidity 0.4.11; + +// solhint-disable-next-line max-line-length +import { UnlimitedAllowanceToken_v1 as UnlimitedAllowanceToken } from "../../../1.0.0/UnlimitedAllowanceToken/UnlimitedAllowanceToken_v1.sol"; -import { UnlimitedAllowanceToken_v1 as UnlimitedAllowanceToken } from "../../../previous/UnlimitedAllowanceToken/UnlimitedAllowanceToken_v1.sol"; contract ZRXToken is UnlimitedAllowanceToken { + // solhint-disable const-name-snakecase uint8 constant public decimals = 18; uint public totalSupply = 10**27; // 1 billion tokens, 18 decimal places string constant public name = "0x Protocol Token"; string constant public symbol = "ZRX"; + // solhint-enableconst-name-snakecase - function ZRXToken() { + function ZRXToken() + public + { balances[msg.sender] = totalSupply; } } diff --git a/packages/contracts/src/2.0.0/utils/LibBytes/LibBytes.sol b/packages/contracts/src/2.0.0/utils/LibBytes/LibBytes.sol new file mode 100644 index 000000000..504e950a8 --- /dev/null +++ b/packages/contracts/src/2.0.0/utils/LibBytes/LibBytes.sol @@ -0,0 +1,560 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + + +library LibBytes { + + using LibBytes for bytes; + + /// @dev Gets the memory address for a byte array. + /// @param input Byte array to lookup. + /// @return memoryAddress Memory address of byte array. This + /// points to the header of the byte array which contains + /// the length. + function rawAddress(bytes memory input) + internal + pure + returns (uint256 memoryAddress) + { + assembly { + memoryAddress := input + } + return memoryAddress; + } + + /// @dev Gets the memory address for the contents of a byte array. + /// @param input Byte array to lookup. + /// @return memoryAddress Memory address of the contents of the byte array. + function contentAddress(bytes memory input) + internal + pure + returns (uint256 memoryAddress) + { + assembly { + memoryAddress := add(input, 32) + } + return memoryAddress; + } + + /// @dev Copies `length` bytes from memory location `source` to `dest`. + /// @param dest memory address to copy bytes to. + /// @param source memory address to copy bytes from. + /// @param length number of bytes to copy. + function memCopy( + uint256 dest, + uint256 source, + uint256 length + ) + internal + pure + { + if (length < 32) { + // Handle a partial word by reading destination and masking + // off the bits we are interested in. + // This correctly handles overlap, zero lengths and source == dest + assembly { + let mask := sub(exp(256, sub(32, length)), 1) + let s := and(mload(source), not(mask)) + let d := and(mload(dest), mask) + mstore(dest, or(s, d)) + } + } else { + // Skip the O(length) loop when source == dest. + if (source == dest) { + return; + } + + // For large copies we copy whole words at a time. The final + // word is aligned to the end of the range (instead of after the + // previous) to handle partial words. So a copy will look like this: + // + // #### + // #### + // #### + // #### + // + // We handle overlap in the source and destination range by + // changing the copying direction. This prevents us from + // overwriting parts of source that we still need to copy. + // + // This correctly handles source == dest + // + if (source > dest) { + assembly { + // We subtract 32 from `sEnd` and `dEnd` because it + // is easier to compare with in the loop, and these + // are also the addresses we need for copying the + // last bytes. + length := sub(length, 32) + let sEnd := add(source, length) + let dEnd := add(dest, length) + + // Remember the last 32 bytes of source + // This needs to be done here and not after the loop + // because we may have overwritten the last bytes in + // source already due to overlap. + let last := mload(sEnd) + + // Copy whole words front to back + // Note: the first check is always true, + // this could have been a do-while loop. + // solhint-disable-next-line no-empty-blocks + for {} lt(source, sEnd) {} { + mstore(dest, mload(source)) + source := add(source, 32) + dest := add(dest, 32) + } + + // Write the last 32 bytes + mstore(dEnd, last) + } + } else { + assembly { + // We subtract 32 from `sEnd` and `dEnd` because those + // are the starting points when copying a word at the end. + length := sub(length, 32) + let sEnd := add(source, length) + let dEnd := add(dest, length) + + // Remember the first 32 bytes of source + // This needs to be done here and not after the loop + // because we may have overwritten the first bytes in + // source already due to overlap. + let first := mload(source) + + // Copy whole words back to front + // We use a signed comparisson here to allow dEnd to become + // negative (happens when source and dest < 32). Valid + // addresses in local memory will never be larger than + // 2**255, so they can be safely re-interpreted as signed. + // Note: the first check is always true, + // this could have been a do-while loop. + // solhint-disable-next-line no-empty-blocks + for {} slt(dest, dEnd) {} { + mstore(dEnd, mload(sEnd)) + sEnd := sub(sEnd, 32) + dEnd := sub(dEnd, 32) + } + + // Write the first 32 bytes + mstore(dest, first) + } + } + } + } + + /// @dev Returns a slices from a byte array. + /// @param b The byte array to take a slice from. + /// @param from The starting index for the slice (inclusive). + /// @param to The final index for the slice (exclusive). + /// @return result The slice containing bytes at indices [from, to) + function slice( + bytes memory b, + uint256 from, + uint256 to + ) + internal + pure + returns (bytes memory result) + { + require( + from <= to, + "FROM_LESS_THAN_TO_REQUIRED" + ); + require( + to < b.length, + "TO_LESS_THAN_LENGTH_REQUIRED" + ); + + // Create a new bytes structure and copy contents + result = new bytes(to - from); + memCopy( + result.contentAddress(), + b.contentAddress() + from, + result.length); + return result; + } + + /// @dev Returns a slice from a byte array without preserving the input. + /// @param b The byte array to take a slice from. Will be destroyed in the process. + /// @param from The starting index for the slice (inclusive). + /// @param to The final index for the slice (exclusive). + /// @return result The slice containing bytes at indices [from, to) + /// @dev When `from == 0`, the original array will match the slice. In other cases its state will be corrupted. + function sliceDestructive( + bytes memory b, + uint256 from, + uint256 to + ) + internal + pure + returns (bytes memory result) + { + require( + from <= to, + "FROM_LESS_THAN_TO_REQUIRED" + ); + require( + to < b.length, + "TO_LESS_THAN_LENGTH_REQUIRED" + ); + + // Create a new bytes structure around [from, to) in-place. + assembly { + result := add(b, from) + mstore(result, sub(to, from)) + } + return result; + } + + /// @dev Pops the last byte off of a byte array by modifying its length. + /// @param b Byte array that will be modified. + /// @return The byte that was popped off. + function popLastByte(bytes memory b) + internal + pure + returns (bytes1 result) + { + require( + b.length > 0, + "GREATER_THAN_ZERO_LENGTH_REQUIRED" + ); + + // Store last byte. + result = b[b.length - 1]; + + assembly { + // Decrement length of byte array. + let newLen := sub(mload(b), 1) + mstore(b, newLen) + } + return result; + } + + /// @dev Pops the last 20 bytes off of a byte array by modifying its length. + /// @param b Byte array that will be modified. + /// @return The 20 byte address that was popped off. + function popLast20Bytes(bytes memory b) + internal + pure + returns (address result) + { + require( + b.length >= 20, + "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED" + ); + + // Store last 20 bytes. + result = readAddress(b, b.length - 20); + + assembly { + // Subtract 20 from byte array length. + let newLen := sub(mload(b), 20) + mstore(b, newLen) + } + return result; + } + + /// @dev Tests equality of two byte arrays. + /// @param lhs First byte array to compare. + /// @param rhs Second byte array to compare. + /// @return True if arrays are the same. False otherwise. + function equals( + bytes memory lhs, + bytes memory rhs + ) + internal + pure + returns (bool equal) + { + // Keccak gas cost is 30 + numWords * 6. This is a cheap way to compare. + // We early exit on unequal lengths, but keccak would also correctly + // handle this. + return lhs.length == rhs.length && keccak256(lhs) == keccak256(rhs); + } + + /// @dev Reads an address from a position in a byte array. + /// @param b Byte array containing an address. + /// @param index Index in byte array of address. + /// @return address from byte array. + function readAddress( + bytes memory b, + uint256 index + ) + internal + pure + returns (address result) + { + require( + b.length >= index + 20, // 20 is length of address + "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED" + ); + + // Add offset to index: + // 1. Arrays are prefixed by 32-byte length parameter (add 32 to index) + // 2. Account for size difference between address length and 32-byte storage word (subtract 12 from index) + index += 20; + + // Read address from array memory + assembly { + // 1. Add index to address of bytes array + // 2. Load 32-byte word from memory + // 3. Apply 20-byte mask to obtain address + result := and(mload(add(b, index)), 0xffffffffffffffffffffffffffffffffffffffff) + } + return result; + } + + /// @dev Writes an address into a specific position in a byte array. + /// @param b Byte array to insert address into. + /// @param index Index in byte array of address. + /// @param input Address to put into byte array. + function writeAddress( + bytes memory b, + uint256 index, + address input + ) + internal + pure + { + require( + b.length >= index + 20, // 20 is length of address + "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED" + ); + + // Add offset to index: + // 1. Arrays are prefixed by 32-byte length parameter (add 32 to index) + // 2. Account for size difference between address length and 32-byte storage word (subtract 12 from index) + index += 20; + + // Store address into array memory + assembly { + // The address occupies 20 bytes and mstore stores 32 bytes. + // First fetch the 32-byte word where we'll be storing the address, then + // apply a mask so we have only the bytes in the word that the address will not occupy. + // Then combine these bytes with the address and store the 32 bytes back to memory with mstore. + + // 1. Add index to address of bytes array + // 2. Load 32-byte word from memory + // 3. Apply 12-byte mask to obtain extra bytes occupying word of memory where we'll store the address + let neighbors := and( + mload(add(b, index)), + 0xffffffffffffffffffffffff0000000000000000000000000000000000000000 + ) + + // Make sure input address is clean. + // (Solidity does not guarantee this) + input := and(input, 0xffffffffffffffffffffffffffffffffffffffff) + + // Store the neighbors and address into memory + mstore(add(b, index), xor(input, neighbors)) + } + } + + /// @dev Reads a bytes32 value from a position in a byte array. + /// @param b Byte array containing a bytes32 value. + /// @param index Index in byte array of bytes32 value. + /// @return bytes32 value from byte array. + function readBytes32( + bytes memory b, + uint256 index + ) + internal + pure + returns (bytes32 result) + { + require( + b.length >= index + 32, + "GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED" + ); + + // Arrays are prefixed by a 256 bit length parameter + index += 32; + + // Read the bytes32 from array memory + assembly { + result := mload(add(b, index)) + } + return result; + } + + /// @dev Writes a bytes32 into a specific position in a byte array. + /// @param b Byte array to insert <input> into. + /// @param index Index in byte array of <input>. + /// @param input bytes32 to put into byte array. + function writeBytes32( + bytes memory b, + uint256 index, + bytes32 input + ) + internal + pure + { + require( + b.length >= index + 32, + "GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED" + ); + + // Arrays are prefixed by a 256 bit length parameter + index += 32; + + // Read the bytes32 from array memory + assembly { + mstore(add(b, index), input) + } + } + + /// @dev Reads a uint256 value from a position in a byte array. + /// @param b Byte array containing a uint256 value. + /// @param index Index in byte array of uint256 value. + /// @return uint256 value from byte array. + function readUint256( + bytes memory b, + uint256 index + ) + internal + pure + returns (uint256 result) + { + return uint256(readBytes32(b, index)); + } + + /// @dev Writes a uint256 into a specific position in a byte array. + /// @param b Byte array to insert <input> into. + /// @param index Index in byte array of <input>. + /// @param input uint256 to put into byte array. + function writeUint256( + bytes memory b, + uint256 index, + uint256 input + ) + internal + pure + { + writeBytes32(b, index, bytes32(input)); + } + + /// @dev Reads an unpadded bytes4 value from a position in a byte array. + /// @param b Byte array containing a bytes4 value. + /// @param index Index in byte array of bytes4 value. + /// @return bytes4 value from byte array. + function readBytes4( + bytes memory b, + uint256 index + ) + internal + pure + returns (bytes4 result) + { + require( + b.length >= index + 4, + "GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED" + ); + assembly { + result := mload(add(b, 32)) + // Solidity does not require us to clean the trailing bytes. + // We do it anyway + result := and(result, 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000) + } + return result; + } + + /// @dev Reads nested bytes from a specific position. + /// @dev NOTE: the returned value overlaps with the input value. + /// Both should be treated as immutable. + /// @param b Byte array containing nested bytes. + /// @param index Index of nested bytes. + /// @return result Nested bytes. + function readBytesWithLength( + bytes memory b, + uint256 index + ) + internal + pure + returns (bytes memory result) + { + // Read length of nested bytes + uint256 nestedBytesLength = readUint256(b, index); + index += 32; + + // Assert length of <b> is valid, given + // length of nested bytes + require( + b.length >= index + nestedBytesLength, + "GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED" + ); + + // Return a pointer to the byte array as it exists inside `b` + assembly { + result := add(b, index) + } + return result; + } + + /// @dev Inserts bytes at a specific position in a byte array. + /// @param b Byte array to insert <input> into. + /// @param index Index in byte array of <input>. + /// @param input bytes to insert. + function writeBytesWithLength( + bytes memory b, + uint256 index, + bytes memory input + ) + internal + pure + { + // Assert length of <b> is valid, given + // length of input + require( + b.length >= index + 32 + input.length, // 32 bytes to store length + "GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED" + ); + + // Copy <input> into <b> + memCopy( + b.contentAddress() + index, + input.rawAddress(), // includes length of <input> + input.length + 32 // +32 bytes to store <input> length + ); + } + + /// @dev Performs a deep copy of a byte array onto another byte array of greater than or equal length. + /// @param dest Byte array that will be overwritten with source bytes. + /// @param source Byte array to copy onto dest bytes. + function deepCopyBytes( + bytes memory dest, + bytes memory source + ) + internal + pure + { + uint256 sourceLen = source.length; + // Dest length must be >= source length, or some bytes would not be copied. + require( + dest.length >= sourceLen, + "GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED" + ); + memCopy( + dest.contentAddress(), + source.contentAddress(), + sourceLen + ); + } +} diff --git a/packages/contracts/src/2.0.0/utils/Ownable/IOwnable.sol b/packages/contracts/src/2.0.0/utils/Ownable/IOwnable.sol new file mode 100644 index 000000000..116b8dc89 --- /dev/null +++ b/packages/contracts/src/2.0.0/utils/Ownable/IOwnable.sol @@ -0,0 +1,13 @@ +pragma solidity 0.4.24; + +/* + * Ownable + * + * Base contract with an owner. + * Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner. + */ + +contract IOwnable { + function transferOwnership(address newOwner) + public; +} diff --git a/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol b/packages/contracts/src/2.0.0/utils/Ownable/Ownable.sol index 9b3d6b9cf..aca65aad2 100644 --- a/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol +++ b/packages/contracts/src/2.0.0/utils/Ownable/Ownable.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.18; +pragma solidity 0.4.24; /* * Ownable @@ -7,17 +7,23 @@ pragma solidity ^0.4.18; * Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner. */ -contract Ownable { +import "./IOwnable.sol"; + + +contract Ownable is IOwnable { address public owner; - function Ownable() + constructor () public { owner = msg.sender; } modifier onlyOwner() { - require(msg.sender == owner); + require( + msg.sender == owner, + "ONLY_CONTRACT_OWNER" + ); _; } diff --git a/packages/contracts/src/contracts/current/utils/SafeMath/SafeMath.sol b/packages/contracts/src/2.0.0/utils/SafeMath/SafeMath.sol index 955a9e379..63a2a085f 100644 --- a/packages/contracts/src/contracts/current/utils/SafeMath/SafeMath.sol +++ b/packages/contracts/src/2.0.0/utils/SafeMath/SafeMath.sol @@ -1,41 +1,54 @@ -pragma solidity ^0.4.18; +pragma solidity 0.4.24; + contract SafeMath { - function safeMul(uint a, uint b) + function safeMul(uint256 a, uint256 b) internal pure returns (uint256) { - uint c = a * b; - assert(a == 0 || c / a == b); + if (a == 0) { + return 0; + } + uint256 c = a * b; + require( + c / a == b, + "UINT256_OVERFLOW" + ); return c; } - function safeDiv(uint a, uint b) + function safeDiv(uint256 a, uint256 b) internal pure returns (uint256) { - uint c = a / b; + uint256 c = a / b; return c; } - function safeSub(uint a, uint b) + function safeSub(uint256 a, uint256 b) internal pure returns (uint256) { - assert(b <= a); + require( + b <= a, + "UINT256_UNDERFLOW" + ); return a - b; } - function safeAdd(uint a, uint b) + function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) { - uint c = a + b; - assert(c >= a); + uint256 c = a + b; + require( + c >= a, + "UINT256_OVERFLOW" + ); return c; } diff --git a/packages/contracts/src/contracts/current/test/DummyToken/DummyToken.sol b/packages/contracts/src/contracts/current/test/DummyToken/DummyToken.sol deleted file mode 100644 index ab04f4d16..000000000 --- a/packages/contracts/src/contracts/current/test/DummyToken/DummyToken.sol +++ /dev/null @@ -1,37 +0,0 @@ -pragma solidity ^0.4.18; - -import { Mintable } from "../Mintable/Mintable.sol"; -import { Ownable } from "../../utils/Ownable/Ownable.sol"; - -contract DummyToken is Mintable, Ownable { - string public name; - string public symbol; - uint public decimals; - - function DummyToken( - string _name, - string _symbol, - uint _decimals, - uint _totalSupply) - public - { - name = _name; - symbol = _symbol; - decimals = _decimals; - totalSupply = _totalSupply; - balances[msg.sender] = _totalSupply; - } - - function setBalance(address _target, uint _value) - public - onlyOwner - { - uint currBalance = balanceOf(_target); - if (_value < currBalance) { - totalSupply = safeSub(totalSupply, safeSub(currBalance, _value)); - } else { - totalSupply = safeAdd(totalSupply, safeSub(_value, currBalance)); - } - balances[_target] = _value; - } -} diff --git a/packages/contracts/src/contracts/current/test/MaliciousToken/MaliciousToken.sol b/packages/contracts/src/contracts/current/test/MaliciousToken/MaliciousToken.sol deleted file mode 100644 index 9e502616c..000000000 --- a/packages/contracts/src/contracts/current/test/MaliciousToken/MaliciousToken.sol +++ /dev/null @@ -1,31 +0,0 @@ -pragma solidity ^0.4.18; - -import { ERC20Token } from "../../tokens/ERC20Token/ERC20Token.sol"; - -contract MaliciousToken is ERC20Token { - uint8 stateToUpdate = 1; // Not null so that change only requires 5000 gas - - function updateState() - internal - { - stateToUpdate++; - } - - function balanceOf(address _owner) - public - constant - returns (uint) - { - updateState(); - return balances[_owner]; - } - - function allowance(address _owner, address _spender) - public - constant - returns (uint) - { - updateState(); - return allowed[_owner][_spender]; - } -} diff --git a/packages/contracts/src/contracts/current/test/Mintable/Mintable.sol b/packages/contracts/src/contracts/current/test/Mintable/Mintable.sol deleted file mode 100644 index cf7ee35a5..000000000 --- a/packages/contracts/src/contracts/current/test/Mintable/Mintable.sol +++ /dev/null @@ -1,18 +0,0 @@ -pragma solidity ^0.4.18; - -import { UnlimitedAllowanceToken } from "../../tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol"; -import { SafeMath } from "../../utils/SafeMath/SafeMath.sol"; - -/* - * Mintable - * Base contract that creates a mintable UnlimitedAllowanceToken - */ -contract Mintable is UnlimitedAllowanceToken, SafeMath { - function mint(uint _value) - public - { - require(_value <= 100000000000000000000); - balances[msg.sender] = safeAdd(_value, balances[msg.sender]); - totalSupply = safeAdd(totalSupply, _value); - } -} diff --git a/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol b/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol deleted file mode 100644 index 0e5b87aa4..000000000 --- a/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol +++ /dev/null @@ -1,58 +0,0 @@ -pragma solidity ^0.4.18; - -import { Token } from "../Token/Token.sol"; - -contract ERC20Token is Token { - - function transfer(address _to, uint _value) - public - returns (bool) - { - require(balances[msg.sender] >= _value && balances[_to] + _value >= balances[_to]); - balances[msg.sender] -= _value; - balances[_to] += _value; - Transfer(msg.sender, _to, _value); - return true; - } - - function transferFrom(address _from, address _to, uint _value) - public - returns (bool) - { - require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value >= balances[_to]); - balances[_to] += _value; - balances[_from] -= _value; - allowed[_from][msg.sender] -= _value; - Transfer(_from, _to, _value); - return true; - } - - function approve(address _spender, uint _value) - public - returns (bool) - { - allowed[msg.sender][_spender] = _value; - Approval(msg.sender, _spender, _value); - return true; - } - - function balanceOf(address _owner) - public - view - returns (uint) - { - return balances[_owner]; - } - - function allowance(address _owner, address _spender) - public - view - returns (uint) - { - return allowed[_owner][_spender]; - } - - mapping (address => uint) balances; - mapping (address => mapping (address => uint)) allowed; - uint public totalSupply; -} diff --git a/packages/contracts/src/contracts/current/tokens/Token/Token.sol b/packages/contracts/src/contracts/current/tokens/Token/Token.sol deleted file mode 100644 index bf4e71dcd..000000000 --- a/packages/contracts/src/contracts/current/tokens/Token/Token.sol +++ /dev/null @@ -1,35 +0,0 @@ -pragma solidity ^0.4.18; - -contract Token { - - /// @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, uint _value) public returns (bool) {} - - /// @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, uint _value) public returns (bool) {} - - /// @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, uint _value) public returns (bool) {} - - /// @param _owner The address from which the balance will be retrieved - /// @return The balance - function balanceOf(address _owner) public view returns (uint) {} - - /// @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) public view returns (uint) {} - - event Transfer(address indexed _from, address indexed _to, uint _value); - event Approval(address indexed _owner, address indexed _spender, uint _value); -} |