aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts')
-rw-r--r--packages/contracts/compiler.json26
-rw-r--r--packages/contracts/package.json10
-rw-r--r--packages/contracts/src/contracts/current/multisig/MultiSigWalletWithTimeLock/MultiSigWalletWithTimeLock.sol2
-rw-r--r--packages/contracts/src/contracts/current/multisig/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.sol2
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol83
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol89
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol73
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol117
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol59
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAuthorizable.sol50
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol39
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAuthorizable.sol42
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol619
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol106
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol446
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol297
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol171
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol191
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol102
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol515
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol41
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchange.sol38
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol80
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol44
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISignatureValidator.sol32
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISigner.sol33
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol33
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol142
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol60
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol68
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol93
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol93
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol51
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol46
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol117
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol65
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol50
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSignatureValidator.sol50
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/mixins/MTransactions.sol35
-rw-r--r--packages/contracts/src/contracts/current/test/DummyERC20Token/DummyERC20Token.sol56
-rw-r--r--packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol58
-rw-r--r--packages/contracts/src/contracts/current/test/DummyToken/DummyToken.sol37
-rw-r--r--packages/contracts/src/contracts/current/test/MaliciousToken/MaliciousToken.sol31
-rw-r--r--packages/contracts/src/contracts/current/test/Mintable/Mintable.sol32
-rw-r--r--packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol34
-rw-r--r--packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol133
-rw-r--r--packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol80
-rw-r--r--packages/contracts/src/contracts/current/test/TestSignatureValidator/TestSignatureValidator.sol41
-rw-r--r--packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol80
-rw-r--r--packages/contracts/src/contracts/current/tokens/ERC20Token/IERC20Token.sol73
-rw-r--r--packages/contracts/src/contracts/current/tokens/ERC721Token/ERC721Token.sol406
-rw-r--r--packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Receiver.sol60
-rw-r--r--packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Token.sol105
-rw-r--r--packages/contracts/src/contracts/current/tokens/Token/Token.sol35
-rw-r--r--packages/contracts/src/contracts/current/tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol28
-rw-r--r--packages/contracts/src/contracts/current/tokens/ZRXToken/ZRXToken.sol2
-rw-r--r--packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol206
-rw-r--r--packages/contracts/src/contracts/current/utils/Ownable/IOwnable.sol14
-rw-r--r--packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol14
-rw-r--r--packages/contracts/src/contracts/current/utils/SafeMath/SafeMath.sol3
-rw-r--r--packages/contracts/src/contracts/previous/Arbitrage/Arbitrage.sol (renamed from packages/contracts/src/contracts/current/tutorials/Arbitrage/Arbitrage.sol)6
-rw-r--r--packages/contracts/src/contracts/previous/EtherDelta/AccountLevels.sol (renamed from packages/contracts/src/contracts/current/tutorials/EtherDelta/AccountLevels.sol)0
-rw-r--r--packages/contracts/src/contracts/previous/EtherDelta/EtherDelta.sol (renamed from packages/contracts/src/contracts/current/tutorials/EtherDelta/EtherDelta.sol)4
-rw-r--r--packages/contracts/src/contracts/previous/Exchange/Exchange_v1.sol602
-rw-r--r--packages/contracts/src/contracts/previous/Exchange/IExchange_v1.sol226
-rw-r--r--packages/contracts/src/contracts/previous/Ownable/IOwnable_v1.sol18
-rw-r--r--packages/contracts/src/contracts/previous/TokenRegistry/ITokenRegistery.sol195
-rw-r--r--packages/contracts/src/contracts/previous/TokenRegistry/TokenRegistry.sol (renamed from packages/contracts/src/contracts/current/protocol/TokenRegistry/TokenRegistry.sol)4
-rw-r--r--packages/contracts/src/contracts/previous/TokenTransferProxy/TokenTransferProxy_v1.sol (renamed from packages/contracts/src/contracts/current/protocol/TokenTransferProxy/TokenTransferProxy.sol)8
-rw-r--r--packages/contracts/src/contracts/previous/UnlimitedAllowanceToken/UnlimitedAllowanceToken_v1.sol2
-rw-r--r--packages/contracts/src/utils/address_utils.ts12
-rw-r--r--packages/contracts/src/utils/artifacts.ts37
-rw-r--r--packages/contracts/src/utils/asset_proxy_utils.ts144
-rw-r--r--packages/contracts/src/utils/chai_setup.ts (renamed from packages/contracts/test/utils/chai_setup.ts)0
-rw-r--r--packages/contracts/src/utils/constants.ts43
-rw-r--r--packages/contracts/src/utils/crypto.ts (renamed from packages/contracts/util/crypto.ts)2
-rw-r--r--packages/contracts/src/utils/erc20_wrapper.ts116
-rw-r--r--packages/contracts/src/utils/erc721_wrapper.ts145
-rw-r--r--packages/contracts/src/utils/exchange_wrapper.ts254
-rw-r--r--packages/contracts/src/utils/formatters.ts60
-rw-r--r--packages/contracts/src/utils/log_decoder.ts39
-rw-r--r--packages/contracts/src/utils/multi_sig_wrapper.ts (renamed from packages/contracts/util/multi_sig_wrapper.ts)2
-rw-r--r--packages/contracts/src/utils/order_factory.ts37
-rw-r--r--packages/contracts/src/utils/order_utils.ts92
-rw-r--r--packages/contracts/src/utils/signing_utils.ts30
-rw-r--r--packages/contracts/src/utils/token_registry_wrapper.ts (renamed from packages/contracts/util/token_registry_wrapper.ts)2
-rw-r--r--packages/contracts/src/utils/transaction_factory.ts35
-rw-r--r--packages/contracts/src/utils/types.ts211
-rw-r--r--packages/contracts/src/utils/web3_wrapper.ts (renamed from packages/contracts/test/utils/web3_wrapper.ts)0
-rw-r--r--packages/contracts/test/asset_proxy/authorizable.ts130
-rw-r--r--packages/contracts/test/asset_proxy/proxies.ts425
-rw-r--r--packages/contracts/test/ether_token.ts18
-rw-r--r--packages/contracts/test/exchange/core.ts1375
-rw-r--r--packages/contracts/test/exchange/dispatcher.ts322
-rw-r--r--packages/contracts/test/exchange/helpers.ts234
-rw-r--r--packages/contracts/test/exchange/libs.ts160
-rw-r--r--packages/contracts/test/exchange/match_orders.ts843
-rw-r--r--packages/contracts/test/exchange/signature_validator.ts98
-rw-r--r--packages/contracts/test/exchange/transactions.ts215
-rw-r--r--packages/contracts/test/exchange/wrapper.ts1106
-rw-r--r--packages/contracts/test/libraries/lib_bytes.ts251
-rw-r--r--packages/contracts/test/multi_sig_with_time_lock.ts56
-rw-r--r--packages/contracts/test/multi_sig_with_time_lock_except_remove_auth_addr.ts18
-rw-r--r--packages/contracts/test/token_registry.ts41
-rw-r--r--packages/contracts/test/token_transfer_proxy/auth.ts105
-rw-r--r--packages/contracts/test/token_transfer_proxy/transfer_from.ts110
-rw-r--r--packages/contracts/test/tutorials/arbitrage.ts496
-rw-r--r--packages/contracts/test/unlimited_allowance_token.ts34
-rw-r--r--packages/contracts/test/utils/match_order_tester.ts356
-rw-r--r--packages/contracts/test/zrx_token.ts46
-rw-r--r--packages/contracts/tsconfig.json9
-rw-r--r--packages/contracts/util/artifacts.ts31
-rw-r--r--packages/contracts/util/balances.ts30
-rw-r--r--packages/contracts/util/constants.ts14
-rw-r--r--packages/contracts/util/exchange_wrapper.ts230
-rw-r--r--packages/contracts/util/formatters.ts111
-rw-r--r--packages/contracts/util/order_factory.ts37
-rw-r--r--packages/contracts/util/signed_order_utils.ts58
-rw-r--r--packages/contracts/util/types.ts102
119 files changed, 12057 insertions, 3068 deletions
diff --git a/packages/contracts/compiler.json b/packages/contracts/compiler.json
index 493d6373d..bf6cc2376 100644
--- a/packages/contracts/compiler.json
+++ b/packages/contracts/compiler.json
@@ -1,7 +1,11 @@
{
- "artifactsDir": "../migrations/artifacts/1.0.0",
+ "artifactsDir": "../migrations/artifacts/2.0.0",
"contractsDir": "src/contracts",
"compilerSettings": {
+ "optimizer": {
+ "enabled": true,
+ "runs": 0
+ },
"outputSelection": {
"*": {
"*": [
@@ -15,19 +19,21 @@
}
},
"contracts": [
+ "DummyERC20Token",
+ "DummyERC721Token",
+ "ERC20Proxy",
+ "ERC721Proxy",
"Exchange",
- "DummyToken",
- "ZRXToken",
- "Token",
- "WETH9",
- "TokenTransferProxy",
+ "MixinAuthorizable",
"MultiSigWallet",
"MultiSigWalletWithTimeLock",
"MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress",
- "MaliciousToken",
+ "TestAssetProxyDispatcher",
+ "TestLibBytes",
+ "TestLibs",
+ "TestSignatureValidator",
"TokenRegistry",
- "Arbitrage",
- "EtherDelta",
- "AccountLevels"
+ "WETH9",
+ "ZRXToken"
]
}
diff --git a/packages/contracts/package.json b/packages/contracts/package.json
index 0e8239e61..d862ae1c0 100644
--- a/packages/contracts/package.json
+++ b/packages/contracts/package.json
@@ -12,13 +12,12 @@
},
"scripts": {
"watch": "tsc -w",
- "prebuild": "run-s clean copy_artifacts generate_contract_wrappers",
- "copy_artifacts": "copyfiles -u 4 '../migrations/artifacts/1.0.0/**/*' ./lib/src/artifacts;",
+ "prebuild": "run-s clean compile copy_artifacts generate_contract_wrappers",
+ "copy_artifacts": "copyfiles -u 4 '../migrations/artifacts/2.0.0/**/*' ./lib/src/artifacts;",
"build": "tsc",
"test": "run-s build run_mocha",
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
"run_mocha": "mocha 'lib/test/**/*.js' --timeout 100000 --bail --exit",
- "compile:comment": "Yarn workspaces do not link binaries correctly so we need to reference them directly https://github.com/yarnpkg/yarn/issues/3846",
"compile": "sol-compiler",
"clean": "shx rm -rf lib src/contract_wrappers/generated",
"generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers && prettier --write 'src/contract_wrappers/generated/**.ts'",
@@ -29,7 +28,8 @@
"test:circleci": "yarn test:coverage"
},
"config": {
- "abis": "../migrations/artifacts/1.0.0/@(DummyToken|TokenTransferProxy|Exchange|TokenRegistry|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TokenRegistry|ZRXToken|Arbitrage|EtherDelta|AccountLevels|WETH9|MaliciousToken).json"
+ "abis":
+ "../migrations/artifacts/2.0.0/@(DummyERC20Token|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TestAssetProxyDispatcher|TestLibBytes|TestLibs|TestSignatureValidator|TokenRegistry|WETH9|ZRXToken).json"
},
"repository": {
"type": "git",
@@ -58,7 +58,7 @@
"npm-run-all": "^4.1.2",
"prettier": "^1.11.1",
"shx": "^0.2.2",
- "solc": "^0.4.23",
+ "solc": "^0.4.24",
"tslint": "5.8.0",
"typescript": "2.7.1",
"yargs": "^10.0.3"
diff --git a/packages/contracts/src/contracts/current/multisig/MultiSigWalletWithTimeLock/MultiSigWalletWithTimeLock.sol b/packages/contracts/src/contracts/current/multisig/MultiSigWalletWithTimeLock/MultiSigWalletWithTimeLock.sol
index a545d9813..e393b565f 100644
--- a/packages/contracts/src/contracts/current/multisig/MultiSigWalletWithTimeLock/MultiSigWalletWithTimeLock.sol
+++ b/packages/contracts/src/contracts/current/multisig/MultiSigWalletWithTimeLock/MultiSigWalletWithTimeLock.sol
@@ -1,6 +1,6 @@
/*
- Copyright 2017 ZeroEx Intl.
+ Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/contracts/src/contracts/current/multisig/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.sol b/packages/contracts/src/contracts/current/multisig/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.sol
index 3c6a3d2ef..3d44e4c07 100644
--- a/packages/contracts/src/contracts/current/multisig/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.sol
+++ b/packages/contracts/src/contracts/current/multisig/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.sol
@@ -1,6 +1,6 @@
/*
- Copyright 2017 ZeroEx Intl.
+ Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol
new file mode 100644
index 000000000..ee0c66fdc
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol
@@ -0,0 +1,83 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../../utils/LibBytes/LibBytes.sol";
+import "../../tokens/ERC20Token/IERC20Token.sol";
+import "./MixinAssetProxy.sol";
+import "./MixinAuthorizable.sol";
+
+contract ERC20Proxy is
+ LibBytes,
+ MixinAssetProxy,
+ MixinAuthorizable
+{
+
+ // Id of this proxy.
+ uint8 constant PROXY_ID = 1;
+
+ // Revert reasons
+ string constant INVALID_METADATA_LENGTH = "Metadata must have a length of 21.";
+ string constant TRANSFER_FAILED = "Transfer failed.";
+ string constant PROXY_ID_MISMATCH = "Proxy id in metadata does not match this proxy id.";
+
+ /// @dev Internal version of `transferFrom`.
+ /// @param assetMetadata Encoded byte array.
+ /// @param from Address to transfer asset from.
+ /// @param to Address to transfer asset to.
+ /// @param amount Amount of asset to transfer.
+ function transferFromInternal(
+ bytes memory assetMetadata,
+ address from,
+ address to,
+ uint256 amount)
+ internal
+ {
+ // Data must be intended for this proxy.
+ require(
+ uint8(assetMetadata[0]) == PROXY_ID,
+ PROXY_ID_MISMATCH
+ );
+
+ // Decode metadata.
+ require(
+ assetMetadata.length == 21,
+ INVALID_METADATA_LENGTH
+ );
+ address token = readAddress(assetMetadata, 1);
+
+ // Transfer tokens.
+ bool success = IERC20Token(token).transferFrom(from, to, amount);
+ require(
+ success == true,
+ TRANSFER_FAILED
+ );
+ }
+
+ /// @dev Gets the proxy id associated with the proxy address.
+ /// @return Proxy id.
+ function getProxyId()
+ external
+ view
+ returns (uint8)
+ {
+ return PROXY_ID;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol
new file mode 100644
index 000000000..94aab9139
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol
@@ -0,0 +1,89 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../../utils/LibBytes/LibBytes.sol";
+import "../../tokens/ERC721Token/ERC721Token.sol";
+import "./MixinAssetProxy.sol";
+import "./MixinAuthorizable.sol";
+
+contract ERC721Proxy is
+ LibBytes,
+ MixinAssetProxy,
+ MixinAuthorizable
+{
+
+ // Id of this proxy.
+ uint8 constant PROXY_ID = 2;
+
+ // Revert reasons
+ string constant INVALID_TRANSFER_AMOUNT = "Transfer amount must equal 1.";
+ string constant INVALID_METADATA_LENGTH = "Metadata must have a length of 53.";
+ string constant PROXY_ID_MISMATCH = "Proxy id in metadata does not match this proxy id.";
+
+ /// @dev Internal version of `transferFrom`.
+ /// @param assetMetadata Encoded byte array.
+ /// @param from Address to transfer asset from.
+ /// @param to Address to transfer asset to.
+ /// @param amount Amount of asset to transfer.
+ function transferFromInternal(
+ bytes memory assetMetadata,
+ address from,
+ address to,
+ uint256 amount)
+ internal
+ {
+ // Data must be intended for this proxy.
+ require(
+ uint8(assetMetadata[0]) == PROXY_ID,
+ PROXY_ID_MISMATCH
+ );
+
+ // There exists only 1 of each token.
+ require(
+ amount == 1,
+ INVALID_TRANSFER_AMOUNT
+ );
+
+ // Decode metadata
+ require(
+ assetMetadata.length == 53,
+ INVALID_METADATA_LENGTH
+ );
+ address token = readAddress(assetMetadata, 1);
+ uint256 tokenId = readUint256(assetMetadata, 21);
+
+ // Transfer token.
+ // Either succeeds or throws.
+ // @TODO: Call safeTransferFrom if there is additional
+ // data stored in `assetMetadata`.
+ ERC721Token(token).transferFrom(from, to, tokenId);
+ }
+
+ /// @dev Gets the proxy id associated with the proxy address.
+ /// @return Proxy id.
+ function getProxyId()
+ external
+ view
+ returns (uint8)
+ {
+ return PROXY_ID;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol
new file mode 100644
index 000000000..4ec31304f
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol
@@ -0,0 +1,73 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./mixins/MAssetProxy.sol";
+import "./mixins/MAuthorizable.sol";
+
+contract MixinAssetProxy is
+ MAuthorizable,
+ MAssetProxy
+{
+
+ /// @dev Transfers assets. Either succeeds or throws.
+ /// @param assetMetadata Encoded byte array.
+ /// @param from Address to transfer asset from.
+ /// @param to Address to transfer asset to.
+ /// @param amount Amount of asset to transfer.
+ function transferFrom(
+ bytes assetMetadata,
+ address from,
+ address to,
+ uint256 amount)
+ external
+ onlyAuthorized
+ {
+ transferFromInternal(
+ assetMetadata,
+ from,
+ to,
+ amount
+ );
+ }
+
+ /// @dev Makes multiple transfers of assets. Either succeeds or throws.
+ /// @param assetMetadata Array of byte arrays encoded for the respective asset proxy.
+ /// @param from Array of addresses to transfer assets from.
+ /// @param to Array of addresses to transfer assets to.
+ /// @param amounts Array of amounts of assets to transfer.
+ function batchTransferFrom(
+ bytes[] memory assetMetadata,
+ address[] memory from,
+ address[] memory to,
+ uint256[] memory amounts)
+ public
+ onlyAuthorized
+ {
+ for (uint256 i = 0; i < assetMetadata.length; i++) {
+ transferFromInternal(
+ assetMetadata[i],
+ from[i],
+ to[i],
+ amounts[i]
+ );
+ }
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol
new file mode 100644
index 000000000..0bbd3b318
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol
@@ -0,0 +1,117 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./mixins/MAuthorizable.sol";
+import "../../utils/Ownable/Ownable.sol";
+
+contract MixinAuthorizable is
+ Ownable,
+ MAuthorizable
+{
+
+ // Revert reasons
+ string constant SENDER_NOT_AUTHORIZED = "Sender not authorized to call this method.";
+ string constant TARGET_NOT_AUTHORIZED = "Target address must be authorized.";
+ string constant TARGET_ALREADY_AUTHORIZED = "Target must not already be authorized.";
+ string constant INDEX_OUT_OF_BOUNDS = "Specified array index is out of bounds.";
+ string constant INDEX_ADDRESS_MISMATCH = "Address found at index does not match target address.";
+
+ /// @dev Only authorized addresses can invoke functions with this modifier.
+ modifier onlyAuthorized {
+ require(
+ authorized[msg.sender],
+ SENDER_NOT_AUTHORIZED
+ );
+ _;
+ }
+
+ mapping (address => bool) public authorized;
+ address[] public authorities;
+
+ /// @dev Authorizes an address.
+ /// @param target Address to authorize.
+ function addAuthorizedAddress(address target)
+ external
+ onlyOwner
+ {
+ require(
+ !authorized[target],
+ TARGET_ALREADY_AUTHORIZED
+ );
+
+ authorized[target] = true;
+ authorities.push(target);
+ emit AuthorizedAddressAdded(target, msg.sender);
+ }
+
+ /// @dev Removes authorizion of an address.
+ /// @param target Address to remove authorization from.
+ function removeAuthorizedAddress(address target)
+ external
+ onlyOwner
+ {
+ require(
+ authorized[target],
+ TARGET_NOT_AUTHORIZED
+ );
+
+ delete authorized[target];
+ for (uint i = 0; i < authorities.length; i++) {
+ if (authorities[i] == target) {
+ authorities[i] = authorities[authorities.length - 1];
+ authorities.length -= 1;
+ break;
+ }
+ }
+ emit AuthorizedAddressRemoved(target, msg.sender);
+ }
+
+ /// @dev Removes authorizion of an address.
+ /// @param target Address to remove authorization from.
+ /// @param index Index of target in authorities array.
+ function removeAuthorizedAddressAtIndex(address target, uint256 index)
+ external
+ {
+ require(
+ index < authorities.length,
+ INDEX_OUT_OF_BOUNDS
+ );
+ require(
+ authorities[index] == target,
+ INDEX_ADDRESS_MISMATCH
+ );
+
+ delete authorized[target];
+ authorities[index] = authorities[authorities.length - 1];
+ authorities.length -= 1;
+ emit AuthorizedAddressRemoved(target, msg.sender);
+ }
+
+ /// @dev Gets all authorized addresses.
+ /// @return Array of authorized addresses.
+ function getAuthorizedAddresses()
+ external
+ view
+ returns (address[] memory)
+ {
+ return authorities;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol
new file mode 100644
index 000000000..8b30dfabb
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol
@@ -0,0 +1,59 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./IAuthorizable.sol";
+
+contract IAssetProxy is
+ IAuthorizable
+{
+
+ /// @dev Transfers assets. Either succeeds or throws.
+ /// @param assetMetadata Byte array encoded for the respective asset proxy.
+ /// @param from Address to transfer asset from.
+ /// @param to Address to transfer asset to.
+ /// @param amount Amount of asset to transfer.
+ function transferFrom(
+ bytes assetMetadata,
+ address from,
+ address to,
+ uint256 amount)
+ external;
+
+ /// @dev Makes multiple transfers of assets. Either succeeds or throws.
+ /// @param assetMetadata Array of byte arrays encoded for the respective asset proxy.
+ /// @param from Array of addresses to transfer assets from.
+ /// @param to Array of addresses to transfer assets to.
+ /// @param amounts Array of amounts of assets to transfer.
+ function batchTransferFrom(
+ bytes[] memory assetMetadata,
+ address[] memory from,
+ address[] memory to,
+ uint256[] memory amounts)
+ public;
+
+ /// @dev Gets the proxy id associated with the proxy address.
+ /// @return Proxy id.
+ function getProxyId()
+ external
+ view
+ returns (uint8);
+}
+
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAuthorizable.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAuthorizable.sol
new file mode 100644
index 000000000..d6fe03898
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAuthorizable.sol
@@ -0,0 +1,50 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../../../utils/Ownable/IOwnable.sol";
+
+contract IAuthorizable is
+ IOwnable
+{
+
+ /// @dev Gets all authorized addresses.
+ /// @return Array of authorized addresses.
+ function getAuthorizedAddresses()
+ external
+ view
+ returns (address[]);
+
+ /// @dev Authorizes an address.
+ /// @param target Address to authorize.
+ function addAuthorizedAddress(address target)
+ external;
+
+ /// @dev Removes authorizion of an address.
+ /// @param target Address to remove authorization from.
+ function removeAuthorizedAddress(address target)
+ external;
+
+ /// @dev Removes authorizion of an address.
+ /// @param target Address to remove authorization from.
+ /// @param index Index of target in authorities array.
+ function removeAuthorizedAddressAtIndex(address target, uint256 index)
+ external;
+}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol
new file mode 100644
index 000000000..3800bf04c
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol
@@ -0,0 +1,39 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../interfaces/IAssetProxy.sol";
+
+contract MAssetProxy is
+ IAssetProxy
+{
+
+ /// @dev Internal version of `transferFrom`.
+ /// @param assetMetadata Encoded byte array.
+ /// @param from Address to transfer asset from.
+ /// @param to Address to transfer asset to.
+ /// @param amount Amount of asset to transfer.
+ function transferFromInternal(
+ bytes memory assetMetadata,
+ address from,
+ address to,
+ uint256 amount)
+ internal;
+}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAuthorizable.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAuthorizable.sol
new file mode 100644
index 000000000..cdf60bdee
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAuthorizable.sol
@@ -0,0 +1,42 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../interfaces/IAuthorizable.sol";
+
+contract MAuthorizable is
+ IAuthorizable
+{
+
+ // Event logged when a new address is authorized.
+ event AuthorizedAddressAdded(
+ address indexed target,
+ address indexed caller
+ );
+
+ // Event logged when a currently authorized address is unauthorized.
+ event AuthorizedAddressRemoved(
+ address indexed target,
+ address indexed caller
+ );
+
+ /// @dev Only authorized addresses can invoke functions with this modifier.
+ modifier onlyAuthorized { _; }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol
index 8dacf797c..b7b308069 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol
@@ -1,6 +1,6 @@
/*
- Copyright 2017 ZeroEx Intl.
+ Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,587 +16,38 @@
*/
-pragma solidity ^0.4.14;
-
-import { TokenTransferProxy } from "../TokenTransferProxy/TokenTransferProxy.sol";
-import { Token_v1 as Token } from "../../../previous/Token/Token_v1.sol";
-import { SafeMath_v1 as SafeMath } from "../../../previous/SafeMath/SafeMath_v1.sol";
-
-/// @title Exchange - Facilitates exchange of ERC20 tokens.
-/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
-contract Exchange is SafeMath {
-
- // Error Codes
- enum Errors {
- ORDER_EXPIRED, // Order has already expired
- ORDER_FULLY_FILLED_OR_CANCELLED, // Order has already been fully filled or cancelled
- ROUNDING_ERROR_TOO_LARGE, // Rounding error too large
- INSUFFICIENT_BALANCE_OR_ALLOWANCE // Insufficient balance or allowance for token transfer
- }
-
- string constant public VERSION = "1.0.0";
- uint16 constant public EXTERNAL_QUERY_GAS_LIMIT = 4999; // Changes to state require at least 5000 gas
-
- address public ZRX_TOKEN_CONTRACT;
- address public TOKEN_TRANSFER_PROXY_CONTRACT;
-
- // Mappings of orderHash => amounts of takerTokenAmount filled or cancelled.
- mapping (bytes32 => uint) public filled;
- mapping (bytes32 => uint) public cancelled;
-
- event LogFill(
- address indexed maker,
- address taker,
- address indexed feeRecipient,
- address makerToken,
- address takerToken,
- uint filledMakerTokenAmount,
- uint filledTakerTokenAmount,
- uint paidMakerFee,
- uint paidTakerFee,
- bytes32 indexed tokens, // keccak256(makerToken, takerToken), allows subscribing to a token pair
- bytes32 orderHash
- );
-
- event LogCancel(
- address indexed maker,
- address indexed feeRecipient,
- address makerToken,
- address takerToken,
- uint cancelledMakerTokenAmount,
- uint cancelledTakerTokenAmount,
- bytes32 indexed tokens,
- bytes32 orderHash
- );
-
- event LogError(uint8 indexed errorId, bytes32 indexed orderHash);
-
- struct Order {
- address maker;
- address taker;
- address makerToken;
- address takerToken;
- address feeRecipient;
- uint makerTokenAmount;
- uint takerTokenAmount;
- uint makerFee;
- uint takerFee;
- uint expirationTimestampInSec;
- bytes32 orderHash;
- }
-
- function Exchange(address _zrxToken, address _tokenTransferProxy) {
- ZRX_TOKEN_CONTRACT = _zrxToken;
- TOKEN_TRANSFER_PROXY_CONTRACT = _tokenTransferProxy;
- }
-
- /*
- * Core exchange functions
- */
-
- /// @dev Fills the input order.
- /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
- /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
- /// @param fillTakerTokenAmount Desired amount of takerToken to fill.
- /// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfer will fail before attempting.
- /// @param v ECDSA signature parameter v.
- /// @param r ECDSA signature parameters r.
- /// @param s ECDSA signature parameters s.
- /// @return Total amount of takerToken filled in trade.
- function fillOrder(
- address[5] orderAddresses,
- uint[6] orderValues,
- uint fillTakerTokenAmount,
- bool shouldThrowOnInsufficientBalanceOrAllowance,
- uint8 v,
- bytes32 r,
- bytes32 s)
- public
- returns (uint filledTakerTokenAmount)
- {
- Order memory order = Order({
- maker: orderAddresses[0],
- taker: orderAddresses[1],
- makerToken: orderAddresses[2],
- takerToken: orderAddresses[3],
- feeRecipient: orderAddresses[4],
- makerTokenAmount: orderValues[0],
- takerTokenAmount: orderValues[1],
- makerFee: orderValues[2],
- takerFee: orderValues[3],
- expirationTimestampInSec: orderValues[4],
- orderHash: getOrderHash(orderAddresses, orderValues)
- });
-
- require(order.taker == address(0) || order.taker == msg.sender);
- require(order.makerTokenAmount > 0 && order.takerTokenAmount > 0 && fillTakerTokenAmount > 0);
- require(isValidSignature(
- order.maker,
- order.orderHash,
- v,
- r,
- s
- ));
-
- if (block.timestamp >= order.expirationTimestampInSec) {
- LogError(uint8(Errors.ORDER_EXPIRED), order.orderHash);
- return 0;
- }
-
- uint remainingTakerTokenAmount = safeSub(order.takerTokenAmount, getUnavailableTakerTokenAmount(order.orderHash));
- filledTakerTokenAmount = min256(fillTakerTokenAmount, remainingTakerTokenAmount);
- if (filledTakerTokenAmount == 0) {
- LogError(uint8(Errors.ORDER_FULLY_FILLED_OR_CANCELLED), order.orderHash);
- return 0;
- }
-
- if (isRoundingError(filledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount)) {
- LogError(uint8(Errors.ROUNDING_ERROR_TOO_LARGE), order.orderHash);
- return 0;
- }
-
- if (!shouldThrowOnInsufficientBalanceOrAllowance && !isTransferable(order, filledTakerTokenAmount)) {
- LogError(uint8(Errors.INSUFFICIENT_BALANCE_OR_ALLOWANCE), order.orderHash);
- return 0;
- }
-
- uint filledMakerTokenAmount = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount);
- uint paidMakerFee;
- uint paidTakerFee;
- filled[order.orderHash] = safeAdd(filled[order.orderHash], filledTakerTokenAmount);
- require(transferViaTokenTransferProxy(
- order.makerToken,
- order.maker,
- msg.sender,
- filledMakerTokenAmount
- ));
- require(transferViaTokenTransferProxy(
- order.takerToken,
- msg.sender,
- order.maker,
- filledTakerTokenAmount
- ));
- if (order.feeRecipient != address(0)) {
- if (order.makerFee > 0) {
- paidMakerFee = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.makerFee);
- require(transferViaTokenTransferProxy(
- ZRX_TOKEN_CONTRACT,
- order.maker,
- order.feeRecipient,
- paidMakerFee
- ));
- }
- if (order.takerFee > 0) {
- paidTakerFee = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.takerFee);
- require(transferViaTokenTransferProxy(
- ZRX_TOKEN_CONTRACT,
- msg.sender,
- order.feeRecipient,
- paidTakerFee
- ));
- }
- }
-
- LogFill(
- order.maker,
- msg.sender,
- order.feeRecipient,
- order.makerToken,
- order.takerToken,
- filledMakerTokenAmount,
- filledTakerTokenAmount,
- paidMakerFee,
- paidTakerFee,
- keccak256(order.makerToken, order.takerToken),
- order.orderHash
- );
- return filledTakerTokenAmount;
- }
-
- /// @dev Cancels the input order.
- /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
- /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
- /// @param cancelTakerTokenAmount Desired amount of takerToken to cancel in order.
- /// @return Amount of takerToken cancelled.
- function cancelOrder(
- address[5] orderAddresses,
- uint[6] orderValues,
- uint cancelTakerTokenAmount)
- public
- returns (uint)
- {
- Order memory order = Order({
- maker: orderAddresses[0],
- taker: orderAddresses[1],
- makerToken: orderAddresses[2],
- takerToken: orderAddresses[3],
- feeRecipient: orderAddresses[4],
- makerTokenAmount: orderValues[0],
- takerTokenAmount: orderValues[1],
- makerFee: orderValues[2],
- takerFee: orderValues[3],
- expirationTimestampInSec: orderValues[4],
- orderHash: getOrderHash(orderAddresses, orderValues)
- });
-
- require(order.maker == msg.sender);
- require(order.makerTokenAmount > 0 && order.takerTokenAmount > 0 && cancelTakerTokenAmount > 0);
-
- if (block.timestamp >= order.expirationTimestampInSec) {
- LogError(uint8(Errors.ORDER_EXPIRED), order.orderHash);
- return 0;
- }
-
- uint remainingTakerTokenAmount = safeSub(order.takerTokenAmount, getUnavailableTakerTokenAmount(order.orderHash));
- uint cancelledTakerTokenAmount = min256(cancelTakerTokenAmount, remainingTakerTokenAmount);
- if (cancelledTakerTokenAmount == 0) {
- LogError(uint8(Errors.ORDER_FULLY_FILLED_OR_CANCELLED), order.orderHash);
- return 0;
- }
-
- cancelled[order.orderHash] = safeAdd(cancelled[order.orderHash], cancelledTakerTokenAmount);
-
- LogCancel(
- order.maker,
- order.feeRecipient,
- order.makerToken,
- order.takerToken,
- getPartialAmount(cancelledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount),
- cancelledTakerTokenAmount,
- keccak256(order.makerToken, order.takerToken),
- order.orderHash
- );
- return cancelledTakerTokenAmount;
- }
-
- /*
- * Wrapper functions
- */
-
- /// @dev Fills an order with specified parameters and ECDSA signature, throws if specified amount not filled entirely.
- /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
- /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
- /// @param fillTakerTokenAmount Desired amount of takerToken to fill.
- /// @param v ECDSA signature parameter v.
- /// @param r ECDSA signature parameters r.
- /// @param s ECDSA signature parameters s.
- function fillOrKillOrder(
- address[5] orderAddresses,
- uint[6] orderValues,
- uint fillTakerTokenAmount,
- uint8 v,
- bytes32 r,
- bytes32 s)
- public
- {
- require(fillOrder(
- orderAddresses,
- orderValues,
- fillTakerTokenAmount,
- false,
- v,
- r,
- s
- ) == fillTakerTokenAmount);
- }
-
- /// @dev Synchronously executes multiple fill orders in a single transaction.
- /// @param orderAddresses Array of address arrays containing individual order addresses.
- /// @param orderValues Array of uint arrays containing individual order values.
- /// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
- /// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting.
- /// @param v Array ECDSA signature v parameters.
- /// @param r Array of ECDSA signature r parameters.
- /// @param s Array of ECDSA signature s parameters.
- function batchFillOrders(
- address[5][] orderAddresses,
- uint[6][] orderValues,
- uint[] fillTakerTokenAmounts,
- bool shouldThrowOnInsufficientBalanceOrAllowance,
- uint8[] v,
- bytes32[] r,
- bytes32[] s)
- public
- {
- for (uint i = 0; i < orderAddresses.length; i++) {
- fillOrder(
- orderAddresses[i],
- orderValues[i],
- fillTakerTokenAmounts[i],
- shouldThrowOnInsufficientBalanceOrAllowance,
- v[i],
- r[i],
- s[i]
- );
- }
- }
-
- /// @dev Synchronously executes multiple fillOrKill orders in a single transaction.
- /// @param orderAddresses Array of address arrays containing individual order addresses.
- /// @param orderValues Array of uint arrays containing individual order values.
- /// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
- /// @param v Array ECDSA signature v parameters.
- /// @param r Array of ECDSA signature r parameters.
- /// @param s Array of ECDSA signature s parameters.
- function batchFillOrKillOrders(
- address[5][] orderAddresses,
- uint[6][] orderValues,
- uint[] fillTakerTokenAmounts,
- uint8[] v,
- bytes32[] r,
- bytes32[] s)
- public
- {
- for (uint i = 0; i < orderAddresses.length; i++) {
- fillOrKillOrder(
- orderAddresses[i],
- orderValues[i],
- fillTakerTokenAmounts[i],
- v[i],
- r[i],
- s[i]
- );
- }
- }
-
- /// @dev Synchronously executes multiple fill orders in a single transaction until total fillTakerTokenAmount filled.
- /// @param orderAddresses Array of address arrays containing individual order addresses.
- /// @param orderValues Array of uint arrays containing individual order values.
- /// @param fillTakerTokenAmount Desired total amount of takerToken to fill in orders.
- /// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting.
- /// @param v Array ECDSA signature v parameters.
- /// @param r Array of ECDSA signature r parameters.
- /// @param s Array of ECDSA signature s parameters.
- /// @return Total amount of fillTakerTokenAmount filled in orders.
- function fillOrdersUpTo(
- address[5][] orderAddresses,
- uint[6][] orderValues,
- uint fillTakerTokenAmount,
- bool shouldThrowOnInsufficientBalanceOrAllowance,
- uint8[] v,
- bytes32[] r,
- bytes32[] s)
- public
- returns (uint)
- {
- uint filledTakerTokenAmount = 0;
- for (uint i = 0; i < orderAddresses.length; i++) {
- require(orderAddresses[i][3] == orderAddresses[0][3]); // takerToken must be the same for each order
- filledTakerTokenAmount = safeAdd(filledTakerTokenAmount, fillOrder(
- orderAddresses[i],
- orderValues[i],
- safeSub(fillTakerTokenAmount, filledTakerTokenAmount),
- shouldThrowOnInsufficientBalanceOrAllowance,
- v[i],
- r[i],
- s[i]
- ));
- if (filledTakerTokenAmount == fillTakerTokenAmount) break;
- }
- return filledTakerTokenAmount;
- }
-
- /// @dev Synchronously cancels multiple orders in a single transaction.
- /// @param orderAddresses Array of address arrays containing individual order addresses.
- /// @param orderValues Array of uint arrays containing individual order values.
- /// @param cancelTakerTokenAmounts Array of desired amounts of takerToken to cancel in orders.
- function batchCancelOrders(
- address[5][] orderAddresses,
- uint[6][] orderValues,
- uint[] cancelTakerTokenAmounts)
- public
- {
- for (uint i = 0; i < orderAddresses.length; i++) {
- cancelOrder(
- orderAddresses[i],
- orderValues[i],
- cancelTakerTokenAmounts[i]
- );
- }
- }
-
- /*
- * Constant public functions
- */
-
- /// @dev Calculates Keccak-256 hash of order with specified parameters.
- /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
- /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
- /// @return Keccak-256 hash of order.
- function getOrderHash(address[5] orderAddresses, uint[6] orderValues)
- public
- constant
- returns (bytes32)
- {
- return keccak256(
- address(this),
- orderAddresses[0], // maker
- orderAddresses[1], // taker
- orderAddresses[2], // makerToken
- orderAddresses[3], // takerToken
- orderAddresses[4], // feeRecipient
- orderValues[0], // makerTokenAmount
- orderValues[1], // takerTokenAmount
- orderValues[2], // makerFee
- orderValues[3], // takerFee
- orderValues[4], // expirationTimestampInSec
- orderValues[5] // salt
- );
- }
-
- /// @dev Verifies that an order signature is valid.
- /// @param signer address of signer.
- /// @param hash Signed Keccak-256 hash.
- /// @param v ECDSA signature parameter v.
- /// @param r ECDSA signature parameters r.
- /// @param s ECDSA signature parameters s.
- /// @return Validity of order signature.
- function isValidSignature(
- address signer,
- bytes32 hash,
- uint8 v,
- bytes32 r,
- bytes32 s)
- public
- constant
- returns (bool)
- {
- return signer == ecrecover(
- keccak256("\x19Ethereum Signed Message:\n32", hash),
- v,
- r,
- s
- );
- }
-
- /// @dev Checks if rounding error > 0.1%.
- /// @param numerator Numerator.
- /// @param denominator Denominator.
- /// @param target Value to multiply with numerator/denominator.
- /// @return Rounding error is present.
- function isRoundingError(uint numerator, uint denominator, uint target)
- public
- constant
- returns (bool)
- {
- uint remainder = mulmod(target, numerator, denominator);
- if (remainder == 0) return false; // No rounding error.
-
- uint errPercentageTimes1000000 = safeDiv(
- safeMul(remainder, 1000000),
- safeMul(numerator, target)
- );
- return errPercentageTimes1000000 > 1000;
- }
-
- /// @dev Calculates partial value given a numerator and denominator.
- /// @param numerator Numerator.
- /// @param denominator Denominator.
- /// @param target Value to calculate partial of.
- /// @return Partial value of target.
- function getPartialAmount(uint numerator, uint denominator, uint target)
- public
- constant
- returns (uint)
- {
- return safeDiv(safeMul(numerator, target), denominator);
- }
-
- /// @dev Calculates the sum of values already filled and cancelled for a given order.
- /// @param orderHash The Keccak-256 hash of the given order.
- /// @return Sum of values already filled and cancelled.
- function getUnavailableTakerTokenAmount(bytes32 orderHash)
- public
- constant
- returns (uint)
- {
- return safeAdd(filled[orderHash], cancelled[orderHash]);
- }
-
-
- /*
- * Internal functions
- */
-
- /// @dev Transfers a token using TokenTransferProxy transferFrom function.
- /// @param token Address of token to transferFrom.
- /// @param from Address transfering token.
- /// @param to Address receiving token.
- /// @param value Amount of token to transfer.
- /// @return Success of token transfer.
- function transferViaTokenTransferProxy(
- address token,
- address from,
- address to,
- uint value)
- internal
- returns (bool)
- {
- return TokenTransferProxy(TOKEN_TRANSFER_PROXY_CONTRACT).transferFrom(token, from, to, value);
- }
-
- /// @dev Checks if any order transfers will fail.
- /// @param order Order struct of params that will be checked.
- /// @param fillTakerTokenAmount Desired amount of takerToken to fill.
- /// @return Predicted result of transfers.
- function isTransferable(Order order, uint fillTakerTokenAmount)
- internal
- constant // The called token contracts may attempt to change state, but will not be able to due to gas limits on getBalance and getAllowance.
- returns (bool)
- {
- address taker = msg.sender;
- uint fillMakerTokenAmount = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount);
-
- if (order.feeRecipient != address(0)) {
- bool isMakerTokenZRX = order.makerToken == ZRX_TOKEN_CONTRACT;
- bool isTakerTokenZRX = order.takerToken == ZRX_TOKEN_CONTRACT;
- uint paidMakerFee = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.makerFee);
- uint paidTakerFee = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.takerFee);
- uint requiredMakerZRX = isMakerTokenZRX ? safeAdd(fillMakerTokenAmount, paidMakerFee) : paidMakerFee;
- uint requiredTakerZRX = isTakerTokenZRX ? safeAdd(fillTakerTokenAmount, paidTakerFee) : paidTakerFee;
-
- if ( getBalance(ZRX_TOKEN_CONTRACT, order.maker) < requiredMakerZRX
- || getAllowance(ZRX_TOKEN_CONTRACT, order.maker) < requiredMakerZRX
- || getBalance(ZRX_TOKEN_CONTRACT, taker) < requiredTakerZRX
- || getAllowance(ZRX_TOKEN_CONTRACT, taker) < requiredTakerZRX
- ) return false;
-
- if (!isMakerTokenZRX && ( getBalance(order.makerToken, order.maker) < fillMakerTokenAmount // Don't double check makerToken if ZRX
- || getAllowance(order.makerToken, order.maker) < fillMakerTokenAmount)
- ) return false;
- if (!isTakerTokenZRX && ( getBalance(order.takerToken, taker) < fillTakerTokenAmount // Don't double check takerToken if ZRX
- || getAllowance(order.takerToken, taker) < fillTakerTokenAmount)
- ) return false;
- } else if ( getBalance(order.makerToken, order.maker) < fillMakerTokenAmount
- || getAllowance(order.makerToken, order.maker) < fillMakerTokenAmount
- || getBalance(order.takerToken, taker) < fillTakerTokenAmount
- || getAllowance(order.takerToken, taker) < fillTakerTokenAmount
- ) return false;
-
- return true;
- }
-
- /// @dev Get token balance of an address.
- /// @param token Address of token.
- /// @param owner Address of owner.
- /// @return Token balance of owner.
- function getBalance(address token, address owner)
- internal
- constant // The called token contract may attempt to change state, but will not be able to due to an added gas limit.
- returns (uint)
- {
- return Token(token).balanceOf.gas(EXTERNAL_QUERY_GAS_LIMIT)(owner); // Limit gas to prevent reentrancy
- }
-
- /// @dev Get allowance of token given to TokenTransferProxy by an address.
- /// @param token Address of token.
- /// @param owner Address of owner.
- /// @return Allowance of token given to TokenTransferProxy by owner.
- function getAllowance(address token, address owner)
- internal
- constant // The called token contract may attempt to change state, but will not be able to due to an added gas limit.
- returns (uint)
- {
- return Token(token).allowance.gas(EXTERNAL_QUERY_GAS_LIMIT)(owner, TOKEN_TRANSFER_PROXY_CONTRACT); // Limit gas to prevent reentrancy
- }
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./MixinExchangeCore.sol";
+import "./MixinSignatureValidator.sol";
+import "./MixinSettlement.sol";
+import "./MixinWrapperFunctions.sol";
+import "./MixinAssetProxyDispatcher.sol";
+import "./MixinTransactions.sol";
+import "./MixinMatchOrders.sol";
+
+contract Exchange is
+ MixinExchangeCore,
+ MixinMatchOrders,
+ MixinSettlement,
+ MixinSignatureValidator,
+ MixinTransactions,
+ MixinAssetProxyDispatcher,
+ MixinWrapperFunctions
+{
+
+ string constant public VERSION = "2.0.1-alpha";
+
+ // Mixins are instantiated in the order they are inherited
+ constructor (bytes memory _zrxProxyData)
+ public
+ MixinExchangeCore()
+ MixinMatchOrders()
+ MixinSettlement(_zrxProxyData)
+ MixinSignatureValidator()
+ MixinTransactions()
+ MixinAssetProxyDispatcher()
+ MixinWrapperFunctions()
+ {}
}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol
new file mode 100644
index 000000000..3b38d1f37
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol
@@ -0,0 +1,106 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "../../utils/Ownable/Ownable.sol";
+import "../AssetProxy/interfaces/IAssetProxy.sol";
+import "./libs/LibExchangeErrors.sol";
+import "./mixins/MAssetProxyDispatcher.sol";
+
+contract MixinAssetProxyDispatcher is
+ LibExchangeErrors,
+ Ownable,
+ MAssetProxyDispatcher
+{
+ // Mapping from Asset Proxy Id's to their respective Asset Proxy
+ mapping (uint8 => IAssetProxy) public assetProxies;
+
+ /// @dev Registers an asset proxy to an asset proxy id.
+ /// An id can only be assigned to a single proxy at a given time.
+ /// @param assetProxyId Id to register`newAssetProxy` under.
+ /// @param newAssetProxy Address of new asset proxy to register, or 0x0 to unset assetProxyId.
+ /// @param oldAssetProxy Existing asset proxy to overwrite, or 0x0 if assetProxyId is currently unused.
+ function registerAssetProxy(
+ uint8 assetProxyId,
+ address newAssetProxy,
+ address oldAssetProxy)
+ external
+ onlyOwner
+ {
+ // Ensure the existing asset proxy is not unintentionally overwritten
+ require(
+ oldAssetProxy == address(assetProxies[assetProxyId]),
+ OLD_ASSET_PROXY_MISMATCH
+ );
+
+ IAssetProxy assetProxy = IAssetProxy(newAssetProxy);
+
+ // Ensure that the id of newAssetProxy matches the passed in assetProxyId, unless it is being reset to 0.
+ if (newAssetProxy != address(0)) {
+ uint8 newAssetProxyId = assetProxy.getProxyId();
+ require(
+ newAssetProxyId == assetProxyId,
+ NEW_ASSET_PROXY_MISMATCH
+ );
+ }
+
+ // Add asset proxy and log registration.
+ assetProxies[assetProxyId] = assetProxy;
+ emit AssetProxySet(assetProxyId, newAssetProxy, oldAssetProxy);
+ }
+
+ /// @dev Gets an asset proxy.
+ /// @param assetProxyId Id of the asset proxy.
+ /// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered.
+ function getAssetProxy(uint8 assetProxyId)
+ external
+ view
+ returns (address)
+ {
+ address assetProxy = address(assetProxies[assetProxyId]);
+ return assetProxy;
+ }
+
+ /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws.
+ /// @param assetMetadata Byte array encoded for the respective asset proxy.
+ /// @param from Address to transfer token from.
+ /// @param to Address to transfer token to.
+ /// @param amount Amount of token to transfer.
+ function dispatchTransferFrom(
+ bytes memory assetMetadata,
+ address from,
+ address to,
+ uint256 amount)
+ internal
+ {
+ // Do nothing if no amount should be transferred.
+ if (amount > 0) {
+ // Lookup asset proxy
+ require(
+ assetMetadata.length >= 1,
+ GT_ZERO_LENGTH_REQUIRED
+ );
+ uint8 assetProxyId = uint8(assetMetadata[0]);
+ IAssetProxy assetProxy = assetProxies[assetProxyId];
+
+ // transferFrom will either succeed or throw.
+ assetProxy.transferFrom(assetMetadata, from, to, amount);
+ }
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
new file mode 100644
index 000000000..1c2420374
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
@@ -0,0 +1,446 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./libs/LibFillResults.sol";
+import "./libs/LibOrder.sol";
+import "./libs/LibMath.sol";
+import "./libs/LibStatus.sol";
+import "./libs/LibExchangeErrors.sol";
+import "./mixins/MExchangeCore.sol";
+import "./mixins/MSettlement.sol";
+import "./mixins/MSignatureValidator.sol";
+import "./mixins/MTransactions.sol";
+
+contract MixinExchangeCore is
+ SafeMath,
+ LibMath,
+ LibStatus,
+ LibOrder,
+ LibFillResults,
+ LibExchangeErrors,
+ MExchangeCore,
+ MSettlement,
+ MSignatureValidator,
+ MTransactions
+{
+ // Mapping of orderHash => amount of takerAsset already bought by maker
+ mapping (bytes32 => uint256) public filled;
+
+ // Mapping of orderHash => cancelled
+ mapping (bytes32 => bool) public cancelled;
+
+ // Mapping of makerAddress => lowest salt an order can have in order to be fillable
+ // Orders with a salt less than their maker's epoch are considered cancelled
+ mapping (address => uint256) public makerEpoch;
+
+ ////// Core exchange functions //////
+
+ /// @dev Cancels all orders reated by sender with a salt less than or equal to the specified salt value.
+ /// @param salt Orders created with a salt less or equal to this value will be cancelled.
+ function cancelOrdersUpTo(uint256 salt)
+ external
+ {
+ uint256 newMakerEpoch = salt + 1; // makerEpoch is initialized to 0, so to cancelUpTo we need salt + 1
+ require(
+ newMakerEpoch > makerEpoch[msg.sender], // epoch must be monotonically increasing
+ INVALID_NEW_MAKER_EPOCH
+ );
+ makerEpoch[msg.sender] = newMakerEpoch;
+ emit CancelUpTo(msg.sender, newMakerEpoch);
+ }
+
+ /// @dev Fills the input order.
+ /// @param order Order struct containing order specifications.
+ /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
+ /// @param signature Proof that order has been created by maker.
+ /// @return Amounts filled and fees paid by maker and taker.
+ function fillOrder(
+ Order memory order,
+ uint256 takerAssetFillAmount,
+ bytes memory signature
+ )
+ public
+ returns (FillResults memory fillResults)
+ {
+ // Fetch order info
+ OrderInfo memory orderInfo = getOrderInfo(order);
+
+ // Fetch taker address
+ address takerAddress = getCurrentContextAddress();
+
+ // Either our context is valid or we revert
+ assertValidFill(
+ order,
+ orderInfo.orderStatus,
+ orderInfo.orderHash,
+ takerAddress,
+ orderInfo.orderTakerAssetFilledAmount,
+ takerAssetFillAmount,
+ signature
+ );
+
+ // Compute proportional fill amounts
+ uint8 status;
+ (status, fillResults) = calculateFillResults(
+ order,
+ orderInfo.orderStatus,
+ orderInfo.orderTakerAssetFilledAmount,
+ takerAssetFillAmount
+ );
+ if (status != uint8(Status.SUCCESS)) {
+ emit ExchangeStatus(uint8(status), orderInfo.orderHash);
+ return getNullFillResults();
+ }
+
+ // Settle order
+ settleOrder(order, takerAddress, fillResults);
+
+ // Update exchange internal state
+ updateFilledState(
+ order,
+ takerAddress,
+ orderInfo.orderHash,
+ orderInfo.orderTakerAssetFilledAmount,
+ fillResults
+ );
+ return fillResults;
+ }
+
+ /// @dev After calling, the order can not be filled anymore.
+ /// Throws if order is invalid or sender does not have permission to cancel.
+ /// @param order Order to cancel. Order must be Status.FILLABLE.
+ /// @return True if the order state changed to cancelled.
+ /// False if the order was valid, but in an
+ /// unfillable state (see LibStatus.STATUS for order states)
+ function cancelOrder(Order memory order)
+ public
+ returns (bool)
+ {
+ // Fetch current order status
+ OrderInfo memory orderInfo = getOrderInfo(order);
+
+ // Validate context
+ assertValidCancel(order, orderInfo.orderStatus, orderInfo.orderHash);
+
+ // Perform cancel
+ return updateCancelledState(order, orderInfo.orderStatus, orderInfo.orderHash);
+ }
+
+ /// @dev Gets information about an order: status, hash, and amount filled.
+ /// @param order Order to gather information on.
+ /// @return OrderInfo Information about the order and its state.
+ /// See LibOrder.OrderInfo for a complete description.
+ function getOrderInfo(Order memory order)
+ public
+ view
+ returns (LibOrder.OrderInfo memory orderInfo)
+ {
+ // Compute the order hash
+ orderInfo.orderHash = getOrderHash(order);
+
+ // If order.makerAssetAmount is zero, we also reject the order.
+ // While the Exchange contract handles them correctly, they create
+ // edge cases in the supporting infrastructure because they have
+ // an 'infinite' price when computed by a simple division.
+ if (order.makerAssetAmount == 0) {
+ orderInfo.orderStatus = uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT);
+ return orderInfo;
+ }
+
+ // If order.takerAssetAmount is zero, then the order will always
+ // be considered filled because 0 == takerAssetAmount == orderTakerAssetFilledAmount
+ // Instead of distinguishing between unfilled and filled zero taker
+ // amount orders, we choose not to support them.
+ if (order.takerAssetAmount == 0) {
+ orderInfo.orderStatus = uint8(Status.ORDER_INVALID_TAKER_ASSET_AMOUNT);
+ return orderInfo;
+ }
+
+ // Validate order expiration
+ if (block.timestamp >= order.expirationTimeSeconds) {
+ orderInfo.orderStatus = uint8(Status.ORDER_EXPIRED);
+ return orderInfo;
+ }
+
+ // Check if order has been cancelled
+ if (cancelled[orderInfo.orderHash]) {
+ orderInfo.orderStatus = uint8(Status.ORDER_CANCELLED);
+ return orderInfo;
+ }
+ if (makerEpoch[order.makerAddress] > order.salt) {
+ orderInfo.orderStatus = uint8(Status.ORDER_CANCELLED);
+ return orderInfo;
+ }
+
+ // Fetch filled amount and validate order availability
+ orderInfo.orderTakerAssetFilledAmount = filled[orderInfo.orderHash];
+ if (orderInfo.orderTakerAssetFilledAmount >= order.takerAssetAmount) {
+ orderInfo.orderStatus = uint8(Status.ORDER_FULLY_FILLED);
+ return orderInfo;
+ }
+
+ // All other statuses are ruled out: order is Fillable
+ orderInfo.orderStatus = uint8(Status.ORDER_FILLABLE);
+ return orderInfo;
+ }
+
+ /// @dev Calculates amounts filled and fees paid by maker and taker.
+ /// @param order to be filled.
+ /// @param orderStatus Status of order to be filled.
+ /// @param orderTakerAssetFilledAmount Amount of order already filled.
+ /// @param takerAssetFillAmount Desired amount of order to fill by taker.
+ /// @return status Return status of calculating fill amounts. Returns Status.SUCCESS on success.
+ /// @return fillResults Amounts filled and fees paid by maker and taker.
+ function calculateFillResults(
+ Order memory order,
+ uint8 orderStatus,
+ uint256 orderTakerAssetFilledAmount,
+ uint256 takerAssetFillAmount
+ )
+ public
+ pure
+ returns (
+ uint8 status,
+ FillResults memory fillResults
+ )
+ {
+ // Fill amount must be greater than 0
+ if (takerAssetFillAmount == 0) {
+ status = uint8(Status.TAKER_ASSET_FILL_AMOUNT_TOO_LOW);
+ return (status, fillResults);
+ }
+
+ // Ensure the order is fillable
+ if (orderStatus != uint8(Status.ORDER_FILLABLE)) {
+ status = orderStatus;
+ return (status, fillResults);
+ }
+
+ // Compute takerAssetFilledAmount
+ uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, orderTakerAssetFilledAmount);
+ uint256 takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount);
+
+ // Validate fill order rounding
+ if (isRoundingError(
+ takerAssetFilledAmount,
+ order.takerAssetAmount,
+ order.makerAssetAmount))
+ {
+ status = uint8(Status.ROUNDING_ERROR_TOO_LARGE);
+ return (status, fillResults);
+ }
+
+ // Compute proportional transfer amounts
+ // TODO: All three are multiplied by the same fraction. This can
+ // potentially be optimized.
+ fillResults.takerAssetFilledAmount = takerAssetFilledAmount;
+ fillResults.makerAssetFilledAmount = getPartialAmount(
+ fillResults.takerAssetFilledAmount,
+ order.takerAssetAmount,
+ order.makerAssetAmount
+ );
+ fillResults.makerFeePaid = getPartialAmount(
+ fillResults.takerAssetFilledAmount,
+ order.takerAssetAmount,
+ order.makerFee
+ );
+ fillResults.takerFeePaid = getPartialAmount(
+ fillResults.takerAssetFilledAmount,
+ order.takerAssetAmount,
+ order.takerFee
+ );
+
+ status = uint8(Status.SUCCESS);
+ return (status, fillResults);
+ }
+
+ /// @dev Validates context for fillOrder. Succeeds or throws.
+ /// @param order to be filled.
+ /// @param orderStatus Status of order to be filled.
+ /// @param orderHash Hash of order to be filled.
+ /// @param takerAddress Address of order taker.
+ /// @param orderTakerAssetFilledAmount Amount of order already filled.
+ /// @param takerAssetFillAmount Desired amount of order to fill by taker.
+ /// @param signature Proof that the orders was created by its maker.
+ function assertValidFill(
+ Order memory order,
+ uint8 orderStatus,
+ bytes32 orderHash,
+ address takerAddress,
+ uint256 orderTakerAssetFilledAmount,
+ uint256 takerAssetFillAmount,
+ bytes memory signature
+ )
+ internal
+ {
+ // Ensure order is valid
+ // An order can only be filled if its status is FILLABLE;
+ // however, only invalid statuses result in a throw.
+ // See LibStatus for a complete description of order statuses.
+ require(
+ orderStatus != uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT),
+ INVALID_ORDER_MAKER_ASSET_AMOUNT
+ );
+ require(
+ orderStatus != uint8(Status.ORDER_INVALID_TAKER_ASSET_AMOUNT),
+ INVALID_ORDER_TAKER_ASSET_AMOUNT
+ );
+
+ // Validate Maker signature (check only if first time seen)
+ if (orderTakerAssetFilledAmount == 0) {
+ require(
+ isValidSignature(orderHash, order.makerAddress, signature),
+ SIGNATURE_VALIDATION_FAILED
+ );
+ }
+
+ // Validate sender is allowed to fill this order
+ if (order.senderAddress != address(0)) {
+ require(
+ order.senderAddress == msg.sender,
+ INVALID_SENDER
+ );
+ }
+
+ // Validate taker is allowed to fill this order
+ if (order.takerAddress != address(0)) {
+ require(
+ order.takerAddress == takerAddress,
+ INVALID_CONTEXT
+ );
+ }
+ require(
+ takerAssetFillAmount > 0,
+ GT_ZERO_AMOUNT_REQUIRED
+ );
+ }
+
+ /// @dev Updates state with results of a fill order.
+ /// @param order that was filled.
+ /// @param takerAddress Address of taker who filled the order.
+ /// @param orderTakerAssetFilledAmount Amount of order already filled.
+ /// @return fillResults Amounts filled and fees paid by maker and taker.
+ function updateFilledState(
+ Order memory order,
+ address takerAddress,
+ bytes32 orderHash,
+ uint256 orderTakerAssetFilledAmount,
+ FillResults memory fillResults
+ )
+ internal
+ {
+ // Update state
+ filled[orderHash] = safeAdd(orderTakerAssetFilledAmount, fillResults.takerAssetFilledAmount);
+
+ // Log order
+ emit Fill(
+ order.makerAddress,
+ takerAddress,
+ order.feeRecipientAddress,
+ fillResults.makerAssetFilledAmount,
+ fillResults.takerAssetFilledAmount,
+ fillResults.makerFeePaid,
+ fillResults.takerFeePaid,
+ orderHash,
+ order.makerAssetData,
+ order.takerAssetData
+ );
+ }
+
+ /// @dev Validates context for cancelOrder. Succeeds or throws.
+ /// @param order that was cancelled.
+ /// @param orderStatus Status of order that was cancelled.
+ /// @param orderHash Hash of order that was cancelled.
+ function assertValidCancel(
+ Order memory order,
+ uint8 orderStatus,
+ bytes32 orderHash
+ )
+ internal
+ {
+ // Ensure order is valid
+ // An order can only be cancelled if its status is FILLABLE;
+ // however, only invalid statuses result in a throw.
+ // See LibStatus for a complete description of order statuses.
+ require(
+ orderStatus != uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT),
+ INVALID_ORDER_MAKER_ASSET_AMOUNT
+ );
+ require(
+ orderStatus != uint8(Status.ORDER_INVALID_TAKER_ASSET_AMOUNT),
+ INVALID_ORDER_TAKER_ASSET_AMOUNT
+ );
+
+ // Validate transaction signed by maker
+ address makerAddress = getCurrentContextAddress();
+ require(
+ order.makerAddress == makerAddress,
+ INVALID_CONTEXT
+ );
+
+ // Validate sender is allowed to cancel this order
+ if (order.senderAddress != address(0)) {
+ require(
+ order.senderAddress == msg.sender,
+ INVALID_SENDER
+ );
+ }
+ }
+
+ /// @dev Updates state with results of cancelling an order.
+ /// State is only updated if the order is currently fillable.
+ /// Otherwise, updating state would have no effect.
+ /// @param order that was cancelled.
+ /// @param orderStatus Status of order that was cancelled.
+ /// @param orderHash Hash of order that was cancelled.
+ /// @return stateUpdated Returns true only if state was updated.
+ function updateCancelledState(
+ Order memory order,
+ uint8 orderStatus,
+ bytes32 orderHash
+ )
+ internal
+ returns (bool stateUpdated)
+ {
+ // Ensure order is fillable (otherwise cancelling does nothing)
+ // See LibStatus for a complete description of order statuses.
+ if (orderStatus != uint8(Status.ORDER_FILLABLE)) {
+ emit ExchangeStatus(uint8(orderStatus), orderHash);
+ stateUpdated = false;
+ return stateUpdated;
+ }
+
+ // Perform cancel
+ cancelled[orderHash] = true;
+ stateUpdated = true;
+
+ // Log cancel
+ emit Cancel(
+ order.makerAddress,
+ order.feeRecipientAddress,
+ orderHash,
+ order.makerAssetData,
+ order.takerAssetData
+ );
+
+ return stateUpdated;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol
new file mode 100644
index 000000000..9d8b521c0
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol
@@ -0,0 +1,297 @@
+/*
+ Copyright 2018 ZeroEx Intl.
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./mixins/MExchangeCore.sol";
+import "./mixins/MMatchOrders.sol";
+import "./mixins/MSettlement.sol";
+import "./mixins/MTransactions.sol";
+import "../../utils/SafeMath/SafeMath.sol";
+import "./libs/LibMath.sol";
+import "./libs/LibOrder.sol";
+import "./libs/LibStatus.sol";
+import "../../utils/LibBytes/LibBytes.sol";
+import "./libs/LibExchangeErrors.sol";
+
+contract MixinMatchOrders is
+ SafeMath,
+ LibBytes,
+ LibMath,
+ LibStatus,
+ LibOrder,
+ LibFillResults,
+ LibExchangeErrors,
+ MExchangeCore,
+ MMatchOrders,
+ MSettlement,
+ MTransactions
+{
+
+ /// @dev Match two complementary orders that have a profitable spread.
+ /// Each order is filled at their respective price point. However, the calculations are
+ /// carried out as though the orders are both being filled at the right order's price point.
+ /// The profit made by the left order goes to the taker (who matched the two orders).
+ /// @param leftOrder First order to match.
+ /// @param rightOrder Second order to match.
+ /// @param leftSignature Proof that order was created by the left maker.
+ /// @param rightSignature Proof that order was created by the right maker.
+ /// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders.
+ /// TODO: Make this function external once supported by Solidity (See Solidity Issues #3199, #1603)
+ function matchOrders(
+ Order memory leftOrder,
+ Order memory rightOrder,
+ bytes memory leftSignature,
+ bytes memory rightSignature
+ )
+ public
+ returns (MatchedFillResults memory matchedFillResults)
+ {
+ // Get left & right order info
+ OrderInfo memory leftOrderInfo = getOrderInfo(leftOrder);
+ OrderInfo memory rightOrderInfo = getOrderInfo(rightOrder);
+
+ // Fetch taker address
+ address takerAddress = getCurrentContextAddress();
+
+ // Either our context is valid or we revert
+ assertValidMatch(leftOrder, rightOrder);
+
+ // Compute proportional fill amounts
+ matchedFillResults = calculateMatchedFillResults(
+ leftOrder,
+ rightOrder,
+ leftOrderInfo.orderStatus,
+ rightOrderInfo.orderStatus,
+ leftOrderInfo.orderTakerAssetFilledAmount,
+ rightOrderInfo.orderTakerAssetFilledAmount
+ );
+
+ // Validate fill contexts
+ assertValidFill(
+ leftOrder,
+ leftOrderInfo.orderStatus,
+ leftOrderInfo.orderHash,
+ takerAddress,
+ leftOrderInfo.orderTakerAssetFilledAmount,
+ matchedFillResults.left.takerAssetFilledAmount,
+ leftSignature
+ );
+ assertValidFill(
+ rightOrder,
+ rightOrderInfo.orderStatus,
+ rightOrderInfo.orderHash,
+ takerAddress,
+ rightOrderInfo.orderTakerAssetFilledAmount,
+ matchedFillResults.right.takerAssetFilledAmount,
+ rightSignature
+ );
+
+ // Settle matched orders. Succeeds or throws.
+ settleMatchedOrders(
+ leftOrder,
+ rightOrder,
+ takerAddress,
+ matchedFillResults
+ );
+
+ // Update exchange state
+ updateFilledState(
+ leftOrder,
+ takerAddress,
+ leftOrderInfo.orderHash,
+ leftOrderInfo.orderTakerAssetFilledAmount,
+ matchedFillResults.left
+ );
+ updateFilledState(
+ rightOrder,
+ takerAddress,
+ rightOrderInfo.orderHash,
+ rightOrderInfo.orderTakerAssetFilledAmount,
+ matchedFillResults.right
+ );
+
+ return matchedFillResults;
+ }
+
+ /// @dev Validates context for matchOrders. Succeeds or throws.
+ /// @param leftOrder First order to match.
+ /// @param rightOrder Second order to match.
+ function assertValidMatch(
+ Order memory leftOrder,
+ Order memory rightOrder
+ )
+ internal
+ {
+ // The leftOrder maker asset must be the same as the rightOrder taker asset.
+ // TODO: Can we safely assume equality and expect a later failure otherwise?
+ require(
+ areBytesEqual(leftOrder.makerAssetData, rightOrder.takerAssetData),
+ ASSET_MISMATCH_MAKER_TAKER
+ );
+
+ // The leftOrder taker asset must be the same as the rightOrder maker asset.
+ // TODO: Can we safely assume equality and expect a later failure otherwise?
+ require(
+ areBytesEqual(leftOrder.takerAssetData, rightOrder.makerAssetData),
+ ASSET_MISMATCH_TAKER_MAKER
+ );
+
+ // Make sure there is a profitable spread.
+ // There is a profitable spread iff the cost per unit bought (OrderA.MakerAmount/OrderA.TakerAmount) for each order is greater
+ // than the profit per unit sold of the matched order (OrderB.TakerAmount/OrderB.MakerAmount).
+ // This is satisfied by the equations below:
+ // <leftOrder.makerAssetAmount> / <leftOrder.takerAssetAmount> >= <rightOrder.takerAssetAmount> / <rightOrder.makerAssetAmount>
+ // AND
+ // <rightOrder.makerAssetAmount> / <rightOrder.takerAssetAmount> >= <leftOrder.takerAssetAmount> / <leftOrder.makerAssetAmount>
+ // These equations can be combined to get the following:
+ require(
+ safeMul(leftOrder.makerAssetAmount, rightOrder.makerAssetAmount) >=
+ safeMul(leftOrder.takerAssetAmount, rightOrder.takerAssetAmount),
+ NEGATIVE_SPREAD
+ );
+ }
+
+ /// @dev Validates matched fill results. Succeeds or throws.
+ /// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
+ function assertValidMatchResults(MatchedFillResults memory matchedFillResults)
+ internal
+ {
+ // If the amount transferred from the left order is different than what is transferred, it is a rounding error amount.
+ // Ensure this difference is negligible by dividing the values with each other. The result should equal to ~1.
+ uint256 amountSpentByLeft = safeAdd(
+ matchedFillResults.right.takerAssetFilledAmount,
+ matchedFillResults.takerFillAmount
+ );
+ require(
+ !isRoundingError(
+ matchedFillResults.left.makerAssetFilledAmount,
+ amountSpentByLeft,
+ 1
+ ),
+ ROUNDING_ERROR_TRANSFER_AMOUNTS
+ );
+
+ // If the amount transferred from the right order is different than what is transferred, it is a rounding error amount.
+ // Ensure this difference is negligible by dividing the values with each other. The result should equal to ~1.
+ require(
+ !isRoundingError(
+ matchedFillResults.right.makerAssetFilledAmount,
+ matchedFillResults.left.takerAssetFilledAmount,
+ 1
+ ),
+ ROUNDING_ERROR_TRANSFER_AMOUNTS
+ );
+ }
+
+ /// @dev Calculates fill amounts for the matched orders.
+ /// Each order is filled at their respective price point. However, the calculations are
+ /// carried out as though the orders are both being filled at the right order's price point.
+ /// The profit made by the leftOrder order goes to the taker (who matched the two orders).
+ /// @param leftOrder First order to match.
+ /// @param rightOrder Second order to match.
+ /// @param leftOrderStatus Order status of left order.
+ /// @param rightOrderStatus Order status of right order.
+ /// @param leftOrderFilledAmount Amount of left order already filled.
+ /// @param rightOrderFilledAmount Amount of right order already filled.
+ /// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
+ function calculateMatchedFillResults(
+ Order memory leftOrder,
+ Order memory rightOrder,
+ uint8 leftOrderStatus,
+ uint8 rightOrderStatus,
+ uint256 leftOrderFilledAmount,
+ uint256 rightOrderFilledAmount
+ )
+ internal
+ returns (MatchedFillResults memory matchedFillResults)
+ {
+ // We settle orders at the exchange rate of the right order.
+ // The amount saved by the left maker goes to the taker.
+ // Either the left or right order will be fully filled; possibly both.
+ // The left order is fully filled iff the right order can sell more than left can buy.
+ // That is: the amount required to fill the left order is less than or equal to
+ // the amount we can spend from the right order:
+ // <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightMakerToTakerRatio>
+ // <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount> / <rightOrder.takerAssetAmount>
+ // <leftTakerAssetAmountRemaining> * <rightOrder.takerAssetAmount> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount>
+ uint256 rightTakerAssetAmountRemaining = safeSub(rightOrder.takerAssetAmount, rightOrderFilledAmount);
+ uint256 leftTakerAssetAmountRemaining = safeSub(leftOrder.takerAssetAmount, leftOrderFilledAmount);
+ uint256 leftOrderAmountToFill;
+ uint256 rightOrderAmountToFill;
+ if (
+ safeMul(leftTakerAssetAmountRemaining, rightOrder.takerAssetAmount) <=
+ safeMul(rightTakerAssetAmountRemaining, rightOrder.makerAssetAmount)
+ ) {
+ // Left order will be fully filled: maximally fill left
+ leftOrderAmountToFill = leftTakerAssetAmountRemaining;
+
+ // The right order receives an amount proportional to how much was spent.
+ // TODO: Can we ensure rounding error is in the correct direction?
+ rightOrderAmountToFill = safeGetPartialAmount(
+ rightOrder.takerAssetAmount,
+ rightOrder.makerAssetAmount,
+ leftOrderAmountToFill
+ );
+ } else {
+ // Right order will be fully filled: maximally fill right
+ rightOrderAmountToFill = rightTakerAssetAmountRemaining;
+
+ // The left order receives an amount proportional to how much was spent.
+ // TODO: Can we ensure rounding error is in the correct direction?
+ leftOrderAmountToFill = safeGetPartialAmount(
+ rightOrder.makerAssetAmount,
+ rightOrder.takerAssetAmount,
+ rightOrderAmountToFill
+ );
+ }
+
+ // Calculate fill results for left order
+ uint8 status;
+ (status, matchedFillResults.left) = calculateFillResults(
+ leftOrder,
+ leftOrderStatus,
+ leftOrderFilledAmount,
+ leftOrderAmountToFill
+ );
+ require(
+ status == uint8(Status.SUCCESS),
+ FAILED_TO_CALCULATE_FILL_RESULTS_FOR_LEFT_ORDER
+ );
+
+ // Calculate fill results for right order
+ (status, matchedFillResults.right) = calculateFillResults(
+ rightOrder,
+ rightOrderStatus,
+ rightOrderFilledAmount,
+ rightOrderAmountToFill
+ );
+ require(
+ status == uint8(Status.SUCCESS),
+ FAILED_TO_CALCULATE_FILL_RESULTS_FOR_RIGHT_ORDER
+ );
+
+ // Calculate amount given to taker
+ matchedFillResults.takerFillAmount = safeSub(
+ matchedFillResults.left.makerAssetFilledAmount,
+ matchedFillResults.right.takerAssetFilledAmount
+ );
+
+ // Validate the fill results
+ assertValidMatchResults(matchedFillResults);
+
+ // Return fill results
+ return matchedFillResults;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol
new file mode 100644
index 000000000..7c03bde75
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol
@@ -0,0 +1,171 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./mixins/MSettlement.sol";
+import "./mixins/MAssetProxyDispatcher.sol";
+import "./libs/LibOrder.sol";
+import "./libs/LibMath.sol";
+import "./libs/LibExchangeErrors.sol";
+import "./libs/LibFillResults.sol";
+import "./mixins/MMatchOrders.sol";
+
+contract MixinSettlement is
+ LibMath,
+ LibFillResults,
+ LibExchangeErrors,
+ MMatchOrders,
+ MSettlement,
+ MAssetProxyDispatcher
+{
+ // ZRX metadata used for fee transfers.
+ // This will be constant throughout the life of the Exchange contract,
+ // since ZRX will always be transferred via the ERC20 AssetProxy.
+ bytes internal ZRX_PROXY_DATA;
+
+ /// @dev Gets the ZRX metadata used for fee transfers.
+ function zrxProxyData()
+ external
+ view
+ returns (bytes memory)
+ {
+ return ZRX_PROXY_DATA;
+ }
+
+ /// TODO: _zrxProxyData should be a constant in production.
+ /// @dev Constructor sets the metadata that will be used for paying ZRX fees.
+ /// @param _zrxProxyData Byte array containing ERC20 proxy id concatenated with address of ZRX.
+ constructor (bytes memory _zrxProxyData)
+ public
+ {
+ ZRX_PROXY_DATA = _zrxProxyData;
+ }
+
+ /// @dev Settles an order by transferring assets between counterparties.
+ /// @param order Order struct containing order specifications.
+ /// @param takerAddress Address selling takerAsset and buying makerAsset.
+ /// @param fillResults Amounts to be filled and fees paid by maker and taker.
+ function settleOrder(
+ LibOrder.Order memory order,
+ address takerAddress,
+ FillResults memory fillResults
+ )
+ internal
+ {
+ dispatchTransferFrom(
+ order.makerAssetData,
+ order.makerAddress,
+ takerAddress,
+ fillResults.makerAssetFilledAmount
+ );
+ dispatchTransferFrom(
+ order.takerAssetData,
+ takerAddress,
+ order.makerAddress,
+ fillResults.takerAssetFilledAmount
+ );
+ dispatchTransferFrom(
+ ZRX_PROXY_DATA,
+ order.makerAddress,
+ order.feeRecipientAddress,
+ fillResults.makerFeePaid
+ );
+ dispatchTransferFrom(
+ ZRX_PROXY_DATA,
+ takerAddress,
+ order.feeRecipientAddress,
+ fillResults.takerFeePaid
+ );
+ }
+
+ /// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient.
+ /// @param leftOrder First matched order.
+ /// @param rightOrder Second matched order.
+ /// @param takerAddress Address that matched the orders. The taker receives the spread between orders as profit.
+ /// @param matchedFillResults Struct holding amounts to transfer between makers, taker, and fee recipients.
+ function settleMatchedOrders(
+ LibOrder.Order memory leftOrder,
+ LibOrder.Order memory rightOrder,
+ address takerAddress,
+ MatchedFillResults memory matchedFillResults
+ )
+ internal
+ {
+ // Order makers and taker
+ dispatchTransferFrom(
+ leftOrder.makerAssetData,
+ leftOrder.makerAddress,
+ rightOrder.makerAddress,
+ matchedFillResults.right.takerAssetFilledAmount
+ );
+ dispatchTransferFrom(
+ rightOrder.makerAssetData,
+ rightOrder.makerAddress,
+ leftOrder.makerAddress,
+ matchedFillResults.left.takerAssetFilledAmount
+ );
+ dispatchTransferFrom(
+ leftOrder.makerAssetData,
+ leftOrder.makerAddress,
+ takerAddress,
+ matchedFillResults.takerFillAmount
+ );
+
+ // Maker fees
+ dispatchTransferFrom(
+ ZRX_PROXY_DATA,
+ leftOrder.makerAddress,
+ leftOrder.feeRecipientAddress,
+ matchedFillResults.left.makerFeePaid
+ );
+ dispatchTransferFrom(
+ ZRX_PROXY_DATA,
+ rightOrder.makerAddress,
+ rightOrder.feeRecipientAddress,
+ matchedFillResults.right.makerFeePaid
+ );
+
+ // Taker fees
+ if (leftOrder.feeRecipientAddress == rightOrder.feeRecipientAddress) {
+ dispatchTransferFrom(
+ ZRX_PROXY_DATA,
+ takerAddress,
+ leftOrder.feeRecipientAddress,
+ safeAdd(
+ matchedFillResults.left.takerFeePaid,
+ matchedFillResults.right.takerFeePaid
+ )
+ );
+ } else {
+ dispatchTransferFrom(
+ ZRX_PROXY_DATA,
+ takerAddress,
+ leftOrder.feeRecipientAddress,
+ matchedFillResults.left.takerFeePaid
+ );
+ dispatchTransferFrom(
+ ZRX_PROXY_DATA,
+ takerAddress,
+ rightOrder.feeRecipientAddress,
+ matchedFillResults.right.takerFeePaid
+ );
+ }
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol
new file mode 100644
index 000000000..f7fcd36b6
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol
@@ -0,0 +1,191 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "./mixins/MSignatureValidator.sol";
+import "./interfaces/ISigner.sol";
+import "./libs/LibExchangeErrors.sol";
+import "../../utils/LibBytes/LibBytes.sol";
+
+contract MixinSignatureValidator is
+ LibBytes,
+ LibExchangeErrors,
+ MSignatureValidator
+{
+
+ // Mapping of hash => signer => signed
+ mapping(bytes32 => mapping(address => bool)) preSigned;
+
+ /// @dev Approves a hash on-chain using any valid signature type.
+ /// After presigning a hash, the preSign signature type will become valid for that hash and signer.
+ /// @param signer Address that should have signed the given hash.
+ /// @param signature Proof that the hash has been signed by signer.
+ function preSign(
+ bytes32 hash,
+ address signer,
+ bytes signature)
+ external
+ {
+ require(
+ isValidSignature(hash, signer, signature),
+ SIGNATURE_VALIDATION_FAILED
+ );
+ preSigned[hash][signer] = true;
+ }
+
+ /// @dev Verifies that a hash has been signed by the given signer.
+ /// @param hash Any 32 byte hash.
+ /// @param signer Address that should have signed the given hash.
+ /// @param signature Proof that the hash has been signed by signer.
+ /// @return True if the address recovered from the provided signature matches the input signer address.
+ function isValidSignature(
+ bytes32 hash,
+ address signer,
+ bytes memory signature)
+ internal
+ view
+ returns (bool isValid)
+ {
+ // TODO: Domain separation: make hash depend on role. (Taker sig should not be valid as maker sig, etc.)
+
+ require(
+ signature.length >= 1,
+ INVALID_SIGNATURE_LENGTH
+ );
+ SignatureType signatureType = SignatureType(uint8(signature[0]));
+
+ // Variables are not scoped in Solidity
+ uint8 v;
+ bytes32 r;
+ bytes32 s;
+ address recovered;
+
+ // Always illegal signature
+ // This is always an implicit option since a signer can create a
+ // signature array with invalid type or length. We may as well make
+ // it an explicit option. This aids testing and analysis. It is
+ // also the initialization value for the enum type.
+ if (signatureType == SignatureType.Illegal) {
+ // NOTE: Reason cannot be assigned to a variable because of https://github.com/ethereum/solidity/issues/4051
+ revert("Illegal signature type.");
+
+ // Always invalid signature
+ // Like Illegal, this is always implicitly available and therefore
+ // offered explicitly. It can be implicitly created by providing
+ // a correctly formatted but incorrect signature.
+ } else if (signatureType == SignatureType.Invalid) {
+ require(
+ signature.length == 1,
+ INVALID_SIGNATURE_LENGTH
+ );
+ isValid = false;
+ return isValid;
+
+ // Implicitly signed by caller
+ // The signer has initiated the call. In the case of non-contract
+ // accounts it means the transaction itself was signed.
+ // Example: let's say for a particular operation three signatures
+ // A, B and C are required. To submit the transaction, A and B can
+ // give a signature to C, who can then submit the transaction using
+ // `Caller` for his own signature. Or A and C can sign and B can
+ // submit using `Caller`. Having `Caller` allows this flexibility.
+ } else if (signatureType == SignatureType.Caller) {
+ require(
+ signature.length == 1,
+ INVALID_SIGNATURE_LENGTH
+ );
+ isValid = signer == msg.sender;
+ return isValid;
+
+ // Signed using web3.eth_sign
+ } else if (signatureType == SignatureType.Ecrecover) {
+ require(
+ signature.length == 66,
+ INVALID_SIGNATURE_LENGTH
+ );
+ v = uint8(signature[1]);
+ r = readBytes32(signature, 2);
+ s = readBytes32(signature, 34);
+ recovered = ecrecover(
+ keccak256("\x19Ethereum Signed Message:\n32", hash),
+ v,
+ r,
+ s
+ );
+ isValid = signer == recovered;
+ return isValid;
+
+ // Signature using EIP712
+ } else if (signatureType == SignatureType.EIP712) {
+ require(
+ signature.length == 66,
+ INVALID_SIGNATURE_LENGTH
+ );
+ v = uint8(signature[1]);
+ r = readBytes32(signature, 2);
+ s = readBytes32(signature, 34);
+ recovered = ecrecover(hash, v, r, s);
+ isValid = signer == recovered;
+ return isValid;
+
+ // Signature from Trezor hardware wallet
+ // It differs from web3.eth_sign in the encoding of message length
+ // (Bitcoin varint encoding vs ascii-decimal, the latter is not
+ // self-terminating which leads to ambiguities).
+ // See also:
+ // https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
+ // https://github.com/trezor/trezor-mcu/blob/master/firmware/ethereum.c#L602
+ // https://github.com/trezor/trezor-mcu/blob/master/firmware/crypto.c#L36
+ } else if (signatureType == SignatureType.Trezor) {
+ require(
+ signature.length == 66,
+ INVALID_SIGNATURE_LENGTH
+ );
+ v = uint8(signature[1]);
+ r = readBytes32(signature, 2);
+ s = readBytes32(signature, 34);
+ recovered = ecrecover(
+ keccak256("\x19Ethereum Signed Message:\n\x41", hash),
+ v,
+ r,
+ s
+ );
+ isValid = signer == recovered;
+ return isValid;
+
+ // Signature verified by signer contract
+ } else if (signatureType == SignatureType.Contract) {
+ isValid = ISigner(signer).isValidSignature(hash, signature);
+ return isValid;
+
+ // Signer signed hash previously using the preSign function
+ } else if (signatureType == SignatureType.PreSigned) {
+ isValid = preSigned[hash][signer];
+ return isValid;
+ }
+
+ // Anything else is illegal (We do not return false because
+ // the signature may actually be valid, just not in a format
+ // that we currently support. In this case returning false
+ // may lead the caller to incorrectly believe that the
+ // signature was invalid.)
+ // NOTE: Reason cannot be assigned to a variable because of https://github.com/ethereum/solidity/issues/4051
+ revert("Unsupported signature type.");
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol
new file mode 100644
index 000000000..f93a80705
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol
@@ -0,0 +1,102 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+pragma solidity ^0.4.24;
+
+import "./mixins/MSignatureValidator.sol";
+import "./mixins/MTransactions.sol";
+import "./libs/LibExchangeErrors.sol";
+
+contract MixinTransactions is
+ LibExchangeErrors,
+ MSignatureValidator,
+ MTransactions
+{
+
+ // Mapping of transaction hash => executed
+ // This prevents transactions from being executed more than once.
+ mapping (bytes32 => bool) public transactions;
+
+ // Address of current transaction signer
+ address public currentContextAddress;
+
+ /// @dev Executes an exchange method call in the context of signer.
+ /// @param salt Arbitrary number to ensure uniqueness of transaction hash.
+ /// @param signer Address of transaction signer.
+ /// @param data AbiV2 encoded calldata.
+ /// @param signature Proof of signer transaction by signer.
+ function executeTransaction(
+ uint256 salt,
+ address signer,
+ bytes data,
+ bytes signature)
+ external
+ {
+ // Prevent reentrancy
+ require(currentContextAddress == address(0));
+
+ // Calculate transaction hash
+ bytes32 transactionHash = keccak256(
+ address(this),
+ salt,
+ data
+ );
+
+ // Validate transaction has not been executed
+ require(
+ !transactions[transactionHash],
+ DUPLICATE_TRANSACTION_HASH
+ );
+
+ // TODO: is SignatureType.Caller necessary if we make this check?
+ if (signer != msg.sender) {
+ // Validate signature
+ require(
+ isValidSignature(transactionHash, signer, signature),
+ SIGNATURE_VALIDATION_FAILED
+ );
+
+ // Set the current transaction signer
+ currentContextAddress = signer;
+ }
+
+ // Execute transaction
+ transactions[transactionHash] = true;
+ require(
+ address(this).delegatecall(data),
+ TRANSACTION_EXECUTION_FAILED
+ );
+
+ // Reset current transaction signer
+ // TODO: Check if gas is paid when currentContextAddress is already 0.
+ currentContextAddress = address(0);
+ }
+
+ /// @dev The current function will be called in the context of this address (either 0x transaction signer or `msg.sender`).
+ /// If calling a fill function, this address will represent the taker.
+ /// If calling a cancel function, this address will represent the maker.
+ /// @return Signer of 0x transaction if entry point is `executeTransaction`.
+ /// `msg.sender` if entry point is any other function.
+ function getCurrentContextAddress()
+ internal
+ view
+ returns (address)
+ {
+ address contextAddress = currentContextAddress == address(0) ? msg.sender : currentContextAddress;
+ return contextAddress;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol
new file mode 100644
index 000000000..15f1a2e0b
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol
@@ -0,0 +1,515 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../../utils/LibBytes/LibBytes.sol";
+import "./mixins/MExchangeCore.sol";
+import "./libs/LibMath.sol";
+import "./libs/LibOrder.sol";
+import "./libs/LibFillResults.sol";
+import "./libs/LibExchangeErrors.sol";
+
+contract MixinWrapperFunctions is
+ SafeMath,
+ LibBytes,
+ LibMath,
+ LibOrder,
+ LibFillResults,
+ LibExchangeErrors,
+ MExchangeCore
+{
+ /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled.
+ /// @param order Order struct containing order specifications.
+ /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
+ /// @param signature Proof that order has been created by maker.
+ function fillOrKillOrder(
+ Order memory order,
+ uint256 takerAssetFillAmount,
+ bytes memory signature)
+ public
+ returns (FillResults memory fillResults)
+ {
+ fillResults = fillOrder(
+ order,
+ takerAssetFillAmount,
+ signature
+ );
+ require(
+ fillResults.takerAssetFilledAmount == takerAssetFillAmount,
+ COMPLETE_FILL_FAILED
+ );
+ return fillResults;
+ }
+
+ /// @dev Fills an order with specified parameters and ECDSA signature.
+ /// Returns false if the transaction would otherwise revert.
+ /// @param order Order struct containing order specifications.
+ /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
+ /// @param signature Proof that order has been created by maker.
+ /// @return Amounts filled and fees paid by maker and taker.
+ function fillOrderNoThrow(
+ Order memory order,
+ uint256 takerAssetFillAmount,
+ bytes memory signature)
+ public
+ returns (FillResults memory fillResults)
+ {
+ // We need to call MExchangeCore.fillOrder using a delegatecall in
+ // assembly so that we can intercept a call that throws. For this, we
+ // need the input encoded in memory in the Ethereum ABIv2 format [1].
+
+ // | Area | Offset | Length | Contents |
+ // | -------- |--------|---------|-------------------------------------------- |
+ // | Header | 0x00 | 4 | function selector |
+ // | Params | | 3 * 32 | function parameters: |
+ // | | 0x00 | | 1. offset to order (*) |
+ // | | 0x20 | | 2. takerAssetFillAmount |
+ // | | 0x40 | | 3. offset to signature (*) |
+ // | Data | | 12 * 32 | order: |
+ // | | 0x000 | | 1. senderAddress |
+ // | | 0x020 | | 2. makerAddress |
+ // | | 0x040 | | 3. takerAddress |
+ // | | 0x060 | | 4. feeRecipientAddress |
+ // | | 0x080 | | 5. makerAssetAmount |
+ // | | 0x0A0 | | 6. takerAssetAmount |
+ // | | 0x0C0 | | 7. makerFeeAmount |
+ // | | 0x0E0 | | 8. takerFeeAmount |
+ // | | 0x100 | | 9. expirationTimeSeconds |
+ // | | 0x120 | | 10. salt |
+ // | | 0x140 | | 11. Offset to makerAssetProxyMetadata (*) |
+ // | | 0x160 | | 12. Offset to takerAssetProxyMetadata (*) |
+ // | | 0x180 | 32 | makerAssetProxyMetadata Length |
+ // | | 0x1A0 | ** | makerAssetProxyMetadata Contents |
+ // | | 0x1C0 | 32 | takerAssetProxyMetadata Length |
+ // | | 0x1E0 | ** | takerAssetProxyMetadata Contents |
+ // | | 0x200 | 32 | signature Length |
+ // | | 0x220 | ** | signature Contents |
+
+ // * Offsets are calculated from the beginning of the current area: Header, Params, Data:
+ // An offset stored in the Params area is calculated from the beginning of the Params section.
+ // An offset stored in the Data area is calculated from the beginning of the Data section.
+
+ // ** The length of dynamic array contents are stored in the field immediately preceeding the contents.
+
+ // [1]: https://solidity.readthedocs.io/en/develop/abi-spec.html
+
+ bytes4 fillOrderSelector = this.fillOrder.selector;
+
+ assembly {
+
+ // Areas below may use the following variables:
+ // 1. <area>Start -- Start of this area in memory
+ // 2. <area>End -- End of this area in memory. This value may
+ // be precomputed (before writing contents),
+ // or it may be computed as contents are written.
+ // 3. <area>Offset -- Current offset into area. If an area's End
+ // is precomputed, this variable tracks the
+ // offsets of contents as they are written.
+
+ /////// Setup Header Area ///////
+ // Load free memory pointer
+ let headerAreaStart := mload(0x40)
+ mstore(headerAreaStart, fillOrderSelector)
+ let headerAreaEnd := add(headerAreaStart, 0x4)
+
+ /////// Setup Params Area ///////
+ // This area is preallocated and written to later.
+ // This is because we need to fill in offsets that have not yet been calculated.
+ let paramsAreaStart := headerAreaEnd
+ let paramsAreaEnd := add(paramsAreaStart, 0x60)
+ let paramsAreaOffset := paramsAreaStart
+
+ /////// Setup Data Area ///////
+ let dataAreaStart := paramsAreaEnd
+ let dataAreaEnd := dataAreaStart
+
+ // Offset from the source data we're reading from
+ let sourceOffset := order
+ // arrayLenBytes and arrayLenWords track the length of a dynamically-allocated bytes array.
+ let arrayLenBytes := 0
+ let arrayLenWords := 0
+
+ /////// Write order Struct ///////
+ // Write memory location of Order, relative to the start of the
+ // parameter list, then increment the paramsAreaOffset respectively.
+ mstore(paramsAreaOffset, sub(dataAreaEnd, paramsAreaStart))
+ paramsAreaOffset := add(paramsAreaOffset, 0x20)
+
+ // Write values for each field in the order
+ // It would be nice to use a loop, but we save on gas by writing
+ // the stores sequentially.
+ mstore(dataAreaEnd, mload(sourceOffset)) // makerAddress
+ mstore(add(dataAreaEnd, 0x20), mload(add(sourceOffset, 0x20))) // takerAddress
+ mstore(add(dataAreaEnd, 0x40), mload(add(sourceOffset, 0x40))) // feeRecipientAddress
+ mstore(add(dataAreaEnd, 0x60), mload(add(sourceOffset, 0x60))) // senderAddress
+ mstore(add(dataAreaEnd, 0x80), mload(add(sourceOffset, 0x80))) // makerAssetAmount
+ mstore(add(dataAreaEnd, 0xA0), mload(add(sourceOffset, 0xA0))) // takerAssetAmount
+ mstore(add(dataAreaEnd, 0xC0), mload(add(sourceOffset, 0xC0))) // makerFeeAmount
+ mstore(add(dataAreaEnd, 0xE0), mload(add(sourceOffset, 0xE0))) // takerFeeAmount
+ mstore(add(dataAreaEnd, 0x100), mload(add(sourceOffset, 0x100))) // expirationTimeSeconds
+ mstore(add(dataAreaEnd, 0x120), mload(add(sourceOffset, 0x120))) // salt
+ mstore(add(dataAreaEnd, 0x140), mload(add(sourceOffset, 0x140))) // Offset to makerAssetProxyMetadata
+ mstore(add(dataAreaEnd, 0x160), mload(add(sourceOffset, 0x160))) // Offset to takerAssetProxyMetadata
+ dataAreaEnd := add(dataAreaEnd, 0x180)
+ sourceOffset := add(sourceOffset, 0x180)
+
+ // Write offset to <order.makerAssetProxyMetadata>
+ mstore(add(dataAreaStart, mul(10, 0x20)), sub(dataAreaEnd, dataAreaStart))
+
+ // Calculate length of <order.makerAssetProxyMetadata>
+ arrayLenBytes := mload(sourceOffset)
+ sourceOffset := add(sourceOffset, 0x20)
+ arrayLenWords := div(add(arrayLenBytes, 0x1F), 0x20)
+
+ // Write length of <order.makerAssetProxyMetadata>
+ mstore(dataAreaEnd, arrayLenBytes)
+ dataAreaEnd := add(dataAreaEnd, 0x20)
+
+ // Write contents of <order.makerAssetProxyMetadata>
+ for {let i := 0} lt(i, arrayLenWords) {i := add(i, 1)} {
+ mstore(dataAreaEnd, mload(sourceOffset))
+ dataAreaEnd := add(dataAreaEnd, 0x20)
+ sourceOffset := add(sourceOffset, 0x20)
+ }
+
+ // Write offset to <order.takerAssetProxyMetadata>
+ mstore(add(dataAreaStart, mul(11, 0x20)), sub(dataAreaEnd, dataAreaStart))
+
+ // Calculate length of <order.takerAssetProxyMetadata>
+ arrayLenBytes := mload(sourceOffset)
+ sourceOffset := add(sourceOffset, 0x20)
+ arrayLenWords := div(add(arrayLenBytes, 0x1F), 0x20)
+
+ // Write length of <order.takerAssetProxyMetadata>
+ mstore(dataAreaEnd, arrayLenBytes)
+ dataAreaEnd := add(dataAreaEnd, 0x20)
+
+ // Write contents of <order.takerAssetProxyMetadata>
+ for {let i := 0} lt(i, arrayLenWords) {i := add(i, 1)} {
+ mstore(dataAreaEnd, mload(sourceOffset))
+ dataAreaEnd := add(dataAreaEnd, 0x20)
+ sourceOffset := add(sourceOffset, 0x20)
+ }
+
+ /////// Write takerAssetFillAmount ///////
+ mstore(paramsAreaOffset, takerAssetFillAmount)
+ paramsAreaOffset := add(paramsAreaOffset, 0x20)
+
+ /////// Write signature ///////
+ // Write offset to paramsArea
+ mstore(paramsAreaOffset, sub(dataAreaEnd, paramsAreaStart))
+
+ // Calculate length of signature
+ sourceOffset := signature
+ arrayLenBytes := mload(sourceOffset)
+ sourceOffset := add(sourceOffset, 0x20)
+ arrayLenWords := div(add(arrayLenBytes, 0x1F), 0x20)
+
+ // Write length of signature
+ mstore(dataAreaEnd, arrayLenBytes)
+ dataAreaEnd := add(dataAreaEnd, 0x20)
+
+ // Write contents of signature
+ for {let i := 0} lt(i, arrayLenWords) {i := add(i, 1)} {
+ mstore(dataAreaEnd, mload(sourceOffset))
+ dataAreaEnd := add(dataAreaEnd, 0x20)
+ sourceOffset := add(sourceOffset, 0x20)
+ }
+
+ // Execute delegatecall
+ let success := delegatecall(
+ gas, // forward all gas, TODO: look into gas consumption of assert/throw
+ address, // call address of this contract
+ headerAreaStart, // pointer to start of input
+ sub(dataAreaEnd, headerAreaStart), // length of input
+ headerAreaStart, // write output over input
+ 128 // output size is 128 bytes
+ )
+ switch success
+ case 0 {
+ mstore(fillResults, 0)
+ mstore(add(fillResults, 32), 0)
+ mstore(add(fillResults, 64), 0)
+ mstore(add(fillResults, 96), 0)
+ }
+ case 1 {
+ mstore(fillResults, mload(headerAreaStart))
+ mstore(add(fillResults, 32), mload(add(headerAreaStart, 32)))
+ mstore(add(fillResults, 64), mload(add(headerAreaStart, 64)))
+ mstore(add(fillResults, 96), mload(add(headerAreaStart, 96)))
+ }
+ }
+ return fillResults;
+ }
+
+ /// @dev Synchronously executes multiple calls of fillOrder.
+ /// @param orders Array of order specifications.
+ /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.
+ /// @param signatures Proofs that orders have been created by makers.
+ function batchFillOrders(
+ Order[] memory orders,
+ uint256[] memory takerAssetFillAmounts,
+ bytes[] memory signatures)
+ public
+ {
+ for (uint256 i = 0; i < orders.length; i++) {
+ fillOrder(
+ orders[i],
+ takerAssetFillAmounts[i],
+ signatures[i]
+ );
+ }
+ }
+
+ /// @dev Synchronously executes multiple calls of fillOrKill.
+ /// @param orders Array of order specifications.
+ /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.
+ /// @param signatures Proofs that orders have been created by makers.
+ function batchFillOrKillOrders(
+ Order[] memory orders,
+ uint256[] memory takerAssetFillAmounts,
+ bytes[] memory signatures)
+ public
+ {
+ for (uint256 i = 0; i < orders.length; i++) {
+ fillOrKillOrder(
+ orders[i],
+ takerAssetFillAmounts[i],
+ signatures[i]
+ );
+ }
+ }
+
+ /// @dev Fills an order with specified parameters and ECDSA signature.
+ /// Returns false if the transaction would otherwise revert.
+ /// @param orders Array of order specifications.
+ /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.
+ /// @param signatures Proofs that orders have been created by makers.
+ function batchFillOrdersNoThrow(
+ Order[] memory orders,
+ uint256[] memory takerAssetFillAmounts,
+ bytes[] memory signatures)
+ public
+ {
+ for (uint256 i = 0; i < orders.length; i++) {
+ fillOrderNoThrow(
+ orders[i],
+ takerAssetFillAmounts[i],
+ signatures[i]
+ );
+ }
+ }
+
+ /// @dev Synchronously executes multiple calls of fillOrder until total amount of takerAsset is sold by taker.
+ /// @param orders Array of order specifications.
+ /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
+ /// @param signatures Proofs that orders have been created by makers.
+ /// @return Amounts filled and fees paid by makers and taker.
+ function marketSellOrders(
+ Order[] memory orders,
+ uint256 takerAssetFillAmount,
+ bytes[] memory signatures)
+ public
+ returns (FillResults memory totalFillResults)
+ {
+ for (uint256 i = 0; i < orders.length; i++) {
+
+ // Token being sold by taker must be the same for each order
+ // TODO: optimize by only using takerAssetData for first order.
+ require(
+ areBytesEqual(orders[i].takerAssetData, orders[0].takerAssetData),
+ ASSET_DATA_MISMATCH
+ );
+
+ // Calculate the remaining amount of takerAsset to sell
+ uint256 remainingTakerAssetFillAmount = safeSub(takerAssetFillAmount, totalFillResults.takerAssetFilledAmount);
+
+ // Attempt to sell the remaining amount of takerAsset
+ FillResults memory singleFillResults = fillOrder(
+ orders[i],
+ remainingTakerAssetFillAmount,
+ signatures[i]
+ );
+
+ // Update amounts filled and fees paid by maker and taker
+ addFillResults(totalFillResults, singleFillResults);
+
+ // Stop execution if the entire amount of takerAsset has been sold
+ if (totalFillResults.takerAssetFilledAmount == takerAssetFillAmount) {
+ break;
+ }
+ }
+ return totalFillResults;
+ }
+
+ /// @dev Synchronously executes multiple calls of fillOrder until total amount of takerAsset is sold by taker.
+ /// Returns false if the transaction would otherwise revert.
+ /// @param orders Array of order specifications.
+ /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
+ /// @param signatures Proofs that orders have been signed by makers.
+ /// @return Amounts filled and fees paid by makers and taker.
+ function marketSellOrdersNoThrow(
+ Order[] memory orders,
+ uint256 takerAssetFillAmount,
+ bytes[] memory signatures)
+ public
+ returns (FillResults memory totalFillResults)
+ {
+ for (uint256 i = 0; i < orders.length; i++) {
+
+ // Token being sold by taker must be the same for each order
+ // TODO: optimize by only using takerAssetData for first order.
+ require(
+ areBytesEqual(orders[i].takerAssetData, orders[0].takerAssetData),
+ ASSET_DATA_MISMATCH
+ );
+
+ // Calculate the remaining amount of takerAsset to sell
+ uint256 remainingTakerAssetFillAmount = safeSub(takerAssetFillAmount, totalFillResults.takerAssetFilledAmount);
+
+ // Attempt to sell the remaining amount of takerAsset
+ FillResults memory singleFillResults = fillOrderNoThrow(
+ orders[i],
+ remainingTakerAssetFillAmount,
+ signatures[i]
+ );
+
+ // Update amounts filled and fees paid by maker and taker
+ addFillResults(totalFillResults, singleFillResults);
+
+ // Stop execution if the entire amount of takerAsset has been sold
+ if (totalFillResults.takerAssetFilledAmount == takerAssetFillAmount) {
+ break;
+ }
+ }
+ return totalFillResults;
+ }
+
+ /// @dev Synchronously executes multiple calls of fillOrder until total amount of makerAsset is bought by taker.
+ /// @param orders Array of order specifications.
+ /// @param makerAssetFillAmount Desired amount of makerAsset to buy.
+ /// @param signatures Proofs that orders have been signed by makers.
+ /// @return Amounts filled and fees paid by makers and taker.
+ function marketBuyOrders(
+ Order[] memory orders,
+ uint256 makerAssetFillAmount,
+ bytes[] memory signatures)
+ public
+ returns (FillResults memory totalFillResults)
+ {
+ for (uint256 i = 0; i < orders.length; i++) {
+
+ // Token being bought by taker must be the same for each order
+ // TODO: optimize by only using makerAssetData for first order.
+ require(
+ areBytesEqual(orders[i].makerAssetData, orders[0].makerAssetData),
+ ASSET_DATA_MISMATCH
+ );
+
+ // Calculate the remaining amount of makerAsset to buy
+ uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount);
+
+ // Convert the remaining amount of makerAsset to buy into remaining amount
+ // of takerAsset to sell, assuming entire amount can be sold in the current order
+ uint256 remainingTakerAssetFillAmount = getPartialAmount(
+ orders[i].takerAssetAmount,
+ orders[i].makerAssetAmount,
+ remainingMakerAssetFillAmount
+ );
+
+ // Attempt to sell the remaining amount of takerAsset
+ FillResults memory singleFillResults = fillOrder(
+ orders[i],
+ remainingTakerAssetFillAmount,
+ signatures[i]
+ );
+
+ // Update amounts filled and fees paid by maker and taker
+ addFillResults(totalFillResults, singleFillResults);
+
+ // Stop execution if the entire amount of makerAsset has been bought
+ if (totalFillResults.makerAssetFilledAmount == makerAssetFillAmount) {
+ break;
+ }
+ }
+ return totalFillResults;
+ }
+
+ /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker.
+ /// Returns false if the transaction would otherwise revert.
+ /// @param orders Array of order specifications.
+ /// @param makerAssetFillAmount Desired amount of makerAsset to buy.
+ /// @param signatures Proofs that orders have been signed by makers.
+ /// @return Amounts filled and fees paid by makers and taker.
+ function marketBuyOrdersNoThrow(
+ Order[] memory orders,
+ uint256 makerAssetFillAmount,
+ bytes[] memory signatures)
+ public
+ returns (FillResults memory totalFillResults)
+ {
+ for (uint256 i = 0; i < orders.length; i++) {
+
+ // Token being bought by taker must be the same for each order
+ // TODO: optimize by only using makerAssetData for first order.
+ require(
+ areBytesEqual(orders[i].makerAssetData, orders[0].makerAssetData),
+ ASSET_DATA_MISMATCH
+ );
+
+ // Calculate the remaining amount of makerAsset to buy
+ uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount);
+
+ // Convert the remaining amount of makerAsset to buy into remaining amount
+ // of takerAsset to sell, assuming entire amount can be sold in the current order
+ uint256 remainingTakerAssetFillAmount = getPartialAmount(
+ orders[i].takerAssetAmount,
+ orders[i].makerAssetAmount,
+ remainingMakerAssetFillAmount
+ );
+
+ // Attempt to sell the remaining amount of takerAsset
+ FillResults memory singleFillResults = fillOrderNoThrow(
+ orders[i],
+ remainingTakerAssetFillAmount,
+ signatures[i]
+ );
+
+ // Update amounts filled and fees paid by maker and taker
+ addFillResults(totalFillResults, singleFillResults);
+
+ // Stop execution if the entire amount of makerAsset has been bought
+ if (totalFillResults.makerAssetFilledAmount == makerAssetFillAmount) {
+ break;
+ }
+ }
+ return totalFillResults;
+ }
+
+ /// @dev Synchronously cancels multiple orders in a single transaction.
+ /// @param orders Array of order specifications.
+ function batchCancelOrders(Order[] memory orders)
+ public
+ {
+ for (uint256 i = 0; i < orders.length; i++) {
+ cancelOrder(orders[i]);
+ }
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol
new file mode 100644
index 000000000..3ce5ef157
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol
@@ -0,0 +1,41 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+contract IAssetProxyDispatcher {
+
+ /// @dev Registers an asset proxy to an asset proxy id.
+ /// An id can only be assigned to a single proxy at a given time.
+ /// @param assetProxyId Id to register`newAssetProxy` under.
+ /// @param newAssetProxy Address of new asset proxy to register, or 0x0 to unset assetProxyId.
+ /// @param oldAssetProxy Existing asset proxy to overwrite, or 0x0 if assetProxyId is currently unused.
+ function registerAssetProxy(
+ uint8 assetProxyId,
+ address newAssetProxy,
+ address oldAssetProxy)
+ external;
+
+ /// @dev Gets an asset proxy.
+ /// @param assetProxyId Id of the asset proxy.
+ /// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered.
+ function getAssetProxy(uint8 assetProxyId)
+ external
+ view
+ returns (address);
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchange.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchange.sol
new file mode 100644
index 000000000..fc428e9c0
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchange.sol
@@ -0,0 +1,38 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./IExchangeCore.sol";
+import "./IMatchOrders";
+import "./ISettlement";
+import "./ISignatureValidator";
+import "./ITransactions";
+import "./IAssetProxyDispatcher";
+import "./IWrapperFunctions";
+
+contract IExchange is
+ IExchangeCore,
+ IMatchOrders,
+ ISettlement,
+ ISignatureValidator,
+ ITransactions,
+ IAssetProxyDispatcher,
+ IWrapperFunctions
+{}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol
new file mode 100644
index 000000000..fc0157d75
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol
@@ -0,0 +1,80 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../libs/LibOrder.sol";
+import "../libs/LibFillResults.sol";
+
+contract IExchangeCore {
+
+ /// @dev Cancels all orders reated by sender with a salt less than or equal to the specified salt value.
+ /// @param salt Orders created with a salt less or equal to this value will be cancelled.
+ function cancelOrdersUpTo(uint256 salt)
+ external;
+
+ /// @dev Fills the input order.
+ /// @param order Order struct containing order specifications.
+ /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
+ /// @param signature Proof that order has been created by maker.
+ /// @return Amounts filled and fees paid by maker and taker.
+ function fillOrder(
+ LibOrder.Order memory order,
+ uint256 takerAssetFillAmount,
+ bytes memory signature)
+ public
+ returns (LibFillResults.FillResults memory fillResults);
+
+ /// @dev After calling, the order can not be filled anymore.
+ /// @param order Order struct containing order specifications.
+ /// @return True if the order state changed to cancelled.
+ /// False if the transaction was already cancelled or expired.
+ function cancelOrder(LibOrder.Order memory order)
+ public
+ returns (bool);
+
+ /// @dev Gets information about an order: status, hash, and amount filled.
+ /// @param order Order to gather information on.
+ /// @return OrderInfo Information about the order and its state.
+ /// See LibOrder.OrderInfo for a complete description.
+ function getOrderInfo(LibOrder.Order memory order)
+ public
+ view
+ returns (LibOrder.OrderInfo memory orderInfo);
+
+ /// @dev Calculates amounts filled and fees paid by maker and taker.
+ /// @param order to be filled.
+ /// @param orderStatus Status of order to be filled.
+ /// @param orderTakerAssetFilledAmount Amount of order already filled.
+ /// @param takerAssetFillAmount Desired amount of order to fill by taker.
+ /// @return status Return status of calculating fill amounts. Returns Status.SUCCESS on success.
+ /// @return fillResults Amounts filled and fees paid by maker and taker.
+ function calculateFillResults(
+ LibOrder.Order memory order,
+ uint8 orderStatus,
+ uint256 orderTakerAssetFilledAmount,
+ uint256 takerAssetFillAmount
+ )
+ public
+ pure
+ returns (
+ uint8 status,
+ LibFillResults.FillResults memory fillResults
+ );
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol
new file mode 100644
index 000000000..df009d063
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol
@@ -0,0 +1,44 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../libs/LibOrder.sol";
+import "../libs/LibFillResults.sol";
+
+contract IMatchOrders {
+
+ /// @dev Match two complementary orders that have a profitable spread.
+ /// Each order is filled at their respective price point. However, the calculations are
+ /// carried out as though the orders are both being filled at the right order's price point.
+ /// The profit made by the left order goes to the taker (who matched the two orders).
+ /// @param leftOrder First order to match.
+ /// @param rightOrder Second order to match.
+ /// @param leftSignature Proof that order was created by the left maker.
+ /// @param rightSignature Proof that order was created by the right maker.
+ /// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders.
+ /// TODO: Make this function external once supported by Solidity (See Solidity Issues #3199, #1603)
+ function matchOrders(
+ LibOrder.Order memory leftOrder,
+ LibOrder.Order memory rightOrder,
+ bytes memory leftSignature,
+ bytes memory rightSignature
+ )
+ public
+ returns (LibFillResults.MatchedFillResults memory matchedFillResults);
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISignatureValidator.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISignatureValidator.sol
new file mode 100644
index 000000000..65ff45f7b
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISignatureValidator.sol
@@ -0,0 +1,32 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+contract ISignatureValidator {
+
+ /// @dev Approves a hash on-chain using any valid signature type.
+ /// After presigning a hash, the preSign signature type will become valid for that hash and signer.
+ /// @param signer Address that should have signed the given hash.
+ /// @param signature Proof that the hash has been signed by signer.
+ function preSign(
+ bytes32 hash,
+ address signer,
+ bytes signature)
+ external;
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISigner.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISigner.sol
new file mode 100644
index 000000000..53c41d331
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISigner.sol
@@ -0,0 +1,33 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+contract ISigner {
+
+ /// @dev Verifies that a signature is valid.
+ /// @param hash Message hash that is signed.
+ /// @param signature Proof of signing.
+ /// @return Validity of order signature.
+ function isValidSignature(
+ bytes32 hash,
+ bytes signature)
+ external
+ view
+ returns (bool isValid);
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol
new file mode 100644
index 000000000..d973bf001
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol
@@ -0,0 +1,33 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+pragma solidity ^0.4.24;
+
+contract ITransactions {
+
+ /// @dev Executes an exchange method call in the context of signer.
+ /// @param salt Arbitrary number to ensure uniqueness of transaction hash.
+ /// @param signer Address of transaction signer.
+ /// @param data AbiV2 encoded calldata.
+ /// @param signature Proof of signer transaction by signer.
+ function executeTransaction(
+ uint256 salt,
+ address signer,
+ bytes data,
+ bytes signature)
+ external;
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol
new file mode 100644
index 000000000..1eb1233ed
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol
@@ -0,0 +1,142 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./libs/LibOrder.sol";
+import "./libs/LibFillResults.sol";
+
+contract IWrapperFunctions is
+ LibBytes,
+ LibMath,
+ LibOrder,
+ LibFillResults,
+ LibExchangeErrors,
+ MExchangeCore
+{
+ /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled.
+ /// @param order LibOrder.Order struct containing order specifications.
+ /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
+ /// @param signature Proof that order has been created by maker.
+ function fillOrKillOrder(
+ LibOrder.LibOrder.Order memory order,
+ uint256 takerAssetFillAmount,
+ bytes memory signature)
+ public
+ returns (LibFillResults.LibFillResults.FillResults memory fillResults);
+
+ /// @dev Fills an order with specified parameters and ECDSA signature.
+ /// Returns false if the transaction would otherwise revert.
+ /// @param order LibOrder.Order struct containing order specifications.
+ /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
+ /// @param signature Proof that order has been created by maker.
+ /// @return Amounts filled and fees paid by maker and taker.
+ function fillOrderNoThrow(
+ LibOrder.Order memory order,
+ uint256 takerAssetFillAmount,
+ bytes memory signature)
+ public
+ returns (LibFillResults.FillResults memory fillResults);
+
+ /// @dev Synchronously executes multiple calls of fillOrder.
+ /// @param orders Array of order specifications.
+ /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.
+ /// @param signatures Proofs that orders have been created by makers.
+ function batchFillOrders(
+ LibOrder.Order[] memory orders,
+ uint256[] memory takerAssetFillAmounts,
+ bytes[] memory signatures)
+ public;
+
+ /// @dev Synchronously executes multiple calls of fillOrKill.
+ /// @param orders Array of order specifications.
+ /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.
+ /// @param signatures Proofs that orders have been created by makers.
+ function batchFillOrKillOrders(
+ LibOrder.Order[] memory orders,
+ uint256[] memory takerAssetFillAmounts,
+ bytes[] memory signatures)
+ public;
+
+ /// @dev Fills an order with specified parameters and ECDSA signature.
+ /// Returns false if the transaction would otherwise revert.
+ /// @param orders Array of order specifications.
+ /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.
+ /// @param signatures Proofs that orders have been created by makers.
+ function batchFillOrdersNoThrow(
+ LibOrder.Order[] memory orders,
+ uint256[] memory takerAssetFillAmounts,
+ bytes[] memory signatures)
+ public;
+
+ /// @dev Synchronously executes multiple calls of fillOrder until total amount of takerAsset is sold by taker.
+ /// @param orders Array of order specifications.
+ /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
+ /// @param signatures Proofs that orders have been created by makers.
+ /// @return Amounts filled and fees paid by makers and taker.
+ function marketSellOrders(
+ LibOrder.Order[] memory orders,
+ uint256 takerAssetFillAmount,
+ bytes[] memory signatures)
+ public
+ returns (LibFillResults.FillResults memory totalFillResults);
+
+ /// @dev Synchronously executes multiple calls of fillOrder until total amount of takerAsset is sold by taker.
+ /// Returns false if the transaction would otherwise revert.
+ /// @param orders Array of order specifications.
+ /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
+ /// @param signatures Proofs that orders have been signed by makers.
+ /// @return Amounts filled and fees paid by makers and taker.
+ function marketSellOrdersNoThrow(
+ LibOrder.Order[] memory orders,
+ uint256 takerAssetFillAmount,
+ bytes[] memory signatures)
+ public
+ returns (LibFillResults.FillResults memory totalFillResults);
+
+ /// @dev Synchronously executes multiple calls of fillOrder until total amount of makerAsset is bought by taker.
+ /// @param orders Array of order specifications.
+ /// @param makerAssetFillAmount Desired amount of makerAsset to buy.
+ /// @param signatures Proofs that orders have been signed by makers.
+ /// @return Amounts filled and fees paid by makers and taker.
+ function marketBuyOrders(
+ LibOrder.Order[] memory orders,
+ uint256 makerAssetFillAmount,
+ bytes[] memory signatures)
+ public
+ returns (LibFillResults.FillResults memory totalFillResults);
+
+ /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker.
+ /// Returns false if the transaction would otherwise revert.
+ /// @param orders Array of order specifications.
+ /// @param makerAssetFillAmount Desired amount of makerAsset to buy.
+ /// @param signatures Proofs that orders have been signed by makers.
+ /// @return Amounts filled and fees paid by makers and taker.
+ function marketBuyOrdersNoThrow(
+ LibOrder.Order[] memory orders,
+ uint256 makerAssetFillAmount,
+ bytes[] memory signatures)
+ public
+ returns (LibFillResults.FillResults memory totalFillResults);
+
+ /// @dev Synchronously cancels multiple orders in a single transaction.
+ /// @param orders Array of order specifications.
+ function batchCancelOrders(LibOrder.Order[] memory orders)
+ public;
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol
new file mode 100644
index 000000000..4712ee36c
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol
@@ -0,0 +1,60 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+contract LibExchangeErrors {
+
+ // Core revert reasons
+ string constant GT_ZERO_AMOUNT_REQUIRED = "Amount must be greater than 0.";
+ string constant SIGNATURE_VALIDATION_FAILED = "Signature validation failed.";
+ string constant INVALID_SENDER = "Invalid `msg.sender`.";
+ string constant INVALID_CONTEXT = "Function called in an invalid context.";
+ string constant INVALID_NEW_MAKER_EPOCH = "Specified salt must be greater than or equal to existing makerEpoch.";
+
+ // Order revert reasons
+ string constant INVALID_ORDER_TAKER_ASSET_AMOUNT = "Invalid order taker asset amount: expected a non-zero value.";
+ string constant INVALID_ORDER_MAKER_ASSET_AMOUNT = "Invalid order maker asset amount: expected a non-zero value.";
+
+ // Transaction revert reasons
+ string constant DUPLICATE_TRANSACTION_HASH = "Transaction has already been executed.";
+ string constant TRANSACTION_EXECUTION_FAILED = "Transaction execution failed.";
+
+ // Wrapper revert reasons
+ string constant COMPLETE_FILL_FAILED = "Desired fill amount could not be completely filled.";
+ string constant ASSET_DATA_MISMATCH = "Asset data must be the same for each order.";
+
+ // Asset proxy dispatcher revert reasons
+ string constant GT_ZERO_LENGTH_REQUIRED = "Length must be greater than 0.";
+ string constant OLD_ASSET_PROXY_MISMATCH = "Old asset proxy does not match asset proxy at given id.";
+ string constant NEW_ASSET_PROXY_MISMATCH = "New asset proxy id does not match given id.";
+
+ // Signature validator revert reasons
+ string constant INVALID_SIGNATURE_LENGTH = "Invalid signature length.";
+ string constant ILLEGAL_SIGNATURE_TYPE = "Illegal signature type.";
+ string constant UNSUPPORTED_SIGNATURE_TYPE = "Unsupported signature type.";
+
+ // Order matching revert reasons
+ string constant ASSET_MISMATCH_MAKER_TAKER = "Left order maker asset is different from right order taker asset.";
+ string constant ASSET_MISMATCH_TAKER_MAKER = "Left order taker asset is different from right order maker asset.";
+ string constant NEGATIVE_SPREAD = "Matched orders must have a positive spread.";
+ string constant MISCALCULATED_TRANSFER_AMOUNTS = "A miscalculation occurred: the left maker would receive more than the right maker would spend.";
+ string constant ROUNDING_ERROR_TRANSFER_AMOUNTS = "A rounding error occurred when calculating transfer amounts for matched orders.";
+ string constant FAILED_TO_CALCULATE_FILL_RESULTS_FOR_LEFT_ORDER = "Failed to calculate fill results for left order.";
+ string constant FAILED_TO_CALCULATE_FILL_RESULTS_FOR_RIGHT_ORDER = "Failed to calculate fill results for right order.";
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol
new file mode 100644
index 000000000..aa54598fa
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol
@@ -0,0 +1,68 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "../../../utils/SafeMath/SafeMath.sol";
+
+contract LibFillResults is
+ SafeMath
+{
+
+ struct FillResults {
+ uint256 makerAssetFilledAmount;
+ uint256 takerAssetFilledAmount;
+ uint256 makerFeePaid;
+ uint256 takerFeePaid;
+ }
+
+ struct MatchedFillResults {
+ LibFillResults.FillResults left;
+ LibFillResults.FillResults right;
+ uint256 takerFillAmount;
+ }
+
+ /// @dev Adds properties of both FillResults instances.
+ /// Modifies the first FillResults instance specified.
+ /// @param totalFillResults Fill results instance that will be added onto.
+ /// @param singleFillResults Fill results instance that will be added to totalFillResults.
+ function addFillResults(FillResults memory totalFillResults, FillResults memory singleFillResults)
+ internal
+ pure
+ {
+ totalFillResults.makerAssetFilledAmount = safeAdd(totalFillResults.makerAssetFilledAmount, singleFillResults.makerAssetFilledAmount);
+ totalFillResults.takerAssetFilledAmount = safeAdd(totalFillResults.takerAssetFilledAmount, singleFillResults.takerAssetFilledAmount);
+ totalFillResults.makerFeePaid = safeAdd(totalFillResults.makerFeePaid, singleFillResults.makerFeePaid);
+ totalFillResults.takerFeePaid = safeAdd(totalFillResults.takerFeePaid, singleFillResults.takerFeePaid);
+ }
+
+ /// @dev Returns a null fill results struct
+ function getNullFillResults()
+ internal
+ pure
+ returns (FillResults memory)
+ {
+ // returns zeroed out FillResults instance
+ return FillResults({
+ makerAssetFilledAmount: 0,
+ takerAssetFilledAmount: 0,
+ makerFeePaid: 0,
+ takerFeePaid: 0
+ });
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol
new file mode 100644
index 000000000..ea8c138d6
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol
@@ -0,0 +1,93 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "../../../utils/SafeMath/SafeMath.sol";
+
+contract LibMath is
+ SafeMath
+{
+ string constant ROUNDING_ERROR_ON_PARTIAL_AMOUNT = "A rounding error occurred when calculating partial transfer amounts.";
+
+ /// @dev Calculates partial value given a numerator and denominator.
+ /// @param numerator Numerator.
+ /// @param denominator Denominator.
+ /// @param target Value to calculate partial of.
+ /// @return Partial value of target.
+ function getPartialAmount(
+ uint256 numerator,
+ uint256 denominator,
+ uint256 target)
+ internal
+ pure
+ returns (uint256 partialAmount)
+ {
+ partialAmount = safeDiv(
+ safeMul(numerator, target),
+ denominator
+ );
+ return partialAmount;
+ }
+
+ /// @dev Calculates partial value given a numerator and denominator.
+ /// Throws if there is a rounding error.
+ /// @param numerator Numerator.
+ /// @param denominator Denominator.
+ /// @param target Value to calculate partial of.
+ /// @return Partial value of target.
+ function safeGetPartialAmount(
+ uint256 numerator,
+ uint256 denominator,
+ uint256 target)
+ internal pure
+ returns (uint256 partialAmount)
+ {
+ require(
+ !isRoundingError(numerator, denominator, target),
+ ROUNDING_ERROR_ON_PARTIAL_AMOUNT
+ );
+ return getPartialAmount(numerator, denominator, target);
+ }
+
+ /// @dev Checks if rounding error > 0.1%.
+ /// @param numerator Numerator.
+ /// @param denominator Denominator.
+ /// @param target Value to multiply with numerator/denominator.
+ /// @return Rounding error is present.
+ function isRoundingError(
+ uint256 numerator,
+ uint256 denominator,
+ uint256 target)
+ internal
+ pure
+ returns (bool isError)
+ {
+ uint256 remainder = mulmod(target, numerator, denominator);
+ if (remainder == 0) {
+ return false; // No rounding error.
+ }
+
+ uint256 errPercentageTimes1000000 = safeDiv(
+ safeMul(remainder, 1000000),
+ safeMul(numerator, target)
+ );
+ isError = errPercentageTimes1000000 > 1000;
+ return isError;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol
new file mode 100644
index 000000000..7d8328c67
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol
@@ -0,0 +1,93 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+contract LibOrder {
+
+ bytes32 constant ORDER_SCHEMA_HASH = keccak256(
+ "address exchangeAddress",
+ "address makerAddress",
+ "address takerAddress",
+ "address feeRecipientAddress",
+ "address senderAddress",
+ "uint256 makerAssetAmount",
+ "uint256 takerAssetAmount",
+ "uint256 makerFee",
+ "uint256 takerFee",
+ "uint256 expirationTimeSeconds",
+ "uint256 salt",
+ "bytes makerAssetData",
+ "bytes takerAssetData"
+ );
+
+ struct 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;
+ }
+
+ struct OrderInfo {
+ // See LibStatus for a complete description of order statuses
+ uint8 orderStatus;
+ // Keccak-256 EIP712 hash of the order
+ bytes32 orderHash;
+ // Amount of order that has been filled
+ uint256 orderTakerAssetFilledAmount;
+ }
+
+ /// @dev Calculates Keccak-256 hash of the order.
+ /// @param order The order structure.
+ /// @return Keccak-256 EIP712 hash of the order.
+ function getOrderHash(Order memory order)
+ internal
+ view
+ returns (bytes32 orderHash)
+ {
+ // TODO: EIP712 is not finalized yet
+ // Source: https://github.com/ethereum/EIPs/pull/712
+ orderHash = keccak256(
+ ORDER_SCHEMA_HASH,
+ keccak256(
+ address(this),
+ order.makerAddress,
+ order.takerAddress,
+ order.feeRecipientAddress,
+ order.senderAddress,
+ order.makerAssetAmount,
+ order.takerAssetAmount,
+ order.makerFee,
+ order.takerFee,
+ order.expirationTimeSeconds,
+ order.salt,
+ order.makerAssetData,
+ order.takerAssetData
+ )
+ );
+ return orderHash;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol
new file mode 100644
index 000000000..f72b7d65f
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol
@@ -0,0 +1,51 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+contract LibStatus {
+
+ // Exchange Status Codes
+ enum Status {
+ /// Default Status ///
+ INVALID, // General invalid status
+
+ /// General Exchange Statuses ///
+ SUCCESS, // Indicates a successful operation
+ ROUNDING_ERROR_TOO_LARGE, // Rounding error too large
+ INSUFFICIENT_BALANCE_OR_ALLOWANCE, // Insufficient balance or allowance for token transfer
+ TAKER_ASSET_FILL_AMOUNT_TOO_LOW, // takerAssetFillAmount is <= 0
+ INVALID_SIGNATURE, // Invalid signature
+ INVALID_SENDER, // Invalid sender
+ INVALID_TAKER, // Invalid taker
+ INVALID_MAKER, // Invalid maker
+
+ /// Order State Statuses ///
+ // A valid order remains fillable until it is expired, fully filled, or cancelled.
+ // An order's state is unaffected by external factors, like account balances.
+ ORDER_INVALID_MAKER_ASSET_AMOUNT, // Order does not have a valid maker asset amount
+ ORDER_INVALID_TAKER_ASSET_AMOUNT, // Order does not have a valid taker asset amount
+ ORDER_FILLABLE, // Order is fillable
+ ORDER_EXPIRED, // Order has already expired
+ ORDER_FULLY_FILLED, // Order is fully filled
+ ORDER_CANCELLED // Order has been cancelled
+ }
+
+ event ExchangeStatus(uint8 indexed statusId, bytes32 indexed orderHash);
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol
new file mode 100644
index 000000000..ccc960d6e
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol
@@ -0,0 +1,46 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../interfaces/IAssetProxyDispatcher.sol";
+
+contract MAssetProxyDispatcher is
+ IAssetProxyDispatcher
+{
+
+ // Logs registration of new asset proxy
+ event AssetProxySet(
+ uint8 id,
+ address newAssetProxy,
+ address oldAssetProxy
+ );
+
+ /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws.
+ /// @param assetMetadata Byte array encoded for the respective asset proxy.
+ /// @param from Address to transfer token from.
+ /// @param to Address to transfer token to.
+ /// @param amount Amount of token to transfer.
+ function dispatchTransferFrom(
+ bytes memory assetMetadata,
+ address from,
+ address to,
+ uint256 amount)
+ internal;
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol
new file mode 100644
index 000000000..ae1e50637
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol
@@ -0,0 +1,117 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../libs/LibOrder.sol";
+import "../libs/LibFillResults.sol";
+import "../interfaces/IExchangeCore.sol";
+
+contract MExchangeCore is
+ IExchangeCore
+{
+
+ // Fill event is emitted whenever an order is filled.
+ event Fill(
+ address indexed makerAddress,
+ address takerAddress,
+ address indexed feeRecipientAddress,
+ uint256 makerAssetFilledAmount,
+ uint256 takerAssetFilledAmount,
+ uint256 makerFeePaid,
+ uint256 takerFeePaid,
+ bytes32 indexed orderHash,
+ bytes makerAssetData,
+ bytes takerAssetData
+ );
+
+ // Cancel event is emitted whenever an individual order is cancelled.
+ event Cancel(
+ address indexed makerAddress,
+ address indexed feeRecipientAddress,
+ bytes32 indexed orderHash,
+ bytes makerAssetData,
+ bytes takerAssetData
+ );
+
+ // CancelUpTo event is emitted whenever `cancelOrdersUpTo` is executed succesfully.
+ event CancelUpTo(
+ address indexed makerAddress,
+ uint256 makerEpoch
+ );
+
+ /// @dev Validates context for fillOrder. Succeeds or throws.
+ /// @param order to be filled.
+ /// @param orderStatus Status of order to be filled.
+ /// @param orderHash Hash of order to be filled.
+ /// @param takerAddress Address of order taker.
+ /// @param orderTakerAssetFilledAmount Amount of order already filled.
+ /// @param takerAssetFillAmount Desired amount of order to fill by taker.
+ /// @param signature Proof that the orders was created by its maker.
+ function assertValidFill(
+ LibOrder.Order memory order,
+ uint8 orderStatus,
+ bytes32 orderHash,
+ address takerAddress,
+ uint256 orderTakerAssetFilledAmount,
+ uint256 takerAssetFillAmount,
+ bytes memory signature
+ )
+ internal;
+
+ /// @dev Updates state with results of a fill order.
+ /// @param order that was filled.
+ /// @param takerAddress Address of taker who filled the order.
+ /// @param orderTakerAssetFilledAmount Amount of order already filled.
+ /// @return fillResults Amounts filled and fees paid by maker and taker.
+ function updateFilledState(
+ LibOrder.Order memory order,
+ address takerAddress,
+ bytes32 orderHash,
+ uint256 orderTakerAssetFilledAmount,
+ LibFillResults.FillResults memory fillResults
+ )
+ internal;
+
+ /// @dev Validates context for cancelOrder. Succeeds or throws.
+ /// @param order that was cancelled.
+ /// @param orderStatus Status of order that was cancelled.
+ /// @param orderHash Hash of order that was cancelled.
+ function assertValidCancel(
+ LibOrder.Order memory order,
+ uint8 orderStatus,
+ bytes32 orderHash
+ )
+ internal;
+
+ /// @dev Updates state with results of cancelling an order.
+ /// State is only updated if the order is currently fillable.
+ /// Otherwise, updating state would have no effect.
+ /// @param order that was cancelled.
+ /// @param orderStatus Status of order that was cancelled.
+ /// @param orderHash Hash of order that was cancelled.
+ /// @return stateUpdated Returns true only if state was updated.
+ function updateCancelledState(
+ LibOrder.Order memory order,
+ uint8 orderStatus,
+ bytes32 orderHash
+ )
+ internal
+ returns (bool stateUpdated);
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol
new file mode 100644
index 000000000..7dd608cf2
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol
@@ -0,0 +1,65 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../libs/LibOrder.sol";
+import "../libs/LibFillResults.sol";
+import "./MExchangeCore.sol";
+import "../interfaces/IMatchOrders.sol";
+
+contract MMatchOrders is
+ IMatchOrders
+{
+
+ /// @dev Validates context for matchOrders. Succeeds or throws.
+ /// @param leftOrder First order to match.
+ /// @param rightOrder Second order to match.
+ function assertValidMatch(
+ LibOrder.Order memory leftOrder,
+ LibOrder.Order memory rightOrder
+ )
+ internal;
+
+ /// @dev Validates matched fill results. Succeeds or throws.
+ /// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
+ function assertValidMatchResults(LibFillResults.MatchedFillResults memory matchedFillResults)
+ internal;
+
+ /// @dev Calculates fill amounts for the matched orders.
+ /// Each order is filled at their respective price point. However, the calculations are
+ /// carried out as though the orders are both being filled at the right order's price point.
+ /// The profit made by the leftOrder order goes to the taker (who matched the two orders).
+ /// @param leftOrder First order to match.
+ /// @param rightOrder Second order to match.
+ /// @param leftOrderStatus Order status of left order.
+ /// @param rightOrderStatus Order status of right order.
+ /// @param leftOrderFilledAmount Amount of left order already filled.
+ /// @param rightOrderFilledAmount Amount of right order already filled.
+ /// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
+ function calculateMatchedFillResults(
+ LibOrder.Order memory leftOrder,
+ LibOrder.Order memory rightOrder,
+ uint8 leftOrderStatus,
+ uint8 rightOrderStatus,
+ uint256 leftOrderFilledAmount,
+ uint256 rightOrderFilledAmount
+ )
+ internal
+ returns (LibFillResults.MatchedFillResults memory matchedFillResults);
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol
new file mode 100644
index 000000000..50b62e79f
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol
@@ -0,0 +1,50 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "../libs/LibOrder.sol";
+import "./MMatchOrders.sol";
+import "../libs/LibFillResults.sol";
+
+contract MSettlement {
+
+ /// @dev Settles an order by transferring assets between counterparties.
+ /// @param order Order struct containing order specifications.
+ /// @param takerAddress Address selling takerAsset and buying makerAsset.
+ /// @param fillResults Amounts to be filled and fees paid by maker and taker.
+ function settleOrder(
+ LibOrder.Order memory order,
+ address takerAddress,
+ LibFillResults.FillResults memory fillResults
+ )
+ internal;
+
+ /// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient.
+ /// @param leftOrder First matched order.
+ /// @param rightOrder Second matched order.
+ /// @param takerAddress Address that matched the orders. The taker receives the spread between orders as profit.
+ /// @param matchedFillResults Struct holding amounts to transfer between makers, taker, and fee recipients.
+ function settleMatchedOrders(
+ LibOrder.Order memory leftOrder,
+ LibOrder.Order memory rightOrder,
+ address takerAddress,
+ LibFillResults.MatchedFillResults memory matchedFillResults
+ )
+ internal;
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSignatureValidator.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSignatureValidator.sol
new file mode 100644
index 000000000..3658e7c6f
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSignatureValidator.sol
@@ -0,0 +1,50 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "../interfaces/ISignatureValidator.sol";
+
+contract MSignatureValidator is
+ ISignatureValidator
+{
+ // Allowed signature types.
+ enum SignatureType {
+ Illegal, // Default value
+ Invalid,
+ Caller,
+ Ecrecover,
+ EIP712,
+ Trezor,
+ Contract,
+ PreSigned
+ }
+
+ /// @dev Verifies that a signature is valid.
+ /// @param hash Message hash that is signed.
+ /// @param signer Address of signer.
+ /// @param signature Proof of signing.
+ /// @return Validity of order signature.
+ function isValidSignature(
+ bytes32 hash,
+ address signer,
+ bytes memory signature)
+ internal
+ view
+ returns (bool isValid);
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MTransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MTransactions.sol
new file mode 100644
index 000000000..e2f89de01
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MTransactions.sol
@@ -0,0 +1,35 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+pragma solidity ^0.4.24;
+
+import "../interfaces/ITransactions.sol";
+
+contract MTransactions is
+ ITransactions
+{
+
+ /// @dev The current function will be called in the context of this address (either 0x transaction signer or `msg.sender`).
+ /// If calling a fill function, this address will represent the taker.
+ /// If calling a cancel function, this address will represent the maker.
+ /// @return Signer of 0x transaction if entry point is `executeTransaction`.
+ /// `msg.sender` if entry point is any other function.
+ function getCurrentContextAddress()
+ internal
+ view
+ returns (address);
+}
diff --git a/packages/contracts/src/contracts/current/test/DummyERC20Token/DummyERC20Token.sol b/packages/contracts/src/contracts/current/test/DummyERC20Token/DummyERC20Token.sol
new file mode 100644
index 000000000..0c7b18c0c
--- /dev/null
+++ b/packages/contracts/src/contracts/current/test/DummyERC20Token/DummyERC20Token.sol
@@ -0,0 +1,56 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../Mintable/Mintable.sol";
+import "../../utils/Ownable/Ownable.sol";
+
+contract DummyERC20Token is Mintable, Ownable {
+ string public name;
+ string public symbol;
+ uint256 public decimals;
+
+ constructor (
+ string _name,
+ string _symbol,
+ uint256 _decimals,
+ uint256 _totalSupply)
+ public
+ {
+ name = _name;
+ symbol = _symbol;
+ decimals = _decimals;
+ totalSupply = _totalSupply;
+ balances[msg.sender] = _totalSupply;
+ }
+
+ function setBalance(address _target, uint256 _value)
+ public
+ onlyOwner
+ {
+ uint256 currBalance = balanceOf(_target);
+ if (_value < currBalance) {
+ totalSupply = safeSub(totalSupply, safeSub(currBalance, _value));
+ } else {
+ totalSupply = safeAdd(totalSupply, safeSub(_value, currBalance));
+ }
+ balances[_target] = _value;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol b/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol
new file mode 100644
index 000000000..369a2950d
--- /dev/null
+++ b/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol
@@ -0,0 +1,58 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../../tokens/ERC721Token/ERC721Token.sol";
+import "../../utils/Ownable/Ownable.sol";
+
+contract DummyERC721Token is
+ Ownable,
+ ERC721Token
+{
+
+ /**
+ * @dev Constructor passes its arguments to the base ERC721Token constructor
+ * @param name of token
+ * @param symbol of token
+ */
+ constructor (
+ string name,
+ string symbol)
+ public
+ ERC721Token(name, symbol)
+ {}
+
+ /**
+ * @dev Function to mint a new token
+ * @dev Reverts if the given token ID already exists
+ * @param to address the beneficiary that will own the minted token
+ * @param tokenId uint256 ID of the token to be minted by the msg.sender
+ */
+ function mint(address to, uint256 tokenId)
+ public
+ onlyOwner
+ {
+ require(
+ !exists(tokenId),
+ "Token with tokenId already exists."
+ );
+ _mint(to, tokenId);
+ }
+}
diff --git a/packages/contracts/src/contracts/current/test/DummyToken/DummyToken.sol b/packages/contracts/src/contracts/current/test/DummyToken/DummyToken.sol
deleted file mode 100644
index ab04f4d16..000000000
--- a/packages/contracts/src/contracts/current/test/DummyToken/DummyToken.sol
+++ /dev/null
@@ -1,37 +0,0 @@
-pragma solidity ^0.4.18;
-
-import { Mintable } from "../Mintable/Mintable.sol";
-import { Ownable } from "../../utils/Ownable/Ownable.sol";
-
-contract DummyToken is Mintable, Ownable {
- string public name;
- string public symbol;
- uint public decimals;
-
- function DummyToken(
- string _name,
- string _symbol,
- uint _decimals,
- uint _totalSupply)
- public
- {
- name = _name;
- symbol = _symbol;
- decimals = _decimals;
- totalSupply = _totalSupply;
- balances[msg.sender] = _totalSupply;
- }
-
- function setBalance(address _target, uint _value)
- public
- onlyOwner
- {
- uint currBalance = balanceOf(_target);
- if (_value < currBalance) {
- totalSupply = safeSub(totalSupply, safeSub(currBalance, _value));
- } else {
- totalSupply = safeAdd(totalSupply, safeSub(_value, currBalance));
- }
- balances[_target] = _value;
- }
-}
diff --git a/packages/contracts/src/contracts/current/test/MaliciousToken/MaliciousToken.sol b/packages/contracts/src/contracts/current/test/MaliciousToken/MaliciousToken.sol
deleted file mode 100644
index 9e502616c..000000000
--- a/packages/contracts/src/contracts/current/test/MaliciousToken/MaliciousToken.sol
+++ /dev/null
@@ -1,31 +0,0 @@
-pragma solidity ^0.4.18;
-
-import { ERC20Token } from "../../tokens/ERC20Token/ERC20Token.sol";
-
-contract MaliciousToken is ERC20Token {
- uint8 stateToUpdate = 1; // Not null so that change only requires 5000 gas
-
- function updateState()
- internal
- {
- stateToUpdate++;
- }
-
- function balanceOf(address _owner)
- public
- constant
- returns (uint)
- {
- updateState();
- return balances[_owner];
- }
-
- function allowance(address _owner, address _spender)
- public
- constant
- returns (uint)
- {
- updateState();
- return allowed[_owner][_spender];
- }
-}
diff --git a/packages/contracts/src/contracts/current/test/Mintable/Mintable.sol b/packages/contracts/src/contracts/current/test/Mintable/Mintable.sol
index cf7ee35a5..a91bfee9e 100644
--- a/packages/contracts/src/contracts/current/test/Mintable/Mintable.sol
+++ b/packages/contracts/src/contracts/current/test/Mintable/Mintable.sol
@@ -1,17 +1,39 @@
-pragma solidity ^0.4.18;
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
-import { UnlimitedAllowanceToken } from "../../tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol";
-import { SafeMath } from "../../utils/SafeMath/SafeMath.sol";
+import "../../tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol";
+import "../../utils/SafeMath/SafeMath.sol";
/*
* Mintable
* Base contract that creates a mintable UnlimitedAllowanceToken
*/
contract Mintable is UnlimitedAllowanceToken, SafeMath {
- function mint(uint _value)
+ function mint(uint256 _value)
public
{
- require(_value <= 100000000000000000000);
+ require(
+ _value <= 100000000000000000000,
+ "Minting more than 100000000000000000000 is not allowed."
+ );
balances[msg.sender] = safeAdd(_value, balances[msg.sender]);
totalSupply = safeAdd(totalSupply, _value);
}
diff --git a/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol
new file mode 100644
index 000000000..11ca0617d
--- /dev/null
+++ b/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol
@@ -0,0 +1,34 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../../protocol/Exchange/MixinAssetProxyDispatcher.sol";
+
+contract TestAssetProxyDispatcher is MixinAssetProxyDispatcher {
+ function publicDispatchTransferFrom(
+ bytes memory assetMetadata,
+ address from,
+ address to,
+ uint256 amount)
+ public
+ {
+ dispatchTransferFrom(assetMetadata, from, to, amount);
+ }
+}
diff --git a/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol b/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol
new file mode 100644
index 000000000..ac4602933
--- /dev/null
+++ b/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol
@@ -0,0 +1,133 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../../utils/LibBytes/LibBytes.sol";
+
+contract TestLibBytes is
+ LibBytes
+{
+
+ /// @dev Tests equality of two byte arrays.
+ /// @param lhs First byte array to compare.
+ /// @param rhs Second byte array to compare.
+ /// @return True if arrays are the same. False otherwise.
+ function publicAreBytesEqual(bytes memory lhs, bytes memory rhs)
+ public
+ pure
+ returns (bool equal)
+ {
+ equal = areBytesEqual(lhs, rhs);
+ return equal;
+ }
+
+ /// @dev Reads an address from a position in a byte array.
+ /// @param b Byte array containing an address.
+ /// @param index Index in byte array of address.
+ /// @return address from byte array.
+ function publicReadAddress(
+ bytes memory b,
+ uint256 index)
+ public
+ pure
+ returns (address result)
+ {
+ result = readAddress(b, index);
+ return result;
+ }
+
+ /// @dev Writes an address into a specific position in a byte array.
+ /// @param b Byte array to insert address into.
+ /// @param index Index in byte array of address.
+ /// @param input Address to put into byte array.
+ function publicWriteAddress(
+ bytes memory b,
+ uint256 index,
+ address input)
+ public
+ pure
+ returns (bytes memory)
+ {
+ writeAddress(b, index, input);
+ return b;
+ }
+
+ /// @dev Reads a bytes32 value from a position in a byte array.
+ /// @param b Byte array containing a bytes32 value.
+ /// @param index Index in byte array of bytes32 value.
+ /// @return bytes32 value from byte array.
+ function publicReadBytes32(
+ bytes memory b,
+ uint256 index)
+ public
+ pure
+ returns (bytes32 result)
+ {
+ result = readBytes32(b, index);
+ return result;
+ }
+
+ /// @dev Writes a bytes32 into a specific position in a byte array.
+ /// @param b Byte array to insert <input> into.
+ /// @param index Index in byte array of <input>.
+ /// @param input bytes32 to put into byte array.
+ function publicWriteBytes32(
+ bytes memory b,
+ uint256 index,
+ bytes32 input)
+ public
+ pure
+ returns (bytes memory)
+ {
+ writeBytes32(b, index, input);
+ return b;
+ }
+
+ /// @dev Reads a uint256 value from a position in a byte array.
+ /// @param b Byte array containing a uint256 value.
+ /// @param index Index in byte array of uint256 value.
+ /// @return uint256 value from byte array.
+ function publicReadUint256(
+ bytes memory b,
+ uint256 index)
+ public
+ pure
+ returns (uint256 result)
+ {
+ result = readUint256(b, index);
+ return result;
+ }
+
+ /// @dev Writes a uint256 into a specific position in a byte array.
+ /// @param b Byte array to insert <input> into.
+ /// @param index Index in byte array of <input>.
+ /// @param input uint256 to put into byte array.
+ function publicWriteUint256(
+ bytes memory b,
+ uint256 index,
+ uint256 input)
+ public
+ pure
+ returns (bytes memory)
+ {
+ writeUint256(b, index, input);
+ return b;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol b/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol
new file mode 100644
index 000000000..b8fc90af1
--- /dev/null
+++ b/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol
@@ -0,0 +1,80 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../../protocol/Exchange/libs/LibMath.sol";
+import "../../protocol/Exchange/libs/LibOrder.sol";
+import "../../protocol/Exchange/libs/LibFillResults.sol";
+
+contract TestLibs is
+ LibMath,
+ LibOrder,
+ LibFillResults
+{
+ function publicGetPartialAmount(
+ uint256 numerator,
+ uint256 denominator,
+ uint256 target)
+ public
+ pure
+ returns (uint256 partialAmount)
+ {
+ partialAmount = getPartialAmount(
+ numerator,
+ denominator,
+ target
+ );
+ return partialAmount;
+ }
+
+ function publicIsRoundingError(
+ uint256 numerator,
+ uint256 denominator,
+ uint256 target)
+ public
+ pure
+ returns (bool isError)
+ {
+ isError = isRoundingError(
+ numerator,
+ denominator,
+ target
+ );
+ return isError;
+ }
+
+ function publicGetOrderHash(Order memory order)
+ public
+ view
+ returns (bytes32 orderHash)
+ {
+ orderHash = getOrderHash(order);
+ return orderHash;
+ }
+
+ function publicAddFillResults(FillResults memory totalFillResults, FillResults memory singleFillResults)
+ public
+ pure
+ returns (FillResults memory)
+ {
+ addFillResults(totalFillResults, singleFillResults);
+ return totalFillResults;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/test/TestSignatureValidator/TestSignatureValidator.sol b/packages/contracts/src/contracts/current/test/TestSignatureValidator/TestSignatureValidator.sol
new file mode 100644
index 000000000..15d9ca189
--- /dev/null
+++ b/packages/contracts/src/contracts/current/test/TestSignatureValidator/TestSignatureValidator.sol
@@ -0,0 +1,41 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../../protocol/Exchange/MixinSignatureValidator.sol";
+
+contract TestSignatureValidator is MixinSignatureValidator {
+
+ function publicIsValidSignature(
+ bytes32 hash,
+ address signer,
+ bytes memory signature)
+ public
+ view
+ returns (bool isValid)
+ {
+ isValid = isValidSignature(
+ hash,
+ signer,
+ signature
+ );
+ return isValid;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol b/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol
index 0e5b87aa4..f0bcdafef 100644
--- a/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol
+++ b/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol
@@ -1,45 +1,90 @@
-pragma solidity ^0.4.18;
+/*
-import { Token } from "../Token/Token.sol";
+ Copyright 2018 ZeroEx Intl.
-contract ERC20Token is Token {
+ 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
- function transfer(address _to, uint _value)
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./IERC20Token.sol";
+
+contract ERC20Token is IERC20Token {
+
+ string constant INSUFFICIENT_BALANCE = "Insufficient balance to complete transfer.";
+ string constant INSUFFICIENT_ALLOWANCE = "Insufficient allowance to complete transfer.";
+ string constant OVERFLOW = "Transfer would result in an overflow.";
+
+ mapping (address => uint256) balances;
+ mapping (address => mapping (address => uint256)) allowed;
+
+ uint256 public totalSupply;
+
+ function transfer(address _to, uint256 _value)
public
returns (bool)
{
- require(balances[msg.sender] >= _value && balances[_to] + _value >= balances[_to]);
+ require(
+ balances[msg.sender] >= _value,
+ INSUFFICIENT_BALANCE
+ );
+ require(
+ balances[_to] + _value >= balances[_to],
+ OVERFLOW
+ );
balances[msg.sender] -= _value;
balances[_to] += _value;
- Transfer(msg.sender, _to, _value);
+ emit Transfer(msg.sender, _to, _value);
return true;
}
- function transferFrom(address _from, address _to, uint _value)
+ function transferFrom(address _from, address _to, uint256 _value)
public
returns (bool)
{
- require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value >= balances[_to]);
+ require(
+ balances[_from] >= _value,
+ INSUFFICIENT_BALANCE
+ );
+ require(
+ allowed[_from][msg.sender] >= _value,
+ INSUFFICIENT_ALLOWANCE
+ );
+ require(
+ balances[_to] + _value >= balances[_to],
+ OVERFLOW
+ );
balances[_to] += _value;
balances[_from] -= _value;
allowed[_from][msg.sender] -= _value;
- Transfer(_from, _to, _value);
+ emit Transfer(_from, _to, _value);
return true;
}
- function approve(address _spender, uint _value)
+ function approve(address _spender, uint256 _value)
public
returns (bool)
{
allowed[msg.sender][_spender] = _value;
- Approval(msg.sender, _spender, _value);
+ emit Approval(msg.sender, _spender, _value);
return true;
}
function balanceOf(address _owner)
- public
- view
- returns (uint)
+ public view
+ returns (uint256)
{
return balances[_owner];
}
@@ -47,12 +92,9 @@ contract ERC20Token is Token {
function allowance(address _owner, address _spender)
public
view
- returns (uint)
+ returns (uint256)
{
return allowed[_owner][_spender];
}
-
- mapping (address => uint) balances;
- mapping (address => mapping (address => uint)) allowed;
- uint public totalSupply;
}
+
diff --git a/packages/contracts/src/contracts/current/tokens/ERC20Token/IERC20Token.sol b/packages/contracts/src/contracts/current/tokens/ERC20Token/IERC20Token.sol
new file mode 100644
index 000000000..eb879b6a8
--- /dev/null
+++ b/packages/contracts/src/contracts/current/tokens/ERC20Token/IERC20Token.sol
@@ -0,0 +1,73 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+contract IERC20Token {
+
+ /// @notice send `value` token to `to` from `msg.sender`
+ /// @param _to The address of the recipient
+ /// @param _value The amount of token to be transferred
+ /// @return Whether the transfer was successful or not
+ function transfer(address _to, uint256 _value)
+ public
+ returns (bool);
+
+ /// @notice send `value` token to `to` from `from` on the condition it is approved by `from`
+ /// @param _from The address of the sender
+ /// @param _to The address of the recipient
+ /// @param _value The amount of token to be transferred
+ /// @return Whether the transfer was successful or not
+ function transferFrom(address _from, address _to, uint256 _value)
+ public
+ returns (bool);
+
+ /// @notice `msg.sender` approves `_spender` to spend `_value` tokens
+ /// @param _spender The address of the account able to transfer the tokens
+ /// @param _value The amount of wei to be approved for transfer
+ /// @return Whether the approval was successful or not
+ function approve(address _spender, uint256 _value)
+ public
+ returns (bool);
+
+ /// @param _owner The address from which the balance will be retrieved
+ /// @return The balance
+ function balanceOf(address _owner)
+ public view
+ returns (uint256);
+
+ /// @param _owner The address of the account owning tokens
+ /// @param _spender The address of the account able to transfer the tokens
+ /// @return Amount of remaining tokens allowed to spent
+ function allowance(address _owner, address _spender)
+ public view
+ returns (uint256);
+
+ event Transfer(
+ address indexed _from,
+ address indexed _to,
+ uint256 _value
+ );
+
+ event Approval(
+ address indexed _owner,
+ address indexed _spender,
+ uint256 _value
+ );
+}
diff --git a/packages/contracts/src/contracts/current/tokens/ERC721Token/ERC721Token.sol b/packages/contracts/src/contracts/current/tokens/ERC721Token/ERC721Token.sol
new file mode 100644
index 000000000..41ba149e3
--- /dev/null
+++ b/packages/contracts/src/contracts/current/tokens/ERC721Token/ERC721Token.sol
@@ -0,0 +1,406 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2016 Smart Contract Solutions, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+pragma solidity ^0.4.24;
+
+import "./IERC721Token.sol";
+import "./IERC721Receiver.sol";
+import "../../utils/SafeMath/SafeMath.sol";
+
+/**
+ * @title ERC721 Non-Fungible Token Standard basic implementation
+ * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
+ * Modified from https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC721/ERC721BasicToken.sol
+ */
+contract ERC721Token is
+ IERC721Token,
+ SafeMath
+{
+ // Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`
+ // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector`
+ bytes4 constant ERC721_RECEIVED = 0xf0b9e5ba;
+
+ // Mapping from token ID to owner
+ mapping (uint256 => address) internal tokenOwner;
+
+ // Mapping from token ID to approved address
+ mapping (uint256 => address) internal tokenApprovals;
+
+ // Mapping from owner to number of owned token
+ mapping (address => uint256) internal ownedTokensCount;
+
+ // Mapping from owner to operator approvals
+ mapping (address => mapping (address => bool)) internal operatorApprovals;
+
+ /**
+ * @dev Guarantees msg.sender is owner of the given token
+ * @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender
+ */
+ modifier onlyOwnerOf(uint256 _tokenId) {
+ require(ownerOf(_tokenId) == msg.sender);
+ _;
+ }
+
+ /**
+ * @dev Checks msg.sender can transfer a token, by being owner, approved, or operator
+ * @param _tokenId uint256 ID of the token to validate
+ */
+ modifier canTransfer(uint256 _tokenId) {
+ require(isApprovedOrOwner(msg.sender, _tokenId));
+ _;
+ }
+
+ function ERC721Token(
+ string _name,
+ string _symbol)
+ public
+ {
+ name_ = _name;
+ symbol_ = _symbol;
+ }
+
+ /**
+ * @dev Gets the token name
+ * @return string representing the token name
+ */
+ function name()
+ public
+ view
+ returns (string)
+ {
+ return name_;
+ }
+
+ /**
+ * @dev Gets the token symbol
+ * @return string representing the token symbol
+ */
+ function symbol()
+ public
+ view
+ returns (string)
+ {
+ return symbol_;
+ }
+
+ /**
+ * @dev Gets the balance of the specified address
+ * @param _owner address to query the balance of
+ * @return uint256 representing the amount owned by the passed address
+ */
+ function balanceOf(address _owner)
+ public
+ view
+ returns (uint256)
+ {
+ require(_owner != address(0));
+ return ownedTokensCount[_owner];
+ }
+
+ /**
+ * @dev Gets the owner of the specified token ID
+ * @param _tokenId uint256 ID of the token to query the owner of
+ * @return owner address currently marked as the owner of the given token ID
+ */
+ function ownerOf(uint256 _tokenId)
+ public
+ view
+ returns (address)
+ {
+ address owner = tokenOwner[_tokenId];
+ require(owner != address(0));
+ return owner;
+ }
+
+ /**
+ * @dev Returns whether the specified token exists
+ * @param _tokenId uint256 ID of the token to query the existance of
+ * @return whether the token exists
+ */
+ function exists(uint256 _tokenId)
+ public
+ view
+ returns (bool)
+ {
+ address owner = tokenOwner[_tokenId];
+ return owner != address(0);
+ }
+
+ /**
+ * @dev Approves another address to transfer the given token ID
+ * @dev The zero address indicates there is no approved address.
+ * @dev There can only be one approved address per token at a given time.
+ * @dev Can only be called by the token owner or an approved operator.
+ * @param _to address to be approved for the given token ID
+ * @param _tokenId uint256 ID of the token to be approved
+ */
+ function approve(address _to, uint256 _tokenId)
+ public
+ {
+ address owner = ownerOf(_tokenId);
+ require(_to != owner);
+ require(msg.sender == owner || isApprovedForAll(owner, msg.sender));
+
+ if (getApproved(_tokenId) != address(0) || _to != address(0)) {
+ tokenApprovals[_tokenId] = _to;
+ emit Approval(owner, _to, _tokenId);
+ }
+ }
+
+ /**
+ * @dev Gets the approved address for a token ID, or zero if no address set
+ * @param _tokenId uint256 ID of the token to query the approval of
+ * @return address currently approved for a the given token ID
+ */
+ function getApproved(uint256 _tokenId)
+ public
+ view
+ returns (address)
+ {
+ return tokenApprovals[_tokenId];
+ }
+
+ /**
+ * @dev Sets or unsets the approval of a given operator
+ * @dev An operator is allowed to transfer all tokens of the sender on their behalf
+ * @param _to operator address to set the approval
+ * @param _approved representing the status of the approval to be set
+ */
+ function setApprovalForAll(address _to, bool _approved)
+ public
+ {
+ require(_to != msg.sender);
+ operatorApprovals[msg.sender][_to] = _approved;
+ emit ApprovalForAll(msg.sender, _to, _approved);
+ }
+
+ /**
+ * @dev Tells whether an operator is approved by a given owner
+ * @param _owner owner address which you want to query the approval of
+ * @param _operator operator address which you want to query the approval of
+ * @return bool whether the given operator is approved by the given owner
+ */
+ function isApprovedForAll(address _owner, address _operator)
+ public
+ view
+ returns (bool)
+ {
+ return operatorApprovals[_owner][_operator];
+ }
+
+ /**
+ * @dev Transfers the ownership of a given token ID to another address
+ * @dev Usage of this method is discouraged, use `safeTransferFrom` whenever possible
+ * @dev Requires the msg sender to be the owner, approved, or operator
+ * @param _from current owner of the token
+ * @param _to address to receive the ownership of the given token ID
+ * @param _tokenId uint256 ID of the token to be transferred
+ */
+ function transferFrom(address _from, address _to, uint256 _tokenId)
+ public
+ canTransfer(_tokenId)
+ {
+ require(_from != address(0));
+ require(_to != address(0));
+
+ clearApproval(_from, _tokenId);
+ removeTokenFrom(_from, _tokenId);
+ addTokenTo(_to, _tokenId);
+
+ emit Transfer(_from, _to, _tokenId);
+ }
+
+ /**
+ * @dev Safely transfers the ownership of a given token ID to another address
+ * @dev If the target address is a contract, it must implement `onERC721Received`,
+ * which is called upon a safe transfer, and return the magic value
+ * `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`; otherwise,
+ * the transfer is reverted.
+ * @dev Requires the msg sender to be the owner, approved, or operator
+ * @param _from current owner of the token
+ * @param _to address to receive the ownership of the given token ID
+ * @param _tokenId uint256 ID of the token to be transferred
+ */
+ function safeTransferFrom(
+ address _from,
+ address _to,
+ uint256 _tokenId)
+ public
+ canTransfer(_tokenId)
+ {
+ // solium-disable-next-line arg-overflow
+ safeTransferFrom(_from, _to, _tokenId, "");
+ }
+
+ /**
+ * @dev Safely transfers the ownership of a given token ID to another address
+ * @dev If the target address is a contract, it must implement `onERC721Received`,
+ * which is called upon a safe transfer, and return the magic value
+ * `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`; otherwise,
+ * the transfer is reverted.
+ * @dev Requires the msg sender to be the owner, approved, or operator
+ * @param _from current owner of the token
+ * @param _to address to receive the ownership of the given token ID
+ * @param _tokenId uint256 ID of the token to be transferred
+ * @param _data bytes data to send along with a safe transfer check
+ */
+ function safeTransferFrom(
+ address _from,
+ address _to,
+ uint256 _tokenId,
+ bytes _data)
+ public
+ canTransfer(_tokenId)
+ {
+ transferFrom(_from, _to, _tokenId);
+ // solium-disable-next-line arg-overflow
+ require(checkAndCallSafeTransfer(_from, _to, _tokenId, _data));
+ }
+
+ /**
+ * @dev Returns whether the given spender can transfer a given token ID
+ * @param _spender address of the spender to query
+ * @param _tokenId uint256 ID of the token to be transferred
+ * @return bool whether the msg.sender is approved for the given token ID,
+ * is an operator of the owner, or is the owner of the token
+ */
+ function isApprovedOrOwner(address _spender, uint256 _tokenId)
+ internal
+ view
+ returns (bool)
+ {
+ address owner = ownerOf(_tokenId);
+ return _spender == owner || getApproved(_tokenId) == _spender || isApprovedForAll(owner, _spender);
+ }
+
+ /**
+ * @dev Internal function to mint a new token
+ * @dev Reverts if the given token ID already exists
+ * @param _to The address that will own the minted token
+ * @param _tokenId uint256 ID of the token to be minted by the msg.sender
+ */
+ function _mint(address _to, uint256 _tokenId)
+ internal
+ {
+ require(_to != address(0));
+ addTokenTo(_to, _tokenId);
+ emit Transfer(address(0), _to, _tokenId);
+ }
+
+ /**
+ * @dev Internal function to burn a specific token
+ * @dev Reverts if the token does not exist
+ * @param _tokenId uint256 ID of the token being burned by the msg.sender
+ */
+ function _burn(address _owner, uint256 _tokenId)
+ internal
+ {
+ clearApproval(_owner, _tokenId);
+ removeTokenFrom(_owner, _tokenId);
+ emit Transfer(_owner, address(0), _tokenId);
+ }
+
+ /**
+ * @dev Internal function to clear current approval of a given token ID
+ * @dev Reverts if the given address is not indeed the owner of the token
+ * @param _owner owner of the token
+ * @param _tokenId uint256 ID of the token to be transferred
+ */
+ function clearApproval(address _owner, uint256 _tokenId)
+ internal
+ {
+ require(ownerOf(_tokenId) == _owner);
+ if (tokenApprovals[_tokenId] != address(0)) {
+ tokenApprovals[_tokenId] = address(0);
+ emit Approval(_owner, address(0), _tokenId);
+ }
+ }
+
+ /**
+ * @dev Internal function to add a token ID to the list of a given address
+ * @param _to address representing the new owner of the given token ID
+ * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address
+ */
+ function addTokenTo(address _to, uint256 _tokenId)
+ internal
+ {
+ require(tokenOwner[_tokenId] == address(0));
+ tokenOwner[_tokenId] = _to;
+ ownedTokensCount[_to] = safeAdd(ownedTokensCount[_to], 1);
+ }
+
+ /**
+ * @dev Internal function to remove a token ID from the list of a given address
+ * @param _from address representing the previous owner of the given token ID
+ * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address
+ */
+ function removeTokenFrom(address _from, uint256 _tokenId)
+ internal
+ {
+ require(ownerOf(_tokenId) == _from);
+ ownedTokensCount[_from] = safeSub(ownedTokensCount[_from], 1);
+ tokenOwner[_tokenId] = address(0);
+ }
+
+ /**
+ * @dev Internal function to invoke `onERC721Received` on a target address
+ * @dev The call is not executed if the target address is not a contract
+ * @param _from address representing the previous owner of the given token ID
+ * @param _to target address that will receive the tokens
+ * @param _tokenId uint256 ID of the token to be transferred
+ * @param _data bytes optional data to send along with the call
+ * @return whether the call correctly returned the expected magic value
+ */
+ function checkAndCallSafeTransfer(
+ address _from,
+ address _to,
+ uint256 _tokenId,
+ bytes _data)
+ internal
+ returns (bool)
+ {
+ if (!isContract(_to)) {
+ return true;
+ }
+ bytes4 retval = IERC721Receiver(_to).onERC721Received(_from, _tokenId, _data);
+ return (retval == ERC721_RECEIVED);
+ }
+
+ function isContract(address addr)
+ internal
+ view
+ returns (bool)
+ {
+ uint256 size;
+ // XXX Currently there is no better way to check if there is a contract in an address
+ // than to check the size of the code at that address.
+ // See https://ethereum.stackexchange.com/a/14016/36603
+ // for more details about how this works.
+ // TODO Check this again before the Serenity release, because all addresses will be
+ // contracts then.
+ assembly { size := extcodesize(addr) } // solium-disable-line security/no-inline-assembly
+ return size > 0;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Receiver.sol b/packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Receiver.sol
new file mode 100644
index 000000000..b0fff3c90
--- /dev/null
+++ b/packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Receiver.sol
@@ -0,0 +1,60 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2016 Smart Contract Solutions, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+pragma solidity ^0.4.24;
+
+/**
+ * @title ERC721 token receiver interface
+ * @dev Interface for any contract that wants to support safeTransfers
+ * rom ERC721 asset contracts.
+ * Modified from https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC721/ERC721Receiver.sol
+ */
+contract IERC721Receiver {
+ /**
+ * @dev Magic value to be returned upon successful reception of an NFT
+ * Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`,
+ * which can be also obtained as `ERC721Receiver(0).onERC721Received.selector`
+ */
+ bytes4 constant ERC721_RECEIVED = 0xf0b9e5ba;
+
+ /**
+ * @notice Handle the receipt of an NFT
+ * @dev The ERC721 smart contract calls this function on the recipient
+ * after a `safetransfer`. This function MAY throw to revert and reject the
+ * transfer. This function MUST use 50,000 gas or less. Return of other
+ * than the magic value MUST result in the transaction being reverted.
+ * Note: the contract address is always the message sender.
+ * @param _from The sending address
+ * @param _tokenId The NFT identifier which is being transfered
+ * @param _data Additional data with no specified format
+ * @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`
+ */
+ function onERC721Received(
+ address _from,
+ uint256 _tokenId,
+ bytes _data)
+ public
+ returns (bytes4);
+}
diff --git a/packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Token.sol b/packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Token.sol
new file mode 100644
index 000000000..345712d67
--- /dev/null
+++ b/packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Token.sol
@@ -0,0 +1,105 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2016 Smart Contract Solutions, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+pragma solidity ^0.4.24;
+
+/**
+ * @title ERC721 Non-Fungible Token Standard basic interface
+ * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
+ * Modified from https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC721/ERC721Basic.sol
+ */
+contract IERC721Token {
+ string internal name_;
+ string internal symbol_;
+
+ event Transfer(
+ address indexed _from,
+ address indexed _to,
+ uint256 _tokenId
+ );
+ event Approval(
+ address indexed _owner,
+ address indexed _approved,
+ uint256 _tokenId
+ );
+ event ApprovalForAll(
+ address indexed _owner,
+ address indexed _operator,
+ bool _approved
+ );
+
+ function name()
+ public
+ view
+ returns (string);
+ function symbol()
+ public
+ view
+ returns (string);
+
+ function balanceOf(address _owner)
+ public
+ view
+ returns (uint256 _balance);
+ function ownerOf(uint256 _tokenId)
+ public
+ view
+ returns (address _owner);
+ function exists(uint256 _tokenId)
+ public
+ view
+ returns (bool _exists);
+
+ function approve(address _to, uint256 _tokenId)
+ public;
+ function getApproved(uint256 _tokenId)
+ public
+ view
+ returns (address _operator);
+
+ function setApprovalForAll(address _operator, bool _approved)
+ public;
+ function isApprovedForAll(address _owner, address _operator)
+ public
+ view
+ returns (bool);
+
+ function transferFrom(
+ address _from,
+ address _to,
+ uint256 _tokenId)
+ public;
+ function safeTransferFrom(
+ address _from,
+ address _to,
+ uint256 _tokenId)
+ public;
+ function safeTransferFrom(
+ address _from,
+ address _to,
+ uint256 _tokenId,
+ bytes _data)
+ public;
+}
diff --git a/packages/contracts/src/contracts/current/tokens/Token/Token.sol b/packages/contracts/src/contracts/current/tokens/Token/Token.sol
deleted file mode 100644
index bf4e71dcd..000000000
--- a/packages/contracts/src/contracts/current/tokens/Token/Token.sol
+++ /dev/null
@@ -1,35 +0,0 @@
-pragma solidity ^0.4.18;
-
-contract Token {
-
- /// @notice send `_value` token to `_to` from `msg.sender`
- /// @param _to The address of the recipient
- /// @param _value The amount of token to be transferred
- /// @return Whether the transfer was successful or not
- function transfer(address _to, uint _value) public returns (bool) {}
-
- /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
- /// @param _from The address of the sender
- /// @param _to The address of the recipient
- /// @param _value The amount of token to be transferred
- /// @return Whether the transfer was successful or not
- function transferFrom(address _from, address _to, uint _value) public returns (bool) {}
-
- /// @notice `msg.sender` approves `_addr` to spend `_value` tokens
- /// @param _spender The address of the account able to transfer the tokens
- /// @param _value The amount of wei to be approved for transfer
- /// @return Whether the approval was successful or not
- function approve(address _spender, uint _value) public returns (bool) {}
-
- /// @param _owner The address from which the balance will be retrieved
- /// @return The balance
- function balanceOf(address _owner) public view returns (uint) {}
-
- /// @param _owner The address of the account owning tokens
- /// @param _spender The address of the account able to transfer the tokens
- /// @return Amount of remaining tokens allowed to spent
- function allowance(address _owner, address _spender) public view returns (uint) {}
-
- event Transfer(address indexed _from, address indexed _to, uint _value);
- event Approval(address indexed _owner, address indexed _spender, uint _value);
-}
diff --git a/packages/contracts/src/contracts/current/tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol b/packages/contracts/src/contracts/current/tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol
index 699f535d2..f62602ab3 100644
--- a/packages/contracts/src/contracts/current/tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol
+++ b/packages/contracts/src/contracts/current/tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol
@@ -1,6 +1,6 @@
/*
- Copyright 2017 ZeroEx Intl.
+ Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,31 +16,43 @@
*/
-pragma solidity ^0.4.18;
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
-import { ERC20Token } from "../ERC20Token/ERC20Token.sol";
+import "../ERC20Token/ERC20Token.sol";
contract UnlimitedAllowanceToken is ERC20Token {
- uint constant MAX_UINT = 2**256 - 1;
+ uint256 constant MAX_UINT = 2**256 - 1;
/// @dev ERC20 transferFrom, modified such that an allowance of MAX_UINT represents an unlimited allowance. See https://github.com/ethereum/EIPs/issues/717
/// @param _from Address to transfer from.
/// @param _to Address to transfer to.
/// @param _value Amount to transfer.
/// @return Success of transfer.
- function transferFrom(address _from, address _to, uint _value)
+ function transferFrom(address _from, address _to, uint256 _value)
public
returns (bool)
{
- uint allowance = allowed[_from][msg.sender];
- require(balances[_from] >= _value && allowance >= _value && balances[_to] + _value >= balances[_to]);
+ uint256 allowance = allowed[_from][msg.sender];
+ require(
+ balances[_from] >= _value,
+ INSUFFICIENT_BALANCE
+ );
+ require(
+ allowance >= _value,
+ INSUFFICIENT_ALLOWANCE
+ );
+ require(
+ balances[_to] + _value >= balances[_to],
+ OVERFLOW
+ );
balances[_to] += _value;
balances[_from] -= _value;
if (allowance < MAX_UINT) {
allowed[_from][msg.sender] -= _value;
}
- Transfer(_from, _to, _value);
+ emit Transfer(_from, _to, _value);
return true;
}
}
diff --git a/packages/contracts/src/contracts/current/tokens/ZRXToken/ZRXToken.sol b/packages/contracts/src/contracts/current/tokens/ZRXToken/ZRXToken.sol
index 7f5e1f849..2d8e74f2c 100644
--- a/packages/contracts/src/contracts/current/tokens/ZRXToken/ZRXToken.sol
+++ b/packages/contracts/src/contracts/current/tokens/ZRXToken/ZRXToken.sol
@@ -1,6 +1,6 @@
/*
- Copyright 2017 ZeroEx Intl.
+ Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol
new file mode 100644
index 000000000..2c5d9e756
--- /dev/null
+++ b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol
@@ -0,0 +1,206 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+contract LibBytes {
+
+ // Revert reasons
+ string constant GTE_20_LENGTH_REQUIRED = "Length must be greater than or equal to 20.";
+ string constant GTE_32_LENGTH_REQUIRED = "Length must be greater than or equal to 32.";
+
+ /// @dev Tests equality of two byte arrays.
+ /// @param lhs First byte array to compare.
+ /// @param rhs Second byte array to compare.
+ /// @return True if arrays are the same. False otherwise.
+ function areBytesEqual(bytes memory lhs, bytes memory rhs)
+ internal
+ pure
+ returns (bool equal)
+ {
+ assembly {
+ // Get the number of words occupied by <lhs>
+ let lenFullWords := div(add(mload(lhs), 0x1F), 0x20)
+
+ // Add 1 to the number of words, to account for the length field
+ lenFullWords := add(lenFullWords, 0x1)
+
+ // Test equality word-by-word.
+ // Terminates early if there is a mismatch.
+ for {let i := 0} lt(i, lenFullWords) {i := add(i, 1)} {
+ let lhsWord := mload(add(lhs, mul(i, 0x20)))
+ let rhsWord := mload(add(rhs, mul(i, 0x20)))
+ equal := eq(lhsWord, rhsWord)
+ if eq(equal, 0) {
+ // Break
+ i := lenFullWords
+ }
+ }
+ }
+
+ return equal;
+ }
+
+ /// @dev Reads an address from a position in a byte array.
+ /// @param b Byte array containing an address.
+ /// @param index Index in byte array of address.
+ /// @return address from byte array.
+ function readAddress(
+ bytes memory b,
+ uint256 index)
+ internal
+ pure
+ returns (address result)
+ {
+ require(
+ b.length >= index + 20, // 20 is length of address
+ GTE_20_LENGTH_REQUIRED
+ );
+
+ // Add offset to index:
+ // 1. Arrays are prefixed by 32-byte length parameter (add 32 to index)
+ // 2. Account for size difference between address length and 32-byte storage word (subtract 12 from index)
+ index += 20;
+
+ // Read address from array memory
+ assembly {
+ // 1. Add index to address of bytes array
+ // 2. Load 32-byte word from memory
+ // 3. Apply 20-byte mask to obtain address
+ result := and(mload(add(b, index)), 0xffffffffffffffffffffffffffffffffffffffff)
+ }
+ return result;
+ }
+
+ /// @dev Writes an address into a specific position in a byte array.
+ /// @param b Byte array to insert address into.
+ /// @param index Index in byte array of address.
+ /// @param input Address to put into byte array.
+ function writeAddress(
+ bytes memory b,
+ uint256 index,
+ address input)
+ internal
+ pure
+ {
+ require(
+ b.length >= index + 20, // 20 is length of address
+ GTE_20_LENGTH_REQUIRED
+ );
+
+ // Add offset to index:
+ // 1. Arrays are prefixed by 32-byte length parameter (add 32 to index)
+ // 2. Account for size difference between address length and 32-byte storage word (subtract 12 from index)
+ index += 20;
+
+ // Store address into array memory
+ assembly {
+ // The address occupies 20 bytes and mstore stores 32 bytes.
+ // First fetch the 32-byte word where we'll be storing the address, then
+ // apply a mask so we have only the bytes in the word that the address will not occupy.
+ // Then combine these bytes with the address and store the 32 bytes back to memory with mstore.
+
+ // 1. Add index to address of bytes array
+ // 2. Load 32-byte word from memory
+ // 3. Apply 12-byte mask to obtain extra bytes occupying word of memory where we'll store the address
+ let neighbors := and(mload(add(b, index)), 0xffffffffffffffffffffffff0000000000000000000000000000000000000000)
+
+ // Store the neighbors and address into memory
+ mstore(add(b, index), xor(input, neighbors))
+ }
+ }
+
+ /// @dev Reads a bytes32 value from a position in a byte array.
+ /// @param b Byte array containing a bytes32 value.
+ /// @param index Index in byte array of bytes32 value.
+ /// @return bytes32 value from byte array.
+ function readBytes32(
+ bytes memory b,
+ uint256 index)
+ internal
+ pure
+ returns (bytes32 result)
+ {
+ require(
+ b.length >= index + 32,
+ GTE_32_LENGTH_REQUIRED
+ );
+
+ // Arrays are prefixed by a 256 bit length parameter
+ index += 32;
+
+ // Read the bytes32 from array memory
+ assembly {
+ result := mload(add(b, index))
+ }
+ return result;
+ }
+
+ /// @dev Writes a bytes32 into a specific position in a byte array.
+ /// @param b Byte array to insert <input> into.
+ /// @param index Index in byte array of <input>.
+ /// @param input bytes32 to put into byte array.
+ function writeBytes32(
+ bytes memory b,
+ uint256 index,
+ bytes32 input)
+ internal
+ pure
+ {
+ require(
+ b.length >= index + 32,
+ GTE_32_LENGTH_REQUIRED
+ );
+
+ // Arrays are prefixed by a 256 bit length parameter
+ index += 32;
+
+ // Read the bytes32 from array memory
+ assembly {
+ mstore(add(b, index), input)
+ }
+ }
+
+ /// @dev Reads a uint256 value from a position in a byte array.
+ /// @param b Byte array containing a uint256 value.
+ /// @param index Index in byte array of uint256 value.
+ /// @return uint256 value from byte array.
+ function readUint256(
+ bytes memory b,
+ uint256 index)
+ internal
+ pure
+ returns (uint256 result)
+ {
+ return uint256(readBytes32(b, index));
+ }
+
+ /// @dev Writes a uint256 into a specific position in a byte array.
+ /// @param b Byte array to insert <input> into.
+ /// @param index Index in byte array of <input>.
+ /// @param input uint256 to put into byte array.
+ function writeUint256(
+ bytes memory b,
+ uint256 index,
+ uint256 input)
+ internal
+ pure
+ {
+ writeBytes32(b, index, bytes32(input));
+ }
+}
diff --git a/packages/contracts/src/contracts/current/utils/Ownable/IOwnable.sol b/packages/contracts/src/contracts/current/utils/Ownable/IOwnable.sol
new file mode 100644
index 000000000..e77680903
--- /dev/null
+++ b/packages/contracts/src/contracts/current/utils/Ownable/IOwnable.sol
@@ -0,0 +1,14 @@
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+/*
+ * Ownable
+ *
+ * Base contract with an owner.
+ * Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
+ */
+
+contract IOwnable {
+ function transferOwnership(address newOwner)
+ public;
+}
diff --git a/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol b/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol
index 9b3d6b9cf..296c6c856 100644
--- a/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol
+++ b/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol
@@ -1,4 +1,5 @@
-pragma solidity ^0.4.18;
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
/*
* Ownable
@@ -7,17 +8,22 @@ pragma solidity ^0.4.18;
* Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
*/
-contract Ownable {
+import "./IOwnable.sol";
+
+contract Ownable is IOwnable {
address public owner;
- function Ownable()
+ constructor ()
public
{
owner = msg.sender;
}
modifier onlyOwner() {
- require(msg.sender == owner);
+ require(
+ msg.sender == owner,
+ "Only contract owner is allowed to call this method."
+ );
_;
}
diff --git a/packages/contracts/src/contracts/current/utils/SafeMath/SafeMath.sol b/packages/contracts/src/contracts/current/utils/SafeMath/SafeMath.sol
index 955a9e379..e137f6ca5 100644
--- a/packages/contracts/src/contracts/current/utils/SafeMath/SafeMath.sol
+++ b/packages/contracts/src/contracts/current/utils/SafeMath/SafeMath.sol
@@ -1,4 +1,5 @@
-pragma solidity ^0.4.18;
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
contract SafeMath {
function safeMul(uint a, uint b)
diff --git a/packages/contracts/src/contracts/current/tutorials/Arbitrage/Arbitrage.sol b/packages/contracts/src/contracts/previous/Arbitrage/Arbitrage.sol
index a9f3c22e6..5054afc2f 100644
--- a/packages/contracts/src/contracts/current/tutorials/Arbitrage/Arbitrage.sol
+++ b/packages/contracts/src/contracts/previous/Arbitrage/Arbitrage.sol
@@ -1,9 +1,9 @@
pragma solidity ^0.4.19;
-import { Exchange } from "../../protocol/Exchange/Exchange.sol";
+import { IExchange_v1 as Exchange } from "../Exchange/IExchange_v1.sol";
import { EtherDelta } from "../EtherDelta/EtherDelta.sol";
-import { Ownable } from "../../utils/Ownable/Ownable.sol";
-import { Token } from "../../tokens/Token/Token.sol";
+import { Ownable_v1 as Ownable } from "../Ownable/Ownable_v1.sol";
+import { IToken_v1 as Token } from "../Token/IToken_v1.sol";
/// @title Arbitrage - Facilitates atomic arbitrage of ERC20 tokens between EtherDelta and 0x Exchange contract.
/// @author Leonid Logvinov - <leo@0xProject.com>
diff --git a/packages/contracts/src/contracts/current/tutorials/EtherDelta/AccountLevels.sol b/packages/contracts/src/contracts/previous/EtherDelta/AccountLevels.sol
index 8d7a930d3..8d7a930d3 100644
--- a/packages/contracts/src/contracts/current/tutorials/EtherDelta/AccountLevels.sol
+++ b/packages/contracts/src/contracts/previous/EtherDelta/AccountLevels.sol
diff --git a/packages/contracts/src/contracts/current/tutorials/EtherDelta/EtherDelta.sol b/packages/contracts/src/contracts/previous/EtherDelta/EtherDelta.sol
index 49847ab48..fe599ca0a 100644
--- a/packages/contracts/src/contracts/current/tutorials/EtherDelta/EtherDelta.sol
+++ b/packages/contracts/src/contracts/previous/EtherDelta/EtherDelta.sol
@@ -1,8 +1,8 @@
pragma solidity ^0.4.19;
-import { SafeMath } from "../../utils/SafeMath/SafeMath.sol";
+import { SafeMath } from "../SafeMath/SafeMath_v1.sol";
import { AccountLevels } from "./AccountLevels.sol";
-import { Token } from "../../tokens/Token/Token.sol";
+import { Token } from "../Token/Token_v1.sol";
contract EtherDelta is SafeMath {
address public admin; //the admin address
diff --git a/packages/contracts/src/contracts/previous/Exchange/Exchange_v1.sol b/packages/contracts/src/contracts/previous/Exchange/Exchange_v1.sol
new file mode 100644
index 000000000..3f8e7368d
--- /dev/null
+++ b/packages/contracts/src/contracts/previous/Exchange/Exchange_v1.sol
@@ -0,0 +1,602 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.14;
+
+import { TokenTransferProxy_v1 as TokenTransferProxy } from "../TokenTransferProxy/TokenTransferProxy_v1.sol";
+import { Token_v1 as Token } from "../Token/Token_v1.sol";
+import { SafeMath_v1 as SafeMath } from "../SafeMath/SafeMath_v1.sol";
+
+/// @title Exchange - Facilitates exchange of ERC20 tokens.
+/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
+contract Exchange_v1 is SafeMath {
+
+ // Error Codes
+ enum Errors {
+ ORDER_EXPIRED, // Order has already expired
+ ORDER_FULLY_FILLED_OR_CANCELLED, // Order has already been fully filled or cancelled
+ ROUNDING_ERROR_TOO_LARGE, // Rounding error too large
+ INSUFFICIENT_BALANCE_OR_ALLOWANCE // Insufficient balance or allowance for token transfer
+ }
+
+ string constant public VERSION = "1.0.0";
+ uint16 constant public EXTERNAL_QUERY_GAS_LIMIT = 4999; // Changes to state require at least 5000 gas
+
+ address public ZRX_TOKEN_CONTRACT;
+ address public TOKEN_TRANSFER_PROXY_CONTRACT;
+
+ // Mappings of orderHash => amounts of takerTokenAmount filled or cancelled.
+ mapping (bytes32 => uint) public filled;
+ mapping (bytes32 => uint) public cancelled;
+
+ event LogFill(
+ address indexed maker,
+ address taker,
+ address indexed feeRecipient,
+ address makerToken,
+ address takerToken,
+ uint filledMakerTokenAmount,
+ uint filledTakerTokenAmount,
+ uint paidMakerFee,
+ uint paidTakerFee,
+ bytes32 indexed tokens, // keccak256(makerToken, takerToken), allows subscribing to a token pair
+ bytes32 orderHash
+ );
+
+ event LogCancel(
+ address indexed maker,
+ address indexed feeRecipient,
+ address makerToken,
+ address takerToken,
+ uint cancelledMakerTokenAmount,
+ uint cancelledTakerTokenAmount,
+ bytes32 indexed tokens,
+ bytes32 orderHash
+ );
+
+ event LogError(uint8 indexed errorId, bytes32 indexed orderHash);
+
+ struct Order {
+ address maker;
+ address taker;
+ address makerToken;
+ address takerToken;
+ address feeRecipient;
+ uint makerTokenAmount;
+ uint takerTokenAmount;
+ uint makerFee;
+ uint takerFee;
+ uint expirationTimestampInSec;
+ bytes32 orderHash;
+ }
+
+ function Exchange_v1(address _zrxToken, address _tokenTransferProxy) {
+ ZRX_TOKEN_CONTRACT = _zrxToken;
+ TOKEN_TRANSFER_PROXY_CONTRACT = _tokenTransferProxy;
+ }
+
+ /*
+ * Core exchange functions
+ */
+
+ /// @dev Fills the input order.
+ /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
+ /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
+ /// @param fillTakerTokenAmount Desired amount of takerToken to fill.
+ /// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfer will fail before attempting.
+ /// @param v ECDSA signature parameter v.
+ /// @param r ECDSA signature parameters r.
+ /// @param s ECDSA signature parameters s.
+ /// @return Total amount of takerToken filled in trade.
+ function fillOrder(
+ address[5] orderAddresses,
+ uint[6] orderValues,
+ uint fillTakerTokenAmount,
+ bool shouldThrowOnInsufficientBalanceOrAllowance,
+ uint8 v,
+ bytes32 r,
+ bytes32 s)
+ public
+ returns (uint filledTakerTokenAmount)
+ {
+ Order memory order = Order({
+ maker: orderAddresses[0],
+ taker: orderAddresses[1],
+ makerToken: orderAddresses[2],
+ takerToken: orderAddresses[3],
+ feeRecipient: orderAddresses[4],
+ makerTokenAmount: orderValues[0],
+ takerTokenAmount: orderValues[1],
+ makerFee: orderValues[2],
+ takerFee: orderValues[3],
+ expirationTimestampInSec: orderValues[4],
+ orderHash: getOrderHash(orderAddresses, orderValues)
+ });
+
+ require(order.taker == address(0) || order.taker == msg.sender);
+ require(order.makerTokenAmount > 0 && order.takerTokenAmount > 0 && fillTakerTokenAmount > 0);
+ require(isValidSignature(
+ order.maker,
+ order.orderHash,
+ v,
+ r,
+ s
+ ));
+
+ if (block.timestamp >= order.expirationTimestampInSec) {
+ LogError(uint8(Errors.ORDER_EXPIRED), order.orderHash);
+ return 0;
+ }
+
+ uint remainingTakerTokenAmount = safeSub(order.takerTokenAmount, getUnavailableTakerTokenAmount(order.orderHash));
+ filledTakerTokenAmount = min256(fillTakerTokenAmount, remainingTakerTokenAmount);
+ if (filledTakerTokenAmount == 0) {
+ LogError(uint8(Errors.ORDER_FULLY_FILLED_OR_CANCELLED), order.orderHash);
+ return 0;
+ }
+
+ if (isRoundingError(filledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount)) {
+ LogError(uint8(Errors.ROUNDING_ERROR_TOO_LARGE), order.orderHash);
+ return 0;
+ }
+
+ if (!shouldThrowOnInsufficientBalanceOrAllowance && !isTransferable(order, filledTakerTokenAmount)) {
+ LogError(uint8(Errors.INSUFFICIENT_BALANCE_OR_ALLOWANCE), order.orderHash);
+ return 0;
+ }
+
+ uint filledMakerTokenAmount = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount);
+ uint paidMakerFee;
+ uint paidTakerFee;
+ filled[order.orderHash] = safeAdd(filled[order.orderHash], filledTakerTokenAmount);
+ require(transferViaTokenTransferProxy(
+ order.makerToken,
+ order.maker,
+ msg.sender,
+ filledMakerTokenAmount
+ ));
+ require(transferViaTokenTransferProxy(
+ order.takerToken,
+ msg.sender,
+ order.maker,
+ filledTakerTokenAmount
+ ));
+ if (order.feeRecipient != address(0)) {
+ if (order.makerFee > 0) {
+ paidMakerFee = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.makerFee);
+ require(transferViaTokenTransferProxy(
+ ZRX_TOKEN_CONTRACT,
+ order.maker,
+ order.feeRecipient,
+ paidMakerFee
+ ));
+ }
+ if (order.takerFee > 0) {
+ paidTakerFee = getPartialAmount(filledTakerTokenAmount, order.takerTokenAmount, order.takerFee);
+ require(transferViaTokenTransferProxy(
+ ZRX_TOKEN_CONTRACT,
+ msg.sender,
+ order.feeRecipient,
+ paidTakerFee
+ ));
+ }
+ }
+
+ LogFill(
+ order.maker,
+ msg.sender,
+ order.feeRecipient,
+ order.makerToken,
+ order.takerToken,
+ filledMakerTokenAmount,
+ filledTakerTokenAmount,
+ paidMakerFee,
+ paidTakerFee,
+ keccak256(order.makerToken, order.takerToken),
+ order.orderHash
+ );
+ return filledTakerTokenAmount;
+ }
+
+ /// @dev Cancels the input order.
+ /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
+ /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
+ /// @param cancelTakerTokenAmount Desired amount of takerToken to cancel in order.
+ /// @return Amount of takerToken cancelled.
+ function cancelOrder(
+ address[5] orderAddresses,
+ uint[6] orderValues,
+ uint cancelTakerTokenAmount)
+ public
+ returns (uint)
+ {
+ Order memory order = Order({
+ maker: orderAddresses[0],
+ taker: orderAddresses[1],
+ makerToken: orderAddresses[2],
+ takerToken: orderAddresses[3],
+ feeRecipient: orderAddresses[4],
+ makerTokenAmount: orderValues[0],
+ takerTokenAmount: orderValues[1],
+ makerFee: orderValues[2],
+ takerFee: orderValues[3],
+ expirationTimestampInSec: orderValues[4],
+ orderHash: getOrderHash(orderAddresses, orderValues)
+ });
+
+ require(order.maker == msg.sender);
+ require(order.makerTokenAmount > 0 && order.takerTokenAmount > 0 && cancelTakerTokenAmount > 0);
+
+ if (block.timestamp >= order.expirationTimestampInSec) {
+ LogError(uint8(Errors.ORDER_EXPIRED), order.orderHash);
+ return 0;
+ }
+
+ uint remainingTakerTokenAmount = safeSub(order.takerTokenAmount, getUnavailableTakerTokenAmount(order.orderHash));
+ uint cancelledTakerTokenAmount = min256(cancelTakerTokenAmount, remainingTakerTokenAmount);
+ if (cancelledTakerTokenAmount == 0) {
+ LogError(uint8(Errors.ORDER_FULLY_FILLED_OR_CANCELLED), order.orderHash);
+ return 0;
+ }
+
+ cancelled[order.orderHash] = safeAdd(cancelled[order.orderHash], cancelledTakerTokenAmount);
+
+ LogCancel(
+ order.maker,
+ order.feeRecipient,
+ order.makerToken,
+ order.takerToken,
+ getPartialAmount(cancelledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount),
+ cancelledTakerTokenAmount,
+ keccak256(order.makerToken, order.takerToken),
+ order.orderHash
+ );
+ return cancelledTakerTokenAmount;
+ }
+
+ /*
+ * Wrapper functions
+ */
+
+ /// @dev Fills an order with specified parameters and ECDSA signature, throws if specified amount not filled entirely.
+ /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
+ /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
+ /// @param fillTakerTokenAmount Desired amount of takerToken to fill.
+ /// @param v ECDSA signature parameter v.
+ /// @param r ECDSA signature parameters r.
+ /// @param s ECDSA signature parameters s.
+ function fillOrKillOrder(
+ address[5] orderAddresses,
+ uint[6] orderValues,
+ uint fillTakerTokenAmount,
+ uint8 v,
+ bytes32 r,
+ bytes32 s)
+ public
+ {
+ require(fillOrder(
+ orderAddresses,
+ orderValues,
+ fillTakerTokenAmount,
+ false,
+ v,
+ r,
+ s
+ ) == fillTakerTokenAmount);
+ }
+
+ /// @dev Synchronously executes multiple fill orders in a single transaction.
+ /// @param orderAddresses Array of address arrays containing individual order addresses.
+ /// @param orderValues Array of uint arrays containing individual order values.
+ /// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
+ /// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting.
+ /// @param v Array ECDSA signature v parameters.
+ /// @param r Array of ECDSA signature r parameters.
+ /// @param s Array of ECDSA signature s parameters.
+ function batchFillOrders(
+ address[5][] orderAddresses,
+ uint[6][] orderValues,
+ uint[] fillTakerTokenAmounts,
+ bool shouldThrowOnInsufficientBalanceOrAllowance,
+ uint8[] v,
+ bytes32[] r,
+ bytes32[] s)
+ public
+ {
+ for (uint i = 0; i < orderAddresses.length; i++) {
+ fillOrder(
+ orderAddresses[i],
+ orderValues[i],
+ fillTakerTokenAmounts[i],
+ shouldThrowOnInsufficientBalanceOrAllowance,
+ v[i],
+ r[i],
+ s[i]
+ );
+ }
+ }
+
+ /// @dev Synchronously executes multiple fillOrKill orders in a single transaction.
+ /// @param orderAddresses Array of address arrays containing individual order addresses.
+ /// @param orderValues Array of uint arrays containing individual order values.
+ /// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
+ /// @param v Array ECDSA signature v parameters.
+ /// @param r Array of ECDSA signature r parameters.
+ /// @param s Array of ECDSA signature s parameters.
+ function batchFillOrKillOrders(
+ address[5][] orderAddresses,
+ uint[6][] orderValues,
+ uint[] fillTakerTokenAmounts,
+ uint8[] v,
+ bytes32[] r,
+ bytes32[] s)
+ public
+ {
+ for (uint i = 0; i < orderAddresses.length; i++) {
+ fillOrKillOrder(
+ orderAddresses[i],
+ orderValues[i],
+ fillTakerTokenAmounts[i],
+ v[i],
+ r[i],
+ s[i]
+ );
+ }
+ }
+
+ /// @dev Synchronously executes multiple fill orders in a single transaction until total fillTakerTokenAmount filled.
+ /// @param orderAddresses Array of address arrays containing individual order addresses.
+ /// @param orderValues Array of uint arrays containing individual order values.
+ /// @param fillTakerTokenAmount Desired total amount of takerToken to fill in orders.
+ /// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting.
+ /// @param v Array ECDSA signature v parameters.
+ /// @param r Array of ECDSA signature r parameters.
+ /// @param s Array of ECDSA signature s parameters.
+ /// @return Total amount of fillTakerTokenAmount filled in orders.
+ function fillOrdersUpTo(
+ address[5][] orderAddresses,
+ uint[6][] orderValues,
+ uint fillTakerTokenAmount,
+ bool shouldThrowOnInsufficientBalanceOrAllowance,
+ uint8[] v,
+ bytes32[] r,
+ bytes32[] s)
+ public
+ returns (uint)
+ {
+ uint filledTakerTokenAmount = 0;
+ for (uint i = 0; i < orderAddresses.length; i++) {
+ require(orderAddresses[i][3] == orderAddresses[0][3]); // takerToken must be the same for each order
+ filledTakerTokenAmount = safeAdd(filledTakerTokenAmount, fillOrder(
+ orderAddresses[i],
+ orderValues[i],
+ safeSub(fillTakerTokenAmount, filledTakerTokenAmount),
+ shouldThrowOnInsufficientBalanceOrAllowance,
+ v[i],
+ r[i],
+ s[i]
+ ));
+ if (filledTakerTokenAmount == fillTakerTokenAmount) break;
+ }
+ return filledTakerTokenAmount;
+ }
+
+ /// @dev Synchronously cancels multiple orders in a single transaction.
+ /// @param orderAddresses Array of address arrays containing individual order addresses.
+ /// @param orderValues Array of uint arrays containing individual order values.
+ /// @param cancelTakerTokenAmounts Array of desired amounts of takerToken to cancel in orders.
+ function batchCancelOrders(
+ address[5][] orderAddresses,
+ uint[6][] orderValues,
+ uint[] cancelTakerTokenAmounts)
+ public
+ {
+ for (uint i = 0; i < orderAddresses.length; i++) {
+ cancelOrder(
+ orderAddresses[i],
+ orderValues[i],
+ cancelTakerTokenAmounts[i]
+ );
+ }
+ }
+
+ /*
+ * Constant public functions
+ */
+
+ /// @dev Calculates Keccak-256 hash of order with specified parameters.
+ /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
+ /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
+ /// @return Keccak-256 hash of order.
+ function getOrderHash(address[5] orderAddresses, uint[6] orderValues)
+ public
+ constant
+ returns (bytes32)
+ {
+ return keccak256(
+ address(this),
+ orderAddresses[0], // maker
+ orderAddresses[1], // taker
+ orderAddresses[2], // makerToken
+ orderAddresses[3], // takerToken
+ orderAddresses[4], // feeRecipient
+ orderValues[0], // makerTokenAmount
+ orderValues[1], // takerTokenAmount
+ orderValues[2], // makerFee
+ orderValues[3], // takerFee
+ orderValues[4], // expirationTimestampInSec
+ orderValues[5] // salt
+ );
+ }
+
+ /// @dev Verifies that an order signature is valid.
+ /// @param signer address of signer.
+ /// @param hash Signed Keccak-256 hash.
+ /// @param v ECDSA signature parameter v.
+ /// @param r ECDSA signature parameters r.
+ /// @param s ECDSA signature parameters s.
+ /// @return Validity of order signature.
+ function isValidSignature(
+ address signer,
+ bytes32 hash,
+ uint8 v,
+ bytes32 r,
+ bytes32 s)
+ public
+ constant
+ returns (bool)
+ {
+ return signer == ecrecover(
+ keccak256("\x19Ethereum Signed Message:\n32", hash),
+ v,
+ r,
+ s
+ );
+ }
+
+ /// @dev Checks if rounding error > 0.1%.
+ /// @param numerator Numerator.
+ /// @param denominator Denominator.
+ /// @param target Value to multiply with numerator/denominator.
+ /// @return Rounding error is present.
+ function isRoundingError(uint numerator, uint denominator, uint target)
+ public
+ constant
+ returns (bool)
+ {
+ uint remainder = mulmod(target, numerator, denominator);
+ if (remainder == 0) return false; // No rounding error.
+
+ uint errPercentageTimes1000000 = safeDiv(
+ safeMul(remainder, 1000000),
+ safeMul(numerator, target)
+ );
+ return errPercentageTimes1000000 > 1000;
+ }
+
+ /// @dev Calculates partial value given a numerator and denominator.
+ /// @param numerator Numerator.
+ /// @param denominator Denominator.
+ /// @param target Value to calculate partial of.
+ /// @return Partial value of target.
+ function getPartialAmount(uint numerator, uint denominator, uint target)
+ public
+ constant
+ returns (uint)
+ {
+ return safeDiv(safeMul(numerator, target), denominator);
+ }
+
+ /// @dev Calculates the sum of values already filled and cancelled for a given order.
+ /// @param orderHash The Keccak-256 hash of the given order.
+ /// @return Sum of values already filled and cancelled.
+ function getUnavailableTakerTokenAmount(bytes32 orderHash)
+ public
+ constant
+ returns (uint)
+ {
+ return safeAdd(filled[orderHash], cancelled[orderHash]);
+ }
+
+
+ /*
+ * Internal functions
+ */
+
+ /// @dev Transfers a token using TokenTransferProxy transferFrom function.
+ /// @param token Address of token to transferFrom.
+ /// @param from Address transfering token.
+ /// @param to Address receiving token.
+ /// @param value Amount of token to transfer.
+ /// @return Success of token transfer.
+ function transferViaTokenTransferProxy(
+ address token,
+ address from,
+ address to,
+ uint value)
+ internal
+ returns (bool)
+ {
+ return TokenTransferProxy(TOKEN_TRANSFER_PROXY_CONTRACT).transferFrom(token, from, to, value);
+ }
+
+ /// @dev Checks if any order transfers will fail.
+ /// @param order Order struct of params that will be checked.
+ /// @param fillTakerTokenAmount Desired amount of takerToken to fill.
+ /// @return Predicted result of transfers.
+ function isTransferable(Order order, uint fillTakerTokenAmount)
+ internal
+ constant // The called token contracts may attempt to change state, but will not be able to due to gas limits on getBalance and getAllowance.
+ returns (bool)
+ {
+ address taker = msg.sender;
+ uint fillMakerTokenAmount = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount);
+
+ if (order.feeRecipient != address(0)) {
+ bool isMakerTokenZRX = order.makerToken == ZRX_TOKEN_CONTRACT;
+ bool isTakerTokenZRX = order.takerToken == ZRX_TOKEN_CONTRACT;
+ uint paidMakerFee = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.makerFee);
+ uint paidTakerFee = getPartialAmount(fillTakerTokenAmount, order.takerTokenAmount, order.takerFee);
+ uint requiredMakerZRX = isMakerTokenZRX ? safeAdd(fillMakerTokenAmount, paidMakerFee) : paidMakerFee;
+ uint requiredTakerZRX = isTakerTokenZRX ? safeAdd(fillTakerTokenAmount, paidTakerFee) : paidTakerFee;
+
+ if ( getBalance(ZRX_TOKEN_CONTRACT, order.maker) < requiredMakerZRX
+ || getAllowance(ZRX_TOKEN_CONTRACT, order.maker) < requiredMakerZRX
+ || getBalance(ZRX_TOKEN_CONTRACT, taker) < requiredTakerZRX
+ || getAllowance(ZRX_TOKEN_CONTRACT, taker) < requiredTakerZRX
+ ) return false;
+
+ if (!isMakerTokenZRX && ( getBalance(order.makerToken, order.maker) < fillMakerTokenAmount // Don't double check makerToken if ZRX
+ || getAllowance(order.makerToken, order.maker) < fillMakerTokenAmount)
+ ) return false;
+ if (!isTakerTokenZRX && ( getBalance(order.takerToken, taker) < fillTakerTokenAmount // Don't double check takerToken if ZRX
+ || getAllowance(order.takerToken, taker) < fillTakerTokenAmount)
+ ) return false;
+ } else if ( getBalance(order.makerToken, order.maker) < fillMakerTokenAmount
+ || getAllowance(order.makerToken, order.maker) < fillMakerTokenAmount
+ || getBalance(order.takerToken, taker) < fillTakerTokenAmount
+ || getAllowance(order.takerToken, taker) < fillTakerTokenAmount
+ ) return false;
+
+ return true;
+ }
+
+ /// @dev Get token balance of an address.
+ /// @param token Address of token.
+ /// @param owner Address of owner.
+ /// @return Token balance of owner.
+ function getBalance(address token, address owner)
+ internal
+ constant // The called token contract may attempt to change state, but will not be able to due to an added gas limit.
+ returns (uint)
+ {
+ return Token(token).balanceOf.gas(EXTERNAL_QUERY_GAS_LIMIT)(owner); // Limit gas to prevent reentrancy
+ }
+
+ /// @dev Get allowance of token given to TokenTransferProxy by an address.
+ /// @param token Address of token.
+ /// @param owner Address of owner.
+ /// @return Allowance of token given to TokenTransferProxy by owner.
+ function getAllowance(address token, address owner)
+ internal
+ constant // The called token contract may attempt to change state, but will not be able to due to an added gas limit.
+ returns (uint)
+ {
+ return Token(token).allowance.gas(EXTERNAL_QUERY_GAS_LIMIT)(owner, TOKEN_TRANSFER_PROXY_CONTRACT); // Limit gas to prevent reentrancy
+ }
+}
diff --git a/packages/contracts/src/contracts/previous/Exchange/IExchange_v1.sol b/packages/contracts/src/contracts/previous/Exchange/IExchange_v1.sol
new file mode 100644
index 000000000..ec4bce7b0
--- /dev/null
+++ b/packages/contracts/src/contracts/previous/Exchange/IExchange_v1.sol
@@ -0,0 +1,226 @@
+pragma solidity ^0.4.19;
+
+contract IExchange_v1 {
+
+ // Error Codes
+ enum Errors {
+ ORDER_EXPIRED, // Order has already expired
+ ORDER_FULLY_FILLED_OR_CANCELLED, // Order has already been fully filled or cancelled
+ ROUNDING_ERROR_TOO_LARGE, // Rounding error too large
+ INSUFFICIENT_BALANCE_OR_ALLOWANCE // Insufficient balance or allowance for token transfer
+ }
+
+ event LogError(uint8 indexed errorId, bytes32 indexed orderHash);
+
+ event LogFill(
+ address indexed maker,
+ address taker,
+ address indexed feeRecipient,
+ address makerToken,
+ address takerToken,
+ uint filledMakerTokenAmount,
+ uint filledTakerTokenAmount,
+ uint paidMakerFee,
+ uint paidTakerFee,
+ bytes32 indexed tokens, // keccak256(makerToken, takerToken), allows subscribing to a token pair
+ bytes32 orderHash
+ );
+
+ event LogCancel(
+ address indexed maker,
+ address indexed feeRecipient,
+ address makerToken,
+ address takerToken,
+ uint cancelledMakerTokenAmount,
+ uint cancelledTakerTokenAmount,
+ bytes32 indexed tokens,
+ bytes32 orderHash
+ );
+
+ function ZRX_TOKEN_CONTRACT()
+ public view
+ returns (address);
+
+ function TOKEN_TRANSFER_PROXY_CONTRACT()
+ public view
+ returns (address);
+
+ function EXTERNAL_QUERY_GAS_LIMIT()
+ public view
+ returns (uint16);
+
+ function VERSION()
+ public view
+ returns (string);
+
+ function filled(bytes32)
+ public view
+ returns (uint256);
+
+ function cancelled(bytes32)
+ public view
+ returns (uint256);
+
+ /// @dev Calculates the sum of values already filled and cancelled for a given order.
+ /// @param orderHash The Keccak-256 hash of the given order.
+ /// @return Sum of values already filled and cancelled.
+ function getUnavailableTakerTokenAmount(bytes32 orderHash)
+ public constant
+ returns (uint);
+
+ /// @dev Calculates partial value given a numerator and denominator.
+ /// @param numerator Numerator.
+ /// @param denominator Denominator.
+ /// @param target Value to calculate partial of.
+ /// @return Partial value of target.
+ function getPartialAmount(uint numerator, uint denominator, uint target)
+ public constant
+ returns (uint);
+
+ /// @dev Checks if rounding error > 0.1%.
+ /// @param numerator Numerator.
+ /// @param denominator Denominator.
+ /// @param target Value to multiply with numerator/denominator.
+ /// @return Rounding error is present.
+ function isRoundingError(uint numerator, uint denominator, uint target)
+ public constant
+ returns (bool);
+
+ /// @dev Calculates Keccak-256 hash of order with specified parameters.
+ /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
+ /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
+ /// @return Keccak-256 hash of order.
+ function getOrderHash(address[5] orderAddresses, uint[6] orderValues)
+ public
+ constant
+ returns (bytes32);
+
+ /// @dev Verifies that an order signature is valid.
+ /// @param signer address of signer.
+ /// @param hash Signed Keccak-256 hash.
+ /// @param v ECDSA signature parameter v.
+ /// @param r ECDSA signature parameters r.
+ /// @param s ECDSA signature parameters s.
+ /// @return Validity of order signature.
+ function isValidSignature(
+ address signer,
+ bytes32 hash,
+ uint8 v,
+ bytes32 r,
+ bytes32 s)
+ public constant
+ returns (bool);
+
+ /// @dev Fills the input order.
+ /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
+ /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
+ /// @param fillTakerTokenAmount Desired amount of takerToken to fill.
+ /// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfer will fail before attempting.
+ /// @param v ECDSA signature parameter v.
+ /// @param r ECDSA signature parameters r.
+ /// @param s ECDSA signature parameters s.
+ /// @return Total amount of takerToken filled in trade.
+ function fillOrder(
+ address[5] orderAddresses,
+ uint[6] orderValues,
+ uint fillTakerTokenAmount,
+ bool shouldThrowOnInsufficientBalanceOrAllowance,
+ uint8 v,
+ bytes32 r,
+ bytes32 s)
+ public
+ returns (uint filledTakerTokenAmount);
+
+ /// @dev Cancels the input order.
+ /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
+ /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
+ /// @param cancelTakerTokenAmount Desired amount of takerToken to cancel in order.
+ /// @return Amount of takerToken cancelled.
+ function cancelOrder(
+ address[5] orderAddresses,
+ uint[6] orderValues,
+ uint cancelTakerTokenAmount)
+ public
+ returns (uint);
+
+
+ /// @dev Fills an order with specified parameters and ECDSA signature, throws if specified amount not filled entirely.
+ /// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
+ /// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
+ /// @param fillTakerTokenAmount Desired amount of takerToken to fill.
+ /// @param v ECDSA signature parameter v.
+ /// @param r ECDSA signature parameters r.
+ /// @param s ECDSA signature parameters s.
+ function fillOrKillOrder(
+ address[5] orderAddresses,
+ uint[6] orderValues,
+ uint fillTakerTokenAmount,
+ uint8 v,
+ bytes32 r,
+ bytes32 s)
+ public;
+
+ /// @dev Synchronously executes multiple fill orders in a single transaction.
+ /// @param orderAddresses Array of address arrays containing individual order addresses.
+ /// @param orderValues Array of uint arrays containing individual order values.
+ /// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
+ /// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting.
+ /// @param v Array ECDSA signature v parameters.
+ /// @param r Array of ECDSA signature r parameters.
+ /// @param s Array of ECDSA signature s parameters.
+ function batchFillOrders(
+ address[5][] orderAddresses,
+ uint[6][] orderValues,
+ uint[] fillTakerTokenAmounts,
+ bool shouldThrowOnInsufficientBalanceOrAllowance,
+ uint8[] v,
+ bytes32[] r,
+ bytes32[] s)
+ public;
+
+ /// @dev Synchronously executes multiple fillOrKill orders in a single transaction.
+ /// @param orderAddresses Array of address arrays containing individual order addresses.
+ /// @param orderValues Array of uint arrays containing individual order values.
+ /// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
+ /// @param v Array ECDSA signature v parameters.
+ /// @param r Array of ECDSA signature r parameters.
+ /// @param s Array of ECDSA signature s parameters.
+ function batchFillOrKillOrders(
+ address[5][] orderAddresses,
+ uint[6][] orderValues,
+ uint[] fillTakerTokenAmounts,
+ uint8[] v,
+ bytes32[] r,
+ bytes32[] s)
+ public;
+
+ /// @dev Synchronously executes multiple fill orders in a single transaction until total fillTakerTokenAmount filled.
+ /// @param orderAddresses Array of address arrays containing individual order addresses.
+ /// @param orderValues Array of uint arrays containing individual order values.
+ /// @param fillTakerTokenAmount Desired total amount of takerToken to fill in orders.
+ /// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting.
+ /// @param v Array ECDSA signature v parameters.
+ /// @param r Array of ECDSA signature r parameters.
+ /// @param s Array of ECDSA signature s parameters.
+ /// @return Total amount of fillTakerTokenAmount filled in orders.
+ function fillOrdersUpTo(
+ address[5][] orderAddresses,
+ uint[6][] orderValues,
+ uint fillTakerTokenAmount,
+ bool shouldThrowOnInsufficientBalanceOrAllowance,
+ uint8[] v,
+ bytes32[] r,
+ bytes32[] s)
+ public
+ returns (uint);
+
+ /// @dev Synchronously cancels multiple orders in a single transaction.
+ /// @param orderAddresses Array of address arrays containing individual order addresses.
+ /// @param orderValues Array of uint arrays containing individual order values.
+ /// @param cancelTakerTokenAmounts Array of desired amounts of takerToken to cancel in orders.
+ function batchCancelOrders(
+ address[5][] orderAddresses,
+ uint[6][] orderValues,
+ uint[] cancelTakerTokenAmounts)
+ public;
+}
diff --git a/packages/contracts/src/contracts/previous/Ownable/IOwnable_v1.sol b/packages/contracts/src/contracts/previous/Ownable/IOwnable_v1.sol
new file mode 100644
index 000000000..7e22d544d
--- /dev/null
+++ b/packages/contracts/src/contracts/previous/Ownable/IOwnable_v1.sol
@@ -0,0 +1,18 @@
+pragma solidity ^0.4.19;
+
+/*
+ * Ownable
+ *
+ * Base contract with an owner.
+ * Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
+ */
+
+contract IOwnable_v1 {
+
+ function owner()
+ public view
+ returns (address);
+
+ function transferOwnership(address newOwner)
+ public;
+}
diff --git a/packages/contracts/src/contracts/previous/TokenRegistry/ITokenRegistery.sol b/packages/contracts/src/contracts/previous/TokenRegistry/ITokenRegistery.sol
new file mode 100644
index 000000000..b8bdaf3b9
--- /dev/null
+++ b/packages/contracts/src/contracts/previous/TokenRegistry/ITokenRegistery.sol
@@ -0,0 +1,195 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.21;
+
+import { IOwnable_v1 as IOwnable } from "../Ownable/IOwnable_v1.sol";
+
+/// @title Token Registry - Stores metadata associated with ERC20 tokens. See ERC22 https://github.com/ethereum/EIPs/issues/22
+/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
+contract ITokenRegistery is IOwnable {
+
+ event LogAddToken(
+ address indexed token,
+ string name,
+ string symbol,
+ uint8 decimals,
+ bytes ipfsHash,
+ bytes swarmHash
+ );
+
+ event LogRemoveToken(
+ address indexed token,
+ string name,
+ string symbol,
+ uint8 decimals,
+ bytes ipfsHash,
+ bytes swarmHash
+ );
+
+ event LogTokenNameChange(
+ address indexed token,
+ string oldName,
+ string newName
+ );
+
+ event LogTokenSymbolChange(
+ address indexed token,
+ string oldSymbol,
+ string newSymbol
+ );
+
+ event LogTokenIpfsHashChange(
+ address indexed token,
+ bytes oldIpfsHash,
+ bytes newIpfsHash
+ );
+
+ event LogTokenSwarmHashChange(
+ address indexed token,
+ bytes oldSwarmHash,
+ bytes newSwarmHash
+ );
+
+ function tokens(address tokenAddress)
+ public view
+ returns (
+ address token,
+ string name,
+ string symbol,
+ uint8 decimals,
+ bytes ipfsHash,
+ bytes swarmHash
+ );
+
+ function tokenAddresses(uint256 index)
+ public view
+ returns (address);
+
+
+ /// @dev Allows owner to add a new token to the registry.
+ /// @param _token Address of new token.
+ /// @param _name Name of new token.
+ /// @param _symbol Symbol for new token.
+ /// @param _decimals Number of decimals, divisibility of new token.
+ /// @param _ipfsHash IPFS hash of token icon.
+ /// @param _swarmHash Swarm hash of token icon.
+ function addToken(
+ address _token,
+ string _name,
+ string _symbol,
+ uint8 _decimals,
+ bytes _ipfsHash,
+ bytes _swarmHash)
+ public;
+
+ /// @dev Allows owner to remove an existing token from the registry.
+ /// @param _token Address of existing token.
+ function removeToken(address _token, uint _index)
+ public;
+
+ /// @dev Allows owner to modify an existing token's name.
+ /// @param _token Address of existing token.
+ /// @param _name New name.
+ function setTokenName(address _token, string _name)
+ public;
+
+ /// @dev Allows owner to modify an existing token's symbol.
+ /// @param _token Address of existing token.
+ /// @param _symbol New symbol.
+ function setTokenSymbol(address _token, string _symbol)
+ public;
+
+ /// @dev Allows owner to modify an existing token's IPFS hash.
+ /// @param _token Address of existing token.
+ /// @param _ipfsHash New IPFS hash.
+ function setTokenIpfsHash(address _token, bytes _ipfsHash)
+ public;
+
+ /// @dev Allows owner to modify an existing token's Swarm hash.
+ /// @param _token Address of existing token.
+ /// @param _swarmHash New Swarm hash.
+ function setTokenSwarmHash(address _token, bytes _swarmHash)
+ public;
+
+ /*
+ * Web3 call functions
+ */
+
+ /// @dev Provides a registered token's address when given the token symbol.
+ /// @param _symbol Symbol of registered token.
+ /// @return Token's address.
+ function getTokenAddressBySymbol(string _symbol)
+ public constant
+ returns (address);
+
+ /// @dev Provides a registered token's address when given the token name.
+ /// @param _name Name of registered token.
+ /// @return Token's address.
+ function getTokenAddressByName(string _name)
+ public constant
+ returns (address);
+
+ /// @dev Provides a registered token's metadata, looked up by address.
+ /// @param _token Address of registered token.
+ /// @return Token metadata.
+ function getTokenMetaData(address _token)
+ public constant
+ returns (
+ address, //tokenAddress
+ string, //name
+ string, //symbol
+ uint8, //decimals
+ bytes, //ipfsHash
+ bytes //swarmHash
+ );
+
+ /// @dev Provides a registered token's metadata, looked up by name.
+ /// @param _name Name of registered token.
+ /// @return Token metadata.
+ function getTokenByName(string _name)
+ public constant
+ returns (
+ address, //tokenAddress
+ string, //name
+ string, //symbol
+ uint8, //decimals
+ bytes, //ipfsHash
+ bytes //swarmHash
+ );
+
+ /// @dev Provides a registered token's metadata, looked up by symbol.
+ /// @param _symbol Symbol of registered token.
+ /// @return Token metadata.
+ function getTokenBySymbol(string _symbol)
+ public constant
+ returns (
+ address, //tokenAddress
+ string, //name
+ string, //symbol
+ uint8, //decimals
+ bytes, //ipfsHash
+ bytes //swarmHash
+ );
+
+ /// @dev Returns an array containing all token addresses.
+ /// @return Array of token addresses.
+ function getTokenAddresses()
+ public constant
+ returns (address[]);
+}
diff --git a/packages/contracts/src/contracts/current/protocol/TokenRegistry/TokenRegistry.sol b/packages/contracts/src/contracts/previous/TokenRegistry/TokenRegistry.sol
index 3bd2fbfaf..7417a10a3 100644
--- a/packages/contracts/src/contracts/current/protocol/TokenRegistry/TokenRegistry.sol
+++ b/packages/contracts/src/contracts/previous/TokenRegistry/TokenRegistry.sol
@@ -1,6 +1,6 @@
/*
- Copyright 2017 ZeroEx Intl.
+ Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@
pragma solidity ^0.4.11;
-import { Ownable_v1 as Ownable } from "../../../previous/Ownable/Ownable_v1.sol";
+import { Ownable_v1 as Ownable } from "../Ownable/Ownable_v1.sol";
/// @title Token Registry - Stores metadata associated with ERC20 tokens. See ERC22 https://github.com/ethereum/EIPs/issues/22
/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
diff --git a/packages/contracts/src/contracts/current/protocol/TokenTransferProxy/TokenTransferProxy.sol b/packages/contracts/src/contracts/previous/TokenTransferProxy/TokenTransferProxy_v1.sol
index 1ce949fa6..e3659d8ba 100644
--- a/packages/contracts/src/contracts/current/protocol/TokenTransferProxy/TokenTransferProxy.sol
+++ b/packages/contracts/src/contracts/previous/TokenTransferProxy/TokenTransferProxy_v1.sol
@@ -1,6 +1,6 @@
/*
- Copyright 2017 ZeroEx Intl.
+ Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -18,12 +18,12 @@
pragma solidity ^0.4.11;
-import { Token_v1 as Token } from "../../../previous/Token/Token_v1.sol";
-import { Ownable_v1 as Ownable } from "../../../previous/Ownable/Ownable_v1.sol";
+import { Token_v1 as Token } from "../Token/Token_v1.sol";
+import { Ownable_v1 as Ownable } from "../Ownable/Ownable_v1.sol";
/// @title TokenTransferProxy - Transfers tokens on behalf of contracts that have been approved via decentralized governance.
/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
-contract TokenTransferProxy is Ownable {
+contract TokenTransferProxy_v1 is Ownable {
/// @dev Only authorized addresses can invoke functions with this modifier.
modifier onlyAuthorized {
diff --git a/packages/contracts/src/contracts/previous/UnlimitedAllowanceToken/UnlimitedAllowanceToken_v1.sol b/packages/contracts/src/contracts/previous/UnlimitedAllowanceToken/UnlimitedAllowanceToken_v1.sol
index 6376f3f2c..46379c43d 100644
--- a/packages/contracts/src/contracts/previous/UnlimitedAllowanceToken/UnlimitedAllowanceToken_v1.sol
+++ b/packages/contracts/src/contracts/previous/UnlimitedAllowanceToken/UnlimitedAllowanceToken_v1.sol
@@ -1,6 +1,6 @@
/*
- Copyright 2017 ZeroEx Intl.
+ Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/contracts/src/utils/address_utils.ts b/packages/contracts/src/utils/address_utils.ts
new file mode 100644
index 000000000..01a7a6fd4
--- /dev/null
+++ b/packages/contracts/src/utils/address_utils.ts
@@ -0,0 +1,12 @@
+import { ZeroEx } from '0x.js';
+
+import { crypto } from './crypto';
+
+export const addressUtils = {
+ generatePseudoRandomAddress(): string {
+ const randomBigNum = ZeroEx.generatePseudoRandomSalt();
+ const randomBuff = crypto.solSHA3([randomBigNum]);
+ const randomAddress = `0x${randomBuff.slice(0, 20).toString('hex')}`;
+ return randomAddress;
+ },
+};
diff --git a/packages/contracts/src/utils/artifacts.ts b/packages/contracts/src/utils/artifacts.ts
new file mode 100644
index 000000000..caf5b94fd
--- /dev/null
+++ b/packages/contracts/src/utils/artifacts.ts
@@ -0,0 +1,37 @@
+import { ContractArtifact } from '@0xproject/sol-compiler';
+
+import * as DummyERC20Token from '../artifacts/DummyERC20Token.json';
+import * as DummyERC721Token from '../artifacts/DummyERC721Token.json';
+import * as ERC20Proxy from '../artifacts/ERC20Proxy.json';
+import * as ERC721Proxy from '../artifacts/ERC721Proxy.json';
+import * as Exchange from '../artifacts/Exchange.json';
+import * as MixinAuthorizable from '../artifacts/MixinAuthorizable.json';
+import * as MultiSigWallet from '../artifacts/MultiSigWallet.json';
+import * as MultiSigWalletWithTimeLock from '../artifacts/MultiSigWalletWithTimeLock.json';
+import * as MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress from '../artifacts/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.json';
+import * as TestAssetProxyDispatcher from '../artifacts/TestAssetProxyDispatcher.json';
+import * as TestLibBytes from '../artifacts/TestLibBytes.json';
+import * as TestLibs from '../artifacts/TestLibs.json';
+import * as TestSignatureValidator from '../artifacts/TestSignatureValidator.json';
+import * as TokenRegistry from '../artifacts/TokenRegistry.json';
+import * as EtherToken from '../artifacts/WETH9.json';
+import * as ZRX from '../artifacts/ZRXToken.json';
+
+export const artifacts = {
+ DummyERC20Token: (DummyERC20Token as any) as ContractArtifact,
+ DummyERC721Token: (DummyERC721Token as any) as ContractArtifact,
+ ERC20Proxy: (ERC20Proxy as any) as ContractArtifact,
+ ERC721Proxy: (ERC721Proxy as any) as ContractArtifact,
+ Exchange: (Exchange as any) as ContractArtifact,
+ EtherToken: (EtherToken as any) as ContractArtifact,
+ MixinAuthorizable: (MixinAuthorizable as any) as ContractArtifact,
+ MultiSigWallet: (MultiSigWallet as any) as ContractArtifact,
+ MultiSigWalletWithTimeLock: (MultiSigWalletWithTimeLock as any) as ContractArtifact,
+ MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress: (MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress as any) as ContractArtifact,
+ TestAssetProxyDispatcher: (TestAssetProxyDispatcher as any) as ContractArtifact,
+ TestLibBytes: (TestLibBytes as any) as ContractArtifact,
+ TestLibs: (TestLibs as any) as ContractArtifact,
+ TestSignatureValidator: (TestSignatureValidator as any) as ContractArtifact,
+ TokenRegistry: (TokenRegistry as any) as ContractArtifact,
+ ZRX: (ZRX as any) as ContractArtifact,
+};
diff --git a/packages/contracts/src/utils/asset_proxy_utils.ts b/packages/contracts/src/utils/asset_proxy_utils.ts
new file mode 100644
index 000000000..c042da5d0
--- /dev/null
+++ b/packages/contracts/src/utils/asset_proxy_utils.ts
@@ -0,0 +1,144 @@
+import { BigNumber } from '@0xproject/utils';
+import BN = require('bn.js');
+import ethUtil = require('ethereumjs-util');
+
+import { AssetProxyId, ERC20ProxyData, ERC721ProxyData, ProxyData } from './types';
+
+export const assetProxyUtils = {
+ encodeAssetProxyId(assetProxyId: AssetProxyId): Buffer {
+ return ethUtil.toBuffer(assetProxyId);
+ },
+ decodeAssetProxyId(encodedAssetProxyId: Buffer): AssetProxyId {
+ return ethUtil.bufferToInt(encodedAssetProxyId);
+ },
+ encodeAddress(address: string): Buffer {
+ if (!ethUtil.isValidAddress(address)) {
+ throw new Error(`Invalid Address: ${address}`);
+ }
+ const encodedAddress = ethUtil.toBuffer(address);
+ return encodedAddress;
+ },
+ decodeAddress(encodedAddress: Buffer): string {
+ const address = ethUtil.bufferToHex(encodedAddress);
+ if (!ethUtil.isValidAddress(address)) {
+ throw new Error(`Invalid Address: ${address}`);
+ }
+ return address;
+ },
+ encodeUint256(value: BigNumber): Buffer {
+ const formattedValue = new BN(value.toString(10));
+ const encodedValue = ethUtil.toBuffer(formattedValue);
+ const paddedValue = ethUtil.setLengthLeft(encodedValue, 32);
+ return paddedValue;
+ },
+ decodeUint256(encodedValue: Buffer): BigNumber {
+ const formattedValue = ethUtil.bufferToHex(encodedValue);
+ const value = new BigNumber(formattedValue, 16);
+ return value;
+ },
+ encodeERC20ProxyData(tokenAddress: string): string {
+ const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC20);
+ const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress);
+ const encodedMetadata = Buffer.concat([encodedAssetProxyId, encodedAddress]);
+ const encodedMetadataHex = ethUtil.bufferToHex(encodedMetadata);
+ return encodedMetadataHex;
+ },
+ decodeERC20ProxyData(proxyData: string): ERC20ProxyData {
+ const encodedProxyMetadata = ethUtil.toBuffer(proxyData);
+ if (encodedProxyMetadata.byteLength !== 21) {
+ throw new Error(
+ `Could not decode ERC20 Proxy Data. Expected length of encoded data to be 21. Got ${
+ encodedProxyMetadata.byteLength
+ }`,
+ );
+ }
+ const encodedAssetProxyId = encodedProxyMetadata.slice(0, 1);
+ const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId);
+ if (assetProxyId !== AssetProxyId.ERC20) {
+ throw new Error(
+ `Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be ERC20 (${
+ AssetProxyId.ERC20
+ }), but got ${assetProxyId}`,
+ );
+ }
+ const encodedTokenAddress = encodedProxyMetadata.slice(1, 21);
+ const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress);
+ const erc20ProxyData = {
+ assetProxyId,
+ tokenAddress,
+ };
+ return erc20ProxyData;
+ },
+ encodeERC721ProxyData(tokenAddress: string, tokenId: BigNumber): string {
+ const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC721);
+ const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress);
+ const encodedTokenId = assetProxyUtils.encodeUint256(tokenId);
+ const encodedMetadata = Buffer.concat([encodedAssetProxyId, encodedAddress, encodedTokenId]);
+ const encodedMetadataHex = ethUtil.bufferToHex(encodedMetadata);
+ return encodedMetadataHex;
+ },
+ decodeERC721ProxyData(proxyData: string): ERC721ProxyData {
+ const encodedProxyMetadata = ethUtil.toBuffer(proxyData);
+ if (encodedProxyMetadata.byteLength !== 53) {
+ throw new Error(
+ `Could not decode ERC20 Proxy Data. Expected length of encoded data to be 53. Got ${
+ encodedProxyMetadata.byteLength
+ }`,
+ );
+ }
+ const encodedAssetProxyId = encodedProxyMetadata.slice(0, 1);
+ const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId);
+ if (assetProxyId !== AssetProxyId.ERC721) {
+ throw new Error(
+ `Could not decode ERC721 Proxy Data. Expected Asset Proxy Id to be ERC721 (${
+ AssetProxyId.ERC721
+ }), but got ${assetProxyId}`,
+ );
+ }
+ const encodedTokenAddress = encodedProxyMetadata.slice(1, 21);
+ const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress);
+ const encodedTokenId = encodedProxyMetadata.slice(21, 53);
+ const tokenId = assetProxyUtils.decodeUint256(encodedTokenId);
+ const erc721ProxyData = {
+ assetProxyId,
+ tokenAddress,
+ tokenId,
+ };
+ return erc721ProxyData;
+ },
+ decodeProxyDataId(proxyData: string): AssetProxyId {
+ const encodedProxyMetadata = ethUtil.toBuffer(proxyData);
+ if (encodedProxyMetadata.byteLength < 1) {
+ throw new Error(
+ `Could not decode Proxy Data. Expected length of encoded data to be at least 1. Got ${
+ encodedProxyMetadata.byteLength
+ }`,
+ );
+ }
+ const encodedAssetProxyId = encodedProxyMetadata.slice(0, 1);
+ const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId);
+ return assetProxyId;
+ },
+ decodeProxyData(proxyData: string): ProxyData {
+ const assetProxyId = assetProxyUtils.decodeProxyDataId(proxyData);
+ switch (assetProxyId) {
+ case AssetProxyId.ERC20:
+ const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(proxyData);
+ const generalizedERC20ProxyData = {
+ assetProxyId,
+ tokenAddress: erc20ProxyData.tokenAddress,
+ };
+ return generalizedERC20ProxyData;
+ case AssetProxyId.ERC721:
+ const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(proxyData);
+ const generaliedERC721ProxyData = {
+ assetProxyId,
+ tokenAddress: erc721ProxyData.tokenAddress,
+ data: erc721ProxyData.tokenId,
+ };
+ return generaliedERC721ProxyData;
+ default:
+ throw new Error(`Unrecognized asset proxy id: ${assetProxyId}`);
+ }
+ },
+};
diff --git a/packages/contracts/test/utils/chai_setup.ts b/packages/contracts/src/utils/chai_setup.ts
index 1a8733093..1a8733093 100644
--- a/packages/contracts/test/utils/chai_setup.ts
+++ b/packages/contracts/src/utils/chai_setup.ts
diff --git a/packages/contracts/src/utils/constants.ts b/packages/contracts/src/utils/constants.ts
new file mode 100644
index 000000000..b876bf6b5
--- /dev/null
+++ b/packages/contracts/src/utils/constants.ts
@@ -0,0 +1,43 @@
+import { ZeroEx } from '0x.js';
+import { BigNumber } from '@0xproject/utils';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+const TESTRPC_PRIVATE_KEYS_STRINGS = [
+ '0xf2f48ee19680706196e2e339e5da3491186e0c4c5030670656b0e0164837257d',
+ '0x5d862464fe9303452126c8bc94274b8c5f9874cbd219789b3eb2128075a76f72',
+ '0xdf02719c4df8b9b8ac7f551fcb5d9ef48fa27eef7a66453879f4d8fdc6e78fb1',
+ '0xff12e391b79415e941a94de3bf3a9aee577aed0731e297d5cfa0b8a1e02fa1d0',
+ '0x752dd9cf65e68cfaba7d60225cbdbc1f4729dd5e5507def72815ed0d8abc6249',
+ '0xefb595a0178eb79a8df953f87c5148402a224cdf725e88c0146727c6aceadccd',
+ '0x83c6d2cc5ddcf9711a6d59b417dc20eb48afd58d45290099e5987e3d768f328f',
+ '0xbb2d3f7c9583780a7d3904a2f55d792707c345f21de1bacb2d389934d82796b2',
+ '0xb2fd4d29c1390b71b8795ae81196bfd60293adf99f9d32a0aff06288fcdac55f',
+ '0x23cb7121166b9a2f93ae0b7c05bde02eae50d64449b2cbb42bc84e9d38d6cc89',
+];
+
+export const constants = {
+ INVALID_OPCODE: 'invalid opcode',
+ REVERT: 'revert',
+ TESTRPC_NETWORK_ID: 50,
+ AWAIT_TRANSACTION_MINED_MS: 100,
+ MAX_ETHERTOKEN_WITHDRAW_GAS: 43000,
+ MAX_TOKEN_TRANSFERFROM_GAS: 80000,
+ MAX_TOKEN_APPROVE_GAS: 60000,
+ DUMMY_TOKEN_NAME: '',
+ DUMMY_TOKEN_SYMBOL: '',
+ DUMMY_TOKEN_DECIMALS: new BigNumber(18),
+ DUMMY_TOKEN_TOTAL_SUPPLY: new BigNumber(0),
+ NUM_DUMMY_ERC20_TO_DEPLOY: 3,
+ NUM_DUMMY_ERC721_TO_DEPLOY: 1,
+ NUM_ERC721_TOKENS_TO_MINT: 2,
+ TESTRPC_PRIVATE_KEYS: _.map(TESTRPC_PRIVATE_KEYS_STRINGS, privateKeyString => ethUtil.toBuffer(privateKeyString)),
+ INITIAL_ERC20_BALANCE: ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18),
+ INITIAL_ERC20_ALLOWANCE: ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18),
+ STATIC_ORDER_PARAMS: {
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
+ makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
+ takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
+ },
+};
diff --git a/packages/contracts/util/crypto.ts b/packages/contracts/src/utils/crypto.ts
index 3661b3afd..80c5f30a5 100644
--- a/packages/contracts/util/crypto.ts
+++ b/packages/contracts/src/utils/crypto.ts
@@ -31,6 +31,8 @@ export const crypto = {
argTypes.push('address');
} else if (_.isString(arg)) {
argTypes.push('string');
+ } else if (_.isBuffer(arg)) {
+ argTypes.push('bytes');
} else if (_.isBoolean(arg)) {
argTypes.push('bool');
} else {
diff --git a/packages/contracts/src/utils/erc20_wrapper.ts b/packages/contracts/src/utils/erc20_wrapper.ts
new file mode 100644
index 000000000..0303649a5
--- /dev/null
+++ b/packages/contracts/src/utils/erc20_wrapper.ts
@@ -0,0 +1,116 @@
+import { Provider } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { DummyERC20TokenContract } from '../contract_wrappers/generated/dummy_e_r_c20_token';
+import { ERC20ProxyContract } from '../contract_wrappers/generated/e_r_c20_proxy';
+
+import { artifacts } from './artifacts';
+import { constants } from './constants';
+import { ERC20BalancesByOwner } from './types';
+import { txDefaults } from './web3_wrapper';
+
+export class ERC20Wrapper {
+ private _tokenOwnerAddresses: string[];
+ private _contractOwnerAddress: string;
+ private _provider: Provider;
+ private _dummyTokenContracts?: DummyERC20TokenContract[];
+ private _proxyContract?: ERC20ProxyContract;
+ constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
+ this._provider = provider;
+ this._tokenOwnerAddresses = tokenOwnerAddresses;
+ this._contractOwnerAddress = contractOwnerAddress;
+ }
+ public async deployDummyTokensAsync(): Promise<DummyERC20TokenContract[]> {
+ this._dummyTokenContracts = await Promise.all(
+ _.times(constants.NUM_DUMMY_ERC20_TO_DEPLOY, async () =>
+ DummyERC20TokenContract.deployFrom0xArtifactAsync(
+ artifacts.DummyERC20Token,
+ this._provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ constants.DUMMY_TOKEN_DECIMALS,
+ constants.DUMMY_TOKEN_TOTAL_SUPPLY,
+ ),
+ ),
+ );
+ return this._dummyTokenContracts;
+ }
+ public async deployProxyAsync(): Promise<ERC20ProxyContract> {
+ this._proxyContract = await ERC20ProxyContract.deployFrom0xArtifactAsync(
+ artifacts.ERC20Proxy,
+ this._provider,
+ txDefaults,
+ );
+ return this._proxyContract;
+ }
+ public async setBalancesAndAllowancesAsync(): Promise<void> {
+ this._validateDummyTokenContractsExistOrThrow();
+ this._validateProxyContractExistsOrThrow();
+ const setBalancePromises: Array<Promise<string>> = [];
+ const setAllowancePromises: Array<Promise<string>> = [];
+ _.forEach(this._dummyTokenContracts, dummyTokenContract => {
+ _.forEach(this._tokenOwnerAddresses, tokenOwnerAddress => {
+ setBalancePromises.push(
+ dummyTokenContract.setBalance.sendTransactionAsync(
+ tokenOwnerAddress,
+ constants.INITIAL_ERC20_BALANCE,
+ { from: this._contractOwnerAddress },
+ ),
+ );
+ setAllowancePromises.push(
+ dummyTokenContract.approve.sendTransactionAsync(
+ (this._proxyContract as ERC20ProxyContract).address,
+ constants.INITIAL_ERC20_ALLOWANCE,
+ { from: tokenOwnerAddress },
+ ),
+ );
+ });
+ });
+ await Promise.all([...setBalancePromises, ...setAllowancePromises]);
+ }
+ public async getBalancesAsync(): Promise<ERC20BalancesByOwner> {
+ this._validateDummyTokenContractsExistOrThrow();
+ const balancesByOwner: ERC20BalancesByOwner = {};
+ const balancePromises: Array<Promise<BigNumber>> = [];
+ const balanceInfo: Array<{ tokenOwnerAddress: string; tokenAddress: string }> = [];
+ _.forEach(this._dummyTokenContracts, dummyTokenContract => {
+ _.forEach(this._tokenOwnerAddresses, tokenOwnerAddress => {
+ balancePromises.push(dummyTokenContract.balanceOf.callAsync(tokenOwnerAddress));
+ balanceInfo.push({
+ tokenOwnerAddress,
+ tokenAddress: dummyTokenContract.address,
+ });
+ });
+ });
+ const balances = await Promise.all(balancePromises);
+ _.forEach(balances, (balance, balanceIndex) => {
+ const tokenAddress = balanceInfo[balanceIndex].tokenAddress;
+ const tokenOwnerAddress = balanceInfo[balanceIndex].tokenOwnerAddress;
+ if (_.isUndefined(balancesByOwner[tokenOwnerAddress])) {
+ balancesByOwner[tokenOwnerAddress] = {};
+ }
+ const wrappedBalance = new BigNumber(balance);
+ balancesByOwner[tokenOwnerAddress][tokenAddress] = wrappedBalance;
+ });
+ return balancesByOwner;
+ }
+ public getTokenOwnerAddresses(): string[] {
+ return this._tokenOwnerAddresses;
+ }
+ public getTokenAddresses(): string[] {
+ const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address);
+ return tokenAddresses;
+ }
+ private _validateDummyTokenContractsExistOrThrow(): void {
+ if (_.isUndefined(this._dummyTokenContracts)) {
+ throw new Error('Dummy ERC20 tokens not yet deployed, please call "deployDummyTokensAsync"');
+ }
+ }
+ private _validateProxyContractExistsOrThrow(): void {
+ if (_.isUndefined(this._proxyContract)) {
+ throw new Error('ERC20 proxy contract not yet deployed, please call "deployProxyAsync"');
+ }
+ }
+}
diff --git a/packages/contracts/src/utils/erc721_wrapper.ts b/packages/contracts/src/utils/erc721_wrapper.ts
new file mode 100644
index 000000000..36988ba31
--- /dev/null
+++ b/packages/contracts/src/utils/erc721_wrapper.ts
@@ -0,0 +1,145 @@
+import { ZeroEx } from '0x.js';
+import { Provider } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { DummyERC721TokenContract } from '../contract_wrappers/generated/dummy_e_r_c721_token';
+import { ERC721ProxyContract } from '../contract_wrappers/generated/e_r_c721_proxy';
+
+import { artifacts } from './artifacts';
+import { constants } from './constants';
+import { ERC721TokenIdsByOwner } from './types';
+import { txDefaults } from './web3_wrapper';
+
+export class ERC721Wrapper {
+ private _tokenOwnerAddresses: string[];
+ private _contractOwnerAddress: string;
+ private _provider: Provider;
+ private _dummyTokenContracts?: DummyERC721TokenContract[];
+ private _proxyContract?: ERC721ProxyContract;
+ private _initialTokenIdsByOwner: ERC721TokenIdsByOwner = {};
+ constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
+ this._provider = provider;
+ this._tokenOwnerAddresses = tokenOwnerAddresses;
+ this._contractOwnerAddress = contractOwnerAddress;
+ }
+ public async deployDummyTokensAsync(): Promise<DummyERC721TokenContract[]> {
+ this._dummyTokenContracts = await Promise.all(
+ _.times(constants.NUM_DUMMY_ERC721_TO_DEPLOY, async () =>
+ DummyERC721TokenContract.deployFrom0xArtifactAsync(
+ artifacts.DummyERC721Token,
+ this._provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ ),
+ ),
+ );
+ return this._dummyTokenContracts;
+ }
+ public async deployProxyAsync(): Promise<ERC721ProxyContract> {
+ this._proxyContract = await ERC721ProxyContract.deployFrom0xArtifactAsync(
+ artifacts.ERC721Proxy,
+ this._provider,
+ txDefaults,
+ );
+ return this._proxyContract;
+ }
+ public async setBalancesAndAllowancesAsync(): Promise<void> {
+ this._validateDummyTokenContractsExistOrThrow();
+ this._validateProxyContractExistsOrThrow();
+ const setBalancePromises: Array<Promise<string>> = [];
+ const setAllowancePromises: Array<Promise<string>> = [];
+ this._initialTokenIdsByOwner = {};
+ _.forEach(this._dummyTokenContracts, dummyTokenContract => {
+ _.forEach(this._tokenOwnerAddresses, tokenOwnerAddress => {
+ _.forEach(_.range(constants.NUM_ERC721_TOKENS_TO_MINT), () => {
+ const tokenId = ZeroEx.generatePseudoRandomSalt();
+ setBalancePromises.push(
+ dummyTokenContract.mint.sendTransactionAsync(tokenOwnerAddress, tokenId, {
+ from: this._contractOwnerAddress,
+ }),
+ );
+ if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress])) {
+ this._initialTokenIdsByOwner[tokenOwnerAddress] = {
+ [dummyTokenContract.address]: [],
+ };
+ }
+ if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address])) {
+ this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address] = [];
+ }
+ this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address].push(tokenId);
+ });
+ const shouldApprove = true;
+ setAllowancePromises.push(
+ dummyTokenContract.setApprovalForAll.sendTransactionAsync(
+ (this._proxyContract as ERC721ProxyContract).address,
+ shouldApprove,
+ { from: tokenOwnerAddress },
+ ),
+ );
+ });
+ });
+ await Promise.all([...setBalancePromises, ...setAllowancePromises]);
+ }
+ public async getBalancesAsync(): Promise<ERC721TokenIdsByOwner> {
+ this._validateDummyTokenContractsExistOrThrow();
+ this._validateBalancesAndAllowancesSetOrThrow();
+ const tokenIdsByOwner: ERC721TokenIdsByOwner = {};
+ const tokenOwnerPromises: Array<Promise<string>> = [];
+ const tokenInfo: Array<{ tokenId: BigNumber; tokenAddress: string }> = [];
+ _.forEach(this._dummyTokenContracts, dummyTokenContract => {
+ _.forEach(this._tokenOwnerAddresses, tokenOwnerAddress => {
+ const initialTokenOwnerIds = this._initialTokenIdsByOwner[tokenOwnerAddress][
+ dummyTokenContract.address
+ ];
+ _.forEach(initialTokenOwnerIds, tokenId => {
+ tokenOwnerPromises.push(dummyTokenContract.ownerOf.callAsync(tokenId));
+ tokenInfo.push({
+ tokenId,
+ tokenAddress: dummyTokenContract.address,
+ });
+ });
+ });
+ });
+ const tokenOwnerAddresses = await Promise.all(tokenOwnerPromises);
+ _.forEach(tokenOwnerAddresses, (tokenOwnerAddress, ownerIndex) => {
+ const tokenAddress = tokenInfo[ownerIndex].tokenAddress;
+ const tokenId = tokenInfo[ownerIndex].tokenId;
+ if (_.isUndefined(tokenIdsByOwner[tokenOwnerAddress])) {
+ tokenIdsByOwner[tokenOwnerAddress] = {
+ [tokenAddress]: [],
+ };
+ }
+ if (_.isUndefined(tokenIdsByOwner[tokenOwnerAddress][tokenAddress])) {
+ tokenIdsByOwner[tokenOwnerAddress][tokenAddress] = [];
+ }
+ tokenIdsByOwner[tokenOwnerAddress][tokenAddress].push(tokenId);
+ });
+ return tokenIdsByOwner;
+ }
+ public getTokenOwnerAddresses(): string[] {
+ return this._tokenOwnerAddresses;
+ }
+ public getTokenAddresses(): string[] {
+ const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address);
+ return tokenAddresses;
+ }
+ private _validateDummyTokenContractsExistOrThrow(): void {
+ if (_.isUndefined(this._dummyTokenContracts)) {
+ throw new Error('Dummy ERC721 tokens not yet deployed, please call "deployDummyTokensAsync"');
+ }
+ }
+ private _validateProxyContractExistsOrThrow(): void {
+ if (_.isUndefined(this._proxyContract)) {
+ throw new Error('ERC721 proxy contract not yet deployed, please call "deployProxyAsync"');
+ }
+ }
+ private _validateBalancesAndAllowancesSetOrThrow(): void {
+ if (_.keys(this._initialTokenIdsByOwner).length === 0) {
+ throw new Error(
+ 'Dummy ERC721 balances and allowances not yet set, please call "setBalancesAndAllowancesAsync"',
+ );
+ }
+ }
+}
diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts
new file mode 100644
index 000000000..21e569f54
--- /dev/null
+++ b/packages/contracts/src/utils/exchange_wrapper.ts
@@ -0,0 +1,254 @@
+import { TransactionReceiptWithDecodedLogs, ZeroEx } from '0x.js';
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+import * as Web3 from 'web3';
+
+import { ExchangeContract } from '../contract_wrappers/generated/exchange';
+
+import { constants } from './constants';
+import { formatters } from './formatters';
+import { LogDecoder } from './log_decoder';
+import { orderUtils } from './order_utils';
+import { AssetProxyId, OrderInfo, SignedOrder, SignedTransaction } from './types';
+
+export class ExchangeWrapper {
+ private _exchange: ExchangeContract;
+ private _logDecoder: LogDecoder = new LogDecoder(constants.TESTRPC_NETWORK_ID);
+ private _zeroEx: ZeroEx;
+ constructor(exchangeContract: ExchangeContract, zeroEx: ZeroEx) {
+ this._exchange = exchangeContract;
+ this._zeroEx = zeroEx;
+ }
+ public async fillOrderAsync(
+ signedOrder: SignedOrder,
+ from: string,
+ opts: { takerAssetFillAmount?: BigNumber } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
+ const txHash = await this._exchange.fillOrder.sendTransactionAsync(
+ params.order,
+ params.takerAssetFillAmount,
+ params.signature,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createCancel(signedOrder);
+ const txHash = await this._exchange.cancelOrder.sendTransactionAsync(params.order, { from });
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async fillOrKillOrderAsync(
+ signedOrder: SignedOrder,
+ from: string,
+ opts: { takerAssetFillAmount?: BigNumber } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
+ const txHash = await this._exchange.fillOrKillOrder.sendTransactionAsync(
+ params.order,
+ params.takerAssetFillAmount,
+ params.signature,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async fillOrderNoThrowAsync(
+ signedOrder: SignedOrder,
+ from: string,
+ opts: { takerAssetFillAmount?: BigNumber } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
+ const txHash = await this._exchange.fillOrderNoThrow.sendTransactionAsync(
+ params.order,
+ params.takerAssetFillAmount,
+ params.signature,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async batchFillOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmounts?: BigNumber[] } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
+ const txHash = await this._exchange.batchFillOrders.sendTransactionAsync(
+ params.orders,
+ params.takerAssetFillAmounts,
+ params.signatures,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async batchFillOrKillOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmounts?: BigNumber[] } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
+ const txHash = await this._exchange.batchFillOrKillOrders.sendTransactionAsync(
+ params.orders,
+ params.takerAssetFillAmounts,
+ params.signatures,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async batchFillOrdersNoThrowAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmounts?: BigNumber[] } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
+ const txHash = await this._exchange.batchFillOrdersNoThrow.sendTransactionAsync(
+ params.orders,
+ params.takerAssetFillAmounts,
+ params.signatures,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async marketSellOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmount: BigNumber },
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount);
+ const txHash = await this._exchange.marketSellOrders.sendTransactionAsync(
+ params.orders,
+ params.takerAssetFillAmount,
+ params.signatures,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async marketSellOrdersNoThrowAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmount: BigNumber },
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount);
+ const txHash = await this._exchange.marketSellOrdersNoThrow.sendTransactionAsync(
+ params.orders,
+ params.takerAssetFillAmount,
+ params.signatures,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async marketBuyOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { makerAssetFillAmount: BigNumber },
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount);
+ const txHash = await this._exchange.marketBuyOrders.sendTransactionAsync(
+ params.orders,
+ params.makerAssetFillAmount,
+ params.signatures,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async marketBuyOrdersNoThrowAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { makerAssetFillAmount: BigNumber },
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount);
+ const txHash = await this._exchange.marketBuyOrdersNoThrow.sendTransactionAsync(
+ params.orders,
+ params.makerAssetFillAmount,
+ params.signatures,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async batchCancelOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createBatchCancel(orders);
+ const txHash = await this._exchange.batchCancelOrders.sendTransactionAsync(params.orders, { from });
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise<TransactionReceiptWithDecodedLogs> {
+ const txHash = await this._exchange.cancelOrdersUpTo.sendTransactionAsync(salt, { from });
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async registerAssetProxyAsync(
+ assetProxyId: AssetProxyId,
+ assetProxyAddress: string,
+ from: string,
+ opts: { oldAssetProxyAddressIfExists?: string } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const oldAssetProxyAddress = _.isUndefined(opts.oldAssetProxyAddressIfExists)
+ ? ZeroEx.NULL_ADDRESS
+ : opts.oldAssetProxyAddressIfExists;
+ const txHash = await this._exchange.registerAssetProxy.sendTransactionAsync(
+ assetProxyId,
+ assetProxyAddress,
+ oldAssetProxyAddress,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async executeTransactionAsync(
+ signedTx: SignedTransaction,
+ from: string,
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const txHash = await this._exchange.executeTransaction.sendTransactionAsync(
+ signedTx.salt,
+ signedTx.signer,
+ signedTx.data,
+ signedTx.signature,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise<BigNumber> {
+ const filledAmount = new BigNumber(await this._exchange.filled.callAsync(orderHashHex));
+ return filledAmount;
+ }
+ public async getOrderInfoAsync(signedOrder: SignedOrder): Promise<OrderInfo> {
+ const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo;
+ return orderInfo;
+ }
+ public async matchOrdersAsync(
+ signedOrderLeft: SignedOrder,
+ signedOrderRight: SignedOrder,
+ from: string,
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight);
+ const txHash = await this._exchange.matchOrders.sendTransactionAsync(
+ params.left,
+ params.right,
+ params.leftSignature,
+ params.rightSignature,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+ private async _getTxWithDecodedExchangeLogsAsync(txHash: string): Promise<TransactionReceiptWithDecodedLogs> {
+ const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash);
+ tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address);
+ tx.logs = _.map(tx.logs, log => this._logDecoder.decodeLogOrThrow(log));
+ return tx;
+ }
+}
diff --git a/packages/contracts/src/utils/formatters.ts b/packages/contracts/src/utils/formatters.ts
new file mode 100644
index 000000000..bfa48d6f1
--- /dev/null
+++ b/packages/contracts/src/utils/formatters.ts
@@ -0,0 +1,60 @@
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { orderUtils } from './order_utils';
+import { BatchCancelOrders, BatchFillOrders, MarketBuyOrders, MarketSellOrders, SignedOrder } from './types';
+
+export const formatters = {
+ createBatchFill(signedOrders: SignedOrder[], takerAssetFillAmounts: BigNumber[] = []): BatchFillOrders {
+ const batchFill: BatchFillOrders = {
+ orders: [],
+ signatures: [],
+ takerAssetFillAmounts,
+ };
+ _.forEach(signedOrders, signedOrder => {
+ const orderStruct = orderUtils.getOrderStruct(signedOrder);
+ batchFill.orders.push(orderStruct);
+ batchFill.signatures.push(signedOrder.signature);
+ if (takerAssetFillAmounts.length < signedOrders.length) {
+ batchFill.takerAssetFillAmounts.push(signedOrder.takerAssetAmount);
+ }
+ });
+ return batchFill;
+ },
+ createMarketSellOrders(signedOrders: SignedOrder[], takerAssetFillAmount: BigNumber): MarketSellOrders {
+ const marketSellOrders: MarketSellOrders = {
+ orders: [],
+ signatures: [],
+ takerAssetFillAmount,
+ };
+ _.forEach(signedOrders, signedOrder => {
+ const orderStruct = orderUtils.getOrderStruct(signedOrder);
+ marketSellOrders.orders.push(orderStruct);
+ marketSellOrders.signatures.push(signedOrder.signature);
+ });
+ return marketSellOrders;
+ },
+ createMarketBuyOrders(signedOrders: SignedOrder[], makerAssetFillAmount: BigNumber): MarketBuyOrders {
+ const marketBuyOrders: MarketBuyOrders = {
+ orders: [],
+ signatures: [],
+ makerAssetFillAmount,
+ };
+ _.forEach(signedOrders, signedOrder => {
+ const orderStruct = orderUtils.getOrderStruct(signedOrder);
+ marketBuyOrders.orders.push(orderStruct);
+ marketBuyOrders.signatures.push(signedOrder.signature);
+ });
+ return marketBuyOrders;
+ },
+ createBatchCancel(signedOrders: SignedOrder[]): BatchCancelOrders {
+ const batchCancel: BatchCancelOrders = {
+ orders: [],
+ };
+ _.forEach(signedOrders, signedOrder => {
+ const orderStruct = orderUtils.getOrderStruct(signedOrder);
+ batchCancel.orders.push(orderStruct);
+ });
+ return batchCancel;
+ },
+};
diff --git a/packages/contracts/src/utils/log_decoder.ts b/packages/contracts/src/utils/log_decoder.ts
new file mode 100644
index 000000000..747c7644d
--- /dev/null
+++ b/packages/contracts/src/utils/log_decoder.ts
@@ -0,0 +1,39 @@
+import { ContractArtifact } from '@0xproject/sol-compiler';
+import { AbiDefinition, LogEntry, LogWithDecodedArgs, RawLog } from '@0xproject/types';
+import { AbiDecoder, BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { artifacts } from './artifacts';
+
+export class LogDecoder {
+ private _abiDecoder: AbiDecoder;
+ constructor(networkIdIfExists?: number) {
+ if (_.isUndefined(networkIdIfExists)) {
+ throw new Error('networkId not specified');
+ }
+ const abiArrays: AbiDefinition[][] = [];
+ _.forEach(artifacts, (artifact: ContractArtifact) => {
+ const compilerOutput = artifact.compilerOutput;
+ abiArrays.push(compilerOutput.abi);
+ });
+ this._abiDecoder = new AbiDecoder(abiArrays);
+ }
+ public decodeLogOrThrow<ArgsType>(log: LogEntry): LogWithDecodedArgs<ArgsType> | RawLog {
+ const logWithDecodedArgsOrLog = this._abiDecoder.tryToDecodeLogOrNoop(log);
+ if (_.isUndefined((logWithDecodedArgsOrLog as LogWithDecodedArgs<ArgsType>).args)) {
+ throw new Error(`Unable to decode log: ${JSON.stringify(log)}`);
+ }
+ wrapLogBigNumbers(logWithDecodedArgsOrLog);
+ return logWithDecodedArgsOrLog;
+ }
+}
+
+function wrapLogBigNumbers(log: any): any {
+ const argNames = _.keys(log.args);
+ for (const argName of argNames) {
+ const isWeb3BigNumber = _.startsWith(log.args[argName].constructor.toString(), 'function BigNumber(');
+ if (isWeb3BigNumber) {
+ log.args[argName] = new BigNumber(log.args[argName]);
+ }
+ }
+}
diff --git a/packages/contracts/util/multi_sig_wrapper.ts b/packages/contracts/src/utils/multi_sig_wrapper.ts
index c66106b9a..41a1dd8d9 100644
--- a/packages/contracts/util/multi_sig_wrapper.ts
+++ b/packages/contracts/src/utils/multi_sig_wrapper.ts
@@ -5,7 +5,7 @@ import ethUtil = require('ethereumjs-util');
import * as _ from 'lodash';
import * as Web3 from 'web3';
-import { MultiSigWalletContract } from '../src/contract_wrappers/generated/multi_sig_wallet';
+import { MultiSigWalletContract } from '../contract_wrappers/generated/multi_sig_wallet';
import { TransactionDataParams } from './types';
diff --git a/packages/contracts/src/utils/order_factory.ts b/packages/contracts/src/utils/order_factory.ts
new file mode 100644
index 000000000..044e9b865
--- /dev/null
+++ b/packages/contracts/src/utils/order_factory.ts
@@ -0,0 +1,37 @@
+import { ZeroEx } from '0x.js';
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { orderUtils } from './order_utils';
+import { signingUtils } from './signing_utils';
+import { SignatureType, SignedOrder, UnsignedOrder } from './types';
+
+export class OrderFactory {
+ private _defaultOrderParams: Partial<UnsignedOrder>;
+ private _privateKey: Buffer;
+ constructor(privateKey: Buffer, defaultOrderParams: Partial<UnsignedOrder>) {
+ this._defaultOrderParams = defaultOrderParams;
+ this._privateKey = privateKey;
+ }
+ public newSignedOrder(
+ customOrderParams: Partial<UnsignedOrder> = {},
+ signatureType: SignatureType = SignatureType.Ecrecover,
+ ): SignedOrder {
+ const randomExpiration = new BigNumber(Math.floor((Date.now() + Math.random() * 100000000000) / 1000));
+ const order = ({
+ senderAddress: ZeroEx.NULL_ADDRESS,
+ expirationTimeSeconds: randomExpiration,
+ salt: ZeroEx.generatePseudoRandomSalt(),
+ takerAddress: ZeroEx.NULL_ADDRESS,
+ ...this._defaultOrderParams,
+ ...customOrderParams,
+ } as any) as UnsignedOrder;
+ const orderHashBuff = orderUtils.getOrderHashBuff(order);
+ const signature = signingUtils.signMessage(orderHashBuff, this._privateKey, signatureType);
+ const signedOrder = {
+ ...order,
+ signature: `0x${signature.toString('hex')}`,
+ };
+ return signedOrder;
+ }
+}
diff --git a/packages/contracts/src/utils/order_utils.ts b/packages/contracts/src/utils/order_utils.ts
new file mode 100644
index 000000000..8adc6b735
--- /dev/null
+++ b/packages/contracts/src/utils/order_utils.ts
@@ -0,0 +1,92 @@
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import ethUtil = require('ethereumjs-util');
+import * as _ from 'lodash';
+
+import { crypto } from './crypto';
+import { CancelOrder, MatchOrder, OrderStruct, SignatureType, SignedOrder, UnsignedOrder } from './types';
+
+export const orderUtils = {
+ createFill: (signedOrder: SignedOrder, takerAssetFillAmount?: BigNumber) => {
+ const fill = {
+ order: orderUtils.getOrderStruct(signedOrder),
+ takerAssetFillAmount: takerAssetFillAmount || signedOrder.takerAssetAmount,
+ signature: signedOrder.signature,
+ };
+ return fill;
+ },
+ createCancel(signedOrder: SignedOrder, takerAssetCancelAmount?: BigNumber): CancelOrder {
+ const cancel = {
+ order: orderUtils.getOrderStruct(signedOrder),
+ takerAssetCancelAmount: takerAssetCancelAmount || signedOrder.takerAssetAmount,
+ };
+ return cancel;
+ },
+ getOrderStruct(signedOrder: SignedOrder): OrderStruct {
+ const orderStruct = {
+ senderAddress: signedOrder.senderAddress,
+ makerAddress: signedOrder.makerAddress,
+ takerAddress: signedOrder.takerAddress,
+ feeRecipientAddress: signedOrder.feeRecipientAddress,
+ makerAssetAmount: signedOrder.makerAssetAmount,
+ takerAssetAmount: signedOrder.takerAssetAmount,
+ makerFee: signedOrder.makerFee,
+ takerFee: signedOrder.takerFee,
+ expirationTimeSeconds: signedOrder.expirationTimeSeconds,
+ salt: signedOrder.salt,
+ makerAssetData: signedOrder.makerAssetData,
+ takerAssetData: signedOrder.takerAssetData,
+ };
+ return orderStruct;
+ },
+ getOrderHashBuff(order: SignedOrder | UnsignedOrder): Buffer {
+ const orderSchemaHashBuff = crypto.solSHA3([
+ 'address exchangeAddress',
+ 'address makerAddress',
+ 'address takerAddress',
+ 'address feeRecipientAddress',
+ 'address senderAddress',
+ 'uint256 makerAssetAmount',
+ 'uint256 takerAssetAmount',
+ 'uint256 makerFee',
+ 'uint256 takerFee',
+ 'uint256 expirationTimeSeconds',
+ 'uint256 salt',
+ 'bytes makerAssetData',
+ 'bytes takerAssetData',
+ ]);
+ const orderParamsHashBuff = crypto.solSHA3([
+ order.exchangeAddress,
+ order.makerAddress,
+ order.takerAddress,
+ order.feeRecipientAddress,
+ order.senderAddress,
+ order.makerAssetAmount,
+ order.takerAssetAmount,
+ order.makerFee,
+ order.takerFee,
+ order.expirationTimeSeconds,
+ order.salt,
+ ethUtil.toBuffer(order.makerAssetData),
+ ethUtil.toBuffer(order.takerAssetData),
+ ]);
+ const orderSchemaHashHex = `0x${orderSchemaHashBuff.toString('hex')}`;
+ const orderParamsHashHex = `0x${orderParamsHashBuff.toString('hex')}`;
+ const orderHashBuff = crypto.solSHA3([new BigNumber(orderSchemaHashHex), new BigNumber(orderParamsHashHex)]);
+ return orderHashBuff;
+ },
+ getOrderHashHex(order: SignedOrder | UnsignedOrder): string {
+ const orderHashBuff = orderUtils.getOrderHashBuff(order);
+ const orderHashHex = `0x${orderHashBuff.toString('hex')}`;
+ return orderHashHex;
+ },
+ createMatchOrders(signedOrderLeft: SignedOrder, signedOrderRight: SignedOrder): MatchOrder {
+ const fill = {
+ left: orderUtils.getOrderStruct(signedOrderLeft),
+ right: orderUtils.getOrderStruct(signedOrderRight),
+ leftSignature: signedOrderLeft.signature,
+ rightSignature: signedOrderRight.signature,
+ };
+ return fill;
+ },
+};
diff --git a/packages/contracts/src/utils/signing_utils.ts b/packages/contracts/src/utils/signing_utils.ts
new file mode 100644
index 000000000..61ab1f138
--- /dev/null
+++ b/packages/contracts/src/utils/signing_utils.ts
@@ -0,0 +1,30 @@
+import * as ethUtil from 'ethereumjs-util';
+
+import { SignatureType } from './types';
+
+export const signingUtils = {
+ signMessage(message: Buffer, privateKey: Buffer, signatureType: SignatureType): Buffer {
+ if (signatureType === SignatureType.Ecrecover) {
+ const prefixedMessage = ethUtil.hashPersonalMessage(message);
+ const ecSignature = ethUtil.ecsign(prefixedMessage, privateKey);
+ const signature = Buffer.concat([
+ ethUtil.toBuffer(signatureType),
+ ethUtil.toBuffer(ecSignature.v),
+ ecSignature.r,
+ ecSignature.s,
+ ]);
+ return signature;
+ } else if (signatureType === SignatureType.EIP712) {
+ const ecSignature = ethUtil.ecsign(message, privateKey);
+ const signature = Buffer.concat([
+ ethUtil.toBuffer(signatureType),
+ ethUtil.toBuffer(ecSignature.v),
+ ecSignature.r,
+ ecSignature.s,
+ ]);
+ return signature;
+ } else {
+ throw new Error(`${signatureType} is not a valid signature type`);
+ }
+ },
+};
diff --git a/packages/contracts/util/token_registry_wrapper.ts b/packages/contracts/src/utils/token_registry_wrapper.ts
index bed62fa53..86daeca62 100644
--- a/packages/contracts/util/token_registry_wrapper.ts
+++ b/packages/contracts/src/utils/token_registry_wrapper.ts
@@ -1,6 +1,6 @@
import * as Web3 from 'web3';
-import { TokenRegistryContract } from '../src/contract_wrappers/generated/token_registry';
+import { TokenRegistryContract } from '../contract_wrappers/generated/token_registry';
import { Token } from './types';
diff --git a/packages/contracts/src/utils/transaction_factory.ts b/packages/contracts/src/utils/transaction_factory.ts
new file mode 100644
index 000000000..3a4f48330
--- /dev/null
+++ b/packages/contracts/src/utils/transaction_factory.ts
@@ -0,0 +1,35 @@
+import { ZeroEx } from '0x.js';
+import { BigNumber } from '@0xproject/utils';
+import * as ethUtil from 'ethereumjs-util';
+
+import { crypto } from './crypto';
+import { signingUtils } from './signing_utils';
+import { SignatureType, SignedTransaction } from './types';
+
+export class TransactionFactory {
+ private _signer: string;
+ private _exchangeAddress: string;
+ private _privateKey: Buffer;
+ constructor(privateKey: Buffer, exchangeAddress: string) {
+ this._privateKey = privateKey;
+ this._exchangeAddress = exchangeAddress;
+ const signerBuff = ethUtil.privateToAddress(this._privateKey);
+ this._signer = `0x${signerBuff.toString('hex')}`;
+ }
+ public newSignedTransaction(
+ data: string,
+ signatureType: SignatureType = SignatureType.Ecrecover,
+ ): SignedTransaction {
+ const salt = ZeroEx.generatePseudoRandomSalt();
+ const txHash = crypto.solSHA3([this._exchangeAddress, salt, ethUtil.toBuffer(data)]);
+ const signature = signingUtils.signMessage(txHash, this._privateKey, signatureType);
+ const signedTx = {
+ exchangeAddress: this._exchangeAddress,
+ salt,
+ signer: this._signer,
+ data,
+ signature: `0x${signature.toString('hex')}`,
+ };
+ return signedTx;
+ }
+}
diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts
new file mode 100644
index 000000000..ef86b4f38
--- /dev/null
+++ b/packages/contracts/src/utils/types.ts
@@ -0,0 +1,211 @@
+import { AbiDefinition, ContractAbi } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+
+export interface ERC20BalancesByOwner {
+ [ownerAddress: string]: {
+ [tokenAddress: string]: BigNumber;
+ };
+}
+
+export interface ERC721TokenIdsByOwner {
+ [ownerAddress: string]: {
+ [tokenAddress: string]: BigNumber[];
+ };
+}
+
+export interface SubmissionContractEventArgs {
+ transactionId: BigNumber;
+}
+
+export interface BatchFillOrders {
+ orders: OrderStruct[];
+ signatures: string[];
+ takerAssetFillAmounts: BigNumber[];
+}
+
+export interface MarketSellOrders {
+ orders: OrderStruct[];
+ signatures: string[];
+ takerAssetFillAmount: BigNumber;
+}
+
+export interface MarketBuyOrders {
+ orders: OrderStruct[];
+ signatures: string[];
+ makerAssetFillAmount: BigNumber;
+}
+
+export interface BatchCancelOrders {
+ orders: OrderStruct[];
+}
+
+export interface CancelOrdersBefore {
+ salt: BigNumber;
+}
+
+export enum AssetProxyId {
+ INVALID,
+ ERC20,
+ ERC721,
+}
+
+export interface TransactionDataParams {
+ name: string;
+ abi: AbiDefinition[];
+ args: any[];
+}
+
+export interface MultiSigConfig {
+ owners: string[];
+ confirmationsRequired: number;
+ secondsRequired: number;
+}
+
+export interface MultiSigConfigByNetwork {
+ [networkName: string]: MultiSigConfig;
+}
+
+export interface Token {
+ address?: string;
+ name: string;
+ symbol: string;
+ decimals: number;
+ ipfsHash: string;
+ swarmHash: string;
+}
+
+export enum ExchangeStatus {
+ INVALID,
+ SUCCESS,
+ ROUNDING_ERROR_TOO_LARGE,
+ INSUFFICIENT_BALANCE_OR_ALLOWANCE,
+ TAKER_ASSET_FILL_AMOUNT_TOO_LOW,
+ INVALID_SIGNATURE,
+ INVALID_SENDER,
+ INVALID_TAKER,
+ INVALID_MAKER,
+ ORDER_INVALID_MAKER_ASSET_AMOUNT,
+ ORDER_INVALID_TAKER_ASSET_AMOUNT,
+ ORDER_FILLABLE,
+ ORDER_EXPIRED,
+ ORDER_FULLY_FILLED,
+ ORDER_CANCELLED,
+}
+
+export enum ContractName {
+ TokenRegistry = 'TokenRegistry',
+ MultiSigWalletWithTimeLock = 'MultiSigWalletWithTimeLock',
+ Exchange = 'Exchange',
+ ZRXToken = 'ZRXToken',
+ DummyERC20Token = 'DummyERC20Token',
+ EtherToken = 'WETH9',
+ MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress = 'MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress',
+ AccountLevels = 'AccountLevels',
+ EtherDelta = 'EtherDelta',
+ Arbitrage = 'Arbitrage',
+ TestAssetProxyDispatcher = 'TestAssetProxyDispatcher',
+ TestLibs = 'TestLibs',
+ TestSignatureValidator = 'TestSignatureValidator',
+ ERC20Proxy = 'ERC20Proxy',
+ ERC721Proxy = 'ERC721Proxy',
+ DummyERC721Token = 'DummyERC721Token',
+ TestLibBytes = 'TestLibBytes',
+ Authorizable = 'Authorizable',
+}
+
+export interface SignedOrder extends UnsignedOrder {
+ signature: string;
+}
+
+export interface OrderStruct {
+ senderAddress: string;
+ makerAddress: string;
+ takerAddress: string;
+ feeRecipientAddress: string;
+ makerAssetAmount: BigNumber;
+ takerAssetAmount: BigNumber;
+ makerFee: BigNumber;
+ takerFee: BigNumber;
+ expirationTimeSeconds: BigNumber;
+ salt: BigNumber;
+ makerAssetData: string;
+ takerAssetData: string;
+}
+
+export interface UnsignedOrder extends OrderStruct {
+ exchangeAddress: string;
+}
+
+export enum SignatureType {
+ Illegal,
+ Invalid,
+ Caller,
+ Ecrecover,
+ EIP712,
+ Trezor,
+ Contract,
+}
+
+export interface SignedTransaction {
+ exchangeAddress: string;
+ salt: BigNumber;
+ signer: string;
+ data: string;
+ signature: string;
+}
+
+export interface TransferAmountsByMatchOrders {
+ // Left Maker
+ amountBoughtByLeftMaker: BigNumber;
+ amountSoldByLeftMaker: BigNumber;
+ amountReceivedByLeftMaker: BigNumber;
+ feePaidByLeftMaker: BigNumber;
+ // Right Maker
+ amountBoughtByRightMaker: BigNumber;
+ amountSoldByRightMaker: BigNumber;
+ amountReceivedByRightMaker: BigNumber;
+ feePaidByRightMaker: BigNumber;
+ // Taker
+ amountReceivedByTaker: BigNumber;
+ feePaidByTakerLeft: BigNumber;
+ feePaidByTakerRight: BigNumber;
+ totalFeePaidByTaker: BigNumber;
+ // Fee Recipients
+ feeReceivedLeft: BigNumber;
+ feeReceivedRight: BigNumber;
+}
+
+export interface OrderInfo {
+ orderStatus: number;
+ orderHash: string;
+ orderTakerAssetFilledAmount: BigNumber;
+}
+
+export interface ERC20ProxyData {
+ assetProxyId: AssetProxyId;
+ tokenAddress: string;
+}
+
+export interface ERC721ProxyData {
+ assetProxyId: AssetProxyId;
+ tokenAddress: string;
+ tokenId: BigNumber;
+}
+
+export interface ProxyData {
+ assetProxyId: AssetProxyId;
+ tokenAddress?: string;
+ data?: any;
+}
+
+export interface CancelOrder {
+ order: OrderStruct;
+ takerAssetCancelAmount: BigNumber;
+}
+
+export interface MatchOrder {
+ left: OrderStruct;
+ right: OrderStruct;
+ leftSignature: string;
+ rightSignature: string;
+}
diff --git a/packages/contracts/test/utils/web3_wrapper.ts b/packages/contracts/src/utils/web3_wrapper.ts
index ed1c488a2..ed1c488a2 100644
--- a/packages/contracts/test/utils/web3_wrapper.ts
+++ b/packages/contracts/src/utils/web3_wrapper.ts
diff --git a/packages/contracts/test/asset_proxy/authorizable.ts b/packages/contracts/test/asset_proxy/authorizable.ts
new file mode 100644
index 000000000..52e9ea87f
--- /dev/null
+++ b/packages/contracts/test/asset_proxy/authorizable.ts
@@ -0,0 +1,130 @@
+import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import * as chai from 'chai';
+import 'make-promises-safe';
+import * as Web3 from 'web3';
+
+import { MixinAuthorizableContract } from '../../src/contract_wrappers/generated/mixin_authorizable';
+import { artifacts } from '../../src/utils/artifacts';
+import { chaiSetup } from '../../src/utils/chai_setup';
+import { constants } from '../../src/utils/constants';
+import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('Authorizable', () => {
+ let owner: string;
+ let notOwner: string;
+ let address: string;
+ let authorizable: MixinAuthorizableContract;
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ owner = address = accounts[0];
+ notOwner = accounts[1];
+ authorizable = await MixinAuthorizableContract.deployFrom0xArtifactAsync(
+ artifacts.MixinAuthorizable,
+ provider,
+ txDefaults,
+ );
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('addAuthorizedAddress', () => {
+ it('should throw if not called by owner', async () => {
+ return expect(
+ authorizable.addAuthorizedAddress.sendTransactionAsync(notOwner, { from: notOwner }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ it('should allow owner to add an authorized address', async () => {
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isAuthorized = await authorizable.authorized.callAsync(address);
+ expect(isAuthorized).to.be.true();
+ });
+ it('should throw if owner attempts to authorize a duplicate address', async () => {
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ return expect(
+ authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+
+ describe('removeAuthorizedAddress', () => {
+ it('should throw if not called by owner', async () => {
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ return expect(
+ authorizable.removeAuthorizedAddress.sendTransactionAsync(address, {
+ from: notOwner,
+ }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should allow owner to remove an authorized address', async () => {
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await authorizable.removeAuthorizedAddress.sendTransactionAsync(address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isAuthorized = await authorizable.authorized.callAsync(address);
+ expect(isAuthorized).to.be.false();
+ });
+
+ it('should throw if owner attempts to remove an address that is not authorized', async () => {
+ return expect(
+ authorizable.removeAuthorizedAddress.sendTransactionAsync(address, {
+ from: owner,
+ }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+
+ describe('getAuthorizedAddresses', () => {
+ it('should return all authorized addresses', async () => {
+ const initial = await authorizable.getAuthorizedAddresses.callAsync();
+ expect(initial).to.have.length(0);
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await authorizable.addAuthorizedAddress.sendTransactionAsync(address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const afterAdd = await authorizable.getAuthorizedAddresses.callAsync();
+ expect(afterAdd).to.have.length(1);
+ expect(afterAdd).to.include(address);
+
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await authorizable.removeAuthorizedAddress.sendTransactionAsync(address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const afterRemove = await authorizable.getAuthorizedAddresses.callAsync();
+ expect(afterRemove).to.have.length(0);
+ });
+ });
+});
diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts
new file mode 100644
index 000000000..0f3080c99
--- /dev/null
+++ b/packages/contracts/test/asset_proxy/proxies.ts
@@ -0,0 +1,425 @@
+import { ZeroEx } from '0x.js';
+import { BlockchainLifecycle } from '@0xproject/dev-utils';
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import * as _ from 'lodash';
+import * as Web3 from 'web3';
+
+import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token';
+import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token';
+import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy';
+import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy';
+import { assetProxyUtils } from '../../src/utils/asset_proxy_utils';
+import { chaiSetup } from '../../src/utils/chai_setup';
+import { constants } from '../../src/utils/constants';
+import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
+import { ERC721Wrapper } from '../../src/utils/erc721_wrapper';
+import { AssetProxyId } from '../../src/utils/types';
+import { provider, web3Wrapper } from '../../src/utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('Asset Transfer Proxies', () => {
+ let owner: string;
+ let notAuthorized: string;
+ let exchangeAddress: string;
+ let makerAddress: string;
+ let takerAddress: string;
+
+ let zrxToken: DummyERC20TokenContract;
+ let erc721Token: DummyERC721TokenContract;
+ let erc20Proxy: ERC20ProxyContract;
+ let erc721Proxy: ERC721ProxyContract;
+
+ let erc20Wrapper: ERC20Wrapper;
+ let erc721Wrapper: ERC721Wrapper;
+ let erc721MakerTokenId: BigNumber;
+
+ let zeroEx: ZeroEx;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ const usedAddresses = ([owner, notAuthorized, exchangeAddress, makerAddress, takerAddress] = accounts);
+
+ erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+ erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
+
+ [zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ erc20Proxy = await erc20Wrapper.deployProxyAsync();
+ await erc20Wrapper.setBalancesAndAllowancesAsync();
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeAddress, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
+ erc721Proxy = await erc721Wrapper.deployProxyAsync();
+ await erc721Wrapper.setBalancesAndAllowancesAsync();
+ const erc721Balances = await erc721Wrapper.getBalancesAsync();
+ erc721MakerTokenId = erc721Balances[makerAddress][erc721Token.address][0];
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeAddress, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ zeroEx = new ZeroEx(provider, {
+ networkId: constants.TESTRPC_NETWORK_ID,
+ });
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('Transfer Proxy - ERC20', () => {
+ describe('transferFrom', () => {
+ it('should successfully transfer tokens', async () => {
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address);
+ // Perform a transfer from makerAddress to takerAddress
+ const erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const amount = new BigNumber(10);
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20Proxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: exchangeAddress },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Verify transfer was successful
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(amount),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].add(amount),
+ );
+ });
+
+ it('should do nothing if transferring 0 amount of a token', async () => {
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address);
+ // Perform a transfer from makerAddress to takerAddress
+ const erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const amount = new BigNumber(0);
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20Proxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: exchangeAddress },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Verify transfer was successful
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address],
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address],
+ );
+ });
+
+ it('should throw if allowances are too low', async () => {
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address);
+ // Create allowance less than transfer amount. Set allowance on proxy.
+ const allowance = new BigNumber(0);
+ const transferAmount = new BigNumber(10);
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, allowance, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Perform a transfer; expect this to fail.
+ return expect(
+ erc20Proxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ transferAmount,
+ { from: notAuthorized },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw if requesting address is not authorized', async () => {
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address);
+ // Perform a transfer from makerAddress to takerAddress
+ const amount = new BigNumber(10);
+ return expect(
+ erc20Proxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ {
+ from: notAuthorized,
+ },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+
+ describe('batchTransferFrom', () => {
+ it('should succesfully make multiple token transfers', async () => {
+ const erc20Balances = await erc20Wrapper.getBalancesAsync();
+
+ const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address);
+ const amount = new BigNumber(10);
+ const numTransfers = 2;
+ const assetMetadata = _.times(numTransfers, () => encodedProxyMetadata);
+ const fromAddresses = _.times(numTransfers, () => makerAddress);
+ const toAddresses = _.times(numTransfers, () => takerAddress);
+ const amounts = _.times(numTransfers, () => amount);
+
+ const txHash = await erc20Proxy.batchTransferFrom.sendTransactionAsync(
+ assetMetadata,
+ fromAddresses,
+ toAddresses,
+ amounts,
+ { from: exchangeAddress },
+ );
+ const res = await zeroEx.awaitTransactionMinedAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ expect(res.logs.length).to.equal(numTransfers);
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(amount.times(numTransfers)),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].add(amount.times(numTransfers)),
+ );
+ });
+
+ it('should throw if not called by an authorized address', async () => {
+ const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address);
+ const amount = new BigNumber(10);
+ const numTransfers = 2;
+ const assetMetadata = _.times(numTransfers, () => encodedProxyMetadata);
+ const fromAddresses = _.times(numTransfers, () => makerAddress);
+ const toAddresses = _.times(numTransfers, () => takerAddress);
+ const amounts = _.times(numTransfers, () => amount);
+
+ return expect(
+ erc20Proxy.batchTransferFrom.sendTransactionAsync(
+ assetMetadata,
+ fromAddresses,
+ toAddresses,
+ amounts,
+ { from: notAuthorized },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+
+ it('should have an id of 1', async () => {
+ const proxyId = await erc20Proxy.getProxyId.callAsync();
+ expect(proxyId).to.equal(1);
+ });
+ });
+
+ describe('Transfer Proxy - ERC721', () => {
+ describe('transferFrom', () => {
+ it('should successfully transfer tokens', async () => {
+ // Construct metadata for ERC721 proxy
+ const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData(
+ erc721Token.address,
+ erc721MakerTokenId,
+ );
+ // Verify pre-condition
+ const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
+ expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
+ // Perform a transfer from makerAddress to takerAddress
+ const erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const amount = new BigNumber(1);
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc721Proxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: exchangeAddress },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Verify transfer was successful
+ const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
+ expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress);
+ });
+
+ it('should throw if transferring 0 amount of a token', async () => {
+ // Construct metadata for ERC721 proxy
+ const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData(
+ erc721Token.address,
+ erc721MakerTokenId,
+ );
+ // Verify pre-condition
+ const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
+ expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
+ // Perform a transfer from makerAddress to takerAddress
+ const erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const amount = new BigNumber(0);
+ return expect(
+ erc721Proxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: exchangeAddress },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw if transferring > 1 amount of a token', async () => {
+ // Construct metadata for ERC721 proxy
+ const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData(
+ erc721Token.address,
+ erc721MakerTokenId,
+ );
+ // Verify pre-condition
+ const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
+ expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
+ // Perform a transfer from makerAddress to takerAddress
+ const erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const amount = new BigNumber(500);
+ return expect(
+ erc721Proxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: exchangeAddress },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw if allowances are too low', async () => {
+ // Construct metadata for ERC721 proxy
+ const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData(
+ erc721Token.address,
+ erc721MakerTokenId,
+ );
+ // Remove transfer approval for makerAddress.
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, false, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Perform a transfer; expect this to fail.
+ const amount = new BigNumber(1);
+ return expect(
+ erc20Proxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ {
+ from: notAuthorized,
+ },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw if requesting address is not authorized', async () => {
+ // Construct metadata for ERC721 proxy
+ const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData(
+ erc721Token.address,
+ erc721MakerTokenId,
+ );
+ // Perform a transfer from makerAddress to takerAddress
+ const amount = new BigNumber(1);
+ return expect(
+ erc721Proxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: notAuthorized },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+
+ describe('batchTransferFrom', () => {
+ it('should succesfully make multiple token transfers', async () => {
+ const erc721TokensById = await erc721Wrapper.getBalancesAsync();
+ const [makerTokenIdA, makerTokenIdB] = erc721TokensById[makerAddress][erc721Token.address];
+
+ const numTransfers = 2;
+ const assetMetadata = [
+ assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerTokenIdA),
+ assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerTokenIdB),
+ ];
+ const fromAddresses = _.times(numTransfers, () => makerAddress);
+ const toAddresses = _.times(numTransfers, () => takerAddress);
+ const amounts = _.times(numTransfers, () => new BigNumber(1));
+
+ const txHash = await erc721Proxy.batchTransferFrom.sendTransactionAsync(
+ assetMetadata,
+ fromAddresses,
+ toAddresses,
+ amounts,
+ { from: exchangeAddress },
+ );
+ const res = await zeroEx.awaitTransactionMinedAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
+ expect(res.logs.length).to.equal(numTransfers);
+
+ const newOwnerMakerAssetA = await erc721Token.ownerOf.callAsync(makerTokenIdA);
+ const newOwnerMakerAssetB = await erc721Token.ownerOf.callAsync(makerTokenIdB);
+ expect(newOwnerMakerAssetA).to.be.bignumber.equal(takerAddress);
+ expect(newOwnerMakerAssetB).to.be.bignumber.equal(takerAddress);
+ });
+
+ it('should throw if not called by an authorized address', async () => {
+ const erc721TokensById = await erc721Wrapper.getBalancesAsync();
+ const [makerTokenIdA, makerTokenIdB] = erc721TokensById[makerAddress][erc721Token.address];
+
+ const numTransfers = 2;
+ const assetMetadata = [
+ assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerTokenIdA),
+ assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerTokenIdB),
+ ];
+ const fromAddresses = _.times(numTransfers, () => makerAddress);
+ const toAddresses = _.times(numTransfers, () => takerAddress);
+ const amounts = _.times(numTransfers, () => new BigNumber(1));
+
+ return expect(
+ erc721Proxy.batchTransferFrom.sendTransactionAsync(
+ assetMetadata,
+ fromAddresses,
+ toAddresses,
+ amounts,
+ { from: notAuthorized },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+
+ it('should have an id of 2', async () => {
+ const proxyId = await erc721Proxy.getProxyId.callAsync();
+ expect(proxyId).to.equal(2);
+ });
+ });
+});
diff --git a/packages/contracts/test/ether_token.ts b/packages/contracts/test/ether_token.ts
index bad7b5961..4c647bbc7 100644
--- a/packages/contracts/test/ether_token.ts
+++ b/packages/contracts/test/ether_token.ts
@@ -6,13 +6,10 @@ import * as chai from 'chai';
import 'make-promises-safe';
import { WETH9Contract } from '../src/contract_wrappers/generated/weth9';
-import { artifacts } from '../util/artifacts';
-import { constants } from '../util/constants';
-import { ContractName } from '../util/types';
-
-import { chaiSetup } from './utils/chai_setup';
-
-import { provider, txDefaults, web3Wrapper } from './utils/web3_wrapper';
+import { artifacts } from '../src/utils/artifacts';
+import { chaiSetup } from '../src/utils/chai_setup';
+import { constants } from '../src/utils/constants';
+import { provider, txDefaults, web3Wrapper } from '../src/utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
@@ -23,6 +20,13 @@ describe('EtherToken', () => {
const gasPrice = ZeroEx.toBaseUnitAmount(new BigNumber(20), 9);
let zeroEx: ZeroEx;
let etherTokenAddress: string;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
account = accounts[0];
diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts
index 72e6c9b0c..1edd44aff 100644
--- a/packages/contracts/test/exchange/core.ts
+++ b/packages/contracts/test/exchange/core.ts
@@ -1,149 +1,130 @@
-import { LogWithDecodedArgs, SignedOrder, TransactionReceiptWithDecodedLogs, ZeroEx } from '0x.js';
-import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
+import { LogWithDecodedArgs, ZeroEx } from '0x.js';
+import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { BigNumber } from '@0xproject/utils';
-import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as chai from 'chai';
import ethUtil = require('ethereumjs-util');
import 'make-promises-safe';
-import * as Web3 from 'web3';
-import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token';
+import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token';
+import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token';
+import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy';
+import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy';
import {
+ CancelContractEventArgs,
ExchangeContract,
- LogCancelContractEventArgs,
- LogErrorContractEventArgs,
- LogFillContractEventArgs,
+ ExchangeStatusContractEventArgs,
+ FillContractEventArgs,
} from '../../src/contract_wrappers/generated/exchange';
-import { MaliciousTokenContract } from '../../src/contract_wrappers/generated/malicious_token';
-import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy';
-import { artifacts } from '../../util/artifacts';
-import { Balances } from '../../util/balances';
-import { constants } from '../../util/constants';
-import { crypto } from '../../util/crypto';
-import { ExchangeWrapper } from '../../util/exchange_wrapper';
-import { OrderFactory } from '../../util/order_factory';
-import { BalancesByOwner, ContractName, ExchangeContractErrs } from '../../util/types';
-import { chaiSetup } from '../utils/chai_setup';
-
-import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
+import { artifacts } from '../../src/utils/artifacts';
+import { assetProxyUtils } from '../../src/utils/asset_proxy_utils';
+import { chaiSetup } from '../../src/utils/chai_setup';
+import { constants } from '../../src/utils/constants';
+import { crypto } from '../../src/utils/crypto';
+import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
+import { ERC721Wrapper } from '../../src/utils/erc721_wrapper';
+import { ExchangeWrapper } from '../../src/utils/exchange_wrapper';
+import { OrderFactory } from '../../src/utils/order_factory';
+import { orderUtils } from '../../src/utils/order_utils';
+import { AssetProxyId, ContractName, ERC20BalancesByOwner, ExchangeStatus, SignedOrder } from '../../src/utils/types';
+import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
-describe('Exchange', () => {
- let maker: string;
- let tokenOwner: string;
- let taker: string;
- let feeRecipient: string;
- const INITIAL_BALANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
- const INITIAL_ALLOWANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
-
- let rep: DummyTokenContract;
- let dgd: DummyTokenContract;
- let zrx: DummyTokenContract;
+describe('Exchange core', () => {
+ let makerAddress: string;
+ let owner: string;
+ let takerAddress: string;
+ let feeRecipientAddress: string;
+
+ let erc20TokenA: DummyERC20TokenContract;
+ let erc20TokenB: DummyERC20TokenContract;
+ let zrxToken: DummyERC20TokenContract;
+ let erc721Token: DummyERC721TokenContract;
let exchange: ExchangeContract;
- let tokenTransferProxy: TokenTransferProxyContract;
+ let erc20Proxy: ERC20ProxyContract;
+ let erc721Proxy: ERC721ProxyContract;
let signedOrder: SignedOrder;
- let balances: BalancesByOwner;
- let exWrapper: ExchangeWrapper;
- let dmyBalances: Balances;
+ let erc20Balances: ERC20BalancesByOwner;
+ let exchangeWrapper: ExchangeWrapper;
+ let erc20Wrapper: ERC20Wrapper;
+ let erc721Wrapper: ERC721Wrapper;
let orderFactory: OrderFactory;
+ let erc721MakerAssetIds: BigNumber[];
+ let erc721TakerAssetIds: BigNumber[];
+
+ let defaultMakerAssetAddress: string;
+ let defaultTakerAssetAddress: string;
+
let zeroEx: ZeroEx;
before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- maker = accounts[0];
- [tokenOwner, taker, feeRecipient] = accounts;
- [rep, dgd, zrx] = await Promise.all([
- DummyTokenContract.deployFrom0xArtifactAsync(
- artifacts.DummyToken,
- provider,
- txDefaults,
- constants.DUMMY_TOKEN_NAME,
- constants.DUMMY_TOKEN_SYMBOL,
- constants.DUMMY_TOKEN_DECIMALS,
- constants.DUMMY_TOKEN_TOTAL_SUPPLY,
- ),
- DummyTokenContract.deployFrom0xArtifactAsync(
- artifacts.DummyToken,
- provider,
- txDefaults,
- constants.DUMMY_TOKEN_NAME,
- constants.DUMMY_TOKEN_SYMBOL,
- constants.DUMMY_TOKEN_DECIMALS,
- constants.DUMMY_TOKEN_TOTAL_SUPPLY,
- ),
- DummyTokenContract.deployFrom0xArtifactAsync(
- artifacts.DummyToken,
- provider,
- txDefaults,
- constants.DUMMY_TOKEN_NAME,
- constants.DUMMY_TOKEN_SYMBOL,
- constants.DUMMY_TOKEN_DECIMALS,
- constants.DUMMY_TOKEN_TOTAL_SUPPLY,
- ),
- ]);
- tokenTransferProxy = await TokenTransferProxyContract.deployFrom0xArtifactAsync(
- artifacts.TokenTransferProxy,
- provider,
- txDefaults,
- );
+ const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts);
+
+ erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+ erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
+
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ erc20Proxy = await erc20Wrapper.deployProxyAsync();
+ await erc20Wrapper.setBalancesAndAllowancesAsync();
+
+ [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
+ erc721Proxy = await erc721Wrapper.deployProxyAsync();
+ await erc721Wrapper.setBalancesAndAllowancesAsync();
+ const erc721Balances = await erc721Wrapper.getBalancesAsync();
+ erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address];
+ erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address];
+
exchange = await ExchangeContract.deployFrom0xArtifactAsync(
artifacts.Exchange,
provider,
txDefaults,
- zrx.address,
- tokenTransferProxy.address,
+ assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
);
- await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] });
zeroEx = new ZeroEx(provider, {
exchangeContractAddress: exchange.address,
networkId: constants.TESTRPC_NETWORK_ID,
});
- exWrapper = new ExchangeWrapper(exchange, zeroEx);
+ exchangeWrapper = new ExchangeWrapper(exchange, zeroEx);
+ await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
+ await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC721, erc721Proxy.address, owner);
- const defaultOrderParams = {
- exchangeContractAddress: exchange.address,
- maker,
- feeRecipient,
- makerTokenAddress: rep.address,
- takerTokenAddress: dgd.address,
- makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
- takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
- makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
- takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
- };
- orderFactory = new OrderFactory(zeroEx, defaultOrderParams);
- dmyBalances = new Balances([rep, dgd, zrx], [maker, taker, feeRecipient]);
- await Promise.all([
- rep.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
- from: maker,
- }),
- rep.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
- from: taker,
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, {
+ from: owner,
}),
- rep.setBalance.sendTransactionAsync(maker, INITIAL_BALANCE, { from: tokenOwner }),
- rep.setBalance.sendTransactionAsync(taker, INITIAL_BALANCE, { from: tokenOwner }),
- dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
- from: maker,
- }),
- dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
- from: taker,
- }),
- dgd.setBalance.sendTransactionAsync(maker, INITIAL_BALANCE, { from: tokenOwner }),
- dgd.setBalance.sendTransactionAsync(taker, INITIAL_BALANCE, { from: tokenOwner }),
- zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
- from: maker,
- }),
- zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
- from: taker,
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, {
+ from: owner,
}),
- zrx.setBalance.sendTransactionAsync(maker, INITIAL_BALANCE, { from: tokenOwner }),
- zrx.setBalance.sendTransactionAsync(taker, INITIAL_BALANCE, { from: tokenOwner }),
- ]);
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ defaultMakerAssetAddress = erc20TokenA.address;
+ defaultTakerAssetAddress = erc20TokenB.address;
+
+ const defaultOrderParams = {
+ ...constants.STATIC_ORDER_PARAMS,
+ exchangeAddress: exchange.address,
+ makerAddress,
+ feeRecipientAddress,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultMakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultTakerAssetAddress),
+ };
+ const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
+ orderFactory = new OrderFactory(privateKey, defaultOrderParams);
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
@@ -155,756 +136,844 @@ describe('Exchange', () => {
it('should include transferViaTokenTransferProxy', () => {
expect((exchange as any).transferViaTokenTransferProxy).to.be.undefined();
});
-
- it('should include isTransferable', () => {
- expect((exchange as any).isTransferable).to.be.undefined();
- });
-
- it('should include getBalance', () => {
- expect((exchange as any).getBalance).to.be.undefined();
- });
-
- it('should include getAllowance', () => {
- expect((exchange as any).getAllowance).to.be.undefined();
- });
});
describe('fillOrder', () => {
beforeEach(async () => {
- balances = await dmyBalances.getAsync();
- signedOrder = await orderFactory.newSignedOrderAsync();
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ signedOrder = orderFactory.newSignedOrder();
});
it('should create an unfillable order', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerTokenAmount: new BigNumber(1001),
- takerTokenAmount: new BigNumber(3),
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: new BigNumber(1001),
+ takerAssetAmount: new BigNumber(3),
});
- const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(
- ZeroEx.getOrderHashHex(signedOrder),
+ const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrder),
);
- expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0);
+ expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0);
- const fillTakerTokenAmount1 = new BigNumber(2);
- await exWrapper.fillOrderAsync(signedOrder, taker, {
- fillTakerTokenAmount: fillTakerTokenAmount1,
+ const fillTakerAssetAmount1 = new BigNumber(2);
+ await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
+ takerAssetFillAmount: fillTakerAssetAmount1,
});
- const filledTakerTokenAmountAfter1 = await zeroEx.exchange.getFilledTakerAmountAsync(
- ZeroEx.getOrderHashHex(signedOrder),
+ const takerAssetFilledAmountAfter1 = await exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrder),
);
- expect(filledTakerTokenAmountAfter1).to.be.bignumber.equal(fillTakerTokenAmount1);
+ expect(takerAssetFilledAmountAfter1).to.be.bignumber.equal(fillTakerAssetAmount1);
- const fillTakerTokenAmount2 = new BigNumber(1);
- await exWrapper.fillOrderAsync(signedOrder, taker, {
- fillTakerTokenAmount: fillTakerTokenAmount2,
+ const fillTakerAssetAmount2 = new BigNumber(1);
+ await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
+ takerAssetFillAmount: fillTakerAssetAmount2,
});
- const filledTakerTokenAmountAfter2 = await zeroEx.exchange.getFilledTakerAmountAsync(
- ZeroEx.getOrderHashHex(signedOrder),
+ const takerAssetFilledAmountAfter2 = await exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrder),
);
- expect(filledTakerTokenAmountAfter2).to.be.bignumber.equal(filledTakerTokenAmountAfter1);
+ expect(takerAssetFilledAmountAfter2).to.be.bignumber.equal(takerAssetFilledAmountAfter1);
});
- it('should transfer the correct amounts when makerTokenAmount === takerTokenAmount', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
- takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => {
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
});
- const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(
- ZeroEx.getOrderHashHex(signedOrder),
+ const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrder),
);
- expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0);
+ expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0);
- const fillTakerTokenAmount = signedOrder.takerTokenAmount.div(2);
- await exWrapper.fillOrderAsync(signedOrder, taker, { fillTakerTokenAmount });
+ const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
- const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync(
- ZeroEx.getOrderHashHex(signedOrder),
+ const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrder),
);
- expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount);
+ expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount);
- const newBalances = await dmyBalances.getAsync();
+ const newBalances = await erc20Wrapper.getBalancesAsync();
- const fillMakerTokenAmount = fillTakerTokenAmount
- .times(signedOrder.makerTokenAmount)
- .dividedToIntegerBy(signedOrder.takerTokenAmount);
- const paidMakerFee = signedOrder.makerFee
- .times(fillMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
- const paidTakerFee = signedOrder.takerFee
- .times(fillMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
- expect(newBalances[maker][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrder.makerTokenAddress].minus(fillMakerTokenAmount),
+ const makerAssetFilledAmount = takerAssetFillAmount
+ .times(signedOrder.makerAssetAmount)
+ .dividedToIntegerBy(signedOrder.takerAssetAmount);
+ const makerFeePaid = signedOrder.makerFee
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ const takerFeePaid = signedOrder.takerFee
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount),
);
- expect(newBalances[maker][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrder.takerTokenAddress].add(fillTakerTokenAmount),
+ expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
);
- expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(
- balances[maker][zrx.address].minus(paidMakerFee),
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid),
);
- expect(newBalances[taker][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrder.takerTokenAddress].minus(fillTakerTokenAmount),
+ expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
);
- expect(newBalances[taker][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrder.makerTokenAddress].add(fillMakerTokenAmount),
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount),
);
- expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(
- balances[taker][zrx.address].minus(paidTakerFee),
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid),
);
- expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal(
- balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)),
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
);
});
- it('should transfer the correct amounts when makerTokenAmount > takerTokenAmount', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
- takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => {
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
});
- const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(
- ZeroEx.getOrderHashHex(signedOrder),
+ const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrder),
);
- expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0);
+ expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0);
- const fillTakerTokenAmount = signedOrder.takerTokenAmount.div(2);
- await exWrapper.fillOrderAsync(signedOrder, taker, { fillTakerTokenAmount });
+ const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
- const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync(
- ZeroEx.getOrderHashHex(signedOrder),
+ const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrder),
);
- expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount);
+ expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount);
- const newBalances = await dmyBalances.getAsync();
+ const newBalances = await erc20Wrapper.getBalancesAsync();
- const fillMakerTokenAmount = fillTakerTokenAmount
- .times(signedOrder.makerTokenAmount)
- .dividedToIntegerBy(signedOrder.takerTokenAmount);
- const paidMakerFee = signedOrder.makerFee
- .times(fillMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
- const paidTakerFee = signedOrder.takerFee
- .times(fillMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
- expect(newBalances[maker][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrder.makerTokenAddress].minus(fillMakerTokenAmount),
+ const makerAssetFilledAmount = takerAssetFillAmount
+ .times(signedOrder.makerAssetAmount)
+ .dividedToIntegerBy(signedOrder.takerAssetAmount);
+ const makerFeePaid = signedOrder.makerFee
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ const takerFeePaid = signedOrder.takerFee
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount),
);
- expect(newBalances[maker][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrder.takerTokenAddress].add(fillTakerTokenAmount),
+ expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
);
- expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(
- balances[maker][zrx.address].minus(paidMakerFee),
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid),
);
- expect(newBalances[taker][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrder.takerTokenAddress].minus(fillTakerTokenAmount),
+ expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
);
- expect(newBalances[taker][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrder.makerTokenAddress].add(fillMakerTokenAmount),
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount),
);
- expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(
- balances[taker][zrx.address].minus(paidTakerFee),
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid),
);
- expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal(
- balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)),
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
);
});
- it('should transfer the correct amounts when makerTokenAmount < takerTokenAmount', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
- takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
+ it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => {
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
});
- const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(
- ZeroEx.getOrderHashHex(signedOrder),
+ const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrder),
);
- expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0);
+ expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0);
- const fillTakerTokenAmount = signedOrder.takerTokenAmount.div(2);
- await exWrapper.fillOrderAsync(signedOrder, taker, { fillTakerTokenAmount });
+ const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
- const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync(
- ZeroEx.getOrderHashHex(signedOrder),
+ const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrder),
);
- expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount);
+ expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount);
- const newBalances = await dmyBalances.getAsync();
+ const newBalances = await erc20Wrapper.getBalancesAsync();
- const fillMakerTokenAmount = fillTakerTokenAmount
- .times(signedOrder.makerTokenAmount)
- .dividedToIntegerBy(signedOrder.takerTokenAmount);
- const paidMakerFee = signedOrder.makerFee
- .times(fillMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
- const paidTakerFee = signedOrder.takerFee
- .times(fillMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
- expect(newBalances[maker][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrder.makerTokenAddress].minus(fillMakerTokenAmount),
+ const makerAssetFilledAmount = takerAssetFillAmount
+ .times(signedOrder.makerAssetAmount)
+ .dividedToIntegerBy(signedOrder.takerAssetAmount);
+ const makerFeePaid = signedOrder.makerFee
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ const takerFeePaid = signedOrder.takerFee
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount),
);
- expect(newBalances[maker][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrder.takerTokenAddress].add(fillTakerTokenAmount),
+ expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
);
- expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(
- balances[maker][zrx.address].minus(paidMakerFee),
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid),
);
- expect(newBalances[taker][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrder.takerTokenAddress].minus(fillTakerTokenAmount),
+ expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
);
- expect(newBalances[taker][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrder.makerTokenAddress].add(fillMakerTokenAmount),
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount),
);
- expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(
- balances[taker][zrx.address].minus(paidTakerFee),
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid),
);
- expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal(
- balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)),
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
);
});
it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- taker,
- makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
- takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
+ signedOrder = orderFactory.newSignedOrder({
+ takerAddress,
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
});
- const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(
- ZeroEx.getOrderHashHex(signedOrder),
+ const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrder),
);
- expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0);
+ expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0);
- const fillTakerTokenAmount = signedOrder.takerTokenAmount.div(2);
- await exWrapper.fillOrderAsync(signedOrder, taker, { fillTakerTokenAmount });
+ const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
- const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync(
- ZeroEx.getOrderHashHex(signedOrder),
+ const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrder),
);
- const expectedFillAmountTAfter = fillTakerTokenAmount.add(filledTakerTokenAmountBefore);
- expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(expectedFillAmountTAfter);
+ const expectedMakerAmountBoughtAfter = takerAssetFillAmount.add(takerAssetFilledAmountBefore);
+ expect(makerAmountBoughtAfter).to.be.bignumber.equal(expectedMakerAmountBoughtAfter);
- const newBalances = await dmyBalances.getAsync();
+ const newBalances = await erc20Wrapper.getBalancesAsync();
- const fillMakerTokenAmount = fillTakerTokenAmount
- .times(signedOrder.makerTokenAmount)
- .dividedToIntegerBy(signedOrder.takerTokenAmount);
- const paidMakerFee = signedOrder.makerFee
- .times(fillMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
- const paidTakerFee = signedOrder.takerFee
- .times(fillMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
- expect(newBalances[maker][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrder.makerTokenAddress].minus(fillMakerTokenAmount),
+ const makerAssetFilledAmount = takerAssetFillAmount
+ .times(signedOrder.makerAssetAmount)
+ .dividedToIntegerBy(signedOrder.takerAssetAmount);
+ const makerFeePaid = signedOrder.makerFee
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ const takerFeePaid = signedOrder.takerFee
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount),
);
- expect(newBalances[maker][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrder.takerTokenAddress].add(fillTakerTokenAmount),
+ expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
);
- expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(
- balances[maker][zrx.address].minus(paidMakerFee),
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid),
);
- expect(newBalances[taker][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrder.takerTokenAddress].minus(fillTakerTokenAmount),
+ expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
);
- expect(newBalances[taker][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrder.makerTokenAddress].add(fillMakerTokenAmount),
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount),
);
- expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(
- balances[taker][zrx.address].minus(paidTakerFee),
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid),
);
- expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal(
- balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)),
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
);
});
- it('should fill remaining value if fillTakerTokenAmount > remaining takerTokenAmount', async () => {
- const fillTakerTokenAmount = signedOrder.takerTokenAmount.div(2);
- await exWrapper.fillOrderAsync(signedOrder, taker, { fillTakerTokenAmount });
+ it('should fill remaining value if takerAssetFillAmount > remaining takerAssetAmount', async () => {
+ const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
- const res = await exWrapper.fillOrderAsync(signedOrder, taker, {
- fillTakerTokenAmount: signedOrder.takerTokenAmount,
+ const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
+ takerAssetFillAmount: signedOrder.takerAssetAmount,
});
- const log = res.logs[0] as LogWithDecodedArgs<LogFillContractEventArgs>;
- expect(log.args.filledTakerTokenAmount).to.be.bignumber.equal(
- signedOrder.takerTokenAmount.minus(fillTakerTokenAmount),
+ const log = res.logs[0] as LogWithDecodedArgs<FillContractEventArgs>;
+ expect(log.args.takerAssetFilledAmount).to.be.bignumber.equal(
+ signedOrder.takerAssetAmount.minus(takerAssetFillAmount),
);
- const newBalances = await dmyBalances.getAsync();
+ const newBalances = await erc20Wrapper.getBalancesAsync();
- expect(newBalances[maker][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrder.makerTokenAddress].minus(signedOrder.makerTokenAmount),
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(signedOrder.makerAssetAmount),
);
- expect(newBalances[maker][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrder.takerTokenAddress].add(signedOrder.takerTokenAmount),
+ expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerAssetAddress].add(signedOrder.takerAssetAmount),
);
- expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(
- balances[maker][zrx.address].minus(signedOrder.makerFee),
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(signedOrder.makerFee),
);
- expect(newBalances[taker][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrder.takerTokenAddress].minus(signedOrder.takerTokenAmount),
+ expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerAssetAddress].minus(signedOrder.takerAssetAmount),
);
- expect(newBalances[taker][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrder.makerTokenAddress].add(signedOrder.makerTokenAmount),
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].add(signedOrder.makerAssetAmount),
);
- expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(
- balances[taker][zrx.address].minus(signedOrder.takerFee),
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(signedOrder.takerFee),
);
- expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal(
- balances[feeRecipient][zrx.address].add(signedOrder.makerFee.add(signedOrder.takerFee)),
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(
+ signedOrder.makerFee.add(signedOrder.takerFee),
+ ),
);
});
it('should log 1 event with the correct arguments when order has a feeRecipient', async () => {
const divisor = 2;
- const res = await exWrapper.fillOrderAsync(signedOrder, taker, {
- fillTakerTokenAmount: signedOrder.takerTokenAmount.div(divisor),
+ const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
+ takerAssetFillAmount: signedOrder.takerAssetAmount.div(divisor),
});
expect(res.logs).to.have.length(1);
- const logArgs = (res.logs[0] as LogWithDecodedArgs<LogFillContractEventArgs>).args;
- const expectedFilledMakerTokenAmount = signedOrder.makerTokenAmount.div(divisor);
- const expectedFilledTakerTokenAmount = signedOrder.takerTokenAmount.div(divisor);
+ const log = res.logs[0] as LogWithDecodedArgs<FillContractEventArgs>;
+ const logArgs = log.args;
+ const expectedFilledMakerAssetAmount = signedOrder.makerAssetAmount.div(divisor);
+ const expectedFilledTakerAssetAmount = signedOrder.takerAssetAmount.div(divisor);
const expectedFeeMPaid = signedOrder.makerFee.div(divisor);
const expectedFeeTPaid = signedOrder.takerFee.div(divisor);
- const tokensHashBuff = crypto.solSHA3([signedOrder.makerTokenAddress, signedOrder.takerTokenAddress]);
- const expectedTokens = ethUtil.bufferToHex(tokensHashBuff);
-
- expect(signedOrder.maker).to.be.equal(logArgs.maker);
- expect(taker).to.be.equal(logArgs.taker);
- expect(signedOrder.feeRecipient).to.be.equal(logArgs.feeRecipient);
- expect(signedOrder.makerTokenAddress).to.be.equal(logArgs.makerToken);
- expect(signedOrder.takerTokenAddress).to.be.equal(logArgs.takerToken);
- expect(expectedFilledMakerTokenAmount).to.be.bignumber.equal(logArgs.filledMakerTokenAmount);
- expect(expectedFilledTakerTokenAmount).to.be.bignumber.equal(logArgs.filledTakerTokenAmount);
- expect(expectedFeeMPaid).to.be.bignumber.equal(logArgs.paidMakerFee);
- expect(expectedFeeTPaid).to.be.bignumber.equal(logArgs.paidTakerFee);
- expect(expectedTokens).to.be.equal(logArgs.tokens);
- expect(ZeroEx.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash);
- });
-
- it('should log 1 event with the correct arguments when order has no feeRecipient', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- feeRecipient: ZeroEx.NULL_ADDRESS,
- });
- const divisor = 2;
- const res = await exWrapper.fillOrderAsync(signedOrder, taker, {
- fillTakerTokenAmount: signedOrder.takerTokenAmount.div(divisor),
- });
- expect(res.logs).to.have.length(1);
- const logArgs = (res.logs[0] as LogWithDecodedArgs<LogFillContractEventArgs>).args;
- const expectedFilledMakerTokenAmount = signedOrder.makerTokenAmount.div(divisor);
- const expectedFilledTakerTokenAmount = signedOrder.takerTokenAmount.div(divisor);
- const expectedFeeMPaid = new BigNumber(0);
- const expectedFeeTPaid = new BigNumber(0);
- const tokensHashBuff = crypto.solSHA3([signedOrder.makerTokenAddress, signedOrder.takerTokenAddress]);
- const expectedTokens = ethUtil.bufferToHex(tokensHashBuff);
-
- expect(signedOrder.maker).to.be.equal(logArgs.maker);
- expect(taker).to.be.equal(logArgs.taker);
- expect(signedOrder.feeRecipient).to.be.equal(logArgs.feeRecipient);
- expect(signedOrder.makerTokenAddress).to.be.equal(logArgs.makerToken);
- expect(signedOrder.takerTokenAddress).to.be.equal(logArgs.takerToken);
- expect(expectedFilledMakerTokenAmount).to.be.bignumber.equal(logArgs.filledMakerTokenAmount);
- expect(expectedFilledTakerTokenAmount).to.be.bignumber.equal(logArgs.filledTakerTokenAmount);
- expect(expectedFeeMPaid).to.be.bignumber.equal(logArgs.paidMakerFee);
- expect(expectedFeeTPaid).to.be.bignumber.equal(logArgs.paidTakerFee);
- expect(expectedTokens).to.be.equal(logArgs.tokens);
- expect(ZeroEx.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash);
+ expect(signedOrder.makerAddress).to.be.equal(logArgs.makerAddress);
+ expect(takerAddress).to.be.equal(logArgs.takerAddress);
+ expect(signedOrder.feeRecipientAddress).to.be.equal(logArgs.feeRecipientAddress);
+ expect(signedOrder.makerAssetData).to.be.equal(logArgs.makerAssetData);
+ expect(signedOrder.takerAssetData).to.be.equal(logArgs.takerAssetData);
+ expect(expectedFilledMakerAssetAmount).to.be.bignumber.equal(logArgs.makerAssetFilledAmount);
+ expect(expectedFilledTakerAssetAmount).to.be.bignumber.equal(logArgs.takerAssetFilledAmount);
+ expect(expectedFeeMPaid).to.be.bignumber.equal(logArgs.makerFeePaid);
+ expect(expectedFeeTPaid).to.be.bignumber.equal(logArgs.takerFeePaid);
+ expect(orderUtils.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash);
});
it('should throw when taker is specified and order is claimed by other', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- taker: feeRecipient,
- makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
- takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
+ signedOrder = orderFactory.newSignedOrder({
+ takerAddress: feeRecipientAddress,
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
});
-
- return expect(exWrapper.fillOrderAsync(signedOrder, taker)).to.be.rejectedWith(constants.REVERT);
+ return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
});
it('should throw if signature is invalid', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
});
- signedOrder.ecSignature.r = ethUtil.bufferToHex(ethUtil.sha3('invalidR'));
- signedOrder.ecSignature.s = ethUtil.bufferToHex(ethUtil.sha3('invalidS'));
- return expect(exWrapper.fillOrderAsync(signedOrder, taker)).to.be.rejectedWith(constants.REVERT);
+ const invalidR = ethUtil.sha3('invalidR');
+ const invalidS = ethUtil.sha3('invalidS');
+ const signatureTypeAndV = signedOrder.signature.slice(0, 6);
+ const invalidSigBuff = Buffer.concat([ethUtil.toBuffer(signatureTypeAndV), invalidR, invalidS]);
+ const invalidSigHex = `0x${invalidSigBuff.toString('hex')}`;
+ signedOrder.signature = invalidSigHex;
+ return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
});
- it('should throw if makerTokenAmount is 0', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerTokenAmount: new BigNumber(0),
+ it('should throw if makerAssetAmount is 0', async () => {
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: new BigNumber(0),
});
- return expect(exWrapper.fillOrderAsync(signedOrder, taker)).to.be.rejectedWith(constants.REVERT);
+ return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
});
- it('should throw if takerTokenAmount is 0', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- takerTokenAmount: new BigNumber(0),
+ it('should throw if takerAssetAmount is 0', async () => {
+ signedOrder = orderFactory.newSignedOrder({
+ takerAssetAmount: new BigNumber(0),
});
- return expect(exWrapper.fillOrderAsync(signedOrder, taker)).to.be.rejectedWith(constants.REVERT);
+ return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
});
- it('should throw if fillTakerTokenAmount is 0', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync();
+ it('should throw if takerAssetFillAmount is 0', async () => {
+ signedOrder = orderFactory.newSignedOrder();
return expect(
- exWrapper.fillOrderAsync(signedOrder, taker, {
- fillTakerTokenAmount: new BigNumber(0),
+ exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
+ takerAssetFillAmount: new BigNumber(0),
}),
).to.be.rejectedWith(constants.REVERT);
});
- it('should not change balances if maker balances are too low to fill order and \
- shouldThrowOnInsufficientBalanceOrAllowance = false', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18),
+ it('should throw if maker erc20Balances are too low to fill order', async () => {
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18),
});
- await exWrapper.fillOrderAsync(signedOrder, taker);
- const newBalances = await dmyBalances.getAsync();
- expect(newBalances).to.be.deep.equal(balances);
- });
-
- it('should throw if maker balances are too low to fill order and \
- shouldThrowOnInsufficientBalanceOrAllowance = true', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18),
- });
-
- return expect(
- exWrapper.fillOrderAsync(signedOrder, taker, {
- shouldThrowOnInsufficientBalanceOrAllowance: true,
- }),
- ).to.be.rejectedWith(constants.REVERT);
+ return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
});
- it('should not change balances if taker balances are too low to fill order and \
- shouldThrowOnInsufficientBalanceOrAllowance = false', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18),
+ it('should throw if taker erc20Balances are too low to fill order', async () => {
+ signedOrder = orderFactory.newSignedOrder({
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18),
});
- await exWrapper.fillOrderAsync(signedOrder, taker);
- const newBalances = await dmyBalances.getAsync();
- expect(newBalances).to.be.deep.equal(balances);
+ return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
});
- it('should throw if taker balances are too low to fill order and \
- shouldThrowOnInsufficientBalanceOrAllowance = true', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18),
- });
-
- return expect(
- exWrapper.fillOrderAsync(signedOrder, taker, {
- shouldThrowOnInsufficientBalanceOrAllowance: true,
+ it('should throw if maker allowances are too low to fill order', async () => {
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), {
+ from: makerAddress,
}),
- ).to.be.rejectedWith(constants.REVERT);
- });
-
- it('should not change balances if maker allowances are too low to fill order and \
- shouldThrowOnInsufficientBalanceOrAllowance = false', async () => {
- await rep.approve.sendTransactionAsync(tokenTransferProxy.address, new BigNumber(0), { from: maker });
- await exWrapper.fillOrderAsync(signedOrder, taker);
- await rep.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
- from: maker,
- });
-
- const newBalances = await dmyBalances.getAsync();
- expect(newBalances).to.be.deep.equal(balances);
- });
-
- it('should throw if maker allowances are too low to fill order and \
- shouldThrowOnInsufficientBalanceOrAllowance = true', async () => {
- await rep.approve.sendTransactionAsync(tokenTransferProxy.address, new BigNumber(0), { from: maker });
- expect(
- exWrapper.fillOrderAsync(signedOrder, taker, {
- shouldThrowOnInsufficientBalanceOrAllowance: true,
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, constants.INITIAL_ERC20_ALLOWANCE, {
+ from: makerAddress,
}),
- ).to.be.rejectedWith(constants.REVERT);
- await rep.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
- from: maker,
- });
- });
-
- it('should not change balances if taker allowances are too low to fill order and \
- shouldThrowOnInsufficientBalanceOrAllowance = false', async () => {
- await dgd.approve.sendTransactionAsync(tokenTransferProxy.address, new BigNumber(0), { from: taker });
- await exWrapper.fillOrderAsync(signedOrder, taker);
- await dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
- from: taker,
- });
-
- const newBalances = await dmyBalances.getAsync();
- expect(newBalances).to.be.deep.equal(balances);
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
});
- it('should throw if taker allowances are too low to fill order and \
- shouldThrowOnInsufficientBalanceOrAllowance = true', async () => {
- await dgd.approve.sendTransactionAsync(tokenTransferProxy.address, new BigNumber(0), { from: taker });
- expect(
- exWrapper.fillOrderAsync(signedOrder, taker, {
- shouldThrowOnInsufficientBalanceOrAllowance: true,
+ it('should throw if taker allowances are too low to fill order', async () => {
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), {
+ from: takerAddress,
}),
- ).to.be.rejectedWith(constants.REVERT);
- await dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
- from: taker,
- });
- });
-
- it('should not change balances if makerTokenAddress is ZRX, makerTokenAmount + makerFee > maker balance, \
- and shouldThrowOnInsufficientBalanceOrAllowance = false', async () => {
- const makerZRXBalance = new BigNumber(balances[maker][zrx.address]);
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerTokenAddress: zrx.address,
- makerTokenAmount: makerZRXBalance,
- makerFee: new BigNumber(1),
- });
- await exWrapper.fillOrderAsync(signedOrder, taker);
- const newBalances = await dmyBalances.getAsync();
- expect(newBalances).to.be.deep.equal(balances);
- });
-
- it('should not change balances if makerTokenAddress is ZRX, makerTokenAmount + makerFee > maker allowance, \
- and shouldThrowOnInsufficientBalanceOrAllowance = false', async () => {
- const makerZRXAllowance = await zrx.allowance.callAsync(maker, tokenTransferProxy.address);
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerTokenAddress: zrx.address,
- makerTokenAmount: new BigNumber(makerZRXAllowance),
- makerFee: new BigNumber(1),
- });
- await exWrapper.fillOrderAsync(signedOrder, taker);
- const newBalances = await dmyBalances.getAsync();
- expect(newBalances).to.be.deep.equal(balances);
- });
-
- it('should not change balances if takerTokenAddress is ZRX, takerTokenAmount + takerFee > taker balance, \
- and shouldThrowOnInsufficientBalanceOrAllowance = false', async () => {
- const takerZRXBalance = new BigNumber(balances[taker][zrx.address]);
- signedOrder = await orderFactory.newSignedOrderAsync({
- takerTokenAddress: zrx.address,
- takerTokenAmount: takerZRXBalance,
- takerFee: new BigNumber(1),
- });
- await exWrapper.fillOrderAsync(signedOrder, taker);
- const newBalances = await dmyBalances.getAsync();
- expect(newBalances).to.be.deep.equal(balances);
- });
-
- it('should not change balances if takerTokenAddress is ZRX, takerTokenAmount + takerFee > taker allowance, \
- and shouldThrowOnInsufficientBalanceOrAllowance = false', async () => {
- const takerZRXAllowance = await zrx.allowance.callAsync(taker, tokenTransferProxy.address);
- signedOrder = await orderFactory.newSignedOrderAsync({
- takerTokenAddress: zrx.address,
- takerTokenAmount: new BigNumber(takerZRXAllowance),
- takerFee: new BigNumber(1),
- });
- await exWrapper.fillOrderAsync(signedOrder, taker);
- const newBalances = await dmyBalances.getAsync();
- expect(newBalances).to.be.deep.equal(balances);
- });
-
- it('should throw if getBalance or getAllowance attempts to change state and \
- shouldThrowOnInsufficientBalanceOrAllowance = false', async () => {
- const maliciousToken = await MaliciousTokenContract.deployFrom0xArtifactAsync(
- artifacts.MaliciousToken,
- provider,
- txDefaults,
+ constants.AWAIT_TRANSACTION_MINED_MS,
);
- await maliciousToken.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
- from: taker,
- });
-
- signedOrder = await orderFactory.newSignedOrderAsync({
- takerTokenAddress: maliciousToken.address,
- });
-
- return expect(
- exWrapper.fillOrderAsync(signedOrder, taker, {
- shouldThrowOnInsufficientBalanceOrAllowance: false,
+ await expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, constants.INITIAL_ERC20_ALLOWANCE, {
+ from: takerAddress,
}),
- ).to.be.rejectedWith(constants.REVERT);
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
});
- it('should not change balances if an order is expired', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- expirationUnixTimestampSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
+ it('should not change erc20Balances if an order is expired', async () => {
+ signedOrder = orderFactory.newSignedOrder({
+ expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
});
- await exWrapper.fillOrderAsync(signedOrder, taker);
+ await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
- const newBalances = await dmyBalances.getAsync();
- expect(newBalances).to.be.deep.equal(balances);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
});
it('should log an error event if an order is expired', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- expirationUnixTimestampSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
+ signedOrder = orderFactory.newSignedOrder({
+ expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
});
- const res = await exWrapper.fillOrderAsync(signedOrder, taker);
+ const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
expect(res.logs).to.have.length(1);
- const log = res.logs[0] as LogWithDecodedArgs<LogErrorContractEventArgs>;
- const errCode = log.args.errorId;
- expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED);
+ const log = res.logs[0] as LogWithDecodedArgs<ExchangeStatusContractEventArgs>;
+ const statusCode = log.args.statusId;
+ expect(statusCode).to.be.equal(ExchangeStatus.ORDER_EXPIRED);
});
it('should log an error event if no value is filled', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({});
- await exWrapper.fillOrderAsync(signedOrder, taker);
+ signedOrder = orderFactory.newSignedOrder({});
+ await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
- const res = await exWrapper.fillOrderAsync(signedOrder, taker);
+ const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
expect(res.logs).to.have.length(1);
- const log = res.logs[0] as LogWithDecodedArgs<LogErrorContractEventArgs>;
- const errCode = log.args.errorId;
- expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_FULLY_FILLED_OR_CANCELLED);
+ const log = res.logs[0] as LogWithDecodedArgs<ExchangeStatusContractEventArgs>;
+ const statusCode = log.args.statusId;
+ expect(statusCode).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
});
});
describe('cancelOrder', () => {
beforeEach(async () => {
- balances = await dmyBalances.getAsync();
- signedOrder = await orderFactory.newSignedOrderAsync();
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ signedOrder = orderFactory.newSignedOrder();
});
it('should throw if not sent by maker', async () => {
- return expect(exWrapper.cancelOrderAsync(signedOrder, taker)).to.be.rejectedWith(constants.REVERT);
+ return expect(exchangeWrapper.cancelOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
});
- it('should throw if makerTokenAmount is 0', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerTokenAmount: new BigNumber(0),
+ it('should throw if makerAssetAmount is 0', async () => {
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: new BigNumber(0),
});
- return expect(exWrapper.cancelOrderAsync(signedOrder, maker)).to.be.rejectedWith(constants.REVERT);
+ return expect(exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
});
- it('should throw if takerTokenAmount is 0', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- takerTokenAmount: new BigNumber(0),
+ it('should throw if takerAssetAmount is 0', async () => {
+ signedOrder = orderFactory.newSignedOrder({
+ takerAssetAmount: new BigNumber(0),
});
- return expect(exWrapper.cancelOrderAsync(signedOrder, maker)).to.be.rejectedWith(constants.REVERT);
+ return expect(exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
});
- it('should throw if cancelTakerTokenAmount is 0', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync();
+ it('should be able to cancel a full order', async () => {
+ await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
+ await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
+ takerAssetFillAmount: signedOrder.takerAssetAmount.div(2),
+ });
- return expect(
- exWrapper.cancelOrderAsync(signedOrder, maker, {
- cancelTakerTokenAmount: new BigNumber(0),
- }),
- ).to.be.rejectedWith(constants.REVERT);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
});
- it('should be able to cancel a full order', async () => {
- await exWrapper.cancelOrderAsync(signedOrder, maker);
- await exWrapper.fillOrderAsync(signedOrder, taker, {
- fillTakerTokenAmount: signedOrder.takerTokenAmount.div(2),
- });
+ it('should log 1 event with correct arguments', async () => {
+ const divisor = 2;
+ const res = await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
+ expect(res.logs).to.have.length(1);
+
+ const log = res.logs[0] as LogWithDecodedArgs<CancelContractEventArgs>;
+ const logArgs = log.args;
- const newBalances = await dmyBalances.getAsync();
- expect(newBalances).to.be.deep.equal(balances);
+ expect(signedOrder.makerAddress).to.be.equal(logArgs.makerAddress);
+ expect(signedOrder.feeRecipientAddress).to.be.equal(logArgs.feeRecipientAddress);
+ expect(signedOrder.makerAssetData).to.be.equal(logArgs.makerAssetData);
+ expect(signedOrder.takerAssetData).to.be.equal(logArgs.takerAssetData);
+ expect(orderUtils.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash);
});
- it('should be able to cancel part of an order', async () => {
- const cancelTakerTokenAmount = signedOrder.takerTokenAmount.div(2);
- await exWrapper.cancelOrderAsync(signedOrder, maker, {
- cancelTakerTokenAmount,
- });
+ it('should log an error if already cancelled', async () => {
+ await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
- const res = await exWrapper.fillOrderAsync(signedOrder, taker, {
- fillTakerTokenAmount: signedOrder.takerTokenAmount,
+ const res = await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
+ expect(res.logs).to.have.length(1);
+ const log = res.logs[0] as LogWithDecodedArgs<ExchangeStatusContractEventArgs>;
+ const statusCode = log.args.statusId;
+ expect(statusCode).to.be.equal(ExchangeStatus.ORDER_CANCELLED);
+ });
+
+ it('should log error if order is expired', async () => {
+ signedOrder = orderFactory.newSignedOrder({
+ expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
});
- const log = res.logs[0] as LogWithDecodedArgs<LogFillContractEventArgs>;
- expect(log.args.filledTakerTokenAmount).to.be.bignumber.equal(
- signedOrder.takerTokenAmount.minus(cancelTakerTokenAmount),
+
+ const res = await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
+ expect(res.logs).to.have.length(1);
+ const log = res.logs[0] as LogWithDecodedArgs<ExchangeStatusContractEventArgs>;
+ const statusCode = log.args.statusId;
+ expect(statusCode).to.be.equal(ExchangeStatus.ORDER_EXPIRED);
+ });
+ });
+
+ describe('cancelOrdersUpTo', () => {
+ it('should fail to set makerEpoch less than current makerEpoch', async () => {
+ const makerEpoch = new BigNumber(1);
+ await exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress);
+ const lesserMakerEpoch = new BigNumber(0);
+ return expect(exchangeWrapper.cancelOrdersUpToAsync(lesserMakerEpoch, makerAddress)).to.be.rejectedWith(
+ constants.REVERT,
);
+ });
- const newBalances = await dmyBalances.getAsync();
- const cancelMakerTokenAmount = cancelTakerTokenAmount
- .times(signedOrder.makerTokenAmount)
- .dividedToIntegerBy(signedOrder.takerTokenAmount);
- const paidMakerFee = signedOrder.makerFee
- .times(cancelMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
- const paidTakerFee = signedOrder.takerFee
- .times(cancelMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
- expect(newBalances[maker][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrder.makerTokenAddress].minus(cancelMakerTokenAmount),
+ it('should fail to set makerEpoch equal to existing makerEpoch', async () => {
+ const makerEpoch = new BigNumber(1);
+ await exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress);
+ return expect(exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress)).to.be.rejectedWith(
+ constants.REVERT,
);
- expect(newBalances[maker][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrder.takerTokenAddress].add(cancelTakerTokenAmount),
+ });
+
+ it('should cancel only orders with a makerEpoch less than existing makerEpoch', async () => {
+ // Cancel all transactions with a makerEpoch less than 1
+ const makerEpoch = new BigNumber(1);
+ await exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress);
+
+ // Create 3 orders with makerEpoch values: 0,1,2,3
+ // Since we cancelled with makerEpoch=1, orders with makerEpoch<=1 will not be processed
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const signedOrders = await Promise.all([
+ orderFactory.newSignedOrder({
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(9), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(9), 18),
+ salt: new BigNumber(0),
+ }),
+ orderFactory.newSignedOrder({
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(79), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(79), 18),
+ salt: new BigNumber(1),
+ }),
+ orderFactory.newSignedOrder({
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(979), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(979), 18),
+ salt: new BigNumber(2),
+ }),
+ orderFactory.newSignedOrder({
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(7979), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(7979), 18),
+ salt: new BigNumber(3),
+ }),
+ ]);
+ await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress);
+
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const fillMakerAssetAmount = signedOrders[2].makerAssetAmount.add(signedOrders[3].makerAssetAmount);
+ const fillTakerAssetAmount = signedOrders[2].takerAssetAmount.add(signedOrders[3].takerAssetAmount);
+ const makerFee = signedOrders[2].makerFee.add(signedOrders[3].makerFee);
+ const takerFee = signedOrders[2].takerFee.add(signedOrders[3].takerFee);
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(fillMakerAssetAmount),
+ );
+ expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerAssetAddress].add(fillTakerAssetAmount),
);
- expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(
- balances[maker][zrx.address].minus(paidMakerFee),
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerFee),
);
- expect(newBalances[taker][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrder.takerTokenAddress].minus(cancelTakerTokenAmount),
+ expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerAssetAddress].minus(fillTakerAssetAmount),
);
- expect(newBalances[taker][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrder.makerTokenAddress].add(cancelMakerTokenAmount),
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].add(fillMakerAssetAmount),
);
- expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(
- balances[taker][zrx.address].minus(paidTakerFee),
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(takerFee),
);
- expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal(
- balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)),
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFee.add(takerFee)),
);
});
+ });
- it('should log 1 event with correct arguments', async () => {
- const divisor = 2;
- const res = await exWrapper.cancelOrderAsync(signedOrder, maker, {
- cancelTakerTokenAmount: signedOrder.takerTokenAmount.div(divisor),
- });
- expect(res.logs).to.have.length(1);
-
- const log = res.logs[0] as LogWithDecodedArgs<LogCancelContractEventArgs>;
- const logArgs = log.args;
- const expectedCancelledMakerTokenAmount = signedOrder.makerTokenAmount.div(divisor);
- const expectedCancelledTakerTokenAmount = signedOrder.takerTokenAmount.div(divisor);
- const tokensHashBuff = crypto.solSHA3([signedOrder.makerTokenAddress, signedOrder.takerTokenAddress]);
- const expectedTokens = ethUtil.bufferToHex(tokensHashBuff);
+ describe('Testing Exchange of ERC721 Tokens', () => {
+ it('should successfully exchange a single token between the maker and taker (via fillOrder)', async () => {
+ // Construct Exchange parameters
+ const makerAssetId = erc721MakerAssetIds[0];
+ const takerAssetId = erc721TakerAssetIds[1];
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: new BigNumber(1),
+ takerAssetAmount: new BigNumber(1),
+ makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId),
+ takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId),
+ });
+ // Verify pre-conditions
+ const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
+ expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress);
+ const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
+ expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
+ // Call Exchange
+ const takerAssetFillAmount = signedOrder.takerAssetAmount;
+ const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
+ // Verify post-conditions
+ const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
+ expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress);
+ const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
+ expect(newOwnerTakerAsset).to.be.bignumber.equal(makerAddress);
+ });
+
+ it('should throw when maker does not own the token with id makerAssetId', async () => {
+ // Construct Exchange parameters
+ const makerAssetId = erc721TakerAssetIds[0];
+ const takerAssetId = erc721TakerAssetIds[1];
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: new BigNumber(1),
+ takerAssetAmount: new BigNumber(1),
+ makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId),
+ takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId),
+ });
+ // Verify pre-conditions
+ const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
+ expect(initialOwnerMakerAsset).to.be.bignumber.not.equal(makerAddress);
+ const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
+ expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
+ // Call Exchange
+ const takerAssetFillAmount = signedOrder.takerAssetAmount;
+ return expect(
+ exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
- expect(signedOrder.maker).to.be.equal(logArgs.maker);
- expect(signedOrder.feeRecipient).to.be.equal(logArgs.feeRecipient);
- expect(signedOrder.makerTokenAddress).to.be.equal(logArgs.makerToken);
- expect(signedOrder.takerTokenAddress).to.be.equal(logArgs.takerToken);
- expect(expectedCancelledMakerTokenAmount).to.be.bignumber.equal(logArgs.cancelledMakerTokenAmount);
- expect(expectedCancelledTakerTokenAmount).to.be.bignumber.equal(logArgs.cancelledTakerTokenAmount);
- expect(expectedTokens).to.be.equal(logArgs.tokens);
- expect(ZeroEx.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash);
+ it('should throw when taker does not own the token with id takerAssetId', async () => {
+ // Construct Exchange parameters
+ const makerAssetId = erc721MakerAssetIds[0];
+ const takerAssetId = erc721MakerAssetIds[1];
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: new BigNumber(1),
+ takerAssetAmount: new BigNumber(1),
+ makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId),
+ takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId),
+ });
+ // Verify pre-conditions
+ const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
+ expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress);
+ const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
+ expect(initialOwnerTakerAsset).to.be.bignumber.not.equal(takerAddress);
+ // Call Exchange
+ const takerAssetFillAmount = signedOrder.takerAssetAmount;
+ return expect(
+ exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
+ ).to.be.rejectedWith(constants.REVERT);
});
- it('should not log events if no value is cancelled', async () => {
- await exWrapper.cancelOrderAsync(signedOrder, maker);
+ it('should throw when makerAssetAmount is greater than 1', async () => {
+ // Construct Exchange parameters
+ const makerAssetId = erc721MakerAssetIds[0];
+ const takerAssetId = erc721TakerAssetIds[0];
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: new BigNumber(2),
+ takerAssetAmount: new BigNumber(1),
+ makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId),
+ takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId),
+ });
+ // Verify pre-conditions
+ const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
+ expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress);
+ const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
+ expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
+ // Call Exchange
+ const takerAssetFillAmount = signedOrder.takerAssetAmount;
+ return expect(
+ exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
- const res = await exWrapper.cancelOrderAsync(signedOrder, maker);
- expect(res.logs).to.have.length(1);
- const log = res.logs[0] as LogWithDecodedArgs<LogErrorContractEventArgs>;
- const errCode = log.args.errorId;
- expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_FULLY_FILLED_OR_CANCELLED);
+ it('should throw when takerAssetAmount is greater than 1', async () => {
+ // Construct Exchange parameters
+ const makerAssetId = erc721MakerAssetIds[0];
+ const takerAssetId = erc721TakerAssetIds[0];
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: new BigNumber(1),
+ takerAssetAmount: new BigNumber(500),
+ makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId),
+ takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId),
+ });
+ // Verify pre-conditions
+ const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
+ expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress);
+ const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
+ expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
+ // Call Exchange
+ const takerAssetFillAmount = signedOrder.takerAssetAmount;
+ return expect(
+ exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
+ ).to.be.rejectedWith(constants.REVERT);
});
- it('should not log events if order is expired', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- expirationUnixTimestampSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
- });
+ it('should throw on partial fill', async () => {
+ // Construct Exchange parameters
+ const makerAssetId = erc721MakerAssetIds[0];
+ const takerAssetId = erc721TakerAssetIds[0];
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: new BigNumber(1),
+ takerAssetAmount: new BigNumber(0),
+ makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId),
+ takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId),
+ });
+ // Verify pre-conditions
+ const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
+ expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress);
+ const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
+ expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
+ // Call Exchange
+ const takerAssetFillAmount = signedOrder.takerAssetAmount;
+ return expect(
+ exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
- const res = await exWrapper.cancelOrderAsync(signedOrder, maker);
- expect(res.logs).to.have.length(1);
- const log = res.logs[0] as LogWithDecodedArgs<LogErrorContractEventArgs>;
- const errCode = log.args.errorId;
- expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED);
+ it('should successfully fill order when makerAsset is ERC721 and takerAsset is ERC20', async () => {
+ // Construct Exchange parameters
+ const makerAssetId = erc721MakerAssetIds[0];
+ signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: new BigNumber(1),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultTakerAssetAddress),
+ });
+ // Verify pre-conditions
+ const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
+ expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress);
+ // Call Exchange
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const takerAssetFillAmount = signedOrder.takerAssetAmount;
+ await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
+ // Verify ERC721 token was transferred from Maker to Taker
+ const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
+ expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress);
+ // Verify ERC20 tokens were transferred from Taker to Maker & fees were paid correctly
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(signedOrder.makerFee),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(signedOrder.takerFee),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(
+ signedOrder.makerFee.add(signedOrder.takerFee),
+ ),
+ );
+ });
+
+ it('should successfully fill order when makerAsset is ERC20 and takerAsset is ERC721', async () => {
+ // Construct Exchange parameters
+ const takerAssetId = erc721TakerAssetIds[0];
+ signedOrder = orderFactory.newSignedOrder({
+ takerAssetAmount: new BigNumber(1),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId),
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultMakerAssetAddress),
+ });
+ // Verify pre-conditions
+ const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
+ expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
+ // Call Exchange
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const takerAssetFillAmount = signedOrder.takerAssetAmount;
+ await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
+ // Verify ERC721 token was transferred from Taker to Maker
+ const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
+ expect(newOwnerTakerAsset).to.be.bignumber.equal(makerAddress);
+ // Verify ERC20 tokens were transferred from Maker to Taker & fees were paid correctly
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].add(signedOrder.makerAssetAmount),
+ );
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(signedOrder.makerAssetAmount),
+ );
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(signedOrder.makerFee),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(signedOrder.takerFee),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(
+ signedOrder.makerFee.add(signedOrder.takerFee),
+ ),
+ );
});
});
}); // tslint:disable-line:max-file-line-count
diff --git a/packages/contracts/test/exchange/dispatcher.ts b/packages/contracts/test/exchange/dispatcher.ts
new file mode 100644
index 000000000..a1c37d8c8
--- /dev/null
+++ b/packages/contracts/test/exchange/dispatcher.ts
@@ -0,0 +1,322 @@
+import { ZeroEx } from '0x.js';
+import { BlockchainLifecycle } from '@0xproject/dev-utils';
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import * as Web3 from 'web3';
+
+import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token';
+import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy';
+import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy';
+import { TestAssetProxyDispatcherContract } from '../../src/contract_wrappers/generated/test_asset_proxy_dispatcher';
+import { artifacts } from '../../src/utils/artifacts';
+import { assetProxyUtils } from '../../src/utils/asset_proxy_utils';
+import { chaiSetup } from '../../src/utils/chai_setup';
+import { constants } from '../../src/utils/constants';
+import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
+import { ERC721Wrapper } from '../../src/utils/erc721_wrapper';
+import { AssetProxyId } from '../../src/utils/types';
+import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('AssetProxyDispatcher', () => {
+ let owner: string;
+ let notOwner: string;
+ let notAuthorized: string;
+ let makerAddress: string;
+ let takerAddress: string;
+
+ let zrxToken: DummyERC20TokenContract;
+ let erc20Proxy: ERC20ProxyContract;
+ let erc721Proxy: ERC721ProxyContract;
+ let assetProxyDispatcher: TestAssetProxyDispatcherContract;
+
+ let erc20Wrapper: ERC20Wrapper;
+ let erc721Wrapper: ERC721Wrapper;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ // Setup accounts & addresses
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ const usedAddresses = ([owner, notOwner, makerAddress, takerAddress] = accounts);
+ notAuthorized = notOwner;
+
+ erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+ erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
+
+ [zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ erc20Proxy = await erc20Wrapper.deployProxyAsync();
+ await erc20Wrapper.setBalancesAndAllowancesAsync();
+
+ erc721Proxy = await erc721Wrapper.deployProxyAsync();
+
+ assetProxyDispatcher = await TestAssetProxyDispatcherContract.deployFrom0xArtifactAsync(
+ artifacts.TestAssetProxyDispatcher,
+ provider,
+ txDefaults,
+ );
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('registerAssetProxy', () => {
+ it('should record proxy upon registration', async () => {
+ const prevProxyAddress = ZeroEx.NULL_ADDRESS;
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20Proxy.address,
+ prevProxyAddress,
+ { from: owner },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(erc20Proxy.address);
+ });
+
+ it('should be able to record multiple proxies', async () => {
+ // Record first proxy
+ const prevERC20ProxyAddress = ZeroEx.NULL_ADDRESS;
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20Proxy.address,
+ prevERC20ProxyAddress,
+ { from: owner },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ let proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(erc20Proxy.address);
+ // Record another proxy
+ const prevERC721ProxyAddress = ZeroEx.NULL_ADDRESS;
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC721,
+ erc721Proxy.address,
+ prevERC721ProxyAddress,
+ { from: owner },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC721);
+ expect(proxyAddress).to.be.equal(erc721Proxy.address);
+ });
+
+ it('should replace proxy address upon re-registration', async () => {
+ // Initial registration
+ const prevProxyAddress = ZeroEx.NULL_ADDRESS;
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20Proxy.address,
+ prevProxyAddress,
+ { from: owner },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ let proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(erc20Proxy.address);
+ // Deploy a new version of the ERC20 Transfer Proxy contract
+ const newErc20TransferProxy = await ERC20ProxyContract.deployFrom0xArtifactAsync(
+ artifacts.ERC20Proxy,
+ provider,
+ txDefaults,
+ );
+ // Register new ERC20 Transfer Proxy contract
+ const newAddress = newErc20TransferProxy.address;
+ const currentAddress = erc20Proxy.address;
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ newAddress,
+ currentAddress,
+ { from: owner },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Verify new asset proxy has replaced initial version
+ proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(newAddress);
+ });
+
+ it('should throw if registering with incorrect "currentAssetProxyAddress" field', async () => {
+ // Initial registration
+ const prevProxyAddress = ZeroEx.NULL_ADDRESS;
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20Proxy.address,
+ prevProxyAddress,
+ { from: owner },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(erc20Proxy.address);
+ // The following transaction will throw because the currentAddress is no longer ZeroEx.NULL_ADDRESS
+ return expect(
+ assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20Proxy.address,
+ ZeroEx.NULL_ADDRESS,
+ { from: owner },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should be able to reset proxy address to NULL', async () => {
+ // Initial registration
+ const prevProxyAddress = ZeroEx.NULL_ADDRESS;
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20Proxy.address,
+ prevProxyAddress,
+ { from: owner },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(erc20Proxy.address);
+ // The following transaction will reset the proxy address
+ const newProxyAddress = ZeroEx.NULL_ADDRESS;
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ newProxyAddress,
+ erc20Proxy.address,
+ { from: owner },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const finalProxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(finalProxyAddress).to.be.equal(newProxyAddress);
+ });
+
+ it('should throw if requesting address is not owner', async () => {
+ const prevProxyAddress = ZeroEx.NULL_ADDRESS;
+ return expect(
+ assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20Proxy.address,
+ prevProxyAddress,
+ { from: notOwner },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw if attempting to register a proxy to the incorrect id', async () => {
+ const prevProxyAddress = ZeroEx.NULL_ADDRESS;
+ return expect(
+ assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC721,
+ erc20Proxy.address,
+ prevProxyAddress,
+ { from: owner },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+
+ describe('getAssetProxy', () => {
+ it('should return correct address of registered proxy', async () => {
+ const prevProxyAddress = ZeroEx.NULL_ADDRESS;
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20Proxy.address,
+ prevProxyAddress,
+ { from: owner },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(erc20Proxy.address);
+ });
+
+ it('should return NULL address if requesting non-existent proxy', async () => {
+ const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(ZeroEx.NULL_ADDRESS);
+ });
+ });
+
+ describe('dispatchTransferFrom', () => {
+ it('should dispatch transfer to registered proxy', async () => {
+ // Register ERC20 proxy
+ const prevProxyAddress = ZeroEx.NULL_ADDRESS;
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20Proxy.address,
+ prevProxyAddress,
+ { from: owner },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address);
+ // Perform a transfer from makerAddress to takerAddress
+ const erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const amount = new BigNumber(10);
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: owner },
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Verify transfer was successful
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(amount),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].add(amount),
+ );
+ });
+
+ it('should throw if dispatching to unregistered proxy', async () => {
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address);
+ // Perform a transfer from makerAddress to takerAddress
+ const erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const amount = new BigNumber(10);
+ return expect(
+ assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: owner },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+});
diff --git a/packages/contracts/test/exchange/helpers.ts b/packages/contracts/test/exchange/helpers.ts
deleted file mode 100644
index 6d779b52a..000000000
--- a/packages/contracts/test/exchange/helpers.ts
+++ /dev/null
@@ -1,234 +0,0 @@
-import { SignedOrder, ZeroEx } from '0x.js';
-import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
-import { BigNumber } from '@0xproject/utils';
-import { Web3Wrapper } from '@0xproject/web3-wrapper';
-import * as chai from 'chai';
-import ethUtil = require('ethereumjs-util');
-import 'make-promises-safe';
-
-import {
- ExchangeContract,
- LogCancelContractEventArgs,
- LogErrorContractEventArgs,
- LogFillContractEventArgs,
-} from '../../src/contract_wrappers/generated/exchange';
-
-import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token';
-import { TokenRegistryContract } from '../../src/contract_wrappers/generated/token_registry';
-import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy';
-import { artifacts } from '../../util/artifacts';
-import { constants } from '../../util/constants';
-import { ExchangeWrapper } from '../../util/exchange_wrapper';
-import { OrderFactory } from '../../util/order_factory';
-import { ContractName } from '../../util/types';
-import { chaiSetup } from '../utils/chai_setup';
-
-import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
-
-chaiSetup.configure();
-const expect = chai.expect;
-
-const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
-
-describe('Exchange', () => {
- let maker: string;
- let feeRecipient: string;
-
- let signedOrder: SignedOrder;
- let exchangeWrapper: ExchangeWrapper;
- let orderFactory: OrderFactory;
-
- before(async () => {
- const accounts = await web3Wrapper.getAvailableAddressesAsync();
- [maker, feeRecipient] = accounts;
- const tokenRegistry = await TokenRegistryContract.deployFrom0xArtifactAsync(
- artifacts.TokenRegistry,
- provider,
- txDefaults,
- );
- const tokenTransferProxy = await TokenTransferProxyContract.deployFrom0xArtifactAsync(
- artifacts.TokenTransferProxy,
- provider,
- txDefaults,
- );
- const [rep, dgd, zrx] = await Promise.all([
- DummyTokenContract.deployFrom0xArtifactAsync(
- artifacts.DummyToken,
- provider,
- txDefaults,
- constants.DUMMY_TOKEN_NAME,
- constants.DUMMY_TOKEN_SYMBOL,
- constants.DUMMY_TOKEN_DECIMALS,
- constants.DUMMY_TOKEN_TOTAL_SUPPLY,
- ),
- DummyTokenContract.deployFrom0xArtifactAsync(
- artifacts.DummyToken,
- provider,
- txDefaults,
- constants.DUMMY_TOKEN_NAME,
- constants.DUMMY_TOKEN_SYMBOL,
- constants.DUMMY_TOKEN_DECIMALS,
- constants.DUMMY_TOKEN_TOTAL_SUPPLY,
- ),
- DummyTokenContract.deployFrom0xArtifactAsync(
- artifacts.DummyToken,
- provider,
- txDefaults,
- constants.DUMMY_TOKEN_NAME,
- constants.DUMMY_TOKEN_SYMBOL,
- constants.DUMMY_TOKEN_DECIMALS,
- constants.DUMMY_TOKEN_TOTAL_SUPPLY,
- ),
- ]);
- const exchange = await ExchangeContract.deployFrom0xArtifactAsync(
- artifacts.Exchange,
- provider,
- txDefaults,
- zrx.address,
- tokenTransferProxy.address,
- );
- await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] });
- const zeroEx = new ZeroEx(provider, { networkId: constants.TESTRPC_NETWORK_ID });
- exchangeWrapper = new ExchangeWrapper(exchange, zeroEx);
- const defaultOrderParams = {
- exchangeContractAddress: exchange.address,
- maker,
- feeRecipient,
- makerTokenAddress: rep.address,
- takerTokenAddress: dgd.address,
- makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
- takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
- makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
- takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
- };
- orderFactory = new OrderFactory(zeroEx, defaultOrderParams);
- signedOrder = await orderFactory.newSignedOrderAsync();
- });
-
- beforeEach(async () => {
- await blockchainLifecycle.startAsync();
- });
- afterEach(async () => {
- await blockchainLifecycle.revertAsync();
- });
- describe('getOrderHash', () => {
- it('should output the correct orderHash', async () => {
- const orderHashHex = await exchangeWrapper.getOrderHashAsync(signedOrder);
- expect(ZeroEx.getOrderHashHex(signedOrder)).to.be.equal(orderHashHex);
- });
- });
-
- describe('isValidSignature', () => {
- beforeEach(async () => {
- signedOrder = await orderFactory.newSignedOrderAsync();
- });
-
- it('should return true with a valid signature', async () => {
- const isValidSignatureForContract = await exchangeWrapper.isValidSignatureAsync(signedOrder);
- const orderHashHex = ZeroEx.getOrderHashHex(signedOrder);
- const isValidSignature = ZeroEx.isValidSignature(orderHashHex, signedOrder.ecSignature, signedOrder.maker);
- expect(isValidSignature).to.be.true();
- expect(isValidSignatureForContract).to.be.true();
- });
-
- it('should return false with an invalid signature', async () => {
- signedOrder.ecSignature.r = ethUtil.bufferToHex(ethUtil.sha3('invalidR'));
- signedOrder.ecSignature.s = ethUtil.bufferToHex(ethUtil.sha3('invalidS'));
- const isValidSignatureForContract = await exchangeWrapper.isValidSignatureAsync(signedOrder);
- const orderHashHex = ZeroEx.getOrderHashHex(signedOrder);
- const isValidSignature = ZeroEx.isValidSignature(orderHashHex, signedOrder.ecSignature, signedOrder.maker);
- expect(isValidSignature).to.be.false();
- expect(isValidSignatureForContract).to.be.false();
- });
- });
-
- describe('isRoundingError', () => {
- it('should return false if there is a rounding error of 0.1%', async () => {
- const numerator = new BigNumber(20);
- const denominator = new BigNumber(999);
- const target = new BigNumber(50);
- // rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1%
- const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target);
- expect(isRoundingError).to.be.false();
- });
-
- it('should return false if there is a rounding of 0.09%', async () => {
- const numerator = new BigNumber(20);
- const denominator = new BigNumber(9991);
- const target = new BigNumber(500);
- // rounding error = ((20*500/9991) - floor(20*500/9991)) / (20*500/9991) = 0.09%
- const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target);
- expect(isRoundingError).to.be.false();
- });
-
- it('should return true if there is a rounding error of 0.11%', async () => {
- const numerator = new BigNumber(20);
- const denominator = new BigNumber(9989);
- const target = new BigNumber(500);
- // rounding error = ((20*500/9989) - floor(20*500/9989)) / (20*500/9989) = 0.011%
- const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target);
- expect(isRoundingError).to.be.true();
- });
-
- it('should return true if there is a rounding error > 0.1%', async () => {
- const numerator = new BigNumber(3);
- const denominator = new BigNumber(7);
- const target = new BigNumber(10);
- // rounding error = ((3*10/7) - floor(3*10/7)) / (3*10/7) = 6.67%
- const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target);
- expect(isRoundingError).to.be.true();
- });
-
- it('should return false when there is no rounding error', async () => {
- const numerator = new BigNumber(1);
- const denominator = new BigNumber(2);
- const target = new BigNumber(10);
-
- const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target);
- expect(isRoundingError).to.be.false();
- });
-
- it('should return false when there is rounding error <= 0.1%', async () => {
- // randomly generated numbers
- const numerator = new BigNumber(76564);
- const denominator = new BigNumber(676373677);
- const target = new BigNumber(105762562);
- // rounding error = ((76564*105762562/676373677) - floor(76564*105762562/676373677)) /
- // (76564*105762562/676373677) = 0.0007%
- const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target);
- expect(isRoundingError).to.be.false();
- });
- });
-
- describe('getPartialAmount', () => {
- it('should return the numerator/denominator*target', async () => {
- const numerator = new BigNumber(1);
- const denominator = new BigNumber(2);
- const target = new BigNumber(10);
-
- const partialAmount = await exchangeWrapper.getPartialAmountAsync(numerator, denominator, target);
- const expectedPartialAmount = 5;
- expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount);
- });
-
- it('should round down', async () => {
- const numerator = new BigNumber(2);
- const denominator = new BigNumber(3);
- const target = new BigNumber(10);
-
- const partialAmount = await exchangeWrapper.getPartialAmountAsync(numerator, denominator, target);
- const expectedPartialAmount = 6;
- expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount);
- });
-
- it('should round .5 down', async () => {
- const numerator = new BigNumber(1);
- const denominator = new BigNumber(20);
- const target = new BigNumber(10);
-
- const partialAmount = await exchangeWrapper.getPartialAmountAsync(numerator, denominator, target);
- const expectedPartialAmount = 0;
- expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount);
- });
- });
-});
diff --git a/packages/contracts/test/exchange/libs.ts b/packages/contracts/test/exchange/libs.ts
new file mode 100644
index 000000000..883de7a12
--- /dev/null
+++ b/packages/contracts/test/exchange/libs.ts
@@ -0,0 +1,160 @@
+import { ZeroEx } from '0x.js';
+import { BlockchainLifecycle } from '@0xproject/dev-utils';
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import ethUtil = require('ethereumjs-util');
+
+import { TestLibsContract } from '../../src/contract_wrappers/generated/test_libs';
+import { addressUtils } from '../../src/utils/address_utils';
+import { artifacts } from '../../src/utils/artifacts';
+import { assetProxyUtils } from '../../src/utils/asset_proxy_utils';
+import { chaiSetup } from '../../src/utils/chai_setup';
+import { constants } from '../../src/utils/constants';
+import { OrderFactory } from '../../src/utils/order_factory';
+import { orderUtils } from '../../src/utils/order_utils';
+import { SignedOrder } from '../../src/utils/types';
+import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('Exchange libs', () => {
+ let signedOrder: SignedOrder;
+ let orderFactory: OrderFactory;
+ let libs: TestLibsContract;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ const makerAddress = accounts[0];
+ libs = await TestLibsContract.deployFrom0xArtifactAsync(artifacts.TestLibs, provider, txDefaults);
+ const zeroEx = new ZeroEx(provider, { networkId: constants.TESTRPC_NETWORK_ID });
+
+ const defaultOrderParams = {
+ ...constants.STATIC_ORDER_PARAMS,
+ exchangeAddress: libs.address,
+ makerAddress,
+ feeRecipientAddress: addressUtils.generatePseudoRandomAddress(),
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(addressUtils.generatePseudoRandomAddress()),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(addressUtils.generatePseudoRandomAddress()),
+ };
+ const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
+ orderFactory = new OrderFactory(privateKey, defaultOrderParams);
+ });
+
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ signedOrder = orderFactory.newSignedOrder();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ describe('LibOrder', () => {
+ describe('getOrderHash', () => {
+ it('should output the correct orderHash', async () => {
+ const orderHashHex = await libs.publicGetOrderHash.callAsync(signedOrder);
+ expect(orderUtils.getOrderHashHex(signedOrder)).to.be.equal(orderHashHex);
+ });
+ });
+ });
+
+ describe('LibMath', () => {
+ describe('isRoundingError', () => {
+ it('should return false if there is a rounding error of 0.1%', async () => {
+ const numerator = new BigNumber(20);
+ const denominator = new BigNumber(999);
+ const target = new BigNumber(50);
+ // rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1%
+ const isRoundingError = await libs.publicIsRoundingError.callAsync(numerator, denominator, target);
+ expect(isRoundingError).to.be.false();
+ });
+
+ it('should return false if there is a rounding of 0.09%', async () => {
+ const numerator = new BigNumber(20);
+ const denominator = new BigNumber(9991);
+ const target = new BigNumber(500);
+ // rounding error = ((20*500/9991) - floor(20*500/9991)) / (20*500/9991) = 0.09%
+ const isRoundingError = await libs.publicIsRoundingError.callAsync(numerator, denominator, target);
+ expect(isRoundingError).to.be.false();
+ });
+
+ it('should return true if there is a rounding error of 0.11%', async () => {
+ const numerator = new BigNumber(20);
+ const denominator = new BigNumber(9989);
+ const target = new BigNumber(500);
+ // rounding error = ((20*500/9989) - floor(20*500/9989)) / (20*500/9989) = 0.011%
+ const isRoundingError = await libs.publicIsRoundingError.callAsync(numerator, denominator, target);
+ expect(isRoundingError).to.be.true();
+ });
+
+ it('should return true if there is a rounding error > 0.1%', async () => {
+ const numerator = new BigNumber(3);
+ const denominator = new BigNumber(7);
+ const target = new BigNumber(10);
+ // rounding error = ((3*10/7) - floor(3*10/7)) / (3*10/7) = 6.67%
+ const isRoundingError = await libs.publicIsRoundingError.callAsync(numerator, denominator, target);
+ expect(isRoundingError).to.be.true();
+ });
+
+ it('should return false when there is no rounding error', async () => {
+ const numerator = new BigNumber(1);
+ const denominator = new BigNumber(2);
+ const target = new BigNumber(10);
+
+ const isRoundingError = await libs.publicIsRoundingError.callAsync(numerator, denominator, target);
+ expect(isRoundingError).to.be.false();
+ });
+
+ it('should return false when there is rounding error <= 0.1%', async () => {
+ // randomly generated numbers
+ const numerator = new BigNumber(76564);
+ const denominator = new BigNumber(676373677);
+ const target = new BigNumber(105762562);
+ // rounding error = ((76564*105762562/676373677) - floor(76564*105762562/676373677)) /
+ // (76564*105762562/676373677) = 0.0007%
+ const isRoundingError = await libs.publicIsRoundingError.callAsync(numerator, denominator, target);
+ expect(isRoundingError).to.be.false();
+ });
+ });
+
+ describe('getPartialAmount', () => {
+ it('should return the numerator/denominator*target', async () => {
+ const numerator = new BigNumber(1);
+ const denominator = new BigNumber(2);
+ const target = new BigNumber(10);
+
+ const partialAmount = await libs.publicGetPartialAmount.callAsync(numerator, denominator, target);
+ const expectedPartialAmount = 5;
+ expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount);
+ });
+
+ it('should round down', async () => {
+ const numerator = new BigNumber(2);
+ const denominator = new BigNumber(3);
+ const target = new BigNumber(10);
+
+ const partialAmount = await libs.publicGetPartialAmount.callAsync(numerator, denominator, target);
+ const expectedPartialAmount = 6;
+ expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount);
+ });
+
+ it('should round .5 down', async () => {
+ const numerator = new BigNumber(1);
+ const denominator = new BigNumber(20);
+ const target = new BigNumber(10);
+
+ const partialAmount = await libs.publicGetPartialAmount.callAsync(numerator, denominator, target);
+ const expectedPartialAmount = 0;
+ expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount);
+ });
+ });
+ });
+});
diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts
new file mode 100644
index 000000000..b08b9e763
--- /dev/null
+++ b/packages/contracts/test/exchange/match_orders.ts
@@ -0,0 +1,843 @@
+import { LogWithDecodedArgs, ZeroEx } from '0x.js';
+import { BlockchainLifecycle } from '@0xproject/dev-utils';
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import ethUtil = require('ethereumjs-util');
+import * as _ from 'lodash';
+
+import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token';
+import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token';
+import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy';
+import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy';
+import {
+ CancelContractEventArgs,
+ ExchangeContract,
+ ExchangeStatusContractEventArgs,
+ FillContractEventArgs,
+} from '../../src/contract_wrappers/generated/exchange';
+import { artifacts } from '../../src/utils/artifacts';
+import { assetProxyUtils } from '../../src/utils/asset_proxy_utils';
+import { chaiSetup } from '../../src/utils/chai_setup';
+import { constants } from '../../src/utils/constants';
+import { crypto } from '../../src/utils/crypto';
+import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
+import { ERC721Wrapper } from '../../src/utils/erc721_wrapper';
+import { ExchangeWrapper } from '../../src/utils/exchange_wrapper';
+import { OrderFactory } from '../../src/utils/order_factory';
+import { orderUtils } from '../../src/utils/order_utils';
+import {
+ AssetProxyId,
+ ContractName,
+ ERC20BalancesByOwner,
+ ERC721TokenIdsByOwner,
+ ExchangeStatus,
+ OrderInfo,
+ SignedOrder,
+} from '../../src/utils/types';
+import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
+
+import { MatchOrderTester } from '../utils/match_order_tester';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('matchOrders', () => {
+ let makerAddressLeft: string;
+ let makerAddressRight: string;
+ let owner: string;
+ let takerAddress: string;
+ let feeRecipientAddressLeft: string;
+ let feeRecipientAddressRight: string;
+
+ let erc20TokenA: DummyERC20TokenContract;
+ let erc20TokenB: DummyERC20TokenContract;
+ let zrxToken: DummyERC20TokenContract;
+ let erc721Token: DummyERC721TokenContract;
+ let exchange: ExchangeContract;
+ let erc20Proxy: ERC20ProxyContract;
+ let erc721Proxy: ERC721ProxyContract;
+
+ let erc20BalancesByOwner: ERC20BalancesByOwner;
+ let erc721TokenIdsByOwner: ERC721TokenIdsByOwner;
+ let exchangeWrapper: ExchangeWrapper;
+ let erc20Wrapper: ERC20Wrapper;
+ let erc721Wrapper: ERC721Wrapper;
+ let orderFactoryLeft: OrderFactory;
+ let orderFactoryRight: OrderFactory;
+
+ let erc721LeftMakerAssetIds: BigNumber[];
+ let erc721RightMakerAssetIds: BigNumber[];
+ let erc721TakerAssetIds: BigNumber[];
+
+ let defaultERC20MakerAssetAddress: string;
+ let defaultERC20TakerAssetAddress: string;
+ let defaultERC721AssetAddress: string;
+
+ let matchOrderTester: MatchOrderTester;
+
+ let zeroEx: ZeroEx;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ // Create accounts
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ const usedAddresses = ([
+ owner,
+ makerAddressLeft,
+ makerAddressRight,
+ takerAddress,
+ feeRecipientAddressLeft,
+ feeRecipientAddressRight,
+ ] = accounts);
+ // Create wrappers
+ erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+ erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
+ // Deploy ERC20 token & ERC20 proxy
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ erc20Proxy = await erc20Wrapper.deployProxyAsync();
+ await erc20Wrapper.setBalancesAndAllowancesAsync();
+ // Deploy ERC721 token and proxy
+ [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
+ erc721Proxy = await erc721Wrapper.deployProxyAsync();
+ await erc721Wrapper.setBalancesAndAllowancesAsync();
+ const erc721Balances = await erc721Wrapper.getBalancesAsync();
+ erc721LeftMakerAssetIds = erc721Balances[makerAddressLeft][erc721Token.address];
+ erc721RightMakerAssetIds = erc721Balances[makerAddressRight][erc721Token.address];
+ erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address];
+ // Depoy exchange
+ exchange = await ExchangeContract.deployFrom0xArtifactAsync(
+ artifacts.Exchange,
+ provider,
+ txDefaults,
+ assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
+ );
+ zeroEx = new ZeroEx(provider, {
+ exchangeContractAddress: exchange.address,
+ networkId: constants.TESTRPC_NETWORK_ID,
+ });
+ exchangeWrapper = new ExchangeWrapper(exchange, zeroEx);
+ await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
+ await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC721, erc721Proxy.address, owner);
+ // Authorize ERC20 and ERC721 trades by exchange
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Set default addresses
+ defaultERC20MakerAssetAddress = erc20TokenA.address;
+ defaultERC20TakerAssetAddress = erc20TokenB.address;
+ defaultERC721AssetAddress = erc721Token.address;
+ // Create default order parameters
+ const defaultOrderParams = {
+ ...constants.STATIC_ORDER_PARAMS,
+ exchangeAddress: exchange.address,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ };
+ const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)];
+ orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParams);
+ const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)];
+ orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParams);
+ // Set match order tester
+ matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper, zrxToken.address);
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('matchOrders', () => {
+ beforeEach(async () => {
+ erc20BalancesByOwner = await erc20Wrapper.getBalancesAsync();
+ erc721TokenIdsByOwner = await erc721Wrapper.getBalancesAsync();
+ });
+
+ it('should transfer the correct amounts when orders completely fill each other', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match signedOrderLeft with signedOrderRight
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was fully filled
+ const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify right order was fully filled
+ const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
+ expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ });
+
+ it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Store original taker balance
+ const takerInitialBalances = _.cloneDeep(erc20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress]);
+ // Match signedOrderLeft with signedOrderRight
+ let newERC20BalancesByOwner: ERC20BalancesByOwner;
+ let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
+ [
+ newERC20BalancesByOwner,
+ newERC721TokenIdsByOwner,
+ ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was fully filled
+ const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify right order was fully filled
+ const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
+ expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify taker did not take a profit
+ expect(takerInitialBalances).to.be.deep.equal(
+ newERC20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress],
+ );
+ });
+
+ it('should transfer the correct amounts when left order is completely filled and right order is partially filled', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(20), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(4), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was fully filled
+ const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify right order was partially filled
+ const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
+ expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
+ });
+
+ it('should transfer the correct amounts when right order is completely filled and left order is partially filled', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was partially filled
+ const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
+ // Verify right order was fully filled
+ const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
+ expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ });
+
+ it('should transfer the correct amounts when consecutive calls are used to completely fill the left order', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ let newERC20BalancesByOwner: ERC20BalancesByOwner;
+ let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
+ [
+ newERC20BalancesByOwner,
+ newERC721TokenIdsByOwner,
+ ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was partially filled
+ const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
+ // Verify right order was fully filled
+ const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
+ expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Construct second right order
+ // Note: This order needs makerAssetAmount=90/takerAssetAmount=[anything <= 45] to fully fill the right order.
+ // However, we use 100/50 to ensure a partial fill as we want to go down the "left fill"
+ // branch in the contract twice for this test.
+ const signedOrderRight2 = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match signedOrderLeft with signedOrderRight2
+ const leftTakerAssetFilledAmount = signedOrderRight.makerAssetAmount;
+ const rightTakerAssetFilledAmount = new BigNumber(0);
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight2,
+ takerAddress,
+ newERC20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ leftTakerAssetFilledAmount,
+ rightTakerAssetFilledAmount,
+ );
+ // Verify left order was fully filled
+ const leftOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo2.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify second right order was partially filled
+ const rightOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight2);
+ expect(rightOrderInfo2.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
+ });
+
+ it('should transfer the correct amounts when consecutive calls are used to completely fill the right order', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ let newERC20BalancesByOwner: ERC20BalancesByOwner;
+ let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
+ [
+ newERC20BalancesByOwner,
+ newERC721TokenIdsByOwner,
+ ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was partially filled
+ const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify right order was fully filled
+ const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
+ expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
+ // Create second left order
+ // Note: This order needs makerAssetAmount=96/takerAssetAmount=48 to fully fill the right order.
+ // However, we use 100/50 to ensure a partial fill as we want to go down the "right fill"
+ // branch in the contract twice for this test.
+ const signedOrderLeft2 = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ // Match signedOrderLeft2 with signedOrderRight
+ const leftTakerAssetFilledAmount = new BigNumber(0);
+ const takerAmountReceived = newERC20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress].minus(
+ erc20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress],
+ );
+ const rightTakerAssetFilledAmount = signedOrderLeft.makerAssetAmount.minus(takerAmountReceived);
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft2,
+ signedOrderRight,
+ takerAddress,
+ newERC20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ leftTakerAssetFilledAmount,
+ rightTakerAssetFilledAmount,
+ );
+ // Verify second left order was partially filled
+ const leftOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft2);
+ expect(leftOrderInfo2.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
+ // Verify right order was fully filled
+ const rightOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
+ expect(rightOrderInfo2.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ });
+
+ it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => {
+ const feeRecipientAddress = feeRecipientAddressLeft;
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress,
+ });
+ // Match orders
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ });
+
+ it('should transfer the correct amounts if taker is also the left order maker', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ takerAddress = signedOrderLeft.makerAddress;
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ });
+
+ it('should transfer the correct amounts if taker is also the right order maker', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ takerAddress = signedOrderRight.makerAddress;
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ });
+
+ it('should transfer the correct amounts if taker is also the left fee recipient', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ takerAddress = feeRecipientAddressLeft;
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ });
+
+ it('should transfer the correct amounts if taker is also the right fee recipient', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ takerAddress = feeRecipientAddressRight;
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ });
+
+ it('should transfer the correct amounts if left maker is the left fee recipient and right maker is the right fee recipient', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: makerAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: makerAddressRight,
+ });
+ // Match orders
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ });
+
+ it('Should throw if left order is not fillable', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Cancel left order
+ await exchangeWrapper.cancelOrderAsync(signedOrderLeft, signedOrderLeft.makerAddress);
+ // Match orders
+ return expect(
+ exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('Should throw if right order is not fillable', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Cancel right order
+ await exchangeWrapper.cancelOrderAsync(signedOrderRight, signedOrderRight.makerAddress);
+ // Match orders
+ return expect(
+ exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw if there is not a positive spread', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ return expect(
+ matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw if the left maker asset is not equal to the right taker asset ', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ return expect(
+ matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw if the right maker asset is not equal to the left taker asset', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ return expect(
+ matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should transfer correct amounts when left order maker asset is an ERC721 token', async () => {
+ // Create orders to match
+ const erc721TokenToTransfer = erc721LeftMakerAssetIds[0];
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: new BigNumber(1),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: new BigNumber(1),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was fully filled
+ const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify right order was fully filled
+ const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
+ expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ });
+
+ it('should transfer correct amounts when right order maker asset is an ERC721 token', async () => {
+ // Create orders to match
+ const erc721TokenToTransfer = erc721RightMakerAssetIds[0];
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: new BigNumber(1),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: new BigNumber(1),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was fully filled
+ const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify right order was fully filled
+ const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
+ expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ });
+ });
+}); // tslint:disable-line:max-file-line-count
diff --git a/packages/contracts/test/exchange/signature_validator.ts b/packages/contracts/test/exchange/signature_validator.ts
new file mode 100644
index 000000000..d96b9e3fb
--- /dev/null
+++ b/packages/contracts/test/exchange/signature_validator.ts
@@ -0,0 +1,98 @@
+import { ZeroEx } from '0x.js';
+import { BlockchainLifecycle } from '@0xproject/dev-utils';
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import ethUtil = require('ethereumjs-util');
+
+import { TestSignatureValidatorContract } from '../../src/contract_wrappers/generated/test_signature_validator';
+import { addressUtils } from '../../src/utils/address_utils';
+import { artifacts } from '../../src/utils/artifacts';
+import { assetProxyUtils } from '../../src/utils/asset_proxy_utils';
+import { chaiSetup } from '../../src/utils/chai_setup';
+import { constants } from '../../src/utils/constants';
+import { OrderFactory } from '../../src/utils/order_factory';
+import { orderUtils } from '../../src/utils/order_utils';
+import { SignedOrder } from '../../src/utils/types';
+import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('MixinSignatureValidator', () => {
+ let signedOrder: SignedOrder;
+ let orderFactory: OrderFactory;
+ let signatureValidator: TestSignatureValidatorContract;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ const makerAddress = accounts[0];
+ signatureValidator = await TestSignatureValidatorContract.deployFrom0xArtifactAsync(
+ artifacts.TestSignatureValidator,
+ provider,
+ txDefaults,
+ );
+ const zeroEx = new ZeroEx(provider, { networkId: constants.TESTRPC_NETWORK_ID });
+
+ const defaultOrderParams = {
+ ...constants.STATIC_ORDER_PARAMS,
+ exchangeAddress: signatureValidator.address,
+ makerAddress,
+ feeRecipientAddress: addressUtils.generatePseudoRandomAddress(),
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(addressUtils.generatePseudoRandomAddress()),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(addressUtils.generatePseudoRandomAddress()),
+ };
+ const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
+ orderFactory = new OrderFactory(privateKey, defaultOrderParams);
+ });
+
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ signedOrder = orderFactory.newSignedOrder();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ describe('isValidSignature', () => {
+ beforeEach(async () => {
+ signedOrder = orderFactory.newSignedOrder();
+ });
+
+ it('should return true with a valid signature', async () => {
+ const orderHashHex = orderUtils.getOrderHashHex(signedOrder);
+ const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
+ orderHashHex,
+ signedOrder.makerAddress,
+ signedOrder.signature,
+ );
+ expect(isValidSignature).to.be.true();
+ });
+
+ it('should return false with an invalid signature', async () => {
+ const invalidR = ethUtil.sha3('invalidR');
+ const invalidS = ethUtil.sha3('invalidS');
+ const invalidSigBuff = Buffer.concat([
+ ethUtil.toBuffer(signedOrder.signature.slice(0, 6)),
+ invalidR,
+ invalidS,
+ ]);
+ const invalidSigHex = `0x${invalidSigBuff.toString('hex')}`;
+ signedOrder.signature = invalidSigHex;
+ const orderHashHex = orderUtils.getOrderHashHex(signedOrder);
+ const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
+ orderHashHex,
+ signedOrder.makerAddress,
+ signedOrder.signature,
+ );
+ expect(isValidSignature).to.be.false();
+ });
+ });
+});
diff --git a/packages/contracts/test/exchange/transactions.ts b/packages/contracts/test/exchange/transactions.ts
new file mode 100644
index 000000000..798c8283b
--- /dev/null
+++ b/packages/contracts/test/exchange/transactions.ts
@@ -0,0 +1,215 @@
+import { ZeroEx } from '0x.js';
+
+import { BlockchainLifecycle } from '@0xproject/dev-utils';
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import * as ethUtil from 'ethereumjs-util';
+import * as Web3 from 'web3';
+
+import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token';
+import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy';
+import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange';
+import { artifacts } from '../../src/utils/artifacts';
+import { assetProxyUtils } from '../../src/utils/asset_proxy_utils';
+import { chaiSetup } from '../../src/utils/chai_setup';
+import { constants } from '../../src/utils/constants';
+import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
+import { ExchangeWrapper } from '../../src/utils/exchange_wrapper';
+import { OrderFactory } from '../../src/utils/order_factory';
+import { orderUtils } from '../../src/utils/order_utils';
+import { TransactionFactory } from '../../src/utils/transaction_factory';
+import {
+ AssetProxyId,
+ ERC20BalancesByOwner,
+ ExchangeStatus,
+ OrderStruct,
+ SignatureType,
+ SignedOrder,
+ SignedTransaction,
+} from '../../src/utils/types';
+import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('Exchange transactions', () => {
+ let senderAddress: string;
+ let owner: string;
+ let makerAddress: string;
+ let takerAddress: string;
+ let feeRecipientAddress: string;
+
+ let erc20TokenA: DummyERC20TokenContract;
+ let erc20TokenB: DummyERC20TokenContract;
+ let zrxToken: DummyERC20TokenContract;
+ let exchange: ExchangeContract;
+ let erc20Proxy: ERC20ProxyContract;
+
+ let erc20Balances: ERC20BalancesByOwner;
+ let signedOrder: SignedOrder;
+ let signedTx: SignedTransaction;
+ let order: OrderStruct;
+ let orderFactory: OrderFactory;
+ let makerTransactionFactory: TransactionFactory;
+ let takerTransactionFactory: TransactionFactory;
+ let exchangeWrapper: ExchangeWrapper;
+ let erc20Wrapper: ERC20Wrapper;
+
+ let defaultMakerTokenAddress: string;
+ let defaultTakerTokenAddress: string;
+
+ let zeroEx: ZeroEx;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ const usedAddresses = ([owner, senderAddress, makerAddress, takerAddress, feeRecipientAddress] = accounts);
+
+ erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ erc20Proxy = await erc20Wrapper.deployProxyAsync();
+ await erc20Wrapper.setBalancesAndAllowancesAsync();
+
+ exchange = await ExchangeContract.deployFrom0xArtifactAsync(
+ artifacts.Exchange,
+ provider,
+ txDefaults,
+ assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
+ );
+ zeroEx = new ZeroEx(provider, {
+ exchangeContractAddress: exchange.address,
+ networkId: constants.TESTRPC_NETWORK_ID,
+ });
+ exchangeWrapper = new ExchangeWrapper(exchange, zeroEx);
+ await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
+
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ defaultMakerTokenAddress = erc20TokenA.address;
+ defaultTakerTokenAddress = erc20TokenB.address;
+
+ const defaultOrderParams = {
+ ...constants.STATIC_ORDER_PARAMS,
+ senderAddress,
+ exchangeAddress: exchange.address,
+ makerAddress,
+ feeRecipientAddress,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultMakerTokenAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultTakerTokenAddress),
+ };
+ const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
+ const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)];
+ orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams);
+ makerTransactionFactory = new TransactionFactory(makerPrivateKey, exchange.address);
+ takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchange.address);
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ describe('executeTransaction', () => {
+ describe('fillOrder', () => {
+ let takerAssetFillAmount: BigNumber;
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ signedOrder = orderFactory.newSignedOrder();
+ order = orderUtils.getOrderStruct(signedOrder);
+
+ takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ const data = exchange.fillOrder.getABIEncodedTransactionData(
+ order,
+ takerAssetFillAmount,
+ signedOrder.signature,
+ );
+ signedTx = takerTransactionFactory.newSignedTransaction(data);
+ });
+
+ it('should throw if not called by specified sender', async () => {
+ return expect(exchangeWrapper.executeTransactionAsync(signedTx, takerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
+ });
+
+ it('should transfer the correct amounts when signed by taker and called by sender', async () => {
+ await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(signedOrder.makerAssetAmount)
+ .dividedToIntegerBy(signedOrder.takerAssetAmount);
+ const makerFeePaid = signedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ const takerFeePaid = signedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerTokenAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerTokenAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
+ );
+ });
+
+ it('should throw if the a 0x transaction with the same transactionHash has already been executed', async () => {
+ await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress);
+ return expect(exchangeWrapper.executeTransactionAsync(signedTx, senderAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
+ });
+
+ it('should reset the currentContextAddress', async () => {
+ await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress);
+ const currentContextAddress = await exchange.currentContextAddress.callAsync();
+ expect(currentContextAddress).to.equal(ZeroEx.NULL_ADDRESS);
+ });
+ });
+
+ describe('cancelOrder', () => {
+ beforeEach(async () => {
+ const data = exchange.cancelOrder.getABIEncodedTransactionData(order);
+ signedTx = makerTransactionFactory.newSignedTransaction(data);
+ });
+
+ it('should throw if not called by specified sender', async () => {
+ return expect(exchangeWrapper.executeTransactionAsync(signedTx, makerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
+ });
+
+ it('should cancel the order when signed by maker and called by sender', async () => {
+ await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress);
+ const res = await exchangeWrapper.fillOrderAsync(signedOrder, senderAddress);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.deep.equal(erc20Balances);
+ });
+ });
+ });
+});
diff --git a/packages/contracts/test/exchange/wrapper.ts b/packages/contracts/test/exchange/wrapper.ts
index cda1432c1..caaceb460 100644
--- a/packages/contracts/test/exchange/wrapper.ts
+++ b/packages/contracts/test/exchange/wrapper.ts
@@ -1,4 +1,4 @@
-import { SignedOrder, ZeroEx } from '0x.js';
+import { ZeroEx } from '0x.js';
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
@@ -7,384 +7,954 @@ import * as _ from 'lodash';
import 'make-promises-safe';
import * as Web3 from 'web3';
-import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token';
-import {
- ExchangeContract,
- LogCancelContractEventArgs,
- LogErrorContractEventArgs,
- LogFillContractEventArgs,
-} from '../../src/contract_wrappers/generated/exchange';
+import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token';
+import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token';
+import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy';
+import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy';
+import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange';
import { TokenRegistryContract } from '../../src/contract_wrappers/generated/token_registry';
-import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy';
-import { artifacts } from '../../util/artifacts';
-import { Balances } from '../../util/balances';
-import { constants } from '../../util/constants';
-import { ExchangeWrapper } from '../../util/exchange_wrapper';
-import { OrderFactory } from '../../util/order_factory';
-import { BalancesByOwner, ContractName } from '../../util/types';
-import { chaiSetup } from '../utils/chai_setup';
-
-import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
+import { artifacts } from '../../src/utils/artifacts';
+import { assetProxyUtils } from '../../src/utils/asset_proxy_utils';
+import { chaiSetup } from '../../src/utils/chai_setup';
+import { constants } from '../../src/utils/constants';
+import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
+import { ERC721Wrapper } from '../../src/utils/erc721_wrapper';
+import { ExchangeWrapper } from '../../src/utils/exchange_wrapper';
+import { OrderFactory } from '../../src/utils/order_factory';
+import { AssetProxyId, ERC20BalancesByOwner, SignedOrder } from '../../src/utils/types';
+import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
-describe('Exchange', () => {
- let maker: string;
- let tokenOwner: string;
- let taker: string;
- let feeRecipient: string;
-
- const INIT_BAL = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
- const INIT_ALLOW = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
+describe('Exchange wrappers', () => {
+ let makerAddress: string;
+ let owner: string;
+ let takerAddress: string;
+ let feeRecipientAddress: string;
- let rep: DummyTokenContract;
- let dgd: DummyTokenContract;
- let zrx: DummyTokenContract;
+ let erc20TokenA: DummyERC20TokenContract;
+ let erc20TokenB: DummyERC20TokenContract;
+ let zrxToken: DummyERC20TokenContract;
+ let erc721Token: DummyERC721TokenContract;
let exchange: ExchangeContract;
- let tokenRegistry: TokenRegistryContract;
- let tokenTransferProxy: TokenTransferProxyContract;
+ let erc20Proxy: ERC20ProxyContract;
+ let erc721Proxy: ERC721ProxyContract;
- let balances: BalancesByOwner;
-
- let exWrapper: ExchangeWrapper;
- let dmyBalances: Balances;
+ let exchangeWrapper: ExchangeWrapper;
+ let erc20Wrapper: ERC20Wrapper;
+ let erc721Wrapper: ERC721Wrapper;
+ let erc20Balances: ERC20BalancesByOwner;
let orderFactory: OrderFactory;
+ let erc721MakerAssetId: BigNumber;
+ let erc721TakerAssetId: BigNumber;
+
+ let defaultMakerAssetAddress: string;
+ let defaultTakerAssetAddress: string;
+
+ let zeroEx: ZeroEx;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- tokenOwner = accounts[0];
- [maker, taker, feeRecipient] = accounts;
- [rep, dgd, zrx] = await Promise.all([
- DummyTokenContract.deployFrom0xArtifactAsync(
- artifacts.DummyToken,
- provider,
- txDefaults,
- constants.DUMMY_TOKEN_NAME,
- constants.DUMMY_TOKEN_SYMBOL,
- constants.DUMMY_TOKEN_DECIMALS,
- constants.DUMMY_TOKEN_TOTAL_SUPPLY,
- ),
- DummyTokenContract.deployFrom0xArtifactAsync(
- artifacts.DummyToken,
- provider,
- txDefaults,
- constants.DUMMY_TOKEN_NAME,
- constants.DUMMY_TOKEN_SYMBOL,
- constants.DUMMY_TOKEN_DECIMALS,
- constants.DUMMY_TOKEN_TOTAL_SUPPLY,
- ),
- DummyTokenContract.deployFrom0xArtifactAsync(
- artifacts.DummyToken,
- provider,
- txDefaults,
- constants.DUMMY_TOKEN_NAME,
- constants.DUMMY_TOKEN_SYMBOL,
- constants.DUMMY_TOKEN_DECIMALS,
- constants.DUMMY_TOKEN_TOTAL_SUPPLY,
- ),
- ]);
- tokenRegistry = await TokenRegistryContract.deployFrom0xArtifactAsync(
- artifacts.TokenRegistry,
- provider,
- txDefaults,
- );
- tokenTransferProxy = await TokenTransferProxyContract.deployFrom0xArtifactAsync(
- artifacts.TokenTransferProxy,
- provider,
- txDefaults,
- );
+ const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts);
+
+ erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+ erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
+
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ erc20Proxy = await erc20Wrapper.deployProxyAsync();
+ await erc20Wrapper.setBalancesAndAllowancesAsync();
+
+ [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
+ erc721Proxy = await erc721Wrapper.deployProxyAsync();
+ await erc721Wrapper.setBalancesAndAllowancesAsync();
+ const erc721Balances = await erc721Wrapper.getBalancesAsync();
+ erc721MakerAssetId = erc721Balances[makerAddress][erc721Token.address][0];
+ erc721TakerAssetId = erc721Balances[takerAddress][erc721Token.address][0];
+
exchange = await ExchangeContract.deployFrom0xArtifactAsync(
artifacts.Exchange,
provider,
txDefaults,
- zrx.address,
- tokenTransferProxy.address,
+ assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
);
- await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] });
- const zeroEx = new ZeroEx(provider, { networkId: constants.TESTRPC_NETWORK_ID });
- exWrapper = new ExchangeWrapper(exchange, zeroEx);
+ zeroEx = new ZeroEx(provider, {
+ exchangeContractAddress: exchange.address,
+ networkId: constants.TESTRPC_NETWORK_ID,
+ });
+ exchangeWrapper = new ExchangeWrapper(exchange, zeroEx);
+ await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
+ await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC721, erc721Proxy.address, owner);
+
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ defaultMakerAssetAddress = erc20TokenA.address;
+ defaultTakerAssetAddress = erc20TokenB.address;
const defaultOrderParams = {
- exchangeContractAddress: exchange.address,
- maker,
- feeRecipient,
- makerTokenAddress: rep.address,
- takerTokenAddress: dgd.address,
- makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
- takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
- makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
- takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
+ ...constants.STATIC_ORDER_PARAMS,
+ exchangeAddress: exchange.address,
+ makerAddress,
+ feeRecipientAddress,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultMakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultTakerAssetAddress),
};
-
- orderFactory = new OrderFactory(zeroEx, defaultOrderParams);
- dmyBalances = new Balances([rep, dgd, zrx], [maker, taker, feeRecipient]);
- await Promise.all([
- rep.approve.sendTransactionAsync(tokenTransferProxy.address, INIT_ALLOW, { from: maker }),
- rep.approve.sendTransactionAsync(tokenTransferProxy.address, INIT_ALLOW, { from: taker }),
- rep.setBalance.sendTransactionAsync(maker, INIT_BAL, { from: tokenOwner }),
- rep.setBalance.sendTransactionAsync(taker, INIT_BAL, { from: tokenOwner }),
- dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INIT_ALLOW, { from: maker }),
- dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INIT_ALLOW, { from: taker }),
- dgd.setBalance.sendTransactionAsync(maker, INIT_BAL, { from: tokenOwner }),
- dgd.setBalance.sendTransactionAsync(taker, INIT_BAL, { from: tokenOwner }),
- zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INIT_ALLOW, { from: maker }),
- zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INIT_ALLOW, { from: taker }),
- zrx.setBalance.sendTransactionAsync(maker, INIT_BAL, { from: tokenOwner }),
- zrx.setBalance.sendTransactionAsync(taker, INIT_BAL, { from: tokenOwner }),
- ]);
+ const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
+ orderFactory = new OrderFactory(privateKey, defaultOrderParams);
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('fillOrKillOrder', () => {
- beforeEach(async () => {
- balances = await dmyBalances.getAsync();
+ it('should transfer the correct amounts', async () => {
+ const signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
+ });
+ const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ await exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress, {
+ takerAssetFillAmount,
+ });
+
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const makerAssetFilledAmount = takerAssetFillAmount
+ .times(signedOrder.makerAssetAmount)
+ .dividedToIntegerBy(signedOrder.takerAssetAmount);
+ const makerFee = signedOrder.makerFee
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ const takerFee = signedOrder.takerFee
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount),
+ );
+ expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerFee),
+ );
+ expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(takerFee),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFee.add(takerFee)),
+ );
});
+ it('should throw if an signedOrder is expired', async () => {
+ const signedOrder = orderFactory.newSignedOrder({
+ expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
+ });
+
+ return expect(exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
+ });
+
+ it('should throw if entire takerAssetFillAmount not filled', async () => {
+ const signedOrder = orderFactory.newSignedOrder();
+
+ await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
+ takerAssetFillAmount: signedOrder.takerAssetAmount.div(2),
+ });
+
+ return expect(exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith(
+ constants.REVERT,
+ );
+ });
+ });
+
+ describe('fillOrderNoThrow', () => {
it('should transfer the correct amounts', async () => {
- const signedOrder = await orderFactory.newSignedOrderAsync({
- makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
- takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
+ const signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
});
- const fillTakerTokenAmount = signedOrder.takerTokenAmount.div(2);
- await exWrapper.fillOrKillOrderAsync(signedOrder, taker, {
- fillTakerTokenAmount,
+ const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress, {
+ takerAssetFillAmount,
});
- const newBalances = await dmyBalances.getAsync();
+ const newBalances = await erc20Wrapper.getBalancesAsync();
- const fillMakerTokenAmount = fillTakerTokenAmount
- .times(signedOrder.makerTokenAmount)
- .dividedToIntegerBy(signedOrder.takerTokenAmount);
+ const makerAssetFilledAmount = takerAssetFillAmount
+ .times(signedOrder.makerAssetAmount)
+ .dividedToIntegerBy(signedOrder.takerAssetAmount);
const makerFee = signedOrder.makerFee
- .times(fillMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
const takerFee = signedOrder.takerFee
- .times(fillMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
- expect(newBalances[maker][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrder.makerTokenAddress].minus(fillMakerTokenAmount),
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount),
+ );
+ expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
);
- expect(newBalances[maker][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrder.takerTokenAddress].add(fillTakerTokenAmount),
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerFee),
);
- expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(balances[maker][zrx.address].minus(makerFee));
- expect(newBalances[taker][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrder.takerTokenAddress].minus(fillTakerTokenAmount),
+ expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
);
- expect(newBalances[taker][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrder.makerTokenAddress].add(fillMakerTokenAmount),
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount),
);
- expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(balances[taker][zrx.address].minus(takerFee));
- expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal(
- balances[feeRecipient][zrx.address].add(makerFee.add(takerFee)),
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(takerFee),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFee.add(takerFee)),
);
});
- it('should throw if an signedOrder is expired', async () => {
- const signedOrder = await orderFactory.newSignedOrderAsync({
- expirationUnixTimestampSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
+ it('should not change erc20Balances if maker erc20Balances are too low to fill order', async () => {
+ const signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18),
+ });
+
+ await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
+ });
+
+ it('should not change erc20Balances if taker erc20Balances are too low to fill order', async () => {
+ const signedOrder = orderFactory.newSignedOrder({
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18),
+ });
+
+ await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
+ });
+
+ it('should not change erc20Balances if maker allowances are too low to fill order', async () => {
+ const signedOrder = orderFactory.newSignedOrder();
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, constants.INITIAL_ERC20_ALLOWANCE, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
+ });
+
+ it('should not change erc20Balances if taker allowances are too low to fill order', async () => {
+ const signedOrder = orderFactory.newSignedOrder();
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, constants.INITIAL_ERC20_ALLOWANCE, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
+ });
+
+ it('should not change erc20Balances if makerAssetAddress is ZRX, makerAssetAmount + makerFee > maker balance', async () => {
+ const makerZRXBalance = new BigNumber(erc20Balances[makerAddress][zrxToken.address]);
+ const signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: makerZRXBalance,
+ makerFee: new BigNumber(1),
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
});
+ await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
+ });
- return expect(exWrapper.fillOrKillOrderAsync(signedOrder, taker)).to.be.rejectedWith(constants.REVERT);
+ it('should not change erc20Balances if makerAssetAddress is ZRX, makerAssetAmount + makerFee > maker allowance', async () => {
+ const makerZRXAllowance = await zrxToken.allowance.callAsync(makerAddress, erc20Proxy.address);
+ const signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: new BigNumber(makerZRXAllowance),
+ makerFee: new BigNumber(1),
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
+ });
+ await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
});
- it('should throw if entire fillTakerTokenAmount not filled', async () => {
- const signedOrder = await orderFactory.newSignedOrderAsync();
+ it('should not change erc20Balances if takerAssetAddress is ZRX, takerAssetAmount + takerFee > taker balance', async () => {
+ const takerZRXBalance = new BigNumber(erc20Balances[takerAddress][zrxToken.address]);
+ const signedOrder = orderFactory.newSignedOrder({
+ takerAssetAmount: takerZRXBalance,
+ takerFee: new BigNumber(1),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
+ });
+ await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
+ });
- const from = taker;
- await exWrapper.fillOrderAsync(signedOrder, from, {
- fillTakerTokenAmount: signedOrder.takerTokenAmount.div(2),
+ it('should not change erc20Balances if takerAssetAddress is ZRX, takerAssetAmount + takerFee > taker allowance', async () => {
+ const takerZRXAllowance = await zrxToken.allowance.callAsync(takerAddress, erc20Proxy.address);
+ const signedOrder = orderFactory.newSignedOrder({
+ takerAssetAmount: new BigNumber(takerZRXAllowance),
+ takerFee: new BigNumber(1),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
});
+ await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
+ });
- return expect(exWrapper.fillOrKillOrderAsync(signedOrder, taker)).to.be.rejectedWith(constants.REVERT);
+ it('should successfully exchange ERC721 tokens', async () => {
+ // Construct Exchange parameters
+ const makerAssetId = erc721MakerAssetId;
+ const takerAssetId = erc721TakerAssetId;
+ const signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: new BigNumber(1),
+ takerAssetAmount: new BigNumber(1),
+ makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId),
+ takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId),
+ });
+ // Verify pre-conditions
+ const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
+ expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress);
+ const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
+ expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
+ // Call Exchange
+ const takerAssetFillAmount = signedOrder.takerAssetAmount;
+ await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress, { takerAssetFillAmount });
+ // Verify post-conditions
+ const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
+ expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress);
+ const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
+ expect(newOwnerTakerAsset).to.be.bignumber.equal(makerAddress);
});
});
describe('batch functions', () => {
let signedOrders: SignedOrder[];
beforeEach(async () => {
- signedOrders = await Promise.all([
- orderFactory.newSignedOrderAsync(),
- orderFactory.newSignedOrderAsync(),
- orderFactory.newSignedOrderAsync(),
- ]);
- balances = await dmyBalances.getAsync();
+ signedOrders = [
+ orderFactory.newSignedOrder(),
+ orderFactory.newSignedOrder(),
+ orderFactory.newSignedOrder(),
+ ];
});
describe('batchFillOrders', () => {
it('should transfer the correct amounts', async () => {
- const fillTakerTokenAmounts: BigNumber[] = [];
- const makerTokenAddress = rep.address;
- const takerTokenAddress = dgd.address;
- signedOrders.forEach(signedOrder => {
- const fillTakerTokenAmount = signedOrder.takerTokenAmount.div(2);
- const fillMakerTokenAmount = fillTakerTokenAmount
- .times(signedOrder.makerTokenAmount)
- .dividedToIntegerBy(signedOrder.takerTokenAmount);
+ const takerAssetFillAmounts: BigNumber[] = [];
+ const makerAssetAddress = erc20TokenA.address;
+ const takerAssetAddress = erc20TokenB.address;
+ _.forEach(signedOrders, signedOrder => {
+ const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ const makerAssetFilledAmount = takerAssetFillAmount
+ .times(signedOrder.makerAssetAmount)
+ .dividedToIntegerBy(signedOrder.takerAssetAmount);
const makerFee = signedOrder.makerFee
- .times(fillMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
const takerFee = signedOrder.takerFee
- .times(fillMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
- fillTakerTokenAmounts.push(fillTakerTokenAmount);
- balances[maker][makerTokenAddress] = balances[maker][makerTokenAddress].minus(fillMakerTokenAmount);
- balances[maker][takerTokenAddress] = balances[maker][takerTokenAddress].add(fillTakerTokenAmount);
- balances[maker][zrx.address] = balances[maker][zrx.address].minus(makerFee);
- balances[taker][makerTokenAddress] = balances[taker][makerTokenAddress].add(fillMakerTokenAmount);
- balances[taker][takerTokenAddress] = balances[taker][takerTokenAddress].minus(fillTakerTokenAmount);
- balances[taker][zrx.address] = balances[taker][zrx.address].minus(takerFee);
- balances[feeRecipient][zrx.address] = balances[feeRecipient][zrx.address].add(
- makerFee.add(takerFee),
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ takerAssetFillAmounts.push(takerAssetFillAmount);
+ erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][
+ makerAssetAddress
+ ].minus(makerAssetFilledAmount);
+ erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][takerAssetAddress].add(
+ takerAssetFillAmount,
+ );
+ erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus(
+ makerFee,
+ );
+ erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][makerAssetAddress].add(
+ makerAssetFilledAmount,
+ );
+ erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][
+ takerAssetAddress
+ ].minus(takerAssetFillAmount);
+ erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus(
+ takerFee,
);
+ erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][
+ zrxToken.address
+ ].add(makerFee.add(takerFee));
});
- await exWrapper.batchFillOrdersAsync(signedOrders, taker, {
- fillTakerTokenAmounts,
+ await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, {
+ takerAssetFillAmounts,
});
- const newBalances = await dmyBalances.getAsync();
- expect(newBalances).to.be.deep.equal(balances);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
});
});
describe('batchFillOrKillOrders', () => {
it('should transfer the correct amounts', async () => {
- const fillTakerTokenAmounts: BigNumber[] = [];
- const makerTokenAddress = rep.address;
- const takerTokenAddress = dgd.address;
- signedOrders.forEach(signedOrder => {
- const fillTakerTokenAmount = signedOrder.takerTokenAmount.div(2);
- const fillMakerTokenAmount = fillTakerTokenAmount
- .times(signedOrder.makerTokenAmount)
- .dividedToIntegerBy(signedOrder.takerTokenAmount);
+ const takerAssetFillAmounts: BigNumber[] = [];
+ const makerAssetAddress = erc20TokenA.address;
+ const takerAssetAddress = erc20TokenB.address;
+ _.forEach(signedOrders, signedOrder => {
+ const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ const makerAssetFilledAmount = takerAssetFillAmount
+ .times(signedOrder.makerAssetAmount)
+ .dividedToIntegerBy(signedOrder.takerAssetAmount);
const makerFee = signedOrder.makerFee
- .times(fillMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
const takerFee = signedOrder.takerFee
- .times(fillMakerTokenAmount)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
- fillTakerTokenAmounts.push(fillTakerTokenAmount);
- balances[maker][makerTokenAddress] = balances[maker][makerTokenAddress].minus(fillMakerTokenAmount);
- balances[maker][takerTokenAddress] = balances[maker][takerTokenAddress].add(fillTakerTokenAmount);
- balances[maker][zrx.address] = balances[maker][zrx.address].minus(makerFee);
- balances[taker][makerTokenAddress] = balances[taker][makerTokenAddress].add(fillMakerTokenAmount);
- balances[taker][takerTokenAddress] = balances[taker][takerTokenAddress].minus(fillTakerTokenAmount);
- balances[taker][zrx.address] = balances[taker][zrx.address].minus(takerFee);
- balances[feeRecipient][zrx.address] = balances[feeRecipient][zrx.address].add(
- makerFee.add(takerFee),
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ takerAssetFillAmounts.push(takerAssetFillAmount);
+ erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][
+ makerAssetAddress
+ ].minus(makerAssetFilledAmount);
+ erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][takerAssetAddress].add(
+ takerAssetFillAmount,
);
+ erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus(
+ makerFee,
+ );
+ erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][makerAssetAddress].add(
+ makerAssetFilledAmount,
+ );
+ erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][
+ takerAssetAddress
+ ].minus(takerAssetFillAmount);
+ erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus(
+ takerFee,
+ );
+ erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][
+ zrxToken.address
+ ].add(makerFee.add(takerFee));
});
- await exWrapper.batchFillOrKillOrdersAsync(signedOrders, taker, {
- fillTakerTokenAmounts,
+ await exchangeWrapper.batchFillOrKillOrdersAsync(signedOrders, takerAddress, {
+ takerAssetFillAmounts,
});
- const newBalances = await dmyBalances.getAsync();
- expect(newBalances).to.be.deep.equal(balances);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
});
it('should throw if a single signedOrder does not fill the expected amount', async () => {
- const fillTakerTokenAmounts: BigNumber[] = [];
- signedOrders.forEach(signedOrder => {
- const fillTakerTokenAmount = signedOrder.takerTokenAmount.div(2);
- fillTakerTokenAmounts.push(fillTakerTokenAmount);
+ const takerAssetFillAmounts: BigNumber[] = [];
+ _.forEach(signedOrders, signedOrder => {
+ const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ takerAssetFillAmounts.push(takerAssetFillAmount);
});
- await exWrapper.fillOrKillOrderAsync(signedOrders[0], taker);
+ await exchangeWrapper.fillOrKillOrderAsync(signedOrders[0], takerAddress);
return expect(
- exWrapper.batchFillOrKillOrdersAsync(signedOrders, taker, {
- fillTakerTokenAmounts,
+ exchangeWrapper.batchFillOrKillOrdersAsync(signedOrders, takerAddress, {
+ takerAssetFillAmounts,
}),
).to.be.rejectedWith(constants.REVERT);
});
});
- describe('fillOrdersUpTo', () => {
- it('should stop when the entire fillTakerTokenAmount is filled', async () => {
- const fillTakerTokenAmount = signedOrders[0].takerTokenAmount.plus(
- signedOrders[1].takerTokenAmount.div(2),
+ describe('batchFillOrdersNoThrow', async () => {
+ it('should transfer the correct amounts', async () => {
+ const takerAssetFillAmounts: BigNumber[] = [];
+ const makerAssetAddress = erc20TokenA.address;
+ const takerAssetAddress = erc20TokenB.address;
+ _.forEach(signedOrders, signedOrder => {
+ const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ const makerAssetFilledAmount = takerAssetFillAmount
+ .times(signedOrder.makerAssetAmount)
+ .dividedToIntegerBy(signedOrder.takerAssetAmount);
+ const makerFee = signedOrder.makerFee
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ const takerFee = signedOrder.takerFee
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ takerAssetFillAmounts.push(takerAssetFillAmount);
+ erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][
+ makerAssetAddress
+ ].minus(makerAssetFilledAmount);
+ erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][takerAssetAddress].add(
+ takerAssetFillAmount,
+ );
+ erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus(
+ makerFee,
+ );
+ erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][makerAssetAddress].add(
+ makerAssetFilledAmount,
+ );
+ erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][
+ takerAssetAddress
+ ].minus(takerAssetFillAmount);
+ erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus(
+ takerFee,
+ );
+ erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][
+ zrxToken.address
+ ].add(makerFee.add(takerFee));
+ });
+
+ await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress, {
+ takerAssetFillAmounts,
+ });
+
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
+ });
+
+ it('should not throw if an order is invalid and fill the remaining orders', async () => {
+ const takerAssetFillAmounts: BigNumber[] = [];
+ const makerAssetAddress = erc20TokenA.address;
+ const takerAssetAddress = erc20TokenB.address;
+
+ const invalidOrder = {
+ ...signedOrders[0],
+ signature: '0x00',
+ };
+ const validOrders = signedOrders.slice(1);
+
+ takerAssetFillAmounts.push(invalidOrder.takerAssetAmount.div(2));
+ _.forEach(validOrders, signedOrder => {
+ const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ const makerAssetFilledAmount = takerAssetFillAmount
+ .times(signedOrder.makerAssetAmount)
+ .dividedToIntegerBy(signedOrder.takerAssetAmount);
+ const makerFee = signedOrder.makerFee
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ const takerFee = signedOrder.takerFee
+ .times(makerAssetFilledAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ takerAssetFillAmounts.push(takerAssetFillAmount);
+ erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][
+ makerAssetAddress
+ ].minus(makerAssetFilledAmount);
+ erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][takerAssetAddress].add(
+ takerAssetFillAmount,
+ );
+ erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus(
+ makerFee,
+ );
+ erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][makerAssetAddress].add(
+ makerAssetFilledAmount,
+ );
+ erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][
+ takerAssetAddress
+ ].minus(takerAssetFillAmount);
+ erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus(
+ takerFee,
+ );
+ erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][
+ zrxToken.address
+ ].add(makerFee.add(takerFee));
+ });
+
+ const newOrders = [invalidOrder, ...validOrders];
+ await exchangeWrapper.batchFillOrdersNoThrowAsync(newOrders, takerAddress, {
+ takerAssetFillAmounts,
+ });
+
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
+ });
+ });
+
+ describe('marketSellOrders', () => {
+ it('should stop when the entire takerAssetFillAmount is filled', async () => {
+ const takerAssetFillAmount = signedOrders[0].takerAssetAmount.plus(
+ signedOrders[1].takerAssetAmount.div(2),
+ );
+ await exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, {
+ takerAssetFillAmount,
+ });
+
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const makerAssetFilledAmount = signedOrders[0].makerAssetAmount.add(
+ signedOrders[1].makerAssetAmount.dividedToIntegerBy(2),
+ );
+ const makerFee = signedOrders[0].makerFee.add(signedOrders[1].makerFee.dividedToIntegerBy(2));
+ const takerFee = signedOrders[0].takerFee.add(signedOrders[1].takerFee.dividedToIntegerBy(2));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount),
+ );
+ expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerFee),
+ );
+ expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(takerFee),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFee.add(takerFee)),
+ );
+ });
+
+ it('should fill all signedOrders if cannot fill entire takerAssetFillAmount', async () => {
+ const takerAssetFillAmount = ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18);
+ _.forEach(signedOrders, signedOrder => {
+ erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][
+ defaultMakerAssetAddress
+ ].minus(signedOrder.makerAssetAmount);
+ erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][
+ defaultTakerAssetAddress
+ ].add(signedOrder.takerAssetAmount);
+ erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus(
+ signedOrder.makerFee,
+ );
+ erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][
+ defaultMakerAssetAddress
+ ].add(signedOrder.makerAssetAmount);
+ erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][
+ defaultTakerAssetAddress
+ ].minus(signedOrder.takerAssetAmount);
+ erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus(
+ signedOrder.takerFee,
+ );
+ erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][
+ zrxToken.address
+ ].add(signedOrder.makerFee.add(signedOrder.takerFee));
+ });
+ await exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, {
+ takerAssetFillAmount,
+ });
+
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
+ });
+
+ it('should throw when an signedOrder does not use the same takerAssetAddress', async () => {
+ signedOrders = [
+ orderFactory.newSignedOrder(),
+ orderFactory.newSignedOrder({
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
+ }),
+ orderFactory.newSignedOrder(),
+ ];
+
+ return expect(
+ exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, {
+ takerAssetFillAmount: ZeroEx.toBaseUnitAmount(new BigNumber(1000), 18),
+ }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+
+ describe('marketSellOrdersNoThrow', () => {
+ it('should stop when the entire takerAssetFillAmount is filled', async () => {
+ const takerAssetFillAmount = signedOrders[0].takerAssetAmount.plus(
+ signedOrders[1].takerAssetAmount.div(2),
+ );
+ await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, {
+ takerAssetFillAmount,
+ });
+
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const makerAssetFilledAmount = signedOrders[0].makerAssetAmount.add(
+ signedOrders[1].makerAssetAmount.dividedToIntegerBy(2),
+ );
+ const makerFee = signedOrders[0].makerFee.add(signedOrders[1].makerFee.dividedToIntegerBy(2));
+ const takerFee = signedOrders[0].takerFee.add(signedOrders[1].takerFee.dividedToIntegerBy(2));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount),
+ );
+ expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerFee),
+ );
+ expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount),
);
- await exWrapper.fillOrdersUpToAsync(signedOrders, taker, {
- fillTakerTokenAmount,
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(takerFee),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFee.add(takerFee)),
+ );
+ });
+
+ it('should fill all signedOrders if cannot fill entire takerAssetFillAmount', async () => {
+ const takerAssetFillAmount = ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18);
+ _.forEach(signedOrders, signedOrder => {
+ erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][
+ defaultMakerAssetAddress
+ ].minus(signedOrder.makerAssetAmount);
+ erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][
+ defaultTakerAssetAddress
+ ].add(signedOrder.takerAssetAmount);
+ erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus(
+ signedOrder.makerFee,
+ );
+ erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][
+ defaultMakerAssetAddress
+ ].add(signedOrder.makerAssetAmount);
+ erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][
+ defaultTakerAssetAddress
+ ].minus(signedOrder.takerAssetAmount);
+ erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus(
+ signedOrder.takerFee,
+ );
+ erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][
+ zrxToken.address
+ ].add(signedOrder.makerFee.add(signedOrder.takerFee));
+ });
+ await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, {
+ takerAssetFillAmount,
+ });
+
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
+ });
+
+ it('should throw when a signedOrder does not use the same takerAssetAddress', async () => {
+ signedOrders = [
+ orderFactory.newSignedOrder(),
+ orderFactory.newSignedOrder({
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
+ }),
+ orderFactory.newSignedOrder(),
+ ];
+
+ return expect(
+ exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, {
+ takerAssetFillAmount: ZeroEx.toBaseUnitAmount(new BigNumber(1000), 18),
+ }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+
+ describe('marketBuyOrders', () => {
+ it('should stop when the entire makerAssetFillAmount is filled', async () => {
+ const makerAssetFillAmount = signedOrders[0].makerAssetAmount.plus(
+ signedOrders[1].makerAssetAmount.div(2),
+ );
+ await exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, {
+ makerAssetFillAmount,
});
- const newBalances = await dmyBalances.getAsync();
+ const newBalances = await erc20Wrapper.getBalancesAsync();
- const fillMakerTokenAmount = signedOrders[0].makerTokenAmount.add(
- signedOrders[1].makerTokenAmount.dividedToIntegerBy(2),
+ const makerAmountBought = signedOrders[0].takerAssetAmount.add(
+ signedOrders[1].takerAssetAmount.dividedToIntegerBy(2),
);
const makerFee = signedOrders[0].makerFee.add(signedOrders[1].makerFee.dividedToIntegerBy(2));
const takerFee = signedOrders[0].takerFee.add(signedOrders[1].takerFee.dividedToIntegerBy(2));
- expect(newBalances[maker][signedOrders[0].makerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrders[0].makerTokenAddress].minus(fillMakerTokenAmount),
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
);
- expect(newBalances[maker][signedOrders[0].takerTokenAddress]).to.be.bignumber.equal(
- balances[maker][signedOrders[0].takerTokenAddress].add(fillTakerTokenAmount),
+ expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerAssetAddress].add(makerAmountBought),
);
- expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(
- balances[maker][zrx.address].minus(makerFee),
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerFee),
);
- expect(newBalances[taker][signedOrders[0].takerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrders[0].takerTokenAddress].minus(fillTakerTokenAmount),
+ expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerAssetAddress].minus(makerAmountBought),
);
- expect(newBalances[taker][signedOrders[0].makerTokenAddress]).to.be.bignumber.equal(
- balances[taker][signedOrders[0].makerTokenAddress].add(fillMakerTokenAmount),
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount),
);
- expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(
- balances[taker][zrx.address].minus(takerFee),
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(takerFee),
);
- expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal(
- balances[feeRecipient][zrx.address].add(makerFee.add(takerFee)),
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFee.add(takerFee)),
);
});
- it('should fill all signedOrders if cannot fill entire fillTakerTokenAmount', async () => {
- const fillTakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18);
- signedOrders.forEach(signedOrder => {
- balances[maker][signedOrder.makerTokenAddress] = balances[maker][
- signedOrder.makerTokenAddress
- ].minus(signedOrder.makerTokenAmount);
- balances[maker][signedOrder.takerTokenAddress] = balances[maker][signedOrder.takerTokenAddress].add(
- signedOrder.takerTokenAmount,
+ it('should fill all signedOrders if cannot fill entire makerAssetFillAmount', async () => {
+ const makerAssetFillAmount = ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18);
+ _.forEach(signedOrders, signedOrder => {
+ erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][
+ defaultMakerAssetAddress
+ ].minus(signedOrder.makerAssetAmount);
+ erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][
+ defaultTakerAssetAddress
+ ].add(signedOrder.takerAssetAmount);
+ erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus(
+ signedOrder.makerFee,
);
- balances[maker][zrx.address] = balances[maker][zrx.address].minus(signedOrder.makerFee);
- balances[taker][signedOrder.makerTokenAddress] = balances[taker][signedOrder.makerTokenAddress].add(
- signedOrder.makerTokenAmount,
+ erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][
+ defaultMakerAssetAddress
+ ].add(signedOrder.makerAssetAmount);
+ erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][
+ defaultTakerAssetAddress
+ ].minus(signedOrder.takerAssetAmount);
+ erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus(
+ signedOrder.takerFee,
+ );
+ erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][
+ zrxToken.address
+ ].add(signedOrder.makerFee.add(signedOrder.takerFee));
+ });
+ await exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, {
+ makerAssetFillAmount,
+ });
+
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
+ });
+
+ it('should throw when an signedOrder does not use the same makerAssetAddress', async () => {
+ signedOrders = [
+ orderFactory.newSignedOrder(),
+ orderFactory.newSignedOrder({
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
+ }),
+ orderFactory.newSignedOrder(),
+ ];
+
+ return expect(
+ exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, {
+ makerAssetFillAmount: ZeroEx.toBaseUnitAmount(new BigNumber(1000), 18),
+ }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+
+ describe('marketBuyOrdersNoThrow', () => {
+ it('should stop when the entire makerAssetFillAmount is filled', async () => {
+ const makerAssetFillAmount = signedOrders[0].makerAssetAmount.plus(
+ signedOrders[1].makerAssetAmount.div(2),
+ );
+ await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, {
+ makerAssetFillAmount,
+ });
+
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const makerAmountBought = signedOrders[0].takerAssetAmount.add(
+ signedOrders[1].takerAssetAmount.dividedToIntegerBy(2),
+ );
+ const makerFee = signedOrders[0].makerFee.add(signedOrders[1].makerFee.dividedToIntegerBy(2));
+ const takerFee = signedOrders[0].takerFee.add(signedOrders[1].takerFee.dividedToIntegerBy(2));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerAssetAddress].add(makerAmountBought),
+ );
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerFee),
+ );
+ expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerAssetAddress].minus(makerAmountBought),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(takerFee),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFee.add(takerFee)),
+ );
+ });
+
+ it('should fill all signedOrders if cannot fill entire takerAssetFillAmount', async () => {
+ const takerAssetFillAmount = ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18);
+ _.forEach(signedOrders, signedOrder => {
+ erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][
+ defaultMakerAssetAddress
+ ].minus(signedOrder.makerAssetAmount);
+ erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][
+ defaultTakerAssetAddress
+ ].add(signedOrder.takerAssetAmount);
+ erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus(
+ signedOrder.makerFee,
);
- balances[taker][signedOrder.takerTokenAddress] = balances[taker][
- signedOrder.takerTokenAddress
- ].minus(signedOrder.takerTokenAmount);
- balances[taker][zrx.address] = balances[taker][zrx.address].minus(signedOrder.takerFee);
- balances[feeRecipient][zrx.address] = balances[feeRecipient][zrx.address].add(
- signedOrder.makerFee.add(signedOrder.takerFee),
+ erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][
+ defaultMakerAssetAddress
+ ].add(signedOrder.makerAssetAmount);
+ erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][
+ defaultTakerAssetAddress
+ ].minus(signedOrder.takerAssetAmount);
+ erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus(
+ signedOrder.takerFee,
);
+ erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][
+ zrxToken.address
+ ].add(signedOrder.makerFee.add(signedOrder.takerFee));
});
- await exWrapper.fillOrdersUpToAsync(signedOrders, taker, {
- fillTakerTokenAmount,
+ await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, {
+ takerAssetFillAmount,
});
- const newBalances = await dmyBalances.getAsync();
- expect(newBalances).to.be.deep.equal(balances);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20Balances);
});
- it('should throw when an signedOrder does not use the same takerTokenAddress', async () => {
- signedOrders = await Promise.all([
- orderFactory.newSignedOrderAsync(),
- orderFactory.newSignedOrderAsync({ takerTokenAddress: zrx.address }),
- orderFactory.newSignedOrderAsync(),
- ]);
+ it('should throw when a signedOrder does not use the same makerAssetAddress', async () => {
+ signedOrders = [
+ orderFactory.newSignedOrder(),
+ orderFactory.newSignedOrder({
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
+ }),
+ orderFactory.newSignedOrder(),
+ ];
return expect(
- exWrapper.fillOrdersUpToAsync(signedOrders, taker, {
- fillTakerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(1000), 18),
+ exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, {
+ makerAssetFillAmount: ZeroEx.toBaseUnitAmount(new BigNumber(1000), 18),
}),
).to.be.rejectedWith(constants.REVERT);
});
@@ -392,17 +962,15 @@ describe('Exchange', () => {
describe('batchCancelOrders', () => {
it('should be able to cancel multiple signedOrders', async () => {
- const cancelTakerTokenAmounts = _.map(signedOrders, signedOrder => signedOrder.takerTokenAmount);
- await exWrapper.batchCancelOrdersAsync(signedOrders, maker, {
- cancelTakerTokenAmounts,
- });
+ const takerAssetCancelAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount);
+ await exchangeWrapper.batchCancelOrdersAsync(signedOrders, makerAddress);
- await exWrapper.batchFillOrdersAsync(signedOrders, taker, {
- fillTakerTokenAmounts: cancelTakerTokenAmounts,
+ await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, {
+ takerAssetFillAmounts: takerAssetCancelAmounts,
});
- const newBalances = await dmyBalances.getAsync();
- expect(balances).to.be.deep.equal(newBalances);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(erc20Balances).to.be.deep.equal(newBalances);
});
});
});
-});
+}); // tslint:disable-line:max-file-line-count
diff --git a/packages/contracts/test/libraries/lib_bytes.ts b/packages/contracts/test/libraries/lib_bytes.ts
new file mode 100644
index 000000000..a468255e9
--- /dev/null
+++ b/packages/contracts/test/libraries/lib_bytes.ts
@@ -0,0 +1,251 @@
+import { LogWithDecodedArgs, TransactionReceiptWithDecodedLogs, ZeroEx } from '0x.js';
+import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import BN = require('bn.js');
+import * as chai from 'chai';
+import ethUtil = require('ethereumjs-util');
+import * as Web3 from 'web3';
+
+import { TestLibBytesContract } from '../../src/contract_wrappers/generated/test_lib_bytes';
+import { artifacts } from '../../src/utils/artifacts';
+import { chaiSetup } from '../../src/utils/chai_setup';
+import { constants } from '../../src/utils/constants';
+import { AssetProxyId } from '../../src/utils/types';
+import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('LibBytes', () => {
+ let owner: string;
+ let libBytes: TestLibBytesContract;
+ const byteArrayShorterThan32Bytes = '0x012345';
+ const byteArrayLongerThan32Bytes =
+ '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
+ const byteArrayLongerThan32BytesFirstBytesSwapped =
+ '0x2301456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
+ const byteArrayLongerThan32BytesLastBytesSwapped =
+ '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abefcd';
+ let testAddress: string;
+ const testBytes32 = '0x102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f01020';
+ const testUint256 = new BigNumber(testBytes32, 16);
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ // Setup accounts & addresses
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ owner = accounts[0];
+ testAddress = accounts[1];
+ // Deploy LibBytes
+ libBytes = await TestLibBytesContract.deployFrom0xArtifactAsync(artifacts.TestLibBytes, provider, txDefaults);
+ // Verify lengths of test data
+ const byteArrayShorterThan32BytesLength = ethUtil.toBuffer(byteArrayShorterThan32Bytes).byteLength;
+ expect(byteArrayShorterThan32BytesLength).to.be.lessThan(32);
+ const byteArrayLongerThan32BytesLength = ethUtil.toBuffer(byteArrayLongerThan32Bytes).byteLength;
+ expect(byteArrayLongerThan32BytesLength).to.be.greaterThan(32);
+ const testBytes32Length = ethUtil.toBuffer(testBytes32).byteLength;
+ expect(testBytes32Length).to.be.equal(32);
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ describe('areBytesEqual', () => {
+ it('should return true if byte arrays are equal (both arrays < 32 bytes)', async () => {
+ const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync(
+ byteArrayShorterThan32Bytes,
+ byteArrayShorterThan32Bytes,
+ );
+ return expect(areBytesEqual).to.be.true();
+ });
+
+ it('should return true if byte arrays are equal (both arrays > 32 bytes)', async () => {
+ const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync(
+ byteArrayLongerThan32Bytes,
+ byteArrayLongerThan32Bytes,
+ );
+ return expect(areBytesEqual).to.be.true();
+ });
+
+ it('should return false if byte arrays are not equal (first array < 32 bytes, second array > 32 bytes)', async () => {
+ const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync(
+ byteArrayShorterThan32Bytes,
+ byteArrayLongerThan32Bytes,
+ );
+ return expect(areBytesEqual).to.be.false();
+ });
+
+ it('should return false if byte arrays are not equal (first array > 32 bytes, second array < 32 bytes)', async () => {
+ const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync(
+ byteArrayLongerThan32Bytes,
+ byteArrayShorterThan32Bytes,
+ );
+ return expect(areBytesEqual).to.be.false();
+ });
+
+ it('should return false if byte arrays are not equal (same length, but a byte in first word differs)', async () => {
+ const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync(
+ byteArrayLongerThan32BytesFirstBytesSwapped,
+ byteArrayLongerThan32Bytes,
+ );
+ return expect(areBytesEqual).to.be.false();
+ });
+
+ it('should return false if byte arrays are not equal (same length, but a byte in last word differs)', async () => {
+ const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync(
+ byteArrayLongerThan32BytesLastBytesSwapped,
+ byteArrayLongerThan32Bytes,
+ );
+ return expect(areBytesEqual).to.be.false();
+ });
+ });
+
+ describe('readAddress', () => {
+ it('should successfully read address when the address takes up the whole array)', async () => {
+ const byteArray = ethUtil.addHexPrefix(testAddress);
+ const testAddressOffset = new BigNumber(0);
+ const address = await libBytes.publicReadAddress.callAsync(byteArray, testAddressOffset);
+ return expect(address).to.be.equal(testAddress);
+ });
+
+ it('should successfully read address when it is offset in the array)', async () => {
+ const addressByteArrayBuffer = ethUtil.toBuffer(testAddress);
+ const prefixByteArrayBuffer = ethUtil.toBuffer('0xabcdef');
+ const combinedByteArrayBuffer = Buffer.concat([prefixByteArrayBuffer, addressByteArrayBuffer]);
+ const combinedByteArray = ethUtil.bufferToHex(combinedByteArrayBuffer);
+ const testAddressOffset = new BigNumber(prefixByteArrayBuffer.byteLength);
+ const address = await libBytes.publicReadAddress.callAsync(combinedByteArray, testAddressOffset);
+ return expect(address).to.be.equal(testAddress);
+ });
+
+ it('should fail if the byte array is too short to hold an address)', async () => {
+ const shortByteArray = '0xabcdef';
+ const offset = new BigNumber(0);
+ return expect(libBytes.publicReadAddress.callAsync(shortByteArray, offset)).to.be.rejectedWith(
+ constants.REVERT,
+ );
+ });
+
+ it('should fail if the length between the offset and end of the byte array is too short to hold an address)', async () => {
+ const byteArray = ethUtil.addHexPrefix(testAddress);
+ const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength);
+ return expect(libBytes.publicReadAddress.callAsync(byteArray, badOffset)).to.be.rejectedWith(
+ constants.REVERT,
+ );
+ });
+ });
+
+ /// @TODO Implement test cases for writeAddress. Test template below.
+ /// Currently, the generated contract wrappers do not support this library's write methods.
+ /*
+ describe('writeAddress', () => {
+ it('should successfully write address when the address takes up the whole array)', async () => {});
+ it('should successfully write address when it is offset in the array)', async () => {});
+ it('should fail if the byte array is too short to hold an address)', async () => {});
+ it('should fail if the length between the offset and end of the byte array is too short to hold an address)', async () => {});
+ });
+ */
+
+ describe('readBytes32', () => {
+ it('should successfully read bytes32 when the bytes32 takes up the whole array)', async () => {
+ const testBytes32Offset = new BigNumber(0);
+ const bytes32 = await libBytes.publicReadBytes32.callAsync(testBytes32, testBytes32Offset);
+ return expect(bytes32).to.be.equal(testBytes32);
+ });
+
+ it('should successfully read bytes32 when it is offset in the array)', async () => {
+ const bytes32ByteArrayBuffer = ethUtil.toBuffer(testBytes32);
+ const prefixByteArrayBuffer = ethUtil.toBuffer('0xabcdef');
+ const combinedByteArrayBuffer = Buffer.concat([prefixByteArrayBuffer, bytes32ByteArrayBuffer]);
+ const combinedByteArray = ethUtil.bufferToHex(combinedByteArrayBuffer);
+ const testAddressOffset = new BigNumber(prefixByteArrayBuffer.byteLength);
+ const bytes32 = await libBytes.publicReadBytes32.callAsync(combinedByteArray, testAddressOffset);
+ return expect(bytes32).to.be.equal(testBytes32);
+ });
+
+ it('should fail if the byte array is too short to hold a bytes32)', async () => {
+ const offset = new BigNumber(0);
+ return expect(libBytes.publicReadBytes32.callAsync(byteArrayShorterThan32Bytes, offset)).to.be.rejectedWith(
+ constants.REVERT,
+ );
+ });
+
+ it('should fail if the length between the offset and end of the byte array is too short to hold a bytes32)', async () => {
+ const badOffset = new BigNumber(ethUtil.toBuffer(testBytes32).byteLength);
+ return expect(libBytes.publicReadBytes32.callAsync(testBytes32, badOffset)).to.be.rejectedWith(
+ constants.REVERT,
+ );
+ });
+ });
+
+ /// @TODO Implement test cases for writeBytes32. Test template below.
+ /// Currently, the generated contract wrappers do not support this library's write methods.
+ /*
+ describe('writeBytes32', () => {
+ it('should successfully write bytes32 when the address takes up the whole array)', async () => {});
+ it('should successfully write bytes32 when it is offset in the array)', async () => {});
+ it('should fail if the byte array is too short to hold a bytes32)', async () => {});
+ it('should fail if the length between the offset and end of the byte array is too short to hold a bytes32)', async () => {});
+ });
+ */
+
+ describe('readUint256', () => {
+ it('should successfully read uint256 when the uint256 takes up the whole array)', async () => {
+ const formattedTestUint256 = new BN(testUint256.toString(10));
+ const testUint256AsBuffer = ethUtil.toBuffer(formattedTestUint256);
+ const byteArray = ethUtil.bufferToHex(testUint256AsBuffer);
+ const testUint256Offset = new BigNumber(0);
+ const uint256 = await libBytes.publicReadUint256.callAsync(byteArray, testUint256Offset);
+ return expect(uint256).to.bignumber.equal(testUint256);
+ });
+
+ it('should successfully read uint256 when it is offset in the array)', async () => {
+ const prefixByteArrayBuffer = ethUtil.toBuffer('0xabcdef');
+ const formattedTestUint256 = new BN(testUint256.toString(10));
+ const testUint256AsBuffer = ethUtil.toBuffer(formattedTestUint256);
+ const combinedByteArrayBuffer = Buffer.concat([prefixByteArrayBuffer, testUint256AsBuffer]);
+ const combinedByteArray = ethUtil.bufferToHex(combinedByteArrayBuffer);
+ const testUint256Offset = new BigNumber(prefixByteArrayBuffer.byteLength);
+ const uint256 = await libBytes.publicReadUint256.callAsync(combinedByteArray, testUint256Offset);
+ return expect(uint256).to.bignumber.equal(testUint256);
+ });
+
+ it('should fail if the byte array is too short to hold a uint256)', async () => {
+ const offset = new BigNumber(0);
+ return expect(libBytes.publicReadUint256.callAsync(byteArrayShorterThan32Bytes, offset)).to.be.rejectedWith(
+ constants.REVERT,
+ );
+ });
+
+ it('should fail if the length between the offset and end of the byte array is too short to hold a uint256)', async () => {
+ const formattedTestUint256 = new BN(testUint256.toString(10));
+ const testUint256AsBuffer = ethUtil.toBuffer(formattedTestUint256);
+ const byteArray = ethUtil.bufferToHex(testUint256AsBuffer);
+ const badOffset = new BigNumber(testUint256AsBuffer.byteLength);
+ return expect(libBytes.publicReadUint256.callAsync(byteArray, badOffset)).to.be.rejectedWith(
+ constants.REVERT,
+ );
+ });
+ });
+
+ /// @TODO Implement test cases for writeUint256. Test template below.
+ /// Currently, the generated contract wrappers do not support this library's write methods.
+ /*
+ describe('writeUint256', () => {
+ it('should successfully write uint256 when the address takes up the whole array)', async () => {});
+ it('should successfully write uint256 when it is offset in the array)', async () => {});
+ it('should fail if the byte array is too short to hold a uint256)', async () => {});
+ it('should fail if the length between the offset and end of the byte array is too short to hold a uint256)', async () => {});
+ });
+ */
+});
diff --git a/packages/contracts/test/multi_sig_with_time_lock.ts b/packages/contracts/test/multi_sig_with_time_lock.ts
index 15e8783db..3f61005e7 100644
--- a/packages/contracts/test/multi_sig_with_time_lock.ts
+++ b/packages/contracts/test/multi_sig_with_time_lock.ts
@@ -9,14 +9,12 @@ import * as Web3 from 'web3';
import * as multiSigWalletJSON from '../../build/contracts/MultiSigWalletWithTimeLock.json';
import { MultiSigWalletContract } from '../src/contract_wrappers/generated/multi_sig_wallet';
import { MultiSigWalletWithTimeLockContract } from '../src/contract_wrappers/generated/multi_sig_wallet_with_time_lock';
-import { artifacts } from '../util/artifacts';
-import { constants } from '../util/constants';
-import { MultiSigWrapper } from '../util/multi_sig_wrapper';
-import { ContractName, SubmissionContractEventArgs } from '../util/types';
-
-import { chaiSetup } from './utils/chai_setup';
-
-import { provider, txDefaults, web3Wrapper } from './utils/web3_wrapper';
+import { artifacts } from '../src/utils/artifacts';
+import { chaiSetup } from '../src/utils/chai_setup';
+import { constants } from '../src/utils/constants';
+import { MultiSigWrapper } from '../src/utils/multi_sig_wrapper';
+import { SubmissionContractEventArgs } from '../src/utils/types';
+import { provider, txDefaults, web3Wrapper } from '../src/utils/web3_wrapper';
const MULTI_SIG_ABI = artifacts.MultiSigWalletWithTimeLock.compilerOutput.abi;
chaiSetup.configure();
@@ -47,6 +45,12 @@ describe('MultiSigWalletWithTimeLock', () => {
describe('changeTimeLock', () => {
describe('initially non-time-locked', async () => {
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
before('deploy a wallet', async () => {
multiSig = await MultiSigWalletWithTimeLockContract.deployFrom0xArtifactAsync(
artifacts.MultiSigWalletWithTimeLock,
@@ -76,8 +80,8 @@ describe('MultiSigWalletWithTimeLock', () => {
args: [SECONDS_TIME_LOCKED.toNumber()],
};
const txHash = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams);
- const subRes = await zeroEx.awaitTransactionMinedAsync(txHash);
- const log = abiDecoder.tryToDecodeLogOrNoop(subRes.logs[0]) as LogWithDecodedArgs<
+ const txReceipt = await zeroEx.awaitTransactionMinedAsync(txHash);
+ const log = abiDecoder.tryToDecodeLogOrNoop(txReceipt.logs[0]) as LogWithDecodedArgs<
SubmissionContractEventArgs
>;
@@ -96,14 +100,14 @@ describe('MultiSigWalletWithTimeLock', () => {
args: [SECONDS_TIME_LOCKED.toNumber()],
};
let txHash = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams);
- const subRes = await zeroEx.awaitTransactionMinedAsync(txHash);
- const log = abiDecoder.tryToDecodeLogOrNoop(subRes.logs[0]) as LogWithDecodedArgs<
+ const txReceipt = await zeroEx.awaitTransactionMinedAsync(txHash);
+ const log = abiDecoder.tryToDecodeLogOrNoop(txReceipt.logs[0]) as LogWithDecodedArgs<
SubmissionContractEventArgs
>;
txId = log.args.transactionId;
txHash = await multiSig.confirmTransaction.sendTransactionAsync(txId, { from: owners[1] });
- const res = await zeroEx.awaitTransactionMinedAsync(txHash);
+ const res = await zeroEx.awaitTransactionMinedAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
expect(res.logs).to.have.length(2);
const blockNum = await web3Wrapper.getBlockNumberAsync();
@@ -123,18 +127,19 @@ describe('MultiSigWalletWithTimeLock', () => {
args: [SECONDS_TIME_LOCKED.toNumber()],
};
let txHash = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams);
- const subRes = await zeroEx.awaitTransactionMinedAsync(txHash);
- const log = abiDecoder.tryToDecodeLogOrNoop(subRes.logs[0]) as LogWithDecodedArgs<
+ const txReceipt = await zeroEx.awaitTransactionMinedAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
+ const log = abiDecoder.tryToDecodeLogOrNoop(txReceipt.logs[0]) as LogWithDecodedArgs<
SubmissionContractEventArgs
>;
txId = log.args.transactionId;
txHash = await multiSig.confirmTransaction.sendTransactionAsync(txId, { from: owners[1] });
+ await web3Wrapper.awaitTransactionMinedAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
expect(initialSecondsTimeLocked).to.be.equal(0);
txHash = await multiSig.executeTransaction.sendTransactionAsync(txId, { from: owners[0] });
- const res = await zeroEx.awaitTransactionMinedAsync(txHash);
+ const res = await zeroEx.awaitTransactionMinedAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
expect(res.logs).to.have.length(2);
const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.callAsync());
@@ -142,6 +147,12 @@ describe('MultiSigWalletWithTimeLock', () => {
});
});
describe('initially time-locked', async () => {
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
before('deploy a wallet', async () => {
multiSig = await MultiSigWalletWithTimeLockContract.deployFrom0xArtifactAsync(
artifacts.MultiSigWalletWithTimeLock,
@@ -163,16 +174,16 @@ describe('MultiSigWalletWithTimeLock', () => {
args: [newSecondsTimeLocked],
};
let txHash = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams);
- const subRes = await zeroEx.awaitTransactionMinedAsync(txHash);
- const log = abiDecoder.tryToDecodeLogOrNoop(subRes.logs[0]) as LogWithDecodedArgs<
+ let txReceipt = await zeroEx.awaitTransactionMinedAsync(txHash);
+ const log = abiDecoder.tryToDecodeLogOrNoop(txReceipt.logs[0]) as LogWithDecodedArgs<
SubmissionContractEventArgs
>;
txId = log.args.transactionId;
txHash = await multiSig.confirmTransaction.sendTransactionAsync(txId, {
from: owners[1],
});
- const confRes = await zeroEx.awaitTransactionMinedAsync(txHash);
- expect(confRes.logs).to.have.length(2);
+ txReceipt = await zeroEx.awaitTransactionMinedAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
+ expect(txReceipt.logs).to.have.length(2);
});
const newSecondsTimeLocked = 0;
it('should throw if it has enough confirmations but is not past the time lock', async () => {
@@ -183,7 +194,10 @@ describe('MultiSigWalletWithTimeLock', () => {
it('should execute if it has enough confirmations and is past the time lock', async () => {
await web3Wrapper.increaseTimeAsync(SECONDS_TIME_LOCKED.toNumber());
- await multiSig.executeTransaction.sendTransactionAsync(txId, { from: owners[0] });
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await multiSig.executeTransaction.sendTransactionAsync(txId, { from: owners[0] }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.callAsync());
expect(secondsTimeLocked).to.be.bignumber.equal(newSecondsTimeLocked);
diff --git a/packages/contracts/test/multi_sig_with_time_lock_except_remove_auth_addr.ts b/packages/contracts/test/multi_sig_with_time_lock_except_remove_auth_addr.ts
index 787668c21..dcd206b1c 100644
--- a/packages/contracts/test/multi_sig_with_time_lock_except_remove_auth_addr.ts
+++ b/packages/contracts/test/multi_sig_with_time_lock_except_remove_auth_addr.ts
@@ -1,3 +1,9 @@
+/*
+ *
+ * @TODO: Before deploying, the MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress contract must be updated
+ * to have a mapping of all approved addresses. These tests must be updated appropriately.
+ * For now, these tests have been commented out by @hysz (greg@0xproject.com).
+ *
import { LogWithDecodedArgs, ZeroEx } from '0x.js';
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
import { AbiDecoder, BigNumber } from '@0xproject/utils';
@@ -9,11 +15,11 @@ import * as Web3 from 'web3';
import { MultiSigWalletContract } from '../src/contract_wrappers/generated/multi_sig_wallet';
import { MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddressContract } from '../src/contract_wrappers/generated/multi_sig_wallet_with_time_lock_except_remove_authorized_address';
import { TokenTransferProxyContract } from '../src/contract_wrappers/generated/token_transfer_proxy';
-import { artifacts } from '../util/artifacts';
-import { constants } from '../util/constants';
-import { crypto } from '../util/crypto';
-import { MultiSigWrapper } from '../util/multi_sig_wrapper';
-import { ContractName, SubmissionContractEventArgs, TransactionDataParams } from '../util/types';
+import { artifacts } from '../src/utils/artifacts';
+import { constants } from '../src/utils/constants';
+import { crypto } from '../src/utils/crypto';
+import { MultiSigWrapper } from '../src/utils/multi_sig_wrapper';
+import { ContractName, SubmissionContractEventArgs, TransactionDataParams } from '../src/utils/types';
import { chaiSetup } from './utils/chai_setup';
@@ -195,3 +201,5 @@ describe('MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', () => {
});
});
});
+
+*/
diff --git a/packages/contracts/test/token_registry.ts b/packages/contracts/test/token_registry.ts
index 2116bbf0c..308dee8e6 100644
--- a/packages/contracts/test/token_registry.ts
+++ b/packages/contracts/test/token_registry.ts
@@ -9,13 +9,11 @@ import 'make-promises-safe';
import * as Web3 from 'web3';
import { TokenRegistryContract } from '../src/contract_wrappers/generated/token_registry';
-import { artifacts } from '../util/artifacts';
-import { constants } from '../util/constants';
-import { TokenRegWrapper } from '../util/token_registry_wrapper';
-import { ContractName } from '../util/types';
-
-import { chaiSetup } from './utils/chai_setup';
-import { provider, txDefaults, web3Wrapper } from './utils/web3_wrapper';
+import { artifacts } from '../src/utils/artifacts';
+import { chaiSetup } from '../src/utils/chai_setup';
+import { constants } from '../src/utils/constants';
+import { TokenRegWrapper } from '../src/utils/token_registry_wrapper';
+import { provider, txDefaults, web3Wrapper } from '../src/utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
@@ -27,6 +25,12 @@ describe('TokenRegistry', () => {
let tokenReg: TokenRegistryContract;
let tokenRegWrapper: TokenRegWrapper;
before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
owner = accounts[0];
notOwner = accounts[1];
@@ -139,9 +143,12 @@ describe('TokenRegistry', () => {
});
it('should change the token name when called by owner', async () => {
- await tokenReg.setTokenName.sendTransactionAsync(token1.address, token2.name, {
- from: owner,
- });
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await tokenReg.setTokenName.sendTransactionAsync(token1.address, token2.name, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
const [newData, oldData] = await Promise.all([
tokenRegWrapper.getTokenByNameAsync(token2.name),
tokenRegWrapper.getTokenByNameAsync(token1.name),
@@ -178,7 +185,10 @@ describe('TokenRegistry', () => {
});
it('should change the token symbol when called by owner', async () => {
- await tokenReg.setTokenSymbol.sendTransactionAsync(token1.address, token2.symbol, { from: owner });
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await tokenReg.setTokenSymbol.sendTransactionAsync(token1.address, token2.symbol, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
const [newData, oldData] = await Promise.all([
tokenRegWrapper.getTokenBySymbolAsync(token2.symbol),
tokenRegWrapper.getTokenBySymbolAsync(token1.symbol),
@@ -219,9 +229,12 @@ describe('TokenRegistry', () => {
it('should remove token metadata when called by owner', async () => {
const index = new BigNumber(0);
- await tokenReg.removeToken.sendTransactionAsync(token1.address, index, {
- from: owner,
- });
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await tokenReg.removeToken.sendTransactionAsync(token1.address, index, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
const tokenData = await tokenRegWrapper.getTokenMetaDataAsync(token1.address);
expect(tokenData).to.be.deep.equal(nullToken);
});
diff --git a/packages/contracts/test/token_transfer_proxy/auth.ts b/packages/contracts/test/token_transfer_proxy/auth.ts
deleted file mode 100644
index 47e2a4d21..000000000
--- a/packages/contracts/test/token_transfer_proxy/auth.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
-import { Web3Wrapper } from '@0xproject/web3-wrapper';
-import * as chai from 'chai';
-import 'make-promises-safe';
-import * as Web3 from 'web3';
-
-import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy';
-import { artifacts } from '../../util/artifacts';
-import { constants } from '../../util/constants';
-import { ContractName } from '../../util/types';
-import { chaiSetup } from '../utils/chai_setup';
-
-import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
-
-chaiSetup.configure();
-const expect = chai.expect;
-const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
-
-describe('TokenTransferProxy', () => {
- let owner: string;
- let notOwner: string;
- let address: string;
- let tokenTransferProxy: TokenTransferProxyContract;
- before(async () => {
- const accounts = await web3Wrapper.getAvailableAddressesAsync();
- owner = address = accounts[0];
- notOwner = accounts[1];
- tokenTransferProxy = await TokenTransferProxyContract.deployFrom0xArtifactAsync(
- artifacts.TokenTransferProxy,
- provider,
- txDefaults,
- );
- });
- beforeEach(async () => {
- await blockchainLifecycle.startAsync();
- });
- afterEach(async () => {
- await blockchainLifecycle.revertAsync();
- });
- describe('addAuthorizedAddress', () => {
- it('should throw if not called by owner', async () => {
- return expect(
- tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(notOwner, { from: notOwner }),
- ).to.be.rejectedWith(constants.REVERT);
- });
- it('should allow owner to add an authorized address', async () => {
- await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(address, { from: owner });
- const isAuthorized = await tokenTransferProxy.authorized.callAsync(address);
- expect(isAuthorized).to.be.true();
- });
- it('should throw if owner attempts to authorize a duplicate address', async () => {
- await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(address, { from: owner });
- return expect(
- tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
- ).to.be.rejectedWith(constants.REVERT);
- });
- });
-
- describe('removeAuthorizedAddress', () => {
- it('should throw if not called by owner', async () => {
- await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(address, { from: owner });
- return expect(
- tokenTransferProxy.removeAuthorizedAddress.sendTransactionAsync(address, {
- from: notOwner,
- }),
- ).to.be.rejectedWith(constants.REVERT);
- });
-
- it('should allow owner to remove an authorized address', async () => {
- await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(address, { from: owner });
- await tokenTransferProxy.removeAuthorizedAddress.sendTransactionAsync(address, {
- from: owner,
- });
- const isAuthorized = await tokenTransferProxy.authorized.callAsync(address);
- expect(isAuthorized).to.be.false();
- });
-
- it('should throw if owner attempts to remove an address that is not authorized', async () => {
- return expect(
- tokenTransferProxy.removeAuthorizedAddress.sendTransactionAsync(address, {
- from: owner,
- }),
- ).to.be.rejectedWith(constants.REVERT);
- });
- });
-
- describe('getAuthorizedAddresses', () => {
- it('should return all authorized addresses', async () => {
- const initial = await tokenTransferProxy.getAuthorizedAddresses.callAsync();
- expect(initial).to.have.length(0);
- await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(address, {
- from: owner,
- });
- const afterAdd = await tokenTransferProxy.getAuthorizedAddresses.callAsync();
- expect(afterAdd).to.have.length(1);
- expect(afterAdd).to.include(address);
-
- await tokenTransferProxy.removeAuthorizedAddress.sendTransactionAsync(address, {
- from: owner,
- });
- const afterRemove = await tokenTransferProxy.getAuthorizedAddresses.callAsync();
- expect(afterRemove).to.have.length(0);
- });
- });
-});
diff --git a/packages/contracts/test/token_transfer_proxy/transfer_from.ts b/packages/contracts/test/token_transfer_proxy/transfer_from.ts
deleted file mode 100644
index 875c575ce..000000000
--- a/packages/contracts/test/token_transfer_proxy/transfer_from.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
-import { BigNumber } from '@0xproject/utils';
-import { Web3Wrapper } from '@0xproject/web3-wrapper';
-import * as chai from 'chai';
-import 'make-promises-safe';
-import * as Web3 from 'web3';
-
-import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token';
-import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy';
-import { artifacts } from '../../util/artifacts';
-import { Balances } from '../../util/balances';
-import { constants } from '../../util/constants';
-import { ContractName } from '../../util/types';
-import { chaiSetup } from '../utils/chai_setup';
-
-import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
-
-chaiSetup.configure();
-const expect = chai.expect;
-const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
-
-describe('TokenTransferProxy', () => {
- let accounts: string[];
- let owner: string;
- let notAuthorized: string;
- const INIT_BAL = new BigNumber(100000000);
- const INIT_ALLOW = new BigNumber(100000000);
-
- let tokenTransferProxy: TokenTransferProxyContract;
- let rep: DummyTokenContract;
- let dmyBalances: Balances;
-
- before(async () => {
- accounts = await web3Wrapper.getAvailableAddressesAsync();
- owner = notAuthorized = accounts[0];
- tokenTransferProxy = await TokenTransferProxyContract.deployFrom0xArtifactAsync(
- artifacts.TokenTransferProxy,
- provider,
- txDefaults,
- );
- rep = await DummyTokenContract.deployFrom0xArtifactAsync(
- artifacts.DummyToken,
- provider,
- txDefaults,
- constants.DUMMY_TOKEN_NAME,
- constants.DUMMY_TOKEN_SYMBOL,
- constants.DUMMY_TOKEN_DECIMALS,
- constants.DUMMY_TOKEN_TOTAL_SUPPLY,
- );
- dmyBalances = new Balances([rep], [accounts[0], accounts[1]]);
- await Promise.all([
- rep.approve.sendTransactionAsync(tokenTransferProxy.address, INIT_ALLOW, {
- from: accounts[0],
- }),
- rep.setBalance.sendTransactionAsync(accounts[0], INIT_BAL, { from: owner }),
- rep.approve.sendTransactionAsync(tokenTransferProxy.address, INIT_ALLOW, {
- from: accounts[1],
- }),
- rep.setBalance.sendTransactionAsync(accounts[1], INIT_BAL, { from: owner }),
- ]);
- });
- beforeEach(async () => {
- await blockchainLifecycle.startAsync();
- });
- afterEach(async () => {
- await blockchainLifecycle.revertAsync();
- });
-
- describe('transferFrom', () => {
- it('should throw when called by an unauthorized address', async () => {
- expect(
- tokenTransferProxy.transferFrom.sendTransactionAsync(
- rep.address,
- accounts[0],
- accounts[1],
- new BigNumber(1000),
- {
- from: notAuthorized,
- },
- ),
- ).to.be.rejectedWith(constants.REVERT);
- });
-
- it('should allow an authorized address to transfer', async () => {
- const balances = await dmyBalances.getAsync();
-
- await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(notAuthorized, {
- from: owner,
- });
- const transferAmt = new BigNumber(10000);
- await tokenTransferProxy.transferFrom.sendTransactionAsync(
- rep.address,
- accounts[0],
- accounts[1],
- transferAmt,
- {
- from: notAuthorized,
- },
- );
-
- const newBalances = await dmyBalances.getAsync();
- expect(newBalances[accounts[0]][rep.address]).to.be.bignumber.equal(
- balances[accounts[0]][rep.address].minus(transferAmt),
- );
- expect(newBalances[accounts[1]][rep.address]).to.be.bignumber.equal(
- balances[accounts[1]][rep.address].add(transferAmt),
- );
- });
- });
-});
diff --git a/packages/contracts/test/tutorials/arbitrage.ts b/packages/contracts/test/tutorials/arbitrage.ts
index 8e2c8de8f..723ddb066 100644
--- a/packages/contracts/test/tutorials/arbitrage.ts
+++ b/packages/contracts/test/tutorials/arbitrage.ts
@@ -1,261 +1,261 @@
-import { ECSignature, SignedOrder, ZeroEx } from '0x.js';
-import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
-import { ExchangeContractErrs } from '@0xproject/types';
-import { BigNumber } from '@0xproject/utils';
-import { Web3Wrapper } from '@0xproject/web3-wrapper';
-import * as chai from 'chai';
-import ethUtil = require('ethereumjs-util');
-import 'make-promises-safe';
-import * as Web3 from 'web3';
+// import { ECSignature, SignedOrder, ZeroEx } from '0x.js';
+// import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
+// import { ExchangeContractErrs } from '@0xproject/types';
+// import { BigNumber } from '@0xproject/utils';
+// import { Web3Wrapper } from '@0xproject/web3-wrapper';
+// import * as chai from 'chai';
+// import 'make-promises-safe';
+// import ethUtil = require('ethereumjs-util');
+// import * as Web3 from 'web3';
-import { AccountLevelsContract } from '../../src/contract_wrappers/generated/account_levels';
-import { ArbitrageContract } from '../../src/contract_wrappers/generated/arbitrage';
-import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token';
-import { EtherDeltaContract } from '../../src/contract_wrappers/generated/ether_delta';
-import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange';
-import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy';
-import { artifacts } from '../../util/artifacts';
-import { Balances } from '../../util/balances';
-import { constants } from '../../util/constants';
-import { crypto } from '../../util/crypto';
-import { ExchangeWrapper } from '../../util/exchange_wrapper';
-import { OrderFactory } from '../../util/order_factory';
-import { BalancesByOwner, ContractName } from '../../util/types';
-import { chaiSetup } from '../utils/chai_setup';
+// import { AccountLevelsContract } from '../../src/contract_wrappers/generated/account_levels';
+// import { ArbitrageContract } from '../../src/contract_wrappers/generated/arbitrage';
+// import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token';
+// import { EtherDeltaContract } from '../../src/contract_wrappers/generated/ether_delta';
+// import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange';
+// import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy';
+// import { artifacts } from '../../util/artifacts';
+// import { Balances } from '../../util/balances';
+// import { constants } from '../../util/constants';
+// import { crypto } from '../../util/crypto';
+// import { ExchangeWrapper } from '../../util/exchange_wrapper';
+// import { OrderFactory } from '../../util/order_factory';
+// import { BalancesByOwner, ContractName } from '../../util/types';
+// import { chaiSetup } from '../utils/chai_setup';
-import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
+// import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
-chaiSetup.configure();
-const expect = chai.expect;
-const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+// chaiSetup.configure();
+// const expect = chai.expect;
+// const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
-describe('Arbitrage', () => {
- let coinbase: string;
- let maker: string;
- let edMaker: string;
- let edFrontRunner: string;
- let amountGet: BigNumber;
- let amountGive: BigNumber;
- let makerTokenAmount: BigNumber;
- let takerTokenAmount: BigNumber;
- const feeRecipient = ZeroEx.NULL_ADDRESS;
- const INITIAL_BALANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
- const INITIAL_ALLOWANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
+// describe('Arbitrage', () => {
+// let coinbase: string;
+// let maker: string;
+// let edMaker: string;
+// let edFrontRunner: string;
+// let amountGet: BigNumber;
+// let amountGive: BigNumber;
+// let makerTokenAmount: BigNumber;
+// let takerTokenAmount: BigNumber;
+// const feeRecipient = ZeroEx.NULL_ADDRESS;
+// const INITIAL_BALANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
+// const INITIAL_ALLOWANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
- let weth: DummyTokenContract;
- let zrx: DummyTokenContract;
- let arbitrage: ArbitrageContract;
- let etherDelta: EtherDeltaContract;
+// let weth: DummyTokenContract;
+// let zrx: DummyTokenContract;
+// let arbitrage: ArbitrageContract;
+// let etherDelta: EtherDeltaContract;
- let signedOrder: SignedOrder;
- let exWrapper: ExchangeWrapper;
- let orderFactory: OrderFactory;
+// let signedOrder: SignedOrder;
+// let exWrapper: ExchangeWrapper;
+// let orderFactory: OrderFactory;
- let zeroEx: ZeroEx;
+// let zeroEx: ZeroEx;
- // From a bird's eye view - we create two orders.
- // 0x order of 1 ZRX (maker) for 1 WETH (taker)
- // ED order of 2 WETH (tokenGive) for 1 ZRX (tokenGet)
- // And then we do an atomic arbitrage between them which gives us 1 WETH.
- before(async () => {
- const accounts = await web3Wrapper.getAvailableAddressesAsync();
- [coinbase, maker, edMaker, edFrontRunner] = accounts;
- weth = await DummyTokenContract.deployFrom0xArtifactAsync(
- artifacts.DummyToken,
- provider,
- txDefaults,
- constants.DUMMY_TOKEN_NAME,
- constants.DUMMY_TOKEN_SYMBOL,
- constants.DUMMY_TOKEN_DECIMALS,
- constants.DUMMY_TOKEN_TOTAL_SUPPLY,
- );
- zrx = await DummyTokenContract.deployFrom0xArtifactAsync(
- artifacts.DummyToken,
- provider,
- txDefaults,
- constants.DUMMY_TOKEN_NAME,
- constants.DUMMY_TOKEN_SYMBOL,
- constants.DUMMY_TOKEN_DECIMALS,
- constants.DUMMY_TOKEN_TOTAL_SUPPLY,
- );
- const accountLevels = await AccountLevelsContract.deployFrom0xArtifactAsync(
- artifacts.AccountLevels,
- provider,
- txDefaults,
- );
- const edAdminAddress = accounts[0];
- const edMakerFee = new BigNumber(0);
- const edTakerFee = new BigNumber(0);
- const edFeeRebate = new BigNumber(0);
- etherDelta = await EtherDeltaContract.deployFrom0xArtifactAsync(
- artifacts.EtherDelta,
- provider,
- txDefaults,
- edAdminAddress,
- feeRecipient,
- accountLevels.address,
- edMakerFee,
- edTakerFee,
- edFeeRebate,
- );
- const tokenTransferProxy = await TokenTransferProxyContract.deployFrom0xArtifactAsync(
- artifacts.TokenTransferProxy,
- provider,
- txDefaults,
- );
- const exchange = await ExchangeContract.deployFrom0xArtifactAsync(
- artifacts.Exchange,
- provider,
- txDefaults,
- zrx.address,
- tokenTransferProxy.address,
- );
- await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] });
- zeroEx = new ZeroEx(provider, {
- exchangeContractAddress: exchange.address,
- networkId: constants.TESTRPC_NETWORK_ID,
- });
- exWrapper = new ExchangeWrapper(exchange, zeroEx);
+// // From a bird's eye view - we create two orders.
+// // 0x order of 1 ZRX (maker) for 1 WETH (taker)
+// // ED order of 2 WETH (tokenGive) for 1 ZRX (tokenGet)
+// // And then we do an atomic arbitrage between them which gives us 1 WETH.
+// before(async () => {
+// const accounts = await web3Wrapper.getAvailableAddressesAsync();
+// [coinbase, maker, edMaker, edFrontRunner] = accounts;
+// weth = await DummyTokenContract.deployFrom0xArtifactAsync(
+// artifacts.DummyToken,
+// provider,
+// txDefaults,
+// constants.DUMMY_TOKEN_NAME,
+// constants.DUMMY_TOKEN_SYMBOL,
+// constants.DUMMY_TOKEN_DECIMALS,
+// constants.DUMMY_TOKEN_TOTAL_SUPPLY,
+// );
+// zrx = await DummyTokenContract.deployFrom0xArtifactAsync(
+// artifacts.DummyToken,
+// provider,
+// txDefaults,
+// constants.DUMMY_TOKEN_NAME,
+// constants.DUMMY_TOKEN_SYMBOL,
+// constants.DUMMY_TOKEN_DECIMALS,
+// constants.DUMMY_TOKEN_TOTAL_SUPPLY,
+// );
+// const accountLevels = await AccountLevelsContract.deployFrom0xArtifactAsync(
+// artifacts.AccountLevels,
+// provider,
+// txDefaults,
+// );
+// const edAdminAddress = accounts[0];
+// const edMakerFee = new BigNumber(0);
+// const edTakerFee = new BigNumber(0);
+// const edFeeRebate = new BigNumber(0);
+// etherDelta = await EtherDeltaContract.deployFrom0xArtifactAsync(
+// artifacts.EtherDelta,
+// provider,
+// txDefaults,
+// edAdminAddress,
+// feeRecipient,
+// accountLevels.address,
+// edMakerFee,
+// edTakerFee,
+// edFeeRebate,
+// );
+// const tokenTransferProxy = await TokenTransferProxyContract.deployFrom0xArtifactAsync(
+// artifacts.TokenTransferProxy,
+// provider,
+// txDefaults,
+// );
+// const exchange = await ExchangeContract.deployFrom0xArtifactAsync(
+// artifacts.Exchange,
+// provider,
+// txDefaults,
+// zrx.address,
+// tokenTransferProxy.address,
+// );
+// await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] });
+// zeroEx = new ZeroEx(provider, {
+// exchangeContractAddress: exchange.address,
+// networkId: constants.TESTRPC_NETWORK_ID,
+// });
+// exWrapper = new ExchangeWrapper(exchange, zeroEx);
- makerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18);
- takerTokenAmount = makerTokenAmount;
- const defaultOrderParams = {
- exchangeContractAddress: exchange.address,
- maker,
- feeRecipient,
- makerTokenAddress: zrx.address,
- takerTokenAddress: weth.address,
- makerTokenAmount,
- takerTokenAmount,
- makerFee: new BigNumber(0),
- takerFee: new BigNumber(0),
- };
- orderFactory = new OrderFactory(zeroEx, defaultOrderParams);
- arbitrage = await ArbitrageContract.deployFrom0xArtifactAsync(
- artifacts.Arbitrage,
- provider,
- txDefaults,
- exchange.address,
- etherDelta.address,
- tokenTransferProxy.address,
- );
- // Enable arbitrage and withdrawals of tokens
- await arbitrage.setAllowances.sendTransactionAsync(weth.address, { from: coinbase });
- await arbitrage.setAllowances.sendTransactionAsync(zrx.address, { from: coinbase });
+// makerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18);
+// takerTokenAmount = makerTokenAmount;
+// const defaultOrderParams = {
+// exchangeContractAddress: exchange.address,
+// maker,
+// feeRecipient,
+// makerTokenAddress: zrx.address,
+// takerTokenAddress: weth.address,
+// makerTokenAmount,
+// takerTokenAmount,
+// makerFee: new BigNumber(0),
+// takerFee: new BigNumber(0),
+// };
+// orderFactory = new OrderFactory(zeroEx, defaultOrderParams);
+// arbitrage = await ArbitrageContract.deployFrom0xArtifactAsync(
+// artifacts.Arbitrage,
+// provider,
+// txDefaults,
+// exchange.address,
+// etherDelta.address,
+// tokenTransferProxy.address,
+// );
+// // Enable arbitrage and withdrawals of tokens
+// await arbitrage.setAllowances.sendTransactionAsync(weth.address, { from: coinbase });
+// await arbitrage.setAllowances.sendTransactionAsync(zrx.address, { from: coinbase });
- // Give some tokens to arbitrage contract
- await weth.setBalance.sendTransactionAsync(arbitrage.address, takerTokenAmount, { from: coinbase });
+// // Give some tokens to arbitrage contract
+// await weth.setBalance.sendTransactionAsync(arbitrage.address, takerTokenAmount, { from: coinbase });
- // Fund the maker on exchange side
- await zrx.setBalance.sendTransactionAsync(maker, makerTokenAmount, { from: coinbase });
- // Set the allowance for the maker on Exchange side
- await zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: maker });
+// // Fund the maker on exchange side
+// await zrx.setBalance.sendTransactionAsync(maker, makerTokenAmount, { from: coinbase });
+// // Set the allowance for the maker on Exchange side
+// await zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: maker });
- amountGive = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18);
- // Fund the maker on EtherDelta side
- await weth.setBalance.sendTransactionAsync(edMaker, amountGive, { from: coinbase });
- // Set the allowance for the maker on EtherDelta side
- await weth.approve.sendTransactionAsync(etherDelta.address, INITIAL_ALLOWANCE, { from: edMaker });
- // Deposit maker funds into EtherDelta
- await etherDelta.depositToken.sendTransactionAsync(weth.address, amountGive, { from: edMaker });
+// amountGive = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18);
+// // Fund the maker on EtherDelta side
+// await weth.setBalance.sendTransactionAsync(edMaker, amountGive, { from: coinbase });
+// // Set the allowance for the maker on EtherDelta side
+// await weth.approve.sendTransactionAsync(etherDelta.address, INITIAL_ALLOWANCE, { from: edMaker });
+// // Deposit maker funds into EtherDelta
+// await etherDelta.depositToken.sendTransactionAsync(weth.address, amountGive, { from: edMaker });
- amountGet = makerTokenAmount;
- // Fund the front runner on EtherDelta side
- await zrx.setBalance.sendTransactionAsync(edFrontRunner, amountGet, { from: coinbase });
- // Set the allowance for the front-runner on EtherDelta side
- await zrx.approve.sendTransactionAsync(etherDelta.address, INITIAL_ALLOWANCE, { from: edFrontRunner });
- // Deposit front runner funds into EtherDelta
- await etherDelta.depositToken.sendTransactionAsync(zrx.address, amountGet, { from: edFrontRunner });
- });
- beforeEach(async () => {
- await blockchainLifecycle.startAsync();
- });
- afterEach(async () => {
- await blockchainLifecycle.revertAsync();
- });
- describe('makeAtomicTrade', () => {
- let addresses: string[];
- let values: BigNumber[];
- let v: number[];
- let r: string[];
- let s: string[];
- let tokenGet: string;
- let tokenGive: string;
- let expires: BigNumber;
- let nonce: BigNumber;
- let edSignature: ECSignature;
- before(async () => {
- signedOrder = await orderFactory.newSignedOrderAsync();
- tokenGet = zrx.address;
- tokenGive = weth.address;
- const blockNumber = await web3Wrapper.getBlockNumberAsync();
- const ED_ORDER_EXPIRATION_IN_BLOCKS = 10;
- expires = new BigNumber(blockNumber + ED_ORDER_EXPIRATION_IN_BLOCKS);
- nonce = new BigNumber(42);
- const edOrderHash = `0x${crypto
- .solSHA256([etherDelta.address, tokenGet, amountGet, tokenGive, amountGive, expires, nonce])
- .toString('hex')}`;
- const shouldAddPersonalMessagePrefix = false;
- edSignature = await zeroEx.signOrderHashAsync(edOrderHash, edMaker, shouldAddPersonalMessagePrefix);
- addresses = [
- signedOrder.maker,
- signedOrder.taker,
- signedOrder.makerTokenAddress,
- signedOrder.takerTokenAddress,
- signedOrder.feeRecipient,
- edMaker,
- ];
- const fillTakerTokenAmount = takerTokenAmount;
- const edFillAmount = makerTokenAmount;
- values = [
- signedOrder.makerTokenAmount,
- signedOrder.takerTokenAmount,
- signedOrder.makerFee,
- signedOrder.takerFee,
- signedOrder.expirationUnixTimestampSec,
- signedOrder.salt,
- fillTakerTokenAmount,
- amountGet,
- amountGive,
- expires,
- nonce,
- edFillAmount,
- ];
- v = [signedOrder.ecSignature.v, edSignature.v];
- r = [signedOrder.ecSignature.r, edSignature.r];
- s = [signedOrder.ecSignature.s, edSignature.s];
- });
- it('should successfully execute the arbitrage if not front-runned', async () => {
- const txHash = await arbitrage.makeAtomicTrade.sendTransactionAsync(addresses, values, v, r, s, {
- from: coinbase,
- });
- const res = await zeroEx.awaitTransactionMinedAsync(txHash);
- const postBalance = await weth.balanceOf.callAsync(arbitrage.address);
- expect(postBalance).to.be.bignumber.equal(amountGive);
- });
- it('should fail and revert if front-runned', async () => {
- const preBalance = await weth.balanceOf.callAsync(arbitrage.address);
- // Front-running transaction
- await etherDelta.trade.sendTransactionAsync(
- tokenGet,
- amountGet,
- tokenGive,
- amountGive,
- expires,
- nonce,
- edMaker,
- edSignature.v,
- edSignature.r,
- edSignature.s,
- amountGet,
- { from: edFrontRunner },
- );
- // tslint:disable-next-line:await-promise
- await expect(
- arbitrage.makeAtomicTrade.sendTransactionAsync(addresses, values, v, r, s, { from: coinbase }),
- ).to.be.rejectedWith(constants.REVERT);
- const postBalance = await weth.balanceOf.callAsync(arbitrage.address);
- expect(preBalance).to.be.bignumber.equal(postBalance);
- });
- });
-});
+// amountGet = makerTokenAmount;
+// // Fund the front runner on EtherDelta side
+// await zrx.setBalance.sendTransactionAsync(edFrontRunner, amountGet, { from: coinbase });
+// // Set the allowance for the front-runner on EtherDelta side
+// await zrx.approve.sendTransactionAsync(etherDelta.address, INITIAL_ALLOWANCE, { from: edFrontRunner });
+// // Deposit front runner funds into EtherDelta
+// await etherDelta.depositToken.sendTransactionAsync(zrx.address, amountGet, { from: edFrontRunner });
+// });
+// beforeEach(async () => {
+// await blockchainLifecycle.startAsync();
+// });
+// afterEach(async () => {
+// await blockchainLifecycle.revertAsync();
+// });
+// describe('makeAtomicTrade', () => {
+// let addresses: string[];
+// let values: BigNumber[];
+// let v: number[];
+// let r: string[];
+// let s: string[];
+// let tokenGet: string;
+// let tokenGive: string;
+// let expires: BigNumber;
+// let nonce: BigNumber;
+// let edSignature: ECSignature;
+// before(async () => {
+// signedOrder = await orderFactory.newSignedOrderAsync();
+// tokenGet = zrx.address;
+// tokenGive = weth.address;
+// const blockNumber = await web3Wrapper.getBlockNumberAsync();
+// const ED_ORDER_EXPIRATION_IN_BLOCKS = 10;
+// expires = new BigNumber(blockNumber + ED_ORDER_EXPIRATION_IN_BLOCKS);
+// nonce = new BigNumber(42);
+// const edOrderHash = `0x${crypto
+// .solSHA256([etherDelta.address, tokenGet, amountGet, tokenGive, amountGive, expires, nonce])
+// .toString('hex')}`;
+// const shouldAddPersonalMessagePrefix = false;
+// edSignature = await zeroEx.signOrderHashAsync(edOrderHash, edMaker, shouldAddPersonalMessagePrefix);
+// addresses = [
+// signedOrder.maker,
+// signedOrder.taker,
+// signedOrder.makerTokenAddress,
+// signedOrder.takerTokenAddress,
+// signedOrder.feeRecipient,
+// edMaker,
+// ];
+// const fillTakerTokenAmount = takerTokenAmount;
+// const edFillAmount = makerTokenAmount;
+// values = [
+// signedOrder.makerTokenAmount,
+// signedOrder.takerTokenAmount,
+// signedOrder.makerFee,
+// signedOrder.takerFee,
+// signedOrder.expirationUnixTimestampSec,
+// signedOrder.salt,
+// fillTakerTokenAmount,
+// amountGet,
+// amountGive,
+// expires,
+// nonce,
+// edFillAmount,
+// ];
+// v = [signedOrder.ecSignature.v, edSignature.v];
+// r = [signedOrder.ecSignature.r, edSignature.r];
+// s = [signedOrder.ecSignature.s, edSignature.s];
+// });
+// it('should successfully execute the arbitrage if not front-runned', async () => {
+// const txHash = await arbitrage.makeAtomicTrade.sendTransactionAsync(addresses, values, v, r, s, {
+// from: coinbase,
+// });
+// const res = await zeroEx.awaitTransactionMinedAsync(txHash);
+// const postBalance = await weth.balanceOf.callAsync(arbitrage.address);
+// expect(postBalance).to.be.bignumber.equal(amountGive);
+// });
+// it('should fail and revert if front-runned', async () => {
+// const preBalance = await weth.balanceOf.callAsync(arbitrage.address);
+// // Front-running transaction
+// await etherDelta.trade.sendTransactionAsync(
+// tokenGet,
+// amountGet,
+// tokenGive,
+// amountGive,
+// expires,
+// nonce,
+// edMaker,
+// edSignature.v,
+// edSignature.r,
+// edSignature.s,
+// amountGet,
+// { from: edFrontRunner },
+// );
+// // tslint:disable-next-line:await-promise
+// await expect(
+// arbitrage.makeAtomicTrade.sendTransactionAsync(addresses, values, v, r, s, { from: coinbase }),
+// ).to.be.rejectedWith(constants.REVERT);
+// const postBalance = await weth.balanceOf.callAsync(arbitrage.address);
+// expect(preBalance).to.be.bignumber.equal(postBalance);
+// });
+// });
+// });
diff --git a/packages/contracts/test/unlimited_allowance_token.ts b/packages/contracts/test/unlimited_allowance_token.ts
index 1bb207558..27b94ead6 100644
--- a/packages/contracts/test/unlimited_allowance_token.ts
+++ b/packages/contracts/test/unlimited_allowance_token.ts
@@ -6,13 +6,11 @@ import * as chai from 'chai';
import 'make-promises-safe';
import * as Web3 from 'web3';
-import { DummyTokenContract } from '../src/contract_wrappers/generated/dummy_token';
-import { artifacts } from '../util/artifacts';
-import { constants } from '../util/constants';
-import { ContractName } from '../util/types';
-
-import { chaiSetup } from './utils/chai_setup';
-import { provider, txDefaults, web3Wrapper } from './utils/web3_wrapper';
+import { DummyERC20TokenContract } from '../src/contract_wrappers/generated/dummy_e_r_c20_token';
+import { artifacts } from '../src/utils/artifacts';
+import { chaiSetup } from '../src/utils/chai_setup';
+import { constants } from '../src/utils/constants';
+import { provider, txDefaults, web3Wrapper } from '../src/utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
@@ -21,21 +19,26 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('UnlimitedAllowanceToken', () => {
let owner: string;
let spender: string;
- const config = {
+ const zeroEx = new ZeroEx(provider, {
networkId: constants.TESTRPC_NETWORK_ID,
- };
- const zeroEx = new ZeroEx(provider, config);
+ });
const MAX_MINT_VALUE = new BigNumber(100000000000000000000);
let tokenAddress: string;
- let token: DummyTokenContract;
+ let token: DummyERC20TokenContract;
before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
owner = accounts[0];
spender = accounts[1];
- token = await DummyTokenContract.deployFrom0xArtifactAsync(
- artifacts.DummyToken,
+ token = await DummyERC20TokenContract.deployFrom0xArtifactAsync(
+ artifacts.DummyERC20Token,
provider,
txDefaults,
constants.DUMMY_TOKEN_NAME,
@@ -43,7 +46,10 @@ describe('UnlimitedAllowanceToken', () => {
constants.DUMMY_TOKEN_DECIMALS,
constants.DUMMY_TOKEN_TOTAL_SUPPLY,
);
- await token.mint.sendTransactionAsync(MAX_MINT_VALUE, { from: owner });
+ await web3Wrapper.awaitTransactionMinedAsync(
+ await token.mint.sendTransactionAsync(MAX_MINT_VALUE, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
tokenAddress = token.address;
});
beforeEach(async () => {
diff --git a/packages/contracts/test/utils/match_order_tester.ts b/packages/contracts/test/utils/match_order_tester.ts
new file mode 100644
index 000000000..ae874e502
--- /dev/null
+++ b/packages/contracts/test/utils/match_order_tester.ts
@@ -0,0 +1,356 @@
+import { LogWithDecodedArgs, ZeroEx } from '0x.js';
+import { BlockchainLifecycle } from '@0xproject/dev-utils';
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import ethUtil = require('ethereumjs-util');
+import * as _ from 'lodash';
+
+import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token';
+import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token';
+import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy';
+import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy';
+import {
+ CancelContractEventArgs,
+ ExchangeContract,
+ FillContractEventArgs,
+} from '../../src/contract_wrappers/generated/exchange';
+import { assetProxyUtils } from '../../src/utils/asset_proxy_utils';
+import { chaiSetup } from '../../src/utils/chai_setup';
+import { constants } from '../../src/utils/constants';
+import { crypto } from '../../src/utils/crypto';
+import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
+import { ERC721Wrapper } from '../../src/utils/erc721_wrapper';
+import { ExchangeWrapper } from '../../src/utils/exchange_wrapper';
+import { OrderFactory } from '../../src/utils/order_factory';
+import { orderUtils } from '../../src/utils/order_utils';
+import {
+ AssetProxyId,
+ ContractName,
+ ERC20BalancesByOwner,
+ ERC721TokenIdsByOwner,
+ ExchangeStatus,
+ SignedOrder,
+ TransferAmountsByMatchOrders as TransferAmounts,
+} from '../../src/utils/types';
+import { provider, web3Wrapper } from '../../src/utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+export class MatchOrderTester {
+ private _exchangeWrapper: ExchangeWrapper;
+ private _erc20Wrapper: ERC20Wrapper;
+ private _erc721Wrapper: ERC721Wrapper;
+ private _feeTokenAddress: string;
+
+ /// @dev Compares a pair of ERC20 balances and a pair of ERC721 token owners.
+ /// @param expectedNewERC20BalancesByOwner Expected ERC20 balances.
+ /// @param realERC20BalancesByOwner Actual ERC20 balances.
+ /// @param expectedNewERC721TokenIdsByOwner Expected ERC721 token owners.
+ /// @param realERC721TokenIdsByOwner Actual ERC20 token owners.
+ /// @return True only if ERC20 balances match and ERC721 token owners match.
+ private static _compareExpectedAndRealBalances(
+ expectedNewERC20BalancesByOwner: ERC20BalancesByOwner,
+ realERC20BalancesByOwner: ERC20BalancesByOwner,
+ expectedNewERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
+ realERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
+ ): boolean {
+ // ERC20 Balances
+ const doesErc20BalancesMatch = _.isEqual(expectedNewERC20BalancesByOwner, realERC20BalancesByOwner);
+ if (!doesErc20BalancesMatch) {
+ return false;
+ }
+ // ERC721 Token Ids
+ const sortedExpectedNewERC721TokenIdsByOwner = _.mapValues(
+ expectedNewERC721TokenIdsByOwner,
+ tokenIdsByOwner => {
+ _.mapValues(tokenIdsByOwner, tokenIds => {
+ _.sortBy(tokenIds);
+ });
+ },
+ );
+ const sortedNewERC721TokenIdsByOwner = _.mapValues(realERC721TokenIdsByOwner, tokenIdsByOwner => {
+ _.mapValues(tokenIdsByOwner, tokenIds => {
+ _.sortBy(tokenIds);
+ });
+ });
+ const doesErc721TokenIdsMatch = _.isEqual(
+ sortedExpectedNewERC721TokenIdsByOwner,
+ sortedNewERC721TokenIdsByOwner,
+ );
+ return doesErc721TokenIdsMatch;
+ }
+ /// @dev Constructs new MatchOrderTester.
+ /// @param exchangeWrapper Used to call to the Exchange.
+ /// @param erc20Wrapper Used to fetch ERC20 balances.
+ /// @param erc721Wrapper Used to fetch ERC721 token owners.
+ /// @param feeTokenAddress Address of ERC20 fee token.
+ constructor(
+ exchangeWrapper: ExchangeWrapper,
+ erc20Wrapper: ERC20Wrapper,
+ erc721Wrapper: ERC721Wrapper,
+ feeTokenAddress: string,
+ ) {
+ this._exchangeWrapper = exchangeWrapper;
+ this._erc20Wrapper = erc20Wrapper;
+ this._erc721Wrapper = erc721Wrapper;
+ this._feeTokenAddress = feeTokenAddress;
+ }
+ /// @dev Matches two complementary orders and validates results.
+ /// Validation either succeeds or throws.
+ /// @param signedOrderLeft First matched order.
+ /// @param signedOrderRight Second matched order.
+ /// @param takerAddress Address of taker (the address who matched the two orders)
+ /// @param erc20BalancesByOwner Current ERC20 balances.
+ /// @param erc721TokenIdsByOwner Current ERC721 token owners.
+ /// @param initialTakerAssetFilledAmountLeft Current amount the left order has been filled.
+ /// @param initialTakerAssetFilledAmountRight Current amount the right order has been filled.
+ /// @return New ERC20 balances & ERC721 token owners.
+ public async matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft: SignedOrder,
+ signedOrderRight: SignedOrder,
+ takerAddress: string,
+ erc20BalancesByOwner: ERC20BalancesByOwner,
+ erc721TokenIdsByOwner: ERC721TokenIdsByOwner,
+ initialTakerAssetFilledAmountLeft?: BigNumber,
+ initialTakerAssetFilledAmountRight?: BigNumber,
+ ): Promise<[ERC20BalancesByOwner, ERC721TokenIdsByOwner]> {
+ // Test setup & verify preconditions
+ const makerAddressLeft = signedOrderLeft.makerAddress;
+ const makerAddressRight = signedOrderRight.makerAddress;
+ const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress;
+ const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress;
+ // Verify Left order preconditions
+ const orderTakerAssetFilledAmountLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrderLeft),
+ );
+ const expectedOrderFilledAmountLeft = initialTakerAssetFilledAmountLeft
+ ? initialTakerAssetFilledAmountLeft
+ : new BigNumber(0);
+ expect(expectedOrderFilledAmountLeft).to.be.bignumber.equal(orderTakerAssetFilledAmountLeft);
+ // Verify Right order preconditions
+ const orderTakerAssetFilledAmountRight = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrderRight),
+ );
+ const expectedOrderFilledAmountRight = initialTakerAssetFilledAmountRight
+ ? initialTakerAssetFilledAmountRight
+ : new BigNumber(0);
+ expect(expectedOrderFilledAmountRight).to.be.bignumber.equal(orderTakerAssetFilledAmountRight);
+ // Match left & right orders
+ await this._exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress);
+ const newERC20BalancesByOwner = await this._erc20Wrapper.getBalancesAsync();
+ const newERC721TokenIdsByOwner = await this._erc721Wrapper.getBalancesAsync();
+ // Calculate expected balance changes
+ const expectedTransferAmounts = await this._calculateExpectedTransferAmountsAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ orderTakerAssetFilledAmountLeft,
+ orderTakerAssetFilledAmountRight,
+ );
+ let expectedERC20BalancesByOwner: ERC20BalancesByOwner;
+ let expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
+ [expectedERC20BalancesByOwner, expectedERC721TokenIdsByOwner] = this._calculateExpectedBalances(
+ signedOrderLeft,
+ signedOrderRight,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ expectedTransferAmounts,
+ );
+ // Assert our expected balances are equal to the actual balances
+ const didExpectedBalancesMatchRealBalances = MatchOrderTester._compareExpectedAndRealBalances(
+ expectedERC20BalancesByOwner,
+ newERC20BalancesByOwner,
+ expectedERC721TokenIdsByOwner,
+ newERC721TokenIdsByOwner,
+ );
+ expect(didExpectedBalancesMatchRealBalances).to.be.true();
+ return [newERC20BalancesByOwner, newERC721TokenIdsByOwner];
+ }
+ /// @dev Calculates expected transfer amounts between order makers, fee recipients, and
+ /// the taker when two orders are matched.
+ /// @param signedOrderLeft First matched order.
+ /// @param signedOrderRight Second matched order.
+ /// @param orderTakerAssetFilledAmountLeft How much left order has been filled, prior to matching orders.
+ /// @param orderTakerAssetFilledAmountRight How much the right order has been filled, prior to matching orders.
+ /// @return TransferAmounts A struct containing the expected transfer amounts.
+ private async _calculateExpectedTransferAmountsAsync(
+ signedOrderLeft: SignedOrder,
+ signedOrderRight: SignedOrder,
+ orderTakerAssetFilledAmountLeft: BigNumber,
+ orderTakerAssetFilledAmountRight: BigNumber,
+ ): Promise<TransferAmounts> {
+ let amountBoughtByLeftMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrderLeft),
+ );
+ amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(orderTakerAssetFilledAmountLeft);
+ const amountSoldByLeftMaker = amountBoughtByLeftMaker
+ .times(signedOrderLeft.makerAssetAmount)
+ .dividedToIntegerBy(signedOrderLeft.takerAssetAmount);
+ const amountReceivedByRightMaker = amountBoughtByLeftMaker
+ .times(signedOrderRight.takerAssetAmount)
+ .dividedToIntegerBy(signedOrderRight.makerAssetAmount);
+ const amountReceivedByTaker = amountSoldByLeftMaker.minus(amountReceivedByRightMaker);
+ let amountBoughtByRightMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrderRight),
+ );
+ amountBoughtByRightMaker = amountBoughtByRightMaker.minus(orderTakerAssetFilledAmountRight);
+ const amountSoldByRightMaker = amountBoughtByRightMaker
+ .times(signedOrderRight.makerAssetAmount)
+ .dividedToIntegerBy(signedOrderRight.takerAssetAmount);
+ const amountReceivedByLeftMaker = amountSoldByRightMaker;
+ const feePaidByLeftMaker = signedOrderLeft.makerFee
+ .times(amountSoldByLeftMaker)
+ .dividedToIntegerBy(signedOrderLeft.makerAssetAmount);
+ const feePaidByRightMaker = signedOrderRight.makerFee
+ .times(amountSoldByRightMaker)
+ .dividedToIntegerBy(signedOrderRight.makerAssetAmount);
+ const feePaidByTakerLeft = signedOrderLeft.takerFee
+ .times(amountSoldByLeftMaker)
+ .dividedToIntegerBy(signedOrderLeft.makerAssetAmount);
+ const feePaidByTakerRight = signedOrderRight.takerFee
+ .times(amountSoldByRightMaker)
+ .dividedToIntegerBy(signedOrderRight.makerAssetAmount);
+ const totalFeePaidByTaker = feePaidByTakerLeft.add(feePaidByTakerRight);
+ const feeReceivedLeft = feePaidByLeftMaker.add(feePaidByTakerLeft);
+ const feeReceivedRight = feePaidByRightMaker.add(feePaidByTakerRight);
+ // Return values
+ const expectedTransferAmounts = {
+ // Left Maker
+ amountBoughtByLeftMaker,
+ amountSoldByLeftMaker,
+ amountReceivedByLeftMaker,
+ feePaidByLeftMaker,
+ // Right Maker
+ amountBoughtByRightMaker,
+ amountSoldByRightMaker,
+ amountReceivedByRightMaker,
+ feePaidByRightMaker,
+ // Taker
+ amountReceivedByTaker,
+ feePaidByTakerLeft,
+ feePaidByTakerRight,
+ totalFeePaidByTaker,
+ // Fee Recipients
+ feeReceivedLeft,
+ feeReceivedRight,
+ };
+ return expectedTransferAmounts;
+ }
+ /// @dev Calculates the expected balances of order makers, fee recipients, and the taker,
+ /// as a result of matching two orders.
+ /// @param signedOrderLeft First matched order.
+ /// @param signedOrderRight Second matched order.
+ /// @param takerAddress Address of taker (the address who matched the two orders)
+ /// @param erc20BalancesByOwner Current ERC20 balances.
+ /// @param erc721TokenIdsByOwner Current ERC721 token owners.
+ /// @param expectedTransferAmounts A struct containing the expected transfer amounts.
+ /// @return Expected ERC20 balances & ERC721 token owners after orders have been matched.
+ private _calculateExpectedBalances(
+ signedOrderLeft: SignedOrder,
+ signedOrderRight: SignedOrder,
+ takerAddress: string,
+ erc20BalancesByOwner: ERC20BalancesByOwner,
+ erc721TokenIdsByOwner: ERC721TokenIdsByOwner,
+ expectedTransferAmounts: TransferAmounts,
+ ): [ERC20BalancesByOwner, ERC721TokenIdsByOwner] {
+ const makerAddressLeft = signedOrderLeft.makerAddress;
+ const makerAddressRight = signedOrderRight.makerAddress;
+ const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress;
+ const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress;
+ // Operations are performed on copies of the balances
+ const expectedNewERC20BalancesByOwner = _.cloneDeep(erc20BalancesByOwner);
+ const expectedNewERC721TokenIdsByOwner = _.cloneDeep(erc721TokenIdsByOwner);
+ // Left Maker Asset (Right Taker Asset)
+ const makerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.makerAssetData);
+ if (makerAssetProxyIdLeft === AssetProxyId.ERC20) {
+ // Decode asset data
+ const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.makerAssetData);
+ const makerAssetAddressLeft = erc20ProxyData.tokenAddress;
+ const takerAssetAddressRight = makerAssetAddressLeft;
+ // Left Maker
+ expectedNewERC20BalancesByOwner[makerAddressLeft][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[
+ makerAddressLeft
+ ][makerAssetAddressLeft].minus(expectedTransferAmounts.amountSoldByLeftMaker);
+ // Right Maker
+ expectedNewERC20BalancesByOwner[makerAddressRight][
+ takerAssetAddressRight
+ ] = expectedNewERC20BalancesByOwner[makerAddressRight][takerAssetAddressRight].add(
+ expectedTransferAmounts.amountReceivedByRightMaker,
+ );
+ // Taker
+ expectedNewERC20BalancesByOwner[takerAddress][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[
+ takerAddress
+ ][makerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByTaker);
+ } else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) {
+ // Decode asset data
+ const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(signedOrderLeft.makerAssetData);
+ const makerAssetAddressLeft = erc721ProxyData.tokenAddress;
+ const makerAssetIdLeft = erc721ProxyData.tokenId;
+ const takerAssetAddressRight = makerAssetAddressLeft;
+ const takerAssetIdRight = makerAssetIdLeft;
+ // Left Maker
+ _.remove(expectedNewERC721TokenIdsByOwner[makerAddressLeft][makerAssetAddressLeft], makerAssetIdLeft);
+ // Right Maker
+ expectedNewERC721TokenIdsByOwner[makerAddressRight][takerAssetAddressRight].push(takerAssetIdRight);
+ // Taker: Since there is only 1 asset transferred, the taker does not receive any of the left maker asset.
+ }
+ // Left Taker Asset (Right Maker Asset)
+ // Note: This exchange is only between the order makers: the Taker does not receive any of the left taker asset.
+ const takerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.takerAssetData);
+ if (takerAssetProxyIdLeft === AssetProxyId.ERC20) {
+ // Decode asset data
+ const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.takerAssetData);
+ const takerAssetAddressLeft = erc20ProxyData.tokenAddress;
+ const makerAssetAddressRight = takerAssetAddressLeft;
+ // Left Maker
+ expectedNewERC20BalancesByOwner[makerAddressLeft][takerAssetAddressLeft] = expectedNewERC20BalancesByOwner[
+ makerAddressLeft
+ ][takerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByLeftMaker);
+ // Right Maker
+ expectedNewERC20BalancesByOwner[makerAddressRight][
+ makerAssetAddressRight
+ ] = expectedNewERC20BalancesByOwner[makerAddressRight][makerAssetAddressRight].minus(
+ expectedTransferAmounts.amountSoldByRightMaker,
+ );
+ } else if (takerAssetProxyIdLeft === AssetProxyId.ERC721) {
+ // Decode asset data
+ const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(signedOrderRight.makerAssetData);
+ const makerAssetAddressRight = erc721ProxyData.tokenAddress;
+ const makerAssetIdRight = erc721ProxyData.tokenId;
+ const takerAssetAddressLeft = makerAssetAddressRight;
+ const takerAssetIdLeft = makerAssetIdRight;
+ // Right Maker
+ _.remove(expectedNewERC721TokenIdsByOwner[makerAddressRight][makerAssetAddressRight], makerAssetIdRight);
+ // Left Maker
+ expectedNewERC721TokenIdsByOwner[makerAddressLeft][takerAssetAddressLeft].push(takerAssetIdLeft);
+ }
+ // Left Maker Fees
+ expectedNewERC20BalancesByOwner[makerAddressLeft][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[
+ makerAddressLeft
+ ][this._feeTokenAddress].minus(expectedTransferAmounts.feePaidByLeftMaker);
+ // Right Maker Fees
+ expectedNewERC20BalancesByOwner[makerAddressRight][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[
+ makerAddressRight
+ ][this._feeTokenAddress].minus(expectedTransferAmounts.feePaidByRightMaker);
+ // Taker Fees
+ expectedNewERC20BalancesByOwner[takerAddress][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[
+ takerAddress
+ ][this._feeTokenAddress].minus(expectedTransferAmounts.totalFeePaidByTaker);
+ // Left Fee Recipient Fees
+ expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][
+ this._feeTokenAddress
+ ] = expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][this._feeTokenAddress].add(
+ expectedTransferAmounts.feeReceivedLeft,
+ );
+ // Right Fee Recipient Fees
+ expectedNewERC20BalancesByOwner[feeRecipientAddressRight][
+ this._feeTokenAddress
+ ] = expectedNewERC20BalancesByOwner[feeRecipientAddressRight][this._feeTokenAddress].add(
+ expectedTransferAmounts.feeReceivedRight,
+ );
+
+ return [expectedNewERC20BalancesByOwner, expectedNewERC721TokenIdsByOwner];
+ }
+}
diff --git a/packages/contracts/test/zrx_token.ts b/packages/contracts/test/zrx_token.ts
index 63c40f0e7..d7658f788 100644
--- a/packages/contracts/test/zrx_token.ts
+++ b/packages/contracts/test/zrx_token.ts
@@ -7,12 +7,10 @@ import 'make-promises-safe';
import * as Web3 from 'web3';
import { ZRXTokenContract } from '../src/contract_wrappers/generated/zrx_token';
-import { artifacts } from '../util/artifacts';
-import { constants } from '../util/constants';
-import { ContractName } from '../util/types';
-
-import { chaiSetup } from './utils/chai_setup';
-import { provider, txDefaults, web3Wrapper } from './utils/web3_wrapper';
+import { artifacts } from '../src/utils/artifacts';
+import { chaiSetup } from '../src/utils/chai_setup';
+import { constants } from '../src/utils/constants';
+import { provider, txDefaults, web3Wrapper } from '../src/utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
@@ -25,18 +23,24 @@ describe('ZRXToken', () => {
let MAX_UINT: BigNumber;
- let zrx: ZRXTokenContract;
+ let zrxToken: ZRXTokenContract;
let zrxAddress: string;
before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
owner = accounts[0];
spender = accounts[1];
zeroEx = new ZeroEx(provider, {
networkId: constants.TESTRPC_NETWORK_ID,
});
- zrx = await ZRXTokenContract.deployFrom0xArtifactAsync(artifacts.ZRX, provider, txDefaults);
- zrxAddress = zrx.address;
+ zrxToken = await ZRXTokenContract.deployFrom0xArtifactAsync(artifacts.ZRX, provider, txDefaults);
+ zrxAddress = zrxToken.address;
MAX_UINT = zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
});
beforeEach(async () => {
@@ -47,25 +51,25 @@ describe('ZRXToken', () => {
});
describe('constants', () => {
it('should have 18 decimals', async () => {
- const decimals = new BigNumber(await zrx.decimals.callAsync());
+ const decimals = new BigNumber(await zrxToken.decimals.callAsync());
const expectedDecimals = 18;
expect(decimals).to.be.bignumber.equal(expectedDecimals);
});
it('should have a total supply of 1 billion tokens', async () => {
- const totalSupply = new BigNumber(await zrx.totalSupply.callAsync());
+ const totalSupply = new BigNumber(await zrxToken.totalSupply.callAsync());
const expectedTotalSupply = 1000000000;
expect(ZeroEx.toUnitAmount(totalSupply, 18)).to.be.bignumber.equal(expectedTotalSupply);
});
it('should be named 0x Protocol Token', async () => {
- const name = await zrx.name.callAsync();
+ const name = await zrxToken.name.callAsync();
const expectedName = '0x Protocol Token';
expect(name).to.be.equal(expectedName);
});
it('should have the symbol ZRX', async () => {
- const symbol = await zrx.symbol.callAsync();
+ const symbol = await zrxToken.symbol.callAsync();
const expectedSymbol = 'ZRX';
expect(symbol).to.be.equal(expectedSymbol);
});
@@ -74,7 +78,7 @@ describe('ZRXToken', () => {
describe('constructor', () => {
it('should initialize owner balance to totalSupply', async () => {
const ownerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner);
- const totalSupply = new BigNumber(await zrx.totalSupply.callAsync());
+ const totalSupply = new BigNumber(await zrxToken.totalSupply.callAsync());
expect(totalSupply).to.be.bignumber.equal(ownerBalance);
});
});
@@ -95,7 +99,7 @@ describe('ZRXToken', () => {
});
it('should return true on a 0 value transfer', async () => {
- const didReturnTrue = await zrx.transfer.callAsync(spender, new BigNumber(0), {
+ const didReturnTrue = await zrxToken.transfer.callAsync(spender, new BigNumber(0), {
from: owner,
});
expect(didReturnTrue).to.be.true();
@@ -109,7 +113,9 @@ describe('ZRXToken', () => {
await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, amountToTransfer, {
gasLimit: constants.MAX_TOKEN_APPROVE_GAS,
});
- const didReturnTrue = await zrx.transferFrom.callAsync(owner, spender, amountToTransfer, { from: spender });
+ const didReturnTrue = await zrxToken.transferFrom.callAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ });
expect(didReturnTrue).to.be.false();
});
@@ -121,13 +127,17 @@ describe('ZRXToken', () => {
const isSpenderAllowanceInsufficient = spenderAllowance.cmp(amountToTransfer) < 0;
expect(isSpenderAllowanceInsufficient).to.be.true();
- const didReturnTrue = await zrx.transferFrom.callAsync(owner, spender, amountToTransfer, { from: spender });
+ const didReturnTrue = await zrxToken.transferFrom.callAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ });
expect(didReturnTrue).to.be.false();
});
it('should return true on a 0 value transfer', async () => {
const amountToTransfer = new BigNumber(0);
- const didReturnTrue = await zrx.transferFrom.callAsync(owner, spender, amountToTransfer, { from: spender });
+ const didReturnTrue = await zrxToken.transferFrom.callAsync(owner, spender, amountToTransfer, {
+ from: spender,
+ });
expect(didReturnTrue).to.be.true();
});
diff --git a/packages/contracts/tsconfig.json b/packages/contracts/tsconfig.json
index 717415073..86b33ede7 100644
--- a/packages/contracts/tsconfig.json
+++ b/packages/contracts/tsconfig.json
@@ -6,6 +6,13 @@
"declaration": false,
"allowJs": true
},
- "include": ["./globals.d.ts", "./src/**/*", "./util/**/*", "./test/**/*", "./migrations/**/*"],
+ "include": [
+ "./globals.d.ts",
+ "./contract_wrappers",
+ "./src/**/*",
+ "./utils/**/*",
+ "./test/**/*",
+ "./migrations/**/*"
+ ],
"exclude": ["./deploy/solc/solc_bin"]
}
diff --git a/packages/contracts/util/artifacts.ts b/packages/contracts/util/artifacts.ts
deleted file mode 100644
index 8511b0082..000000000
--- a/packages/contracts/util/artifacts.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { ContractArtifact } from '@0xproject/sol-compiler';
-
-import * as AccountLevels from '../src/artifacts/AccountLevels.json';
-import * as Arbitrage from '../src/artifacts/Arbitrage.json';
-import * as DummyToken from '../src/artifacts/DummyToken.json';
-import * as EtherDelta from '../src/artifacts/EtherDelta.json';
-import * as Exchange from '../src/artifacts/Exchange.json';
-import * as MaliciousToken from '../src/artifacts/MaliciousToken.json';
-import * as MultiSigWalletWithTimeLock from '../src/artifacts/MultiSigWalletWithTimeLock.json';
-import * as MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress from '../src/artifacts/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.json';
-import * as Token from '../src/artifacts/Token.json';
-import * as TokenRegistry from '../src/artifacts/TokenRegistry.json';
-import * as TokenTransferProxy from '../src/artifacts/TokenTransferProxy.json';
-import * as EtherToken from '../src/artifacts/WETH9.json';
-import * as ZRX from '../src/artifacts/ZRXToken.json';
-
-export const artifacts = {
- AccountLevels: (AccountLevels as any) as ContractArtifact,
- Arbitrage: (Arbitrage as any) as ContractArtifact,
- EtherDelta: (EtherDelta as any) as ContractArtifact,
- ZRX: (ZRX as any) as ContractArtifact,
- DummyToken: (DummyToken as any) as ContractArtifact,
- Token: (Token as any) as ContractArtifact,
- Exchange: (Exchange as any) as ContractArtifact,
- EtherToken: (EtherToken as any) as ContractArtifact,
- TokenRegistry: (TokenRegistry as any) as ContractArtifact,
- MaliciousToken: (MaliciousToken as any) as ContractArtifact,
- TokenTransferProxy: (TokenTransferProxy as any) as ContractArtifact,
- MultiSigWalletWithTimeLock: (MultiSigWalletWithTimeLock as any) as ContractArtifact,
- MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress: (MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress as any) as ContractArtifact,
-};
diff --git a/packages/contracts/util/balances.ts b/packages/contracts/util/balances.ts
deleted file mode 100644
index d03d4b3c5..000000000
--- a/packages/contracts/util/balances.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { BigNumber } from '@0xproject/utils';
-import * as _ from 'lodash';
-import * as Web3 from 'web3';
-
-import { DummyTokenContract } from '../src/contract_wrappers/generated/dummy_token';
-
-import { BalancesByOwner } from './types';
-
-export class Balances {
- private _tokenContractInstances: DummyTokenContract[];
- private _ownerAddresses: string[];
- constructor(tokenContractInstances: DummyTokenContract[], ownerAddresses: string[]) {
- this._tokenContractInstances = tokenContractInstances;
- this._ownerAddresses = ownerAddresses;
- }
- public async getAsync(): Promise<BalancesByOwner> {
- const balancesByOwner: BalancesByOwner = {};
- for (const tokenContractInstance of this._tokenContractInstances) {
- for (const ownerAddress of this._ownerAddresses) {
- let balance = await tokenContractInstance.balanceOf.callAsync(ownerAddress);
- balance = new BigNumber(balance);
- if (_.isUndefined(balancesByOwner[ownerAddress])) {
- balancesByOwner[ownerAddress] = {};
- }
- balancesByOwner[ownerAddress][tokenContractInstance.address] = balance;
- }
- }
- return balancesByOwner;
- }
-}
diff --git a/packages/contracts/util/constants.ts b/packages/contracts/util/constants.ts
deleted file mode 100644
index d287986b7..000000000
--- a/packages/contracts/util/constants.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { BigNumber } from '@0xproject/utils';
-
-export const constants = {
- INVALID_OPCODE: 'invalid opcode',
- REVERT: 'revert',
- TESTRPC_NETWORK_ID: 50,
- MAX_ETHERTOKEN_WITHDRAW_GAS: 43000,
- MAX_TOKEN_TRANSFERFROM_GAS: 80000,
- MAX_TOKEN_APPROVE_GAS: 60000,
- DUMMY_TOKEN_NAME: '',
- DUMMY_TOKEN_SYMBOL: '',
- DUMMY_TOKEN_DECIMALS: new BigNumber(18),
- DUMMY_TOKEN_TOTAL_SUPPLY: new BigNumber(0),
-};
diff --git a/packages/contracts/util/exchange_wrapper.ts b/packages/contracts/util/exchange_wrapper.ts
deleted file mode 100644
index f016067fe..000000000
--- a/packages/contracts/util/exchange_wrapper.ts
+++ /dev/null
@@ -1,230 +0,0 @@
-import { SignedOrder, TransactionReceiptWithDecodedLogs, ZeroEx } from '0x.js';
-import { BigNumber } from '@0xproject/utils';
-import * as _ from 'lodash';
-import * as Web3 from 'web3';
-
-import { ExchangeContract } from '../src/contract_wrappers/generated/exchange';
-
-import { formatters } from './formatters';
-import { signedOrderUtils } from './signed_order_utils';
-
-export class ExchangeWrapper {
- private _exchange: ExchangeContract;
- private _zeroEx: ZeroEx;
- constructor(exchangeContract: ExchangeContract, zeroEx: ZeroEx) {
- this._exchange = exchangeContract;
- this._zeroEx = zeroEx;
- }
- public async fillOrderAsync(
- signedOrder: SignedOrder,
- from: string,
- opts: {
- fillTakerTokenAmount?: BigNumber;
- shouldThrowOnInsufficientBalanceOrAllowance?: boolean;
- } = {},
- ): Promise<TransactionReceiptWithDecodedLogs> {
- const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance;
- const params = signedOrderUtils.createFill(
- signedOrder,
- shouldThrowOnInsufficientBalanceOrAllowance,
- opts.fillTakerTokenAmount,
- );
- const txHash = await this._exchange.fillOrder.sendTransactionAsync(
- params.orderAddresses,
- params.orderValues,
- params.fillTakerTokenAmount,
- params.shouldThrowOnInsufficientBalanceOrAllowance,
- params.v,
- params.r,
- params.s,
- { from },
- );
- const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash);
- tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address);
- _.each(tx.logs, log => wrapLogBigNumbers(log));
- return tx;
- }
- public async cancelOrderAsync(
- signedOrder: SignedOrder,
- from: string,
- opts: { cancelTakerTokenAmount?: BigNumber } = {},
- ): Promise<TransactionReceiptWithDecodedLogs> {
- const params = signedOrderUtils.createCancel(signedOrder, opts.cancelTakerTokenAmount);
- const txHash = await this._exchange.cancelOrder.sendTransactionAsync(
- params.orderAddresses,
- params.orderValues,
- params.cancelTakerTokenAmount,
- { from },
- );
- const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash);
- tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address);
- _.each(tx.logs, log => wrapLogBigNumbers(log));
- return tx;
- }
- public async fillOrKillOrderAsync(
- signedOrder: SignedOrder,
- from: string,
- opts: { fillTakerTokenAmount?: BigNumber } = {},
- ): Promise<TransactionReceiptWithDecodedLogs> {
- const shouldThrowOnInsufficientBalanceOrAllowance = true;
- const params = signedOrderUtils.createFill(
- signedOrder,
- shouldThrowOnInsufficientBalanceOrAllowance,
- opts.fillTakerTokenAmount,
- );
- const txHash = await this._exchange.fillOrKillOrder.sendTransactionAsync(
- params.orderAddresses,
- params.orderValues,
- params.fillTakerTokenAmount,
- params.v,
- params.r,
- params.s,
- { from },
- );
- const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash);
- tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address);
- _.each(tx.logs, log => wrapLogBigNumbers(log));
- return tx;
- }
- public async batchFillOrdersAsync(
- orders: SignedOrder[],
- from: string,
- opts: {
- fillTakerTokenAmounts?: BigNumber[];
- shouldThrowOnInsufficientBalanceOrAllowance?: boolean;
- } = {},
- ): Promise<TransactionReceiptWithDecodedLogs> {
- const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance;
- const params = formatters.createBatchFill(
- orders,
- shouldThrowOnInsufficientBalanceOrAllowance,
- opts.fillTakerTokenAmounts,
- );
- const txHash = await this._exchange.batchFillOrders.sendTransactionAsync(
- params.orderAddresses,
- params.orderValues,
- params.fillTakerTokenAmounts,
- params.shouldThrowOnInsufficientBalanceOrAllowance,
- params.v,
- params.r,
- params.s,
- { from },
- );
- const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash);
- tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address);
- _.each(tx.logs, log => wrapLogBigNumbers(log));
- return tx;
- }
- public async batchFillOrKillOrdersAsync(
- orders: SignedOrder[],
- from: string,
- opts: { fillTakerTokenAmounts?: BigNumber[]; shouldThrowOnInsufficientBalanceOrAllowance?: boolean } = {},
- ): Promise<TransactionReceiptWithDecodedLogs> {
- const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance;
- const params = formatters.createBatchFill(
- orders,
- shouldThrowOnInsufficientBalanceOrAllowance,
- opts.fillTakerTokenAmounts,
- );
- const txHash = await this._exchange.batchFillOrKillOrders.sendTransactionAsync(
- params.orderAddresses,
- params.orderValues,
- params.fillTakerTokenAmounts,
- params.v,
- params.r,
- params.s,
- { from },
- );
- const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash);
- tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address);
- _.each(tx.logs, log => wrapLogBigNumbers(log));
- return tx;
- }
- public async fillOrdersUpToAsync(
- orders: SignedOrder[],
- from: string,
- opts: { fillTakerTokenAmount: BigNumber; shouldThrowOnInsufficientBalanceOrAllowance?: boolean },
- ): Promise<TransactionReceiptWithDecodedLogs> {
- const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance;
- const params = formatters.createFillUpTo(
- orders,
- shouldThrowOnInsufficientBalanceOrAllowance,
- opts.fillTakerTokenAmount,
- );
- const txHash = await this._exchange.fillOrdersUpTo.sendTransactionAsync(
- params.orderAddresses,
- params.orderValues,
- params.fillTakerTokenAmount,
- params.shouldThrowOnInsufficientBalanceOrAllowance,
- params.v,
- params.r,
- params.s,
- { from },
- );
- const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash);
- tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address);
- _.each(tx.logs, log => wrapLogBigNumbers(log));
- return tx;
- }
- public async batchCancelOrdersAsync(
- orders: SignedOrder[],
- from: string,
- opts: { cancelTakerTokenAmounts?: BigNumber[] } = {},
- ): Promise<TransactionReceiptWithDecodedLogs> {
- const params = formatters.createBatchCancel(orders, opts.cancelTakerTokenAmounts);
- const txHash = await this._exchange.batchCancelOrders.sendTransactionAsync(
- params.orderAddresses,
- params.orderValues,
- params.cancelTakerTokenAmounts,
- { from },
- );
- const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash);
- tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address);
- _.each(tx.logs, log => wrapLogBigNumbers(log));
- return tx;
- }
- public async getOrderHashAsync(signedOrder: SignedOrder): Promise<string> {
- const shouldThrowOnInsufficientBalanceOrAllowance = false;
- const params = signedOrderUtils.getOrderAddressesAndValues(signedOrder);
- const orderHash = await this._exchange.getOrderHash.callAsync(params.orderAddresses, params.orderValues);
- return orderHash;
- }
- public async isValidSignatureAsync(signedOrder: SignedOrder): Promise<boolean> {
- const isValidSignature = await this._exchange.isValidSignature.callAsync(
- signedOrder.maker,
- ZeroEx.getOrderHashHex(signedOrder),
- signedOrder.ecSignature.v,
- signedOrder.ecSignature.r,
- signedOrder.ecSignature.s,
- );
- return isValidSignature;
- }
- public async isRoundingErrorAsync(
- numerator: BigNumber,
- denominator: BigNumber,
- target: BigNumber,
- ): Promise<boolean> {
- const isRoundingError = await this._exchange.isRoundingError.callAsync(numerator, denominator, target);
- return isRoundingError;
- }
- public async getPartialAmountAsync(
- numerator: BigNumber,
- denominator: BigNumber,
- target: BigNumber,
- ): Promise<BigNumber> {
- const partialAmount = new BigNumber(
- await this._exchange.getPartialAmount.callAsync(numerator, denominator, target),
- );
- return partialAmount;
- }
-}
-
-function wrapLogBigNumbers(log: any): any {
- const argNames = _.keys(log.args);
- for (const argName of argNames) {
- const isWeb3BigNumber = _.startsWith(log.args[argName].constructor.toString(), 'function BigNumber(');
- if (isWeb3BigNumber) {
- log.args[argName] = new BigNumber(log.args[argName]);
- }
- }
-}
diff --git a/packages/contracts/util/formatters.ts b/packages/contracts/util/formatters.ts
deleted file mode 100644
index 3e3b67495..000000000
--- a/packages/contracts/util/formatters.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import { SignedOrder } from '0x.js';
-import { BigNumber } from '@0xproject/utils';
-import * as _ from 'lodash';
-
-import { BatchCancelOrders, BatchFillOrders, FillOrdersUpTo } from './types';
-
-export const formatters = {
- createBatchFill(
- signedOrders: SignedOrder[],
- shouldThrowOnInsufficientBalanceOrAllowance: boolean,
- fillTakerTokenAmounts: BigNumber[] = [],
- ): BatchFillOrders {
- const batchFill: BatchFillOrders = {
- orderAddresses: [],
- orderValues: [],
- fillTakerTokenAmounts,
- shouldThrowOnInsufficientBalanceOrAllowance,
- v: [],
- r: [],
- s: [],
- };
- _.forEach(signedOrders, signedOrder => {
- batchFill.orderAddresses.push([
- signedOrder.maker,
- signedOrder.taker,
- signedOrder.makerTokenAddress,
- signedOrder.takerTokenAddress,
- signedOrder.feeRecipient,
- ]);
- batchFill.orderValues.push([
- signedOrder.makerTokenAmount,
- signedOrder.takerTokenAmount,
- signedOrder.makerFee,
- signedOrder.takerFee,
- signedOrder.expirationUnixTimestampSec,
- signedOrder.salt,
- ]);
- batchFill.v.push(signedOrder.ecSignature.v);
- batchFill.r.push(signedOrder.ecSignature.r);
- batchFill.s.push(signedOrder.ecSignature.s);
- if (fillTakerTokenAmounts.length < signedOrders.length) {
- batchFill.fillTakerTokenAmounts.push(signedOrder.takerTokenAmount);
- }
- });
- return batchFill;
- },
- createFillUpTo(
- signedOrders: SignedOrder[],
- shouldThrowOnInsufficientBalanceOrAllowance: boolean,
- fillTakerTokenAmount: BigNumber,
- ): FillOrdersUpTo {
- const fillUpTo: FillOrdersUpTo = {
- orderAddresses: [],
- orderValues: [],
- fillTakerTokenAmount,
- shouldThrowOnInsufficientBalanceOrAllowance,
- v: [],
- r: [],
- s: [],
- };
- signedOrders.forEach(signedOrder => {
- fillUpTo.orderAddresses.push([
- signedOrder.maker,
- signedOrder.taker,
- signedOrder.makerTokenAddress,
- signedOrder.takerTokenAddress,
- signedOrder.feeRecipient,
- ]);
- fillUpTo.orderValues.push([
- signedOrder.makerTokenAmount,
- signedOrder.takerTokenAmount,
- signedOrder.makerFee,
- signedOrder.takerFee,
- signedOrder.expirationUnixTimestampSec,
- signedOrder.salt,
- ]);
- fillUpTo.v.push(signedOrder.ecSignature.v);
- fillUpTo.r.push(signedOrder.ecSignature.r);
- fillUpTo.s.push(signedOrder.ecSignature.s);
- });
- return fillUpTo;
- },
- createBatchCancel(signedOrders: SignedOrder[], cancelTakerTokenAmounts: BigNumber[] = []): BatchCancelOrders {
- const batchCancel: BatchCancelOrders = {
- orderAddresses: [],
- orderValues: [],
- cancelTakerTokenAmounts,
- };
- signedOrders.forEach(signedOrder => {
- batchCancel.orderAddresses.push([
- signedOrder.maker,
- signedOrder.taker,
- signedOrder.makerTokenAddress,
- signedOrder.takerTokenAddress,
- signedOrder.feeRecipient,
- ]);
- batchCancel.orderValues.push([
- signedOrder.makerTokenAmount,
- signedOrder.takerTokenAmount,
- signedOrder.makerFee,
- signedOrder.takerFee,
- signedOrder.expirationUnixTimestampSec,
- signedOrder.salt,
- ]);
- if (cancelTakerTokenAmounts.length < signedOrders.length) {
- batchCancel.cancelTakerTokenAmounts.push(signedOrder.takerTokenAmount);
- }
- });
- return batchCancel;
- },
-};
diff --git a/packages/contracts/util/order_factory.ts b/packages/contracts/util/order_factory.ts
deleted file mode 100644
index 8ba5df24a..000000000
--- a/packages/contracts/util/order_factory.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Order, SignedOrder, ZeroEx } from '0x.js';
-import { BigNumber } from '@0xproject/utils';
-import { Web3Wrapper } from '@0xproject/web3-wrapper';
-import * as _ from 'lodash';
-
-import { DefaultOrderParams } from './types';
-
-export class OrderFactory {
- private _defaultOrderParams: Partial<Order>;
- private _zeroEx: ZeroEx;
- constructor(zeroEx: ZeroEx, defaultOrderParams: Partial<Order>) {
- this._defaultOrderParams = defaultOrderParams;
- this._zeroEx = zeroEx;
- }
- public async newSignedOrderAsync(customOrderParams: Partial<Order> = {}): Promise<SignedOrder> {
- const randomExpiration = new BigNumber(Math.floor((Date.now() + Math.random() * 100000000000) / 1000));
- const order = ({
- expirationUnixTimestampSec: randomExpiration,
- salt: ZeroEx.generatePseudoRandomSalt(),
- taker: ZeroEx.NULL_ADDRESS,
- ...this._defaultOrderParams,
- ...customOrderParams,
- } as any) as Order;
- const orderHashHex = ZeroEx.getOrderHashHex(order);
- const shouldAddPersonalMessagePrefix = false;
- const ecSignature = await this._zeroEx.signOrderHashAsync(
- orderHashHex,
- order.maker,
- shouldAddPersonalMessagePrefix,
- );
- const signedOrder = {
- ...order,
- ecSignature,
- };
- return signedOrder;
- }
-}
diff --git a/packages/contracts/util/signed_order_utils.ts b/packages/contracts/util/signed_order_utils.ts
deleted file mode 100644
index 30a2814e7..000000000
--- a/packages/contracts/util/signed_order_utils.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { SignedOrder } from '0x.js';
-import { BigNumber } from '@0xproject/utils';
-import { Web3Wrapper } from '@0xproject/web3-wrapper';
-import ethUtil = require('ethereumjs-util');
-import * as _ from 'lodash';
-
-import { crypto } from './crypto';
-
-interface OrderAddressesAndValues {
- orderAddresses: string[];
- orderValues: BigNumber[];
-}
-
-interface OrderCancel extends OrderAddressesAndValues {
- cancelTakerTokenAmount: BigNumber;
-}
-
-export const signedOrderUtils = {
- createFill: (
- signedOrder: SignedOrder,
- shouldThrowOnInsufficientBalanceOrAllowance?: boolean,
- fillTakerTokenAmount?: BigNumber,
- ) => {
- const fill = {
- ...signedOrderUtils.getOrderAddressesAndValues(signedOrder),
- fillTakerTokenAmount: fillTakerTokenAmount || signedOrder.takerTokenAmount,
- shouldThrowOnInsufficientBalanceOrAllowance: !!shouldThrowOnInsufficientBalanceOrAllowance,
- ...signedOrder.ecSignature,
- };
- return fill;
- },
- createCancel(signedOrder: SignedOrder, cancelTakerTokenAmount?: BigNumber): OrderCancel {
- const cancel = {
- ...signedOrderUtils.getOrderAddressesAndValues(signedOrder),
- cancelTakerTokenAmount: cancelTakerTokenAmount || signedOrder.takerTokenAmount,
- };
- return cancel;
- },
- getOrderAddressesAndValues(signedOrder: SignedOrder): OrderAddressesAndValues {
- return {
- orderAddresses: [
- signedOrder.maker,
- signedOrder.taker,
- signedOrder.makerTokenAddress,
- signedOrder.takerTokenAddress,
- signedOrder.feeRecipient,
- ],
- orderValues: [
- signedOrder.makerTokenAmount,
- signedOrder.takerTokenAmount,
- signedOrder.makerFee,
- signedOrder.takerFee,
- signedOrder.expirationUnixTimestampSec,
- signedOrder.salt,
- ],
- };
- },
-};
diff --git a/packages/contracts/util/types.ts b/packages/contracts/util/types.ts
deleted file mode 100644
index 9bc412433..000000000
--- a/packages/contracts/util/types.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import { AbiDefinition, ContractAbi } from '@0xproject/types';
-import { BigNumber } from '@0xproject/utils';
-
-export interface BalancesByOwner {
- [ownerAddress: string]: {
- [tokenAddress: string]: BigNumber;
- };
-}
-
-export interface SubmissionContractEventArgs {
- transactionId: BigNumber;
-}
-
-export interface BatchFillOrders {
- orderAddresses: string[][];
- orderValues: BigNumber[][];
- fillTakerTokenAmounts: BigNumber[];
- shouldThrowOnInsufficientBalanceOrAllowance: boolean;
- v: number[];
- r: string[];
- s: string[];
-}
-
-export interface FillOrdersUpTo {
- orderAddresses: string[][];
- orderValues: BigNumber[][];
- fillTakerTokenAmount: BigNumber;
- shouldThrowOnInsufficientBalanceOrAllowance: boolean;
- v: number[];
- r: string[];
- s: string[];
-}
-
-export interface BatchCancelOrders {
- orderAddresses: string[][];
- orderValues: BigNumber[][];
- cancelTakerTokenAmounts: BigNumber[];
-}
-
-export interface DefaultOrderParams {
- exchangeContractAddress: string;
- maker: string;
- feeRecipient: string;
- makerTokenAddress: string;
- takerTokenAddress: string;
- makerTokenAmount: BigNumber;
- takerTokenAmount: BigNumber;
- makerFee: BigNumber;
- takerFee: BigNumber;
-}
-
-export interface TransactionDataParams {
- name: string;
- abi: AbiDefinition[];
- args: any[];
-}
-
-export interface MultiSigConfig {
- owners: string[];
- confirmationsRequired: number;
- secondsRequired: number;
-}
-
-export interface MultiSigConfigByNetwork {
- [networkName: string]: MultiSigConfig;
-}
-
-export interface Token {
- address?: string;
- name: string;
- symbol: string;
- decimals: number;
- ipfsHash: string;
- swarmHash: string;
-}
-
-export interface TokenInfoByNetwork {
- development: Token[];
- live: Token[];
-}
-
-export enum ExchangeContractErrs {
- ERROR_ORDER_EXPIRED,
- ERROR_ORDER_FULLY_FILLED_OR_CANCELLED,
- ERROR_ROUNDING_ERROR_TOO_LARGE,
- ERROR_INSUFFICIENT_BALANCE_OR_ALLOWANCE,
-}
-
-export enum ContractName {
- TokenTransferProxy = 'TokenTransferProxy',
- TokenRegistry = 'TokenRegistry',
- MultiSigWalletWithTimeLock = 'MultiSigWalletWithTimeLock',
- Exchange = 'Exchange',
- ZRXToken = 'ZRXToken',
- DummyToken = 'DummyToken',
- EtherToken = 'WETH9',
- MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress = 'MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress',
- MaliciousToken = 'MaliciousToken',
- AccountLevels = 'AccountLevels',
- EtherDelta = 'EtherDelta',
- Arbitrage = 'Arbitrage',
-}