Compare commits
	
		
			220 Commits
		
	
	
		
			@0x/utils@
			...
			@0x/contra
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 93f2b6b4d8 | ||
|  | 00e616b57a | ||
|  | e436673304 | ||
|  | ce04d3ce41 | ||
|  | 4e46bf4697 | ||
|  | 9b95508f99 | ||
|  | c8c24456c1 | ||
|  | 80a6e82e1b | ||
|  | 80cec20d38 | ||
|  | 26cb22020d | ||
|  | 09d05d09c9 | ||
|  | 160c91f908 | ||
|  | 58f41bcd42 | ||
|  | 0235429995 | ||
|  | f79697e117 | ||
|  | 6f0019c71e | ||
|  | 1a04a18245 | ||
|  | 0cf3ff8209 | ||
|  | 7f56091fbd | ||
|  | 391f9b31f6 | ||
|  | 1d7c0f504a | ||
|  | 9cdc62f918 | ||
|  | 6027d0481e | ||
|  | 277fb92f9e | ||
|  | 79b6c3c1af | ||
|  | ad17174119 | ||
|  | 6c2692eec0 | ||
|  | 08640e8575 | ||
|  | 4a2575136f | ||
|  | e792afad17 | ||
|  | 5ecc4b027d | ||
|  | bc540f0cf0 | ||
|  | a24b14c465 | ||
|  | c3f36c3123 | ||
|  | 1d34da7557 | ||
|  | 27d74a4327 | ||
|  | 8a20cc682c | ||
|  | 9b20359e7b | ||
|  | 8866d0ccef | ||
|  | 7fa91a9971 | ||
|  | 28d1f3eef0 | ||
|  | f321cf6655 | ||
|  | 14ade737da | ||
|  | 41b1b1f141 | ||
|  | 25dfd47d32 | ||
|  | 6afa9c8b92 | ||
|  | 2fc449da4c | ||
|  | 5dd3b8cf9d | ||
|  | e834fa0050 | ||
|  | 9a97401606 | ||
|  | 410a3fef18 | ||
|  | 969b9814d5 | ||
|  | 2275e27b87 | ||
|  | 62b06cd204 | ||
|  | 350934ca21 | ||
|  | 6332673434 | ||
|  | f217840998 | ||
|  | 089ec35ceb | ||
|  | fecd0b809e | ||
|  | 4707a46561 | ||
|  | 616533c5a8 | ||
|  | c5b2991821 | ||
|  | c36d0fdc7c | ||
|  | 544e09cf4b | ||
|  | c110dc9e6a | ||
|  | 3bf37d6afd | ||
|  | b80ae5796b | ||
|  | 2083632299 | ||
|  | 3ca2f8ac9e | ||
|  | 7172432084 | ||
|  | 0e6afd147f | ||
|  | 46275a4f43 | ||
|  | 1dca378e03 | ||
|  | 06669594b1 | ||
|  | c09ac58ac0 | ||
|  | e01d32ef1a | ||
|  | 5ea3bcf59e | ||
|  | aa8b14b7ee | ||
|  | e1722cf739 | ||
|  | 7a7f70e15d | ||
|  | b3c3ec16e5 | ||
|  | 149f863951 | ||
|  | 684d09faac | ||
|  | 8a42691c80 | ||
|  | d591b3dd98 | ||
|  | a90fb4d8b6 | ||
|  | ebd08d9c63 | ||
|  | 71731d223b | ||
|  | 726ea5e01e | ||
|  | 16c7d2964b | ||
|  | 5a6e494bda | ||
|  | 88c6d89fbb | ||
|  | de12da18da | ||
|  | 8d10736934 | ||
|  | 2328e02d82 | ||
|  | 87cd5fca90 | ||
|  | b70cb726c5 | ||
|  | 295811ed5a | ||
|  | 4bc55551c6 | ||
|  | 2b8c6dc8f9 | ||
|  | 8b27380feb | ||
|  | 8de3a90851 | ||
|  | f8bb94d721 | ||
|  | 0c6d06e7bb | ||
|  | b0aa5d3af2 | ||
|  | 27d09713fd | ||
|  | ff18852879 | ||
|  | e515c91e5e | ||
|  | 1d5800c4f7 | ||
|  | de8f190945 | ||
|  | f371e3c8d3 | ||
|  | b15a6290a7 | ||
|  | 0f151db355 | ||
|  | fa99b75d1f | ||
|  | 4a299c1f39 | ||
|  | 30a2015a68 | ||
|  | c7c8a4891f | ||
|  | fe9fc6b459 | ||
|  | 2113fb490d | ||
|  | 0afedbd252 | ||
|  | 3dec38450a | ||
|  | d7a00b05e3 | ||
|  | ff2cc8c887 | ||
|  | 0571a96cea | ||
|  | b7b457b076 | ||
|  | d2c12005b2 | ||
|  | 25f26d7e5f | ||
|  | 9d5724e1a0 | ||
|  | 784d23ec87 | ||
|  | 709689a7ee | ||
|  | 35d5d3d995 | ||
|  | 630a8d8a4e | ||
|  | 54eb1d9055 | ||
|  | cb63caea61 | ||
|  | 8e046bb022 | ||
|  | 189b53b8c4 | ||
|  | 9b7277d464 | ||
|  | 551a65c069 | ||
|  | 4c21a697f4 | ||
|  | a8506c07ae | ||
|  | b0feb85b5c | ||
|  | f371eba8ad | ||
|  | 265fa52ace | ||
|  | c1f5322d38 | ||
|  | 4415e00b38 | ||
|  | d4e46c5a9c | ||
|  | bf5b9949fe | ||
|  | 3c11a2b1da | ||
|  | 1248868169 | ||
|  | 930b95a548 | ||
|  | 27c9f68c7c | ||
|  | 358d4d86a7 | ||
|  | d55eea2239 | ||
|  | 4507954ea5 | ||
|  | 8e0a83f8d8 | ||
|  | b6ec09e6cf | ||
|  | ed4e90623d | ||
|  | 38cdb48748 | ||
|  | 71bfe9b745 | ||
|  | 9e7645a167 | ||
|  | 6dccc37143 | ||
|  | 3310310d8c | ||
|  | abb499aad8 | ||
|  | 1afc09b08a | ||
|  | 0e86d72f05 | ||
|  | c9857a2764 | ||
|  | 701ba3902c | ||
|  | bb3ec970a9 | ||
|  | 1d023e6db5 | ||
|  | 1bd906ecb3 | ||
|  | 7cbffdb86b | ||
|  | b979196ffd | ||
|  | 2949db5f49 | ||
|  | 47c3ed9705 | ||
|  | 51ca3109eb | ||
|  | 2bcb79dc44 | ||
|  | ecec985649 | ||
|  | 994908549d | ||
|  | e00f059a4a | ||
|  | 6808e0d531 | ||
|  | 410c95308a | ||
|  | bec1f23616 | ||
|  | 34596b7f83 | ||
|  | 5ca7169ee5 | ||
|  | 3300aaa1b9 | ||
|  | 54afc8a4a1 | ||
|  | f19f4310f4 | ||
|  | 444125a7e1 | ||
|  | 56cbb69401 | ||
|  | 70870ffcd2 | ||
|  | a556d91673 | ||
|  | 8ecbde8e1e | ||
|  | a24b293818 | ||
|  | cab5ebf94b | ||
|  | a54b5baef2 | ||
|  | c324fe204e | ||
|  | 37d972ed9e | ||
|  | e4a3b1cb05 | ||
|  | 49538f272e | ||
|  | 1283232144 | ||
|  | 2f9891f0aa | ||
|  | 865a2b1fb0 | ||
|  | 1fde62eeb6 | ||
|  | 6754cd48e2 | ||
|  | ccb477687a | ||
|  | be0e6c8925 | ||
|  | 1c2cb947c0 | ||
|  | 4663eec950 | ||
|  | fff3c1eb36 | ||
|  | 4b7434d1e8 | ||
|  | 8cc35a60e6 | ||
|  | 130653a1aa | ||
|  | 1dcbebd130 | ||
|  | faf306ad23 | ||
|  | d11cdcd5d2 | ||
|  | 0e59bd0bf3 | ||
|  | c0c6154ec1 | ||
|  | cb5384c2fb | ||
|  | 038c836fe5 | ||
|  | 731a823cc2 | 
| @@ -23,7 +23,7 @@ jobs: | ||||
|             #       command: npm set prefix=/home/circleci/npm && echo 'export PATH=$HOME/circleci/npm/bin:$PATH' >> /home/circleci/.bashrc | ||||
|             - run: | ||||
|                   name: install-yarn | ||||
|                   command: npm install --global yarn@1.17.0 | ||||
|                   command: npm install --force --global yarn@1.17.0 | ||||
|             - run: | ||||
|                   name: yarn | ||||
|                   command: yarn --frozen-lockfile --ignore-engines install || yarn --frozen-lockfile --ignore-engines install | ||||
| @@ -77,7 +77,7 @@ jobs: | ||||
|             - restore_cache: | ||||
|                   keys: | ||||
|                       - repo-{{ .Environment.CIRCLE_SHA1 }} | ||||
|             - run: yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-asset-proxy @0x/contracts-exchange-forwarder @0x/contracts-tests @0x/contracts-staking @0x/contracts-coordinator @0x/contracts-erc20-bridge-sampler  | ||||
|             - run: yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-asset-proxy @0x/contracts-exchange-forwarder @0x/contracts-tests @0x/contracts-staking @0x/contracts-coordinator @0x/contracts-erc20-bridge-sampler | ||||
|             # TODO(dorothy-zbornak): Re-enable after updating this package for | ||||
|             # 3.0. At that time, also remove exclusion from monorepo | ||||
|             # package.json's test script. | ||||
| @@ -193,17 +193,14 @@ jobs: | ||||
|         working_directory: ~/repo | ||||
|         docker: | ||||
|             - image: nikolaik/python-nodejs:python3.7-nodejs8 | ||||
|             - image: 0xorg/ganache-cli | ||||
|             - image: 0xorg/ganache-cli:6.0.0 | ||||
|             - image: 0xorg/mesh:0xV3 | ||||
|               environment: | ||||
|                   ETHEREUM_RPC_URL: 'http://localhost:8545' | ||||
|                   ETHEREUM_NETWORK_ID: '50' | ||||
|                   ETHEREUM_CHAIN_ID: '1337' | ||||
|                   USE_BOOTSTRAP_LIST: 'true' | ||||
|                   VERBOSITY: 3 | ||||
|                   PRIVATE_KEY_PATH: '' | ||||
|                   BLOCK_POLLING_INTERVAL: '5s' | ||||
|                   P2P_LISTEN_PORT: '60557' | ||||
|                   VERBOSITY: 5 | ||||
|                   BLOCK_POLLING_INTERVAL: '50ms' | ||||
|                   ETHEREUM_RPC_MAX_REQUESTS_PER_24_HR_UTC: '1778000' | ||||
|               command: | | ||||
|                   sh -c "waitForGanache () { until printf 'POST /\r\nContent-Length: 26\r\n\r\n{\"method\":\"net_listening\"}' | nc localhost 8545 | grep true; do continue; done }; waitForGanache && ./mesh" | ||||
|             - image: 0xorg/launch-kit-backend:v3 | ||||
|   | ||||
							
								
								
									
										28
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -160,33 +160,7 @@ contracts/exchange-forwarder/generated-wrappers/ | ||||
| contracts/exchange-forwarder/test/generated-wrappers/ | ||||
| contracts/dev-utils/generated-wrappers/ | ||||
| contracts/dev-utils/test/generated-wrappers/ | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/dev_utils/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc20_token/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/asset_proxy_owner/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/coordinator/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/coordinator_registry/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/dummy_erc20_token/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/dummy_erc721_token/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/dutch_auction/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc1155_mintable/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc1155_proxy/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc20_bridge_proxy/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc20_proxy/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc721_proxy/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc721_token/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/forwarder/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/i_asset_proxy/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/i_validator/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/i_wallet/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/multi_asset_proxy/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/order_validator/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/staking/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/staking_proxy/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/static_call_proxy/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/weth9/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/zrx_token/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/zrx_vault/__init__.py | ||||
| python-packages/contract_wrappers/src/zero_ex/contract_wrappers/*/__init__.py | ||||
|  | ||||
| # solc-bin in sol-compiler | ||||
| packages/sol-compiler/solc_bin/ | ||||
|   | ||||
| @@ -14,8 +14,8 @@ packages/abi-gen/ @feuGeneA | ||||
| packages/base-contract/ @xianny | ||||
| packages/connect/ @fragosti  | ||||
| packages/abi-gen-templates/ @feuGeneA @xianny | ||||
| packages/contract-addresses/ @albrow | ||||
| packages/contract-artifacts/ @albrow | ||||
| packages/contract-addresses/ @abandeali1 | ||||
| packages/contract-artifacts/ @abandeali1 | ||||
| packages/dev-utils/ @LogvinovLeon @fabioberger | ||||
| packages/devnet/ @albrow | ||||
| packages/ethereum-types/ @LogvinovLeon | ||||
|   | ||||
| @@ -1,4 +1,40 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1579682890, | ||||
|         "version": "3.1.1", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "version": "3.1.0", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Integration tests for DydxBridge with ERC20BridgeProxy.", | ||||
|                 "pr": 2401 | ||||
|             }, | ||||
|             { | ||||
|                 "note": "Fix `UniswapBridge` token -> token transfer call.", | ||||
|                 "pr": 2412 | ||||
|             }, | ||||
|             { | ||||
|                 "note": "Fix `KyberBridge` incorrect `minConversionRate` calculation.", | ||||
|                 "pr": 2412 | ||||
|             } | ||||
|         ], | ||||
|         "timestamp": 1578272714 | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1576540892, | ||||
|         "version": "3.0.2", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1575931811, | ||||
|         "version": "3.0.1", | ||||
| @@ -64,6 +100,10 @@ | ||||
|             { | ||||
|                 "note": "Implement `KyberBridge`.", | ||||
|                 "pr": 2352 | ||||
|             }, | ||||
|             { | ||||
|                 "note": "Implement `DydxBridge`.", | ||||
|                 "pr": 2365 | ||||
|             } | ||||
|         ], | ||||
|         "timestamp": 1575290197 | ||||
|   | ||||
| @@ -5,6 +5,20 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v3.1.1 - _January 22, 2020_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v3.1.0 - _January 6, 2020_ | ||||
|  | ||||
|     * Integration tests for DydxBridge with ERC20BridgeProxy. (#2401) | ||||
|     * Fix `UniswapBridge` token -> token transfer call. (#2412) | ||||
|     * Fix `KyberBridge` incorrect `minConversionRate` calculation. (#2412) | ||||
|  | ||||
| ## v3.0.2 - _December 17, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v3.0.1 - _December 9, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
| @@ -26,6 +40,7 @@ CHANGELOG | ||||
| ## v2.3.0-beta.4 - _December 2, 2019_ | ||||
|  | ||||
|     * Implement `KyberBridge`. (#2352) | ||||
|     * Implement `DydxBridge`. (#2365) | ||||
|  | ||||
| ## v2.3.0-beta.3 - _November 20, 2019_ | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|     "useDockerisedSolc": false, | ||||
|     "isOfflineMode": false, | ||||
|     "compilerSettings": { | ||||
|         "evmVersion": "constantinople", | ||||
|         "evmVersion": "istanbul", | ||||
|         "optimizer": { | ||||
|             "enabled": true, | ||||
|             "runs": 1000000, | ||||
|   | ||||
							
								
								
									
										241
									
								
								contracts/asset-proxy/contracts/src/bridges/DydxBridge.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								contracts/asset-proxy/contracts/src/bridges/DydxBridge.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,241 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2019 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.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; | ||||
| import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; | ||||
| import "../interfaces/IERC20Bridge.sol"; | ||||
| import "../interfaces/IDydxBridge.sol"; | ||||
| import "../interfaces/IDydx.sol"; | ||||
|  | ||||
|  | ||||
| contract DydxBridge is | ||||
|     IERC20Bridge, | ||||
|     IDydxBridge, | ||||
|     DeploymentConstants | ||||
| { | ||||
|  | ||||
|     using LibSafeMath for uint256; | ||||
|  | ||||
|     /// @dev Callback for `IERC20Bridge`. Deposits or withdraws tokens from a dydx account. | ||||
|     ///      Notes: | ||||
|     ///         1. This bridge must be set as an operator of the input dydx account. | ||||
|     ///         2. This function may only be called in the context of the 0x Exchange. | ||||
|     ///         3. The maker or taker of the 0x order must be the dydx account owner. | ||||
|     ///         4. Deposits into dydx are made from the `from` address. | ||||
|     ///         5. Withdrawals from dydx are made to the `to` address. | ||||
|     ///         6. Calling this function must always withdraw at least `amount`, | ||||
|     ///            otherwise the `ERC20Bridge` will revert. | ||||
|     /// @param from The sender of the tokens and owner of the dydx account. | ||||
|     /// @param to The recipient of the tokens. | ||||
|     /// @param amount Minimum amount of `toTokenAddress` tokens to deposit or withdraw. | ||||
|     /// @param encodedBridgeData An abi-encoded `BridgeData` struct. | ||||
|     /// @return success The magic bytes if successful. | ||||
|     function bridgeTransferFrom( | ||||
|         address, | ||||
|         address from, | ||||
|         address to, | ||||
|         uint256 amount, | ||||
|         bytes calldata encodedBridgeData | ||||
|     ) | ||||
|         external | ||||
|         returns (bytes4 success) | ||||
|     { | ||||
|         // Ensure that only the `ERC20BridgeProxy` can call this function. | ||||
|         require( | ||||
|             msg.sender == _getERC20BridgeProxyAddress(), | ||||
|             "DydxBridge/ONLY_CALLABLE_BY_ERC20_BRIDGE_PROXY" | ||||
|         ); | ||||
|  | ||||
|         // Decode bridge data. | ||||
|         (BridgeData memory bridgeData) = abi.decode(encodedBridgeData, (BridgeData)); | ||||
|  | ||||
|         // The dydx accounts are owned by the `from` address. | ||||
|         IDydx.AccountInfo[] memory accounts = _createAccounts(from, bridgeData); | ||||
|  | ||||
|         // Create dydx actions to run on the dydx accounts. | ||||
|         IDydx.ActionArgs[] memory actions = _createActions( | ||||
|             from, | ||||
|             to, | ||||
|             amount, | ||||
|             bridgeData | ||||
|         ); | ||||
|  | ||||
|         // Run operation. This will revert on failure. | ||||
|         IDydx(_getDydxAddress()).operate(accounts, actions); | ||||
|         return BRIDGE_SUCCESS; | ||||
|     } | ||||
|  | ||||
|     /// @dev Creates an array of accounts for dydx to operate on. | ||||
|     ///      All accounts must belong to the same owner. | ||||
|     /// @param accountOwner Owner of the dydx account. | ||||
|     /// @param bridgeData A `BridgeData` struct. | ||||
|     function _createAccounts( | ||||
|         address accountOwner, | ||||
|         BridgeData memory bridgeData | ||||
|     ) | ||||
|         internal | ||||
|         returns (IDydx.AccountInfo[] memory accounts) | ||||
|     { | ||||
|         uint256[] memory accountNumbers = bridgeData.accountNumbers; | ||||
|         uint256 nAccounts = accountNumbers.length; | ||||
|         accounts = new IDydx.AccountInfo[](nAccounts); | ||||
|         for (uint256 i = 0; i < nAccounts; ++i) { | ||||
|             accounts[i] = IDydx.AccountInfo({ | ||||
|                 owner: accountOwner, | ||||
|                 number: accountNumbers[i] | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Creates an array of actions to carry out on dydx. | ||||
|     /// @param depositFrom Deposit value from this address (owner of the dydx account). | ||||
|     /// @param withdrawTo Withdraw value to this address. | ||||
|     /// @param amount The amount of value available to operate on. | ||||
|     /// @param bridgeData A `BridgeData` struct. | ||||
|     function _createActions( | ||||
|         address depositFrom, | ||||
|         address withdrawTo, | ||||
|         uint256 amount, | ||||
|         BridgeData memory bridgeData | ||||
|     ) | ||||
|         internal | ||||
|         returns (IDydx.ActionArgs[] memory actions) | ||||
|     { | ||||
|         BridgeAction[] memory bridgeActions = bridgeData.actions; | ||||
|         uint256 nBridgeActions = bridgeActions.length; | ||||
|         actions = new IDydx.ActionArgs[](nBridgeActions); | ||||
|         for (uint256 i = 0; i < nBridgeActions; ++i) { | ||||
|             // Cache current bridge action. | ||||
|             BridgeAction memory bridgeAction = bridgeActions[i]; | ||||
|  | ||||
|             // Scale amount, if conversion rate is set. | ||||
|             uint256 scaledAmount; | ||||
|             if (bridgeAction.conversionRateDenominator > 0) { | ||||
|                 scaledAmount = LibMath.safeGetPartialAmountFloor( | ||||
|                     bridgeAction.conversionRateNumerator, | ||||
|                     bridgeAction.conversionRateDenominator, | ||||
|                     amount | ||||
|                 ); | ||||
|             } else { | ||||
|                 scaledAmount = amount; | ||||
|             } | ||||
|  | ||||
|             // Construct dydx action. | ||||
|             if (bridgeAction.actionType == BridgeActionType.Deposit) { | ||||
|                 // Deposit tokens from the account owner into their dydx account. | ||||
|                 actions[i] = _createDepositAction( | ||||
|                     depositFrom, | ||||
|                     scaledAmount, | ||||
|                     bridgeAction | ||||
|                 ); | ||||
|             } else if (bridgeAction.actionType == BridgeActionType.Withdraw) { | ||||
|                 // Withdraw tokens from dydx to the `otherAccount`. | ||||
|                 actions[i] = _createWithdrawAction( | ||||
|                     withdrawTo, | ||||
|                     scaledAmount, | ||||
|                     bridgeAction | ||||
|                 ); | ||||
|             } else { | ||||
|                 // If all values in the `Action` enum are handled then this | ||||
|                 // revert is unreachable: Solidity will revert when casting | ||||
|                 // from `uint8` to `Action`. | ||||
|                 revert("DydxBridge/UNRECOGNIZED_BRIDGE_ACTION"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Returns a dydx `DepositAction`. | ||||
|     /// @param depositFrom Deposit tokens from this address who is also the account owner. | ||||
|     /// @param amount of tokens to deposit. | ||||
|     /// @param bridgeAction A `BridgeAction` struct. | ||||
|     /// @return depositAction The encoded dydx action. | ||||
|     function _createDepositAction( | ||||
|         address depositFrom, | ||||
|         uint256 amount, | ||||
|         BridgeAction memory bridgeAction | ||||
|     ) | ||||
|         internal | ||||
|         pure | ||||
|         returns ( | ||||
|             IDydx.ActionArgs memory depositAction | ||||
|         ) | ||||
|     { | ||||
|         // Create dydx amount. | ||||
|         IDydx.AssetAmount memory dydxAmount = IDydx.AssetAmount({ | ||||
|             sign: true,                                 // true if positive. | ||||
|             denomination: IDydx.AssetDenomination.Wei,  // Wei => actual token amount held in account. | ||||
|             ref: IDydx.AssetReference.Delta,                // Delta => a relative amount. | ||||
|             value: amount                               // amount to deposit. | ||||
|         }); | ||||
|  | ||||
|         // Create dydx deposit action. | ||||
|         depositAction = IDydx.ActionArgs({ | ||||
|             actionType: IDydx.ActionType.Deposit,           // deposit tokens. | ||||
|             amount: dydxAmount,                             // amount to deposit. | ||||
|             accountId: bridgeAction.accountId,              // index in the `accounts` when calling `operate`. | ||||
|             primaryMarketId: bridgeAction.marketId,         // indicates which token to deposit. | ||||
|             otherAddress: depositFrom,                      // deposit from the account owner. | ||||
|             // unused parameters | ||||
|             secondaryMarketId: 0, | ||||
|             otherAccountId: 0, | ||||
|             data: hex'' | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /// @dev Returns a dydx `WithdrawAction`. | ||||
|     /// @param withdrawTo Withdraw tokens to this address. | ||||
|     /// @param amount of tokens to withdraw. | ||||
|     /// @param bridgeAction A `BridgeAction` struct. | ||||
|     /// @return withdrawAction The encoded dydx action. | ||||
|     function _createWithdrawAction( | ||||
|         address withdrawTo, | ||||
|         uint256 amount, | ||||
|         BridgeAction memory bridgeAction | ||||
|     ) | ||||
|         internal | ||||
|         pure | ||||
|         returns ( | ||||
|             IDydx.ActionArgs memory withdrawAction | ||||
|         ) | ||||
|     { | ||||
|         // Create dydx amount. | ||||
|         IDydx.AssetAmount memory amountToWithdraw = IDydx.AssetAmount({ | ||||
|             sign: false,                                    // false if negative. | ||||
|             denomination: IDydx.AssetDenomination.Wei,      // Wei => actual token amount held in account. | ||||
|             ref: IDydx.AssetReference.Delta,                // Delta => a relative amount. | ||||
|             value: amount                                   // amount to withdraw. | ||||
|         }); | ||||
|  | ||||
|         // Create withdraw action. | ||||
|         withdrawAction = IDydx.ActionArgs({ | ||||
|             actionType: IDydx.ActionType.Withdraw,          // withdraw tokens. | ||||
|             amount: amountToWithdraw,                       // amount to withdraw. | ||||
|             accountId: bridgeAction.accountId,              // index in the `accounts` when calling `operate`. | ||||
|             primaryMarketId: bridgeAction.marketId,         // indicates which token to withdraw. | ||||
|             otherAddress: withdrawTo,                       // withdraw tokens to this address. | ||||
|             // unused parameters | ||||
|             secondaryMarketId: 0, | ||||
|             otherAccountId: 0, | ||||
|             data: hex'' | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @@ -24,6 +24,7 @@ import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol"; | ||||
| import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; | ||||
| import "../interfaces/IERC20Bridge.sol"; | ||||
| import "../interfaces/IKyberNetworkProxy.sol"; | ||||
|  | ||||
| @@ -34,6 +35,8 @@ contract KyberBridge is | ||||
|     IWallet, | ||||
|     DeploymentConstants | ||||
| { | ||||
|     using LibSafeMath for uint256; | ||||
|  | ||||
|     // @dev Structure used internally to get around stack limits. | ||||
|     struct TradeState { | ||||
|         IKyberNetworkProxy kyber; | ||||
| @@ -41,6 +44,7 @@ contract KyberBridge is | ||||
|         address fromTokenAddress; | ||||
|         uint256 fromTokenBalance; | ||||
|         uint256 payableAmount; | ||||
|         uint256 conversionRate; | ||||
|     } | ||||
|  | ||||
|     /// @dev Kyber ETH pseudo-address. | ||||
| @@ -81,11 +85,23 @@ contract KyberBridge is | ||||
|         state.weth = IEtherToken(_getWethAddress()); | ||||
|         // Decode the bridge data to get the `fromTokenAddress`. | ||||
|         (state.fromTokenAddress) = abi.decode(bridgeData, (address)); | ||||
|         // Query the balance of "from" tokens. | ||||
|         state.fromTokenBalance = IERC20Token(state.fromTokenAddress).balanceOf(address(this)); | ||||
|         if (state.fromTokenBalance == 0) { | ||||
|             // Return failure if no input tokens. | ||||
|             return BRIDGE_FAILED; | ||||
|         } | ||||
|         // Compute the conversion rate, expressed in 18 decimals. | ||||
|         // The sequential notation is to get around stack limits. | ||||
|         state.conversionRate = KYBER_RATE_BASE; | ||||
|         state.conversionRate = state.conversionRate.safeMul(amount); | ||||
|         state.conversionRate = state.conversionRate.safeMul( | ||||
|             10 ** uint256(LibERC20Token.decimals(state.fromTokenAddress)) | ||||
|         ); | ||||
|         state.conversionRate = state.conversionRate.safeDiv(state.fromTokenBalance); | ||||
|         state.conversionRate = state.conversionRate.safeDiv( | ||||
|             10 ** uint256(LibERC20Token.decimals(toTokenAddress)) | ||||
|         ); | ||||
|         if (state.fromTokenAddress == toTokenAddress) { | ||||
|             // Just transfer the tokens if they're the same. | ||||
|             LibERC20Token.transfer(state.fromTokenAddress, to, state.fromTokenBalance); | ||||
| @@ -118,7 +134,7 @@ contract KyberBridge is | ||||
|             uint256(-1), | ||||
|             // Compute the minimum conversion rate, which is expressed in units with | ||||
|             // 18 decimal places. | ||||
|             (KYBER_RATE_BASE * amount) / state.fromTokenBalance, | ||||
|             state.conversionRate, | ||||
|             // No affiliate address. | ||||
|             address(0) | ||||
|         ); | ||||
|   | ||||
| @@ -134,8 +134,8 @@ contract UniswapBridge is | ||||
|                 state.fromTokenBalance, | ||||
|                 // Minimum buy amount. | ||||
|                 amount, | ||||
|                 // No minimum intermediate ETH buy amount. | ||||
|                 0, | ||||
|                 // Must buy at least 1 intermediate ETH. | ||||
|                 1, | ||||
|                 // Expires after this block. | ||||
|                 block.timestamp, | ||||
|                 // Recipient is `to`. | ||||
|   | ||||
| @@ -18,10 +18,22 @@ | ||||
|  | ||||
| pragma solidity ^0.5.9; | ||||
|  | ||||
| import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; | ||||
|  | ||||
|  | ||||
| contract PotLike { | ||||
|     function chi() external returns (uint256); | ||||
|     function rho() external returns (uint256); | ||||
|     function drip() external returns (uint256); | ||||
|     function join(uint256) external; | ||||
|     function exit(uint256) external; | ||||
| } | ||||
|  | ||||
|  | ||||
| // The actual Chai contract can be found here: https://github.com/dapphub/chai | ||||
| contract IChai { | ||||
|  | ||||
| contract IChai is | ||||
|     IERC20Token | ||||
| { | ||||
|     /// @dev Withdraws Dai owned by `src` | ||||
|     /// @param src Address that owns Dai. | ||||
|     /// @param wad Amount of Dai to withdraw. | ||||
| @@ -30,4 +42,25 @@ contract IChai { | ||||
|         uint256 wad | ||||
|     ) | ||||
|         external; | ||||
|  | ||||
|     /// @dev Queries Dai balance of Chai holder. | ||||
|     /// @param usr Address of Chai holder. | ||||
|     /// @return Dai balance. | ||||
|     function dai(address usr) | ||||
|         external | ||||
|         returns (uint256); | ||||
|  | ||||
|     /// @dev Queries the Pot contract used by the Chai contract. | ||||
|     function pot() | ||||
|         external | ||||
|         returns (PotLike); | ||||
|  | ||||
|     /// @dev Deposits Dai in exchange for Chai | ||||
|     /// @param dst Address to receive Chai. | ||||
|     /// @param wad Amount of Dai to deposit. | ||||
|     function join( | ||||
|         address dst, | ||||
|         uint256 wad | ||||
|     ) | ||||
|         external; | ||||
| } | ||||
|   | ||||
							
								
								
									
										89
									
								
								contracts/asset-proxy/contracts/src/interfaces/IDydx.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								contracts/asset-proxy/contracts/src/interfaces/IDydx.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2019 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.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
|  | ||||
| interface IDydx { | ||||
|  | ||||
|     /// @dev Represents the unique key that specifies an account | ||||
|     struct AccountInfo { | ||||
|         address owner;  // The address that owns the account | ||||
|         uint256 number; // A nonce that allows a single address to control many accounts | ||||
|     } | ||||
|  | ||||
|     enum ActionType { | ||||
|         Deposit,   // supply tokens | ||||
|         Withdraw,  // borrow tokens | ||||
|         Transfer,  // transfer balance between accounts | ||||
|         Buy,       // buy an amount of some token (externally) | ||||
|         Sell,      // sell an amount of some token (externally) | ||||
|         Trade,     // trade tokens against another account | ||||
|         Liquidate, // liquidate an undercollateralized or expiring account | ||||
|         Vaporize,  // use excess tokens to zero-out a completely negative account | ||||
|         Call       // send arbitrary data to an address | ||||
|     } | ||||
|  | ||||
|     /// @dev Arguments that are passed to Solo in an ordered list as part of a single operation. | ||||
|     /// Each ActionArgs has an actionType which specifies which action struct that this data will be | ||||
|     /// parsed into before being processed. | ||||
|     struct ActionArgs { | ||||
|         ActionType actionType; | ||||
|         uint256 accountId; | ||||
|         AssetAmount amount; | ||||
|         uint256 primaryMarketId; | ||||
|         uint256 secondaryMarketId; | ||||
|         address otherAddress; | ||||
|         uint256 otherAccountId; | ||||
|         bytes data; | ||||
|     } | ||||
|  | ||||
|     enum AssetDenomination { | ||||
|         Wei, // the amount is denominated in wei | ||||
|         Par  // the amount is denominated in par | ||||
|     } | ||||
|  | ||||
|     enum AssetReference { | ||||
|         Delta, // the amount is given as a delta from the current value | ||||
|         Target // the amount is given as an exact number to end up at | ||||
|     } | ||||
|  | ||||
|     struct AssetAmount { | ||||
|         bool sign; // true if positive | ||||
|         AssetDenomination denomination; | ||||
|         AssetReference ref; | ||||
|         uint256 value; | ||||
|     } | ||||
|  | ||||
|     /// @dev The main entry-point to Solo that allows users and contracts to manage accounts. | ||||
|     ///      Take one or more actions on one or more accounts. The msg.sender must be the owner or | ||||
|     ///      operator of all accounts except for those being liquidated, vaporized, or traded with. | ||||
|     ///      One call to operate() is considered a singular "operation". Account collateralization is | ||||
|     ///      ensured only after the completion of the entire operation. | ||||
|     /// @param  accounts  A list of all accounts that will be used in this operation. Cannot contain | ||||
|     ///                   duplicates. In each action, the relevant account will be referred-to by its | ||||
|     ///                   index in the list. | ||||
|     /// @param  actions   An ordered list of all actions that will be taken in this operation. The | ||||
|     ///                   actions will be processed in order. | ||||
|     function operate( | ||||
|         AccountInfo[] calldata accounts, | ||||
|         ActionArgs[] calldata actions | ||||
|     ) | ||||
|         external; | ||||
| } | ||||
| @@ -0,0 +1,42 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2019 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.5.9; | ||||
|  | ||||
|  | ||||
| interface IDydxBridge { | ||||
|  | ||||
|     /// @dev This is the subset of `IDydx.ActionType` that are supported by the bridge. | ||||
|     enum BridgeActionType { | ||||
|         Deposit,                    // Deposit tokens into dydx account. | ||||
|         Withdraw                    // Withdraw tokens from dydx account. | ||||
|     } | ||||
|  | ||||
|     struct BridgeAction { | ||||
|         BridgeActionType actionType;            // Action to run on dydx account. | ||||
|         uint256 accountId;                      // Index in `BridgeData.accountNumbers` for this action. | ||||
|         uint256 marketId;                       // Market to operate on. | ||||
|         uint256 conversionRateNumerator;        // Optional. If set, transfer amount is scaled by (conversionRateNumerator/conversionRateDenominator). | ||||
|         uint256 conversionRateDenominator;      // Optional. If set, transfer amount is scaled by (conversionRateNumerator/conversionRateDenominator). | ||||
|     } | ||||
|  | ||||
|     struct BridgeData { | ||||
|         uint256[] accountNumbers;               // Account number used to identify the owner's specific account. | ||||
|         BridgeAction[] actions;                 // Actions to carry out on the owner's accounts. | ||||
|     } | ||||
| } | ||||
							
								
								
									
										191
									
								
								contracts/asset-proxy/contracts/test/TestDydxBridge.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								contracts/asset-proxy/contracts/test/TestDydxBridge.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2019 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.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; | ||||
| import "../src/bridges/DydxBridge.sol"; | ||||
|  | ||||
|  | ||||
| contract TestDydxBridgeToken { | ||||
|  | ||||
|     uint256 private constant INIT_HOLDER_BALANCE = 10 * 10**18; // 10 tokens | ||||
|     mapping (address => uint256) private _balances; | ||||
|  | ||||
|     /// @dev Sets initial balance of token holders. | ||||
|     constructor(address[] memory holders) | ||||
|         public | ||||
|     { | ||||
|         for (uint256 i = 0; i != holders.length; ++i) { | ||||
|             _balances[holders[i]] = INIT_HOLDER_BALANCE; | ||||
|         } | ||||
|         _balances[msg.sender] = INIT_HOLDER_BALANCE; | ||||
|     } | ||||
|  | ||||
|     /// @dev Basic transferFrom implementation. | ||||
|     function transferFrom(address from, address to, uint256 amount) | ||||
|         external | ||||
|         returns (bool) | ||||
|     { | ||||
|         if (_balances[from] < amount || _balances[to] + amount < _balances[to]) { | ||||
|             return false; | ||||
|         } | ||||
|         _balances[from] -= amount; | ||||
|         _balances[to] += amount; | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /// @dev Returns balance of `holder`. | ||||
|     function balanceOf(address holder) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _balances[holder]; | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| // solhint-disable space-after-comma | ||||
| contract TestDydxBridge is | ||||
|     IDydx, | ||||
|     DydxBridge | ||||
| { | ||||
|  | ||||
|     address private constant ALWAYS_REVERT_ADDRESS = address(1); | ||||
|     address private _testTokenAddress; | ||||
|     bool private _shouldRevertOnOperate; | ||||
|  | ||||
|     event OperateAccount( | ||||
|         address owner, | ||||
|         uint256 number | ||||
|     ); | ||||
|  | ||||
|     event OperateAction( | ||||
|         ActionType actionType, | ||||
|         uint256 accountId, | ||||
|         bool amountSign, | ||||
|         AssetDenomination amountDenomination, | ||||
|         AssetReference amountRef, | ||||
|         uint256 amountValue, | ||||
|         uint256 primaryMarketId, | ||||
|         uint256 secondaryMarketId, | ||||
|         address otherAddress, | ||||
|         uint256 otherAccountId, | ||||
|         bytes data | ||||
|     ); | ||||
|  | ||||
|     constructor(address[] memory holders) | ||||
|         public | ||||
|     { | ||||
|         // Deploy a test token. This represents the asset being deposited/withdrawn from dydx. | ||||
|         _testTokenAddress = address(new TestDydxBridgeToken(holders)); | ||||
|     } | ||||
|  | ||||
|     /// @dev Simulates `operate` in dydx contract. | ||||
|     ///      Emits events so that arguments can be validated client-side. | ||||
|     function operate( | ||||
|         AccountInfo[] calldata accounts, | ||||
|         ActionArgs[] calldata actions | ||||
|     ) | ||||
|         external | ||||
|     { | ||||
|         if (_shouldRevertOnOperate) { | ||||
|             revert("TestDydxBridge/SHOULD_REVERT_ON_OPERATE"); | ||||
|         } | ||||
|  | ||||
|         for (uint i = 0; i < accounts.length; ++i) { | ||||
|             emit OperateAccount( | ||||
|                 accounts[i].owner, | ||||
|                 accounts[i].number | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         for (uint i = 0; i < actions.length; ++i) { | ||||
|             emit OperateAction( | ||||
|                 actions[i].actionType, | ||||
|                 actions[i].accountId, | ||||
|                 actions[i].amount.sign, | ||||
|                 actions[i].amount.denomination, | ||||
|                 actions[i].amount.ref, | ||||
|                 actions[i].amount.value, | ||||
|                 actions[i].primaryMarketId, | ||||
|                 actions[i].secondaryMarketId, | ||||
|                 actions[i].otherAddress, | ||||
|                 actions[i].otherAccountId, | ||||
|                 actions[i].data | ||||
|             ); | ||||
|  | ||||
|             if (actions[i].actionType == IDydx.ActionType.Withdraw) { | ||||
|                 require( | ||||
|                     IERC20Token(_testTokenAddress).transferFrom( | ||||
|                         address(this), | ||||
|                         actions[i].otherAddress, | ||||
|                         actions[i].amount.value | ||||
|                     ), | ||||
|                     "TestDydxBridge/WITHDRAW_FAILED" | ||||
|                 ); | ||||
|             } else if (actions[i].actionType == IDydx.ActionType.Deposit) { | ||||
|                 require( | ||||
|                     IERC20Token(_testTokenAddress).transferFrom( | ||||
|                         actions[i].otherAddress, | ||||
|                         address(this), | ||||
|                         actions[i].amount.value | ||||
|                     ), | ||||
|                     "TestDydxBridge/DEPOSIT_FAILED" | ||||
|                 ); | ||||
|             } else { | ||||
|                 revert("TestDydxBridge/UNSUPPORTED_ACTION"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev If `true` then subsequent calls to `operate` will revert. | ||||
|     function setRevertOnOperate(bool shouldRevert) | ||||
|         external | ||||
|     { | ||||
|         _shouldRevertOnOperate = shouldRevert; | ||||
|     } | ||||
|  | ||||
|     /// @dev Returns test token. | ||||
|     function getTestToken() | ||||
|         external | ||||
|         returns (address) | ||||
|     { | ||||
|         return _testTokenAddress; | ||||
|     } | ||||
|  | ||||
|     /// @dev overrides `_getDydxAddress()` from `DeploymentConstants` to return this address. | ||||
|     function _getDydxAddress() | ||||
|         internal | ||||
|         view | ||||
|         returns (address) | ||||
|     { | ||||
|         return address(this); | ||||
|     } | ||||
|  | ||||
|     /// @dev overrides `_getERC20BridgeProxyAddress()` from `DeploymentConstants` for testing. | ||||
|     function _getERC20BridgeProxyAddress() | ||||
|         internal | ||||
|         view | ||||
|         returns (address) | ||||
|     { | ||||
|         return msg.sender == ALWAYS_REVERT_ADDRESS ? address(0) : msg.sender; | ||||
|     } | ||||
| } | ||||
| @@ -67,9 +67,11 @@ interface ITestContract { | ||||
| /// @dev A minimalist ERC20/WETH token. | ||||
| contract TestToken { | ||||
|  | ||||
|     uint8 public decimals; | ||||
|     ITestContract private _testContract; | ||||
|  | ||||
|     constructor() public { | ||||
|     constructor(uint8 decimals_) public { | ||||
|         decimals = decimals_; | ||||
|         _testContract = ITestContract(msg.sender); | ||||
|     } | ||||
|  | ||||
| @@ -165,7 +167,7 @@ contract TestKyberBridge is | ||||
|     uint256 private _nextFillAmount; | ||||
|  | ||||
|     constructor() public { | ||||
|         weth = IEtherToken(address(new TestToken())); | ||||
|         weth = IEtherToken(address(new TestToken(18))); | ||||
|     } | ||||
|  | ||||
|     /// @dev Implementation of `IKyberNetworkProxy.trade()` | ||||
| @@ -195,11 +197,11 @@ contract TestKyberBridge is | ||||
|         return _nextFillAmount; | ||||
|     } | ||||
|  | ||||
|     function createToken() | ||||
|     function createToken(uint8 decimals) | ||||
|         external | ||||
|         returns (address tokenAddress) | ||||
|     { | ||||
|         return address(new TestToken()); | ||||
|         return address(new TestToken(decimals)); | ||||
|     } | ||||
|  | ||||
|     function setNextFillAmount(uint256 amount) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0x/contracts-asset-proxy", | ||||
|     "version": "3.0.1", | ||||
|     "version": "3.1.1", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -38,8 +38,7 @@ | ||||
|         "docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" | ||||
|     }, | ||||
|     "config": { | ||||
|         "publicInterfaceContracts": "ERC1155Proxy,ERC20Proxy,ERC721Proxy,MultiAssetProxy,StaticCallProxy,ERC20BridgeProxy,Eth2DaiBridge,IAssetData,IAssetProxy,UniswapBridge,KyberBridge,ChaiBridge,TestStaticCallTarget", | ||||
|         "abis": "./test/generated-artifacts/@(ChaiBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IChai|IERC20Bridge|IEth2Dai|IKyberNetworkProxy|IUniswapExchange|IUniswapExchangeFactory|KyberBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MultiAssetProxy|Ownable|StaticCallProxy|TestChaiBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|UniswapBridge).json", | ||||
|         "abis": "./test/generated-artifacts/@(ChaiBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IChai|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IKyberNetworkProxy|IUniswapExchange|IUniswapExchangeFactory|KyberBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MultiAssetProxy|Ownable|StaticCallProxy|TestChaiBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|UniswapBridge).json", | ||||
|         "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." | ||||
|     }, | ||||
|     "repository": { | ||||
| @@ -52,15 +51,15 @@ | ||||
|     }, | ||||
|     "homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md", | ||||
|     "devDependencies": { | ||||
|         "@0x/abi-gen": "^5.0.1", | ||||
|         "@0x/contracts-gen": "^2.0.1", | ||||
|         "@0x/contracts-test-utils": "^5.0.0", | ||||
|         "@0x/contracts-utils": "^4.0.1", | ||||
|         "@0x/dev-utils": "^3.0.1", | ||||
|         "@0x/sol-compiler": "^4.0.1", | ||||
|         "@0x/abi-gen": "^5.1.0", | ||||
|         "@0x/contracts-gen": "^2.0.4", | ||||
|         "@0x/contracts-test-utils": "^5.1.1", | ||||
|         "@0x/contracts-utils": "^4.1.0", | ||||
|         "@0x/dev-utils": "^3.1.1", | ||||
|         "@0x/sol-compiler": "^4.0.4", | ||||
|         "@0x/ts-doc-gen": "^0.0.22", | ||||
|         "@0x/tslint-config": "^4.0.0", | ||||
|         "@0x/types": "^3.1.0", | ||||
|         "@0x/types": "^3.1.1", | ||||
|         "@types/lodash": "4.14.104", | ||||
|         "@types/mocha": "^5.2.7", | ||||
|         "@types/node": "*", | ||||
| @@ -80,15 +79,16 @@ | ||||
|         "typescript": "3.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0x/base-contract": "^6.0.1", | ||||
|         "@0x/contracts-dev-utils": "^1.0.1", | ||||
|         "@0x/contracts-erc1155": "^2.0.1", | ||||
|         "@0x/contracts-erc20": "^3.0.1", | ||||
|         "@0x/contracts-erc721": "^3.0.1", | ||||
|         "@0x/order-utils": "^10.0.0", | ||||
|         "@0x/typescript-typings": "^5.0.0", | ||||
|         "@0x/utils": "^5.1.0", | ||||
|         "@0x/web3-wrapper": "^7.0.1", | ||||
|         "@0x/base-contract": "^6.1.0", | ||||
|         "@0x/contracts-dev-utils": "^1.0.4", | ||||
|         "@0x/contracts-erc1155": "^2.0.4", | ||||
|         "@0x/contracts-erc20": "^3.0.4", | ||||
|         "@0x/contracts-erc721": "^3.0.4", | ||||
|         "@0x/contracts-exchange-libs": "^4.1.0", | ||||
|         "@0x/order-utils": "^10.1.1", | ||||
|         "@0x/typescript-typings": "^5.0.1", | ||||
|         "@0x/utils": "^5.2.0", | ||||
|         "@0x/web3-wrapper": "^7.0.4", | ||||
|         "ethereum-types": "^3.0.0", | ||||
|         "lodash": "^4.17.11" | ||||
|     }, | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| import { ContractArtifact } from 'ethereum-types'; | ||||
|  | ||||
| import * as ChaiBridge from '../generated-artifacts/ChaiBridge.json'; | ||||
| import * as DydxBridge from '../generated-artifacts/DydxBridge.json'; | ||||
| import * as ERC1155Proxy from '../generated-artifacts/ERC1155Proxy.json'; | ||||
| import * as ERC20BridgeProxy from '../generated-artifacts/ERC20BridgeProxy.json'; | ||||
| import * as ERC20Proxy from '../generated-artifacts/ERC20Proxy.json'; | ||||
| @@ -13,23 +14,62 @@ import * as ERC721Proxy from '../generated-artifacts/ERC721Proxy.json'; | ||||
| import * as Eth2DaiBridge from '../generated-artifacts/Eth2DaiBridge.json'; | ||||
| import * as IAssetData from '../generated-artifacts/IAssetData.json'; | ||||
| import * as IAssetProxy from '../generated-artifacts/IAssetProxy.json'; | ||||
| import * as IAssetProxyDispatcher from '../generated-artifacts/IAssetProxyDispatcher.json'; | ||||
| import * as IAuthorizable from '../generated-artifacts/IAuthorizable.json'; | ||||
| import * as IChai from '../generated-artifacts/IChai.json'; | ||||
| import * as IDydx from '../generated-artifacts/IDydx.json'; | ||||
| import * as IDydxBridge from '../generated-artifacts/IDydxBridge.json'; | ||||
| import * as IERC20Bridge from '../generated-artifacts/IERC20Bridge.json'; | ||||
| import * as IEth2Dai from '../generated-artifacts/IEth2Dai.json'; | ||||
| import * as IKyberNetworkProxy from '../generated-artifacts/IKyberNetworkProxy.json'; | ||||
| import * as IUniswapExchange from '../generated-artifacts/IUniswapExchange.json'; | ||||
| import * as IUniswapExchangeFactory from '../generated-artifacts/IUniswapExchangeFactory.json'; | ||||
| import * as KyberBridge from '../generated-artifacts/KyberBridge.json'; | ||||
| import * as MixinAssetProxyDispatcher from '../generated-artifacts/MixinAssetProxyDispatcher.json'; | ||||
| import * as MixinAuthorizable from '../generated-artifacts/MixinAuthorizable.json'; | ||||
| import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json'; | ||||
| import * as Ownable from '../generated-artifacts/Ownable.json'; | ||||
| import * as StaticCallProxy from '../generated-artifacts/StaticCallProxy.json'; | ||||
| import * as TestChaiBridge from '../generated-artifacts/TestChaiBridge.json'; | ||||
| import * as TestDydxBridge from '../generated-artifacts/TestDydxBridge.json'; | ||||
| import * as TestERC20Bridge from '../generated-artifacts/TestERC20Bridge.json'; | ||||
| import * as TestEth2DaiBridge from '../generated-artifacts/TestEth2DaiBridge.json'; | ||||
| import * as TestKyberBridge from '../generated-artifacts/TestKyberBridge.json'; | ||||
| import * as TestStaticCallTarget from '../generated-artifacts/TestStaticCallTarget.json'; | ||||
| import * as TestUniswapBridge from '../generated-artifacts/TestUniswapBridge.json'; | ||||
| import * as UniswapBridge from '../generated-artifacts/UniswapBridge.json'; | ||||
| export const artifacts = { | ||||
|     MixinAssetProxyDispatcher: MixinAssetProxyDispatcher as ContractArtifact, | ||||
|     MixinAuthorizable: MixinAuthorizable as ContractArtifact, | ||||
|     Ownable: Ownable as ContractArtifact, | ||||
|     ERC1155Proxy: ERC1155Proxy as ContractArtifact, | ||||
|     ERC20BridgeProxy: ERC20BridgeProxy as ContractArtifact, | ||||
|     ERC20Proxy: ERC20Proxy as ContractArtifact, | ||||
|     ERC721Proxy: ERC721Proxy as ContractArtifact, | ||||
|     MultiAssetProxy: MultiAssetProxy as ContractArtifact, | ||||
|     StaticCallProxy: StaticCallProxy as ContractArtifact, | ||||
|     ERC20BridgeProxy: ERC20BridgeProxy as ContractArtifact, | ||||
|     ChaiBridge: ChaiBridge as ContractArtifact, | ||||
|     DydxBridge: DydxBridge as ContractArtifact, | ||||
|     Eth2DaiBridge: Eth2DaiBridge as ContractArtifact, | ||||
|     KyberBridge: KyberBridge as ContractArtifact, | ||||
|     UniswapBridge: UniswapBridge as ContractArtifact, | ||||
|     IAssetData: IAssetData as ContractArtifact, | ||||
|     IAssetProxy: IAssetProxy as ContractArtifact, | ||||
|     UniswapBridge: UniswapBridge as ContractArtifact, | ||||
|     KyberBridge: KyberBridge as ContractArtifact, | ||||
|     ChaiBridge: ChaiBridge as ContractArtifact, | ||||
|     IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact, | ||||
|     IAuthorizable: IAuthorizable as ContractArtifact, | ||||
|     IChai: IChai as ContractArtifact, | ||||
|     IDydx: IDydx as ContractArtifact, | ||||
|     IDydxBridge: IDydxBridge as ContractArtifact, | ||||
|     IERC20Bridge: IERC20Bridge as ContractArtifact, | ||||
|     IEth2Dai: IEth2Dai as ContractArtifact, | ||||
|     IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact, | ||||
|     IUniswapExchange: IUniswapExchange as ContractArtifact, | ||||
|     IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact, | ||||
|     TestChaiBridge: TestChaiBridge as ContractArtifact, | ||||
|     TestDydxBridge: TestDydxBridge as ContractArtifact, | ||||
|     TestERC20Bridge: TestERC20Bridge as ContractArtifact, | ||||
|     TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact, | ||||
|     TestKyberBridge: TestKyberBridge as ContractArtifact, | ||||
|     TestStaticCallTarget: TestStaticCallTarget as ContractArtifact, | ||||
|     TestUniswapBridge: TestUniswapBridge as ContractArtifact, | ||||
| }; | ||||
|   | ||||
							
								
								
									
										40
									
								
								contracts/asset-proxy/src/dydx_bridge_encoder.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								contracts/asset-proxy/src/dydx_bridge_encoder.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| import { AbiEncoder, BigNumber } from '@0x/utils'; | ||||
|  | ||||
| export enum DydxBridgeActionType { | ||||
|     Deposit, | ||||
|     Withdraw, | ||||
| } | ||||
|  | ||||
| export interface DydxBrigeAction { | ||||
|     actionType: DydxBridgeActionType; | ||||
|     accountId: BigNumber; | ||||
|     marketId: BigNumber; | ||||
|     conversionRateNumerator: BigNumber; | ||||
|     conversionRateDenominator: BigNumber; | ||||
| } | ||||
|  | ||||
| export interface DydxBridgeData { | ||||
|     accountNumbers: BigNumber[]; | ||||
|     actions: DydxBrigeAction[]; | ||||
| } | ||||
|  | ||||
| export const dydxBridgeDataEncoder = AbiEncoder.create([ | ||||
|     { | ||||
|         name: 'bridgeData', | ||||
|         type: 'tuple', | ||||
|         components: [ | ||||
|             { name: 'accountNumbers', type: 'uint256[]' }, | ||||
|             { | ||||
|                 name: 'actions', | ||||
|                 type: 'tuple[]', | ||||
|                 components: [ | ||||
|                     { name: 'actionType', type: 'uint8' }, | ||||
|                     { name: 'accountId', type: 'uint256' }, | ||||
|                     { name: 'marketId', type: 'uint256' }, | ||||
|                     { name: 'conversionRateNumerator', type: 'uint256' }, | ||||
|                     { name: 'conversionRateDenominator', type: 'uint256' }, | ||||
|                 ], | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
| ]); | ||||
| @@ -60,7 +60,7 @@ export class ERC1155ProxyWrapper { | ||||
|                 txDefaults, | ||||
|                 artifacts, | ||||
|             ); | ||||
|             const erc1155Wrapper = new Erc1155Wrapper(erc1155Contract, this._provider, this._contractOwnerAddress); | ||||
|             const erc1155Wrapper = new Erc1155Wrapper(erc1155Contract, this._contractOwnerAddress); | ||||
|             this._dummyTokenWrappers.push(erc1155Wrapper); | ||||
|         } | ||||
|         return this._dummyTokenWrappers; | ||||
|   | ||||
| @@ -5,12 +5,17 @@ export { | ||||
|     ERC20ProxyContract, | ||||
|     ERC721ProxyContract, | ||||
|     Eth2DaiBridgeContract, | ||||
|     DydxBridgeContract, | ||||
|     TestDydxBridgeContract, | ||||
|     IAssetDataContract, | ||||
|     IAssetProxyContract, | ||||
|     MultiAssetProxyContract, | ||||
|     StaticCallProxyContract, | ||||
|     TestStaticCallTargetContract, | ||||
|     UniswapBridgeContract, | ||||
|     KyberBridgeContract, | ||||
|     ChaiBridgeContract, | ||||
|     IChaiContract, | ||||
| } from './wrappers'; | ||||
|  | ||||
| export { ERC20Wrapper } from './erc20_wrapper'; | ||||
| @@ -62,3 +67,4 @@ export { | ||||
|     TupleDataItem, | ||||
|     StateMutability, | ||||
| } from 'ethereum-types'; | ||||
| export * from './dydx_bridge_encoder'; | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|  * ----------------------------------------------------------------------------- | ||||
|  */ | ||||
| export * from '../generated-wrappers/chai_bridge'; | ||||
| export * from '../generated-wrappers/dydx_bridge'; | ||||
| export * from '../generated-wrappers/erc1155_proxy'; | ||||
| export * from '../generated-wrappers/erc20_bridge_proxy'; | ||||
| export * from '../generated-wrappers/erc20_proxy'; | ||||
| @@ -11,8 +12,27 @@ export * from '../generated-wrappers/erc721_proxy'; | ||||
| export * from '../generated-wrappers/eth2_dai_bridge'; | ||||
| export * from '../generated-wrappers/i_asset_data'; | ||||
| export * from '../generated-wrappers/i_asset_proxy'; | ||||
| export * from '../generated-wrappers/i_asset_proxy_dispatcher'; | ||||
| export * from '../generated-wrappers/i_authorizable'; | ||||
| export * from '../generated-wrappers/i_chai'; | ||||
| export * from '../generated-wrappers/i_dydx'; | ||||
| export * from '../generated-wrappers/i_dydx_bridge'; | ||||
| export * from '../generated-wrappers/i_erc20_bridge'; | ||||
| export * from '../generated-wrappers/i_eth2_dai'; | ||||
| export * from '../generated-wrappers/i_kyber_network_proxy'; | ||||
| export * from '../generated-wrappers/i_uniswap_exchange'; | ||||
| export * from '../generated-wrappers/i_uniswap_exchange_factory'; | ||||
| export * from '../generated-wrappers/kyber_bridge'; | ||||
| export * from '../generated-wrappers/mixin_asset_proxy_dispatcher'; | ||||
| export * from '../generated-wrappers/mixin_authorizable'; | ||||
| export * from '../generated-wrappers/multi_asset_proxy'; | ||||
| export * from '../generated-wrappers/ownable'; | ||||
| export * from '../generated-wrappers/static_call_proxy'; | ||||
| export * from '../generated-wrappers/test_chai_bridge'; | ||||
| export * from '../generated-wrappers/test_dydx_bridge'; | ||||
| export * from '../generated-wrappers/test_erc20_bridge'; | ||||
| export * from '../generated-wrappers/test_eth2_dai_bridge'; | ||||
| export * from '../generated-wrappers/test_kyber_bridge'; | ||||
| export * from '../generated-wrappers/test_static_call_target'; | ||||
| export * from '../generated-wrappers/test_uniswap_bridge'; | ||||
| export * from '../generated-wrappers/uniswap_bridge'; | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| import { ContractArtifact } from 'ethereum-types'; | ||||
|  | ||||
| import * as ChaiBridge from '../test/generated-artifacts/ChaiBridge.json'; | ||||
| import * as DydxBridge from '../test/generated-artifacts/DydxBridge.json'; | ||||
| import * as ERC1155Proxy from '../test/generated-artifacts/ERC1155Proxy.json'; | ||||
| import * as ERC20BridgeProxy from '../test/generated-artifacts/ERC20BridgeProxy.json'; | ||||
| import * as ERC20Proxy from '../test/generated-artifacts/ERC20Proxy.json'; | ||||
| @@ -16,6 +17,8 @@ import * as IAssetProxy from '../test/generated-artifacts/IAssetProxy.json'; | ||||
| import * as IAssetProxyDispatcher from '../test/generated-artifacts/IAssetProxyDispatcher.json'; | ||||
| import * as IAuthorizable from '../test/generated-artifacts/IAuthorizable.json'; | ||||
| import * as IChai from '../test/generated-artifacts/IChai.json'; | ||||
| import * as IDydx from '../test/generated-artifacts/IDydx.json'; | ||||
| import * as IDydxBridge from '../test/generated-artifacts/IDydxBridge.json'; | ||||
| import * as IERC20Bridge from '../test/generated-artifacts/IERC20Bridge.json'; | ||||
| import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json'; | ||||
| import * as IKyberNetworkProxy from '../test/generated-artifacts/IKyberNetworkProxy.json'; | ||||
| @@ -28,6 +31,7 @@ import * as MultiAssetProxy from '../test/generated-artifacts/MultiAssetProxy.js | ||||
| import * as Ownable from '../test/generated-artifacts/Ownable.json'; | ||||
| import * as StaticCallProxy from '../test/generated-artifacts/StaticCallProxy.json'; | ||||
| import * as TestChaiBridge from '../test/generated-artifacts/TestChaiBridge.json'; | ||||
| import * as TestDydxBridge from '../test/generated-artifacts/TestDydxBridge.json'; | ||||
| import * as TestERC20Bridge from '../test/generated-artifacts/TestERC20Bridge.json'; | ||||
| import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json'; | ||||
| import * as TestKyberBridge from '../test/generated-artifacts/TestKyberBridge.json'; | ||||
| @@ -45,6 +49,7 @@ export const artifacts = { | ||||
|     MultiAssetProxy: MultiAssetProxy as ContractArtifact, | ||||
|     StaticCallProxy: StaticCallProxy as ContractArtifact, | ||||
|     ChaiBridge: ChaiBridge as ContractArtifact, | ||||
|     DydxBridge: DydxBridge as ContractArtifact, | ||||
|     Eth2DaiBridge: Eth2DaiBridge as ContractArtifact, | ||||
|     KyberBridge: KyberBridge as ContractArtifact, | ||||
|     UniswapBridge: UniswapBridge as ContractArtifact, | ||||
| @@ -53,12 +58,15 @@ export const artifacts = { | ||||
|     IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact, | ||||
|     IAuthorizable: IAuthorizable as ContractArtifact, | ||||
|     IChai: IChai as ContractArtifact, | ||||
|     IDydx: IDydx as ContractArtifact, | ||||
|     IDydxBridge: IDydxBridge as ContractArtifact, | ||||
|     IERC20Bridge: IERC20Bridge as ContractArtifact, | ||||
|     IEth2Dai: IEth2Dai as ContractArtifact, | ||||
|     IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact, | ||||
|     IUniswapExchange: IUniswapExchange as ContractArtifact, | ||||
|     IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact, | ||||
|     TestChaiBridge: TestChaiBridge as ContractArtifact, | ||||
|     TestDydxBridge: TestDydxBridge as ContractArtifact, | ||||
|     TestERC20Bridge: TestERC20Bridge as ContractArtifact, | ||||
|     TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact, | ||||
|     TestKyberBridge: TestKyberBridge as ContractArtifact, | ||||
|   | ||||
							
								
								
									
										399
									
								
								contracts/asset-proxy/test/dydx_bridge.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										399
									
								
								contracts/asset-proxy/test/dydx_bridge.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,399 @@ | ||||
| import { LibMathRevertErrors } from '@0x/contracts-exchange-libs'; | ||||
| import { blockchainTests, constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils'; | ||||
| import { AssetProxyId, RevertReason } from '@0x/types'; | ||||
| import { BigNumber } from '@0x/utils'; | ||||
| import * as _ from 'lodash'; | ||||
|  | ||||
| import { DydxBridgeActionType, DydxBridgeData, dydxBridgeDataEncoder } from '../src/dydx_bridge_encoder'; | ||||
| import { ERC20BridgeProxyContract, IAssetDataContract } from '../src/wrappers'; | ||||
|  | ||||
| import { artifacts } from './artifacts'; | ||||
| import { TestDydxBridgeContract, TestDydxBridgeEvents } from './wrappers'; | ||||
|  | ||||
| blockchainTests.resets('DydxBridge unit tests', env => { | ||||
|     const defaultAccountNumber = new BigNumber(1); | ||||
|     const marketId = new BigNumber(2); | ||||
|     const defaultAmount = new BigNumber(4); | ||||
|     const notAuthorized = '0x0000000000000000000000000000000000000001'; | ||||
|     const defaultDepositAction = { | ||||
|         actionType: DydxBridgeActionType.Deposit, | ||||
|         accountId: constants.ZERO_AMOUNT, | ||||
|         marketId, | ||||
|         conversionRateNumerator: constants.ZERO_AMOUNT, | ||||
|         conversionRateDenominator: constants.ZERO_AMOUNT, | ||||
|     }; | ||||
|     const defaultWithdrawAction = { | ||||
|         actionType: DydxBridgeActionType.Withdraw, | ||||
|         accountId: constants.ZERO_AMOUNT, | ||||
|         marketId, | ||||
|         conversionRateNumerator: constants.ZERO_AMOUNT, | ||||
|         conversionRateDenominator: constants.ZERO_AMOUNT, | ||||
|     }; | ||||
|     let testContract: TestDydxBridgeContract; | ||||
|     let testProxyContract: ERC20BridgeProxyContract; | ||||
|     let assetDataEncoder: IAssetDataContract; | ||||
|     let owner: string; | ||||
|     let authorized: string; | ||||
|     let accountOwner: string; | ||||
|     let receiver: string; | ||||
|  | ||||
|     before(async () => { | ||||
|         // Get accounts | ||||
|         const accounts = await env.web3Wrapper.getAvailableAddressesAsync(); | ||||
|         [owner, authorized, accountOwner, receiver] = accounts; | ||||
|  | ||||
|         // Deploy dydx bridge | ||||
|         testContract = await TestDydxBridgeContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.TestDydxBridge, | ||||
|             env.provider, | ||||
|             env.txDefaults, | ||||
|             artifacts, | ||||
|             [accountOwner, receiver], | ||||
|         ); | ||||
|  | ||||
|         // Deploy test erc20 bridge proxy | ||||
|         testProxyContract = await ERC20BridgeProxyContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.ERC20BridgeProxy, | ||||
|             env.provider, | ||||
|             env.txDefaults, | ||||
|             artifacts, | ||||
|         ); | ||||
|         await testProxyContract.addAuthorizedAddress(authorized).awaitTransactionSuccessAsync({ from: owner }); | ||||
|  | ||||
|         // Setup asset data encoder | ||||
|         assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider); | ||||
|     }); | ||||
|  | ||||
|     describe('bridgeTransferFrom()', () => { | ||||
|         const callBridgeTransferFrom = async ( | ||||
|             from: string, | ||||
|             to: string, | ||||
|             amount: BigNumber, | ||||
|             bridgeData: DydxBridgeData, | ||||
|             sender: string, | ||||
|         ): Promise<string> => { | ||||
|             const returnValue = await testContract | ||||
|                 .bridgeTransferFrom( | ||||
|                     constants.NULL_ADDRESS, | ||||
|                     from, | ||||
|                     to, | ||||
|                     amount, | ||||
|                     dydxBridgeDataEncoder.encode({ bridgeData }), | ||||
|                 ) | ||||
|                 .callAsync({ from: sender }); | ||||
|             return returnValue; | ||||
|         }; | ||||
|         const executeBridgeTransferFromAndVerifyEvents = async ( | ||||
|             from: string, | ||||
|             to: string, | ||||
|             amount: BigNumber, | ||||
|             bridgeData: DydxBridgeData, | ||||
|             sender: string, | ||||
|         ): Promise<void> => { | ||||
|             // Execute transaction. | ||||
|             const txReceipt = await testContract | ||||
|                 .bridgeTransferFrom( | ||||
|                     constants.NULL_ADDRESS, | ||||
|                     from, | ||||
|                     to, | ||||
|                     amount, | ||||
|                     dydxBridgeDataEncoder.encode({ bridgeData }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ from: sender }); | ||||
|  | ||||
|             // Verify `OperateAccount` event. | ||||
|             const expectedOperateAccountEvents = []; | ||||
|             for (const accountNumber of bridgeData.accountNumbers) { | ||||
|                 expectedOperateAccountEvents.push({ | ||||
|                     owner: accountOwner, | ||||
|                     number: accountNumber, | ||||
|                 }); | ||||
|             } | ||||
|             verifyEventsFromLogs(txReceipt.logs, expectedOperateAccountEvents, TestDydxBridgeEvents.OperateAccount); | ||||
|  | ||||
|             // Verify `OperateAction` event. | ||||
|             const weiDenomination = 0; | ||||
|             const deltaAmountRef = 0; | ||||
|             const expectedOperateActionEvents = []; | ||||
|             for (const action of bridgeData.actions) { | ||||
|                 expectedOperateActionEvents.push({ | ||||
|                     actionType: action.actionType as number, | ||||
|                     accountId: action.accountId, | ||||
|                     amountSign: action.actionType === DydxBridgeActionType.Deposit ? true : false, | ||||
|                     amountDenomination: weiDenomination, | ||||
|                     amountRef: deltaAmountRef, | ||||
|                     amountValue: action.conversionRateDenominator.gt(0) | ||||
|                         ? amount | ||||
|                               .times(action.conversionRateNumerator) | ||||
|                               .dividedToIntegerBy(action.conversionRateDenominator) | ||||
|                         : amount, | ||||
|                     primaryMarketId: marketId, | ||||
|                     secondaryMarketId: constants.ZERO_AMOUNT, | ||||
|                     otherAddress: action.actionType === DydxBridgeActionType.Deposit ? from : to, | ||||
|                     otherAccountId: constants.ZERO_AMOUNT, | ||||
|                     data: '0x', | ||||
|                 }); | ||||
|             } | ||||
|             verifyEventsFromLogs(txReceipt.logs, expectedOperateActionEvents, TestDydxBridgeEvents.OperateAction); | ||||
|         }; | ||||
|         it('succeeds when calling with zero amount', async () => { | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [defaultAccountNumber], | ||||
|                 actions: [defaultDepositAction], | ||||
|             }; | ||||
|             await executeBridgeTransferFromAndVerifyEvents( | ||||
|                 accountOwner, | ||||
|                 receiver, | ||||
|                 constants.ZERO_AMOUNT, | ||||
|                 bridgeData, | ||||
|                 authorized, | ||||
|             ); | ||||
|         }); | ||||
|         it('succeeds when calling with no accounts', async () => { | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [], | ||||
|                 actions: [defaultDepositAction], | ||||
|             }; | ||||
|             await executeBridgeTransferFromAndVerifyEvents( | ||||
|                 accountOwner, | ||||
|                 receiver, | ||||
|                 defaultAmount, | ||||
|                 bridgeData, | ||||
|                 authorized, | ||||
|             ); | ||||
|         }); | ||||
|         it('succeeds when calling with no actions', async () => { | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [defaultAccountNumber], | ||||
|                 actions: [], | ||||
|             }; | ||||
|             await executeBridgeTransferFromAndVerifyEvents( | ||||
|                 accountOwner, | ||||
|                 receiver, | ||||
|                 defaultAmount, | ||||
|                 bridgeData, | ||||
|                 authorized, | ||||
|             ); | ||||
|         }); | ||||
|         it('succeeds when calling `operate` with the `deposit` action and a single account', async () => { | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [defaultAccountNumber], | ||||
|                 actions: [defaultDepositAction], | ||||
|             }; | ||||
|             await executeBridgeTransferFromAndVerifyEvents( | ||||
|                 accountOwner, | ||||
|                 receiver, | ||||
|                 defaultAmount, | ||||
|                 bridgeData, | ||||
|                 authorized, | ||||
|             ); | ||||
|         }); | ||||
|         it('succeeds when calling `operate` with the `deposit` action and multiple accounts', async () => { | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)], | ||||
|                 actions: [defaultDepositAction], | ||||
|             }; | ||||
|             await executeBridgeTransferFromAndVerifyEvents( | ||||
|                 accountOwner, | ||||
|                 receiver, | ||||
|                 defaultAmount, | ||||
|                 bridgeData, | ||||
|                 authorized, | ||||
|             ); | ||||
|         }); | ||||
|         it('succeeds when calling `operate` with the `withdraw` action and a single account', async () => { | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [defaultAccountNumber], | ||||
|                 actions: [defaultWithdrawAction], | ||||
|             }; | ||||
|             await executeBridgeTransferFromAndVerifyEvents( | ||||
|                 accountOwner, | ||||
|                 receiver, | ||||
|                 defaultAmount, | ||||
|                 bridgeData, | ||||
|                 authorized, | ||||
|             ); | ||||
|         }); | ||||
|         it('succeeds when calling `operate` with the `withdraw` action and multiple accounts', async () => { | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)], | ||||
|                 actions: [defaultWithdrawAction], | ||||
|             }; | ||||
|             await executeBridgeTransferFromAndVerifyEvents( | ||||
|                 accountOwner, | ||||
|                 receiver, | ||||
|                 defaultAmount, | ||||
|                 bridgeData, | ||||
|                 authorized, | ||||
|             ); | ||||
|         }); | ||||
|         it('succeeds when calling `operate` with the `deposit` action and multiple accounts', async () => { | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)], | ||||
|                 actions: [defaultWithdrawAction, defaultDepositAction], | ||||
|             }; | ||||
|             await executeBridgeTransferFromAndVerifyEvents( | ||||
|                 accountOwner, | ||||
|                 receiver, | ||||
|                 defaultAmount, | ||||
|                 bridgeData, | ||||
|                 authorized, | ||||
|             ); | ||||
|         }); | ||||
|         it('succeeds when calling `operate` with multiple actions under a single account', async () => { | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [defaultAccountNumber], | ||||
|                 actions: [defaultWithdrawAction, defaultDepositAction], | ||||
|             }; | ||||
|             await executeBridgeTransferFromAndVerifyEvents( | ||||
|                 accountOwner, | ||||
|                 receiver, | ||||
|                 defaultAmount, | ||||
|                 bridgeData, | ||||
|                 authorized, | ||||
|             ); | ||||
|         }); | ||||
|         it('succeeds when scaling the `amount` to deposit', async () => { | ||||
|             const conversionRateNumerator = new BigNumber(1); | ||||
|             const conversionRateDenominator = new BigNumber(2); | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [defaultAccountNumber], | ||||
|                 actions: [ | ||||
|                     defaultWithdrawAction, | ||||
|                     { | ||||
|                         ...defaultDepositAction, | ||||
|                         conversionRateNumerator, | ||||
|                         conversionRateDenominator, | ||||
|                     }, | ||||
|                 ], | ||||
|             }; | ||||
|             await executeBridgeTransferFromAndVerifyEvents( | ||||
|                 accountOwner, | ||||
|                 receiver, | ||||
|                 defaultAmount, | ||||
|                 bridgeData, | ||||
|                 authorized, | ||||
|             ); | ||||
|         }); | ||||
|         it('succeeds when scaling the `amount` to withdraw', async () => { | ||||
|             const conversionRateNumerator = new BigNumber(1); | ||||
|             const conversionRateDenominator = new BigNumber(2); | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [defaultAccountNumber], | ||||
|                 actions: [ | ||||
|                     defaultDepositAction, | ||||
|                     { | ||||
|                         ...defaultWithdrawAction, | ||||
|                         conversionRateNumerator, | ||||
|                         conversionRateDenominator, | ||||
|                     }, | ||||
|                 ], | ||||
|             }; | ||||
|             await executeBridgeTransferFromAndVerifyEvents( | ||||
|                 accountOwner, | ||||
|                 receiver, | ||||
|                 defaultAmount, | ||||
|                 bridgeData, | ||||
|                 authorized, | ||||
|             ); | ||||
|         }); | ||||
|         it('reverts if not called by the ERC20 Bridge Proxy', async () => { | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [defaultAccountNumber], | ||||
|                 actions: [defaultDepositAction], | ||||
|             }; | ||||
|             const callBridgeTransferFromPromise = callBridgeTransferFrom( | ||||
|                 accountOwner, | ||||
|                 receiver, | ||||
|                 defaultAmount, | ||||
|                 bridgeData, | ||||
|                 notAuthorized, | ||||
|             ); | ||||
|             const expectedError = RevertReason.DydxBridgeOnlyCallableByErc20BridgeProxy; | ||||
|             return expect(callBridgeTransferFromPromise).to.revertWith(expectedError); | ||||
|         }); | ||||
|         it('should return magic bytes if call succeeds', async () => { | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [defaultAccountNumber], | ||||
|                 actions: [defaultDepositAction], | ||||
|             }; | ||||
|             const returnValue = await callBridgeTransferFrom( | ||||
|                 accountOwner, | ||||
|                 receiver, | ||||
|                 defaultAmount, | ||||
|                 bridgeData, | ||||
|                 authorized, | ||||
|             ); | ||||
|             expect(returnValue).to.equal(AssetProxyId.ERC20Bridge); | ||||
|         }); | ||||
|         it('should revert when `Operate` reverts', async () => { | ||||
|             // Set revert flag. | ||||
|             await testContract.setRevertOnOperate(true).awaitTransactionSuccessAsync(); | ||||
|  | ||||
|             // Execute transfer. | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [defaultAccountNumber], | ||||
|                 actions: [defaultDepositAction], | ||||
|             }; | ||||
|             const tx = callBridgeTransferFrom(accountOwner, receiver, defaultAmount, bridgeData, authorized); | ||||
|             const expectedError = 'TestDydxBridge/SHOULD_REVERT_ON_OPERATE'; | ||||
|             return expect(tx).to.revertWith(expectedError); | ||||
|         }); | ||||
|         it('should revert when there is a rounding error', async () => { | ||||
|             // Setup a rounding error | ||||
|             const conversionRateNumerator = new BigNumber(5318); | ||||
|             const conversionRateDenominator = new BigNumber(47958); | ||||
|             const amount = new BigNumber(9000); | ||||
|             const bridgeData = { | ||||
|                 accountNumbers: [defaultAccountNumber], | ||||
|                 actions: [ | ||||
|                     defaultDepositAction, | ||||
|                     { | ||||
|                         ...defaultWithdrawAction, | ||||
|                         conversionRateNumerator, | ||||
|                         conversionRateDenominator, | ||||
|                     }, | ||||
|                 ], | ||||
|             }; | ||||
|  | ||||
|             // Execute transfer and assert error. | ||||
|             const tx = callBridgeTransferFrom(accountOwner, receiver, amount, bridgeData, authorized); | ||||
|             const expectedError = new LibMathRevertErrors.RoundingError( | ||||
|                 conversionRateNumerator, | ||||
|                 conversionRateDenominator, | ||||
|                 amount, | ||||
|             ); | ||||
|             return expect(tx).to.revertWith(expectedError); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('ERC20BridgeProxy.transferFrom()', () => { | ||||
|         const bridgeData = { | ||||
|             accountNumbers: [defaultAccountNumber], | ||||
|             actions: [defaultWithdrawAction], | ||||
|         }; | ||||
|         let assetData: string; | ||||
|  | ||||
|         before(async () => { | ||||
|             const testTokenAddress = await testContract.getTestToken().callAsync(); | ||||
|             assetData = assetDataEncoder | ||||
|                 .ERC20Bridge(testTokenAddress, testContract.address, dydxBridgeDataEncoder.encode({ bridgeData })) | ||||
|                 .getABIEncodedTransactionData(); | ||||
|         }); | ||||
|  | ||||
|         it('should succeed if `bridgeTransferFrom` succeeds', async () => { | ||||
|             await testProxyContract | ||||
|                 .transferFrom(assetData, accountOwner, receiver, defaultAmount) | ||||
|                 .awaitTransactionSuccessAsync({ from: authorized }); | ||||
|         }); | ||||
|         it('should revert if `bridgeTransferFrom` reverts', async () => { | ||||
|             // Set revert flag. | ||||
|             await testContract.setRevertOnOperate(true).awaitTransactionSuccessAsync(); | ||||
|             const tx = testProxyContract | ||||
|                 .transferFrom(assetData, accountOwner, receiver, defaultAmount) | ||||
|                 .awaitTransactionSuccessAsync({ from: authorized }); | ||||
|             const expectedError = 'TestDydxBridge/SHOULD_REVERT_ON_OPERATE'; | ||||
|             return expect(tx).to.revertWith(expectedError); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -3,6 +3,7 @@ import { | ||||
|     constants, | ||||
|     expect, | ||||
|     getRandomInteger, | ||||
|     getRandomPortion, | ||||
|     randomAddress, | ||||
|     verifyEventsFromLogs, | ||||
| } from '@0x/contracts-test-utils'; | ||||
| @@ -17,6 +18,12 @@ import { TestKyberBridgeContract, TestKyberBridgeEvents } from './wrappers'; | ||||
|  | ||||
| blockchainTests.resets('KyberBridge unit tests', env => { | ||||
|     const KYBER_ETH_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; | ||||
|     const FROM_TOKEN_DECIMALS = 6; | ||||
|     const TO_TOKEN_DECIMALS = 18; | ||||
|     const FROM_TOKEN_BASE = new BigNumber(10).pow(FROM_TOKEN_DECIMALS); | ||||
|     const TO_TOKEN_BASE = new BigNumber(10).pow(TO_TOKEN_DECIMALS); | ||||
|     const WETH_BASE = new BigNumber(10).pow(18); | ||||
|     const KYBER_RATE_BASE = WETH_BASE; | ||||
|     let testContract: TestKyberBridgeContract; | ||||
|  | ||||
|     before(async () => { | ||||
| @@ -45,10 +52,10 @@ blockchainTests.resets('KyberBridge unit tests', env => { | ||||
|  | ||||
|         before(async () => { | ||||
|             wethAddress = await testContract.weth().callAsync(); | ||||
|             fromTokenAddress = await testContract.createToken().callAsync(); | ||||
|             await testContract.createToken().awaitTransactionSuccessAsync(); | ||||
|             toTokenAddress = await testContract.createToken().callAsync(); | ||||
|             await testContract.createToken().awaitTransactionSuccessAsync(); | ||||
|             fromTokenAddress = await testContract.createToken(FROM_TOKEN_DECIMALS).callAsync(); | ||||
|             await testContract.createToken(FROM_TOKEN_DECIMALS).awaitTransactionSuccessAsync(); | ||||
|             toTokenAddress = await testContract.createToken(TO_TOKEN_DECIMALS).callAsync(); | ||||
|             await testContract.createToken(TO_TOKEN_DECIMALS).awaitTransactionSuccessAsync(); | ||||
|         }); | ||||
|  | ||||
|         const STATIC_KYBER_TRADE_ARGS = { | ||||
| @@ -75,13 +82,14 @@ blockchainTests.resets('KyberBridge unit tests', env => { | ||||
|         } | ||||
|  | ||||
|         function createTransferFromOpts(opts?: Partial<TransferFromOpts>): TransferFromOpts { | ||||
|             const amount = getRandomInteger(1, TO_TOKEN_BASE.times(100)); | ||||
|             return { | ||||
|                 fromTokenAddress, | ||||
|                 toTokenAddress, | ||||
|                 amount, | ||||
|                 toAddress: randomAddress(), | ||||
|                 amount: getRandomInteger(1, 10e18), | ||||
|                 fillAmount: getRandomInteger(1, 10e18), | ||||
|                 fromTokenBalance: getRandomInteger(1, 10e18), | ||||
|                 fillAmount: getRandomPortion(amount), | ||||
|                 fromTokenBalance: getRandomInteger(1, FROM_TOKEN_BASE.times(100)), | ||||
|                 ...opts, | ||||
|             }; | ||||
|         } | ||||
| @@ -119,9 +127,12 @@ blockchainTests.resets('KyberBridge unit tests', env => { | ||||
|         } | ||||
|  | ||||
|         function getMinimumConversionRate(opts: TransferFromOpts): BigNumber { | ||||
|             const fromBase = opts.fromTokenAddress === wethAddress ? WETH_BASE : FROM_TOKEN_BASE; | ||||
|             const toBase = opts.toTokenAddress === wethAddress ? WETH_BASE : TO_TOKEN_BASE; | ||||
|             return opts.amount | ||||
|                 .times(constants.ONE_ETHER) | ||||
|                 .div(opts.fromTokenBalance) | ||||
|                 .div(toBase) | ||||
|                 .div(opts.fromTokenBalance.div(fromBase)) | ||||
|                 .times(KYBER_RATE_BASE) | ||||
|                 .integerValue(BigNumber.ROUND_DOWN); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -176,7 +176,7 @@ blockchainTests.resets('UniswapBridge unit tests', env => { | ||||
|                 expect(calls[0].exchange).to.eq(exchangeAddress); | ||||
|                 expect(calls[0].tokensSold).to.bignumber.eq(opts.fromTokenBalance); | ||||
|                 expect(calls[0].minTokensBought).to.bignumber.eq(opts.amount); | ||||
|                 expect(calls[0].minEthBought).to.bignumber.eq(0); | ||||
|                 expect(calls[0].minEthBought).to.bignumber.eq(1); | ||||
|                 expect(calls[0].deadline).to.bignumber.eq(blockTime); | ||||
|                 expect(calls[0].recipient).to.eq(opts.toAddress); | ||||
|                 expect(calls[0].toTokenAddress).to.eq(opts.toTokenAddress); | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|  * ----------------------------------------------------------------------------- | ||||
|  */ | ||||
| export * from '../test/generated-wrappers/chai_bridge'; | ||||
| export * from '../test/generated-wrappers/dydx_bridge'; | ||||
| export * from '../test/generated-wrappers/erc1155_proxy'; | ||||
| export * from '../test/generated-wrappers/erc20_bridge_proxy'; | ||||
| export * from '../test/generated-wrappers/erc20_proxy'; | ||||
| @@ -14,6 +15,8 @@ export * from '../test/generated-wrappers/i_asset_proxy'; | ||||
| export * from '../test/generated-wrappers/i_asset_proxy_dispatcher'; | ||||
| export * from '../test/generated-wrappers/i_authorizable'; | ||||
| export * from '../test/generated-wrappers/i_chai'; | ||||
| export * from '../test/generated-wrappers/i_dydx'; | ||||
| export * from '../test/generated-wrappers/i_dydx_bridge'; | ||||
| export * from '../test/generated-wrappers/i_erc20_bridge'; | ||||
| export * from '../test/generated-wrappers/i_eth2_dai'; | ||||
| export * from '../test/generated-wrappers/i_kyber_network_proxy'; | ||||
| @@ -26,6 +29,7 @@ export * from '../test/generated-wrappers/multi_asset_proxy'; | ||||
| export * from '../test/generated-wrappers/ownable'; | ||||
| export * from '../test/generated-wrappers/static_call_proxy'; | ||||
| export * from '../test/generated-wrappers/test_chai_bridge'; | ||||
| export * from '../test/generated-wrappers/test_dydx_bridge'; | ||||
| export * from '../test/generated-wrappers/test_erc20_bridge'; | ||||
| export * from '../test/generated-wrappers/test_eth2_dai_bridge'; | ||||
| export * from '../test/generated-wrappers/test_kyber_bridge'; | ||||
|   | ||||
| @@ -84,7 +84,7 @@ module.exports = { | ||||
|         solc: { | ||||
|             version: '0.5.9', | ||||
|             settings: { | ||||
|                 evmVersion: 'constantinople', | ||||
|                 evmVersion: 'istanbul', | ||||
|                 optimizer: { | ||||
|                     enabled: true, | ||||
|                     runs: 1000000, | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|     "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], | ||||
|     "files": [ | ||||
|         "generated-artifacts/ChaiBridge.json", | ||||
|         "generated-artifacts/DydxBridge.json", | ||||
|         "generated-artifacts/ERC1155Proxy.json", | ||||
|         "generated-artifacts/ERC20BridgeProxy.json", | ||||
|         "generated-artifacts/ERC20Proxy.json", | ||||
| @@ -11,12 +12,32 @@ | ||||
|         "generated-artifacts/Eth2DaiBridge.json", | ||||
|         "generated-artifacts/IAssetData.json", | ||||
|         "generated-artifacts/IAssetProxy.json", | ||||
|         "generated-artifacts/IAssetProxyDispatcher.json", | ||||
|         "generated-artifacts/IAuthorizable.json", | ||||
|         "generated-artifacts/IChai.json", | ||||
|         "generated-artifacts/IDydx.json", | ||||
|         "generated-artifacts/IDydxBridge.json", | ||||
|         "generated-artifacts/IERC20Bridge.json", | ||||
|         "generated-artifacts/IEth2Dai.json", | ||||
|         "generated-artifacts/IKyberNetworkProxy.json", | ||||
|         "generated-artifacts/IUniswapExchange.json", | ||||
|         "generated-artifacts/IUniswapExchangeFactory.json", | ||||
|         "generated-artifacts/KyberBridge.json", | ||||
|         "generated-artifacts/MixinAssetProxyDispatcher.json", | ||||
|         "generated-artifacts/MixinAuthorizable.json", | ||||
|         "generated-artifacts/MultiAssetProxy.json", | ||||
|         "generated-artifacts/Ownable.json", | ||||
|         "generated-artifacts/StaticCallProxy.json", | ||||
|         "generated-artifacts/TestChaiBridge.json", | ||||
|         "generated-artifacts/TestDydxBridge.json", | ||||
|         "generated-artifacts/TestERC20Bridge.json", | ||||
|         "generated-artifacts/TestEth2DaiBridge.json", | ||||
|         "generated-artifacts/TestKyberBridge.json", | ||||
|         "generated-artifacts/TestStaticCallTarget.json", | ||||
|         "generated-artifacts/TestUniswapBridge.json", | ||||
|         "generated-artifacts/UniswapBridge.json", | ||||
|         "test/generated-artifacts/ChaiBridge.json", | ||||
|         "test/generated-artifacts/DydxBridge.json", | ||||
|         "test/generated-artifacts/ERC1155Proxy.json", | ||||
|         "test/generated-artifacts/ERC20BridgeProxy.json", | ||||
|         "test/generated-artifacts/ERC20Proxy.json", | ||||
| @@ -27,6 +48,8 @@ | ||||
|         "test/generated-artifacts/IAssetProxyDispatcher.json", | ||||
|         "test/generated-artifacts/IAuthorizable.json", | ||||
|         "test/generated-artifacts/IChai.json", | ||||
|         "test/generated-artifacts/IDydx.json", | ||||
|         "test/generated-artifacts/IDydxBridge.json", | ||||
|         "test/generated-artifacts/IERC20Bridge.json", | ||||
|         "test/generated-artifacts/IEth2Dai.json", | ||||
|         "test/generated-artifacts/IKyberNetworkProxy.json", | ||||
| @@ -39,6 +62,7 @@ | ||||
|         "test/generated-artifacts/Ownable.json", | ||||
|         "test/generated-artifacts/StaticCallProxy.json", | ||||
|         "test/generated-artifacts/TestChaiBridge.json", | ||||
|         "test/generated-artifacts/TestDydxBridge.json", | ||||
|         "test/generated-artifacts/TestERC20Bridge.json", | ||||
|         "test/generated-artifacts/TestEth2DaiBridge.json", | ||||
|         "test/generated-artifacts/TestKyberBridge.json", | ||||
|   | ||||
| @@ -1,4 +1,31 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1579682890, | ||||
|         "version": "3.0.4", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1578272714, | ||||
|         "version": "3.0.3", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1576540892, | ||||
|         "version": "3.0.2", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1575931811, | ||||
|         "version": "3.0.1", | ||||
|   | ||||
| @@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v3.0.4 - _January 22, 2020_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v3.0.3 - _January 6, 2020_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v3.0.2 - _December 17, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v3.0.1 - _December 9, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
|     "contractsDir": "./contracts", | ||||
|     "useDockerisedSolc": false, | ||||
|     "compilerSettings": { | ||||
|         "evmVersion": "constantinople", | ||||
|         "evmVersion": "istanbul", | ||||
|         "optimizer": { | ||||
|             "enabled": true, | ||||
|             "runs": 1000000, | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0x/contracts-coordinator", | ||||
|     "version": "3.0.1", | ||||
|     "version": "3.0.4", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -52,19 +52,19 @@ | ||||
|     }, | ||||
|     "homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md", | ||||
|     "devDependencies": { | ||||
|         "@0x/abi-gen": "^5.0.1", | ||||
|         "@0x/contracts-asset-proxy": "^3.0.1", | ||||
|         "@0x/contracts-dev-utils": "^1.0.1", | ||||
|         "@0x/contracts-erc20": "^3.0.1", | ||||
|         "@0x/contracts-exchange": "^3.0.1", | ||||
|         "@0x/contracts-gen": "^2.0.1", | ||||
|         "@0x/contracts-test-utils": "^5.0.0", | ||||
|         "@0x/dev-utils": "^3.0.1", | ||||
|         "@0x/order-utils": "^10.0.0", | ||||
|         "@0x/sol-compiler": "^4.0.1", | ||||
|         "@0x/abi-gen": "^5.1.0", | ||||
|         "@0x/contracts-asset-proxy": "^3.1.1", | ||||
|         "@0x/contracts-dev-utils": "^1.0.4", | ||||
|         "@0x/contracts-erc20": "^3.0.4", | ||||
|         "@0x/contracts-exchange": "^3.1.0", | ||||
|         "@0x/contracts-gen": "^2.0.4", | ||||
|         "@0x/contracts-test-utils": "^5.1.1", | ||||
|         "@0x/dev-utils": "^3.1.1", | ||||
|         "@0x/order-utils": "^10.1.1", | ||||
|         "@0x/sol-compiler": "^4.0.4", | ||||
|         "@0x/ts-doc-gen": "^0.0.22", | ||||
|         "@0x/tslint-config": "^4.0.0", | ||||
|         "@0x/web3-wrapper": "^7.0.1", | ||||
|         "@0x/web3-wrapper": "^7.0.4", | ||||
|         "@types/lodash": "4.14.104", | ||||
|         "@types/mocha": "^5.2.7", | ||||
|         "@types/node": "*", | ||||
| @@ -84,14 +84,14 @@ | ||||
|         "typescript": "3.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0x/assert": "^3.0.1", | ||||
|         "@0x/base-contract": "^6.0.1", | ||||
|         "@0x/contract-addresses": "^4.0.0", | ||||
|         "@0x/contracts-utils": "^4.0.1", | ||||
|         "@0x/json-schemas": "^5.0.1", | ||||
|         "@0x/types": "^3.1.0", | ||||
|         "@0x/typescript-typings": "^5.0.0", | ||||
|         "@0x/utils": "^5.1.0", | ||||
|         "@0x/assert": "^3.0.4", | ||||
|         "@0x/base-contract": "^6.1.0", | ||||
|         "@0x/contract-addresses": "^4.3.0", | ||||
|         "@0x/contracts-utils": "^4.1.0", | ||||
|         "@0x/json-schemas": "^5.0.4", | ||||
|         "@0x/types": "^3.1.1", | ||||
|         "@0x/typescript-typings": "^5.0.1", | ||||
|         "@0x/utils": "^5.2.0", | ||||
|         "ethereum-types": "^3.0.0", | ||||
|         "http-status-codes": "^1.3.2" | ||||
|     }, | ||||
|   | ||||
| @@ -84,7 +84,7 @@ module.exports = { | ||||
|         solc: { | ||||
|             version: '0.5.9', | ||||
|             settings: { | ||||
|                 evmVersion: 'constantinople', | ||||
|                 evmVersion: 'istanbul', | ||||
|                 optimizer: { | ||||
|                     enabled: true, | ||||
|                     runs: 1000000, | ||||
|   | ||||
| @@ -1,4 +1,32 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1579682890, | ||||
|         "version": "1.0.4", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "version": "1.0.3", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Fixed ERC721 duplicate token ID bug", | ||||
|                 "pr": 2400 | ||||
|             } | ||||
|         ], | ||||
|         "timestamp": 1578272714 | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1576540892, | ||||
|         "version": "1.0.2", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1575931811, | ||||
|         "version": "1.0.1", | ||||
|   | ||||
| @@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.0.4 - _January 22, 2020_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v1.0.3 - _January 6, 2020_ | ||||
|  | ||||
|     * Fixed ERC721 duplicate token ID bug (#2400) | ||||
|  | ||||
| ## v1.0.2 - _December 17, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v1.0.1 - _December 9, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -4,10 +4,10 @@ | ||||
|     "useDockerisedSolc": false, | ||||
|     "isOfflineMode": false, | ||||
|     "compilerSettings": { | ||||
|         "evmVersion": "constantinople", | ||||
|         "evmVersion": "istanbul", | ||||
|         "optimizer": { | ||||
|             "enabled": true, | ||||
|             "runs": 1666, | ||||
|             "runs": 5000, | ||||
|             "details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true } | ||||
|         }, | ||||
|         "outputSelection": { | ||||
|   | ||||
| @@ -26,26 +26,33 @@ import "@0x/contracts-utils/contracts/src/LibEIP712.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/LibBytes.sol"; | ||||
| import "./OrderValidationUtils.sol"; | ||||
| import "./OrderTransferSimulationUtils.sol"; | ||||
| import "./LibTransactionDecoder.sol"; | ||||
| import "./EthBalanceChecker.sol"; | ||||
|  | ||||
|  | ||||
| // solhint-disable no-empty-blocks | ||||
| contract DevUtils is | ||||
|     OrderValidationUtils, | ||||
|     LibTransactionDecoder, | ||||
|     LibEIP712ExchangeDomain, | ||||
|     EthBalanceChecker, | ||||
|     OrderTransferSimulationUtils | ||||
|     EthBalanceChecker | ||||
| { | ||||
|     constructor (address _exchange) | ||||
|     constructor ( | ||||
|         address _exchange, | ||||
|         address _chaiBridge | ||||
|     ) | ||||
|         public | ||||
|         OrderValidationUtils(_exchange) | ||||
|         OrderValidationUtils( | ||||
|             _exchange, | ||||
|             _chaiBridge | ||||
|         ) | ||||
|         OrderTransferSimulationUtils(_exchange) | ||||
|         LibEIP712ExchangeDomain(uint256(0), address(0)) // null args because because we only use constants | ||||
|     {} | ||||
|  | ||||
|     function getOrderHash(LibOrder.Order memory order, uint256 chainId, address exchange) | ||||
|     function getOrderHash( | ||||
|         LibOrder.Order memory order, | ||||
|         uint256 chainId, | ||||
|         address exchange | ||||
|     ) | ||||
|         public | ||||
|         pure | ||||
|         returns (bytes32 orderHash) | ||||
|   | ||||
| @@ -26,10 +26,14 @@ import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetProxy.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; | ||||
| import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol"; | ||||
| import "@0x/contracts-erc1155/contracts/src/interfaces/IERC1155.sol"; | ||||
| import "@0x/contracts-asset-proxy/contracts/src/interfaces/IChai.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; | ||||
| import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; | ||||
|  | ||||
|  | ||||
| contract LibAssetData { | ||||
|  | ||||
| contract LibAssetData is | ||||
|     DeploymentConstants | ||||
| { | ||||
|     // 2^256 - 1 | ||||
|     uint256 constant internal _MAX_UINT256 = uint256(-1); | ||||
|  | ||||
| @@ -41,9 +45,13 @@ contract LibAssetData { | ||||
|     address internal _ERC721_PROXY_ADDRESS; | ||||
|     address internal _ERC1155_PROXY_ADDRESS; | ||||
|     address internal _STATIC_CALL_PROXY_ADDRESS; | ||||
|     address internal _CHAI_BRIDGE_ADDRESS; | ||||
|     // solhint-enable var-name-mixedcase | ||||
|  | ||||
|     constructor (address _exchange) | ||||
|     constructor ( | ||||
|         address _exchange, | ||||
|         address _chaiBridge | ||||
|     ) | ||||
|         public | ||||
|     { | ||||
|         _EXCHANGE = IExchange(_exchange); | ||||
| @@ -51,6 +59,7 @@ contract LibAssetData { | ||||
|         _ERC721_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(IAssetData(address(0)).ERC721Token.selector); | ||||
|         _ERC1155_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(IAssetData(address(0)).ERC1155Assets.selector); | ||||
|         _STATIC_CALL_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(IAssetData(address(0)).StaticCall.selector); | ||||
|         _CHAI_BRIDGE_ADDRESS = _chaiBridge; | ||||
|     } | ||||
|  | ||||
|     /// @dev Returns the owner's balance of the assets(s) specified in | ||||
| @@ -62,7 +71,6 @@ contract LibAssetData { | ||||
|     /// @return Number of assets (or asset baskets) held by owner. | ||||
|     function getBalance(address ownerAddress, bytes memory assetData) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256 balance) | ||||
|     { | ||||
|         // Get id of AssetProxy contract | ||||
| @@ -71,16 +79,8 @@ contract LibAssetData { | ||||
|         if (assetProxyId == IAssetData(address(0)).ERC20Token.selector) { | ||||
|             // Get ERC20 token address | ||||
|             address tokenAddress = assetData.readAddress(16); | ||||
|             balance = _erc20BalanceOf(tokenAddress, ownerAddress); | ||||
|  | ||||
|             // Encode data for `balanceOf(ownerAddress)` | ||||
|             bytes memory balanceOfData = abi.encodeWithSelector( | ||||
|                 IERC20Token(address(0)).balanceOf.selector, | ||||
|                 ownerAddress | ||||
|             ); | ||||
|  | ||||
|             // Query balance | ||||
|             (bool success, bytes memory returnData) = tokenAddress.staticcall(balanceOfData); | ||||
|             balance = success && returnData.length == 32 ? returnData.readUint256(0) : 0; | ||||
|         } else if (assetProxyId == IAssetData(address(0)).ERC721Token.selector) { | ||||
|             // Get ERC721 token address and id | ||||
|             (, address tokenAddress, uint256 tokenId) = decodeERC721AssetData(assetData); | ||||
| @@ -94,12 +94,18 @@ contract LibAssetData { | ||||
|             (bool success, bytes memory returnData) = tokenAddress.staticcall(ownerOfCalldata); | ||||
|             address currentOwnerAddress = (success && returnData.length == 32) ? returnData.readAddress(12) : address(0); | ||||
|             balance = currentOwnerAddress == ownerAddress ? 1 : 0; | ||||
|  | ||||
|         } else if (assetProxyId == IAssetData(address(0)).ERC1155Assets.selector) { | ||||
|             // Get ERC1155 token address, array of ids, and array of values | ||||
|             (, address tokenAddress, uint256[] memory tokenIds, uint256[] memory tokenValues,) = decodeERC1155AssetData(assetData); | ||||
|  | ||||
|             uint256 length = tokenIds.length; | ||||
|             for (uint256 i = 0; i != length; i++) { | ||||
|                 // Skip over the token if the corresponding value is 0. | ||||
|                 if (tokenValues[i] == 0) { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 // Encode data for `balanceOf(ownerAddress, tokenIds[i]) | ||||
|                 bytes memory balanceOfData = abi.encodeWithSelector( | ||||
|                     IERC1155(address(0)).balanceOf.selector, | ||||
| @@ -113,10 +119,14 @@ contract LibAssetData { | ||||
|  | ||||
|                 // Scale total balance down by corresponding value in assetData | ||||
|                 uint256 scaledBalance = totalBalance / tokenValues[i]; | ||||
|                 if (scaledBalance == 0) { | ||||
|                     return 0; | ||||
|                 } | ||||
|                 if (scaledBalance < balance || balance == 0) { | ||||
|                     balance = scaledBalance; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         } else if (assetProxyId == IAssetData(address(0)).StaticCall.selector) { | ||||
|             // Encode data for `staticCallProxy.transferFrom(assetData,...)` | ||||
|             bytes memory transferFromData = abi.encodeWithSelector( | ||||
| @@ -132,22 +142,41 @@ contract LibAssetData { | ||||
|  | ||||
|             // Success means that the staticcall can be made an unlimited amount of times | ||||
|             balance = success ? _MAX_UINT256 : 0; | ||||
|  | ||||
|         } else if (assetProxyId == IAssetData(address(0)).ERC20Bridge.selector) { | ||||
|             // Get address of ERC20 token and bridge contract | ||||
|             (, address tokenAddress, address bridgeAddress,) = decodeERC20BridgeAssetData(assetData); | ||||
|             if (tokenAddress == _getDaiAddress() && bridgeAddress == _CHAI_BRIDGE_ADDRESS) { | ||||
|                 uint256 chaiBalance = _erc20BalanceOf(_getChaiAddress(), ownerAddress); | ||||
|                 // Calculate Dai balance | ||||
|                 balance = _convertChaiToDaiAmount(chaiBalance); | ||||
|             } | ||||
|             // Balance will be 0 if bridge is not supported | ||||
|  | ||||
|         } else if (assetProxyId == IAssetData(address(0)).MultiAsset.selector) { | ||||
|             // Get array of values and array of assetDatas | ||||
|             (, uint256[] memory assetAmounts, bytes[] memory nestedAssetData) = decodeMultiAssetData(assetData); | ||||
|  | ||||
|             uint256 length = nestedAssetData.length; | ||||
|             for (uint256 i = 0; i != length; i++) { | ||||
|                 // Skip over the asset if the corresponding amount is 0. | ||||
|                 if (assetAmounts[i] == 0) { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 // Query balance of individual assetData | ||||
|                 uint256 totalBalance = getBalance(ownerAddress, nestedAssetData[i]); | ||||
|  | ||||
|                 // Scale total balance down by corresponding value in assetData | ||||
|                 uint256 scaledBalance = totalBalance / assetAmounts[i]; | ||||
|                 if (scaledBalance == 0) { | ||||
|                     return 0; | ||||
|                 } | ||||
|                 if (scaledBalance < balance || balance == 0) { | ||||
|                     balance = scaledBalance; | ||||
|                 } | ||||
|             } | ||||
|         }  | ||||
|         } | ||||
|  | ||||
|         // Balance will be 0 if assetProxyId is unknown | ||||
|         return balance; | ||||
| @@ -160,7 +189,6 @@ contract LibAssetData { | ||||
|     /// corresponding to the same-indexed element in the assetData input. | ||||
|     function getBatchBalances(address ownerAddress, bytes[] memory assetData) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory balances) | ||||
|     { | ||||
|         uint256 length = assetData.length; | ||||
| @@ -181,7 +209,6 @@ contract LibAssetData { | ||||
|     /// @return Number of assets (or asset baskets) that the corresponding AssetProxy is authorized to spend. | ||||
|     function getAssetProxyAllowance(address ownerAddress, bytes memory assetData) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256 allowance) | ||||
|     { | ||||
|         // Get id of AssetProxy contract | ||||
| @@ -193,11 +220,19 @@ contract LibAssetData { | ||||
|  | ||||
|             uint256 length = nestedAssetData.length; | ||||
|             for (uint256 i = 0; i != length; i++) { | ||||
|                 // Skip over the asset if the corresponding amount is 0. | ||||
|                 if (amounts[i] == 0) { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 // Query allowance of individual assetData | ||||
|                 uint256 totalAllowance = getAssetProxyAllowance(ownerAddress, nestedAssetData[i]); | ||||
|  | ||||
|                 // Scale total allowance down by corresponding value in assetData | ||||
|                 uint256 scaledAllowance = totalAllowance / amounts[i]; | ||||
|                 if (scaledAllowance == 0) { | ||||
|                     return 0; | ||||
|                 } | ||||
|                 if (scaledAllowance < allowance || allowance == 0) { | ||||
|                     allowance = scaledAllowance; | ||||
|                 } | ||||
| @@ -219,6 +254,7 @@ contract LibAssetData { | ||||
|             // Query allowance | ||||
|             (bool success, bytes memory returnData) = tokenAddress.staticcall(allowanceData); | ||||
|             allowance = success && returnData.length == 32 ? returnData.readUint256(0) : 0; | ||||
|  | ||||
|         } else if (assetProxyId == IAssetData(address(0)).ERC721Token.selector) { | ||||
|             // Get ERC721 token address and id | ||||
|             (, address tokenAddress, uint256 tokenId) = decodeERC721AssetData(assetData); | ||||
| @@ -244,6 +280,7 @@ contract LibAssetData { | ||||
|                 // Allowance is 2^256 - 1 if `isApprovedForAll` returned true | ||||
|                 allowance = _MAX_UINT256; | ||||
|             } | ||||
|  | ||||
|         } else if (assetProxyId == IAssetData(address(0)).ERC1155Assets.selector) { | ||||
|             // Get ERC1155 token address | ||||
|             (, address tokenAddress, , , ) = decodeERC1155AssetData(assetData); | ||||
| @@ -258,9 +295,26 @@ contract LibAssetData { | ||||
|             // Query allowance | ||||
|             (bool success, bytes memory returnData) = tokenAddress.staticcall(isApprovedForAllData); | ||||
|             allowance = success && returnData.length == 32 && returnData.readUint256(0) == 1 ? _MAX_UINT256 : 0; | ||||
|  | ||||
|         } else if (assetProxyId == IAssetData(address(0)).StaticCall.selector) { | ||||
|             // The StaticCallProxy does not require any approvals | ||||
|             allowance = _MAX_UINT256; | ||||
|  | ||||
|         } else if (assetProxyId == IAssetData(address(0)).ERC20Bridge.selector) { | ||||
|             // Get address of ERC20 token and bridge contract | ||||
|             (, address tokenAddress, address bridgeAddress,) = decodeERC20BridgeAssetData(assetData); | ||||
|             if (tokenAddress == _getDaiAddress() && bridgeAddress == _CHAI_BRIDGE_ADDRESS) { | ||||
|                 bytes memory allowanceData = abi.encodeWithSelector( | ||||
|                     IERC20Token(address(0)).allowance.selector, | ||||
|                     ownerAddress, | ||||
|                     _CHAI_BRIDGE_ADDRESS | ||||
|                 ); | ||||
|                 (bool success, bytes memory returnData) = _getChaiAddress().staticcall(allowanceData); | ||||
|                 uint256 chaiAllowance = success && returnData.length == 32 ? returnData.readUint256(0) : 0; | ||||
|                 // Dai allowance is unlimited if Chai allowance is unlimited | ||||
|                 allowance = chaiAllowance == _MAX_UINT256 ? _MAX_UINT256 : _convertChaiToDaiAmount(chaiAllowance); | ||||
|             } | ||||
|             // Allowance will be 0 if bridge is not supported | ||||
|         } | ||||
|  | ||||
|         // Allowance will be 0 if the assetProxyId is unknown | ||||
| @@ -274,7 +328,6 @@ contract LibAssetData { | ||||
|     /// element corresponding to the same-indexed element in the assetData input. | ||||
|     function getBatchAssetProxyAllowances(address ownerAddress, bytes[] memory assetData) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory allowances) | ||||
|     { | ||||
|         uint256 length = assetData.length; | ||||
| @@ -292,7 +345,6 @@ contract LibAssetData { | ||||
|     /// of assets (or asset baskets) that the corresponding AssetProxy is authorized to spend. | ||||
|     function getBalanceAndAssetProxyAllowance(address ownerAddress, bytes memory assetData) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256 balance, uint256 allowance) | ||||
|     { | ||||
|         balance = getBalance(ownerAddress, assetData); | ||||
| @@ -308,7 +360,6 @@ contract LibAssetData { | ||||
|     /// corresponding to the same-indexed element in the assetData input. | ||||
|     function getBatchBalancesAndAssetProxyAllowances(address ownerAddress, bytes[] memory assetData) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory balances, uint256[] memory allowances) | ||||
|     { | ||||
|         balances = getBatchBalances(ownerAddress, assetData); | ||||
| @@ -316,7 +367,7 @@ contract LibAssetData { | ||||
|         return (balances, allowances); | ||||
|     } | ||||
|  | ||||
|     /// @dev Decode AssetProxy identifier  | ||||
|     /// @dev Decode AssetProxy identifier | ||||
|     /// @param assetData AssetProxy-compliant asset data describing an ERC-20, ERC-721, ERC1155, or MultiAsset asset. | ||||
|     /// @return The AssetProxy identifier | ||||
|     function decodeAssetProxyId(bytes memory assetData) | ||||
| @@ -353,7 +404,7 @@ contract LibAssetData { | ||||
|  | ||||
|     /// @dev Decode ERC-20 asset data from the format described in the AssetProxy contract specification. | ||||
|     /// @param assetData AssetProxy-compliant asset data describing an ERC-20 asset. | ||||
|     /// @return The AssetProxy identifier, and the address of the ERC-20  | ||||
|     /// @return The AssetProxy identifier, and the address of the ERC-20 | ||||
|     /// contract hosting this asset. | ||||
|     function decodeERC20AssetData(bytes memory assetData) | ||||
|         public | ||||
| @@ -589,9 +640,38 @@ contract LibAssetData { | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Decode ERC20Bridge asset data from the format described in the AssetProxy contract specification. | ||||
|     /// @param assetData AssetProxy-compliant asset data describing an ERC20Bridge asset | ||||
|     /// @return The ERC20BridgeProxy identifier, the address of the ERC20 token to transfer, the address | ||||
|     /// of the bridge contract, and extra data to be passed to the bridge contract. | ||||
|     function decodeERC20BridgeAssetData(bytes memory assetData) | ||||
|         public | ||||
|         pure | ||||
|         returns ( | ||||
|             bytes4 assetProxyId, | ||||
|             address tokenAddress, | ||||
|             address bridgeAddress, | ||||
|             bytes memory bridgeData | ||||
|         ) | ||||
|     { | ||||
|         assetProxyId = assetData.readBytes4(0); | ||||
|  | ||||
|         require( | ||||
|             assetProxyId == IAssetData(address(0)).ERC20Bridge.selector, | ||||
|             "WRONG_PROXY_ID" | ||||
|         ); | ||||
|  | ||||
|         (tokenAddress, bridgeAddress, bridgeData) = abi.decode( | ||||
|             assetData.slice(4, assetData.length), | ||||
|             (address, address, bytes) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Reverts if assetData is not of a valid format for its given proxy id. | ||||
|     /// @param assetData AssetProxy compliant asset data. | ||||
|     function revertIfInvalidAssetData(bytes memory assetData) | ||||
|         public | ||||
|         pure  | ||||
|         pure | ||||
|     { | ||||
|         bytes4 assetProxyId = assetData.readBytes4(0); | ||||
|  | ||||
| @@ -605,8 +685,50 @@ contract LibAssetData { | ||||
|             decodeMultiAssetData(assetData); | ||||
|         } else if (assetProxyId == IAssetData(address(0)).StaticCall.selector) { | ||||
|             decodeStaticCallAssetData(assetData); | ||||
|         } else if (assetProxyId == IAssetData(address(0)).ERC20Bridge.selector) { | ||||
|             decodeERC20BridgeAssetData(assetData); | ||||
|         } else { | ||||
|             revert("WRONG_PROXY_ID"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Queries balance of an ERC20 token. Returns 0 if call was unsuccessful. | ||||
|     /// @param tokenAddress Address of ERC20 token. | ||||
|     /// @param ownerAddress Address of owner of ERC20 token. | ||||
|     /// @return balance ERC20 token balance of owner. | ||||
|     function _erc20BalanceOf( | ||||
|         address tokenAddress, | ||||
|         address ownerAddress | ||||
|     ) | ||||
|         internal | ||||
|         view | ||||
|         returns (uint256 balance) | ||||
|     { | ||||
|         // Encode data for `balanceOf(ownerAddress)` | ||||
|         bytes memory balanceOfData = abi.encodeWithSelector( | ||||
|             IERC20Token(address(0)).balanceOf.selector, | ||||
|             ownerAddress | ||||
|         ); | ||||
|  | ||||
|         // Query balance | ||||
|         (bool success, bytes memory returnData) = tokenAddress.staticcall(balanceOfData); | ||||
|         balance = success && returnData.length == 32 ? returnData.readUint256(0) : 0; | ||||
|         return balance; | ||||
|     } | ||||
|  | ||||
|     /// @dev Converts an amount of Chai into its equivalent Dai amount. | ||||
|     ///      Also accumulates Dai from DSR if called after the last time it was collected. | ||||
|     /// @param chaiAmount Amount of Chai to converts. | ||||
|     function _convertChaiToDaiAmount(uint256 chaiAmount) | ||||
|         internal | ||||
|         returns (uint256 daiAmount) | ||||
|     { | ||||
|         PotLike pot = IChai(_getChaiAddress()).pot(); | ||||
|         // Accumulate savings if called after last time savings were collected | ||||
|         uint256 chiMultiplier = (now > pot.rho()) | ||||
|             ? pot.drip() | ||||
|             : pot.chi(); | ||||
|         daiAmount = LibMath.getPartialAmountFloor(chiMultiplier, 10**27, chaiAmount); | ||||
|         return daiAmount; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -41,6 +41,10 @@ contract OrderTransferSimulationUtils is | ||||
|         TransfersSuccessful       // All transfers in the order were successful | ||||
|     } | ||||
|  | ||||
|     // NOTE(jalextowle): This is a random address that we use to avoid issues that addresses like `address(1)` | ||||
|     // may cause later. | ||||
|     address constant internal UNUSED_ADDRESS = address(0x377f698C4c287018D09b516F415317aEC5919332); | ||||
|  | ||||
|     // keccak256(abi.encodeWithSignature("Error(string)", "TRANSFERS_SUCCESSFUL")); | ||||
|     bytes32 constant internal _TRANSFERS_SUCCESSFUL_RESULT_HASH = 0xf43f26ea5a94b478394a975e856464913dc1a8a1ca70939d974aa7c238aa0ce0; | ||||
|  | ||||
| @@ -54,6 +58,51 @@ contract OrderTransferSimulationUtils is | ||||
|         _EXCHANGE = IExchange(_exchange); | ||||
|     } | ||||
|  | ||||
|     /// @dev Simulates the maker transfers within an order and returns the index of the first failed transfer. | ||||
|     /// @param order The order to simulate transfers for. | ||||
|     /// @param takerAddress The address of the taker that will fill the order. | ||||
|     /// @param takerAssetFillAmount The amount of takerAsset that the taker wished to fill. | ||||
|     /// @return The index of the first failed transfer (or 4 if all transfers are successful). | ||||
|     function getSimulatedOrderMakerTransferResults( | ||||
|         LibOrder.Order memory order, | ||||
|         address takerAddress, | ||||
|         uint256 takerAssetFillAmount | ||||
|     ) | ||||
|         public | ||||
|         returns (OrderTransferResults orderTransferResults) | ||||
|     { | ||||
|         LibFillResults.FillResults memory fillResults = LibFillResults.calculateFillResults( | ||||
|             order, | ||||
|             takerAssetFillAmount, | ||||
|             _EXCHANGE.protocolFeeMultiplier(), | ||||
|             tx.gasprice | ||||
|         ); | ||||
|  | ||||
|         bytes[] memory assetData = new bytes[](2); | ||||
|         address[] memory fromAddresses = new address[](2); | ||||
|         address[] memory toAddresses = new address[](2); | ||||
|         uint256[] memory amounts = new uint256[](2); | ||||
|  | ||||
|         // Transfer `makerAsset` from maker to taker | ||||
|         assetData[0] = order.makerAssetData; | ||||
|         fromAddresses[0] = order.makerAddress; | ||||
|         toAddresses[0] = takerAddress == address(0) ? UNUSED_ADDRESS : takerAddress; | ||||
|         amounts[0] = fillResults.makerAssetFilledAmount; | ||||
|  | ||||
|         // Transfer `makerFeeAsset` from maker to feeRecipient | ||||
|         assetData[1] = order.makerFeeAssetData; | ||||
|         fromAddresses[1] = order.makerAddress; | ||||
|         toAddresses[1] = order.feeRecipientAddress == address(0) ? UNUSED_ADDRESS : order.feeRecipientAddress; | ||||
|         amounts[1] = fillResults.makerFeePaid; | ||||
|  | ||||
|         return _simulateTransferFromCalls( | ||||
|             assetData, | ||||
|             fromAddresses, | ||||
|             toAddresses, | ||||
|             amounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Simulates all of the transfers within an order and returns the index of the first failed transfer. | ||||
|     /// @param order The order to simulate transfers for. | ||||
|     /// @param takerAddress The address of the taker that will fill the order. | ||||
| @@ -89,21 +138,69 @@ contract OrderTransferSimulationUtils is | ||||
|         // Transfer `makerAsset` from maker to taker | ||||
|         assetData[1] = order.makerAssetData; | ||||
|         fromAddresses[1] = order.makerAddress; | ||||
|         toAddresses[1] = takerAddress; | ||||
|         toAddresses[1] = takerAddress == address(0) ? UNUSED_ADDRESS : takerAddress; | ||||
|         amounts[1] = fillResults.makerAssetFilledAmount; | ||||
|  | ||||
|         // Transfer `takerFeeAsset` from taker to feeRecipient | ||||
|         assetData[2] = order.takerFeeAssetData; | ||||
|         fromAddresses[2] = takerAddress; | ||||
|         toAddresses[2] = order.feeRecipientAddress; | ||||
|         toAddresses[2] = order.feeRecipientAddress == address(0) ? UNUSED_ADDRESS : order.feeRecipientAddress; | ||||
|         amounts[2] = fillResults.takerFeePaid; | ||||
|  | ||||
|         // Transfer `makerFeeAsset` from maker to feeRecipient | ||||
|         assetData[3] = order.makerFeeAssetData; | ||||
|         fromAddresses[3] = order.makerAddress; | ||||
|         toAddresses[3] = order.feeRecipientAddress; | ||||
|         toAddresses[3] = order.feeRecipientAddress == address(0) ? UNUSED_ADDRESS : order.feeRecipientAddress; | ||||
|         amounts[3] = fillResults.makerFeePaid; | ||||
|  | ||||
|         return _simulateTransferFromCalls( | ||||
|             assetData, | ||||
|             fromAddresses, | ||||
|             toAddresses, | ||||
|             amounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Simulates all of the transfers for each given order and returns the indices of each first failed transfer. | ||||
|     /// @param orders Array of orders to individually simulate transfers for. | ||||
|     /// @param takerAddresses Array of addresses of takers that will fill each order. | ||||
|     /// @param takerAssetFillAmounts Array of amounts of takerAsset that will be filled for each order. | ||||
|     /// @return The indices of the first failed transfer (or 4 if all transfers are successful) for each order. | ||||
|     function getSimulatedOrdersTransferResults( | ||||
|         LibOrder.Order[] memory orders, | ||||
|         address[] memory takerAddresses, | ||||
|         uint256[] memory takerAssetFillAmounts | ||||
|     ) | ||||
|         public | ||||
|         returns (OrderTransferResults[] memory orderTransferResults) | ||||
|     { | ||||
|         uint256 length = orders.length; | ||||
|         orderTransferResults = new OrderTransferResults[](length); | ||||
|         for (uint256 i = 0; i != length; i++) { | ||||
|             orderTransferResults[i] = getSimulatedOrderTransferResults( | ||||
|                 orders[i], | ||||
|                 takerAddresses[i], | ||||
|                 takerAssetFillAmounts[i] | ||||
|             ); | ||||
|         } | ||||
|         return orderTransferResults; | ||||
|     } | ||||
|  | ||||
|     /// @dev Makes the simulation call with information about the transfers and processes | ||||
|     ///      the returndata. | ||||
|     /// @param assetData The assetdata to use to make transfers. | ||||
|     /// @param fromAddresses The addresses to transfer funds. | ||||
|     /// @param toAddresses The addresses that will receive funds | ||||
|     /// @param amounts The amounts involved in the transfer. | ||||
|     function _simulateTransferFromCalls( | ||||
|         bytes[] memory assetData, | ||||
|         address[] memory fromAddresses, | ||||
|         address[] memory toAddresses, | ||||
|         uint256[] memory amounts | ||||
|     ) | ||||
|         internal | ||||
|         returns (OrderTransferResults orderTransferResults) | ||||
|     { | ||||
|         // Encode data for `simulateDispatchTransferFromCalls(assetData, fromAddresses, toAddresses, amounts)` | ||||
|         bytes memory simulateDispatchTransferFromCallsData = abi.encodeWithSelector( | ||||
|             IExchange(address(0)).simulateDispatchTransferFromCalls.selector, | ||||
| @@ -132,29 +229,4 @@ contract OrderTransferSimulationUtils is | ||||
|             revert("UNKNOWN_RETURN_DATA"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Simulates all of the transfers for each given order and returns the indices of each first failed transfer. | ||||
|     /// @param orders Array of orders to individually simulate transfers for. | ||||
|     /// @param takerAddresses Array of addresses of takers that will fill each order. | ||||
|     /// @param takerAssetFillAmounts Array of amounts of takerAsset that will be filled for each order. | ||||
|     /// @return The indices of the first failed transfer (or 4 if all transfers are successful) for each order. | ||||
|     function getSimulatedOrdersTransferResults( | ||||
|         LibOrder.Order[] memory orders, | ||||
|         address[] memory takerAddresses, | ||||
|         uint256[] memory takerAssetFillAmounts | ||||
|     ) | ||||
|         public | ||||
|         returns (OrderTransferResults[] memory orderTransferResults) | ||||
|     { | ||||
|         uint256 length = orders.length; | ||||
|         orderTransferResults = new OrderTransferResults[](length); | ||||
|         for (uint256 i = 0; i != length; i++) { | ||||
|             orderTransferResults[i] = getSimulatedOrderTransferResults( | ||||
|                 orders[i], | ||||
|                 takerAddresses[i], | ||||
|                 takerAssetFillAmounts[i] | ||||
|             ); | ||||
|         } | ||||
|         return orderTransferResults; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -16,7 +16,7 @@ | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.5.5; | ||||
| pragma solidity ^0.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; | ||||
| @@ -25,17 +25,25 @@ import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/LibBytes.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; | ||||
| import "./LibAssetData.sol"; | ||||
| import "./OrderTransferSimulationUtils.sol"; | ||||
|  | ||||
|  | ||||
| contract OrderValidationUtils is | ||||
|     LibAssetData | ||||
|     LibAssetData, | ||||
|     OrderTransferSimulationUtils | ||||
| { | ||||
|     using LibBytes for bytes; | ||||
|     using LibSafeMath for uint256; | ||||
|  | ||||
|     constructor (address _exchange) | ||||
|     constructor ( | ||||
|         address _exchange, | ||||
|         address _chaiBridge | ||||
|     ) | ||||
|         public | ||||
|         LibAssetData(_exchange) | ||||
|         LibAssetData( | ||||
|             _exchange, | ||||
|             _chaiBridge | ||||
|         ) | ||||
|     {} | ||||
|  | ||||
|     /// @dev Fetches all order-relevant information needed to validate if the supplied order is fillable. | ||||
| @@ -50,7 +58,6 @@ contract OrderValidationUtils is | ||||
|     /// amount of each asset that can be filled. | ||||
|     function getOrderRelevantState(LibOrder.Order memory order, bytes memory signature) | ||||
|         public | ||||
|         view | ||||
|         returns ( | ||||
|             LibOrder.OrderInfo memory orderInfo, | ||||
|             uint256 fillableTakerAssetAmount, | ||||
| @@ -99,7 +106,6 @@ contract OrderValidationUtils is | ||||
|             } else { | ||||
|                 // Get the transferable amount of the `makerFeeAsset` | ||||
|                 uint256 transferableMakerFeeAssetAmount = getTransferableAssetAmount(makerAddress, order.makerFeeAssetData); | ||||
|  | ||||
|                 uint256 transferableMakerToTakerAmount = LibMath.getPartialAmountFloor( | ||||
|                     transferableMakerAssetAmount, | ||||
|                     order.makerAssetAmount, | ||||
| @@ -120,6 +126,25 @@ contract OrderValidationUtils is | ||||
|             transferableTakerAssetAmount | ||||
|         ); | ||||
|  | ||||
|         // Execute the maker transfers. | ||||
|         fillableTakerAssetAmount = getSimulatedOrderMakerTransferResults( | ||||
|             order, | ||||
|             order.takerAddress, | ||||
|             fillableTakerAssetAmount | ||||
|         ) == OrderTransferResults.TransfersSuccessful ? fillableTakerAssetAmount : 0; | ||||
|  | ||||
|         if (!_isAssetDataValid(order.takerAssetData)) { | ||||
|             fillableTakerAssetAmount = 0; | ||||
|         } | ||||
|  | ||||
|         if (order.takerFee != 0 && !_isAssetDataValid(order.takerFeeAssetData)) { | ||||
|             fillableTakerAssetAmount = 0; | ||||
|         } | ||||
|  | ||||
|         if (orderInfo.orderStatus != LibOrder.OrderStatus.FILLABLE) { | ||||
|             fillableTakerAssetAmount = 0; | ||||
|         } | ||||
|  | ||||
|         return (orderInfo, fillableTakerAssetAmount, isValidSignature); | ||||
|     } | ||||
|  | ||||
| @@ -135,7 +160,6 @@ contract OrderValidationUtils is | ||||
|     /// the `takerAssetData` to get the final amount of each asset that can be filled. | ||||
|     function getOrderRelevantStates(LibOrder.Order[] memory orders, bytes[] memory signatures) | ||||
|         public | ||||
|         view | ||||
|         returns ( | ||||
|             LibOrder.OrderInfo[] memory ordersInfo, | ||||
|             uint256[] memory fillableTakerAssetAmounts, | ||||
| @@ -167,11 +191,69 @@ contract OrderValidationUtils is | ||||
|     /// the individual asset amounts located within the `assetData`. | ||||
|     function getTransferableAssetAmount(address ownerAddress, bytes memory assetData) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256 transferableAssetAmount) | ||||
|     { | ||||
|         (uint256 balance, uint256 allowance) = getBalanceAndAssetProxyAllowance(ownerAddress, assetData); | ||||
|         transferableAssetAmount = LibSafeMath.min256(balance, allowance); | ||||
|         return transferableAssetAmount; | ||||
|     } | ||||
|  | ||||
|     /// @dev This function handles the edge cases around taker validation. This function | ||||
|     ///      currently attempts to find duplicate ERC721 token's in the taker | ||||
|     ///      multiAssetData. | ||||
|     /// @param assetData The asset data that should be validated. | ||||
|     /// @return Whether or not the order should be considered valid. | ||||
|     function _isAssetDataValid(bytes memory assetData) | ||||
|         internal | ||||
|         pure | ||||
|         returns (bool) | ||||
|     { | ||||
|         // Asset data must be composed of an asset proxy Id and a bytes segment with | ||||
|         // a length divisible by 32. | ||||
|         if (assetData.length % 32 != 4) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // Only process the taker asset data if it is multiAssetData. | ||||
|         bytes4 assetProxyId = assetData.readBytes4(0); | ||||
|         if (assetProxyId != IAssetData(address(0)).MultiAsset.selector) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // Get array of values and array of assetDatas | ||||
|         (, uint256[] memory assetAmounts, bytes[] memory nestedAssetData) = decodeMultiAssetData(assetData); | ||||
|  | ||||
|         uint256 length = nestedAssetData.length; | ||||
|         for (uint256 i = 0; i != length; i++) { | ||||
|             // TODO(jalextowle): Implement similar validation for non-fungible ERC1155 asset data. | ||||
|             bytes4 nestedAssetProxyId = nestedAssetData[i].readBytes4(0); | ||||
|             if (nestedAssetProxyId == IAssetData(address(0)).ERC721Token.selector) { | ||||
|                 if (_isAssetDataDuplicated(nestedAssetData, i)) { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /// Determines whether or not asset data is duplicated later in the nested asset data. | ||||
|     /// @param nestedAssetData The asset data to scan for duplication. | ||||
|     /// @param startIdx The index where the scan should begin. | ||||
|     /// @return A boolean reflecting whether or not the starting asset data was duplicated. | ||||
|     function _isAssetDataDuplicated( | ||||
|         bytes[] memory nestedAssetData, | ||||
|         uint256 startIdx | ||||
|     ) | ||||
|         internal | ||||
|         pure | ||||
|         returns (bool) | ||||
|     { | ||||
|         uint256 length = nestedAssetData.length; | ||||
|         for (uint256 i = startIdx + 1; i < length; i++) { | ||||
|             if (nestedAssetData[startIdx].equals(nestedAssetData[i])) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0x/contracts-dev-utils", | ||||
|     "version": "1.0.1", | ||||
|     "version": "1.0.4", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -8,7 +8,7 @@ | ||||
|     "main": "lib/src/index.js", | ||||
|     "scripts": { | ||||
|         "build": "yarn pre_build && tsc -b", | ||||
|         "test": "yarn assert_deployable && echo !!! Tests are run via @0x/contracts-tests !!!", | ||||
|         "test": "yarn assert_deployable && echo !!! Tests are run via @0x/contracts-integrations !!!", | ||||
|         "assert_deployable": "node -e \"const bytecodeLen = (require('./generated-artifacts/DevUtils.json').compilerOutput.evm.bytecode.object.length-2)/2; assert(bytecodeLen<=0x6000,'DevUtils contract is too big to deploy, per EIP-170. '+bytecodeLen+'>'+0x6000)\"", | ||||
|         "build:ci": "yarn build", | ||||
|         "pre_build": "run-s compile quantify_bytecode contracts:gen generate_contract_wrappers contracts:copy", | ||||
| @@ -41,10 +41,10 @@ | ||||
|     }, | ||||
|     "homepage": "https://github.com/0xProject/0x-monorepo/contracts/dev-utils/README.md", | ||||
|     "devDependencies": { | ||||
|         "@0x/abi-gen": "^5.0.1", | ||||
|         "@0x/assert": "^3.0.1", | ||||
|         "@0x/contracts-gen": "^2.0.1", | ||||
|         "@0x/sol-compiler": "^4.0.1", | ||||
|         "@0x/abi-gen": "^5.1.0", | ||||
|         "@0x/assert": "^3.0.4", | ||||
|         "@0x/contracts-gen": "^2.0.4", | ||||
|         "@0x/sol-compiler": "^4.0.4", | ||||
|         "@0x/ts-doc-gen": "^0.0.22", | ||||
|         "@0x/tslint-config": "^4.0.0", | ||||
|         "@types/node": "*", | ||||
| @@ -59,7 +59,7 @@ | ||||
|         "typescript": "3.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0x/base-contract": "^6.0.1" | ||||
|         "@0x/base-contract": "^6.1.0" | ||||
|     }, | ||||
|     "publishConfig": { | ||||
|         "access": "public" | ||||
|   | ||||
| @@ -84,7 +84,7 @@ module.exports = { | ||||
|         solc: { | ||||
|             version: '0.5.9', | ||||
|             settings: { | ||||
|                 evmVersion: 'constantinople', | ||||
|                 evmVersion: 'istanbul', | ||||
|                 optimizer: { | ||||
|                     enabled: true, | ||||
|                     runs: 1000000, | ||||
|   | ||||
| @@ -1,4 +1,31 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1579682890, | ||||
|         "version": "2.0.4", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1578272714, | ||||
|         "version": "2.0.3", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1576540892, | ||||
|         "version": "2.0.2", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1575931811, | ||||
|         "version": "2.0.1", | ||||
|   | ||||
| @@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v2.0.4 - _January 22, 2020_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v2.0.3 - _January 6, 2020_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v2.0.2 - _December 17, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v2.0.1 - _December 9, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|     "useDockerisedSolc": false, | ||||
|     "isOfflineMode": false, | ||||
|     "compilerSettings": { | ||||
|         "evmVersion": "constantinople", | ||||
|         "evmVersion": "istanbul", | ||||
|         "optimizer": { | ||||
|             "enabled": true, | ||||
|             "runs": 1000000, | ||||
|   | ||||
| @@ -17,6 +17,7 @@ | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/LibAddress.sol"; | ||||
|   | ||||
| @@ -1,4 +1,23 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2019 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.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; | ||||
| import "./ERC1155.sol"; | ||||
|   | ||||
| @@ -17,6 +17,7 @@ | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
|  | ||||
| contract MixinNonFungibleToken { | ||||
| @@ -64,7 +65,7 @@ contract MixinNonFungibleToken { | ||||
|         // A base type has the NF bit but does has an index. | ||||
|         return (id & TYPE_NF_BIT == TYPE_NF_BIT) && (id & NF_INDEX_MASK != 0); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// @dev returns owner of a non-fungible token | ||||
|     function ownerOf(uint256 id) public view returns (address) { | ||||
|         return nfOwners[id]; | ||||
|   | ||||
| @@ -17,13 +17,14 @@ | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
|  | ||||
| /// @title ERC-1155 Multi Token Standard | ||||
| /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md | ||||
| /// Note: The ERC-165 identifier for this interface is 0xd9b67a26. | ||||
| interface IERC1155 { | ||||
|      | ||||
|  | ||||
|     /// @dev Either TransferSingle or TransferBatch MUST emit when tokens are transferred, | ||||
|     ///      including zero value transfers as well as minting or burning. | ||||
|     /// Operator will always be msg.sender. | ||||
|   | ||||
| @@ -1,4 +1,23 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2019 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.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./IERC1155.sol"; | ||||
|  | ||||
|   | ||||
| @@ -17,10 +17,11 @@ | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
|  | ||||
| interface IERC1155Receiver { | ||||
|      | ||||
|  | ||||
|     /// @notice Handle the receipt of a single ERC1155 token type | ||||
|     /// @dev The smart contract calls this function on the recipient | ||||
|     /// after a `safeTransferFrom`. This function MAY throw to revert and reject the | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0x/contracts-erc1155", | ||||
|     "version": "2.0.1", | ||||
|     "version": "2.0.4", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -52,15 +52,15 @@ | ||||
|     }, | ||||
|     "homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md", | ||||
|     "devDependencies": { | ||||
|         "@0x/abi-gen": "^5.0.1", | ||||
|         "@0x/contracts-gen": "^2.0.1", | ||||
|         "@0x/contracts-utils": "^4.0.1", | ||||
|         "@0x/dev-utils": "^3.0.1", | ||||
|         "@0x/sol-compiler": "^4.0.1", | ||||
|         "@0x/abi-gen": "^5.1.0", | ||||
|         "@0x/contracts-gen": "^2.0.4", | ||||
|         "@0x/contracts-utils": "^4.1.0", | ||||
|         "@0x/dev-utils": "^3.1.1", | ||||
|         "@0x/sol-compiler": "^4.0.4", | ||||
|         "@0x/ts-doc-gen": "^0.0.22", | ||||
|         "@0x/tslint-config": "^4.0.0", | ||||
|         "@0x/types": "^3.1.0", | ||||
|         "@0x/typescript-typings": "^5.0.0", | ||||
|         "@0x/types": "^3.1.1", | ||||
|         "@0x/typescript-typings": "^5.0.1", | ||||
|         "@types/lodash": "4.14.104", | ||||
|         "@types/mocha": "^5.2.7", | ||||
|         "@types/node": "*", | ||||
| @@ -80,10 +80,10 @@ | ||||
|         "typescript": "3.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0x/base-contract": "^6.0.1", | ||||
|         "@0x/contracts-test-utils": "^5.0.0", | ||||
|         "@0x/utils": "^5.1.0", | ||||
|         "@0x/web3-wrapper": "^7.0.1", | ||||
|         "@0x/base-contract": "^6.1.0", | ||||
|         "@0x/contracts-test-utils": "^5.1.1", | ||||
|         "@0x/utils": "^5.2.0", | ||||
|         "@0x/web3-wrapper": "^7.0.4", | ||||
|         "lodash": "^4.17.11" | ||||
|     }, | ||||
|     "publishConfig": { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { BigNumber } from '@0x/utils'; | ||||
| import { LogWithDecodedArgs, Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; | ||||
| import { LogWithDecodedArgs, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; | ||||
| import * as _ from 'lodash'; | ||||
|  | ||||
| import { ERC1155MintableContract, ERC1155TransferSingleEventArgs } from './wrappers'; | ||||
| @@ -8,7 +8,7 @@ export class Erc1155Wrapper { | ||||
|     private readonly _erc1155Contract: ERC1155MintableContract; | ||||
|     private readonly _contractOwner: string; | ||||
|  | ||||
|     constructor(contractInstance: ERC1155MintableContract, provider: Provider, contractOwner: string) { | ||||
|     constructor(contractInstance: ERC1155MintableContract, contractOwner: string) { | ||||
|         this._erc1155Contract = contractInstance; | ||||
|         this._contractOwner = contractOwner; | ||||
|     } | ||||
|   | ||||
| @@ -5,21 +5,18 @@ export { | ||||
|     IERC1155ReceiverContract, | ||||
|     DummyERC1155ReceiverBatchTokenReceivedEventArgs, | ||||
|     ERC1155TransferSingleEventArgs, | ||||
|     ERC1155TransferBatchEventArgs, | ||||
|     ERC1155Events, | ||||
| } from './wrappers'; | ||||
| export { artifacts } from './artifacts'; | ||||
| export { Erc1155Wrapper } from './erc1155_wrapper'; | ||||
| export { | ||||
|     Provider, | ||||
|     TransactionReceiptWithDecodedLogs, | ||||
|     JSONRPCRequestPayload, | ||||
|     JSONRPCResponsePayload, | ||||
|     JSONRPCResponseError, | ||||
|     JSONRPCErrorCallback, | ||||
|     TransactionReceiptStatus, | ||||
|     ContractArtifact, | ||||
|     ContractChains, | ||||
|     CompilerOpts, | ||||
|     StandardContractOutput, | ||||
|     ContractArtifact, | ||||
|     CompilerSettings, | ||||
|     ContractChainData, | ||||
|     ContractAbi, | ||||
|   | ||||
| @@ -67,7 +67,7 @@ describe('ERC1155Token', () => { | ||||
|         ); | ||||
|         receiver = erc1155Receiver.address; | ||||
|         // create wrapper & mint erc1155 tokens | ||||
|         erc1155Wrapper = new Erc1155Wrapper(erc1155Contract, provider, owner); | ||||
|         erc1155Wrapper = new Erc1155Wrapper(erc1155Contract, owner); | ||||
|         fungibleToken = await erc1155Wrapper.mintFungibleTokensAsync([spender], spenderInitialFungibleBalance); | ||||
|         let nonFungibleTokens: BigNumber[]; | ||||
|         [, nonFungibleTokens] = await erc1155Wrapper.mintNonFungibleTokensAsync([spender]); | ||||
|   | ||||
| @@ -84,7 +84,7 @@ module.exports = { | ||||
|         solc: { | ||||
|             version: '0.5.9', | ||||
|             settings: { | ||||
|                 evmVersion: 'constantinople', | ||||
|                 evmVersion: 'istanbul', | ||||
|                 optimizer: { | ||||
|                     enabled: true, | ||||
|                     runs: 1000000, | ||||
|   | ||||
| @@ -1,4 +1,38 @@ | ||||
| [ | ||||
|     { | ||||
|         "version": "1.1.0", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Add batch functions to query quotes", | ||||
|                 "pr": 2427 | ||||
|             }, | ||||
|             { | ||||
|                 "note": "Early exit if a DEX sample fails", | ||||
|                 "pr": 2427 | ||||
|             } | ||||
|         ], | ||||
|         "timestamp": 1579682890 | ||||
|     }, | ||||
|     { | ||||
|         "version": "1.0.3", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Add gas limits to external quote calls.", | ||||
|                 "pr": 2405 | ||||
|             } | ||||
|         ], | ||||
|         "timestamp": 1578272714 | ||||
|     }, | ||||
|     { | ||||
|         "version": "1.0.2", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Do not query empty/unsigned orders. Swallow revets on DEX quotes.", | ||||
|                 "pr": 2395 | ||||
|             } | ||||
|         ], | ||||
|         "timestamp": 1576540892 | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1575931811, | ||||
|         "version": "1.0.1", | ||||
|   | ||||
| @@ -5,6 +5,19 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v1.1.0 - _January 22, 2020_ | ||||
|  | ||||
|     * Add batch functions to query quotes (#2427) | ||||
|     * Early exit if a DEX sample fails (#2427) | ||||
|  | ||||
| ## v1.0.3 - _January 6, 2020_ | ||||
|  | ||||
|     * Add gas limits to external quote calls. (#2405) | ||||
|  | ||||
| ## v1.0.2 - _December 17, 2019_ | ||||
|  | ||||
|     * Do not query empty/unsigned orders. Swallow revets on DEX quotes. (#2395) | ||||
|  | ||||
| ## v1.0.1 - _December 9, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|     "useDockerisedSolc": false, | ||||
|     "isOfflineMode": false, | ||||
|     "compilerSettings": { | ||||
|         "evmVersion": "constantinople", | ||||
|         "evmVersion": "istanbul", | ||||
|         "optimizer": { | ||||
|             "enabled": true, | ||||
|             "runs": 1000000, | ||||
|   | ||||
| @@ -1,92 +0,0 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2019 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.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol"; | ||||
| import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; | ||||
| import "./IEth2Dai.sol"; | ||||
| import "./IKyberNetwork.sol"; | ||||
|  | ||||
|  | ||||
| contract DeploymentConstants { | ||||
|  | ||||
|     /// @dev Address of the 0x Exchange contract. | ||||
|     address constant public EXCHANGE_ADDRESS = 0x080bf510FCbF18b91105470639e9561022937712; | ||||
|     /// @dev Address of the Eth2Dai MatchingMarket contract. | ||||
|     address constant public ETH2DAI_ADDRESS = 0x39755357759cE0d7f32dC8dC45414CCa409AE24e; | ||||
|     /// @dev Address of the UniswapExchangeFactory contract. | ||||
|     address constant public UNISWAP_EXCHANGE_FACTORY_ADDRESS = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95; | ||||
|     /// @dev Address of the KyberNeworkProxy contract. | ||||
|     address constant public KYBER_NETWORK_PROXY_ADDRESS = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755; | ||||
|     /// @dev Address of the WETH contract. | ||||
|     address constant public WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; | ||||
|     /// @dev Kyber ETH pseudo-address. | ||||
|     address constant public KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; | ||||
|  | ||||
|     /// @dev An overridable way to retrieve the 0x Exchange contract. | ||||
|     /// @return zeroex The 0x Exchange contract. | ||||
|     function _getExchangeContract() | ||||
|         internal | ||||
|         view | ||||
|         returns (IExchange zeroex) | ||||
|     { | ||||
|         return IExchange(EXCHANGE_ADDRESS); | ||||
|     } | ||||
|  | ||||
|     /// @dev An overridable way to retrieve the Eth2Dai exchange contract. | ||||
|     /// @return eth2dai The Eth2Dai exchange contract. | ||||
|     function _getEth2DaiContract() | ||||
|         internal | ||||
|         view | ||||
|         returns (IEth2Dai eth2dai) | ||||
|     { | ||||
|         return IEth2Dai(ETH2DAI_ADDRESS); | ||||
|     } | ||||
|  | ||||
|     /// @dev An overridable way to retrieve the Uniswap exchange factory contract. | ||||
|     /// @return uniswap The UniswapExchangeFactory contract. | ||||
|     function _getUniswapExchangeFactoryContract() | ||||
|         internal | ||||
|         view | ||||
|         returns (IUniswapExchangeFactory uniswap) | ||||
|     { | ||||
|         return IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS); | ||||
|     } | ||||
|  | ||||
|     /// @dev An overridable way to retrieve the Kyber network proxy contract. | ||||
|     /// @return kyber The KyberNeworkProxy contract. | ||||
|     function _getKyberNetworkContract() | ||||
|         internal | ||||
|         view | ||||
|         returns (IKyberNetwork kyber) | ||||
|     { | ||||
|         return IKyberNetwork(KYBER_NETWORK_PROXY_ADDRESS); | ||||
|     } | ||||
|  | ||||
|     /// @dev An overridable way to retrieve the WETH contract address. | ||||
|     /// @return weth The WETH contract address. | ||||
|     function _getWETHAddress() | ||||
|         internal | ||||
|         view | ||||
|         returns (address weth) | ||||
|     { | ||||
|         return WETH_ADDRESS; | ||||
|     } | ||||
| } | ||||
| @@ -23,11 +23,13 @@ import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFacto | ||||
| import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol"; | ||||
| import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; | ||||
| import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; | ||||
| import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; | ||||
| import "./IDevUtils.sol"; | ||||
| import "./IERC20BridgeSampler.sol"; | ||||
| import "./IEth2Dai.sol"; | ||||
| import "./IKyberNetwork.sol"; | ||||
| import "./IUniswapExchangeQuotes.sol"; | ||||
| import "./DeploymentConstants.sol"; | ||||
|  | ||||
|  | ||||
| contract ERC20BridgeSampler is | ||||
| @@ -35,29 +37,102 @@ contract ERC20BridgeSampler is | ||||
|     DeploymentConstants | ||||
| { | ||||
|     bytes4 constant internal ERC20_PROXY_ID = 0xf47261b0; // bytes4(keccak256("ERC20Token(address)")); | ||||
|     uint256 constant internal KYBER_SAMPLE_CALL_GAS = 1500e3; | ||||
|     uint256 constant internal UNISWAP_SAMPLE_CALL_GAS = 150e3; | ||||
|     uint256 constant internal ETH2DAI_SAMPLE_CALL_GAS = 1000e3; | ||||
|  | ||||
|     /// @dev Query batches of native orders and sample sell quotes on multiple DEXes at once. | ||||
|     /// @param orders Batches of Native orders to query. | ||||
|     /// @param orderSignatures Batches of Signatures for each respective order in `orders`. | ||||
|     /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. | ||||
|     /// @param takerTokenAmounts Batches of Taker token sell amount for each sample. | ||||
|     /// @return ordersAndSamples How much taker asset can be filled | ||||
|     ///         by each order in `orders`. Maker amounts bought for each source at | ||||
|     ///         each taker token amount. First indexed by source index, then sample | ||||
|     ///         index. | ||||
|     function queryBatchOrdersAndSampleSells( | ||||
|         LibOrder.Order[][] memory orders, | ||||
|         bytes[][] memory orderSignatures, | ||||
|         address[] memory sources, | ||||
|         uint256[][] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns ( | ||||
|             OrdersAndSample[] memory ordersAndSamples | ||||
|         ) | ||||
|     { | ||||
|         ordersAndSamples = new OrdersAndSample[](orders.length); | ||||
|         for (uint256 i = 0; i != orders.length; i++) { | ||||
|             ( | ||||
|                 uint256[] memory orderFillableAssetAmounts, | ||||
|                 uint256[][] memory tokenAmountsBySource | ||||
|             ) = queryOrdersAndSampleSells(orders[i], orderSignatures[i], sources, takerTokenAmounts[i]); | ||||
|             ordersAndSamples[i].orderFillableAssetAmounts = orderFillableAssetAmounts; | ||||
|             ordersAndSamples[i].tokenAmountsBySource = tokenAmountsBySource; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Query batches of native orders and sample buy quotes on multiple DEXes at once. | ||||
|     /// @param orders Batches of Native orders to query. | ||||
|     /// @param orderSignatures Batches of Signatures for each respective order in `orders`. | ||||
|     /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. | ||||
|     /// @param makerTokenAmounts Batches of Maker token sell amount for each sample. | ||||
|     /// @return ordersAndSamples How much taker asset can be filled | ||||
|     ///         by each order in `orders`. Taker amounts sold for each source at | ||||
|     ///         each maker token amount. First indexed by source index, then sample | ||||
|     ///         index. | ||||
|     function queryBatchOrdersAndSampleBuys( | ||||
|         LibOrder.Order[][] memory orders, | ||||
|         bytes[][] memory orderSignatures, | ||||
|         address[] memory sources, | ||||
|         uint256[][] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns ( | ||||
|             OrdersAndSample[] memory ordersAndSamples | ||||
|         ) | ||||
|     { | ||||
|         ordersAndSamples = new OrdersAndSample[](orders.length); | ||||
|         for (uint256 i = 0; i != orders.length; i++) { | ||||
|             ( | ||||
|                 uint256[] memory orderFillableAssetAmounts, | ||||
|                 uint256[][] memory tokenAmountsBySource | ||||
|             ) = queryOrdersAndSampleBuys(orders[i], orderSignatures[i], sources, makerTokenAmounts[i]); | ||||
|             ordersAndSamples[i].orderFillableAssetAmounts = orderFillableAssetAmounts; | ||||
|             ordersAndSamples[i].tokenAmountsBySource = tokenAmountsBySource; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Query native orders and sample sell quotes on multiple DEXes at once. | ||||
|     /// @param orders Native orders to query. | ||||
|     /// @param orderSignatures Signatures for each respective order in `orders`. | ||||
|     /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return orderInfos `OrderInfo`s for each order in `orders`. | ||||
|     /// @return orderFillableTakerAssetAmounts How much taker asset can be filled | ||||
|     ///         by each order in `orders`. | ||||
|     /// @return makerTokenAmountsBySource Maker amounts bought for each source at | ||||
|     ///         each taker token amount. First indexed by source index, then sample | ||||
|     ///         index. | ||||
|     function queryOrdersAndSampleSells( | ||||
|         LibOrder.Order[] memory orders, | ||||
|         bytes[] memory orderSignatures, | ||||
|         address[] memory sources, | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns ( | ||||
|             LibOrder.OrderInfo[] memory orderInfos, | ||||
|             uint256[] memory orderFillableTakerAssetAmounts, | ||||
|             uint256[][] memory makerTokenAmountsBySource | ||||
|         ) | ||||
|     { | ||||
|         require(orders.length != 0, "EMPTY_ORDERS"); | ||||
|         orderInfos = queryOrders(orders); | ||||
|         require(orders.length != 0, "ERC20BridgeSampler/EMPTY_ORDERS"); | ||||
|         orderFillableTakerAssetAmounts = getOrderFillableTakerAssetAmounts( | ||||
|             orders, | ||||
|             orderSignatures | ||||
|         ); | ||||
|         makerTokenAmountsBySource = sampleSells( | ||||
|             sources, | ||||
|             _assetDataToTokenAddress(orders[0].takerAssetData), | ||||
| @@ -68,26 +143,32 @@ contract ERC20BridgeSampler is | ||||
|  | ||||
|     /// @dev Query native orders and sample buy quotes on multiple DEXes at once. | ||||
|     /// @param orders Native orders to query. | ||||
|     /// @param orderSignatures Signatures for each respective order in `orders`. | ||||
|     /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return orderInfos `OrderInfo`s for each order in `orders`. | ||||
|     /// @return orderFillableMakerAssetAmounts How much maker asset can be filled | ||||
|     ///         by each order in `orders`. | ||||
|     /// @return takerTokenAmountsBySource Taker amounts sold for each source at | ||||
|     ///         each maker token amount. First indexed by source index, then sample | ||||
|     ///         index. | ||||
|     function queryOrdersAndSampleBuys( | ||||
|         LibOrder.Order[] memory orders, | ||||
|         bytes[] memory orderSignatures, | ||||
|         address[] memory sources, | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns ( | ||||
|             LibOrder.OrderInfo[] memory orderInfos, | ||||
|             uint256[] memory orderFillableMakerAssetAmounts, | ||||
|             uint256[][] memory makerTokenAmountsBySource | ||||
|         ) | ||||
|     { | ||||
|         require(orders.length != 0, "EMPTY_ORDERS"); | ||||
|         orderInfos = queryOrders(orders); | ||||
|         require(orders.length != 0, "ERC20BridgeSampler/EMPTY_ORDERS"); | ||||
|         orderFillableMakerAssetAmounts = getOrderFillableMakerAssetAmounts( | ||||
|             orders, | ||||
|             orderSignatures | ||||
|         ); | ||||
|         makerTokenAmountsBySource = sampleBuys( | ||||
|             sources, | ||||
|             _assetDataToTokenAddress(orders[0].takerAssetData), | ||||
| @@ -96,18 +177,77 @@ contract ERC20BridgeSampler is | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Queries the status of several native orders. | ||||
|     /// @dev Queries the fillable taker asset amounts of native orders. | ||||
|     ///      Effectively ignores orders that have empty signatures or | ||||
|     /// maker/taker asset amounts (returning 0). | ||||
|     /// @param orders Native orders to query. | ||||
|     /// @return orderInfos Order info for each respective order. | ||||
|     function queryOrders(LibOrder.Order[] memory orders) | ||||
|     /// @param orderSignatures Signatures for each respective order in `orders`. | ||||
|     /// @return orderFillableTakerAssetAmounts How much taker asset can be filled | ||||
|     ///         by each order in `orders`. | ||||
|     function getOrderFillableTakerAssetAmounts( | ||||
|         LibOrder.Order[] memory orders, | ||||
|         bytes[] memory orderSignatures | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (LibOrder.OrderInfo[] memory orderInfos) | ||||
|         returns (uint256[] memory orderFillableTakerAssetAmounts) | ||||
|     { | ||||
|         uint256 numOrders = orders.length; | ||||
|         orderInfos = new LibOrder.OrderInfo[](numOrders); | ||||
|         for (uint256 i = 0; i < numOrders; i++) { | ||||
|             orderInfos[i] = _getExchangeContract().getOrderInfo(orders[i]); | ||||
|         orderFillableTakerAssetAmounts = new uint256[](orders.length); | ||||
|         for (uint256 i = 0; i != orders.length; i++) { | ||||
|             // Ignore orders with no signature or empty maker/taker amounts. | ||||
|             if (orderSignatures[i].length == 0 || | ||||
|                 orders[i].makerAssetAmount == 0 || | ||||
|                 orders[i].takerAssetAmount == 0) { | ||||
|                 orderFillableTakerAssetAmounts[i] = 0; | ||||
|                 continue; | ||||
|             } | ||||
|             ( | ||||
|                 LibOrder.OrderInfo memory orderInfo, | ||||
|                 uint256 fillableTakerAssetAmount, | ||||
|                 bool isValidSignature | ||||
|             ) = IDevUtils(_getDevUtilsAddress()).getOrderRelevantState( | ||||
|                 orders[i], | ||||
|                 orderSignatures[i] | ||||
|             ); | ||||
|             // The fillable amount is zero if the order is not fillable or if the | ||||
|             // signature is invalid. | ||||
|             if (orderInfo.orderStatus != LibOrder.OrderStatus.FILLABLE || | ||||
|                 !isValidSignature) { | ||||
|                 orderFillableTakerAssetAmounts[i] = 0; | ||||
|             } else { | ||||
|                 orderFillableTakerAssetAmounts[i] = fillableTakerAssetAmount; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Queries the fillable taker asset amounts of native orders. | ||||
|     ///      Effectively ignores orders that have empty signatures or | ||||
|     /// @param orders Native orders to query. | ||||
|     /// @param orderSignatures Signatures for each respective order in `orders`. | ||||
|     /// @return orderFillableMakerAssetAmounts How much maker asset can be filled | ||||
|     ///         by each order in `orders`. | ||||
|     function getOrderFillableMakerAssetAmounts( | ||||
|         LibOrder.Order[] memory orders, | ||||
|         bytes[] memory orderSignatures | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory orderFillableMakerAssetAmounts) | ||||
|     { | ||||
|         orderFillableMakerAssetAmounts = getOrderFillableTakerAssetAmounts( | ||||
|             orders, | ||||
|             orderSignatures | ||||
|         ); | ||||
|         // `orderFillableMakerAssetAmounts` now holds taker asset amounts, so | ||||
|         // convert them to maker asset amounts. | ||||
|         for (uint256 i = 0; i < orders.length; ++i) { | ||||
|             if (orderFillableMakerAssetAmounts[i] != 0) { | ||||
|                 orderFillableMakerAssetAmounts[i] = LibMath.getPartialAmountCeil( | ||||
|                     orderFillableMakerAssetAmounts[i], | ||||
|                     orders[i].takerAssetAmount, | ||||
|                     orders[i].makerAssetAmount | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -187,18 +327,27 @@ contract ERC20BridgeSampler is | ||||
|         returns (uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         address _takerToken = takerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : takerToken; | ||||
|         address _makerToken = makerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : makerToken; | ||||
|         address _takerToken = takerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : takerToken; | ||||
|         address _makerToken = makerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : makerToken; | ||||
|         uint256 takerTokenDecimals = _getTokenDecimals(takerToken); | ||||
|         uint256 makerTokenDecimals = _getTokenDecimals(makerToken); | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             (uint256 rate,) = _getKyberNetworkContract().getExpectedRate( | ||||
|                 _takerToken, | ||||
|                 _makerToken, | ||||
|                 takerTokenAmounts[i] | ||||
|             ); | ||||
|             (bool didSucceed, bytes memory resultData) = | ||||
|                 _getKyberNetworkProxyAddress().staticcall.gas(KYBER_SAMPLE_CALL_GAS)( | ||||
|                     abi.encodeWithSelector( | ||||
|                         IKyberNetwork(0).getExpectedRate.selector, | ||||
|                         _takerToken, | ||||
|                         _makerToken, | ||||
|                         takerTokenAmounts[i] | ||||
|                     )); | ||||
|             uint256 rate = 0; | ||||
|             if (didSucceed) { | ||||
|                 rate = abi.decode(resultData, (uint256)); | ||||
|             } else { | ||||
|                 break; | ||||
|             } | ||||
|             makerTokenAmounts[i] = | ||||
|                 rate * | ||||
|                 takerTokenAmounts[i] * | ||||
| @@ -227,11 +376,21 @@ contract ERC20BridgeSampler is | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             makerTokenAmounts[i] = _getEth2DaiContract().getBuyAmount( | ||||
|                 makerToken, | ||||
|                 takerToken, | ||||
|                 takerTokenAmounts[i] | ||||
|             ); | ||||
|             (bool didSucceed, bytes memory resultData) = | ||||
|                 _getEth2DaiAddress().staticcall.gas(ETH2DAI_SAMPLE_CALL_GAS)( | ||||
|                     abi.encodeWithSelector( | ||||
|                         IEth2Dai(0).getBuyAmount.selector, | ||||
|                         makerToken, | ||||
|                         takerToken, | ||||
|                         takerTokenAmounts[i] | ||||
|                     )); | ||||
|             uint256 buyAmount = 0; | ||||
|             if (didSucceed) { | ||||
|                 buyAmount = abi.decode(resultData, (uint256)); | ||||
|             } else{ | ||||
|                 break; | ||||
|             } | ||||
|             makerTokenAmounts[i] = buyAmount; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -254,11 +413,21 @@ contract ERC20BridgeSampler is | ||||
|         uint256 numSamples = makerTokenAmounts.length; | ||||
|         takerTokenAmounts = new uint256[](numSamples); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             takerTokenAmounts[i] = _getEth2DaiContract().getPayAmount( | ||||
|                 takerToken, | ||||
|                 makerToken, | ||||
|                 makerTokenAmounts[i] | ||||
|             ); | ||||
|             (bool didSucceed, bytes memory resultData) = | ||||
|                 _getEth2DaiAddress().staticcall.gas(ETH2DAI_SAMPLE_CALL_GAS)( | ||||
|                     abi.encodeWithSelector( | ||||
|                         IEth2Dai(0).getPayAmount.selector, | ||||
|                         takerToken, | ||||
|                         makerToken, | ||||
|                         makerTokenAmounts[i] | ||||
|                     )); | ||||
|             uint256 sellAmount = 0; | ||||
|             if (didSucceed) { | ||||
|                 sellAmount = abi.decode(resultData, (uint256)); | ||||
|             } else { | ||||
|                 break; | ||||
|             } | ||||
|             takerTokenAmounts[i] = sellAmount; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -280,26 +449,43 @@ contract ERC20BridgeSampler is | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|         IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ? | ||||
|         IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ? | ||||
|             IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken); | ||||
|         IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWETHAddress() ? | ||||
|         IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ? | ||||
|             IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             if (makerToken == _getWETHAddress()) { | ||||
|                 makerTokenAmounts[i] = takerTokenExchange.getTokenToEthInputPrice( | ||||
|             bool didSucceed = true; | ||||
|             if (makerToken == _getWethAddress()) { | ||||
|                 (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                     address(takerTokenExchange), | ||||
|                     takerTokenExchange.getTokenToEthInputPrice.selector, | ||||
|                     takerTokenAmounts[i] | ||||
|                 ); | ||||
|             } else if (takerToken == _getWETHAddress()) { | ||||
|                 makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice( | ||||
|             } else if (takerToken == _getWethAddress()) { | ||||
|                 (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                     address(makerTokenExchange), | ||||
|                     makerTokenExchange.getEthToTokenInputPrice.selector, | ||||
|                     takerTokenAmounts[i] | ||||
|                 ); | ||||
|             } else { | ||||
|                 uint256 ethBought = takerTokenExchange.getTokenToEthInputPrice( | ||||
|                 uint256 ethBought; | ||||
|                 (ethBought, didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                     address(takerTokenExchange), | ||||
|                     takerTokenExchange.getTokenToEthInputPrice.selector, | ||||
|                     takerTokenAmounts[i] | ||||
|                 ); | ||||
|                 makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice( | ||||
|                     ethBought | ||||
|                 ); | ||||
|                 if (ethBought != 0) { | ||||
|                     (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                         address(makerTokenExchange), | ||||
|                         makerTokenExchange.getEthToTokenInputPrice.selector, | ||||
|                         ethBought | ||||
|                     ); | ||||
|                 } else { | ||||
|                     makerTokenAmounts[i] = 0; | ||||
|                 } | ||||
|             } | ||||
|             if (!didSucceed) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -322,26 +508,43 @@ contract ERC20BridgeSampler is | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         uint256 numSamples = makerTokenAmounts.length; | ||||
|         takerTokenAmounts = new uint256[](numSamples); | ||||
|         IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ? | ||||
|         IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ? | ||||
|             IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken); | ||||
|         IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWETHAddress() ? | ||||
|         IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ? | ||||
|             IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             if (makerToken == _getWETHAddress()) { | ||||
|                 takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice( | ||||
|             bool didSucceed = true; | ||||
|             if (makerToken == _getWethAddress()) { | ||||
|                 (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                     address(takerTokenExchange), | ||||
|                     takerTokenExchange.getTokenToEthOutputPrice.selector, | ||||
|                     makerTokenAmounts[i] | ||||
|                 ); | ||||
|             } else if (takerToken == _getWETHAddress()) { | ||||
|                 takerTokenAmounts[i] = makerTokenExchange.getEthToTokenOutputPrice( | ||||
|             } else if (takerToken == _getWethAddress()) { | ||||
|                 (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                     address(makerTokenExchange), | ||||
|                     makerTokenExchange.getEthToTokenOutputPrice.selector, | ||||
|                     makerTokenAmounts[i] | ||||
|                 ); | ||||
|             } else { | ||||
|                 uint256 ethSold = makerTokenExchange.getEthToTokenOutputPrice( | ||||
|                 uint256 ethSold; | ||||
|                 (ethSold, didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                     address(makerTokenExchange), | ||||
|                     makerTokenExchange.getEthToTokenOutputPrice.selector, | ||||
|                     makerTokenAmounts[i] | ||||
|                 ); | ||||
|                 takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice( | ||||
|                     ethSold | ||||
|                 ); | ||||
|                 if (ethSold != 0) { | ||||
|                     (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                         address(takerTokenExchange), | ||||
|                         takerTokenExchange.getTokenToEthOutputPrice.selector, | ||||
|                         ethSold | ||||
|                     ); | ||||
|                 } else { | ||||
|                     takerTokenAmounts[i] = 0; | ||||
|                 } | ||||
|             } | ||||
|             if (!didSucceed) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -357,6 +560,36 @@ contract ERC20BridgeSampler is | ||||
|         return LibERC20Token.decimals(tokenAddress); | ||||
|     } | ||||
|  | ||||
|     /// @dev Gracefully calls a Uniswap pricing function. | ||||
|     /// @param uniswapExchangeAddress Address of an `IUniswapExchangeQuotes` exchange. | ||||
|     /// @param functionSelector Selector of the target function. | ||||
|     /// @param inputAmount Quantity parameter particular to the pricing function. | ||||
|     /// @return outputAmount The returned amount from the function call. Will be | ||||
|     ///         zero if the call fails or if `uniswapExchangeAddress` is zero. | ||||
|     function _callUniswapExchangePriceFunction( | ||||
|         address uniswapExchangeAddress, | ||||
|         bytes4 functionSelector, | ||||
|         uint256 inputAmount | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|         returns (uint256 outputAmount, bool didSucceed) | ||||
|     { | ||||
|         if (uniswapExchangeAddress == address(0)) { | ||||
|             return (outputAmount, didSucceed); | ||||
|         } | ||||
|         bytes memory resultData; | ||||
|         (didSucceed, resultData) = | ||||
|             uniswapExchangeAddress.staticcall.gas(UNISWAP_SAMPLE_CALL_GAS)( | ||||
|                 abi.encodeWithSelector( | ||||
|                     functionSelector, | ||||
|                     inputAmount | ||||
|                 )); | ||||
|         if (didSucceed) { | ||||
|             outputAmount = abi.decode(resultData, (uint256)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Samples a supported sell source, defined by its address. | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
| @@ -373,16 +606,16 @@ contract ERC20BridgeSampler is | ||||
|         view | ||||
|         returns (uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         if (source == address(_getEth2DaiContract())) { | ||||
|         if (source == _getEth2DaiAddress()) { | ||||
|             return sampleSellsFromEth2Dai(takerToken, makerToken, takerTokenAmounts); | ||||
|         } | ||||
|         if (source == address(_getUniswapExchangeFactoryContract())) { | ||||
|         if (source == _getUniswapExchangeFactoryAddress()) { | ||||
|             return sampleSellsFromUniswap(takerToken, makerToken, takerTokenAmounts); | ||||
|         } | ||||
|         if (source == address(_getKyberNetworkContract())) { | ||||
|         if (source == _getKyberNetworkProxyAddress()) { | ||||
|             return sampleSellsFromKyberNetwork(takerToken, makerToken, takerTokenAmounts); | ||||
|         } | ||||
|         revert("UNSUPPORTED_SOURCE"); | ||||
|         revert("ERC20BridgeSampler/UNSUPPORTED_SOURCE"); | ||||
|     } | ||||
|  | ||||
|     /// @dev Samples a supported buy source, defined by its address. | ||||
| @@ -401,13 +634,13 @@ contract ERC20BridgeSampler is | ||||
|         view | ||||
|         returns (uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         if (source == address(_getEth2DaiContract())) { | ||||
|         if (source == _getEth2DaiAddress()) { | ||||
|             return sampleBuysFromEth2Dai(takerToken, makerToken, makerTokenAmounts); | ||||
|         } | ||||
|         if (source == address(_getUniswapExchangeFactoryContract())) { | ||||
|         if (source == _getUniswapExchangeFactoryAddress()) { | ||||
|             return sampleBuysFromUniswap(takerToken, makerToken, makerTokenAmounts); | ||||
|         } | ||||
|         revert("UNSUPPORTED_SOURCE"); | ||||
|         revert("ERC20BridgeSampler/UNSUPPORTED_SOURCE"); | ||||
|     } | ||||
|  | ||||
|     /// @dev Retrive an existing Uniswap exchange contract. | ||||
| @@ -420,9 +653,9 @@ contract ERC20BridgeSampler is | ||||
|         returns (IUniswapExchangeQuotes exchange) | ||||
|     { | ||||
|         exchange = IUniswapExchangeQuotes( | ||||
|             address(_getUniswapExchangeFactoryContract().getExchange(tokenAddress)) | ||||
|             address(IUniswapExchangeFactory(_getUniswapExchangeFactoryAddress()) | ||||
|             .getExchange(tokenAddress)) | ||||
|         ); | ||||
|         require(address(exchange) != address(0), "UNSUPPORTED_UNISWAP_EXCHANGE"); | ||||
|     } | ||||
|  | ||||
|     /// @dev Extract the token address from ERC20 proxy asset data. | ||||
| @@ -433,19 +666,19 @@ contract ERC20BridgeSampler is | ||||
|         pure | ||||
|         returns (address tokenAddress) | ||||
|     { | ||||
|         require(assetData.length == 36, "INVALID_ASSET_DATA"); | ||||
|         require(assetData.length == 36, "ERC20BridgeSampler/INVALID_ASSET_DATA"); | ||||
|         bytes4 selector; | ||||
|         assembly { | ||||
|             selector := and(mload(add(assetData, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000) | ||||
|             tokenAddress := mload(add(assetData, 0x24)) | ||||
|         } | ||||
|         require(selector == ERC20_PROXY_ID, "UNSUPPORTED_ASSET_PROXY"); | ||||
|         require(selector == ERC20_PROXY_ID, "ERC20BridgeSampler/UNSUPPORTED_ASSET_PROXY"); | ||||
|     } | ||||
|  | ||||
|     function _assertValidPair(address makerToken, address takerToken) | ||||
|         private | ||||
|         pure | ||||
|     { | ||||
|         require(makerToken != takerToken, "INVALID_TOKEN_PAIR"); | ||||
|         require(makerToken != takerToken, "ERC20BridgeSampler/INVALID_TOKEN_PAIR"); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										45
									
								
								contracts/erc20-bridge-sampler/contracts/src/IDevUtils.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								contracts/erc20-bridge-sampler/contracts/src/IDevUtils.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2019 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.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; | ||||
|  | ||||
|  | ||||
| interface IDevUtils { | ||||
|  | ||||
|     /// @dev Fetches all order-relevant information needed to validate if the supplied order is fillable. | ||||
|     /// @param order The order structure. | ||||
|     /// @param signature Signature provided by maker that proves the order's authenticity. | ||||
|     /// `0x01` can always be provided if the signature does not need to be validated. | ||||
|     /// @return The orderInfo (hash, status, and `takerAssetAmount` already filled for the given order), | ||||
|     /// fillableTakerAssetAmount (amount of the order's `takerAssetAmount` that is fillable given all on-chain state), | ||||
|     /// and isValidSignature (validity of the provided signature). | ||||
|     /// NOTE: If the `takerAssetData` encodes data for multiple assets, `fillableTakerAssetAmount` will represent a "scaled" | ||||
|     /// amount, meaning it must be multiplied by all the individual asset amounts within the `takerAssetData` to get the final | ||||
|     /// amount of each asset that can be filled. | ||||
|     function getOrderRelevantState(LibOrder.Order calldata order, bytes calldata signature) | ||||
|         external | ||||
|         view | ||||
|         returns ( | ||||
|             LibOrder.OrderInfo memory orderInfo, | ||||
|             uint256 fillableTakerAssetAmount, | ||||
|             bool isValidSignature | ||||
|         ); | ||||
| } | ||||
| @@ -19,59 +19,128 @@ | ||||
| pragma solidity ^0.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; | ||||
| import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; | ||||
|  | ||||
|  | ||||
| interface IERC20BridgeSampler { | ||||
|     struct OrdersAndSample { | ||||
|         uint256[] orderFillableAssetAmounts; | ||||
|         uint256[][] tokenAmountsBySource; | ||||
|     } | ||||
|  | ||||
|     /// @dev Query native orders and sample sell orders on multiple DEXes at once. | ||||
|     /// @dev Query batches of native orders and sample sell quotes on multiple DEXes at once. | ||||
|     /// @param orders Batches of Native orders to query. | ||||
|     /// @param orderSignatures Batches of Signatures for each respective order in `orders`. | ||||
|     /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. | ||||
|     /// @param takerTokenAmounts Batches of Taker token sell amount for each sample. | ||||
|     /// @return ordersAndSamples How much taker asset can be filled | ||||
|     ///         by each order in `orders`. Maker amounts bought for each source at | ||||
|     ///         each taker token amount. First indexed by source index, then sample | ||||
|     ///         index. | ||||
|     function queryBatchOrdersAndSampleSells( | ||||
|         LibOrder.Order[][] calldata orders, | ||||
|         bytes[][] calldata orderSignatures, | ||||
|         address[] calldata sources, | ||||
|         uint256[][] calldata takerTokenAmounts | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns ( | ||||
|             OrdersAndSample[] memory ordersAndSamples | ||||
|         ); | ||||
|  | ||||
|     /// @dev Query batches of native orders and sample buy quotes on multiple DEXes at once. | ||||
|     /// @param orders Batches of Native orders to query. | ||||
|     /// @param orderSignatures Batches of Signatures for each respective order in `orders`. | ||||
|     /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. | ||||
|     /// @param makerTokenAmounts Batches of Maker token sell amount for each sample. | ||||
|     /// @return ordersAndSamples How much taker asset can be filled | ||||
|     ///         by each order in `orders`. Taker amounts sold for each source at | ||||
|     ///         each maker token amount. First indexed by source index, then sample | ||||
|     ///         index | ||||
|     function queryBatchOrdersAndSampleBuys( | ||||
|         LibOrder.Order[][] calldata orders, | ||||
|         bytes[][] calldata orderSignatures, | ||||
|         address[] calldata sources, | ||||
|         uint256[][] calldata makerTokenAmounts | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns ( | ||||
|             OrdersAndSample[] memory ordersAndSamples | ||||
|         ); | ||||
|  | ||||
|     /// @dev Query native orders and sample sell quotes on multiple DEXes at once. | ||||
|     /// @param orders Native orders to query. | ||||
|     /// @param sources Address of each DEX. Passing in an unknown DEX will throw. | ||||
|     /// @param takerTokenAmounts Taker sell amount for each sample. | ||||
|     /// @return orderInfos `OrderInfo`s for each order in `orders`. | ||||
|     /// @param orderSignatures Signatures for each respective order in `orders`. | ||||
|     /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return orderFillableTakerAssetAmounts How much taker asset can be filled | ||||
|     ///         by each order in `orders`. | ||||
|     /// @return makerTokenAmountsBySource Maker amounts bought for each source at | ||||
|     ///         each taker token amount. First indexed by source index, then sample | ||||
|     ///         index. | ||||
|     function queryOrdersAndSampleSells( | ||||
|         LibOrder.Order[] calldata orders, | ||||
|         bytes[] calldata orderSignatures, | ||||
|         address[] calldata sources, | ||||
|         uint256[] calldata takerTokenAmounts | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns ( | ||||
|             LibOrder.OrderInfo[] memory orderInfos, | ||||
|             uint256[] memory orderFillableTakerAssetAmounts, | ||||
|             uint256[][] memory makerTokenAmountsBySource | ||||
|         ); | ||||
|  | ||||
|     /// @dev Query native orders and sample buy orders on multiple DEXes at once. | ||||
|     /// @dev Query native orders and sample buy quotes on multiple DEXes at once. | ||||
|     /// @param orders Native orders to query. | ||||
|     /// @param sources Address of each DEX. Passing in an unknown DEX will throw. | ||||
|     /// @param makerTokenAmounts Maker sell amount for each sample. | ||||
|     /// @return orderInfos `OrderInfo`s for each order in `orders`. | ||||
|     /// @param orderSignatures Signatures for each respective order in `orders`. | ||||
|     /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return orderFillableMakerAssetAmounts How much maker asset can be filled | ||||
|     ///         by each order in `orders`. | ||||
|     /// @return takerTokenAmountsBySource Taker amounts sold for each source at | ||||
|     ///         each maker token amount. First indexed by source index, then sample | ||||
|     ///         index. | ||||
|     function queryOrdersAndSampleBuys( | ||||
|         LibOrder.Order[] calldata orders, | ||||
|         bytes[] calldata orderSignatures, | ||||
|         address[] calldata sources, | ||||
|         uint256[] calldata makerTokenAmounts | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns ( | ||||
|             LibOrder.OrderInfo[] memory orderInfos, | ||||
|             uint256[] memory orderFillableMakerAssetAmounts, | ||||
|             uint256[][] memory makerTokenAmountsBySource | ||||
|         ); | ||||
|  | ||||
|     /// @dev Queries the status of several native orders. | ||||
|     /// @dev Queries the fillable taker asset amounts of native orders. | ||||
|     /// @param orders Native orders to query. | ||||
|     /// @return orderInfos Order info for each respective order. | ||||
|     function queryOrders(LibOrder.Order[] calldata orders) | ||||
|     /// @param orderSignatures Signatures for each respective order in `orders`. | ||||
|     /// @return orderFillableTakerAssetAmounts How much taker asset can be filled | ||||
|     ///         by each order in `orders`. | ||||
|     function getOrderFillableTakerAssetAmounts( | ||||
|         LibOrder.Order[] calldata orders, | ||||
|         bytes[] calldata orderSignatures | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns (LibOrder.OrderInfo[] memory orderInfos); | ||||
|         returns (uint256[] memory orderFillableTakerAssetAmounts); | ||||
|  | ||||
|     /// @dev Queries the fillable maker asset amounts of native orders. | ||||
|     /// @param orders Native orders to query. | ||||
|     /// @param orderSignatures Signatures for each respective order in `orders`. | ||||
|     /// @return orderFillableMakerAssetAmounts How much maker asset can be filled | ||||
|     ///         by each order in `orders`. | ||||
|     function getOrderFillableMakerAssetAmounts( | ||||
|         LibOrder.Order[] calldata orders, | ||||
|         bytes[] calldata orderSignatures | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256[] memory orderFillableMakerAssetAmounts); | ||||
|  | ||||
|     /// @dev Sample sell quotes on multiple DEXes at once. | ||||
|     /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. | ||||
|   | ||||
| @@ -23,6 +23,7 @@ import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; | ||||
| import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; | ||||
| import "../src/ERC20BridgeSampler.sol"; | ||||
| import "../src/IEth2Dai.sol"; | ||||
| import "../src/IDevUtils.sol"; | ||||
| import "../src/IKyberNetwork.sol"; | ||||
|  | ||||
|  | ||||
| @@ -90,9 +91,28 @@ library LibDeterministicQuotes { | ||||
| } | ||||
|  | ||||
|  | ||||
| contract FailTrigger { | ||||
|  | ||||
|     // Give this address a balance to force operations to fail. | ||||
|     address payable constant public FAILURE_ADDRESS = 0xe9dB8717BC5DFB20aaf538b4a5a02B7791FF430C; | ||||
|  | ||||
|     // Funds `FAILURE_ADDRESS`. | ||||
|     function enableFailTrigger() external payable { | ||||
|         FAILURE_ADDRESS.transfer(msg.value); | ||||
|     } | ||||
|  | ||||
|     function _revertIfShouldFail() internal view { | ||||
|         if (FAILURE_ADDRESS.balance != 0) { | ||||
|             revert("FAIL_TRIGGERED"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| contract TestERC20BridgeSamplerUniswapExchange is | ||||
|     IUniswapExchangeQuotes, | ||||
|     DeploymentConstants | ||||
|     DeploymentConstants, | ||||
|     FailTrigger | ||||
| { | ||||
|     bytes32 constant private BASE_SALT = 0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab; | ||||
|  | ||||
| @@ -112,10 +132,11 @@ contract TestERC20BridgeSamplerUniswapExchange is | ||||
|         view | ||||
|         returns (uint256 tokensBought) | ||||
|     { | ||||
|         _revertIfShouldFail(); | ||||
|         return LibDeterministicQuotes.getDeterministicSellQuote( | ||||
|             salt, | ||||
|             tokenAddress, | ||||
|             WETH_ADDRESS, | ||||
|             _getWethAddress(), | ||||
|             ethSold | ||||
|         ); | ||||
|     } | ||||
| @@ -128,9 +149,10 @@ contract TestERC20BridgeSamplerUniswapExchange is | ||||
|         view | ||||
|         returns (uint256 ethSold) | ||||
|     { | ||||
|         _revertIfShouldFail(); | ||||
|         return LibDeterministicQuotes.getDeterministicBuyQuote( | ||||
|             salt, | ||||
|             WETH_ADDRESS, | ||||
|             _getWethAddress(), | ||||
|             tokenAddress, | ||||
|             tokensBought | ||||
|         ); | ||||
| @@ -144,10 +166,11 @@ contract TestERC20BridgeSamplerUniswapExchange is | ||||
|         view | ||||
|         returns (uint256 ethBought) | ||||
|     { | ||||
|         _revertIfShouldFail(); | ||||
|         return LibDeterministicQuotes.getDeterministicSellQuote( | ||||
|             salt, | ||||
|             tokenAddress, | ||||
|             WETH_ADDRESS, | ||||
|             _getWethAddress(), | ||||
|             tokensSold | ||||
|         ); | ||||
|     } | ||||
| @@ -160,9 +183,10 @@ contract TestERC20BridgeSamplerUniswapExchange is | ||||
|         view | ||||
|         returns (uint256 tokensSold) | ||||
|     { | ||||
|         _revertIfShouldFail(); | ||||
|         return LibDeterministicQuotes.getDeterministicBuyQuote( | ||||
|             salt, | ||||
|             WETH_ADDRESS, | ||||
|             _getWethAddress(), | ||||
|             tokenAddress, | ||||
|             ethBought | ||||
|         ); | ||||
| @@ -172,7 +196,8 @@ contract TestERC20BridgeSamplerUniswapExchange is | ||||
|  | ||||
| contract TestERC20BridgeSamplerKyberNetwork is | ||||
|     IKyberNetwork, | ||||
|     DeploymentConstants | ||||
|     DeploymentConstants, | ||||
|     FailTrigger | ||||
| { | ||||
|     bytes32 constant private SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7; | ||||
|     address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; | ||||
| @@ -187,8 +212,9 @@ contract TestERC20BridgeSamplerKyberNetwork is | ||||
|         view | ||||
|         returns (uint256 expectedRate, uint256) | ||||
|     { | ||||
|         fromToken = fromToken == ETH_ADDRESS ? WETH_ADDRESS : fromToken; | ||||
|         toToken = toToken == ETH_ADDRESS ? WETH_ADDRESS : toToken; | ||||
|         _revertIfShouldFail(); | ||||
|         fromToken = fromToken == ETH_ADDRESS ? _getWethAddress() : fromToken; | ||||
|         toToken = toToken == ETH_ADDRESS ? _getWethAddress() : toToken; | ||||
|         expectedRate = LibDeterministicQuotes.getDeterministicRate( | ||||
|             SALT, | ||||
|             fromToken, | ||||
| @@ -199,7 +225,8 @@ contract TestERC20BridgeSamplerKyberNetwork is | ||||
|  | ||||
|  | ||||
| contract TestERC20BridgeSamplerEth2Dai is | ||||
|     IEth2Dai | ||||
|     IEth2Dai, | ||||
|     FailTrigger | ||||
| { | ||||
|     bytes32 constant private SALT = 0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7; | ||||
|  | ||||
| @@ -213,6 +240,7 @@ contract TestERC20BridgeSamplerEth2Dai is | ||||
|         view | ||||
|         returns (uint256 buyAmount) | ||||
|     { | ||||
|         _revertIfShouldFail(); | ||||
|         return LibDeterministicQuotes.getDeterministicSellQuote( | ||||
|             SALT, | ||||
|             payToken, | ||||
| @@ -231,6 +259,7 @@ contract TestERC20BridgeSamplerEth2Dai is | ||||
|         view | ||||
|         returns (uint256 payAmount) | ||||
|     { | ||||
|         _revertIfShouldFail(); | ||||
|         return LibDeterministicQuotes.getDeterministicBuyQuote( | ||||
|             SALT, | ||||
|             payToken, | ||||
| @@ -269,12 +298,15 @@ contract TestERC20BridgeSamplerUniswapExchangeFactory is | ||||
|  | ||||
|  | ||||
| contract TestERC20BridgeSampler is | ||||
|     ERC20BridgeSampler | ||||
|     ERC20BridgeSampler, | ||||
|     FailTrigger | ||||
| { | ||||
|     TestERC20BridgeSamplerUniswapExchangeFactory public uniswap; | ||||
|     TestERC20BridgeSamplerEth2Dai public eth2Dai; | ||||
|     TestERC20BridgeSamplerKyberNetwork public kyber; | ||||
|  | ||||
|     uint8 private constant MAX_ORDER_STATUS = uint8(LibOrder.OrderStatus.CANCELLED) + 1; | ||||
|  | ||||
|     constructor() public { | ||||
|         uniswap = new TestERC20BridgeSamplerUniswapExchangeFactory(); | ||||
|         eth2Dai = new TestERC20BridgeSamplerEth2Dai(); | ||||
| @@ -288,18 +320,33 @@ contract TestERC20BridgeSampler is | ||||
|         uniswap.createTokenExchanges(tokenAddresses); | ||||
|     } | ||||
|  | ||||
|     // `IExchange.getOrderInfo()`, overridden to return deterministic order infos. | ||||
|     function getOrderInfo(LibOrder.Order memory order) | ||||
|     // `IDevUtils.getOrderRelevantState()`, overridden to return deterministic | ||||
|     // states. | ||||
|     function getOrderRelevantState( | ||||
|         LibOrder.Order memory order, | ||||
|         bytes memory | ||||
|     ) | ||||
|         public | ||||
|         pure | ||||
|         returns (LibOrder.OrderInfo memory orderInfo) | ||||
|         view | ||||
|         returns ( | ||||
|             LibOrder.OrderInfo memory orderInfo, | ||||
|             uint256 fillableTakerAssetAmount, | ||||
|             bool isValidSignature | ||||
|         ) | ||||
|     { | ||||
|         // The order hash is just the hash of the salt. | ||||
|         bytes32 orderHash = keccak256(abi.encode(order.salt)); | ||||
|         // Everything else is derived from the hash. | ||||
|         orderInfo.orderHash = orderHash; | ||||
|         orderInfo.orderStatus = uint8(uint256(orderHash) % uint8(-1)); | ||||
|         if (uint256(orderHash) % 100 > 90) { | ||||
|             orderInfo.orderStatus = LibOrder.OrderStatus.FULLY_FILLED; | ||||
|         } else { | ||||
|             orderInfo.orderStatus = LibOrder.OrderStatus.FILLABLE; | ||||
|         } | ||||
|         orderInfo.orderTakerAssetFilledAmount = uint256(orderHash) % order.takerAssetAmount; | ||||
|         fillableTakerAssetAmount = | ||||
|             order.takerAssetAmount - orderInfo.orderTakerAssetFilledAmount; | ||||
|         isValidSignature = uint256(orderHash) % 2 == 1; | ||||
|     } | ||||
|  | ||||
|     // Overriden to return deterministic decimals. | ||||
| @@ -312,38 +359,38 @@ contract TestERC20BridgeSampler is | ||||
|     } | ||||
|  | ||||
|     // Overriden to point to a this contract. | ||||
|     function _getExchangeContract() | ||||
|     function _getDevUtilsAddress() | ||||
|         internal | ||||
|         view | ||||
|         returns (IExchange zeroex) | ||||
|         returns (address devUtilAddress) | ||||
|     { | ||||
|         return IExchange(address(this)); | ||||
|         return address(this); | ||||
|     } | ||||
|  | ||||
|     // Overriden to point to a custom contract. | ||||
|     function _getEth2DaiContract() | ||||
|     function _getEth2DaiAddress() | ||||
|         internal | ||||
|         view | ||||
|         returns (IEth2Dai eth2dai_) | ||||
|         returns (address eth2daiAddress) | ||||
|     { | ||||
|         return eth2Dai; | ||||
|         return address(eth2Dai); | ||||
|     } | ||||
|  | ||||
|     // Overriden to point to a custom contract. | ||||
|     function _getUniswapExchangeFactoryContract() | ||||
|     function _getUniswapExchangeFactoryAddress() | ||||
|         internal | ||||
|         view | ||||
|         returns (IUniswapExchangeFactory uniswap_) | ||||
|         returns (address uniswapAddress) | ||||
|     { | ||||
|         return uniswap; | ||||
|         return address(uniswap); | ||||
|     } | ||||
|  | ||||
|     // Overriden to point to a custom contract. | ||||
|     function _getKyberNetworkContract() | ||||
|     function _getKyberNetworkProxyAddress() | ||||
|         internal | ||||
|         view | ||||
|         returns (IKyberNetwork kyber_) | ||||
|         returns (address kyberAddress) | ||||
|     { | ||||
|         return kyber; | ||||
|         return address(kyber); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0x/contracts-erc20-bridge-sampler", | ||||
|     "version": "1.0.1", | ||||
|     "version": "1.1.0", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -38,7 +38,7 @@ | ||||
|     "config": { | ||||
|         "publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler", | ||||
|         "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", | ||||
|         "abis": "./test/generated-artifacts/@(DeploymentConstants|ERC20BridgeSampler|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|TestERC20BridgeSampler).json" | ||||
|         "abis": "./test/generated-artifacts/@(ERC20BridgeSampler|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|TestERC20BridgeSampler).json" | ||||
|     }, | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
| @@ -50,18 +50,18 @@ | ||||
|     }, | ||||
|     "homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md", | ||||
|     "devDependencies": { | ||||
|         "@0x/abi-gen": "^5.0.1", | ||||
|         "@0x/contracts-asset-proxy": "^3.0.1", | ||||
|         "@0x/contracts-erc20": "^3.0.1", | ||||
|         "@0x/contracts-exchange": "^3.0.1", | ||||
|         "@0x/contracts-exchange-libs": "^4.0.1", | ||||
|         "@0x/contracts-gen": "^2.0.1", | ||||
|         "@0x/contracts-test-utils": "^5.0.0", | ||||
|         "@0x/contracts-utils": "^4.0.1", | ||||
|         "@0x/dev-utils": "^3.0.1", | ||||
|         "@0x/sol-compiler": "^4.0.1", | ||||
|         "@0x/abi-gen": "^5.1.0", | ||||
|         "@0x/contracts-asset-proxy": "^3.1.1", | ||||
|         "@0x/contracts-erc20": "^3.0.4", | ||||
|         "@0x/contracts-exchange": "^3.1.0", | ||||
|         "@0x/contracts-exchange-libs": "^4.1.0", | ||||
|         "@0x/contracts-gen": "^2.0.4", | ||||
|         "@0x/contracts-test-utils": "^5.1.1", | ||||
|         "@0x/contracts-utils": "^4.1.0", | ||||
|         "@0x/dev-utils": "^3.1.1", | ||||
|         "@0x/sol-compiler": "^4.0.4", | ||||
|         "@0x/tslint-config": "^4.0.0", | ||||
|         "@0x/web3-wrapper": "^7.0.1", | ||||
|         "@0x/web3-wrapper": "^7.0.4", | ||||
|         "@types/lodash": "4.14.104", | ||||
|         "@types/mocha": "^5.2.7", | ||||
|         "@types/node": "*", | ||||
| @@ -79,10 +79,10 @@ | ||||
|         "typescript": "3.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0x/base-contract": "^6.0.1", | ||||
|         "@0x/types": "^3.1.0", | ||||
|         "@0x/typescript-typings": "^5.0.0", | ||||
|         "@0x/utils": "^5.1.0", | ||||
|         "@0x/base-contract": "^6.1.0", | ||||
|         "@0x/types": "^3.1.1", | ||||
|         "@0x/typescript-typings": "^5.0.1", | ||||
|         "@0x/utils": "^5.2.0", | ||||
|         "ethereum-types": "^3.0.0", | ||||
|         "lodash": "^4.17.11" | ||||
|     }, | ||||
|   | ||||
| @@ -5,16 +5,16 @@ | ||||
|  */ | ||||
| import { ContractArtifact } from 'ethereum-types'; | ||||
|  | ||||
| import * as DeploymentConstants from '../test/generated-artifacts/DeploymentConstants.json'; | ||||
| import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json'; | ||||
| import * as IDevUtils from '../test/generated-artifacts/IDevUtils.json'; | ||||
| import * as IERC20BridgeSampler from '../test/generated-artifacts/IERC20BridgeSampler.json'; | ||||
| import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json'; | ||||
| import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json'; | ||||
| import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json'; | ||||
| import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json'; | ||||
| export const artifacts = { | ||||
|     DeploymentConstants: DeploymentConstants as ContractArtifact, | ||||
|     ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact, | ||||
|     IDevUtils: IDevUtils as ContractArtifact, | ||||
|     IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact, | ||||
|     IEth2Dai: IEth2Dai as ContractArtifact, | ||||
|     IKyberNetwork: IKyberNetwork as ContractArtifact, | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import { | ||||
|     getRandomPortion, | ||||
|     randomAddress, | ||||
| } from '@0x/contracts-test-utils'; | ||||
| import { Order, OrderInfo } from '@0x/types'; | ||||
| import { Order } from '@0x/types'; | ||||
| import { BigNumber, hexUtils } from '@0x/utils'; | ||||
| import * as _ from 'lodash'; | ||||
|  | ||||
| @@ -30,12 +30,13 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|     const INVALID_ASSET_DATA = hexUtils.random(37); | ||||
|     const SELL_SOURCES = ['Eth2Dai', 'Kyber', 'Uniswap']; | ||||
|     const BUY_SOURCES = ['Eth2Dai', 'Uniswap']; | ||||
|     const EMPTY_ORDERS_ERROR = 'EMPTY_ORDERS'; | ||||
|     const UNSUPPORTED_ASSET_PROXY_ERROR = 'UNSUPPORTED_ASSET_PROXY'; | ||||
|     const INVALID_ASSET_DATA_ERROR = 'INVALID_ASSET_DATA'; | ||||
|     const UNSUPPORTED_UNISWAP_EXCHANGE_ERROR = 'UNSUPPORTED_UNISWAP_EXCHANGE'; | ||||
|     const UNSUPPORTED_SOURCE_ERROR = 'UNSUPPORTED_SOURCE'; | ||||
|     const INVALID_TOKEN_PAIR_ERROR = 'INVALID_TOKEN_PAIR'; | ||||
|     const EMPTY_ORDERS_ERROR = 'ERC20BridgeSampler/EMPTY_ORDERS'; | ||||
|     const UNSUPPORTED_ASSET_PROXY_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_ASSET_PROXY'; | ||||
|     const INVALID_ASSET_DATA_ERROR = 'ERC20BridgeSampler/INVALID_ASSET_DATA'; | ||||
|     const UNSUPPORTED_SOURCE_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_SOURCE'; | ||||
|     const INVALID_TOKEN_PAIR_ERROR = 'ERC20BridgeSampler/INVALID_TOKEN_PAIR'; | ||||
|     const MAKER_TOKEN = randomAddress(); | ||||
|     const TAKER_TOKEN = randomAddress(); | ||||
|  | ||||
|     before(async () => { | ||||
|         testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync( | ||||
| @@ -192,13 +193,22 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         return quotes; | ||||
|     } | ||||
|  | ||||
|     function getDeterministicOrderInfo(order: Order): OrderInfo { | ||||
|         const hash = getPackedHash(hexUtils.leftPad(order.salt, 32)); | ||||
|         return { | ||||
|             orderHash: hash, | ||||
|             orderStatus: new BigNumber(hash).mod(255).toNumber(), | ||||
|             orderTakerAssetFilledAmount: new BigNumber(hash).mod(order.takerAssetAmount), | ||||
|         }; | ||||
|     function getDeterministicFillableTakerAssetAmount(order: Order): BigNumber { | ||||
|         const hash = getPackedHash(hexUtils.toHex(order.salt, 32)); | ||||
|         const orderStatus = new BigNumber(hash).mod(100).toNumber() > 90 ? 5 : 3; | ||||
|         const isValidSignature = !!new BigNumber(hash).mod(2).toNumber(); | ||||
|         if (orderStatus !== 3 || !isValidSignature) { | ||||
|             return constants.ZERO_AMOUNT; | ||||
|         } | ||||
|         return order.takerAssetAmount.minus(new BigNumber(hash).mod(order.takerAssetAmount)); | ||||
|     } | ||||
|  | ||||
|     function getDeterministicFillableMakerAssetAmount(order: Order): BigNumber { | ||||
|         const takerAmount = getDeterministicFillableTakerAssetAmount(order); | ||||
|         return order.makerAssetAmount | ||||
|             .times(takerAmount) | ||||
|             .div(order.takerAssetAmount) | ||||
|             .integerValue(BigNumber.ROUND_UP); | ||||
|     } | ||||
|  | ||||
|     function getERC20AssetData(tokenAddress: string): string { | ||||
| @@ -238,57 +248,115 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         return _.times(count || _.random(1, 16), () => createOrder(makerToken, takerToken)); | ||||
|     } | ||||
|  | ||||
|     describe('queryOrders()', () => { | ||||
|         const MAKER_TOKEN = randomAddress(); | ||||
|         const TAKER_TOKEN = randomAddress(); | ||||
|     async function enableFailTriggerAsync(): Promise<void> { | ||||
|         await testContract.enableFailTrigger().awaitTransactionSuccessAsync({ value: 1 }); | ||||
|     } | ||||
|  | ||||
|         it('returns the results of `getOrderInfo()` for each order', async () => { | ||||
|     describe('getOrderFillableTakerAssetAmounts()', () => { | ||||
|         it('returns the expected amount for each order', async () => { | ||||
|             const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); | ||||
|             const expected = orders.map(getDeterministicOrderInfo); | ||||
|             const actual = await testContract.queryOrders(orders).callAsync(); | ||||
|             const signatures: string[] = _.times(orders.length, i => hexUtils.random()); | ||||
|             const expected = orders.map(getDeterministicFillableTakerAssetAmount); | ||||
|             const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync(); | ||||
|             expect(actual).to.deep.eq(expected); | ||||
|         }); | ||||
|  | ||||
|         it('returns empty for no orders', async () => { | ||||
|             const actual = await testContract.queryOrders([]).callAsync(); | ||||
|             const actual = await testContract.getOrderFillableTakerAssetAmounts([], []).callAsync(); | ||||
|             expect(actual).to.deep.eq([]); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero for an order with zero maker asset amount', async () => { | ||||
|             const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1); | ||||
|             orders[0].makerAssetAmount = constants.ZERO_AMOUNT; | ||||
|             const signatures: string[] = _.times(orders.length, i => hexUtils.random()); | ||||
|             const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync(); | ||||
|             expect(actual).to.deep.eq([constants.ZERO_AMOUNT]); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero for an order with zero taker asset amount', async () => { | ||||
|             const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1); | ||||
|             orders[0].takerAssetAmount = constants.ZERO_AMOUNT; | ||||
|             const signatures: string[] = _.times(orders.length, i => hexUtils.random()); | ||||
|             const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync(); | ||||
|             expect(actual).to.deep.eq([constants.ZERO_AMOUNT]); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero for an order with an empty signature', async () => { | ||||
|             const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1); | ||||
|             const signatures: string[] = _.times(orders.length, () => constants.NULL_BYTES); | ||||
|             const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync(); | ||||
|             expect(actual).to.deep.eq([constants.ZERO_AMOUNT]); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('getOrderFillableMakerAssetAmounts()', () => { | ||||
|         it('returns the expected amount for each order', async () => { | ||||
|             const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); | ||||
|             const signatures: string[] = _.times(orders.length, i => hexUtils.random()); | ||||
|             const expected = orders.map(getDeterministicFillableMakerAssetAmount); | ||||
|             const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync(); | ||||
|             expect(actual).to.deep.eq(expected); | ||||
|         }); | ||||
|  | ||||
|         it('returns empty for no orders', async () => { | ||||
|             const actual = await testContract.getOrderFillableMakerAssetAmounts([], []).callAsync(); | ||||
|             expect(actual).to.deep.eq([]); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero for an order with zero maker asset amount', async () => { | ||||
|             const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1); | ||||
|             orders[0].makerAssetAmount = constants.ZERO_AMOUNT; | ||||
|             const signatures: string[] = _.times(orders.length, i => hexUtils.random()); | ||||
|             const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync(); | ||||
|             expect(actual).to.deep.eq([constants.ZERO_AMOUNT]); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero for an order with zero taker asset amount', async () => { | ||||
|             const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1); | ||||
|             orders[0].takerAssetAmount = constants.ZERO_AMOUNT; | ||||
|             const signatures: string[] = _.times(orders.length, i => hexUtils.random()); | ||||
|             const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync(); | ||||
|             expect(actual).to.deep.eq([constants.ZERO_AMOUNT]); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero for an order with an empty signature', async () => { | ||||
|             const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1); | ||||
|             const signatures: string[] = _.times(orders.length, () => constants.NULL_BYTES); | ||||
|             const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync(); | ||||
|             expect(actual).to.deep.eq([constants.ZERO_AMOUNT]); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('queryOrdersAndSampleSells()', () => { | ||||
|         const MAKER_TOKEN = randomAddress(); | ||||
|         const TAKER_TOKEN = randomAddress(); | ||||
|         const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN); | ||||
|         const SIGNATURES: string[] = _.times(ORDERS.length, i => hexUtils.random()); | ||||
|  | ||||
|         before(async () => { | ||||
|             await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); | ||||
|         }); | ||||
|  | ||||
|         it('returns the results of `getOrderInfo()` for each order', async () => { | ||||
|         it('returns expected fillable amounts for each order', async () => { | ||||
|             const takerTokenAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); | ||||
|             const expectedOrderInfos = orders.map(getDeterministicOrderInfo); | ||||
|             const expectedFillableAmounts = ORDERS.map(getDeterministicFillableTakerAssetAmount); | ||||
|             const [orderInfos] = await testContract | ||||
|                 .queryOrdersAndSampleSells(orders, SELL_SOURCES.map(n => allSources[n]), takerTokenAmounts) | ||||
|                 .queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => allSources[n]), takerTokenAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(orderInfos).to.deep.eq(expectedOrderInfos); | ||||
|             expect(orderInfos).to.deep.eq(expectedFillableAmounts); | ||||
|         }); | ||||
|  | ||||
|         it('can return quotes for all sources', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, SELL_SOURCES, sampleAmounts); | ||||
|             const [, quotes] = await testContract | ||||
|                 .queryOrdersAndSampleSells( | ||||
|                     createOrders(MAKER_TOKEN, TAKER_TOKEN), | ||||
|                     SELL_SOURCES.map(n => allSources[n]), | ||||
|                     sampleAmounts, | ||||
|                 ) | ||||
|                 .queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => allSources[n]), sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('throws if no orders are passed in', async () => { | ||||
|             const tx = testContract | ||||
|                 .queryOrdersAndSampleSells([], SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN)) | ||||
|                 .queryOrdersAndSampleSells([], [], SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN)) | ||||
|                 .callAsync(); | ||||
|             return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR); | ||||
|         }); | ||||
| @@ -296,7 +364,8 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         it('throws with an unsupported source', async () => { | ||||
|             const tx = testContract | ||||
|                 .queryOrdersAndSampleSells( | ||||
|                     createOrders(MAKER_TOKEN, TAKER_TOKEN), | ||||
|                     ORDERS, | ||||
|                     SIGNATURES, | ||||
|                     [...SELL_SOURCES.map(n => allSources[n]), randomAddress()], | ||||
|                     getSampleAmounts(TAKER_TOKEN), | ||||
|                 ) | ||||
| @@ -307,10 +376,11 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         it('throws with non-ERC20 maker asset data', async () => { | ||||
|             const tx = testContract | ||||
|                 .queryOrdersAndSampleSells( | ||||
|                     createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ | ||||
|                     ORDERS.map(o => ({ | ||||
|                         ...o, | ||||
|                         makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, | ||||
|                     })), | ||||
|                     SIGNATURES, | ||||
|                     SELL_SOURCES.map(n => allSources[n]), | ||||
|                     getSampleAmounts(TAKER_TOKEN), | ||||
|                 ) | ||||
| @@ -321,10 +391,11 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         it('throws with non-ERC20 taker asset data', async () => { | ||||
|             const tx = testContract | ||||
|                 .queryOrdersAndSampleSells( | ||||
|                     createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ | ||||
|                     ORDERS.map(o => ({ | ||||
|                         ...o, | ||||
|                         takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, | ||||
|                     })), | ||||
|                     SIGNATURES, | ||||
|                     SELL_SOURCES.map(n => allSources[n]), | ||||
|                     getSampleAmounts(TAKER_TOKEN), | ||||
|                 ) | ||||
| @@ -335,10 +406,11 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         it('throws with invalid maker asset data', async () => { | ||||
|             const tx = testContract | ||||
|                 .queryOrdersAndSampleSells( | ||||
|                     createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ | ||||
|                     ORDERS.map(o => ({ | ||||
|                         ...o, | ||||
|                         makerAssetData: INVALID_ASSET_DATA, | ||||
|                     })), | ||||
|                     SIGNATURES, | ||||
|                     SELL_SOURCES.map(n => allSources[n]), | ||||
|                     getSampleAmounts(TAKER_TOKEN), | ||||
|                 ) | ||||
| @@ -349,10 +421,11 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         it('throws with invalid taker asset data', async () => { | ||||
|             const tx = testContract | ||||
|                 .queryOrdersAndSampleSells( | ||||
|                     createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ | ||||
|                     ORDERS.map(o => ({ | ||||
|                         ...o, | ||||
|                         takerAssetData: INVALID_ASSET_DATA, | ||||
|                     })), | ||||
|                     SIGNATURES, | ||||
|                     SELL_SOURCES.map(n => allSources[n]), | ||||
|                     getSampleAmounts(TAKER_TOKEN), | ||||
|                 ) | ||||
| @@ -362,39 +435,34 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|     }); | ||||
|  | ||||
|     describe('queryOrdersAndSampleBuys()', () => { | ||||
|         const MAKER_TOKEN = randomAddress(); | ||||
|         const TAKER_TOKEN = randomAddress(); | ||||
|         const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN); | ||||
|         const SIGNATURES: string[] = _.times(ORDERS.length, i => hexUtils.random()); | ||||
|  | ||||
|         before(async () => { | ||||
|             await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); | ||||
|         }); | ||||
|  | ||||
|         it('returns the results of `getOrderInfo()` for each order', async () => { | ||||
|         it('returns expected fillable amounts for each order', async () => { | ||||
|             const takerTokenAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); | ||||
|             const expectedOrderInfos = orders.map(getDeterministicOrderInfo); | ||||
|             const expectedFillableAmounts = ORDERS.map(getDeterministicFillableMakerAssetAmount); | ||||
|             const [orderInfos] = await testContract | ||||
|                 .queryOrdersAndSampleBuys(orders, BUY_SOURCES.map(n => allSources[n]), takerTokenAmounts) | ||||
|                 .queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => allSources[n]), takerTokenAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(orderInfos).to.deep.eq(expectedOrderInfos); | ||||
|             expect(orderInfos).to.deep.eq(expectedFillableAmounts); | ||||
|         }); | ||||
|  | ||||
|         it('can return quotes for all sources', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, BUY_SOURCES, sampleAmounts); | ||||
|             const [, quotes] = await testContract | ||||
|                 .queryOrdersAndSampleBuys( | ||||
|                     createOrders(MAKER_TOKEN, TAKER_TOKEN), | ||||
|                     BUY_SOURCES.map(n => allSources[n]), | ||||
|                     sampleAmounts, | ||||
|                 ) | ||||
|                 .queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => allSources[n]), sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('throws if no orders are passed in', async () => { | ||||
|             const tx = testContract | ||||
|                 .queryOrdersAndSampleBuys([], BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN)) | ||||
|                 .queryOrdersAndSampleBuys([], [], BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN)) | ||||
|                 .callAsync(); | ||||
|             return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR); | ||||
|         }); | ||||
| @@ -402,7 +470,8 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         it('throws with an unsupported source', async () => { | ||||
|             const tx = testContract | ||||
|                 .queryOrdersAndSampleBuys( | ||||
|                     createOrders(MAKER_TOKEN, TAKER_TOKEN), | ||||
|                     ORDERS, | ||||
|                     SIGNATURES, | ||||
|                     [...BUY_SOURCES.map(n => allSources[n]), randomAddress()], | ||||
|                     getSampleAmounts(MAKER_TOKEN), | ||||
|                 ) | ||||
| @@ -414,7 +483,8 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             const sources = [...BUY_SOURCES, 'Kyber']; | ||||
|             const tx = testContract | ||||
|                 .queryOrdersAndSampleBuys( | ||||
|                     createOrders(MAKER_TOKEN, TAKER_TOKEN), | ||||
|                     ORDERS, | ||||
|                     SIGNATURES, | ||||
|                     sources.map(n => allSources[n]), | ||||
|                     getSampleAmounts(MAKER_TOKEN), | ||||
|                 ) | ||||
| @@ -425,10 +495,11 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         it('throws with non-ERC20 maker asset data', async () => { | ||||
|             const tx = testContract | ||||
|                 .queryOrdersAndSampleBuys( | ||||
|                     createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ | ||||
|                     ORDERS.map(o => ({ | ||||
|                         ...o, | ||||
|                         makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, | ||||
|                     })), | ||||
|                     SIGNATURES, | ||||
|                     BUY_SOURCES.map(n => allSources[n]), | ||||
|                     getSampleAmounts(MAKER_TOKEN), | ||||
|                 ) | ||||
| @@ -439,10 +510,11 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         it('throws with non-ERC20 taker asset data', async () => { | ||||
|             const tx = testContract | ||||
|                 .queryOrdersAndSampleBuys( | ||||
|                     createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ | ||||
|                     ORDERS.map(o => ({ | ||||
|                         ...o, | ||||
|                         takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, | ||||
|                     })), | ||||
|                     SIGNATURES, | ||||
|                     BUY_SOURCES.map(n => allSources[n]), | ||||
|                     getSampleAmounts(MAKER_TOKEN), | ||||
|                 ) | ||||
| @@ -453,10 +525,11 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         it('throws with invalid maker asset data', async () => { | ||||
|             const tx = testContract | ||||
|                 .queryOrdersAndSampleBuys( | ||||
|                     createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ | ||||
|                     ORDERS.map(o => ({ | ||||
|                         ...o, | ||||
|                         makerAssetData: INVALID_ASSET_DATA, | ||||
|                     })), | ||||
|                     SIGNATURES, | ||||
|                     BUY_SOURCES.map(n => allSources[n]), | ||||
|                     getSampleAmounts(MAKER_TOKEN), | ||||
|                 ) | ||||
| @@ -467,10 +540,11 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         it('throws with invalid taker asset data', async () => { | ||||
|             const tx = testContract | ||||
|                 .queryOrdersAndSampleBuys( | ||||
|                     createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ | ||||
|                     ORDERS.map(o => ({ | ||||
|                         ...o, | ||||
|                         takerAssetData: INVALID_ASSET_DATA, | ||||
|                     })), | ||||
|                     SIGNATURES, | ||||
|                     BUY_SOURCES.map(n => allSources[n]), | ||||
|                     getSampleAmounts(MAKER_TOKEN), | ||||
|                 ) | ||||
| @@ -480,9 +554,6 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|     }); | ||||
|  | ||||
|     describe('sampleSells()', () => { | ||||
|         const MAKER_TOKEN = randomAddress(); | ||||
|         const TAKER_TOKEN = randomAddress(); | ||||
|  | ||||
|         before(async () => { | ||||
|             await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); | ||||
|         }); | ||||
| @@ -528,9 +599,6 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|     }); | ||||
|  | ||||
|     describe('sampleBuys()', () => { | ||||
|         const MAKER_TOKEN = randomAddress(); | ||||
|         const TAKER_TOKEN = randomAddress(); | ||||
|  | ||||
|         before(async () => { | ||||
|             await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); | ||||
|         }); | ||||
| @@ -583,10 +651,7 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('sampleSellsFromKyberNetwork()', () => { | ||||
|         const MAKER_TOKEN = randomAddress(); | ||||
|         const TAKER_TOKEN = randomAddress(); | ||||
|  | ||||
|     blockchainTests.resets('sampleSellsFromKyberNetwork()', () => { | ||||
|         before(async () => { | ||||
|             await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); | ||||
|         }); | ||||
| @@ -601,7 +666,7 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq([]); | ||||
|         }); | ||||
|  | ||||
|         it('can return many quotes', async () => { | ||||
|         it('can quote token - token', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts); | ||||
|             const quotes = await testContract | ||||
| @@ -610,6 +675,16 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if token -> token fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('can quote token -> ETH', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts); | ||||
| @@ -619,6 +694,16 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if token -> ETH fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('can quote ETH -> token', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts); | ||||
| @@ -627,12 +712,19 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if ETH -> token fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('sampleSellsFromEth2Dai()', () => { | ||||
|         const MAKER_TOKEN = randomAddress(); | ||||
|         const TAKER_TOKEN = randomAddress(); | ||||
|  | ||||
|     blockchainTests.resets('sampleSellsFromEth2Dai()', () => { | ||||
|         before(async () => { | ||||
|             await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); | ||||
|         }); | ||||
| @@ -647,7 +739,7 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq([]); | ||||
|         }); | ||||
|  | ||||
|         it('can return many quotes', async () => { | ||||
|         it('can quote token -> token', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts); | ||||
|             const quotes = await testContract | ||||
| @@ -656,6 +748,16 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if token -> token fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('can quote token -> ETH', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts); | ||||
| @@ -665,6 +767,16 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if token -> ETH fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromEth2Dai(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('can quote ETH -> token', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts); | ||||
| @@ -673,12 +785,19 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if ETH -> token fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromEth2Dai(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('sampleBuysFromEth2Dai()', () => { | ||||
|         const MAKER_TOKEN = randomAddress(); | ||||
|         const TAKER_TOKEN = randomAddress(); | ||||
|  | ||||
|     blockchainTests.resets('sampleBuysFromEth2Dai()', () => { | ||||
|         before(async () => { | ||||
|             await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); | ||||
|         }); | ||||
| @@ -693,7 +812,7 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq([]); | ||||
|         }); | ||||
|  | ||||
|         it('can return many quotes', async () => { | ||||
|         it('can quote token -> token', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts); | ||||
|             const quotes = await testContract | ||||
| @@ -702,6 +821,16 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if token -> token fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('can quote token -> ETH', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts); | ||||
| @@ -711,6 +840,16 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if token -> ETH fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromEth2Dai(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('can quote ETH -> token', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts); | ||||
| @@ -719,12 +858,19 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if ETH -> token fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromEth2Dai(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('sampleSellsFromUniswap()', () => { | ||||
|         const MAKER_TOKEN = randomAddress(); | ||||
|         const TAKER_TOKEN = randomAddress(); | ||||
|  | ||||
|     blockchainTests.resets('sampleSellsFromUniswap()', () => { | ||||
|         before(async () => { | ||||
|             await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); | ||||
|         }); | ||||
| @@ -739,7 +885,7 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq([]); | ||||
|         }); | ||||
|  | ||||
|         it('can return many quotes', async () => { | ||||
|         it('can quote token -> token', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts); | ||||
|             const quotes = await testContract | ||||
| @@ -748,6 +894,16 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if token -> token fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromUniswap(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('can quote token -> ETH', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts); | ||||
| @@ -757,6 +913,16 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if token -> ETH fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromUniswap(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('can quote ETH -> token', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts); | ||||
| @@ -766,27 +932,38 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('throws if no exchange exists for the maker token', async () => { | ||||
|             const nonExistantToken = randomAddress(); | ||||
|             const tx = testContract | ||||
|                 .sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN)) | ||||
|         it('returns zero if ETH -> token fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('throws if no exchange exists for the taker token', async () => { | ||||
|         it('returns zero if no exchange exists for the maker token', async () => { | ||||
|             const nonExistantToken = randomAddress(); | ||||
|             const tx = testContract | ||||
|                 .sampleSellsFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken)) | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if no exchange exists for the taker token', async () => { | ||||
|             const nonExistantToken = randomAddress(); | ||||
|             const sampleAmounts = getSampleAmounts(nonExistantToken); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromUniswap(nonExistantToken, MAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('sampleBuysFromUniswap()', () => { | ||||
|         const MAKER_TOKEN = randomAddress(); | ||||
|         const TAKER_TOKEN = randomAddress(); | ||||
|  | ||||
|     blockchainTests.resets('sampleBuysFromUniswap()', () => { | ||||
|         before(async () => { | ||||
|             await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); | ||||
|         }); | ||||
| @@ -801,7 +978,7 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq([]); | ||||
|         }); | ||||
|  | ||||
|         it('can return many quotes', async () => { | ||||
|         it('can quote token -> token', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts); | ||||
|             const quotes = await testContract | ||||
| @@ -810,6 +987,16 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if token -> token fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromUniswap(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('can quote token -> ETH', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts); | ||||
| @@ -819,6 +1006,16 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if token -> ETH fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromUniswap(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('can quote ETH -> token', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts); | ||||
| @@ -828,20 +1025,34 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('throws if no exchange exists for the maker token', async () => { | ||||
|             const nonExistantToken = randomAddress(); | ||||
|             const tx = testContract | ||||
|                 .sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN)) | ||||
|         it('returns zero if ETH -> token fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('throws if no exchange exists for the taker token', async () => { | ||||
|         it('returns zero if no exchange exists for the maker token', async () => { | ||||
|             const nonExistantToken = randomAddress(); | ||||
|             const tx = testContract | ||||
|                 .sampleBuysFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken)) | ||||
|             const sampleAmounts = getSampleAmounts(nonExistantToken); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if no exchange exists for the taker token', async () => { | ||||
|             const nonExistantToken = randomAddress(); | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromUniswap(nonExistantToken, MAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
|   | ||||
| @@ -3,8 +3,8 @@ | ||||
|  * Warning: This file is auto-generated by contracts-gen. Don't edit manually. | ||||
|  * ----------------------------------------------------------------------------- | ||||
|  */ | ||||
| export * from '../test/generated-wrappers/deployment_constants'; | ||||
| export * from '../test/generated-wrappers/erc20_bridge_sampler'; | ||||
| export * from '../test/generated-wrappers/i_dev_utils'; | ||||
| export * from '../test/generated-wrappers/i_erc20_bridge_sampler'; | ||||
| export * from '../test/generated-wrappers/i_eth2_dai'; | ||||
| export * from '../test/generated-wrappers/i_kyber_network'; | ||||
|   | ||||
| @@ -84,7 +84,7 @@ module.exports = { | ||||
|         solc: { | ||||
|             version: '0.5.9', | ||||
|             settings: { | ||||
|                 evmVersion: 'constantinople', | ||||
|                 evmVersion: 'istanbul', | ||||
|                 optimizer: { | ||||
|                     enabled: true, | ||||
|                     runs: 1000000, | ||||
|   | ||||
| @@ -5,8 +5,8 @@ | ||||
|     "files": [ | ||||
|         "generated-artifacts/ERC20BridgeSampler.json", | ||||
|         "generated-artifacts/IERC20BridgeSampler.json", | ||||
|         "test/generated-artifacts/DeploymentConstants.json", | ||||
|         "test/generated-artifacts/ERC20BridgeSampler.json", | ||||
|         "test/generated-artifacts/IDevUtils.json", | ||||
|         "test/generated-artifacts/IERC20BridgeSampler.json", | ||||
|         "test/generated-artifacts/IEth2Dai.json", | ||||
|         "test/generated-artifacts/IKyberNetwork.json", | ||||
|   | ||||
| @@ -1,4 +1,31 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1579682890, | ||||
|         "version": "3.0.4", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1578272714, | ||||
|         "version": "3.0.3", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1576540892, | ||||
|         "version": "3.0.2", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1575931811, | ||||
|         "version": "3.0.1", | ||||
|   | ||||
| @@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v3.0.4 - _January 22, 2020_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v3.0.3 - _January 6, 2020_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v3.0.2 - _December 17, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v3.0.1 - _December 9, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|     "useDockerisedSolc": false, | ||||
|     "isOfflineMode": false, | ||||
|     "compilerSettings": { | ||||
|         "evmVersion": "constantinople", | ||||
|         "evmVersion": "istanbul", | ||||
|         "optimizer": { | ||||
|             "enabled": true, | ||||
|             "runs": 1000000, | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| // solhint-disable | ||||
| pragma solidity ^0.4.18; | ||||
| pragma solidity ^0.5.9; | ||||
|  | ||||
|  | ||||
| contract WETH9 { | ||||
| @@ -30,27 +30,27 @@ contract WETH9 { | ||||
|     mapping (address => uint)                       public  balanceOf; | ||||
|     mapping (address => mapping (address => uint))  public  allowance; | ||||
|  | ||||
|     function() public payable { | ||||
|     function() external payable { | ||||
|         deposit(); | ||||
|     } | ||||
|     function deposit() public payable { | ||||
|         balanceOf[msg.sender] += msg.value; | ||||
|         Deposit(msg.sender, msg.value); | ||||
|         emit Deposit(msg.sender, msg.value); | ||||
|     } | ||||
|     function withdraw(uint wad) public { | ||||
|         require(balanceOf[msg.sender] >= wad); | ||||
|         balanceOf[msg.sender] -= wad; | ||||
|         msg.sender.transfer(wad); | ||||
|         Withdrawal(msg.sender, wad); | ||||
|         emit Withdrawal(msg.sender, wad); | ||||
|     } | ||||
|  | ||||
|     function totalSupply() public view returns (uint) { | ||||
|         return this.balance; | ||||
|         return address(this).balance; | ||||
|     } | ||||
|  | ||||
|     function approve(address guy, uint wad) public returns (bool) { | ||||
|         allowance[msg.sender][guy] = wad; | ||||
|         Approval(msg.sender, guy, wad); | ||||
|         emit Approval(msg.sender, guy, wad); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
| @@ -72,7 +72,7 @@ contract WETH9 { | ||||
|         balanceOf[src] -= wad; | ||||
|         balanceOf[dst] += wad; | ||||
|  | ||||
|         Transfer(src, dst, wad); | ||||
|         emit Transfer(src, dst, wad); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0x/contracts-erc20", | ||||
|     "version": "3.0.1", | ||||
|     "version": "3.0.4", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -51,18 +51,18 @@ | ||||
|     }, | ||||
|     "homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md", | ||||
|     "devDependencies": { | ||||
|         "@0x/abi-gen": "^5.0.1", | ||||
|         "@0x/contracts-gen": "^2.0.1", | ||||
|         "@0x/contracts-test-utils": "^5.0.0", | ||||
|         "@0x/contracts-utils": "^4.0.1", | ||||
|         "@0x/dev-utils": "^3.0.1", | ||||
|         "@0x/sol-compiler": "^4.0.1", | ||||
|         "@0x/abi-gen": "^5.1.0", | ||||
|         "@0x/contracts-gen": "^2.0.4", | ||||
|         "@0x/contracts-test-utils": "^5.1.1", | ||||
|         "@0x/contracts-utils": "^4.1.0", | ||||
|         "@0x/dev-utils": "^3.1.1", | ||||
|         "@0x/sol-compiler": "^4.0.4", | ||||
|         "@0x/ts-doc-gen": "^0.0.22", | ||||
|         "@0x/tslint-config": "^4.0.0", | ||||
|         "@0x/types": "^3.1.0", | ||||
|         "@0x/typescript-typings": "^5.0.0", | ||||
|         "@0x/utils": "^5.1.0", | ||||
|         "@0x/web3-wrapper": "^7.0.1", | ||||
|         "@0x/types": "^3.1.1", | ||||
|         "@0x/typescript-typings": "^5.0.1", | ||||
|         "@0x/utils": "^5.2.0", | ||||
|         "@0x/web3-wrapper": "^7.0.4", | ||||
|         "@types/lodash": "4.14.104", | ||||
|         "@types/mocha": "^5.2.7", | ||||
|         "@types/node": "*", | ||||
| @@ -82,7 +82,7 @@ | ||||
|         "typescript": "3.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0x/base-contract": "^6.0.1" | ||||
|         "@0x/base-contract": "^6.1.0" | ||||
|     }, | ||||
|     "publishConfig": { | ||||
|         "access": "public" | ||||
|   | ||||
| @@ -3,6 +3,9 @@ export { | ||||
|     DummyMultipleReturnERC20TokenContract, | ||||
|     DummyNoReturnERC20TokenContract, | ||||
|     WETH9Contract, | ||||
|     WETH9Events, | ||||
|     WETH9DepositEventArgs, | ||||
|     WETH9TransferEventArgs, | ||||
|     ZRXTokenContract, | ||||
|     DummyERC20TokenTransferEventArgs, | ||||
|     ERC20TokenEventArgs, | ||||
|   | ||||
| @@ -1,4 +1,31 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1579682890, | ||||
|         "version": "3.0.4", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1578272714, | ||||
|         "version": "3.0.3", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1576540892, | ||||
|         "version": "3.0.2", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1575931811, | ||||
|         "version": "3.0.1", | ||||
|   | ||||
| @@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v3.0.4 - _January 22, 2020_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v3.0.3 - _January 6, 2020_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v3.0.2 - _December 17, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v3.0.1 - _December 9, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|     "useDockerisedSolc": false, | ||||
|     "isOfflineMode": false, | ||||
|     "compilerSettings": { | ||||
|         "evmVersion": "constantinople", | ||||
|         "evmVersion": "istanbul", | ||||
|         "optimizer": { | ||||
|             "enabled": true, | ||||
|             "runs": 1000000, | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0x/contracts-erc721", | ||||
|     "version": "3.0.1", | ||||
|     "version": "3.0.4", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -52,18 +52,18 @@ | ||||
|     }, | ||||
|     "homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md", | ||||
|     "devDependencies": { | ||||
|         "@0x/abi-gen": "^5.0.1", | ||||
|         "@0x/contracts-gen": "^2.0.1", | ||||
|         "@0x/contracts-test-utils": "^5.0.0", | ||||
|         "@0x/contracts-utils": "^4.0.1", | ||||
|         "@0x/dev-utils": "^3.0.1", | ||||
|         "@0x/sol-compiler": "^4.0.1", | ||||
|         "@0x/abi-gen": "^5.1.0", | ||||
|         "@0x/contracts-gen": "^2.0.4", | ||||
|         "@0x/contracts-test-utils": "^5.1.1", | ||||
|         "@0x/contracts-utils": "^4.1.0", | ||||
|         "@0x/dev-utils": "^3.1.1", | ||||
|         "@0x/sol-compiler": "^4.0.4", | ||||
|         "@0x/ts-doc-gen": "^0.0.22", | ||||
|         "@0x/tslint-config": "^4.0.0", | ||||
|         "@0x/types": "^3.1.0", | ||||
|         "@0x/typescript-typings": "^5.0.0", | ||||
|         "@0x/utils": "^5.1.0", | ||||
|         "@0x/web3-wrapper": "^7.0.1", | ||||
|         "@0x/types": "^3.1.1", | ||||
|         "@0x/typescript-typings": "^5.0.1", | ||||
|         "@0x/utils": "^5.2.0", | ||||
|         "@0x/web3-wrapper": "^7.0.4", | ||||
|         "@types/lodash": "4.14.104", | ||||
|         "@types/mocha": "^5.2.7", | ||||
|         "@types/node": "*", | ||||
| @@ -84,7 +84,7 @@ | ||||
|         "typescript": "3.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0x/base-contract": "^6.0.1" | ||||
|         "@0x/base-contract": "^6.1.0" | ||||
|     }, | ||||
|     "publishConfig": { | ||||
|         "access": "public" | ||||
|   | ||||
| @@ -84,7 +84,7 @@ module.exports = { | ||||
|         solc: { | ||||
|             version: '0.5.9', | ||||
|             settings: { | ||||
|                 evmVersion: 'constantinople', | ||||
|                 evmVersion: 'istanbul', | ||||
|                 optimizer: { | ||||
|                     enabled: true, | ||||
|                     runs: 1000000, | ||||
|   | ||||
| @@ -1,4 +1,31 @@ | ||||
| [ | ||||
|     { | ||||
|         "timestamp": 1579682890, | ||||
|         "version": "4.0.4", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1578272714, | ||||
|         "version": "4.0.3", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1576540892, | ||||
|         "version": "4.0.2", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Dependencies updated" | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1575931811, | ||||
|         "version": "4.0.1", | ||||
|   | ||||
| @@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only. | ||||
|  | ||||
| CHANGELOG | ||||
|  | ||||
| ## v4.0.4 - _January 22, 2020_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v4.0.3 - _January 6, 2020_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v4.0.2 - _December 17, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|  | ||||
| ## v4.0.1 - _December 9, 2019_ | ||||
|  | ||||
|     * Dependencies updated | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|     "useDockerisedSolc": false, | ||||
|     "isOfflineMode": false, | ||||
|     "compilerSettings": { | ||||
|         "evmVersion": "constantinople", | ||||
|         "evmVersion": "istanbul", | ||||
|         "optimizer": { | ||||
|             "enabled": true, | ||||
|             "runs": 1000000, | ||||
|   | ||||
| @@ -21,6 +21,7 @@ pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./MixinForwarderCore.sol"; | ||||
| import "./libs/LibConstants.sol"; | ||||
| import "./MixinReceiver.sol"; | ||||
|  | ||||
|  | ||||
| // solhint-disable no-empty-blocks | ||||
| @@ -28,16 +29,19 @@ import "./libs/LibConstants.sol"; | ||||
| // MixinForwarderCore. | ||||
| contract Forwarder is | ||||
|     LibConstants, | ||||
|     MixinForwarderCore | ||||
|     MixinForwarderCore, | ||||
|     MixinReceiver | ||||
| { | ||||
|     constructor ( | ||||
|         address _exchange, | ||||
|         address _exchangeV2, | ||||
|         address _weth | ||||
|     ) | ||||
|         public | ||||
|         Ownable() | ||||
|         LibConstants( | ||||
|             _exchange, | ||||
|             _exchangeV2, | ||||
|             _weth | ||||
|         ) | ||||
|         MixinForwarderCore() | ||||
|   | ||||
| @@ -22,9 +22,9 @@ import "@0x/contracts-utils/contracts/src/LibBytes.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/Ownable.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol"; | ||||
| import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol"; | ||||
| import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol"; | ||||
| import "./libs/LibConstants.sol"; | ||||
| import "./libs/LibAssetDataTransfer.sol"; | ||||
| import "./libs/LibForwarderRichErrors.sol"; | ||||
| import "./interfaces/IAssets.sol"; | ||||
|  | ||||
| @@ -35,6 +35,7 @@ contract MixinAssets is | ||||
|     IAssets | ||||
| { | ||||
|     using LibBytes for bytes; | ||||
|     using LibAssetDataTransfer for bytes; | ||||
|  | ||||
|     /// @dev Withdraws assets from this contract. It may be used by the owner to withdraw assets | ||||
|     ///      that were accidentally sent to this contract. | ||||
| @@ -47,7 +48,7 @@ contract MixinAssets is | ||||
|         external | ||||
|         onlyOwner | ||||
|     { | ||||
|         _transferAssetToSender(assetData, amount); | ||||
|         assetData.transferOut(amount); | ||||
|     } | ||||
|  | ||||
|     /// @dev Approves the respective proxy for a given asset to transfer tokens on the Forwarder contract's behalf. | ||||
| @@ -72,69 +73,4 @@ contract MixinAssets is | ||||
|             LibERC20Token.approve(token, proxyAddress, MAX_UINT); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @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 == IAssetData(address(0)).ERC20Token.selector || | ||||
|             proxyId == IAssetData(address(0)).ERC20Bridge.selector | ||||
|         ) { | ||||
|             _transferERC20Token(assetData, amount); | ||||
|         } else if (proxyId == IAssetData(address(0)).ERC721Token.selector) { | ||||
|             _transferERC721Token(assetData, amount); | ||||
|         } else { | ||||
|             LibRichErrors.rrevert(LibForwarderRichErrors.UnsupportedAssetProxyError( | ||||
|                 proxyId | ||||
|             )); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Decodes ERC20 or ERC20Bridge 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. | ||||
|         LibERC20Token.transfer(token, msg.sender, amount); | ||||
|     } | ||||
|  | ||||
|     /// @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 | ||||
|     { | ||||
|         if (amount != 1) { | ||||
|             LibRichErrors.rrevert(LibForwarderRichErrors.Erc721AmountMustEqualOneError( | ||||
|                 amount | ||||
|             )); | ||||
|         } | ||||
|         // Decode asset data. | ||||
|         address token = assetData.readAddress(16); | ||||
|         uint256 tokenId = assetData.readUint256(36); | ||||
|  | ||||
|         // Perform transfer. | ||||
|         IERC721Token(token).transferFrom( | ||||
|             address(this), | ||||
|             msg.sender, | ||||
|             tokenId | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -30,6 +30,7 @@ import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; | ||||
| import "./libs/LibConstants.sol"; | ||||
| import "./libs/LibForwarderRichErrors.sol"; | ||||
| import "./interfaces/IExchangeV2.sol"; | ||||
| import "./MixinAssets.sol"; | ||||
|  | ||||
|  | ||||
| @@ -54,23 +55,19 @@ contract MixinExchangeWrapper is | ||||
|         internal | ||||
|         returns (LibFillResults.FillResults memory fillResults) | ||||
|     { | ||||
|         // ABI encode calldata for `fillOrder` | ||||
|         bytes memory fillOrderCalldata = abi.encodeWithSelector( | ||||
|             IExchange(address(0)).fillOrder.selector, | ||||
|         if (_isV2Order(order)) { | ||||
|             return _fillV2OrderNoThrow( | ||||
|                 order, | ||||
|                 takerAssetFillAmount, | ||||
|                 signature | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         return _fillV3OrderNoThrow( | ||||
|             order, | ||||
|             takerAssetFillAmount, | ||||
|             signature | ||||
|         ); | ||||
|  | ||||
|         address exchange = address(EXCHANGE); | ||||
|         (bool didSucceed, bytes memory returnData) = exchange.call(fillOrderCalldata); | ||||
|         if (didSucceed) { | ||||
|             assert(returnData.length == 160); | ||||
|             fillResults = abi.decode(returnData, (LibFillResults.FillResults)); | ||||
|         } | ||||
|  | ||||
|         // fillResults values will be 0 by default if call was unsuccessful | ||||
|         return fillResults; | ||||
|     } | ||||
|  | ||||
|     /// @dev Executes a single call of fillOrder according to the wethSellAmount and | ||||
| @@ -162,8 +159,7 @@ contract MixinExchangeWrapper is | ||||
|         uint256 protocolFee = tx.gasprice.safeMul(EXCHANGE.protocolFeeMultiplier()); | ||||
|         bytes4 erc20BridgeProxyId = IAssetData(address(0)).ERC20Bridge.selector; | ||||
|  | ||||
|         uint256 ordersLength = orders.length; | ||||
|         for (uint256 i = 0; i != ordersLength; i++) { | ||||
|         for (uint256 i = 0; i != orders.length; i++) { | ||||
|             // Preemptively skip to avoid division by zero in _marketSellSingleOrder | ||||
|             if (orders[i].makerAssetAmount == 0 || orders[i].takerAssetAmount == 0) { | ||||
|                 continue; | ||||
| @@ -172,7 +168,7 @@ contract MixinExchangeWrapper is | ||||
|             // The remaining amount of WETH to sell | ||||
|             uint256 remainingTakerAssetFillAmount = wethSellAmount | ||||
|                 .safeSub(totalWethSpentAmount) | ||||
|                 .safeSub(protocolFee); | ||||
|                 .safeSub(_isV2Order(orders[i]) ? 0 : protocolFee); | ||||
|  | ||||
|             // If the maker asset is ERC20Bridge, take a snapshot of the Forwarder contract's balance. | ||||
|             bytes4 makerAssetProxyId = orders[i].makerAssetData.readBytes4(0); | ||||
| @@ -201,7 +197,7 @@ contract MixinExchangeWrapper is | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             _transferAssetToSender(orders[i].makerAssetData, makerAssetAcquiredAmount); | ||||
|             orders[i].makerAssetData.transferOut(makerAssetAcquiredAmount); | ||||
|  | ||||
|             totalWethSpentAmount = totalWethSpentAmount | ||||
|                 .safeAdd(wethSpentAmount); | ||||
| @@ -349,7 +345,7 @@ contract MixinExchangeWrapper is | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             _transferAssetToSender(orders[i].makerAssetData, makerAssetAcquiredAmount); | ||||
|             orders[i].makerAssetData.transferOut(makerAssetAcquiredAmount); | ||||
|  | ||||
|             totalWethSpentAmount = totalWethSpentAmount | ||||
|                 .safeAdd(wethSpentAmount); | ||||
| @@ -370,6 +366,91 @@ contract MixinExchangeWrapper is | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Fills the input ExchangeV2 order. The `makerFeeAssetData` must be | ||||
|     //       equal to EXCHANGE_V2_ORDER_ID (0x770501f8). | ||||
|     ///      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 _fillV2OrderNoThrow( | ||||
|         LibOrder.Order memory order, | ||||
|         uint256 takerAssetFillAmount, | ||||
|         bytes memory signature | ||||
|     ) | ||||
|         internal | ||||
|         returns (LibFillResults.FillResults memory fillResults) | ||||
|     { | ||||
|         // Strip v3 specific fields from order | ||||
|         IExchangeV2.Order memory v2Order = IExchangeV2.Order({ | ||||
|             makerAddress: order.makerAddress, | ||||
|             takerAddress: order.takerAddress, | ||||
|             feeRecipientAddress: order.feeRecipientAddress, | ||||
|             senderAddress: order.senderAddress, | ||||
|             makerAssetAmount: order.makerAssetAmount, | ||||
|             takerAssetAmount: order.takerAssetAmount, | ||||
|             // NOTE: We assume fees are 0 for all v2 orders. Orders with non-zero fees will fail to be filled. | ||||
|             makerFee: 0, | ||||
|             takerFee: 0, | ||||
|             expirationTimeSeconds: order.expirationTimeSeconds, | ||||
|             salt: order.salt, | ||||
|             makerAssetData: order.makerAssetData, | ||||
|             takerAssetData: order.takerAssetData | ||||
|         }); | ||||
|  | ||||
|         // ABI encode calldata for `fillOrder` | ||||
|         bytes memory fillOrderCalldata = abi.encodeWithSelector( | ||||
|             IExchangeV2(address(0)).fillOrder.selector, | ||||
|             v2Order, | ||||
|             takerAssetFillAmount, | ||||
|             signature | ||||
|         ); | ||||
|  | ||||
|         address exchange = address(EXCHANGE_V2); | ||||
|         (bool didSucceed, bytes memory returnData) = exchange.call(fillOrderCalldata); | ||||
|         if (didSucceed) { | ||||
|             assert(returnData.length == 128); | ||||
|             // NOTE: makerFeePaid, takerFeePaid, and protocolFeePaid will always be 0 for v2 orders | ||||
|             (fillResults.makerAssetFilledAmount, fillResults.takerAssetFilledAmount) = abi.decode(returnData, (uint256, uint256)); | ||||
|         } | ||||
|  | ||||
|         // fillResults values will be 0 by default if call was unsuccessful | ||||
|         return fillResults; | ||||
|     } | ||||
|  | ||||
|     /// @dev Fills the input ExchangeV3 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 _fillV3OrderNoThrow( | ||||
|         LibOrder.Order memory order, | ||||
|         uint256 takerAssetFillAmount, | ||||
|         bytes memory signature | ||||
|     ) | ||||
|         internal | ||||
|         returns (LibFillResults.FillResults memory fillResults) | ||||
|     { | ||||
|         // ABI encode calldata for `fillOrder` | ||||
|         bytes memory fillOrderCalldata = abi.encodeWithSelector( | ||||
|             IExchange(address(0)).fillOrder.selector, | ||||
|             order, | ||||
|             takerAssetFillAmount, | ||||
|             signature | ||||
|         ); | ||||
|  | ||||
|         address exchange = address(EXCHANGE); | ||||
|         (bool didSucceed, bytes memory returnData) = exchange.call(fillOrderCalldata); | ||||
|         if (didSucceed) { | ||||
|             assert(returnData.length == 160); | ||||
|             fillResults = abi.decode(returnData, (LibFillResults.FillResults)); | ||||
|         } | ||||
|  | ||||
|         // fillResults values will be 0 by default if call was unsuccessful | ||||
|         return fillResults; | ||||
|     } | ||||
|  | ||||
|     /// @dev Checks whether one asset is effectively equal to another asset. | ||||
|     ///      This is the case if they have the same ERC20Proxy/ERC20BridgeProxy asset data, or if | ||||
|     ///      one is the ERC20Bridge equivalent of the other. | ||||
| @@ -398,7 +479,18 @@ contract MixinExchangeWrapper is | ||||
|             address token2 = assetData2.readAddress(16); | ||||
|             return (token1 == token2); | ||||
|         } else { | ||||
|             return false; | ||||
|             return assetData1.equals(assetData2); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Checks whether an order is a v2 order. | ||||
|     /// @param order Order struct containing order specifications. | ||||
|     /// @return True if the order's `makerFeeAssetData` is set to the v2 order id. | ||||
|     function _isV2Order(LibOrder.Order memory order) | ||||
|         internal | ||||
|         pure | ||||
|         returns (bool) | ||||
|     { | ||||
|         return order.makerFeeAssetData.length > 3 && order.makerFeeAssetData.readBytes4(0) == EXCHANGE_V2_ORDER_ID; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -85,7 +85,6 @@ contract MixinForwarderCore is | ||||
|             ethFeeAmounts, | ||||
|             feeRecipients | ||||
|         ); | ||||
|  | ||||
|         // Spends up to wethRemaining to fill orders, transfers purchased assets to msg.sender, | ||||
|         // and pays WETH order fees. | ||||
|         ( | ||||
|   | ||||
							
								
								
									
										76
									
								
								contracts/exchange-forwarder/contracts/src/MixinReceiver.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								contracts/exchange-forwarder/contracts/src/MixinReceiver.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2019 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.5.9; | ||||
|  | ||||
|  | ||||
| contract MixinReceiver { | ||||
|  | ||||
|     bytes4 constant public ERC1155_RECEIVED       = 0xf23a6e61; | ||||
|     bytes4 constant public ERC1155_BATCH_RECEIVED = 0xbc197c81; | ||||
|  | ||||
|     /// @notice Handle the receipt of a single ERC1155 token type | ||||
|     /// @dev The smart contract calls this function on the recipient | ||||
|     /// after a `safeTransferFrom`. This function MAY throw to revert and reject the | ||||
|     /// transfer. Return of other than the magic value MUST result in the | ||||
|     ///transaction being reverted | ||||
|     /// Note: the contract address is always the message sender | ||||
|     /// @param operator  The address which called `safeTransferFrom` function | ||||
|     /// @param from      The address which previously owned the token | ||||
|     /// @param id        An array containing the ids of the token being transferred | ||||
|     /// @param value     An array containing the amount of tokens being transferred | ||||
|     /// @param data      Additional data with no specified format | ||||
|     /// @return          `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` | ||||
|     function onERC1155Received( | ||||
|         address operator, | ||||
|         address from, | ||||
|         uint256 id, | ||||
|         uint256 value, | ||||
|         bytes calldata data | ||||
|     ) | ||||
|         external | ||||
|         returns (bytes4) | ||||
|     { | ||||
|         return ERC1155_RECEIVED; | ||||
|     } | ||||
|  | ||||
|     /// @notice Handle the receipt of multiple ERC1155 token types | ||||
|     /// @dev The smart contract calls this function on the recipient | ||||
|     /// after a `safeTransferFrom`. This function MAY throw to revert and reject the | ||||
|     /// transfer. Return of other than the magic value MUST result in the | ||||
|     /// transaction being reverted | ||||
|     /// Note: the contract address is always the message sender | ||||
|     /// @param operator  The address which called `safeTransferFrom` function | ||||
|     /// @param from      The address which previously owned the token | ||||
|     /// @param ids       An array containing ids of each token being transferred | ||||
|     /// @param values    An array containing amounts of each token being transferred | ||||
|     /// @param data      Additional data with no specified format | ||||
|     /// @return           `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` | ||||
|     function onERC1155BatchReceived( | ||||
|         address operator, | ||||
|         address from, | ||||
|         uint256[] calldata ids, | ||||
|         uint256[] calldata values, | ||||
|         bytes calldata data | ||||
|     ) | ||||
|         external | ||||
|         returns (bytes4) | ||||
|     { | ||||
|         return ERC1155_BATCH_RECEIVED; | ||||
|     } | ||||
| } | ||||
| @@ -109,7 +109,6 @@ contract MixinWeth is | ||||
|         if (wethRemaining > 0) { | ||||
|             // Convert remaining WETH to ETH | ||||
|             ETHER_TOKEN.withdraw(wethRemaining); | ||||
|  | ||||
|             // Transfer remaining ETH to sender | ||||
|             msg.sender.transfer(wethRemaining); | ||||
|         } | ||||
|   | ||||
| @@ -0,0 +1,75 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2019 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.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
|  | ||||
| contract IExchangeV2 { | ||||
|  | ||||
|     // 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 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 OrderInfo { | ||||
|         uint8 orderStatus;                    // Status that describes order's validity and fillability. | ||||
|         bytes32 orderHash;                    // EIP712 typed data hash of the order (see LibOrder.getTypedDataHash). | ||||
|         uint256 orderTakerAssetFilledAmount;  // Amount of order that has already been filled. | ||||
|     } | ||||
|  | ||||
|     /// @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); | ||||
|  | ||||
|     /// @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 | ||||
|         returns (OrderInfo memory orderInfo); | ||||
| } | ||||
| @@ -0,0 +1,258 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2019 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.5.9; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-utils/contracts/src/LibBytes.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol"; | ||||
| import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol"; | ||||
| import "@0x/contracts-erc1155/contracts/src/interfaces/IERC1155.sol"; | ||||
| import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol"; | ||||
| import "./LibForwarderRichErrors.sol"; | ||||
|  | ||||
|  | ||||
| library LibAssetDataTransfer { | ||||
|  | ||||
|     using LibBytes for bytes; | ||||
|     using LibSafeMath for uint256; | ||||
|     using LibAssetDataTransfer for bytes; | ||||
|  | ||||
|     /// @dev Transfers given amount of asset to sender. | ||||
|     /// @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 to sender. | ||||
|     function transferFrom( | ||||
|         bytes memory assetData, | ||||
|         address from, | ||||
|         address to, | ||||
|         uint256 amount | ||||
|     ) | ||||
|         internal | ||||
|     { | ||||
|         if (amount == 0) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         bytes4 proxyId = assetData.readBytes4(0); | ||||
|  | ||||
|         if ( | ||||
|             proxyId == IAssetData(address(0)).ERC20Token.selector || | ||||
|             proxyId == IAssetData(address(0)).ERC20Bridge.selector | ||||
|         ) { | ||||
|             assetData.transferERC20Token( | ||||
|                 from, | ||||
|                 to, | ||||
|                 amount | ||||
|             ); | ||||
|         } else if (proxyId == IAssetData(address(0)).ERC721Token.selector) { | ||||
|             assetData.transferERC721Token( | ||||
|                 from, | ||||
|                 to, | ||||
|                 amount | ||||
|             ); | ||||
|         } else if (proxyId == IAssetData(address(0)).ERC1155Assets.selector) { | ||||
|             assetData.transferERC1155Assets( | ||||
|                 from, | ||||
|                 to, | ||||
|                 amount | ||||
|             ); | ||||
|         } else if (proxyId == IAssetData(address(0)).MultiAsset.selector) { | ||||
|             assetData.transferMultiAsset( | ||||
|                 from, | ||||
|                 to, | ||||
|                 amount | ||||
|             ); | ||||
|         } else if (proxyId != IAssetData(address(0)).StaticCall.selector) { | ||||
|             LibRichErrors.rrevert(LibForwarderRichErrors.UnsupportedAssetProxyError( | ||||
|                 proxyId | ||||
|             )); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     ///@dev Transfer asset from sender to this contract. | ||||
|     /// @param assetData Byte array encoded for the respective asset proxy. | ||||
|     /// @param amount Amount of asset to transfer to sender. | ||||
|     function transferIn( | ||||
|         bytes memory assetData, | ||||
|         uint256 amount | ||||
|     ) | ||||
|         internal | ||||
|     { | ||||
|         assetData.transferFrom( | ||||
|             msg.sender, | ||||
|             address(this), | ||||
|             amount | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     ///@dev Transfer asset from this contract to sender. | ||||
|     /// @param assetData Byte array encoded for the respective asset proxy. | ||||
|     /// @param amount Amount of asset to transfer to sender. | ||||
|     function transferOut( | ||||
|         bytes memory assetData, | ||||
|         uint256 amount | ||||
|     ) | ||||
|         internal | ||||
|     { | ||||
|         assetData.transferFrom( | ||||
|             address(this), | ||||
|             msg.sender, | ||||
|             amount | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Decodes ERC20 or ERC20Bridge assetData and transfers given amount to sender. | ||||
|     /// @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 to sender. | ||||
|     function transferERC20Token( | ||||
|         bytes memory assetData, | ||||
|         address from, | ||||
|         address to, | ||||
|         uint256 amount | ||||
|     ) | ||||
|         internal | ||||
|     { | ||||
|         address token = assetData.readAddress(16); | ||||
|         // Transfer tokens. | ||||
|         if (from == address(this)) { | ||||
|             LibERC20Token.transfer( | ||||
|                 token, | ||||
|                 to, | ||||
|                 amount | ||||
|             ); | ||||
|         } else { | ||||
|             LibERC20Token.transferFrom( | ||||
|                 token, | ||||
|                 from, | ||||
|                 to, | ||||
|                 amount | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Decodes ERC721 assetData and transfers given amount to sender. | ||||
|     /// @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 to sender. | ||||
|     function transferERC721Token( | ||||
|         bytes memory assetData, | ||||
|         address from, | ||||
|         address to, | ||||
|         uint256 amount | ||||
|     ) | ||||
|         internal | ||||
|     { | ||||
|         if (amount != 1) { | ||||
|             LibRichErrors.rrevert(LibForwarderRichErrors.Erc721AmountMustEqualOneError( | ||||
|                 amount | ||||
|             )); | ||||
|         } | ||||
|         // Decode asset data. | ||||
|         address token = assetData.readAddress(16); | ||||
|         uint256 tokenId = assetData.readUint256(36); | ||||
|  | ||||
|         // Perform transfer. | ||||
|         IERC721Token(token).transferFrom( | ||||
|             from, | ||||
|             to, | ||||
|             tokenId | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Decodes ERC1155 assetData and transfers given amounts to sender. | ||||
|     /// @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 to sender. | ||||
|     function transferERC1155Assets( | ||||
|         bytes memory assetData, | ||||
|         address from, | ||||
|         address to, | ||||
|         uint256 amount | ||||
|     ) | ||||
|         internal | ||||
|     { | ||||
|         // Decode assetData | ||||
|         // solhint-disable | ||||
|         ( | ||||
|             address token, | ||||
|             uint256[] memory ids, | ||||
|             uint256[] memory values, | ||||
|             bytes memory data | ||||
|         ) = abi.decode( | ||||
|             assetData.slice(4, assetData.length), | ||||
|             (address, uint256[], uint256[], bytes) | ||||
|         ); | ||||
|         // solhint-enable | ||||
|  | ||||
|         // Scale up values by `amount` | ||||
|         uint256 length = values.length; | ||||
|         uint256[] memory scaledValues = new uint256[](length); | ||||
|         for (uint256 i = 0; i != length; i++) { | ||||
|             scaledValues[i] = values[i].safeMul(amount); | ||||
|         } | ||||
|  | ||||
|         // Execute `safeBatchTransferFrom` call | ||||
|         // Either succeeds or throws | ||||
|         IERC1155(token).safeBatchTransferFrom( | ||||
|             from, | ||||
|             to, | ||||
|             ids, | ||||
|             scaledValues, | ||||
|             data | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Decodes MultiAsset assetData and recursively transfers assets to sender. | ||||
|     /// @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 to sender. | ||||
|     function transferMultiAsset( | ||||
|         bytes memory assetData, | ||||
|         address from, | ||||
|         address to, | ||||
|         uint256 amount | ||||
|     ) | ||||
|         internal | ||||
|     { | ||||
|         // solhint-disable indent | ||||
|         (uint256[] memory nestedAmounts, bytes[] memory nestedAssetData) = abi.decode( | ||||
|             assetData.slice(4, assetData.length), | ||||
|             (uint256[], bytes[]) | ||||
|         ); | ||||
|         // solhint-enable indent | ||||
|  | ||||
|         uint256 numNestedAssets = nestedAssetData.length; | ||||
|         for (uint256 i = 0; i != numNestedAssets; i++) { | ||||
|             transferFrom( | ||||
|                 nestedAssetData[i], | ||||
|                 from, | ||||
|                 to, | ||||
|                 amount.safeMul(nestedAmounts[i]) | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -18,29 +18,49 @@ | ||||
|  | ||||
| pragma solidity ^0.5.9; | ||||
|  | ||||
| import "@0x/contracts-utils/contracts/src/LibBytes.sol"; | ||||
| import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol"; | ||||
| import "../interfaces/IExchangeV2.sol"; | ||||
|  | ||||
|  | ||||
| contract LibConstants { | ||||
|  | ||||
|     using LibBytes for bytes; | ||||
|     uint256 constant internal MAX_UINT = uint256(-1); | ||||
|  | ||||
|     uint256 constant internal MAX_UINT = 2**256 - 1; | ||||
|     // The v2 order id is the first 4 bytes of the ExchangeV2 order schema hash. | ||||
|     // bytes4(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", | ||||
|     //     ")" | ||||
|     // ))); | ||||
|     bytes4 constant public EXCHANGE_V2_ORDER_ID = 0x770501f8; | ||||
|  | ||||
|      // solhint-disable var-name-mixedcase | ||||
|     IExchange internal EXCHANGE; | ||||
|     IExchangeV2 internal EXCHANGE_V2; | ||||
|     IEtherToken internal ETHER_TOKEN; | ||||
|     // solhint-enable var-name-mixedcase | ||||
|  | ||||
|     constructor ( | ||||
|         address _exchange, | ||||
|         address _exchangeV2, | ||||
|         address _weth | ||||
|     ) | ||||
|         public | ||||
|     { | ||||
|         EXCHANGE = IExchange(_exchange); | ||||
|         EXCHANGE_V2 = IExchangeV2(_exchangeV2); | ||||
|         ETHER_TOKEN = IEtherToken(_weth); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -21,16 +21,22 @@ pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../src/MixinExchangeWrapper.sol"; | ||||
| import "../src/libs/LibConstants.sol"; | ||||
| import "../src/libs/LibAssetDataTransfer.sol"; | ||||
| import "../src/MixinReceiver.sol"; | ||||
|  | ||||
|  | ||||
| contract TestForwarder is | ||||
|     LibConstants, | ||||
|     MixinExchangeWrapper | ||||
|     MixinExchangeWrapper, | ||||
|     MixinReceiver | ||||
| { | ||||
|     using LibAssetDataTransfer for bytes; | ||||
|  | ||||
|     // solhint-disable no-empty-blocks | ||||
|     constructor () | ||||
|         public | ||||
|         LibConstants( | ||||
|             address(0), | ||||
|             address(0), | ||||
|             address(0) | ||||
|         ) | ||||
| @@ -49,15 +55,12 @@ contract TestForwarder is | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function transferAssetToSender( | ||||
|     function transferOut( | ||||
|         bytes memory assetData, | ||||
|         uint256 amount | ||||
|     ) | ||||
|         public | ||||
|     { | ||||
|         _transferAssetToSender( | ||||
|             assetData, | ||||
|             amount | ||||
|         ); | ||||
|         assetData.transferOut(amount); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@0x/contracts-exchange-forwarder", | ||||
|     "version": "4.0.1", | ||||
|     "version": "4.0.4", | ||||
|     "engines": { | ||||
|         "node": ">=6.12" | ||||
|     }, | ||||
| @@ -38,8 +38,8 @@ | ||||
|         "docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" | ||||
|     }, | ||||
|     "config": { | ||||
|         "publicInterfaceContracts": "Forwarder", | ||||
|         "abis": "./test/generated-artifacts/@(Forwarder|IAssets|IForwarder|IForwarderCore|LibConstants|LibForwarderRichErrors|MixinAssets|MixinExchangeWrapper|MixinForwarderCore|MixinWeth|TestForwarder).json", | ||||
|         "publicInterfaceContracts": "Forwarder,IExchangeV2", | ||||
|         "abis": "./test/generated-artifacts/@(Forwarder|IAssets|IExchangeV2|IForwarder|IForwarderCore|LibAssetDataTransfer|LibConstants|LibForwarderRichErrors|MixinAssets|MixinExchangeWrapper|MixinForwarderCore|MixinReceiver|MixinWeth|TestForwarder).json", | ||||
|         "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." | ||||
|     }, | ||||
|     "repository": { | ||||
| @@ -52,24 +52,25 @@ | ||||
|     }, | ||||
|     "homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md", | ||||
|     "devDependencies": { | ||||
|         "@0x/abi-gen": "^5.0.1", | ||||
|         "@0x/contracts-asset-proxy": "^3.0.1", | ||||
|         "@0x/contracts-dev-utils": "^1.0.1", | ||||
|         "@0x/contracts-erc20": "^3.0.1", | ||||
|         "@0x/contracts-erc721": "^3.0.1", | ||||
|         "@0x/contracts-exchange": "^3.0.1", | ||||
|         "@0x/contracts-exchange-libs": "^4.0.1", | ||||
|         "@0x/contracts-gen": "^2.0.1", | ||||
|         "@0x/contracts-test-utils": "^5.0.0", | ||||
|         "@0x/contracts-utils": "^4.0.1", | ||||
|         "@0x/dev-utils": "^3.0.1", | ||||
|         "@0x/order-utils": "^10.0.0", | ||||
|         "@0x/sol-compiler": "^4.0.1", | ||||
|         "@0x/abi-gen": "^5.1.0", | ||||
|         "@0x/contracts-asset-proxy": "^3.1.1", | ||||
|         "@0x/contracts-dev-utils": "^1.0.4", | ||||
|         "@0x/contracts-erc1155": "^2.0.4", | ||||
|         "@0x/contracts-erc20": "^3.0.4", | ||||
|         "@0x/contracts-erc721": "^3.0.4", | ||||
|         "@0x/contracts-exchange": "^3.1.0", | ||||
|         "@0x/contracts-exchange-libs": "^4.1.0", | ||||
|         "@0x/contracts-gen": "^2.0.4", | ||||
|         "@0x/contracts-test-utils": "^5.1.1", | ||||
|         "@0x/contracts-utils": "^4.1.0", | ||||
|         "@0x/dev-utils": "^3.1.1", | ||||
|         "@0x/order-utils": "^10.1.1", | ||||
|         "@0x/sol-compiler": "^4.0.4", | ||||
|         "@0x/ts-doc-gen": "^0.0.22", | ||||
|         "@0x/tslint-config": "^4.0.0", | ||||
|         "@0x/types": "^3.1.0", | ||||
|         "@0x/utils": "^5.1.0", | ||||
|         "@0x/web3-wrapper": "^7.0.1", | ||||
|         "@0x/types": "^3.1.1", | ||||
|         "@0x/utils": "^5.2.0", | ||||
|         "@0x/web3-wrapper": "^7.0.4", | ||||
|         "@types/lodash": "4.14.104", | ||||
|         "@types/mocha": "^5.2.7", | ||||
|         "@types/node": "*", | ||||
| @@ -89,8 +90,8 @@ | ||||
|         "typescript": "3.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@0x/base-contract": "^6.0.1", | ||||
|         "@0x/typescript-typings": "^5.0.0", | ||||
|         "@0x/base-contract": "^6.1.0", | ||||
|         "@0x/typescript-typings": "^5.0.1", | ||||
|         "ethereum-types": "^3.0.0" | ||||
|     }, | ||||
|     "publishConfig": { | ||||
|   | ||||
| @@ -6,4 +6,5 @@ | ||||
| import { ContractArtifact } from 'ethereum-types'; | ||||
|  | ||||
| import * as Forwarder from '../generated-artifacts/Forwarder.json'; | ||||
| export const artifacts = { Forwarder: Forwarder as ContractArtifact }; | ||||
| import * as IExchangeV2 from '../generated-artifacts/IExchangeV2.json'; | ||||
| export const artifacts = { Forwarder: Forwarder as ContractArtifact, IExchangeV2: IExchangeV2 as ContractArtifact }; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| export { artifacts } from './artifacts'; | ||||
| export { ForwarderContract } from './wrappers'; | ||||
| export { ForwarderContract, IExchangeV2Contract } from './wrappers'; | ||||
| export { ExchangeForwarderRevertErrors } from '@0x/utils'; | ||||
| export { | ||||
|     ContractArtifact, | ||||
|   | ||||
| @@ -4,3 +4,4 @@ | ||||
|  * ----------------------------------------------------------------------------- | ||||
|  */ | ||||
| export * from '../generated-wrappers/forwarder'; | ||||
| export * from '../generated-wrappers/i_exchange_v2'; | ||||
|   | ||||
| @@ -7,13 +7,16 @@ import { ContractArtifact } from 'ethereum-types'; | ||||
|  | ||||
| import * as Forwarder from '../test/generated-artifacts/Forwarder.json'; | ||||
| import * as IAssets from '../test/generated-artifacts/IAssets.json'; | ||||
| import * as IExchangeV2 from '../test/generated-artifacts/IExchangeV2.json'; | ||||
| import * as IForwarder from '../test/generated-artifacts/IForwarder.json'; | ||||
| import * as IForwarderCore from '../test/generated-artifacts/IForwarderCore.json'; | ||||
| import * as LibAssetDataTransfer from '../test/generated-artifacts/LibAssetDataTransfer.json'; | ||||
| import * as LibConstants from '../test/generated-artifacts/LibConstants.json'; | ||||
| import * as LibForwarderRichErrors from '../test/generated-artifacts/LibForwarderRichErrors.json'; | ||||
| import * as MixinAssets from '../test/generated-artifacts/MixinAssets.json'; | ||||
| import * as MixinExchangeWrapper from '../test/generated-artifacts/MixinExchangeWrapper.json'; | ||||
| import * as MixinForwarderCore from '../test/generated-artifacts/MixinForwarderCore.json'; | ||||
| import * as MixinReceiver from '../test/generated-artifacts/MixinReceiver.json'; | ||||
| import * as MixinWeth from '../test/generated-artifacts/MixinWeth.json'; | ||||
| import * as TestForwarder from '../test/generated-artifacts/TestForwarder.json'; | ||||
| export const artifacts = { | ||||
| @@ -21,10 +24,13 @@ export const artifacts = { | ||||
|     MixinAssets: MixinAssets as ContractArtifact, | ||||
|     MixinExchangeWrapper: MixinExchangeWrapper as ContractArtifact, | ||||
|     MixinForwarderCore: MixinForwarderCore as ContractArtifact, | ||||
|     MixinReceiver: MixinReceiver as ContractArtifact, | ||||
|     MixinWeth: MixinWeth as ContractArtifact, | ||||
|     IAssets: IAssets as ContractArtifact, | ||||
|     IExchangeV2: IExchangeV2 as ContractArtifact, | ||||
|     IForwarder: IForwarder as ContractArtifact, | ||||
|     IForwarderCore: IForwarderCore as ContractArtifact, | ||||
|     LibAssetDataTransfer: LibAssetDataTransfer as ContractArtifact, | ||||
|     LibConstants: LibConstants as ContractArtifact, | ||||
|     LibForwarderRichErrors: LibForwarderRichErrors as ContractArtifact, | ||||
|     TestForwarder: TestForwarder as ContractArtifact, | ||||
|   | ||||
| @@ -1,4 +1,11 @@ | ||||
| import { IAssetDataContract } from '@0x/contracts-asset-proxy'; | ||||
| import { | ||||
|     artifacts as ERC1155Artifacts, | ||||
|     ERC1155Events, | ||||
|     ERC1155MintableContract, | ||||
|     ERC1155TransferBatchEventArgs, | ||||
|     Erc1155Wrapper, | ||||
| } from '@0x/contracts-erc1155'; | ||||
| import { | ||||
|     artifacts as ERC20Artifacts, | ||||
|     DummyERC20TokenContract, | ||||
| @@ -20,11 +27,13 @@ import { | ||||
|     verifyEventsFromLogs, | ||||
| } from '@0x/contracts-test-utils'; | ||||
| import { BigNumber, ExchangeForwarderRevertErrors, hexUtils } from '@0x/utils'; | ||||
| import { LogWithDecodedArgs } from 'ethereum-types'; | ||||
|  | ||||
| import { artifacts } from './artifacts'; | ||||
| import { TestForwarderContract } from './wrappers'; | ||||
|  | ||||
| blockchainTests('Supported asset type unit tests', env => { | ||||
| // tslint:disable:no-unnecessary-type-assertion | ||||
| blockchainTests.resets('Supported asset type unit tests', env => { | ||||
|     let forwarder: TestForwarderContract; | ||||
|     let assetDataEncoder: IAssetDataContract; | ||||
|     let bridgeAddress: string; | ||||
| @@ -33,11 +42,15 @@ blockchainTests('Supported asset type unit tests', env => { | ||||
|  | ||||
|     let erc20Token: DummyERC20TokenContract; | ||||
|     let erc721Token: DummyERC721TokenContract; | ||||
|     let erc1155Token: ERC1155MintableContract; | ||||
|     let erc1155Wrapper: Erc1155Wrapper; | ||||
|     let nftId: BigNumber; | ||||
|  | ||||
|     let erc20AssetData: string; | ||||
|     let erc721AssetData: string; | ||||
|     let erc20BridgeAssetData: string; | ||||
|     let staticCallAssetData: string; | ||||
|     let multiAssetData: string; | ||||
|  | ||||
|     before(async () => { | ||||
|         [receiver] = await env.getAccountAddressesAsync(); | ||||
| @@ -47,7 +60,7 @@ blockchainTests('Supported asset type unit tests', env => { | ||||
|             artifacts.TestForwarder, | ||||
|             env.provider, | ||||
|             env.txDefaults, | ||||
|             { ...artifacts, ...ERC20Artifacts, ...ERC721Artifacts }, | ||||
|             { ...artifacts, ...ERC20Artifacts, ...ERC721Artifacts, ...ERC1155Artifacts }, | ||||
|         ); | ||||
|  | ||||
|         erc20Token = await DummyERC20TokenContract.deployFrom0xArtifactAsync( | ||||
| @@ -70,14 +83,30 @@ blockchainTests('Supported asset type unit tests', env => { | ||||
|             constants.DUMMY_TOKEN_NAME, | ||||
|             constants.DUMMY_TOKEN_SYMBOL, | ||||
|         ); | ||||
|         nftId = getRandomInteger(constants.ZERO_AMOUNT, constants.MAX_UINT256); | ||||
|         nftId = getRandomInteger(0, constants.MAX_UINT256); | ||||
|         erc721AssetData = assetDataEncoder.ERC721Token(erc721Token.address, nftId).getABIEncodedTransactionData(); | ||||
|  | ||||
|         erc1155Token = await ERC1155MintableContract.deployFrom0xArtifactAsync( | ||||
|             ERC1155Artifacts.ERC1155Mintable, | ||||
|             env.provider, | ||||
|             env.txDefaults, | ||||
|             ERC1155Artifacts, | ||||
|         ); | ||||
|         erc1155Wrapper = new Erc1155Wrapper(erc1155Token, receiver); | ||||
|  | ||||
|         bridgeAddress = randomAddress(); | ||||
|         bridgeData = hexUtils.random(); | ||||
|         erc20BridgeAssetData = assetDataEncoder | ||||
|             .ERC20Bridge(erc20Token.address, bridgeAddress, bridgeData) | ||||
|             .getABIEncodedTransactionData(); | ||||
|  | ||||
|         staticCallAssetData = assetDataEncoder | ||||
|             .StaticCall(randomAddress(), hexUtils.random(), constants.KECCAK256_NULL) | ||||
|             .getABIEncodedTransactionData(); | ||||
|  | ||||
|         multiAssetData = assetDataEncoder | ||||
|             .MultiAsset([new BigNumber(1)], [erc20AssetData]) | ||||
|             .getABIEncodedTransactionData(); | ||||
|     }); | ||||
|  | ||||
|     describe('_areUnderlyingAssetsEqual', () => { | ||||
| @@ -115,13 +144,64 @@ blockchainTests('Supported asset type unit tests', env => { | ||||
|                 .callAsync(); | ||||
|             expect(result).to.be.false(); | ||||
|         }); | ||||
|         it('returns false if assetData1 == assetData2 are ERC721', async () => { | ||||
|         it('returns true if assetData1 == assetData2 are ERC721', async () => { | ||||
|             const result = await forwarder.areUnderlyingAssetsEqual(erc721AssetData, erc721AssetData).callAsync(); | ||||
|             expect(result).to.be.true(); | ||||
|         }); | ||||
|         it('returns false if assetData1 != assetData2 are ERC721', async () => { | ||||
|             const differentErc721AssetData = assetDataEncoder | ||||
|                 .ERC721Token(randomAddress(), getRandomInteger(0, constants.MAX_UINT256)) | ||||
|                 .getABIEncodedTransactionData(); | ||||
|             const result = await forwarder | ||||
|                 .areUnderlyingAssetsEqual(erc721AssetData, differentErc721AssetData) | ||||
|                 .callAsync(); | ||||
|             expect(result).to.be.false(); | ||||
|         }); | ||||
|         it('returns true if assetData1 == assetData2 are StaticCall', async () => { | ||||
|             const result = await forwarder | ||||
|                 .areUnderlyingAssetsEqual(staticCallAssetData, staticCallAssetData) | ||||
|                 .callAsync(); | ||||
|             expect(result).to.be.true(); | ||||
|         }); | ||||
|         it('returns false if assetData1 != assetData2 are StaticCall', async () => { | ||||
|             const differentStaticCallAssetData = assetDataEncoder | ||||
|                 .StaticCall(randomAddress(), hexUtils.random(), constants.KECCAK256_NULL) | ||||
|                 .getABIEncodedTransactionData(); | ||||
|             const result = await forwarder | ||||
|                 .areUnderlyingAssetsEqual(staticCallAssetData, differentStaticCallAssetData) | ||||
|                 .callAsync(); | ||||
|             expect(result).to.be.false(); | ||||
|         }); | ||||
|         it('returns false if assetData1 is ERC20 and assetData2 is MultiAsset', async () => { | ||||
|             const result = await forwarder.areUnderlyingAssetsEqual(erc20AssetData, multiAssetData).callAsync(); | ||||
|             expect(result).to.be.false(); | ||||
|         }); | ||||
|         it('returns true if assetData1 == assetData2 are MultiAsset (single nested asset)', async () => { | ||||
|             const result = await forwarder.areUnderlyingAssetsEqual(multiAssetData, multiAssetData).callAsync(); | ||||
|             expect(result).to.be.true(); | ||||
|         }); | ||||
|         it('returns true if assetData1 == assetData2 are MultiAsset (multiple nested assets)', async () => { | ||||
|             const assetData = assetDataEncoder | ||||
|                 .MultiAsset( | ||||
|                     [getRandomInteger(0, constants.MAX_UINT256), new BigNumber(1)], | ||||
|                     [erc20AssetData, erc721AssetData], | ||||
|                 ) | ||||
|                 .getABIEncodedTransactionData(); | ||||
|             const result = await forwarder.areUnderlyingAssetsEqual(assetData, assetData).callAsync(); | ||||
|             expect(result).to.be.true(); | ||||
|         }); | ||||
|         it('returns false if assetData1 != assetData2 are MultiAsset', async () => { | ||||
|             const differentMultiAssetData = assetDataEncoder | ||||
|                 .MultiAsset([getRandomInteger(0, constants.MAX_UINT256)], [erc721AssetData]) | ||||
|                 .getABIEncodedTransactionData(); | ||||
|             const result = await forwarder | ||||
|                 .areUnderlyingAssetsEqual(multiAssetData, differentMultiAssetData) | ||||
|                 .callAsync(); | ||||
|             expect(result).to.be.false(); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('_transferAssetToSender', () => { | ||||
|     describe('transferOut', () => { | ||||
|         const TRANSFER_AMOUNT = new BigNumber(1); | ||||
|         before(async () => { | ||||
|             await erc20Token | ||||
| @@ -132,7 +212,7 @@ blockchainTests('Supported asset type unit tests', env => { | ||||
|  | ||||
|         it('transfers an ERC20 token given ERC20 assetData', async () => { | ||||
|             const txReceipt = await forwarder | ||||
|                 .transferAssetToSender(erc20AssetData, TRANSFER_AMOUNT) | ||||
|                 .transferOut(erc20AssetData, TRANSFER_AMOUNT) | ||||
|                 .awaitTransactionSuccessAsync({ from: receiver }); | ||||
|             verifyEventsFromLogs<ERC20TokenTransferEventArgs>( | ||||
|                 txReceipt.logs, | ||||
| @@ -142,7 +222,7 @@ blockchainTests('Supported asset type unit tests', env => { | ||||
|         }); | ||||
|         it('transfers an ERC721 token given ERC721 assetData and amount == 1', async () => { | ||||
|             const txReceipt = await forwarder | ||||
|                 .transferAssetToSender(erc721AssetData, TRANSFER_AMOUNT) | ||||
|                 .transferOut(erc721AssetData, TRANSFER_AMOUNT) | ||||
|                 .awaitTransactionSuccessAsync({ from: receiver }); | ||||
|             verifyEventsFromLogs<ERC721TokenTransferEventArgs>( | ||||
|                 txReceipt.logs, | ||||
| @@ -153,14 +233,120 @@ blockchainTests('Supported asset type unit tests', env => { | ||||
|         it('reverts if attempting to transfer an ERC721 token with amount != 1', async () => { | ||||
|             const invalidAmount = new BigNumber(2); | ||||
|             const tx = forwarder | ||||
|                 .transferAssetToSender(erc721AssetData, invalidAmount) | ||||
|                 .transferOut(erc721AssetData, invalidAmount) | ||||
|                 .awaitTransactionSuccessAsync({ from: receiver }); | ||||
|             const expectedError = new ExchangeForwarderRevertErrors.Erc721AmountMustEqualOneError(invalidAmount); | ||||
|             return expect(tx).to.revertWith(expectedError); | ||||
|         }); | ||||
|         it('transfers a single ERC1155 token', async () => { | ||||
|             const values = [new BigNumber(1)]; | ||||
|             const amount = new BigNumber(1); | ||||
|             const ids = [await erc1155Wrapper.mintFungibleTokensAsync([forwarder.address], values)]; | ||||
|             const assetData = assetDataEncoder | ||||
|                 .ERC1155Assets(erc1155Token.address, ids, values, constants.NULL_BYTES) | ||||
|                 .getABIEncodedTransactionData(); | ||||
|             const txReceipt = await forwarder | ||||
|                 .transferOut(assetData, amount) | ||||
|                 .awaitTransactionSuccessAsync({ from: receiver }); | ||||
|             verifyEventsFromLogs<ERC1155TransferBatchEventArgs>( | ||||
|                 txReceipt.logs, | ||||
|                 [{ operator: forwarder.address, from: forwarder.address, to: receiver, ids, values }], | ||||
|                 ERC1155Events.TransferBatch, | ||||
|             ); | ||||
|         }); | ||||
|         it('transfers multiple ids of an ERC1155 token', async () => { | ||||
|             const amount = new BigNumber(1); | ||||
|             const ids = [ | ||||
|                 await erc1155Wrapper.mintFungibleTokensAsync([forwarder.address], [amount]), | ||||
|                 await erc1155Wrapper.mintFungibleTokensAsync([forwarder.address], [amount]), | ||||
|             ]; | ||||
|             const values = [amount, amount]; | ||||
|             const assetData = assetDataEncoder | ||||
|                 .ERC1155Assets(erc1155Token.address, ids, values, constants.NULL_BYTES) | ||||
|                 .getABIEncodedTransactionData(); | ||||
|             const txReceipt = await forwarder.transferOut(assetData, amount).awaitTransactionSuccessAsync(); | ||||
|             verifyEventsFromLogs<ERC1155TransferBatchEventArgs>( | ||||
|                 txReceipt.logs, | ||||
|                 [{ operator: forwarder.address, from: forwarder.address, to: receiver, ids, values }], | ||||
|                 ERC1155Events.TransferBatch, | ||||
|             ); | ||||
|         }); | ||||
|         it('scales up values when transfering ERC1155 tokens', async () => { | ||||
|             const amount = new BigNumber(2); | ||||
|             const values = [new BigNumber(1), new BigNumber(2)]; | ||||
|             const scaledValues = values.map(value => value.times(amount)); | ||||
|             const ids = [ | ||||
|                 await erc1155Wrapper.mintFungibleTokensAsync([forwarder.address], [scaledValues[0]]), | ||||
|                 await erc1155Wrapper.mintFungibleTokensAsync([forwarder.address], [scaledValues[1]]), | ||||
|             ]; | ||||
|             const assetData = assetDataEncoder | ||||
|                 .ERC1155Assets(erc1155Token.address, ids, values, constants.NULL_BYTES) | ||||
|                 .getABIEncodedTransactionData(); | ||||
|             const txReceipt = await forwarder.transferOut(assetData, amount).awaitTransactionSuccessAsync(); | ||||
|             verifyEventsFromLogs<ERC1155TransferBatchEventArgs>( | ||||
|                 txReceipt.logs, | ||||
|                 [{ operator: forwarder.address, from: forwarder.address, to: receiver, ids, values: scaledValues }], | ||||
|                 ERC1155Events.TransferBatch, | ||||
|             ); | ||||
|         }); | ||||
|         it('transfers a single ERC20 token wrapped as MultiAsset', async () => { | ||||
|             const nestedAmount = new BigNumber(1337); | ||||
|             const erc20MultiAssetData = assetDataEncoder | ||||
|                 .MultiAsset([nestedAmount], [erc20AssetData]) | ||||
|                 .getABIEncodedTransactionData(); | ||||
|             const multiAssetAmount = new BigNumber(2); | ||||
|             const txReceipt = await forwarder | ||||
|                 .transferOut(erc20MultiAssetData, multiAssetAmount) | ||||
|                 .awaitTransactionSuccessAsync({ from: receiver }); | ||||
|             verifyEventsFromLogs<ERC20TokenTransferEventArgs>( | ||||
|                 txReceipt.logs, | ||||
|                 [{ _from: forwarder.address, _to: receiver, _value: multiAssetAmount.times(nestedAmount) }], | ||||
|                 ERC20TokenEvents.Transfer, | ||||
|             ); | ||||
|         }); | ||||
|         it('transfers ERC20, ERC721, and StaticCall assets wrapped as MultiAsset', async () => { | ||||
|             const nestedAmounts = [new BigNumber(1337), TRANSFER_AMOUNT, TRANSFER_AMOUNT]; | ||||
|             const assortedMultiAssetData = assetDataEncoder | ||||
|                 .MultiAsset(nestedAmounts, [erc20AssetData, erc721AssetData, staticCallAssetData]) | ||||
|                 .getABIEncodedTransactionData(); | ||||
|             const txReceipt = await forwarder | ||||
|                 .transferOut(assortedMultiAssetData, TRANSFER_AMOUNT) | ||||
|                 .awaitTransactionSuccessAsync({ from: receiver }); | ||||
|             expect(txReceipt.logs.length).to.equal(2); | ||||
|             // tslint:disable:no-unnecessary-type-assertion | ||||
|             const erc20TransferEvent = (txReceipt.logs[0] as LogWithDecodedArgs<ERC20TokenTransferEventArgs>).args; | ||||
|             const erc721TransferEvent = (txReceipt.logs[1] as LogWithDecodedArgs<ERC721TokenTransferEventArgs>).args; | ||||
|             // tslint:enable:no-unnecessary-type-assertion | ||||
|             expect(erc20TransferEvent).to.deep.equal({ | ||||
|                 _from: forwarder.address, | ||||
|                 _to: receiver, | ||||
|                 _value: nestedAmounts[0], | ||||
|             }); | ||||
|             expect(erc721TransferEvent).to.deep.equal({ _from: forwarder.address, _to: receiver, _tokenId: nftId }); | ||||
|         }); | ||||
|         it('performs nested MultiAsset transfers', async () => { | ||||
|             const nestedAmounts = [TRANSFER_AMOUNT, TRANSFER_AMOUNT, TRANSFER_AMOUNT]; | ||||
|             const assortedMultiAssetData = assetDataEncoder | ||||
|                 .MultiAsset(nestedAmounts, [multiAssetData, erc721AssetData, staticCallAssetData]) | ||||
|                 .getABIEncodedTransactionData(); | ||||
|             const txReceipt = await forwarder | ||||
|                 .transferOut(assortedMultiAssetData, TRANSFER_AMOUNT) | ||||
|                 .awaitTransactionSuccessAsync({ from: receiver }); | ||||
|             expect(txReceipt.logs.length).to.equal(2); | ||||
|             // tslint:disable:no-unnecessary-type-assertion | ||||
|             const erc20TransferEvent = (txReceipt.logs[0] as LogWithDecodedArgs<ERC20TokenTransferEventArgs>).args; | ||||
|             const erc721TransferEvent = (txReceipt.logs[1] as LogWithDecodedArgs<ERC721TokenTransferEventArgs>).args; | ||||
|             // tslint:enable:no-unnecessary-type-assertion | ||||
|             expect(erc20TransferEvent).to.deep.equal({ | ||||
|                 _from: forwarder.address, | ||||
|                 _to: receiver, | ||||
|                 _value: TRANSFER_AMOUNT, | ||||
|             }); | ||||
|             expect(erc721TransferEvent).to.deep.equal({ _from: forwarder.address, _to: receiver, _tokenId: nftId }); | ||||
|         }); | ||||
|         it('transfers an ERC20 token given ERC20Bridge assetData', async () => { | ||||
|             const txReceipt = await forwarder | ||||
|                 .transferAssetToSender(erc20BridgeAssetData, TRANSFER_AMOUNT) | ||||
|                 .transferOut(erc20BridgeAssetData, TRANSFER_AMOUNT) | ||||
|                 .awaitTransactionSuccessAsync({ from: receiver }); | ||||
|             verifyEventsFromLogs<ERC20TokenTransferEventArgs>( | ||||
|                 txReceipt.logs, | ||||
| @@ -168,10 +354,16 @@ blockchainTests('Supported asset type unit tests', env => { | ||||
|                 ERC20TokenEvents.Transfer, | ||||
|             ); | ||||
|         }); | ||||
|         it('noops (emits no events) for StaticCall assetData', async () => { | ||||
|             const txReceipt = await forwarder | ||||
|                 .transferOut(staticCallAssetData, TRANSFER_AMOUNT) | ||||
|                 .awaitTransactionSuccessAsync({ from: receiver }); | ||||
|             expect(txReceipt.logs.length).to.equal(0); | ||||
|         }); | ||||
|         it('reverts if assetData is unsupported', async () => { | ||||
|             const randomBytes = hexUtils.random(); | ||||
|             const tx = forwarder | ||||
|                 .transferAssetToSender(randomBytes, TRANSFER_AMOUNT) | ||||
|                 .transferOut(randomBytes, TRANSFER_AMOUNT) | ||||
|                 .awaitTransactionSuccessAsync({ from: receiver }); | ||||
|             const expectedError = new ExchangeForwarderRevertErrors.UnsupportedAssetProxyError( | ||||
|                 hexUtils.slice(randomBytes, 0, 4), | ||||
|   | ||||
| @@ -5,12 +5,15 @@ | ||||
|  */ | ||||
| export * from '../test/generated-wrappers/forwarder'; | ||||
| export * from '../test/generated-wrappers/i_assets'; | ||||
| export * from '../test/generated-wrappers/i_exchange_v2'; | ||||
| export * from '../test/generated-wrappers/i_forwarder'; | ||||
| export * from '../test/generated-wrappers/i_forwarder_core'; | ||||
| export * from '../test/generated-wrappers/lib_asset_data_transfer'; | ||||
| export * from '../test/generated-wrappers/lib_constants'; | ||||
| export * from '../test/generated-wrappers/lib_forwarder_rich_errors'; | ||||
| export * from '../test/generated-wrappers/mixin_assets'; | ||||
| export * from '../test/generated-wrappers/mixin_exchange_wrapper'; | ||||
| export * from '../test/generated-wrappers/mixin_forwarder_core'; | ||||
| export * from '../test/generated-wrappers/mixin_receiver'; | ||||
| export * from '../test/generated-wrappers/mixin_weth'; | ||||
| export * from '../test/generated-wrappers/test_forwarder'; | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user