aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts')
-rw-r--r--packages/contracts/compiler.json5
-rw-r--r--packages/contracts/package.json5
-rw-r--r--packages/contracts/src/abstract/abstract_asset_wrapper.ts3
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol137
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol177
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol75
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol14
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol82
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol94
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetData.sol (renamed from packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibTransferErrors.sol)21
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol16
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol18
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol40
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol111
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol31
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol17
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol36
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol10
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol20
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol4
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/libs/LibConstants.sol3
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol5
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol1
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol6
-rw-r--r--packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol16
-rw-r--r--packages/contracts/src/contracts/current/test/TestAssetDataDecoders/TestAssetDataDecoders.sol56
-rw-r--r--packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol3
-rw-r--r--packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol10
-rw-r--r--packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol5
-rw-r--r--packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol67
-rw-r--r--packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol5
-rw-r--r--packages/contracts/src/utils/artifacts.ts2
-rw-r--r--packages/contracts/src/utils/assertions.ts16
-rw-r--r--packages/contracts/src/utils/asset_wrapper.ts217
-rw-r--r--packages/contracts/src/utils/constants.ts13
-rw-r--r--packages/contracts/src/utils/core_combinatorial_utils.ts801
-rw-r--r--packages/contracts/src/utils/erc20_wrapper.ts55
-rw-r--r--packages/contracts/src/utils/erc721_wrapper.ts119
-rw-r--r--packages/contracts/src/utils/exchange_wrapper.ts8
-rw-r--r--packages/contracts/src/utils/order_factory_from_scenario.ts277
-rw-r--r--packages/contracts/src/utils/order_utils.ts7
-rw-r--r--packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts19
-rw-r--r--packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts24
-rw-r--r--packages/contracts/src/utils/types.ts77
-rw-r--r--packages/contracts/test/asset_proxy/authorizable.ts27
-rw-r--r--packages/contracts/test/asset_proxy/decoder.ts85
-rw-r--r--packages/contracts/test/asset_proxy/proxies.ts202
-rw-r--r--packages/contracts/test/exchange/core.ts554
-rw-r--r--packages/contracts/test/exchange/dispatcher.ts31
-rw-r--r--packages/contracts/test/exchange/fill_order.ts306
-rw-r--r--packages/contracts/test/exchange/match_orders.ts33
-rw-r--r--packages/contracts/test/exchange/signature_validator.ts10
-rw-r--r--packages/contracts/test/exchange/transactions.ts53
-rw-r--r--packages/contracts/test/exchange/wrapper.ts39
-rw-r--r--packages/contracts/test/libraries/lib_bytes.ts45
-rw-r--r--packages/contracts/test/unlimited_allowance_token.ts7
56 files changed, 2696 insertions, 1424 deletions
diff --git a/packages/contracts/compiler.json b/packages/contracts/compiler.json
index 6b7b69caa..1ee58081a 100644
--- a/packages/contracts/compiler.json
+++ b/packages/contracts/compiler.json
@@ -24,14 +24,17 @@
"DummyERC721Receiver",
"DummyERC721Token",
"ERC20Proxy",
+ "ERC20Token",
"ERC721Proxy",
"Exchange",
"ExchangeWrapper",
+ "IAssetData",
+ "IValidator",
+ "IWallet",
"MixinAuthorizable",
"MultiSigWallet",
"MultiSigWalletWithTimeLock",
"TestAssetProxyOwner",
- "TestAssetDataDecoders",
"TestAssetProxyDispatcher",
"TestLibBytes",
"TestLibs",
diff --git a/packages/contracts/package.json b/packages/contracts/package.json
index 8249b9e0c..b5a599fe5 100644
--- a/packages/contracts/package.json
+++ b/packages/contracts/package.json
@@ -20,7 +20,8 @@
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
"test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",
"test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha",
- "run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit",
+ "run_mocha":
+ "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit",
"compile": "sol-compiler",
"clean": "shx rm -rf lib src/generated_contract_wrappers",
"generate_contract_wrappers":
@@ -34,7 +35,7 @@
},
"config": {
"abis":
- "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|ExchangeWrapper|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetProxyOwner|TestAssetDataDecoders|TestAssetProxyDispatcher|TestLibBytes|TestLibs|TestSignatureValidator|TestValidator|TestWallet|TokenRegistry|Whitelist|WETH9|ZRXToken).json"
+ "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|ExchangeWrapper|IAssetData|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetProxyOwner|TestAssetProxyDispatcher|TestLibBytes|TestLibs|TestSignatureValidator|TestValidator|TestWallet|TokenRegistry|Whitelist|WETH9|ZRXToken).json"
},
"repository": {
"type": "git",
diff --git a/packages/contracts/src/abstract/abstract_asset_wrapper.ts b/packages/contracts/src/abstract/abstract_asset_wrapper.ts
new file mode 100644
index 000000000..4b56a8502
--- /dev/null
+++ b/packages/contracts/src/abstract/abstract_asset_wrapper.ts
@@ -0,0 +1,3 @@
+export abstract class AbstractAssetWrapper {
+ public abstract getProxyId(): string;
+}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol
index 7ca823d1f..b4780b52b 100644
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol
@@ -20,24 +20,147 @@ pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "../../utils/LibBytes/LibBytes.sol";
-import "./MixinAssetProxy.sol";
+import "./interfaces/IAssetProxy.sol";
import "./MixinAuthorizable.sol";
-import "./MixinERC20Transfer.sol";
contract ERC20Proxy is
- MixinAssetProxy,
- MixinAuthorizable,
- MixinERC20Transfer
+ IAssetProxy,
+ MixinAuthorizable
{
// Id of this proxy.
- uint8 constant PROXY_ID = 1;
+ bytes4 constant PROXY_ID = bytes4(keccak256("ERC20Token(address)"));
+
+ /// @dev Internal version of `transferFrom`.
+ /// @param assetData 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 assetData,
+ address from,
+ address to,
+ uint256 amount
+ )
+ external
+ {
+ require(
+ authorized[msg.sender],
+ "SENDER_NOT_AUTHORIZED"
+ );
+
+ // `transferFrom`.
+ // The function is marked `external`, so no abi decodeding is done for
+ // us. Instead, we expect the `calldata` memory to contain the
+ // following:
+ //
+ // | Area | Offset | Length | Contents |
+ // |----------|--------|---------|-------------------------------------|
+ // | Header | 0 | 4 | function selector |
+ // | Params | | 4 * 32 | function parameters: |
+ // | | 4 | | 1. offset to assetData (*) |
+ // | | 36 | | 2. from |
+ // | | 68 | | 3. to |
+ // | | 100 | | 4. amount |
+ // | Data | | | assetData: |
+ // | | 132 | 32 | assetData Length |
+ // | | 164 | ** | assetData Contents |
+ //
+ // (*): offset is computed from start of function parameters, so offset
+ // by an additional 4 bytes in the calldata.
+ //
+ // WARNING: The ABIv2 specification allows additional padding between
+ // the Params and Data section. This will result in a larger
+ // offset to assetData.
+
+ // Asset data itself is encoded as follows:
+ //
+ // | Area | Offset | Length | Contents |
+ // |----------|--------|---------|-------------------------------------|
+ // | Header | 0 | 4 | function selector |
+ // | Params | | 1 * 32 | function parameters: |
+ // | | 4 | 12 + 20 | 1. token address |
+
+ // Transfer tokens.
+ // We do a raw call so we can check the success separate
+ // from the return data.
+ // We construct calldata for the `token.transferFrom` ABI.
+ // The layout of this calldata is in the table below.
+ //
+ // | Area | Offset | Length | Contents |
+ // |----------|--------|---------|-------------------------------------|
+ // | Header | 0 | 4 | function selector |
+ // | Params | | 3 * 32 | function parameters: |
+ // | | 4 | | 1. from |
+ // | | 36 | | 2. to |
+ // | | 68 | | 3. amount |
+
+ assembly {
+ /////// Token contract address ///////
+ // The token address is found as follows:
+ // * It is stored at offset 4 in `assetData` contents.
+ // * This is stored at offset 32 from `assetData`.
+ // * The offset to `assetData` from Params is stored at offset
+ // 4 in calldata.
+ // * The offset of Params in calldata is 4.
+ // So we read location 4 and add 32 + 4 + 4 to it.
+ let token := calldataload(add(calldataload(4), 40))
+
+ /////// Setup Header Area ///////
+ // This area holds the 4-byte `transferFrom` selector.
+ // Any trailing data in transferFromSelector will be
+ // overwritten in the next `mstore` call.
+ mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
+
+ /////// Setup Params Area ///////
+ // We copy the fields `from`, `to` and `amount` in bulk
+ // from our own calldata to the new calldata.
+ calldatacopy(4, 36, 96)
+
+ /////// Call `token.transferFrom` using the calldata ///////
+ let success := call(
+ gas, // forward all gas
+ token, // call address of token contract
+ 0, // don't send any ETH
+ 0, // pointer to start of input
+ 100, // length of input
+ 0, // write output over input
+ 32 // output size should be 32 bytes
+ )
+
+ /////// Check return data. ///////
+ // If there is no return data, we assume the token incorrectly
+ // does not return a bool. In this case we expect it to revert
+ // on failure, which was handled above.
+ // If the token does return data, we require that it is a single
+ // nonzero 32 bytes value.
+ // So the transfer succeeded if the call succeeded and either
+ // returned nothing, or returned a non-zero 32 byte value.
+ success := and(success, or(
+ iszero(returndatasize),
+ and(
+ eq(returndatasize, 32),
+ gt(mload(0), 0)
+ )
+ ))
+ if success {
+ return(0, 0)
+ }
+
+ // Revert with `Error("TRANSFER_FAILED")`
+ mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
+ mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
+ mstore(64, 0x0000000f5452414e534645525f4641494c454400000000000000000000000000)
+ mstore(96, 0)
+ revert(0, 100)
+ }
+ }
/// @dev Gets the proxy id associated with the proxy address.
/// @return Proxy id.
function getProxyId()
external
view
- returns (uint8)
+ returns (bytes4)
{
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
index 7ff25aea3..8d7cea717 100644
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol
@@ -20,24 +20,187 @@ pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "../../utils/LibBytes/LibBytes.sol";
-import "./MixinAssetProxy.sol";
+import "./interfaces/IAssetProxy.sol";
import "./MixinAuthorizable.sol";
-import "./MixinERC721Transfer.sol";
contract ERC721Proxy is
- MixinAssetProxy,
- MixinAuthorizable,
- MixinERC721Transfer
+ IAssetProxy,
+ MixinAuthorizable
{
// Id of this proxy.
- uint8 constant PROXY_ID = 2;
+ bytes4 constant PROXY_ID = bytes4(keccak256("ERC721Token(address,uint256,bytes)"));
+
+ /// @dev Internal version of `transferFrom`.
+ /// @param assetData 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 assetData,
+ address from,
+ address to,
+ uint256 amount
+ )
+ external
+ {
+ require(
+ authorized[msg.sender],
+ "SENDER_NOT_AUTHORIZED"
+ );
+
+ // `transferFrom`.
+ // The function is marked `external`, so no abi decodeding is done for
+ // us. Instead, we expect the `calldata` memory to contain the
+ // following:
+ //
+ // | Area | Offset | Length | Contents |
+ // |----------|--------|---------|-------------------------------------|
+ // | Header | 0 | 4 | function selector |
+ // | Params | | 4 * 32 | function parameters: |
+ // | | 4 | | 1. offset to assetData (*) |
+ // | | 36 | | 2. from |
+ // | | 68 | | 3. to |
+ // | | 100 | | 4. amount |
+ // | Data | | | assetData: |
+ // | | 132 | 32 | assetData Length |
+ // | | 164 | ** | assetData Contents |
+ //
+ // (*): offset is computed from start of function parameters, so offset
+ // by an additional 4 bytes in the calldata.
+ //
+ // WARNING: The ABIv2 specification allows additional padding between
+ // the Params and Data section. This will result in a larger
+ // offset to assetData.
+
+ // Asset data itself is encoded as follows:
+ //
+ // | Area | Offset | Length | Contents |
+ // |----------|--------|---------|-------------------------------------|
+ // | Header | 0 | 4 | function selector |
+ // | Params | | 3 * 32 | function parameters: |
+ // | | 4 | 12 + 20 | 1. token address |
+ // | | 36 | | 2. tokenId |
+ // | | 68 | | 3. offset to receiverData (*) |
+ // | Data | | | receiverData: |
+ // | | 100 | 32 | receiverData Length |
+ // | | 132 | ** | receiverData Contents |
+
+ // We construct calldata for the `token.safeTransferFrom` ABI.
+ // The layout of this calldata is in the table below.
+ //
+ // | Area | Offset | Length | Contents |
+ // |----------|--------|---------|-------------------------------------|
+ // | Header | 0 | 4 | function selector |
+ // | Params | | 4 * 32 | function parameters: |
+ // | | 4 | | 1. from |
+ // | | 36 | | 2. to |
+ // | | 68 | | 3. tokenId |
+ // | | 100 | | 4. offset to receiverData (*) |
+ // | Data | | | receiverData: |
+ // | | 132 | 32 | receiverData Length |
+ // | | 164 | ** | receiverData Contents |
+
+ assembly {
+ // There exists only 1 of each token.
+ // require(amount == 1, "INVALID_AMOUNT")
+ if sub(calldataload(100), 1) {
+ // Revert with `Error("INVALID_AMOUNT")`
+ mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
+ mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
+ mstore(64, 0x0000000e494e56414c49445f414d4f554e540000000000000000000000000000)
+ mstore(96, 0)
+ revert(0, 100)
+ }
+
+ // Require assetData to be at least 132 bytes
+ let offset := calldataload(4)
+ if lt(calldataload(add(offset, 4)), 132) {
+ // Revert with `Error("LENGTH_GREATER_THAN_131_REQUIRED")`
+ mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
+ mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
+ mstore(64, 0x000000204c454e4754485f475245415445525f5448414e5f3133315f52455155)
+ mstore(96, 0x4952454400000000000000000000000000000000000000000000000000000000)
+ revert(0, 100)
+ }
+
+ /////// Setup State ///////
+ // `cdStart` is the start of the calldata for
+ // `token.safeTransferFrom` (equal to free memory ptr).
+ let cdStart := mload(64)
+ // `dataAreaLength` is the total number of words
+ // needed to store `receiverData`
+ // As-per the ABI spec, this value is padded up to
+ // the nearest multiple of 32,
+ // and includes 32-bytes for length.
+ // It's calculated as folows:
+ // - Unpadded length in bytes = `mload(receiverData) + 32`
+ // - Add 31 to convert rounding down to rounding up.
+ // Combined with the previous and this is `63`.
+ // - Round down to nearest multiple of 32 by clearing
+ // bits 0x1F. This is done with `and` and a mask.
+
+ /////// Setup Header Area ///////
+ // This area holds the 4-byte `transferFromSelector`.
+ // Any trailing data in transferFromSelector will be
+ // overwritten in the next `mstore` call.
+ mstore(cdStart, 0xb88d4fde00000000000000000000000000000000000000000000000000000000)
+
+ /////// Setup Params Area ///////
+ // Each parameter is padded to 32-bytes.
+ // The entire Params Area is 128 bytes.
+ // Notes:
+ // 1. A 20-byte mask is applied to addresses
+ // to zero-out the unused bytes.
+ // 2. The offset to `receiverData` is the length
+ // of the Params Area (128 bytes).
+
+ let length := calldataload(add(offset, 136))
+ let token := calldataload(add(offset, 40))
+
+ // Round length up to multiple of 32
+ length := and(add(length, 31), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0)
+
+ // Copy `from` and `to`
+ calldatacopy(add(cdStart, 4), 36, 64)
+
+ // TokenId
+ mstore(add(cdStart, 68), calldataload(add(offset, 72)))
+
+ // Offset to receiverData
+ mstore(add(cdStart, 100), 128)
+
+ // receiverData (including length)
+ calldatacopy(add(cdStart, 132), add(offset, 136), add(length, 32))
+
+ /////// Call `token.safeTransferFrom` using the calldata ///////
+ let success := call(
+ gas, // forward all gas
+ token, // call address of token contract
+ 0, // don't send any ETH
+ cdStart, // pointer to start of input
+ add(length, 164), // length of input
+ 0, // write output to null
+ 0 // output size is 0 bytes
+ )
+ if success {
+ return(0, 0)
+ }
+
+ // Revert with `Error("TRANSFER_FAILED")`
+ mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
+ mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
+ mstore(64, 0x0000000f5452414e534645525f4641494c454400000000000000000000000000)
+ mstore(96, 0)
+ revert(0, 100)
+ }
+ }
/// @dev Gets the proxy id associated with the proxy address.
/// @return Proxy id.
function getProxyId()
external
view
- returns (uint8)
+ returns (bytes4)
{
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
deleted file mode 100644
index 9032658e7..000000000
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
-
- 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 "./mixins/MAssetProxy.sol";
-
-contract MixinAssetProxy is
- MAuthorizable,
- MAssetProxy
-{
-
- /// @dev Transfers assets. Either succeeds or throws.
- /// @param assetData 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 assetData,
- address from,
- address to,
- uint256 amount
- )
- external
- onlyAuthorized
- {
- transferFromInternal(
- assetData,
- from,
- to,
- amount
- );
- }
-
- /// @dev Makes multiple transfers of assets. Either succeeds or throws.
- /// @param assetData 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 assetData,
- address[] memory from,
- address[] memory to,
- uint256[] memory amounts
- )
- public
- onlyAuthorized
- {
- for (uint256 i = 0; i < assetData.length; i++) {
- transferFromInternal(
- assetData[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
index 37c12f861..3b9584a44 100644
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol
@@ -19,12 +19,10 @@
pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
-import "./libs/LibAssetProxyErrors.sol";
import "../../utils/Ownable/Ownable.sol";
import "./mixins/MAuthorizable.sol";
contract MixinAuthorizable is
- LibAssetProxyErrors,
Ownable,
MAuthorizable
{
@@ -33,7 +31,7 @@ contract MixinAuthorizable is
modifier onlyAuthorized {
require(
authorized[msg.sender],
- SENDER_NOT_AUTHORIZED
+ "SENDER_NOT_AUTHORIZED"
);
_;
}
@@ -49,7 +47,7 @@ contract MixinAuthorizable is
{
require(
!authorized[target],
- TARGET_ALREADY_AUTHORIZED
+ "TARGET_ALREADY_AUTHORIZED"
);
authorized[target] = true;
@@ -65,7 +63,7 @@ contract MixinAuthorizable is
{
require(
authorized[target],
- TARGET_NOT_AUTHORIZED
+ "TARGET_NOT_AUTHORIZED"
);
delete authorized[target];
@@ -91,15 +89,15 @@ contract MixinAuthorizable is
{
require(
authorized[target],
- TARGET_NOT_AUTHORIZED
+ "TARGET_NOT_AUTHORIZED"
);
require(
index < authorities.length,
- INDEX_OUT_OF_BOUNDS
+ "INDEX_OUT_OF_BOUNDS"
);
require(
authorities[index] == target,
- AUTHORIZED_ADDRESS_MISMATCH
+ "AUTHORIZED_ADDRESS_MISMATCH"
);
delete authorized[target];
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol
deleted file mode 100644
index 0e7f3fc89..000000000
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
-
- 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 "./libs/LibTransferErrors.sol";
-
-contract MixinERC20Transfer is
- LibTransferErrors
-{
- using LibBytes for bytes;
-
- /// @dev Internal version of `transferFrom`.
- /// @param assetData 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 assetData,
- address from,
- address to,
- uint256 amount
- )
- internal
- {
- // Decode asset data.
- address token = assetData.readAddress(0);
-
- // Transfer tokens.
- // We do a raw call so we can check the success separate
- // from the return data.
- bool success = token.call(abi.encodeWithSelector(
- IERC20Token(token).transferFrom.selector,
- from,
- to,
- amount
- ));
- require(
- success,
- TRANSFER_FAILED
- );
-
- // Check return data.
- // If there is no return data, we assume the token incorrectly
- // does not return a bool. In this case we expect it to revert
- // on failure, which was handled above.
- // If the token does return data, we require that it is a single
- // value that evaluates to true.
- assembly {
- if returndatasize {
- success := 0
- if eq(returndatasize, 32) {
- // First 64 bytes of memory are reserved scratch space
- returndatacopy(0, 0, 32)
- success := mload(0)
- }
- }
- }
- require(
- success,
- TRANSFER_FAILED
- );
- }
-}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol
deleted file mode 100644
index 944068bbb..000000000
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
-
- 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 "./libs/LibTransferErrors.sol";
-
-contract MixinERC721Transfer is
- LibTransferErrors
-{
- using LibBytes for bytes;
-
- /// @dev Internal version of `transferFrom`.
- /// @param assetData 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 assetData,
- address from,
- address to,
- uint256 amount
- )
- internal
- {
- // There exists only 1 of each token.
- require(
- amount == 1,
- INVALID_AMOUNT
- );
-
- // Decode asset data.
- (
- address token,
- uint256 tokenId,
- bytes memory receiverData
- ) = decodeERC721AssetData(assetData);
-
- ERC721Token(token).safeTransferFrom(
- from,
- to,
- tokenId,
- receiverData
- );
- }
-
- /// @dev Decodes ERC721 Asset data.
- /// @param assetData Encoded byte array.
- /// @return proxyId Intended ERC721 proxy id.
- /// @return token ERC721 token address.
- /// @return tokenId ERC721 token id.
- /// @return receiverData Additional data with no specific format, which
- /// is passed to the receiving contract's onERC721Received.
- function decodeERC721AssetData(bytes memory assetData)
- internal
- pure
- returns (
- address token,
- uint256 tokenId,
- bytes memory receiverData
- )
- {
- // Decode asset data.
- token = assetData.readAddress(0);
- tokenId = assetData.readUint256(20);
- if (assetData.length > 52) {
- receiverData = assetData.readBytesWithLength(52);
- }
-
- return (
- token,
- tokenId,
- receiverData
- );
- }
-}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibTransferErrors.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetData.sol
index ba784ab22..8c78ee8c4 100644
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibTransferErrors.sol
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetData.sol
@@ -16,10 +16,21 @@
*/
-pragma solidity ^0.4.24;
+pragma solidity ^0.4.23;
-contract LibTransferErrors {
- /// Transfer errors ///
- string constant INVALID_AMOUNT = "INVALID_AMOUNT"; // Transfer amount must equal 1.
- string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Transfer failed.
+// @dev Interface of the asset proxy's assetData.
+// The asset proxies take an ABI encoded `bytes assetData` as argument.
+// This argument is ABI encoded as one of the methods of this interface.
+interface IAssetData {
+
+ function ERC20Token(
+ address tokenContract)
+ external pure;
+
+ function ERC721Token(
+ address tokenContract,
+ uint256 tokenId,
+ bytes receiverData)
+ external pure;
+
}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol
index 22f43b12f..ae8e195da 100644
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol
@@ -38,24 +38,10 @@ contract IAssetProxy is
)
external;
- /// @dev Makes multiple transfers of assets. Either succeeds or throws.
- /// @param assetData 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 assetData,
- 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);
+ returns (bytes4);
}
-
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol
index dca4f400f..338cb12e2 100644
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol
@@ -18,11 +18,19 @@
pragma solidity ^0.4.24;
+/// @dev This contract documents the revert reasons used in the AssetProxy contracts.
+/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons.
contract LibAssetProxyErrors {
+
/// Authorizable errors ///
- string constant SENDER_NOT_AUTHORIZED = "SENDER_NOT_AUTHORIZED"; // Sender not authorized to call this method.
- string constant TARGET_NOT_AUTHORIZED = "TARGET_NOT_AUTHORIZED"; // Target address not authorized to call this method.
- string constant TARGET_ALREADY_AUTHORIZED = "TARGET_ALREADY_AUTHORIZED"; // Target address must not already be authorized.
- string constant INDEX_OUT_OF_BOUNDS = "INDEX_OUT_OF_BOUNDS"; // Specified array index is out of bounds.
- string constant AUTHORIZED_ADDRESS_MISMATCH = "AUTHORIZED_ADDRESS_MISMATCH"; // Address at index does not match given target address.
+ string constant SENDER_NOT_AUTHORIZED = "SENDER_NOT_AUTHORIZED"; // Sender not authorized to call this method.
+ string constant TARGET_NOT_AUTHORIZED = "TARGET_NOT_AUTHORIZED"; // Target address not authorized to call this method.
+ string constant TARGET_ALREADY_AUTHORIZED = "TARGET_ALREADY_AUTHORIZED"; // Target address must not already be authorized.
+ string constant INDEX_OUT_OF_BOUNDS = "INDEX_OUT_OF_BOUNDS"; // Specified array index is out of bounds.
+ string constant AUTHORIZED_ADDRESS_MISMATCH = "AUTHORIZED_ADDRESS_MISMATCH"; // Address at index does not match given target address.
+
+ /// Transfer errors ///
+ string constant INVALID_AMOUNT = "INVALID_AMOUNT"; // Transfer amount must equal 1.
+ string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Transfer failed.
+ string constant LENGTH_GREATER_THAN_131_REQUIRED = "LENGTH_GREATER_THAN_131_REQUIRED"; // Byte array must have a length greater than 0.
}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol
deleted file mode 100644
index a52cb56f9..000000000
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
-
- 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 assetData 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 assetData,
- address from,
- address to,
- uint256 amount
- )
- internal;
-}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol
index b8d6c0722..f7086f543 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol
@@ -19,17 +19,18 @@
pragma solidity ^0.4.24;
import "../../utils/Ownable/Ownable.sol";
-import "./libs/LibExchangeErrors.sol";
+import "../../utils/LibBytes/LibBytes.sol";
import "./mixins/MAssetProxyDispatcher.sol";
import "../AssetProxy/interfaces/IAssetProxy.sol";
contract MixinAssetProxyDispatcher is
Ownable,
- LibExchangeErrors,
MAssetProxyDispatcher
{
+ using LibBytes for bytes;
+
// Mapping from Asset Proxy Id's to their respective Asset Proxy
- mapping (uint8 => IAssetProxy) public assetProxies;
+ mapping (bytes4 => 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.
@@ -37,7 +38,7 @@ contract MixinAssetProxyDispatcher is
/// @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,
+ bytes4 assetProxyId,
address newAssetProxy,
address oldAssetProxy
)
@@ -48,17 +49,17 @@ contract MixinAssetProxyDispatcher is
address currentAssetProxy = assetProxies[assetProxyId];
require(
oldAssetProxy == currentAssetProxy,
- ASSET_PROXY_MISMATCH
+ "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();
+ bytes4 newAssetProxyId = assetProxy.getProxyId();
require(
newAssetProxyId == assetProxyId,
- ASSET_PROXY_ID_MISMATCH
+ "ASSET_PROXY_ID_MISMATCH"
);
}
@@ -74,7 +75,7 @@ contract MixinAssetProxyDispatcher is
/// @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)
+ function getAssetProxy(bytes4 assetProxyId)
external
view
returns (address)
@@ -83,14 +84,12 @@ contract MixinAssetProxyDispatcher is
}
/// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws.
- /// @param assetData Byte array encoded for the respective asset proxy.
- /// @param assetProxyId Id of assetProxy to dispach to.
+ /// @param assetData Byte array encoded for the asset.
/// @param from Address to transfer token from.
/// @param to Address to transfer token to.
/// @param amount Amount of token to transfer.
function dispatchTransferFrom(
bytes memory assetData,
- uint8 assetProxyId,
address from,
address to,
uint256 amount
@@ -99,20 +98,94 @@ contract MixinAssetProxyDispatcher is
{
// Do nothing if no amount should be transferred.
if (amount > 0) {
+ // Ensure assetData length is valid
+ require(
+ assetData.length > 3,
+ "LENGTH_GREATER_THAN_3_REQUIRED"
+ );
+
// Lookup assetProxy
+ bytes4 assetProxyId;
+ assembly {
+ assetProxyId := and(mload(
+ add(assetData, 32)),
+ 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000
+ )
+ }
IAssetProxy assetProxy = assetProxies[assetProxyId];
+
// Ensure that assetProxy exists
require(
assetProxy != address(0),
- ASSET_PROXY_DOES_NOT_EXIST
- );
- // transferFrom will either succeed or throw.
- assetProxy.transferFrom(
- assetData,
- from,
- to,
- amount
+ "ASSET_PROXY_DOES_NOT_EXIST"
);
+
+ // We construct calldata for the `assetProxy.transferFrom` ABI.
+ // The layout of this calldata is in the table below.
+ //
+ // | Area | Offset | Length | Contents |
+ // | -------- |--------|---------|-------------------------------------------- |
+ // | Header | 0 | 4 | function selector |
+ // | Params | | 4 * 32 | function parameters: |
+ // | | 4 | | 1. offset to assetData (*) |
+ // | | 36 | | 2. from |
+ // | | 68 | | 3. to |
+ // | | 100 | | 4. amount |
+ // | Data | | | assetData: |
+ // | | 132 | 32 | assetData Length |
+ // | | 164 | ** | assetData Contents |
+
+ bool success;
+ assembly {
+ /////// Setup State ///////
+ // `cdStart` is the start of the calldata for `assetProxy.transferFrom` (equal to free memory ptr).
+ let cdStart := mload(64)
+ // `dataAreaLength` is the total number of words needed to store `assetData`
+ // As-per the ABI spec, this value is padded up to the nearest multiple of 32,
+ // and includes 32-bytes for length.
+ let dataAreaLength := and(add(mload(assetData), 63), 0xFFFFFFFFFFFE0)
+ // `cdEnd` is the end of the calldata for `assetProxy.transferFrom`.
+ let cdEnd := add(cdStart, add(132, dataAreaLength))
+
+
+ /////// Setup Header Area ///////
+ // This area holds the 4-byte `transferFromSelector`.
+ // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4
+ mstore(cdStart, 0xa85e59e400000000000000000000000000000000000000000000000000000000)
+
+ /////// Setup Params Area ///////
+ // Each parameter is padded to 32-bytes. The entire Params Area is 128 bytes.
+ // Notes:
+ // 1. The offset to `assetData` is the length of the Params Area (128 bytes).
+ // 2. A 20-byte mask is applied to addresses to zero-out the unused bytes.
+ mstore(add(cdStart, 4), 128)
+ mstore(add(cdStart, 36), and(from, 0xffffffffffffffffffffffffffffffffffffffff))
+ mstore(add(cdStart, 68), and(to, 0xffffffffffffffffffffffffffffffffffffffff))
+ mstore(add(cdStart, 100), amount)
+
+ /////// Setup Data Area ///////
+ // This area holds `assetData`.
+ let dataArea := add(cdStart, 132)
+ for {} lt(dataArea, cdEnd) {} {
+ mstore(dataArea, mload(assetData))
+ dataArea := add(dataArea, 32)
+ assetData := add(assetData, 32)
+ }
+
+ /////// Call `assetProxy.transferFrom` using the constructed calldata ///////
+ success := call(
+ gas, // forward all gas
+ assetProxy, // call address of asset proxy
+ 0, // don't send any ETH
+ cdStart, // pointer to start of input
+ sub(cdEnd, cdStart), // length of input
+ cdStart, // write output over input
+ 512 // reserve 512 bytes for output
+ )
+ if eq(success, 0) {
+ revert(cdStart, returndatasize())
+ }
+ }
}
}
}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
index b207b3e57..c0ed023ac 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
@@ -20,11 +20,9 @@ pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "./libs/LibConstants.sol";
-import "../../utils/LibBytes/LibBytes.sol";
import "./libs/LibFillResults.sol";
import "./libs/LibOrder.sol";
import "./libs/LibMath.sol";
-import "./libs/LibExchangeErrors.sol";
import "./mixins/MExchangeCore.sol";
import "./mixins/MSignatureValidator.sol";
import "./mixins/MTransactions.sol";
@@ -35,14 +33,11 @@ contract MixinExchangeCore is
LibMath,
LibOrder,
LibFillResults,
- LibExchangeErrors,
MAssetProxyDispatcher,
MExchangeCore,
MSignatureValidator,
MTransactions
{
- using LibBytes for bytes;
-
// Mapping of orderHash => amount of takerAsset already bought by maker
mapping (bytes32 => uint256) public filled;
@@ -73,7 +68,7 @@ contract MixinExchangeCore is
// Ensure orderEpoch is monotonically increasing
require(
newOrderEpoch > oldOrderEpoch,
- INVALID_NEW_ORDER_EPOCH
+ "INVALID_NEW_ORDER_EPOCH"
);
// Update orderEpoch
@@ -285,20 +280,20 @@ contract MixinExchangeCore is
// An order can only be filled if its status is FILLABLE.
require(
orderInfo.orderStatus == uint8(OrderStatus.FILLABLE),
- ORDER_UNFILLABLE
+ "ORDER_UNFILLABLE"
);
// Revert if fill amount is invalid
require(
takerAssetFillAmount != 0,
- INVALID_TAKER_AMOUNT
+ "INVALID_TAKER_AMOUNT"
);
// Validate sender is allowed to fill this order
if (order.senderAddress != address(0)) {
require(
order.senderAddress == msg.sender,
- INVALID_SENDER
+ "INVALID_SENDER"
);
}
@@ -306,7 +301,7 @@ contract MixinExchangeCore is
if (order.takerAddress != address(0)) {
require(
order.takerAddress == takerAddress,
- INVALID_TAKER
+ "INVALID_TAKER"
);
}
@@ -318,7 +313,7 @@ contract MixinExchangeCore is
order.makerAddress,
signature
),
- INVALID_ORDER_SIGNATURE
+ "INVALID_ORDER_SIGNATURE"
);
}
@@ -329,7 +324,7 @@ contract MixinExchangeCore is
order.takerAssetAmount,
order.makerAssetAmount
),
- ROUNDING_ERROR
+ "ROUNDING_ERROR"
);
}
@@ -347,14 +342,14 @@ contract MixinExchangeCore is
// An order can only be cancelled if its status is FILLABLE.
require(
orderInfo.orderStatus == uint8(OrderStatus.FILLABLE),
- ORDER_UNFILLABLE
+ "ORDER_UNFILLABLE"
);
// Validate sender is allowed to cancel this order
if (order.senderAddress != address(0)) {
require(
order.senderAddress == msg.sender,
- INVALID_SENDER
+ "INVALID_SENDER"
);
}
@@ -362,7 +357,7 @@ contract MixinExchangeCore is
address makerAddress = getCurrentContextAddress();
require(
order.makerAddress == makerAddress,
- INVALID_MAKER
+ "INVALID_MAKER"
);
}
@@ -412,33 +407,27 @@ contract MixinExchangeCore is
)
private
{
- uint8 makerAssetProxyId = uint8(order.makerAssetData.popLastByte());
- uint8 takerAssetProxyId = uint8(order.takerAssetData.popLastByte());
bytes memory zrxAssetData = ZRX_ASSET_DATA;
dispatchTransferFrom(
order.makerAssetData,
- makerAssetProxyId,
order.makerAddress,
takerAddress,
fillResults.makerAssetFilledAmount
);
dispatchTransferFrom(
order.takerAssetData,
- takerAssetProxyId,
takerAddress,
order.makerAddress,
fillResults.takerAssetFilledAmount
);
dispatchTransferFrom(
zrxAssetData,
- ZRX_PROXY_ID,
order.makerAddress,
order.feeRecipientAddress,
fillResults.makerFeePaid
);
dispatchTransferFrom(
zrxAssetData,
- ZRX_PROXY_ID,
takerAddress,
order.feeRecipientAddress,
fillResults.takerFeePaid
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol
index e36fcc2c5..1a43eec79 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol
@@ -15,11 +15,9 @@ pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "./libs/LibConstants.sol";
-import "../../utils/LibBytes/LibBytes.sol";
import "./libs/LibMath.sol";
import "./libs/LibOrder.sol";
import "./libs/LibFillResults.sol";
-import "./libs/LibExchangeErrors.sol";
import "./mixins/MExchangeCore.sol";
import "./mixins/MMatchOrders.sol";
import "./mixins/MTransactions.sol";
@@ -28,14 +26,11 @@ import "./mixins/MAssetProxyDispatcher.sol";
contract MixinMatchOrders is
LibConstants,
LibMath,
- LibExchangeErrors,
MAssetProxyDispatcher,
MExchangeCore,
MMatchOrders,
MTransactions
{
- using LibBytes for bytes;
-
/// @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.
@@ -144,7 +139,7 @@ contract MixinMatchOrders is
require(
safeMul(leftOrder.makerAssetAmount, rightOrder.makerAssetAmount) >=
safeMul(leftOrder.takerAssetAmount, rightOrder.takerAssetAmount),
- NEGATIVE_SPREAD_REQUIRED
+ "NEGATIVE_SPREAD_REQUIRED"
);
}
@@ -242,27 +237,22 @@ contract MixinMatchOrders is
)
private
{
- uint8 leftMakerAssetProxyId = uint8(leftOrder.makerAssetData.popLastByte());
- uint8 rightMakerAssetProxyId = uint8(rightOrder.makerAssetData.popLastByte());
bytes memory zrxAssetData = ZRX_ASSET_DATA;
// Order makers and taker
dispatchTransferFrom(
leftOrder.makerAssetData,
- leftMakerAssetProxyId,
leftOrder.makerAddress,
rightOrder.makerAddress,
matchedFillResults.right.takerAssetFilledAmount
);
dispatchTransferFrom(
rightOrder.makerAssetData,
- rightMakerAssetProxyId,
rightOrder.makerAddress,
leftOrder.makerAddress,
matchedFillResults.left.takerAssetFilledAmount
);
dispatchTransferFrom(
leftOrder.makerAssetData,
- leftMakerAssetProxyId,
leftOrder.makerAddress,
takerAddress,
matchedFillResults.leftMakerAssetSpreadAmount
@@ -271,14 +261,12 @@ contract MixinMatchOrders is
// Maker fees
dispatchTransferFrom(
zrxAssetData,
- ZRX_PROXY_ID,
leftOrder.makerAddress,
leftOrder.feeRecipientAddress,
matchedFillResults.left.makerFeePaid
);
dispatchTransferFrom(
zrxAssetData,
- ZRX_PROXY_ID,
rightOrder.makerAddress,
rightOrder.feeRecipientAddress,
matchedFillResults.right.makerFeePaid
@@ -288,7 +276,6 @@ contract MixinMatchOrders is
if (leftOrder.feeRecipientAddress == rightOrder.feeRecipientAddress) {
dispatchTransferFrom(
zrxAssetData,
- ZRX_PROXY_ID,
takerAddress,
leftOrder.feeRecipientAddress,
safeAdd(
@@ -299,14 +286,12 @@ contract MixinMatchOrders is
} else {
dispatchTransferFrom(
zrxAssetData,
- ZRX_PROXY_ID,
takerAddress,
leftOrder.feeRecipientAddress,
matchedFillResults.left.takerFeePaid
);
dispatchTransferFrom(
zrxAssetData,
- ZRX_PROXY_ID,
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
index cbb55bfce..29172057a 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol
@@ -19,23 +19,17 @@
pragma solidity ^0.4.24;
import "../../utils/LibBytes/LibBytes.sol";
-import "./libs/LibExchangeErrors.sol";
import "./mixins/MSignatureValidator.sol";
import "./mixins/MTransactions.sol";
import "./interfaces/IWallet.sol";
import "./interfaces/IValidator.sol";
contract MixinSignatureValidator is
- LibExchangeErrors,
MSignatureValidator,
MTransactions
{
using LibBytes for bytes;
- // Personal message headers
- string constant ETH_PERSONAL_MESSAGE = "\x19Ethereum Signed Message:\n32";
- string constant TREZOR_PERSONAL_MESSAGE = "\x19Ethereum Signed Message:\n\x20";
-
// Mapping of hash => signer => signed
mapping (bytes32 => mapping (address => bool)) public preSigned;
@@ -59,7 +53,7 @@ contract MixinSignatureValidator is
signerAddress,
signature
),
- INVALID_SIGNATURE
+ "INVALID_SIGNATURE"
);
preSigned[hash][signerAddress] = true;
}
@@ -99,14 +93,14 @@ contract MixinSignatureValidator is
// TODO: Domain separation: make hash depend on role. (Taker sig should not be valid as maker sig, etc.)
require(
signature.length > 0,
- LENGTH_GREATER_THAN_0_REQUIRED
+ "LENGTH_GREATER_THAN_0_REQUIRED"
);
// Ensure signature is supported
uint8 signatureTypeRaw = uint8(signature.popLastByte());
require(
signatureTypeRaw < uint8(SignatureType.NSignatureTypes),
- SIGNATURE_UNSUPPORTED
+ "SIGNATURE_UNSUPPORTED"
);
// Pop last byte off of signature byte array.
@@ -124,7 +118,7 @@ contract MixinSignatureValidator is
// it an explicit option. This aids testing and analysis. It is
// also the initialization value for the enum type.
if (signatureType == SignatureType.Illegal) {
- revert(SIGNATURE_ILLEGAL);
+ revert("SIGNATURE_ILLEGAL");
// Always invalid signature.
// Like Illegal, this is always implicitly available and therefore
@@ -133,7 +127,7 @@ contract MixinSignatureValidator is
} else if (signatureType == SignatureType.Invalid) {
require(
signature.length == 0,
- LENGTH_0_REQUIRED
+ "LENGTH_0_REQUIRED"
);
isValid = false;
return isValid;
@@ -142,7 +136,7 @@ contract MixinSignatureValidator is
} else if (signatureType == SignatureType.EIP712) {
require(
signature.length == 65,
- LENGTH_65_REQUIRED
+ "LENGTH_65_REQUIRED"
);
v = uint8(signature[0]);
r = signature.readBytes32(1);
@@ -155,13 +149,16 @@ contract MixinSignatureValidator is
} else if (signatureType == SignatureType.EthSign) {
require(
signature.length == 65,
- LENGTH_65_REQUIRED
+ "LENGTH_65_REQUIRED"
);
v = uint8(signature[0]);
r = signature.readBytes32(1);
s = signature.readBytes32(33);
recovered = ecrecover(
- keccak256(abi.encodePacked(ETH_PERSONAL_MESSAGE, hash)),
+ keccak256(abi.encodePacked(
+ "\x19Ethereum Signed Message:\n32",
+ hash
+ )),
v,
r,
s
@@ -180,7 +177,7 @@ contract MixinSignatureValidator is
} else if (signatureType == SignatureType.Caller) {
require(
signature.length == 0,
- LENGTH_0_REQUIRED
+ "LENGTH_0_REQUIRED"
);
isValid = signerAddress == msg.sender;
return isValid;
@@ -230,13 +227,16 @@ contract MixinSignatureValidator is
} else if (signatureType == SignatureType.Trezor) {
require(
signature.length == 65,
- LENGTH_65_REQUIRED
+ "LENGTH_65_REQUIRED"
);
v = uint8(signature[0]);
r = signature.readBytes32(1);
s = signature.readBytes32(33);
recovered = ecrecover(
- keccak256(abi.encodePacked(TREZOR_PERSONAL_MESSAGE, hash)),
+ keccak256(abi.encodePacked(
+ "\x19Ethereum Signed Message:\n\x20",
+ hash
+ )),
v,
r,
s
@@ -250,6 +250,6 @@ contract MixinSignatureValidator is
// that we currently support. In this case returning false
// may lead the caller to incorrectly believe that the
// signature was invalid.)
- revert(SIGNATURE_UNSUPPORTED);
+ revert("SIGNATURE_UNSUPPORTED");
}
}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol
index 20a4a12df..e0f450d0a 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol
@@ -20,12 +20,10 @@ pragma solidity ^0.4.24;
import "./libs/LibExchangeErrors.sol";
import "./mixins/MSignatureValidator.sol";
import "./mixins/MTransactions.sol";
-import "./libs/LibExchangeErrors.sol";
import "./libs/LibEIP712.sol";
contract MixinTransactions is
LibEIP712,
- LibExchangeErrors,
MSignatureValidator,
MTransactions
{
@@ -90,7 +88,7 @@ contract MixinTransactions is
// Prevent reentrancy
require(
currentContextAddress == address(0),
- REENTRANCY_ILLEGAL
+ "REENTRANCY_ILLEGAL"
);
bytes32 transactionHash = hashEIP712Message(hashZeroExTransaction(
@@ -102,7 +100,7 @@ contract MixinTransactions is
// Validate transaction has not been executed
require(
!transactions[transactionHash],
- INVALID_TX_HASH
+ "INVALID_TX_HASH"
);
// Transaction always valid if signer is sender of transaction
@@ -114,7 +112,7 @@ contract MixinTransactions is
signerAddress,
signature
),
- INVALID_TX_SIGNATURE
+ "INVALID_TX_SIGNATURE"
);
// Set the current transaction signer
@@ -125,7 +123,7 @@ contract MixinTransactions is
transactions[transactionHash] = true;
require(
address(this).delegatecall(data),
- FAILED_EXECUTION
+ "FAILED_EXECUTION"
);
// Reset current transaction signer
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol
index 724f95518..00668ca43 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol
@@ -22,13 +22,11 @@ pragma experimental ABIEncoderV2;
import "./libs/LibMath.sol";
import "./libs/LibOrder.sol";
import "./libs/LibFillResults.sol";
-import "./libs/LibExchangeErrors.sol";
import "./mixins/MExchangeCore.sol";
contract MixinWrapperFunctions is
LibMath,
LibFillResults,
- LibExchangeErrors,
MExchangeCore
{
/// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled.
@@ -50,7 +48,7 @@ contract MixinWrapperFunctions is
);
require(
fillResults.takerAssetFilledAmount == takerAssetFillAmount,
- COMPLETE_FILL_FAILED
+ "COMPLETE_FILL_FAILED"
);
return fillResults;
}
@@ -366,14 +364,6 @@ contract MixinWrapperFunctions is
signatures[i]
);
- // HACK: the proxyId is "popped" from the byte array before a fill is settled
- // by subtracting from the length of the array. Since the popped byte is
- // still in memory, we can "unpop" it by incrementing the length of the byte array.
- assembly {
- let len := mload(takerAssetData)
- mstore(takerAssetData, add(len, 1))
- }
-
// Update amounts filled and fees paid by maker and taker
addFillResults(totalFillResults, singleFillResults);
@@ -467,14 +457,6 @@ contract MixinWrapperFunctions is
signatures[i]
);
- // HACK: the proxyId is "popped" from the byte array before a fill is settled
- // by subtracting from the length of the array. Since the popped byte is
- // still in memory, we can "unpop" it by incrementing the length of the byte array.
- assembly {
- let len := mload(makerAssetData)
- mstore(makerAssetData, add(len, 1))
- }
-
// Update amounts filled and fees paid by maker and taker
addFillResults(totalFillResults, singleFillResults);
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol
index 2c331dc34..fa55dff00 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol
@@ -26,7 +26,7 @@ contract IAssetProxyDispatcher {
/// @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,
+ bytes4 assetProxyId,
address newAssetProxy,
address oldAssetProxy
)
@@ -35,7 +35,7 @@ contract IAssetProxyDispatcher {
/// @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)
+ function getAssetProxy(bytes4 assetProxyId)
external
view
returns (address);
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibConstants.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibConstants.sol
index 4a9452448..488ca956c 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibConstants.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibConstants.sol
@@ -25,9 +25,6 @@ contract LibConstants {
// not constant to make testing easier.
bytes public ZRX_ASSET_DATA;
- // Proxy Id for ZRX token.
- uint8 constant ZRX_PROXY_ID = 1;
-
// @TODO: Remove when we deploy.
constructor (bytes memory zrxAssetData)
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
index a43f0f927..e37f41ada 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol
@@ -18,7 +18,10 @@
pragma solidity ^0.4.24;
+/// @dev This contract documents the revert reasons used in the Exchange contract.
+/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons.
contract LibExchangeErrors {
+
/// Order validation errors ///
string constant ORDER_UNFILLABLE = "ORDER_UNFILLABLE"; // Order cannot be filled.
string constant INVALID_MAKER = "INVALID_MAKER"; // Invalid makerAddress.
@@ -56,9 +59,11 @@ contract LibExchangeErrors {
/// dispatchTransferFrom errors ///
string constant ASSET_PROXY_DOES_NOT_EXIST = "ASSET_PROXY_DOES_NOT_EXIST"; // No assetProxy registered at given id.
+ string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Asset transfer unsuccesful.
/// Length validation errors ///
string constant LENGTH_GREATER_THAN_0_REQUIRED = "LENGTH_GREATER_THAN_0_REQUIRED"; // Byte array must have a length greater than 0.
+ string constant LENGTH_GREATER_THAN_3_REQUIRED = "LENGTH_GREATER_THAN_3_REQUIRED"; // Byte array must have a length greater than 3.
string constant LENGTH_0_REQUIRED = "LENGTH_0_REQUIRED"; // Byte array must have a length of 0.
string constant LENGTH_65_REQUIRED = "LENGTH_65_REQUIRED"; // Byte array must have a length of 65.
}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol
index 233547b9f..bfe2fd33f 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol
@@ -23,7 +23,6 @@ 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.
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol
index 788f42c60..c2b506dcf 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol
@@ -27,20 +27,18 @@ contract MAssetProxyDispatcher is
// Logs registration of new asset proxy
event AssetProxySet(
- uint8 id, // Id of new registered AssetProxy.
+ bytes4 id, // Id of new registered AssetProxy.
address newAssetProxy, // Address of new registered AssetProxy.
address oldAssetProxy // Address of AssetProxy that was overwritten at given id (or null address).
);
/// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws.
- /// @param assetData Byte array encoded for the respective asset proxy.
- /// @param assetProxyId Id of assetProxy to dispach to.
+ /// @param assetData Byte array encoded for the asset.
/// @param from Address to transfer token from.
/// @param to Address to transfer token to.
/// @param amount Amount of token to transfer.
function dispatchTransferFrom(
bytes memory assetData,
- uint8 assetProxyId,
address from,
address to,
uint256 amount
diff --git a/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol b/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol
index 5503eb2f2..78ea96447 100644
--- a/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol
+++ b/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol
@@ -56,4 +56,20 @@ contract DummyERC721Token is
);
_mint(to, tokenId);
}
+
+ /**
+ * @dev Function to burn a token
+ * @dev Reverts if the given token ID doesn't exist
+ * @param tokenId uint256 ID of the token to be minted by the msg.sender
+ */
+ function burn(address owner, uint256 tokenId)
+ public
+ onlyOwner
+ {
+ require(
+ exists(tokenId),
+ "Token with tokenId does not exist."
+ );
+ _burn(owner, tokenId);
+ }
}
diff --git a/packages/contracts/src/contracts/current/test/TestAssetDataDecoders/TestAssetDataDecoders.sol b/packages/contracts/src/contracts/current/test/TestAssetDataDecoders/TestAssetDataDecoders.sol
deleted file mode 100644
index 5987291d2..000000000
--- a/packages/contracts/src/contracts/current/test/TestAssetDataDecoders/TestAssetDataDecoders.sol
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
-
- 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/AssetProxy/ERC20Proxy.sol";
-import "../../protocol/AssetProxy/ERC721Proxy.sol";
-
-contract TestAssetDataDecoders is
- ERC721Proxy
-{
- /// @dev Decodes ERC721 Asset data.
- /// @param assetData Encoded byte array.
- /// @return proxyId Intended ERC721 proxy id.
- /// @return token ERC721 token address.
- /// @return tokenId ERC721 token id.
- /// @return receiverData Additional data with no specific format, which
- /// is passed to the receiving contract's onERC721Received.
- function publicDecodeERC721Data(bytes memory assetData)
- public
- pure
- returns (
- address token,
- uint256 tokenId,
- bytes memory receiverData
- )
- {
- (
- token,
- tokenId,
- receiverData
- ) = decodeERC721AssetData(assetData);
-
- return (
- token,
- tokenId,
- receiverData
- );
- }
-}
diff --git a/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol
index d469a07f0..2ae69e0ef 100644
--- a/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol
+++ b/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol
@@ -24,12 +24,11 @@ import "../../protocol/Exchange/MixinAssetProxyDispatcher.sol";
contract TestAssetProxyDispatcher is MixinAssetProxyDispatcher {
function publicDispatchTransferFrom(
bytes memory assetData,
- uint8 assetProxyId,
address from,
address to,
uint256 amount)
public
{
- dispatchTransferFrom(assetData, assetProxyId, from, to, amount);
+ dispatchTransferFrom(assetData, from, to, amount);
}
}
diff --git a/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol b/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol
index d35815474..8b52858b1 100644
--- a/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol
+++ b/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol
@@ -23,13 +23,13 @@ import "../../protocol/Exchange/interfaces/IExchange.sol";
import "../../protocol/Exchange/libs/LibOrder.sol";
import "../../utils/Ownable/Ownable.sol";
-contract Whitelist is
+contract Whitelist is
Ownable
{
// Revert reasons
- string constant MAKER_NOT_WHITELISTED = "Maker address not whitelisted.";
- string constant TAKER_NOT_WHITELISTED = "Taker address not whitelisted.";
- string constant INVALID_SENDER = "Sender must equal transaction origin.";
+ string constant MAKER_NOT_WHITELISTED = "MAKER_NOT_WHITELISTED"; // Maker address not whitelisted.
+ string constant TAKER_NOT_WHITELISTED = "TAKER_NOT_WHITELISTED"; // Taker address not whitelisted.
+ string constant INVALID_SENDER = "INVALID_SENDER"; // Sender must equal transaction origin.
// Mapping of address => whitelist status.
mapping (address => bool) public isWhitelisted;
@@ -77,7 +77,7 @@ contract Whitelist is
public
{
address takerAddress = msg.sender;
-
+
// This contract must be the entry point for the transaction.
require(
takerAddress == tx.origin,
diff --git a/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol b/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol
index f0bcdafef..b6961a6ec 100644
--- a/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol
+++ b/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol
@@ -23,8 +23,8 @@ 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 INSUFFICIENT_BALANCE = "ERC20_INSUFFICIENT_BALANCE";
+ string constant INSUFFICIENT_ALLOWANCE = "ERC20_INSUFFICIENT_ALLOWANCE";
string constant OVERFLOW = "Transfer would result in an overflow.";
mapping (address => uint256) balances;
@@ -97,4 +97,3 @@ contract ERC20Token is IERC20Token {
return allowed[_owner][_spender];
}
}
-
diff --git a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol
index e4cbf318b..78b1ddf7c 100644
--- a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol
+++ b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol
@@ -19,17 +19,8 @@
pragma solidity ^0.4.24;
library LibBytes {
- using LibBytes for bytes;
- // Revert reasons
- string constant GREATER_THAN_ZERO_LENGTH_REQUIRED = "GREATER_THAN_ZERO_LENGTH_REQUIRED";
- string constant GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED";
- string constant GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED";
- string constant GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED";
- string constant GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED";
- string constant GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED";
- string constant FROM_LESS_THAN_TO_REQUIRED = "FROM_LESS_THAN_TO_REQUIRED";
- string constant TO_LESS_THAN_LENGTH_REQUIRED = "TO_LESS_THAN_LENGTH_REQUIRED";
+ using LibBytes for bytes;
/// @dev Gets the memory address for a byte array.
/// @param input Byte array to lookup.
@@ -176,8 +167,14 @@ library LibBytes {
pure
returns (bytes memory result)
{
- require(from <= to, FROM_LESS_THAN_TO_REQUIRED);
- require(to < b.length, TO_LESS_THAN_LENGTH_REQUIRED);
+ require(
+ from <= to,
+ "FROM_LESS_THAN_TO_REQUIRED"
+ );
+ require(
+ to < b.length,
+ "TO_LESS_THAN_LENGTH_REQUIRED"
+ );
// Create a new bytes structure and copy contents
result = new bytes(to - from);
@@ -199,8 +196,14 @@ library LibBytes {
pure
returns (bytes memory result)
{
- require(from <= to, FROM_LESS_THAN_TO_REQUIRED);
- require(to < b.length, TO_LESS_THAN_LENGTH_REQUIRED);
+ require(
+ from <= to,
+ "FROM_LESS_THAN_TO_REQUIRED"
+ );
+ require(
+ to < b.length,
+ "TO_LESS_THAN_LENGTH_REQUIRED"
+ );
// Create a new bytes structure around [from, to) in-place.
assembly {
@@ -220,7 +223,7 @@ library LibBytes {
{
require(
b.length > 0,
- GREATER_THAN_ZERO_LENGTH_REQUIRED
+ "GREATER_THAN_ZERO_LENGTH_REQUIRED"
);
// Store last byte.
@@ -244,7 +247,7 @@ library LibBytes {
{
require(
b.length >= 20,
- GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED
+ "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED"
);
// Store last 20 bytes.
@@ -290,7 +293,7 @@ library LibBytes {
{
require(
b.length >= index + 20, // 20 is length of address
- GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED
+ "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED"
);
// Add offset to index:
@@ -322,7 +325,7 @@ library LibBytes {
{
require(
b.length >= index + 20, // 20 is length of address
- GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED
+ "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED"
);
// Add offset to index:
@@ -365,7 +368,7 @@ library LibBytes {
{
require(
b.length >= index + 32,
- GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED
+ "GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED"
);
// Arrays are prefixed by a 256 bit length parameter
@@ -392,7 +395,7 @@ library LibBytes {
{
require(
b.length >= index + 32,
- GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED
+ "GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED"
);
// Arrays are prefixed by a 256 bit length parameter
@@ -447,7 +450,7 @@ library LibBytes {
{
require(
b.length >= index + 4,
- GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED
+ "GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED"
);
assembly {
result := mload(add(b, 32))
@@ -459,6 +462,8 @@ library LibBytes {
}
/// @dev Reads nested bytes from a specific position.
+ /// @dev NOTE: the returned value overlaps with the input value.
+ /// Both should be treated as immutable.
/// @param b Byte array containing nested bytes.
/// @param index Index of nested bytes.
/// @return result Nested bytes.
@@ -478,17 +483,13 @@ library LibBytes {
// length of nested bytes
require(
b.length >= index + nestedBytesLength,
- GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED
+ "GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED"
);
-
- // Allocate memory and copy value to result
- result = new bytes(nestedBytesLength);
- memCopy(
- result.contentAddress(),
- b.contentAddress() + index,
- nestedBytesLength
- );
-
+
+ // Return a pointer to the byte array as it exists inside `b`
+ assembly {
+ result := add(b, index)
+ }
return result;
}
@@ -508,7 +509,7 @@ library LibBytes {
// length of input
require(
b.length >= index + 32 /* 32 bytes to store length */ + input.length,
- GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED
+ "GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED"
);
// Copy <input> into <b>
@@ -533,7 +534,7 @@ library LibBytes {
// Dest length must be >= source length, or some bytes would not be copied.
require(
dest.length >= sourceLen,
- GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED
+ "GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED"
);
memCopy(
dest.contentAddress(),
diff --git a/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol b/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol
index 99b93d0d2..6f5761cc7 100644
--- a/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol
+++ b/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol
@@ -13,9 +13,6 @@ import "./IOwnable.sol";
contract Ownable is IOwnable {
address public owner;
- // Revert reasons
- string constant ONLY_CONTRACT_OWNER = "ONLY_CONTRACT_OWNER";
-
constructor ()
public
{
@@ -25,7 +22,7 @@ contract Ownable is IOwnable {
modifier onlyOwner() {
require(
msg.sender == owner,
- ONLY_CONTRACT_OWNER
+ "ONLY_CONTRACT_OWNER"
);
_;
}
diff --git a/packages/contracts/src/utils/artifacts.ts b/packages/contracts/src/utils/artifacts.ts
index a46bab9ae..d58e7973c 100644
--- a/packages/contracts/src/utils/artifacts.ts
+++ b/packages/contracts/src/utils/artifacts.ts
@@ -11,7 +11,6 @@ import * as ExchangeWrapper from '../artifacts/ExchangeWrapper.json';
import * as MixinAuthorizable from '../artifacts/MixinAuthorizable.json';
import * as MultiSigWallet from '../artifacts/MultiSigWallet.json';
import * as MultiSigWalletWithTimeLock from '../artifacts/MultiSigWalletWithTimeLock.json';
-import * as TestAssetDataDecoders from '../artifacts/TestAssetDataDecoders.json';
import * as TestAssetProxyDispatcher from '../artifacts/TestAssetProxyDispatcher.json';
import * as TestAssetProxyOwner from '../artifacts/TestAssetProxyOwner.json';
import * as TestLibBytes from '../artifacts/TestLibBytes.json';
@@ -39,7 +38,6 @@ export const artifacts = {
MultiSigWalletWithTimeLock: (MultiSigWalletWithTimeLock as any) as ContractArtifact,
TestAssetProxyOwner: (TestAssetProxyOwner as any) as ContractArtifact,
TestAssetProxyDispatcher: (TestAssetProxyDispatcher as any) as ContractArtifact,
- TestAssetDataDecoders: (TestAssetDataDecoders as any) as ContractArtifact,
TestLibBytes: (TestLibBytes as any) as ContractArtifact,
TestLibs: (TestLibs as any) as ContractArtifact,
TestSignatureValidator: (TestSignatureValidator as any) as ContractArtifact,
diff --git a/packages/contracts/src/utils/assertions.ts b/packages/contracts/src/utils/assertions.ts
index 615e648f3..e702a3200 100644
--- a/packages/contracts/src/utils/assertions.ts
+++ b/packages/contracts/src/utils/assertions.ts
@@ -1,3 +1,4 @@
+import { RevertReason } from '@0xproject/types';
import * as chai from 'chai';
import * as _ from 'lodash';
@@ -52,6 +53,21 @@ export function expectRevertOrAlwaysFailingTransactionAsync<T>(p: Promise<T>): P
}
/**
+ * Rejects if the given Promise does not reject with the given revert reason or "always
+ * failing transaction" error.
+ * @param p the Promise which is expected to reject
+ * @param reason a specific revert reason
+ * @returns a new Promise which will reject if the conditions are not met and
+ * otherwise resolve with no value.
+ */
+export function expectRevertReasonOrAlwaysFailingTransactionAsync<T>(
+ p: Promise<T>,
+ reason: RevertReason,
+): PromiseLike<void> {
+ return _expectEitherErrorAsync(p, 'always failing transaction', reason);
+}
+
+/**
* Rejects if the given Promise does not reject with a "revert" or "Contract
* call failed" error.
* @param p the Promise which is expected to reject
diff --git a/packages/contracts/src/utils/asset_wrapper.ts b/packages/contracts/src/utils/asset_wrapper.ts
new file mode 100644
index 000000000..a7f91f413
--- /dev/null
+++ b/packages/contracts/src/utils/asset_wrapper.ts
@@ -0,0 +1,217 @@
+import { assetProxyUtils } from '@0xproject/order-utils';
+import { AssetProxyId } from '@0xproject/types';
+import { BigNumber, errorUtils } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { AbstractAssetWrapper } from '../abstract/abstract_asset_wrapper';
+
+import { constants } from './constants';
+import { ERC20Wrapper } from './erc20_wrapper';
+import { ERC721Wrapper } from './erc721_wrapper';
+
+interface ProxyIdToAssetWrappers {
+ [proxyId: string]: AbstractAssetWrapper;
+}
+
+/**
+ * This class abstracts away the differences between ERC20 and ERC721 tokens so that
+ * the logic that uses it does not need to care what standard a token belongs to.
+ */
+export class AssetWrapper {
+ private _proxyIdToAssetWrappers: ProxyIdToAssetWrappers;
+ constructor(assetWrappers: AbstractAssetWrapper[]) {
+ this._proxyIdToAssetWrappers = {};
+ _.each(assetWrappers, assetWrapper => {
+ const proxyId = assetWrapper.getProxyId();
+ this._proxyIdToAssetWrappers[proxyId] = assetWrapper;
+ });
+ }
+ public async getBalanceAsync(userAddress: string, assetData: string): Promise<BigNumber> {
+ const proxyId = assetProxyUtils.decodeAssetDataId(assetData);
+ switch (proxyId) {
+ case AssetProxyId.ERC20: {
+ const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper;
+ const balance = await erc20Wrapper.getBalanceAsync(userAddress, assetData);
+ return balance;
+ }
+ case AssetProxyId.ERC721: {
+ const assetWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper;
+ const assetProxyData = assetProxyUtils.decodeERC721AssetData(assetData);
+ const isOwner = await assetWrapper.isOwnerAsync(
+ userAddress,
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ );
+ const balance = isOwner ? new BigNumber(1) : new BigNumber(0);
+ return balance;
+ }
+ default:
+ throw errorUtils.spawnSwitchErr('proxyId', proxyId);
+ }
+ }
+ public async setBalanceAsync(userAddress: string, assetData: string, desiredBalance: BigNumber): Promise<void> {
+ const proxyId = assetProxyUtils.decodeAssetDataId(assetData);
+ switch (proxyId) {
+ case AssetProxyId.ERC20: {
+ const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper;
+ await erc20Wrapper.setBalanceAsync(userAddress, assetData, desiredBalance);
+ return;
+ }
+ case AssetProxyId.ERC721: {
+ if (!desiredBalance.eq(0) && !desiredBalance.eq(1)) {
+ throw new Error(`Balance for ERC721 token can only be set to 0 or 1. Got: ${desiredBalance}`);
+ }
+ const erc721Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper;
+ const assetProxyData = assetProxyUtils.decodeERC721AssetData(assetData);
+ const doesTokenExist = erc721Wrapper.doesTokenExistAsync(
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ );
+ if (!doesTokenExist && desiredBalance.eq(1)) {
+ await erc721Wrapper.mintAsync(assetProxyData.tokenAddress, assetProxyData.tokenId, userAddress);
+ return;
+ } else if (!doesTokenExist && desiredBalance.eq(0)) {
+ return; // noop
+ }
+ const tokenOwner = await erc721Wrapper.ownerOfAsync(
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ );
+ if (userAddress !== tokenOwner && desiredBalance.eq(1)) {
+ await erc721Wrapper.transferFromAsync(
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ tokenOwner,
+ userAddress,
+ );
+ } else if (tokenOwner === userAddress && desiredBalance.eq(0)) {
+ // Burn token
+ await erc721Wrapper.burnAsync(assetProxyData.tokenAddress, assetProxyData.tokenId, userAddress);
+ return;
+ } else if (
+ (userAddress !== tokenOwner && desiredBalance.eq(0)) ||
+ (tokenOwner === userAddress && desiredBalance.eq(1))
+ ) {
+ return; // noop
+ }
+ break;
+ }
+ default:
+ throw errorUtils.spawnSwitchErr('proxyId', proxyId);
+ }
+ }
+ public async getProxyAllowanceAsync(userAddress: string, assetData: string): Promise<BigNumber> {
+ const proxyId = assetProxyUtils.decodeAssetDataId(assetData);
+ switch (proxyId) {
+ case AssetProxyId.ERC20: {
+ const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper;
+ const allowance = await erc20Wrapper.getProxyAllowanceAsync(userAddress, assetData);
+ return allowance;
+ }
+ case AssetProxyId.ERC721: {
+ const assetWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper;
+ const erc721ProxyData = assetProxyUtils.decodeERC721AssetData(assetData);
+ const isProxyApprovedForAll = await assetWrapper.isProxyApprovedForAllAsync(
+ userAddress,
+ erc721ProxyData.tokenAddress,
+ );
+ if (isProxyApprovedForAll) {
+ return constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
+ }
+
+ const isProxyApproved = await assetWrapper.isProxyApprovedAsync(
+ erc721ProxyData.tokenAddress,
+ erc721ProxyData.tokenId,
+ );
+ const allowance = isProxyApproved ? new BigNumber(1) : new BigNumber(0);
+ return allowance;
+ }
+ default:
+ throw errorUtils.spawnSwitchErr('proxyId', proxyId);
+ }
+ }
+ public async setProxyAllowanceAsync(
+ userAddress: string,
+ assetData: string,
+ desiredAllowance: BigNumber,
+ ): Promise<void> {
+ const proxyId = assetProxyUtils.decodeAssetDataId(assetData);
+ switch (proxyId) {
+ case AssetProxyId.ERC20: {
+ const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper;
+ await erc20Wrapper.setAllowanceAsync(userAddress, assetData, desiredAllowance);
+ return;
+ }
+ case AssetProxyId.ERC721: {
+ if (
+ !desiredAllowance.eq(0) &&
+ !desiredAllowance.eq(1) &&
+ !desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)
+ ) {
+ throw new Error(
+ `Allowance for ERC721 token can only be set to 0, 1 or 2^256-1. Got: ${desiredAllowance}`,
+ );
+ }
+ const erc721Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper;
+ const assetProxyData = assetProxyUtils.decodeERC721AssetData(assetData);
+
+ const doesTokenExist = await erc721Wrapper.doesTokenExistAsync(
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ );
+ if (!doesTokenExist) {
+ throw new Error(
+ `Cannot setProxyAllowance on non-existent token: ${assetProxyData.tokenAddress} ${
+ assetProxyData.tokenId
+ }`,
+ );
+ }
+ const isProxyApprovedForAll = await erc721Wrapper.isProxyApprovedForAllAsync(
+ userAddress,
+ assetProxyData.tokenAddress,
+ );
+ if (!isProxyApprovedForAll && desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
+ const isApproved = true;
+ await erc721Wrapper.approveProxyForAllAsync(
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ isApproved,
+ );
+ } else if (isProxyApprovedForAll && desiredAllowance.eq(0)) {
+ const isApproved = false;
+ await erc721Wrapper.approveProxyForAllAsync(
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ isApproved,
+ );
+ } else if (isProxyApprovedForAll && desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
+ return; // Noop
+ }
+
+ const isProxyApproved = await erc721Wrapper.isProxyApprovedAsync(
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ );
+ if (!isProxyApproved && desiredAllowance.eq(1)) {
+ await erc721Wrapper.approveProxyAsync(assetProxyData.tokenAddress, assetProxyData.tokenId);
+ } else if (isProxyApproved && desiredAllowance.eq(0)) {
+ // Remove approval
+ await erc721Wrapper.approveAsync(
+ constants.NULL_ADDRESS,
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ );
+ } else if (
+ (!isProxyApproved && desiredAllowance.eq(0)) ||
+ (isProxyApproved && desiredAllowance.eq(1))
+ ) {
+ return; // noop
+ }
+
+ break;
+ }
+ default:
+ throw errorUtils.spawnSwitchErr('proxyId', proxyId);
+ }
+ }
+}
diff --git a/packages/contracts/src/utils/constants.ts b/packages/contracts/src/utils/constants.ts
index f21b8c7a0..2b2bd2425 100644
--- a/packages/contracts/src/utils/constants.ts
+++ b/packages/contracts/src/utils/constants.ts
@@ -19,19 +19,6 @@ const TESTRPC_PRIVATE_KEYS_STRINGS = [
export const constants = {
INVALID_OPCODE: 'invalid opcode',
REVERT: 'revert',
- LIB_BYTES_GREATER_THAN_ZERO_LENGTH_REQUIRED: 'GREATER_THAN_ZERO_LENGTH_REQUIRED',
- LIB_BYTES_GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED',
- LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED',
- LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED',
- LIB_BYTES_GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED',
- LIB_BYTES_GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED',
- ERC20_INSUFFICIENT_BALANCE: 'Insufficient balance to complete transfer.',
- ERC20_INSUFFICIENT_ALLOWANCE: 'Insufficient allowance to complete transfer.',
- EXCHANGE_LENGTH_GREATER_THAN_0_REQUIRED: 'LENGTH_GREATER_THAN_0_REQUIRED',
- EXCHANGE_SIGNATURE_UNSUPPORTED: 'SIGNATURE_UNSUPPORTED',
- EXCHANGE_SIGNATURE_ILLEGAL: 'SIGNATURE_ILLEGAL',
- EXCHANGE_LENGTH_0_REQUIRED: 'LENGTH_0_REQUIRED',
- EXCHANGE_LENGTH_65_REQUIRED: 'LENGTH_65_REQUIRED',
TESTRPC_NETWORK_ID: 50,
// Note(albrow): In practice V8 and most other engines limit the minimum
// interval for setInterval to 10ms. We still set it to 0 here in order to
diff --git a/packages/contracts/src/utils/core_combinatorial_utils.ts b/packages/contracts/src/utils/core_combinatorial_utils.ts
new file mode 100644
index 000000000..5b725fbe3
--- /dev/null
+++ b/packages/contracts/src/utils/core_combinatorial_utils.ts
@@ -0,0 +1,801 @@
+import {
+ assetProxyUtils,
+ BalanceAndProxyAllowanceLazyStore,
+ ExchangeTransferSimulator,
+ orderHashUtils,
+ OrderStateUtils,
+ OrderValidationUtils,
+} from '@0xproject/order-utils';
+import { AssetProxyId, RevertReason, SignatureType, SignedOrder } from '@0xproject/types';
+import { BigNumber, errorUtils, logUtils } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import * as chai from 'chai';
+import { LogWithDecodedArgs, Provider, TxData } from 'ethereum-types';
+import * as _ from 'lodash';
+import 'make-promises-safe';
+
+import { ExchangeContract, FillContractEventArgs } from '../generated_contract_wrappers/exchange';
+import { artifacts } from '../utils/artifacts';
+import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../utils/assertions';
+import { AssetWrapper } from '../utils/asset_wrapper';
+import { chaiSetup } from '../utils/chai_setup';
+import { constants } from '../utils/constants';
+import { ERC20Wrapper } from '../utils/erc20_wrapper';
+import { ERC721Wrapper } from '../utils/erc721_wrapper';
+import { ExchangeWrapper } from '../utils/exchange_wrapper';
+import { OrderFactoryFromScenario } from '../utils/order_factory_from_scenario';
+import { orderUtils } from '../utils/order_utils';
+import { signingUtils } from '../utils/signing_utils';
+import { SimpleAssetBalanceAndProxyAllowanceFetcher } from '../utils/simple_asset_balance_and_proxy_allowance_fetcher';
+import { SimpleOrderFilledCancelledFetcher } from '../utils/simple_order_filled_cancelled_fetcher';
+import {
+ AllowanceAmountScenario,
+ AssetDataScenario,
+ BalanceAmountScenario,
+ ExpirationTimeSecondsScenario,
+ FeeRecipientAddressScenario,
+ FillScenario,
+ OrderAssetAmountScenario,
+ TakerAssetFillAmountScenario,
+ TakerScenario,
+ TraderStateScenario,
+} from '../utils/types';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+/**
+ * Instantiates a new instance of CoreCombinatorialUtils. Since this method has some
+ * required async setup, a factory method is required.
+ * @param web3Wrapper Web3Wrapper instance
+ * @param txDefaults Default Ethereum tx options
+ * @return CoreCombinatorialUtils instance
+ */
+export async function coreCombinatorialUtilsFactoryAsync(
+ web3Wrapper: Web3Wrapper,
+ txDefaults: Partial<TxData>,
+): Promise<CoreCombinatorialUtils> {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ const userAddresses = _.slice(accounts, 0, 5);
+ const [ownerAddress, makerAddress, takerAddress] = userAddresses;
+ const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
+
+ const provider = web3Wrapper.getProvider();
+ const erc20Wrapper = new ERC20Wrapper(provider, userAddresses, ownerAddress);
+ const erc721Wrapper = new ERC721Wrapper(provider, userAddresses, ownerAddress);
+
+ const erc20EighteenDecimalTokenCount = 3;
+ const eighteenDecimals = new BigNumber(18);
+ const [
+ erc20EighteenDecimalTokenA,
+ erc20EighteenDecimalTokenB,
+ zrxToken,
+ ] = await erc20Wrapper.deployDummyTokensAsync(erc20EighteenDecimalTokenCount, eighteenDecimals);
+ const zrxAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
+
+ const erc20FiveDecimalTokenCount = 2;
+ const fiveDecimals = new BigNumber(18);
+ const [erc20FiveDecimalTokenA, erc20FiveDecimalTokenB] = await erc20Wrapper.deployDummyTokensAsync(
+ erc20FiveDecimalTokenCount,
+ fiveDecimals,
+ );
+ const erc20Proxy = await erc20Wrapper.deployProxyAsync();
+ await erc20Wrapper.setBalancesAndAllowancesAsync();
+
+ const [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
+ const erc721Proxy = await erc721Wrapper.deployProxyAsync();
+ await erc721Wrapper.setBalancesAndAllowancesAsync();
+ const erc721Balances = await erc721Wrapper.getBalancesAsync();
+
+ const assetWrapper = new AssetWrapper([erc20Wrapper, erc721Wrapper]);
+
+ const exchangeContract = await ExchangeContract.deployFrom0xArtifactAsync(
+ artifacts.Exchange,
+ provider,
+ txDefaults,
+ zrxAssetData,
+ );
+ const exchangeWrapper = new ExchangeWrapper(exchangeContract, provider);
+ await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, ownerAddress);
+ await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC721, erc721Proxy.address, ownerAddress);
+
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, {
+ from: ownerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, {
+ from: ownerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const orderFactory = new OrderFactoryFromScenario(
+ userAddresses,
+ zrxToken.address,
+ [erc20EighteenDecimalTokenA.address, erc20EighteenDecimalTokenB.address],
+ [erc20FiveDecimalTokenA.address, erc20FiveDecimalTokenB.address],
+ erc721Token,
+ erc721Balances,
+ exchangeContract.address,
+ );
+
+ const coreCombinatorialUtils = new CoreCombinatorialUtils(
+ orderFactory,
+ ownerAddress,
+ makerAddress,
+ makerPrivateKey,
+ takerAddress,
+ zrxAssetData,
+ exchangeWrapper,
+ assetWrapper,
+ );
+ return coreCombinatorialUtils;
+}
+
+export class CoreCombinatorialUtils {
+ public orderFactory: OrderFactoryFromScenario;
+ public ownerAddress: string;
+ public makerAddress: string;
+ public makerPrivateKey: Buffer;
+ public takerAddress: string;
+ public zrxAssetData: string;
+ public exchangeWrapper: ExchangeWrapper;
+ public assetWrapper: AssetWrapper;
+ public static generateFillOrderCombinations(): FillScenario[] {
+ const takerScenarios = [TakerScenario.Unspecified];
+ const feeRecipientScenarios = [FeeRecipientAddressScenario.EthUserAddress];
+ const makerAssetAmountScenario = [OrderAssetAmountScenario.Large];
+ const takerAssetAmountScenario = [OrderAssetAmountScenario.Large];
+ const makerFeeScenario = [OrderAssetAmountScenario.Large];
+ const takerFeeScenario = [OrderAssetAmountScenario.Large];
+ const expirationTimeSecondsScenario = [ExpirationTimeSecondsScenario.InFuture];
+ const makerAssetDataScenario = [
+ AssetDataScenario.ERC20FiveDecimals,
+ AssetDataScenario.ERC20NonZRXEighteenDecimals,
+ AssetDataScenario.ERC721,
+ AssetDataScenario.ZRXFeeToken,
+ ];
+ const takerAssetDataScenario = [
+ AssetDataScenario.ERC20FiveDecimals,
+ AssetDataScenario.ERC20NonZRXEighteenDecimals,
+ AssetDataScenario.ERC721,
+ AssetDataScenario.ZRXFeeToken,
+ ];
+ const takerAssetFillAmountScenario = [TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount];
+ const fillScenarioArrays = CoreCombinatorialUtils._getAllCombinations([
+ takerScenarios,
+ feeRecipientScenarios,
+ makerAssetAmountScenario,
+ takerAssetAmountScenario,
+ makerFeeScenario,
+ takerFeeScenario,
+ expirationTimeSecondsScenario,
+ makerAssetDataScenario,
+ takerAssetDataScenario,
+ takerAssetFillAmountScenario,
+ ]);
+
+ const fillScenarios = _.map(fillScenarioArrays, fillScenarioArray => {
+ const fillScenario: FillScenario = {
+ orderScenario: {
+ takerScenario: fillScenarioArray[0] as TakerScenario,
+ feeRecipientScenario: fillScenarioArray[1] as FeeRecipientAddressScenario,
+ makerAssetAmountScenario: fillScenarioArray[2] as OrderAssetAmountScenario,
+ takerAssetAmountScenario: fillScenarioArray[3] as OrderAssetAmountScenario,
+ makerFeeScenario: fillScenarioArray[4] as OrderAssetAmountScenario,
+ takerFeeScenario: fillScenarioArray[5] as OrderAssetAmountScenario,
+ expirationTimeSecondsScenario: fillScenarioArray[6] as ExpirationTimeSecondsScenario,
+ makerAssetDataScenario: fillScenarioArray[7] as AssetDataScenario,
+ takerAssetDataScenario: fillScenarioArray[8] as AssetDataScenario,
+ },
+ takerAssetFillAmountScenario: fillScenarioArray[9] as TakerAssetFillAmountScenario,
+ makerStateScenario: {
+ traderAssetBalance: BalanceAmountScenario.Higher,
+ traderAssetAllowance: AllowanceAmountScenario.Higher,
+ zrxFeeBalance: BalanceAmountScenario.Higher,
+ zrxFeeAllowance: AllowanceAmountScenario.Higher,
+ },
+ takerStateScenario: {
+ traderAssetBalance: BalanceAmountScenario.Higher,
+ traderAssetAllowance: AllowanceAmountScenario.Higher,
+ zrxFeeBalance: BalanceAmountScenario.Higher,
+ zrxFeeAllowance: AllowanceAmountScenario.Higher,
+ },
+ };
+ return fillScenario;
+ });
+
+ return fillScenarios;
+ }
+ /**
+ * Recursive implementation of generating all combinations of the supplied
+ * string-containing arrays.
+ */
+ private static _getAllCombinations(arrays: string[][]): string[][] {
+ // Base case
+ if (arrays.length === 1) {
+ const remainingValues = _.map(arrays[0], val => {
+ return [val];
+ });
+ return remainingValues;
+ } else {
+ const result = [];
+ const restOfArrays = arrays.slice(1);
+ const allCombinationsOfRemaining = CoreCombinatorialUtils._getAllCombinations(restOfArrays); // recur with the rest of array
+ // tslint:disable:prefer-for-of
+ for (let i = 0; i < allCombinationsOfRemaining.length; i++) {
+ for (let j = 0; j < arrays[0].length; j++) {
+ result.push([arrays[0][j], ...allCombinationsOfRemaining[i]]);
+ }
+ }
+ // tslint:enable:prefer-for-of
+ return result;
+ }
+ }
+ constructor(
+ orderFactory: OrderFactoryFromScenario,
+ ownerAddress: string,
+ makerAddress: string,
+ makerPrivateKey: Buffer,
+ takerAddress: string,
+ zrxAssetData: string,
+ exchangeWrapper: ExchangeWrapper,
+ assetWrapper: AssetWrapper,
+ ) {
+ this.orderFactory = orderFactory;
+ this.ownerAddress = ownerAddress;
+ this.makerAddress = makerAddress;
+ this.makerPrivateKey = makerPrivateKey;
+ this.takerAddress = takerAddress;
+ this.zrxAssetData = zrxAssetData;
+ this.exchangeWrapper = exchangeWrapper;
+ this.assetWrapper = assetWrapper;
+ }
+ public async testFillOrderScenarioAsync(
+ provider: Provider,
+ fillScenario: FillScenario,
+ isVerbose: boolean = false,
+ ): Promise<void> {
+ // 1. Generate order
+ const order = this.orderFactory.generateOrder(fillScenario.orderScenario);
+
+ // 2. Sign order
+ const orderHashBuff = orderHashUtils.getOrderHashBuffer(order);
+ const signature = signingUtils.signMessage(orderHashBuff, this.makerPrivateKey, SignatureType.EthSign);
+ const signedOrder = {
+ ...order,
+ signature: `0x${signature.toString('hex')}`,
+ };
+
+ const balanceAndProxyAllowanceFetcher = new SimpleAssetBalanceAndProxyAllowanceFetcher(this.assetWrapper);
+ const orderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher(
+ this.exchangeWrapper,
+ this.zrxAssetData,
+ );
+
+ // 3. Figure out fill amount
+ const takerAssetFillAmount = await this._getTakerAssetFillAmountAsync(
+ signedOrder,
+ fillScenario.takerAssetFillAmountScenario,
+ balanceAndProxyAllowanceFetcher,
+ orderFilledCancelledFetcher,
+ );
+
+ // 4. Permutate the maker and taker balance/allowance scenarios
+ await this._modifyTraderStateAsync(
+ fillScenario.makerStateScenario,
+ fillScenario.takerStateScenario,
+ signedOrder,
+ takerAssetFillAmount,
+ );
+
+ // 5. If I fill it by X, what are the resulting balances/allowances/filled amounts expected?
+ const orderValidationUtils = new OrderValidationUtils(orderFilledCancelledFetcher);
+ const lazyStore = new BalanceAndProxyAllowanceLazyStore(balanceAndProxyAllowanceFetcher);
+ const exchangeTransferSimulator = new ExchangeTransferSimulator(lazyStore);
+
+ let fillRevertReasonIfExists;
+ try {
+ await orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
+ exchangeTransferSimulator,
+ provider,
+ signedOrder,
+ takerAssetFillAmount,
+ this.takerAddress,
+ this.zrxAssetData,
+ );
+ if (isVerbose) {
+ logUtils.log(`Expecting fillOrder to succeed.`);
+ }
+ } catch (err) {
+ fillRevertReasonIfExists = err.message;
+ if (isVerbose) {
+ logUtils.log(`Expecting fillOrder to fail with:`);
+ logUtils.log(err);
+ }
+ }
+
+ // 6. Fill the order
+ await this._fillOrderAndAssertOutcomeAsync(
+ signedOrder,
+ takerAssetFillAmount,
+ lazyStore,
+ fillRevertReasonIfExists,
+ );
+ }
+ private async _fillOrderAndAssertOutcomeAsync(
+ signedOrder: SignedOrder,
+ takerAssetFillAmount: BigNumber,
+ lazyStore: BalanceAndProxyAllowanceLazyStore,
+ fillRevertReasonIfExists: RevertReason | undefined,
+ ): Promise<void> {
+ if (!_.isUndefined(fillRevertReasonIfExists)) {
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
+ this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, { takerAssetFillAmount }),
+ fillRevertReasonIfExists,
+ );
+ }
+
+ const makerAddress = signedOrder.makerAddress;
+ const makerAssetData = signedOrder.makerAssetData;
+ const takerAssetData = signedOrder.takerAssetData;
+ const feeRecipient = signedOrder.feeRecipientAddress;
+
+ const expMakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(makerAssetData, makerAddress);
+ const expMakerAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(makerAssetData, makerAddress);
+ const expTakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(takerAssetData, makerAddress);
+ const expZRXAssetBalanceOfMaker = await lazyStore.getBalanceAsync(this.zrxAssetData, makerAddress);
+ const expZRXAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(this.zrxAssetData, makerAddress);
+ const expTakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(takerAssetData, this.takerAddress);
+ const expTakerAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync(takerAssetData, this.takerAddress);
+ const expMakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(makerAssetData, this.takerAddress);
+ const expZRXAssetBalanceOfTaker = await lazyStore.getBalanceAsync(this.zrxAssetData, this.takerAddress);
+ const expZRXAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync(
+ this.zrxAssetData,
+ this.takerAddress,
+ );
+ const expZRXAssetBalanceOfFeeRecipient = await lazyStore.getBalanceAsync(this.zrxAssetData, feeRecipient);
+
+ const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
+ const alreadyFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash);
+ const remainingTakerAmountToFill = signedOrder.takerAssetAmount.minus(alreadyFilledTakerAmount);
+ const expFilledTakerAmount = takerAssetFillAmount.gt(remainingTakerAmountToFill)
+ ? remainingTakerAmountToFill
+ : alreadyFilledTakerAmount.add(takerAssetFillAmount);
+
+ const expFilledMakerAmount = orderUtils.getPartialAmount(
+ expFilledTakerAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerAssetAmount,
+ );
+
+ // - Let's fill the order!
+ const txReceipt = await this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, {
+ takerAssetFillAmount,
+ });
+
+ const actFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash);
+ expect(actFilledTakerAmount).to.be.bignumber.equal(expFilledTakerAmount, 'filledTakerAmount');
+
+ expect(txReceipt.logs.length).to.be.equal(1, 'logs length');
+ // tslint:disable-next-line:no-unnecessary-type-assertion
+ const log = txReceipt.logs[0] as LogWithDecodedArgs<FillContractEventArgs>;
+ expect(log.args.makerAddress).to.be.equal(makerAddress, 'log.args.makerAddress');
+ expect(log.args.takerAddress).to.be.equal(this.takerAddress, 'log.args.this.takerAddress');
+ expect(log.args.feeRecipientAddress).to.be.equal(feeRecipient, 'log.args.feeRecipientAddress');
+ expect(log.args.makerAssetFilledAmount).to.be.bignumber.equal(
+ expFilledMakerAmount,
+ 'log.args.makerAssetFilledAmount',
+ );
+ expect(log.args.takerAssetFilledAmount).to.be.bignumber.equal(
+ expFilledTakerAmount,
+ 'log.args.takerAssetFilledAmount',
+ );
+ const expMakerFeePaid = orderUtils.getPartialAmount(
+ expFilledTakerAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerFee,
+ );
+ expect(log.args.makerFeePaid).to.be.bignumber.equal(expMakerFeePaid, 'log.args.makerFeePaid');
+ const expTakerFeePaid = orderUtils.getPartialAmount(
+ expFilledTakerAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.takerFee,
+ );
+ expect(log.args.takerFeePaid).to.be.bignumber.equal(expTakerFeePaid, 'logs.args.takerFeePaid');
+ expect(log.args.orderHash).to.be.equal(orderHash, 'log.args.orderHash');
+ expect(log.args.makerAssetData).to.be.equal(makerAssetData, 'log.args.makerAssetData');
+ expect(log.args.takerAssetData).to.be.equal(takerAssetData, 'log.args.takerAssetData');
+
+ const actMakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, makerAssetData);
+ expect(actMakerAssetBalanceOfMaker).to.be.bignumber.equal(
+ expMakerAssetBalanceOfMaker,
+ 'makerAssetBalanceOfMaker',
+ );
+
+ const actMakerAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync(
+ makerAddress,
+ makerAssetData,
+ );
+ expect(actMakerAssetAllowanceOfMaker).to.be.bignumber.equal(
+ expMakerAssetAllowanceOfMaker,
+ 'makerAssetAllowanceOfMaker',
+ );
+
+ const actTakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, takerAssetData);
+ expect(actTakerAssetBalanceOfMaker).to.be.bignumber.equal(
+ expTakerAssetBalanceOfMaker,
+ 'takerAssetBalanceOfMaker',
+ );
+
+ const actZRXAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, this.zrxAssetData);
+ expect(actZRXAssetBalanceOfMaker).to.be.bignumber.equal(expZRXAssetBalanceOfMaker, 'ZRXAssetBalanceOfMaker');
+
+ const actZRXAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync(
+ makerAddress,
+ this.zrxAssetData,
+ );
+ expect(actZRXAssetAllowanceOfMaker).to.be.bignumber.equal(
+ expZRXAssetAllowanceOfMaker,
+ 'ZRXAssetAllowanceOfMaker',
+ );
+
+ const actTakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, takerAssetData);
+ expect(actTakerAssetBalanceOfTaker).to.be.bignumber.equal(
+ expTakerAssetBalanceOfTaker,
+ 'TakerAssetBalanceOfTaker',
+ );
+
+ const actTakerAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync(
+ this.takerAddress,
+ takerAssetData,
+ );
+
+ expect(actTakerAssetAllowanceOfTaker).to.be.bignumber.equal(
+ expTakerAssetAllowanceOfTaker,
+ 'TakerAssetAllowanceOfTaker',
+ );
+
+ const actMakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, makerAssetData);
+ expect(actMakerAssetBalanceOfTaker).to.be.bignumber.equal(
+ expMakerAssetBalanceOfTaker,
+ 'MakerAssetBalanceOfTaker',
+ );
+
+ const actZRXAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, this.zrxAssetData);
+ expect(actZRXAssetBalanceOfTaker).to.be.bignumber.equal(expZRXAssetBalanceOfTaker, 'ZRXAssetBalanceOfTaker');
+
+ const actZRXAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync(
+ this.takerAddress,
+ this.zrxAssetData,
+ );
+ expect(actZRXAssetAllowanceOfTaker).to.be.bignumber.equal(
+ expZRXAssetAllowanceOfTaker,
+ 'ZRXAssetAllowanceOfTaker',
+ );
+
+ const actZRXAssetBalanceOfFeeRecipient = await this.assetWrapper.getBalanceAsync(
+ feeRecipient,
+ this.zrxAssetData,
+ );
+ expect(actZRXAssetBalanceOfFeeRecipient).to.be.bignumber.equal(
+ expZRXAssetBalanceOfFeeRecipient,
+ 'ZRXAssetBalanceOfFeeRecipient',
+ );
+ }
+ private async _getTakerAssetFillAmountAsync(
+ signedOrder: SignedOrder,
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario,
+ balanceAndProxyAllowanceFetcher: SimpleAssetBalanceAndProxyAllowanceFetcher,
+ orderFilledCancelledFetcher: SimpleOrderFilledCancelledFetcher,
+ ): Promise<BigNumber> {
+ const orderStateUtils = new OrderStateUtils(balanceAndProxyAllowanceFetcher, orderFilledCancelledFetcher);
+ const fillableTakerAssetAmount = await orderStateUtils.getMaxFillableTakerAssetAmountAsync(
+ signedOrder,
+ this.takerAddress,
+ );
+
+ let takerAssetFillAmount;
+ switch (takerAssetFillAmountScenario) {
+ case TakerAssetFillAmountScenario.Zero:
+ takerAssetFillAmount = new BigNumber(0);
+ break;
+
+ case TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount:
+ takerAssetFillAmount = fillableTakerAssetAmount;
+ break;
+
+ case TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount:
+ takerAssetFillAmount = fillableTakerAssetAmount.add(1);
+ break;
+
+ case TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount:
+ const takerAssetProxyId = assetProxyUtils.decodeAssetDataId(signedOrder.takerAssetData);
+ const makerAssetProxyId = assetProxyUtils.decodeAssetDataId(signedOrder.makerAssetData);
+ const isEitherAssetERC721 =
+ takerAssetProxyId === AssetProxyId.ERC721 || makerAssetProxyId === AssetProxyId.ERC721;
+ if (isEitherAssetERC721) {
+ throw new Error(
+ 'Cannot test `TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount` together with ERC721 assets since orders involving ERC721 must always be filled exactly.',
+ );
+ }
+ takerAssetFillAmount = fillableTakerAssetAmount.div(2).floor();
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr('TakerAssetFillAmountScenario', takerAssetFillAmountScenario);
+ }
+
+ return takerAssetFillAmount;
+ }
+ private async _modifyTraderStateAsync(
+ makerStateScenario: TraderStateScenario,
+ takerStateScenario: TraderStateScenario,
+ signedOrder: SignedOrder,
+ takerAssetFillAmount: BigNumber,
+ ): Promise<void> {
+ const makerAssetFillAmount = orderUtils.getPartialAmount(
+ takerAssetFillAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerAssetAmount,
+ );
+ switch (makerStateScenario.traderAssetBalance) {
+ case BalanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case BalanceAmountScenario.TooLow:
+ if (makerAssetFillAmount.eq(0)) {
+ throw new Error(`Cannot set makerAssetBalanceOfMaker TooLow if makerAssetFillAmount is 0`);
+ }
+ const tooLowBalance = makerAssetFillAmount.minus(1);
+ await this.assetWrapper.setBalanceAsync(
+ signedOrder.makerAddress,
+ signedOrder.makerAssetData,
+ tooLowBalance,
+ );
+ break;
+
+ case BalanceAmountScenario.Exact:
+ const exactBalance = makerAssetFillAmount;
+ await this.assetWrapper.setBalanceAsync(
+ signedOrder.makerAddress,
+ signedOrder.makerAssetData,
+ exactBalance,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'makerStateScenario.traderAssetBalance',
+ makerStateScenario.traderAssetBalance,
+ );
+ }
+
+ const makerFee = orderUtils.getPartialAmount(
+ takerAssetFillAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerFee,
+ );
+ switch (makerStateScenario.zrxFeeBalance) {
+ case BalanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case BalanceAmountScenario.TooLow:
+ if (makerFee.eq(0)) {
+ throw new Error(`Cannot set zrxAsserBalanceOfMaker TooLow if makerFee is 0`);
+ }
+ const tooLowBalance = makerFee.minus(1);
+ await this.assetWrapper.setBalanceAsync(signedOrder.makerAddress, this.zrxAssetData, tooLowBalance);
+ break;
+
+ case BalanceAmountScenario.Exact:
+ const exactBalance = makerFee;
+ await this.assetWrapper.setBalanceAsync(signedOrder.makerAddress, this.zrxAssetData, exactBalance);
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr('makerStateScenario.zrxFeeBalance', makerStateScenario.zrxFeeBalance);
+ }
+
+ switch (makerStateScenario.traderAssetAllowance) {
+ case AllowanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case AllowanceAmountScenario.TooLow:
+ const tooLowAllowance = makerAssetFillAmount.minus(1);
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ signedOrder.makerAssetData,
+ tooLowAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Exact:
+ const exactAllowance = makerAssetFillAmount;
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ signedOrder.makerAssetData,
+ exactAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Unlimited:
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ signedOrder.makerAssetData,
+ constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'makerStateScenario.traderAssetAllowance',
+ makerStateScenario.traderAssetAllowance,
+ );
+ }
+
+ switch (makerStateScenario.zrxFeeAllowance) {
+ case AllowanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case AllowanceAmountScenario.TooLow:
+ const tooLowAllowance = makerFee.minus(1);
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ this.zrxAssetData,
+ tooLowAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Exact:
+ const exactAllowance = makerFee;
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ this.zrxAssetData,
+ exactAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Unlimited:
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ this.zrxAssetData,
+ constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'makerStateScenario.zrxFeeAllowance',
+ makerStateScenario.zrxFeeAllowance,
+ );
+ }
+
+ switch (takerStateScenario.traderAssetBalance) {
+ case BalanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case BalanceAmountScenario.TooLow:
+ if (takerAssetFillAmount.eq(0)) {
+ throw new Error(`Cannot set takerAssetBalanceOfTaker TooLow if takerAssetFillAmount is 0`);
+ }
+ const tooLowBalance = takerAssetFillAmount.minus(1);
+ await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, tooLowBalance);
+ break;
+
+ case BalanceAmountScenario.Exact:
+ const exactBalance = takerAssetFillAmount;
+ await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, exactBalance);
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'takerStateScenario.traderAssetBalance',
+ takerStateScenario.traderAssetBalance,
+ );
+ }
+
+ const takerFee = orderUtils.getPartialAmount(
+ takerAssetFillAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.takerFee,
+ );
+ switch (takerStateScenario.zrxFeeBalance) {
+ case BalanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case BalanceAmountScenario.TooLow:
+ if (takerFee.eq(0)) {
+ throw new Error(`Cannot set zrxAssetBalanceOfTaker TooLow if takerFee is 0`);
+ }
+ const tooLowBalance = takerFee.minus(1);
+ await this.assetWrapper.setBalanceAsync(this.takerAddress, this.zrxAssetData, tooLowBalance);
+ break;
+
+ case BalanceAmountScenario.Exact:
+ const exactBalance = takerFee;
+ await this.assetWrapper.setBalanceAsync(this.takerAddress, this.zrxAssetData, exactBalance);
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr('takerStateScenario.zrxFeeBalance', takerStateScenario.zrxFeeBalance);
+ }
+
+ switch (takerStateScenario.traderAssetAllowance) {
+ case AllowanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case AllowanceAmountScenario.TooLow:
+ const tooLowAllowance = takerAssetFillAmount.minus(1);
+ await this.assetWrapper.setProxyAllowanceAsync(
+ this.takerAddress,
+ signedOrder.takerAssetData,
+ tooLowAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Exact:
+ const exactAllowance = takerAssetFillAmount;
+ await this.assetWrapper.setProxyAllowanceAsync(
+ this.takerAddress,
+ signedOrder.takerAssetData,
+ exactAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Unlimited:
+ await this.assetWrapper.setProxyAllowanceAsync(
+ this.takerAddress,
+ signedOrder.takerAssetData,
+ constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'takerStateScenario.traderAssetAllowance',
+ takerStateScenario.traderAssetAllowance,
+ );
+ }
+
+ switch (takerStateScenario.zrxFeeAllowance) {
+ case AllowanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case AllowanceAmountScenario.TooLow:
+ const tooLowAllowance = takerFee.minus(1);
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.takerAddress,
+ this.zrxAssetData,
+ tooLowAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Exact:
+ const exactAllowance = takerFee;
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.takerAddress,
+ this.zrxAssetData,
+ exactAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Unlimited:
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.takerAddress,
+ this.zrxAssetData,
+ constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'takerStateScenario.zrxFeeAllowance',
+ takerStateScenario.zrxFeeAllowance,
+ );
+ }
+ }
+} // tslint:disable:max-file-line-count
diff --git a/packages/contracts/src/utils/erc20_wrapper.ts b/packages/contracts/src/utils/erc20_wrapper.ts
index 91c9d50b7..7f00e2061 100644
--- a/packages/contracts/src/utils/erc20_wrapper.ts
+++ b/packages/contracts/src/utils/erc20_wrapper.ts
@@ -1,3 +1,4 @@
+import { assetProxyUtils } from '@0xproject/order-utils';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { Provider } from 'ethereum-types';
@@ -18,6 +19,7 @@ export class ERC20Wrapper {
private _provider: Provider;
private _dummyTokenContracts: DummyERC20TokenContract[];
private _proxyContract?: ERC20ProxyContract;
+ private _proxyIdIfExists?: string;
constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
this._dummyTokenContracts = [];
this._web3Wrapper = new Web3Wrapper(provider);
@@ -25,8 +27,11 @@ export class ERC20Wrapper {
this._tokenOwnerAddresses = tokenOwnerAddresses;
this._contractOwnerAddress = contractOwnerAddress;
}
- public async deployDummyTokensAsync(): Promise<DummyERC20TokenContract[]> {
- for (let i = 0; i < constants.NUM_DUMMY_ERC20_TO_DEPLOY; i++) {
+ public async deployDummyTokensAsync(
+ numberToDeploy: number,
+ decimals: BigNumber,
+ ): Promise<DummyERC20TokenContract[]> {
+ for (let i = 0; i < numberToDeploy; i++) {
this._dummyTokenContracts.push(
await DummyERC20TokenContract.deployFrom0xArtifactAsync(
artifacts.DummyERC20Token,
@@ -34,7 +39,7 @@ export class ERC20Wrapper {
txDefaults,
constants.DUMMY_TOKEN_NAME,
constants.DUMMY_TOKEN_SYMBOL,
- constants.DUMMY_TOKEN_DECIMALS,
+ decimals,
constants.DUMMY_TOKEN_TOTAL_SUPPLY,
),
);
@@ -47,8 +52,13 @@ export class ERC20Wrapper {
this._provider,
txDefaults,
);
+ this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync();
return this._proxyContract;
}
+ public getProxyId(): string {
+ this._validateProxyContractExistsOrThrow();
+ return this._proxyIdIfExists as string;
+ }
public async setBalancesAndAllowancesAsync(): Promise<void> {
this._validateDummyTokenContractsExistOrThrow();
this._validateProxyContractExistsOrThrow();
@@ -73,6 +83,36 @@ export class ERC20Wrapper {
}
}
}
+ public async getBalanceAsync(userAddress: string, assetData: string): Promise<BigNumber> {
+ const tokenContract = this._getTokenContractFromAssetData(assetData);
+ const balance = new BigNumber(await tokenContract.balanceOf.callAsync(userAddress));
+ return balance;
+ }
+ public async setBalanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(assetData);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.setBalance.sendTransactionAsync(userAddress, amount, {
+ from: this._contractOwnerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async getProxyAllowanceAsync(userAddress: string, assetData: string): Promise<BigNumber> {
+ const tokenContract = this._getTokenContractFromAssetData(assetData);
+ const proxyAddress = (this._proxyContract as ERC20ProxyContract).address;
+ const allowance = new BigNumber(await tokenContract.allowance.callAsync(userAddress, proxyAddress));
+ return allowance;
+ }
+ public async setAllowanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(assetData);
+ const proxyAddress = (this._proxyContract as ERC20ProxyContract).address;
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.approve.sendTransactionAsync(proxyAddress, amount, {
+ from: userAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
public async getBalancesAsync(): Promise<ERC20BalancesByOwner> {
this._validateDummyTokenContractsExistOrThrow();
const balancesByOwner: ERC20BalancesByOwner = {};
@@ -105,6 +145,15 @@ export class ERC20Wrapper {
const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address);
return tokenAddresses;
}
+ private _getTokenContractFromAssetData(assetData: string): DummyERC20TokenContract {
+ const erc20ProxyData = assetProxyUtils.decodeERC20AssetData(assetData);
+ const tokenAddress = erc20ProxyData.tokenAddress;
+ const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress);
+ if (_.isUndefined(tokenContractIfExists)) {
+ throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`);
+ }
+ return tokenContractIfExists;
+ }
private _validateDummyTokenContractsExistOrThrow(): void {
if (_.isUndefined(this._dummyTokenContracts)) {
throw new Error('Dummy ERC20 tokens not yet deployed, please call "deployDummyTokensAsync"');
diff --git a/packages/contracts/src/utils/erc721_wrapper.ts b/packages/contracts/src/utils/erc721_wrapper.ts
index b20d9acd2..309135ae4 100644
--- a/packages/contracts/src/utils/erc721_wrapper.ts
+++ b/packages/contracts/src/utils/erc721_wrapper.ts
@@ -19,6 +19,7 @@ export class ERC721Wrapper {
private _provider: Provider;
private _dummyTokenContracts: DummyERC721TokenContract[];
private _proxyContract?: ERC721ProxyContract;
+ private _proxyIdIfExists?: string;
private _initialTokenIdsByOwner: ERC721TokenIdsByOwner = {};
constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
this._web3Wrapper = new Web3Wrapper(provider);
@@ -47,8 +48,13 @@ export class ERC721Wrapper {
this._provider,
txDefaults,
);
+ this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync();
return this._proxyContract;
}
+ public getProxyId(): string {
+ this._validateProxyContractExistsOrThrow();
+ return this._proxyIdIfExists as string;
+ }
public async setBalancesAndAllowancesAsync(): Promise<void> {
this._validateDummyTokenContractsExistOrThrow();
this._validateProxyContractExistsOrThrow();
@@ -57,12 +63,7 @@ export class ERC721Wrapper {
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
for (let i = 0; i < constants.NUM_ERC721_TOKENS_TO_MINT; i++) {
const tokenId = generatePseudoRandomSalt();
- await this._web3Wrapper.awaitTransactionSuccessAsync(
- await dummyTokenContract.mint.sendTransactionAsync(tokenOwnerAddress, tokenId, {
- from: this._contractOwnerAddress,
- }),
- constants.AWAIT_TRANSACTION_MINED_MS,
- );
+ await this.mintAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress);
if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress])) {
this._initialTokenIdsByOwner[tokenOwnerAddress] = {
[dummyTokenContract.address]: [],
@@ -72,19 +73,100 @@ export class ERC721Wrapper {
this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address] = [];
}
this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address].push(tokenId);
+
+ await this.approveProxyAsync(dummyTokenContract.address, tokenId);
}
- const shouldApprove = true;
- await this._web3Wrapper.awaitTransactionSuccessAsync(
- await dummyTokenContract.setApprovalForAll.sendTransactionAsync(
- (this._proxyContract as ERC721ProxyContract).address,
- shouldApprove,
- { from: tokenOwnerAddress },
- ),
- constants.AWAIT_TRANSACTION_MINED_MS,
- );
}
}
}
+ public async doesTokenExistAsync(tokenAddress: string, tokenId: BigNumber): Promise<boolean> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const doesExist = await tokenContract.exists.callAsync(tokenId);
+ return doesExist;
+ }
+ public async approveProxyAsync(tokenAddress: string, tokenId: BigNumber): Promise<void> {
+ const proxyAddress = (this._proxyContract as ERC721ProxyContract).address;
+ await this.approveAsync(proxyAddress, tokenAddress, tokenId);
+ }
+ public async approveProxyForAllAsync(tokenAddress: string, tokenId: BigNumber, isApproved: boolean): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId);
+ const proxyAddress = (this._proxyContract as ERC721ProxyContract).address;
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.setApprovalForAll.sendTransactionAsync(proxyAddress, isApproved, {
+ from: tokenOwner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async approveAsync(to: string, tokenAddress: string, tokenId: BigNumber): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.approve.sendTransactionAsync(to, tokenId, {
+ from: tokenOwner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async transferFromAsync(
+ tokenAddress: string,
+ tokenId: BigNumber,
+ currentOwner: string,
+ userAddress: string,
+ ): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.transferFrom.sendTransactionAsync(currentOwner, userAddress, tokenId, {
+ from: currentOwner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async mintAsync(tokenAddress: string, tokenId: BigNumber, userAddress: string): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.mint.sendTransactionAsync(userAddress, tokenId, {
+ from: this._contractOwnerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async burnAsync(tokenAddress: string, tokenId: BigNumber, owner: string): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.burn.sendTransactionAsync(owner, tokenId, {
+ from: this._contractOwnerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async ownerOfAsync(tokenAddress: string, tokenId: BigNumber): Promise<string> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const owner = await tokenContract.ownerOf.callAsync(tokenId);
+ return owner;
+ }
+ public async isOwnerAsync(userAddress: string, tokenAddress: string, tokenId: BigNumber): Promise<boolean> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const tokenOwner = await tokenContract.ownerOf.callAsync(tokenId);
+ const isOwner = tokenOwner === userAddress;
+ return isOwner;
+ }
+ public async isProxyApprovedForAllAsync(userAddress: string, tokenAddress: string): Promise<boolean> {
+ this._validateProxyContractExistsOrThrow();
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const operator = (this._proxyContract as ERC721ProxyContract).address;
+ const didApproveAll = await tokenContract.isApprovedForAll.callAsync(userAddress, operator);
+ return didApproveAll;
+ }
+ public async isProxyApprovedAsync(tokenAddress: string, tokenId: BigNumber): Promise<boolean> {
+ this._validateProxyContractExistsOrThrow();
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const approvedAddress = await tokenContract.getApproved.callAsync(tokenId);
+ const proxyAddress = (this._proxyContract as ERC721ProxyContract).address;
+ const isProxyAnApprovedOperator = approvedAddress === proxyAddress;
+ return isProxyAnApprovedOperator;
+ }
public async getBalancesAsync(): Promise<ERC721TokenIdsByOwner> {
this._validateDummyTokenContractsExistOrThrow();
this._validateBalancesAndAllowancesSetOrThrow();
@@ -127,6 +209,13 @@ export class ERC721Wrapper {
const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address);
return tokenAddresses;
}
+ private _getTokenContractFromAssetData(tokenAddress: string): DummyERC721TokenContract {
+ const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress);
+ if (_.isUndefined(tokenContractIfExists)) {
+ throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`);
+ }
+ return tokenContractIfExists;
+ }
private _validateDummyTokenContractsExistOrThrow(): void {
if (_.isUndefined(this._dummyTokenContracts)) {
throw new Error('Dummy ERC721 tokens not yet deployed, please call "deployDummyTokensAsync"');
diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts
index 06eb70644..ad68c8ff4 100644
--- a/packages/contracts/src/utils/exchange_wrapper.ts
+++ b/packages/contracts/src/utils/exchange_wrapper.ts
@@ -33,8 +33,8 @@ export class ExchangeWrapper {
params.signature,
{ from },
);
- const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
- return tx;
+ const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
+ return txReceipt;
}
public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createCancel(signedOrder);
@@ -227,6 +227,10 @@ export class ExchangeWrapper {
const filledAmount = new BigNumber(await this._exchange.filled.callAsync(orderHashHex));
return filledAmount;
}
+ public async isCancelledAsync(orderHashHex: string): Promise<boolean> {
+ const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex);
+ return isCancelled;
+ }
public async getOrderInfoAsync(signedOrder: SignedOrder): Promise<OrderInfo> {
const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo;
return orderInfo;
diff --git a/packages/contracts/src/utils/order_factory_from_scenario.ts b/packages/contracts/src/utils/order_factory_from_scenario.ts
new file mode 100644
index 000000000..b150e59f6
--- /dev/null
+++ b/packages/contracts/src/utils/order_factory_from_scenario.ts
@@ -0,0 +1,277 @@
+import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils';
+import { Order } from '@0xproject/types';
+import { BigNumber, errorUtils } from '@0xproject/utils';
+
+import { DummyERC721TokenContract } from '../generated_contract_wrappers/dummy_e_r_c721_token';
+
+import { constants } from './constants';
+import {
+ AssetDataScenario,
+ ERC721TokenIdsByOwner,
+ ExpirationTimeSecondsScenario,
+ FeeRecipientAddressScenario,
+ OrderAssetAmountScenario,
+ OrderScenario,
+ TakerScenario,
+} from './types';
+
+const TEN_UNITS_EIGHTEEN_DECIMALS = new BigNumber(10_000_000_000_000_000_000);
+const FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(5_000_000_000_000_000_000);
+const POINT_ONE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(100_000_000_000_000_000);
+const POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(50_000_000_000_000_000);
+const TEN_UNITS_FIVE_DECIMALS = new BigNumber(1_000_000);
+const FIVE_UNITS_FIVE_DECIMALS = new BigNumber(500_000);
+const ONE_NFT_UNIT = new BigNumber(1);
+
+export class OrderFactoryFromScenario {
+ private _userAddresses: string[];
+ private _zrxAddress: string;
+ private _nonZrxERC20EighteenDecimalTokenAddresses: string[];
+ private _erc20FiveDecimalTokenAddresses: string[];
+ private _erc721Token: DummyERC721TokenContract;
+ private _erc721Balances: ERC721TokenIdsByOwner;
+ private _exchangeAddress: string;
+ constructor(
+ userAddresses: string[],
+ zrxAddress: string,
+ nonZrxERC20EighteenDecimalTokenAddresses: string[],
+ erc20FiveDecimalTokenAddresses: string[],
+ erc721Token: DummyERC721TokenContract,
+ erc721Balances: ERC721TokenIdsByOwner,
+ exchangeAddress: string,
+ ) {
+ this._userAddresses = userAddresses;
+ this._zrxAddress = zrxAddress;
+ this._nonZrxERC20EighteenDecimalTokenAddresses = nonZrxERC20EighteenDecimalTokenAddresses;
+ this._erc20FiveDecimalTokenAddresses = erc20FiveDecimalTokenAddresses;
+ this._erc721Token = erc721Token;
+ this._erc721Balances = erc721Balances;
+ this._exchangeAddress = exchangeAddress;
+ }
+ public generateOrder(orderScenario: OrderScenario): Order {
+ const makerAddress = this._userAddresses[1];
+ let takerAddress = this._userAddresses[2];
+ const erc721MakerAssetIds = this._erc721Balances[makerAddress][this._erc721Token.address];
+ const erc721TakerAssetIds = this._erc721Balances[takerAddress][this._erc721Token.address];
+ let feeRecipientAddress;
+ let makerAssetAmount;
+ let takerAssetAmount;
+ let makerFee;
+ let takerFee;
+ let expirationTimeSeconds;
+ let makerAssetData;
+ let takerAssetData;
+
+ switch (orderScenario.feeRecipientScenario) {
+ case FeeRecipientAddressScenario.BurnAddress:
+ feeRecipientAddress = constants.NULL_ADDRESS;
+ break;
+ case FeeRecipientAddressScenario.EthUserAddress:
+ feeRecipientAddress = this._userAddresses[4];
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('FeeRecipientAddressScenario', orderScenario.feeRecipientScenario);
+ }
+
+ switch (orderScenario.makerAssetDataScenario) {
+ case AssetDataScenario.ZRXFeeToken:
+ makerAssetData = assetProxyUtils.encodeERC20AssetData(this._zrxAddress);
+ break;
+ case AssetDataScenario.ERC20NonZRXEighteenDecimals:
+ makerAssetData = assetProxyUtils.encodeERC20AssetData(
+ this._nonZrxERC20EighteenDecimalTokenAddresses[0],
+ );
+ break;
+ case AssetDataScenario.ERC20FiveDecimals:
+ makerAssetData = assetProxyUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[0]);
+ break;
+ case AssetDataScenario.ERC721:
+ makerAssetData = assetProxyUtils.encodeERC721AssetData(
+ this._erc721Token.address,
+ erc721MakerAssetIds[0],
+ );
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario);
+ }
+
+ switch (orderScenario.takerAssetDataScenario) {
+ case AssetDataScenario.ZRXFeeToken:
+ takerAssetData = assetProxyUtils.encodeERC20AssetData(this._zrxAddress);
+ break;
+ case AssetDataScenario.ERC20NonZRXEighteenDecimals:
+ takerAssetData = assetProxyUtils.encodeERC20AssetData(
+ this._nonZrxERC20EighteenDecimalTokenAddresses[1],
+ );
+ break;
+ case AssetDataScenario.ERC20FiveDecimals:
+ takerAssetData = assetProxyUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[1]);
+ break;
+ case AssetDataScenario.ERC721:
+ takerAssetData = assetProxyUtils.encodeERC721AssetData(
+ this._erc721Token.address,
+ erc721TakerAssetIds[0],
+ );
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario);
+ }
+
+ switch (orderScenario.makerAssetAmountScenario) {
+ case OrderAssetAmountScenario.Large:
+ switch (orderScenario.makerAssetDataScenario) {
+ case AssetDataScenario.ZRXFeeToken:
+ case AssetDataScenario.ERC20NonZRXEighteenDecimals:
+ makerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case AssetDataScenario.ERC20FiveDecimals:
+ makerAssetAmount = TEN_UNITS_FIVE_DECIMALS;
+ break;
+ case AssetDataScenario.ERC721:
+ makerAssetAmount = ONE_NFT_UNIT;
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario);
+ }
+ break;
+ case OrderAssetAmountScenario.Small:
+ switch (orderScenario.makerAssetDataScenario) {
+ case AssetDataScenario.ZRXFeeToken:
+ case AssetDataScenario.ERC20NonZRXEighteenDecimals:
+ makerAssetAmount = FIVE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case AssetDataScenario.ERC20FiveDecimals:
+ makerAssetAmount = FIVE_UNITS_FIVE_DECIMALS;
+ break;
+ case AssetDataScenario.ERC721:
+ makerAssetAmount = ONE_NFT_UNIT;
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario);
+ }
+ break;
+ case OrderAssetAmountScenario.Zero:
+ makerAssetAmount = new BigNumber(0);
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.makerAssetAmountScenario);
+ }
+
+ switch (orderScenario.takerAssetAmountScenario) {
+ case OrderAssetAmountScenario.Large:
+ switch (orderScenario.takerAssetDataScenario) {
+ case AssetDataScenario.ERC20NonZRXEighteenDecimals:
+ case AssetDataScenario.ZRXFeeToken:
+ takerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case AssetDataScenario.ERC20FiveDecimals:
+ takerAssetAmount = TEN_UNITS_FIVE_DECIMALS;
+ break;
+ case AssetDataScenario.ERC721:
+ takerAssetAmount = ONE_NFT_UNIT;
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario);
+ }
+ break;
+ case OrderAssetAmountScenario.Small:
+ switch (orderScenario.takerAssetDataScenario) {
+ case AssetDataScenario.ERC20NonZRXEighteenDecimals:
+ case AssetDataScenario.ZRXFeeToken:
+ takerAssetAmount = FIVE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case AssetDataScenario.ERC20FiveDecimals:
+ takerAssetAmount = FIVE_UNITS_FIVE_DECIMALS;
+ break;
+ case AssetDataScenario.ERC721:
+ takerAssetAmount = ONE_NFT_UNIT;
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario);
+ }
+ break;
+ case OrderAssetAmountScenario.Zero:
+ takerAssetAmount = new BigNumber(0);
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.takerAssetAmountScenario);
+ }
+
+ switch (orderScenario.makerFeeScenario) {
+ case OrderAssetAmountScenario.Large:
+ makerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case OrderAssetAmountScenario.Small:
+ makerFee = POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case OrderAssetAmountScenario.Zero:
+ makerFee = new BigNumber(0);
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.makerFeeScenario);
+ }
+
+ switch (orderScenario.takerFeeScenario) {
+ case OrderAssetAmountScenario.Large:
+ takerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case OrderAssetAmountScenario.Small:
+ takerFee = POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case OrderAssetAmountScenario.Zero:
+ takerFee = new BigNumber(0);
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.takerFeeScenario);
+ }
+
+ switch (orderScenario.expirationTimeSecondsScenario) {
+ case ExpirationTimeSecondsScenario.InFuture:
+ expirationTimeSeconds = new BigNumber(2524604400); // Close to infinite
+ break;
+ case ExpirationTimeSecondsScenario.InPast:
+ expirationTimeSeconds = new BigNumber(0); // Jan 1, 1970
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'ExpirationTimeSecondsScenario',
+ orderScenario.expirationTimeSecondsScenario,
+ );
+ }
+
+ switch (orderScenario.takerScenario) {
+ case TakerScenario.CorrectlySpecified:
+ break; // noop since takerAddress is already specified
+
+ case TakerScenario.IncorrectlySpecified:
+ const notTaker = this._userAddresses[3];
+ takerAddress = notTaker;
+ break;
+
+ case TakerScenario.Unspecified:
+ takerAddress = constants.NULL_ADDRESS;
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr('TakerScenario', orderScenario.takerScenario);
+ }
+
+ const order = {
+ senderAddress: constants.NULL_ADDRESS,
+ makerAddress,
+ takerAddress,
+ makerFee,
+ takerFee,
+ makerAssetAmount,
+ takerAssetAmount,
+ makerAssetData,
+ takerAssetData,
+ salt: generatePseudoRandomSalt(),
+ exchangeAddress: this._exchangeAddress,
+ feeRecipientAddress,
+ expirationTimeSeconds,
+ };
+
+ return order;
+ }
+}
diff --git a/packages/contracts/src/utils/order_utils.ts b/packages/contracts/src/utils/order_utils.ts
index a9f994d80..019f6e74b 100644
--- a/packages/contracts/src/utils/order_utils.ts
+++ b/packages/contracts/src/utils/order_utils.ts
@@ -5,6 +5,13 @@ import { constants } from './constants';
import { CancelOrder, MatchOrder } from './types';
export const orderUtils = {
+ getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber {
+ const partialAmount = numerator
+ .mul(target)
+ .div(denominator)
+ .floor();
+ return partialAmount;
+ },
createFill: (signedOrder: SignedOrder, takerAssetFillAmount?: BigNumber) => {
const fill = {
order: orderUtils.getOrderWithoutExchangeAddress(signedOrder),
diff --git a/packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts b/packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts
new file mode 100644
index 000000000..a295a40c4
--- /dev/null
+++ b/packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts
@@ -0,0 +1,19 @@
+import { AbstractBalanceAndProxyAllowanceFetcher } from '@0xproject/order-utils';
+import { BigNumber } from '@0xproject/utils';
+
+import { AssetWrapper } from './asset_wrapper';
+
+export class SimpleAssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher {
+ private _assetWrapper: AssetWrapper;
+ constructor(assetWrapper: AssetWrapper) {
+ this._assetWrapper = assetWrapper;
+ }
+ public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
+ const balance = await this._assetWrapper.getBalanceAsync(userAddress, assetData);
+ return balance;
+ }
+ public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
+ const proxyAllowance = await this._assetWrapper.getProxyAllowanceAsync(userAddress, assetData);
+ return proxyAllowance;
+ }
+}
diff --git a/packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts b/packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts
new file mode 100644
index 000000000..24afe36b7
--- /dev/null
+++ b/packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts
@@ -0,0 +1,24 @@
+import { AbstractOrderFilledCancelledFetcher } from '@0xproject/order-utils';
+import { BigNumber } from '@0xproject/utils';
+
+import { ExchangeWrapper } from './exchange_wrapper';
+
+export class SimpleOrderFilledCancelledFetcher implements AbstractOrderFilledCancelledFetcher {
+ private _exchangeWrapper: ExchangeWrapper;
+ private _zrxAssetData: string;
+ constructor(exchange: ExchangeWrapper, zrxAssetData: string) {
+ this._exchangeWrapper = exchange;
+ this._zrxAssetData = zrxAssetData;
+ }
+ public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
+ const filledTakerAmount = new BigNumber(await this._exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash));
+ return filledTakerAmount;
+ }
+ public async isOrderCancelledAsync(orderHash: string): Promise<boolean> {
+ const isCancelled = await this._exchangeWrapper.isCancelledAsync(orderHash);
+ return isCancelled;
+ }
+ public getZRXAssetData(): string {
+ return this._zrxAssetData;
+ }
+}
diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts
index 5dfac64fc..b792bb90a 100644
--- a/packages/contracts/src/utils/types.ts
+++ b/packages/contracts/src/utils/types.ts
@@ -150,3 +150,80 @@ export interface MatchOrder {
leftSignature: string;
rightSignature: string;
}
+
+// Combinatorial testing types
+
+export enum FeeRecipientAddressScenario {
+ BurnAddress = 'BURN_ADDRESS',
+ EthUserAddress = 'ETH_USER_ADDRESS',
+}
+
+export enum OrderAssetAmountScenario {
+ Zero = 'ZERO',
+ Large = 'LARGE',
+ Small = 'SMALL',
+}
+
+export enum TakerScenario {
+ CorrectlySpecified = 'CORRECTLY_SPECIFIED',
+ IncorrectlySpecified = 'INCORRECTLY_SPECIFIED',
+ Unspecified = 'UNSPECIFIED',
+}
+
+export enum ExpirationTimeSecondsScenario {
+ InPast = 'IN_PAST',
+ InFuture = 'IN_FUTURE',
+}
+
+export enum AssetDataScenario {
+ ERC721 = 'ERC721',
+ ZRXFeeToken = 'ZRX_FEE_TOKEN',
+ ERC20FiveDecimals = 'ERC20_FIVE_DECIMALS',
+ ERC20NonZRXEighteenDecimals = 'ERC20_NON_ZRX_EIGHTEEN_DECIMALS',
+}
+
+export enum TakerAssetFillAmountScenario {
+ Zero = 'ZERO',
+ GreaterThanRemainingFillableTakerAssetAmount = 'GREATER_THAN_REMAINING_FILLABLE_TAKER_ASSET_AMOUNT',
+ LessThanRemainingFillableTakerAssetAmount = 'LESS_THAN_REMAINING_FILLABLE_TAKER_ASSET_AMOUNT',
+ ExactlyRemainingFillableTakerAssetAmount = 'EXACTLY_REMAINING_FILLABLE_TAKER_ASSET_AMOUNT',
+}
+
+export interface OrderScenario {
+ takerScenario: TakerScenario;
+ feeRecipientScenario: FeeRecipientAddressScenario;
+ makerAssetAmountScenario: OrderAssetAmountScenario;
+ takerAssetAmountScenario: OrderAssetAmountScenario;
+ makerFeeScenario: OrderAssetAmountScenario;
+ takerFeeScenario: OrderAssetAmountScenario;
+ expirationTimeSecondsScenario: ExpirationTimeSecondsScenario;
+ makerAssetDataScenario: AssetDataScenario;
+ takerAssetDataScenario: AssetDataScenario;
+}
+
+export enum BalanceAmountScenario {
+ Exact = 'EXACT',
+ TooLow = 'TOO_LOW',
+ Higher = 'HIGHER',
+}
+
+export enum AllowanceAmountScenario {
+ Exact = 'EXACT',
+ TooLow = 'TOO_LOW',
+ Higher = 'HIGHER',
+ Unlimited = 'UNLIMITED',
+}
+
+export interface TraderStateScenario {
+ traderAssetBalance: BalanceAmountScenario;
+ traderAssetAllowance: AllowanceAmountScenario;
+ zrxFeeBalance: BalanceAmountScenario;
+ zrxFeeAllowance: AllowanceAmountScenario;
+}
+
+export interface FillScenario {
+ orderScenario: OrderScenario;
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario;
+ makerStateScenario: TraderStateScenario;
+ takerStateScenario: TraderStateScenario;
+}
diff --git a/packages/contracts/test/asset_proxy/authorizable.ts b/packages/contracts/test/asset_proxy/authorizable.ts
index c35dc7882..8c9d0495d 100644
--- a/packages/contracts/test/asset_proxy/authorizable.ts
+++ b/packages/contracts/test/asset_proxy/authorizable.ts
@@ -1,10 +1,11 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
+import { RevertReason } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import { MixinAuthorizableContract } from '../../src/generated_contract_wrappers/mixin_authorizable';
import { artifacts } from '../../src/utils/artifacts';
-import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
+import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
import { chaiSetup } from '../../src/utils/chai_setup';
import { constants } from '../../src/utils/constants';
import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
@@ -43,8 +44,9 @@ describe('Authorizable', () => {
});
describe('addAuthorizedAddress', () => {
it('should throw if not called by owner', async () => {
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
authorizable.addAuthorizedAddress.sendTransactionAsync(notOwner, { from: notOwner }),
+ RevertReason.OnlyContractOwner,
);
});
it('should allow owner to add an authorized address', async () => {
@@ -60,8 +62,9 @@ describe('Authorizable', () => {
await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
+ RevertReason.TargetAlreadyAuthorized,
);
});
});
@@ -72,10 +75,11 @@ describe('Authorizable', () => {
await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
authorizable.removeAuthorizedAddress.sendTransactionAsync(address, {
from: notOwner,
}),
+ RevertReason.OnlyContractOwner,
);
});
@@ -95,10 +99,11 @@ describe('Authorizable', () => {
});
it('should throw if owner attempts to remove an address that is not authorized', async () => {
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
authorizable.removeAuthorizedAddress.sendTransactionAsync(address, {
from: owner,
}),
+ RevertReason.TargetNotAuthorized,
);
});
});
@@ -110,10 +115,11 @@ describe('Authorizable', () => {
constants.AWAIT_TRANSACTION_MINED_MS,
);
const index = new BigNumber(0);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
from: notOwner,
}),
+ RevertReason.OnlyContractOwner,
);
});
it('should throw if index is >= authorities.length', async () => {
@@ -122,18 +128,20 @@ describe('Authorizable', () => {
constants.AWAIT_TRANSACTION_MINED_MS,
);
const index = new BigNumber(1);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
from: owner,
}),
+ RevertReason.IndexOutOfBounds,
);
});
it('should throw if owner attempts to remove an address that is not authorized', async () => {
const index = new BigNumber(0);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
from: owner,
}),
+ RevertReason.TargetNotAuthorized,
);
});
it('should throw if address at index does not match target', async () => {
@@ -148,10 +156,11 @@ describe('Authorizable', () => {
constants.AWAIT_TRANSACTION_MINED_MS,
);
const address1Index = new BigNumber(0);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address2, address1Index, {
from: owner,
}),
+ RevertReason.AuthorizedAddressMismatch,
);
});
it('should allow owner to remove an authorized address', async () => {
diff --git a/packages/contracts/test/asset_proxy/decoder.ts b/packages/contracts/test/asset_proxy/decoder.ts
deleted file mode 100644
index 98d18aa38..000000000
--- a/packages/contracts/test/asset_proxy/decoder.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import { BlockchainLifecycle } from '@0xproject/dev-utils';
-import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils';
-import { BigNumber } from '@0xproject/utils';
-import * as chai from 'chai';
-import ethUtil = require('ethereumjs-util');
-
-import { TestAssetDataDecodersContract } from '../../src/generated_contract_wrappers/test_asset_data_decoders';
-import { artifacts } from '../../src/utils/artifacts';
-import { chaiSetup } from '../../src/utils/chai_setup';
-import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
-
-chaiSetup.configure();
-const expect = chai.expect;
-const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
-
-describe('TestAssetDataDecoders', () => {
- let testAssetProxyDecoder: TestAssetDataDecodersContract;
- let testAddress: string;
-
- before(async () => {
- await blockchainLifecycle.startAsync();
- });
- after(async () => {
- await blockchainLifecycle.revertAsync();
- });
- before(async () => {
- // Setup accounts & addresses
- const accounts = await web3Wrapper.getAvailableAddressesAsync();
- testAddress = accounts[0];
- testAssetProxyDecoder = await TestAssetDataDecodersContract.deployFrom0xArtifactAsync(
- artifacts.TestAssetDataDecoders,
- provider,
- txDefaults,
- );
- });
- beforeEach(async () => {
- await blockchainLifecycle.startAsync();
- });
- afterEach(async () => {
- await blockchainLifecycle.revertAsync();
- });
-
- describe('Asset Data Decoders', () => {
- it('should correctly decode ERC721 asset data', async () => {
- const tokenId = generatePseudoRandomSalt();
- const encodedAssetData = assetProxyUtils.encodeERC721AssetData(testAddress, tokenId);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
- const expectedDecodedAssetData = assetProxyUtils.decodeERC721AssetData(encodedAssetData);
- let decodedTokenAddress: string;
- let decodedTokenId: BigNumber;
- let decodedData: string;
- [
- decodedTokenAddress,
- decodedTokenId,
- decodedData,
- ] = await testAssetProxyDecoder.publicDecodeERC721Data.callAsync(encodedAssetDataWithoutProxyId);
- expect(decodedTokenAddress).to.be.equal(expectedDecodedAssetData.tokenAddress);
- expect(decodedTokenId).to.be.bignumber.equal(expectedDecodedAssetData.tokenId);
- expect(decodedData).to.be.equal(expectedDecodedAssetData.receiverData);
- });
-
- it('should correctly decode ERC721 asset data with receiver data', async () => {
- const tokenId = generatePseudoRandomSalt();
- const receiverDataFirst32Bytes = ethUtil.bufferToHex(
- assetProxyUtils.encodeUint256(generatePseudoRandomSalt()),
- );
- const receiverDataExtraBytes = 'FFFF';
- // We add extra bytes to generate a value that doesn't fit perfectly into one word
- const receiverData = receiverDataFirst32Bytes + receiverDataExtraBytes;
- const encodedAssetData = assetProxyUtils.encodeERC721AssetData(testAddress, tokenId, receiverData);
- const expectedDecodedAssetData = assetProxyUtils.decodeERC721AssetData(encodedAssetData);
- let decodedTokenAddress: string;
- let decodedTokenId: BigNumber;
- let decodedReceiverData: string;
- [
- decodedTokenAddress,
- decodedTokenId,
- decodedReceiverData,
- ] = await testAssetProxyDecoder.publicDecodeERC721Data.callAsync(encodedAssetData);
- expect(decodedTokenAddress).to.be.equal(expectedDecodedAssetData.tokenAddress);
- expect(decodedTokenId).to.be.bignumber.equal(expectedDecodedAssetData.tokenId);
- expect(decodedReceiverData).to.be.equal(expectedDecodedAssetData.receiverData);
- });
- });
-});
diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts
index 2c27f7382..39121d95c 100644
--- a/packages/contracts/test/asset_proxy/proxies.ts
+++ b/packages/contracts/test/asset_proxy/proxies.ts
@@ -1,5 +1,6 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils';
+import { RevertReason } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import { LogWithDecodedArgs } from 'ethereum-types';
@@ -15,7 +16,7 @@ import { DummyERC721TokenContract } from '../../src/generated_contract_wrappers/
import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy';
import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy';
import { artifacts } from '../../src/utils/artifacts';
-import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
+import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
import { chaiSetup } from '../../src/utils/chai_setup';
import { constants } from '../../src/utils/constants';
import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
@@ -53,12 +54,17 @@ describe('Asset Transfer Proxies', () => {
});
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- const usedAddresses = ([owner, notAuthorized, exchangeAddress, makerAddress, takerAddress] = accounts);
+ const usedAddresses = ([owner, notAuthorized, exchangeAddress, makerAddress, takerAddress] = _.slice(
+ accounts,
+ 0,
+ 5,
+ ));
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
- [zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ const numDummyErc20ToDeploy = 1;
+ [zrxToken] = await erc20Wrapper.deployDummyTokensAsync(numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS);
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
await web3Wrapper.awaitTransactionSuccessAsync(
@@ -96,13 +102,12 @@ describe('Asset Transfer Proxies', () => {
it('should successfully transfer tokens', async () => {
// Construct ERC20 asset data
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Perform a transfer from makerAddress to takerAddress
const erc20Balances = await erc20Wrapper.getBalancesAsync();
const amount = new BigNumber(10);
await web3Wrapper.awaitTransactionSuccessAsync(
await erc20Proxy.transferFrom.sendTransactionAsync(
- encodedAssetDataWithoutProxyId,
+ encodedAssetData,
makerAddress,
takerAddress,
amount,
@@ -123,13 +128,12 @@ describe('Asset Transfer Proxies', () => {
it('should do nothing if transferring 0 amount of a token', async () => {
// Construct ERC20 asset data
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Perform a transfer from makerAddress to takerAddress
const erc20Balances = await erc20Wrapper.getBalancesAsync();
const amount = new BigNumber(0);
await web3Wrapper.awaitTransactionSuccessAsync(
await erc20Proxy.transferFrom.sendTransactionAsync(
- encodedAssetDataWithoutProxyId,
+ encodedAssetData,
makerAddress,
takerAddress,
amount,
@@ -160,93 +164,37 @@ describe('Asset Transfer Proxies', () => {
constants.AWAIT_TRANSACTION_MINED_MS,
);
// Perform a transfer; expect this to fail.
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
erc20Proxy.transferFrom.sendTransactionAsync(
encodedAssetData,
makerAddress,
takerAddress,
transferAmount,
- { from: notAuthorized },
+ { from: exchangeAddress },
),
+ RevertReason.TransferFailed,
);
});
it('should throw if requesting address is not authorized', async () => {
// Construct ERC20 asset data
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
- // Perform a transfer from makerAddress to takerAddress
- const amount = new BigNumber(10);
- return expectRevertOrAlwaysFailingTransactionAsync(
- erc20Proxy.transferFrom.sendTransactionAsync(
- encodedAssetDataWithoutProxyId,
- makerAddress,
- takerAddress,
- amount,
- {
- from: notAuthorized,
- },
- ),
- );
- });
- });
-
- describe('batchTransferFrom', () => {
- it('should succesfully make multiple token transfers', async () => {
- const erc20Balances = await erc20Wrapper.getBalancesAsync();
-
- const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
- const amount = new BigNumber(10);
- const numTransfers = 2;
- const assetData = _.times(numTransfers, () => encodedAssetDataWithoutProxyId);
- const fromAddresses = _.times(numTransfers, () => makerAddress);
- const toAddresses = _.times(numTransfers, () => takerAddress);
- const amounts = _.times(numTransfers, () => amount);
-
- const txHash = await erc20Proxy.batchTransferFrom.sendTransactionAsync(
- assetData,
- fromAddresses,
- toAddresses,
- amounts,
- { from: exchangeAddress },
- );
- const res = await web3Wrapper.awaitTransactionSuccessAsync(
- 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 encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
+ // Perform a transfer from makerAddress to takerAddress
const amount = new BigNumber(10);
- const numTransfers = 2;
- const assetData = _.times(numTransfers, () => encodedAssetDataWithoutProxyId);
- const fromAddresses = _.times(numTransfers, () => makerAddress);
- const toAddresses = _.times(numTransfers, () => takerAddress);
- const amounts = _.times(numTransfers, () => amount);
-
- return expectRevertOrAlwaysFailingTransactionAsync(
- erc20Proxy.batchTransferFrom.sendTransactionAsync(assetData, fromAddresses, toAddresses, amounts, {
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
+ erc20Proxy.transferFrom.sendTransactionAsync(encodedAssetData, makerAddress, takerAddress, amount, {
from: notAuthorized,
}),
+ RevertReason.SenderNotAuthorized,
);
});
});
- it('should have an id of 1', async () => {
+ it('should have an id of 0xf47261b0', async () => {
const proxyId = await erc20Proxy.getProxyId.callAsync();
- expect(proxyId).to.equal(1);
+ const expectedProxyId = '0xf47261b0';
+ expect(proxyId).to.equal(expectedProxyId);
});
});
@@ -255,7 +203,6 @@ describe('Asset Transfer Proxies', () => {
it('should successfully transfer tokens', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Verify pre-condition
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
@@ -263,7 +210,7 @@ describe('Asset Transfer Proxies', () => {
const amount = new BigNumber(1);
await web3Wrapper.awaitTransactionSuccessAsync(
await erc721Proxy.transferFrom.sendTransactionAsync(
- encodedAssetDataWithoutProxyId,
+ encodedAssetData,
makerAddress,
takerAddress,
amount,
@@ -279,14 +226,13 @@ describe('Asset Transfer Proxies', () => {
it('should call onERC721Received when transferring to a smart contract without receiver data', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// 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 amount = new BigNumber(1);
const txHash = await erc721Proxy.transferFrom.sendTransactionAsync(
- encodedAssetDataWithoutProxyId,
+ encodedAssetData,
makerAddress,
erc721Receiver.address,
amount,
@@ -315,14 +261,13 @@ describe('Asset Transfer Proxies', () => {
erc721MakerTokenId,
receiverData,
);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// 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 amount = new BigNumber(1);
const txHash = await erc721Proxy.transferFrom.sendTransactionAsync(
- encodedAssetDataWithoutProxyId,
+ encodedAssetData,
makerAddress,
erc721Receiver.address,
amount,
@@ -351,67 +296,66 @@ describe('Asset Transfer Proxies', () => {
erc721MakerTokenId,
receiverData,
);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// 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 amount = new BigNumber(1);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
erc721Proxy.transferFrom.sendTransactionAsync(
- encodedAssetDataWithoutProxyId,
+ encodedAssetData,
makerAddress,
erc20Proxy.address, // the ERC20 proxy does not have an ERC721 receiver
amount,
{ from: exchangeAddress },
),
+ RevertReason.TransferFailed,
);
});
it('should throw if transferring 0 amount of a token', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// 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 amount = new BigNumber(0);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
erc721Proxy.transferFrom.sendTransactionAsync(
- encodedAssetDataWithoutProxyId,
+ encodedAssetData,
makerAddress,
takerAddress,
amount,
{ from: exchangeAddress },
),
+ RevertReason.InvalidAmount,
);
});
it('should throw if transferring > 1 amount of a token', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// 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 amount = new BigNumber(500);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
erc721Proxy.transferFrom.sendTransactionAsync(
- encodedAssetDataWithoutProxyId,
+ encodedAssetData,
makerAddress,
takerAddress,
amount,
{ from: exchangeAddress },
),
+ RevertReason.InvalidAmount,
);
});
it('should throw if allowances are too low', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Remove transfer approval for makerAddress.
await web3Wrapper.awaitTransactionSuccessAsync(
await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, false, {
@@ -421,94 +365,36 @@ describe('Asset Transfer Proxies', () => {
);
// Perform a transfer; expect this to fail.
const amount = new BigNumber(1);
- return expectRevertOrAlwaysFailingTransactionAsync(
- erc20Proxy.transferFrom.sendTransactionAsync(
- encodedAssetDataWithoutProxyId,
- makerAddress,
- takerAddress,
- amount,
- {
- from: notAuthorized,
- },
- ),
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
+ erc20Proxy.transferFrom.sendTransactionAsync(encodedAssetData, makerAddress, takerAddress, amount, {
+ from: exchangeAddress,
+ }),
+ RevertReason.TransferFailed,
);
});
it('should throw if requesting address is not authorized', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Perform a transfer from makerAddress to takerAddress
const amount = new BigNumber(1);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
erc721Proxy.transferFrom.sendTransactionAsync(
- encodedAssetDataWithoutProxyId,
+ encodedAssetData,
makerAddress,
takerAddress,
amount,
{ from: notAuthorized },
),
+ RevertReason.SenderNotAuthorized,
);
});
});
- 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 assetData = [
- assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdA).slice(0, -2),
- assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdB).slice(0, -2),
- ];
- const fromAddresses = _.times(numTransfers, () => makerAddress);
- const toAddresses = _.times(numTransfers, () => takerAddress);
- const amounts = _.times(numTransfers, () => new BigNumber(1));
-
- const txHash = await erc721Proxy.batchTransferFrom.sendTransactionAsync(
- assetData,
- fromAddresses,
- toAddresses,
- amounts,
- { from: exchangeAddress },
- );
- const res = await web3Wrapper.awaitTransactionSuccessAsync(
- 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 assetData = [
- assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdA).slice(0, -2),
- assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdB).slice(0, -2),
- ];
- const fromAddresses = _.times(numTransfers, () => makerAddress);
- const toAddresses = _.times(numTransfers, () => takerAddress);
- const amounts = _.times(numTransfers, () => new BigNumber(1));
-
- return expectRevertOrAlwaysFailingTransactionAsync(
- erc721Proxy.batchTransferFrom.sendTransactionAsync(assetData, fromAddresses, toAddresses, amounts, {
- from: notAuthorized,
- }),
- );
- });
- });
-
- it('should have an id of 2', async () => {
+ it('should have an id of 0x08e937fa', async () => {
const proxyId = await erc721Proxy.getProxyId.callAsync();
- expect(proxyId).to.equal(2);
+ const expectedProxyId = '0x08e937fa';
+ expect(proxyId).to.equal(expectedProxyId);
});
});
});
diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts
index ff652d3aa..0c7381aac 100644
--- a/packages/contracts/test/exchange/core.ts
+++ b/packages/contracts/test/exchange/core.ts
@@ -1,23 +1,20 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { assetProxyUtils, orderHashUtils } from '@0xproject/order-utils';
-import { AssetProxyId, SignedOrder } from '@0xproject/types';
+import { AssetProxyId, RevertReason, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as chai from 'chai';
import { LogWithDecodedArgs } from 'ethereum-types';
import ethUtil = require('ethereumjs-util');
+import * as _ from 'lodash';
import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token';
import { DummyERC721TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c721_token';
import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy';
import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy';
-import {
- CancelContractEventArgs,
- ExchangeContract,
- FillContractEventArgs,
-} from '../../src/generated_contract_wrappers/exchange';
+import { CancelContractEventArgs, ExchangeContract } from '../../src/generated_contract_wrappers/exchange';
import { artifacts } from '../../src/utils/artifacts';
-import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
+import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
import { chaiSetup } from '../../src/utils/chai_setup';
import { constants } from '../../src/utils/constants';
import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
@@ -66,12 +63,16 @@ describe('Exchange core', () => {
});
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts);
+ const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = _.slice(accounts, 0, 4));
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
- [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ const numDummyErc20ToDeploy = 3;
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
@@ -86,7 +87,7 @@ describe('Exchange core', () => {
artifacts.Exchange,
provider,
txDefaults,
- zrxToken.address,
+ assetProxyUtils.encodeERC20AssetData(zrxToken.address),
);
exchangeWrapper = new ExchangeWrapper(exchange, provider);
await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
@@ -130,295 +131,6 @@ describe('Exchange core', () => {
erc20Balances = await erc20Wrapper.getBalancesAsync();
signedOrder = orderFactory.newSignedOrder();
});
- it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => {
- signedOrder = orderFactory.newSignedOrder({
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
- });
-
- const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync(
- orderHashUtils.getOrderHashHex(signedOrder),
- );
- expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0);
-
- const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
- await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
-
- const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync(
- orderHashUtils.getOrderHashHex(signedOrder),
- );
- expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount);
-
- const newBalances = await erc20Wrapper.getBalancesAsync();
-
- 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[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(makerFeePaid),
- );
- 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(takerFeePaid),
- );
- expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
- erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
- );
- });
-
- it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => {
- signedOrder = orderFactory.newSignedOrder({
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18),
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
- });
-
- const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync(
- orderHashUtils.getOrderHashHex(signedOrder),
- );
- expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0);
-
- const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
- await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
-
- const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync(
- orderHashUtils.getOrderHashHex(signedOrder),
- );
- expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount);
-
- const newBalances = await erc20Wrapper.getBalancesAsync();
-
- 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[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(makerFeePaid),
- );
- 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(takerFeePaid),
- );
- expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
- erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
- );
- });
-
- it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => {
- signedOrder = orderFactory.newSignedOrder({
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18),
- });
-
- const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync(
- orderHashUtils.getOrderHashHex(signedOrder),
- );
- expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0);
-
- const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
- await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
-
- const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync(
- orderHashUtils.getOrderHashHex(signedOrder),
- );
- expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount);
-
- const newBalances = await erc20Wrapper.getBalancesAsync();
-
- 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[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(makerFeePaid),
- );
- 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(takerFeePaid),
- );
- 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 = orderFactory.newSignedOrder({
- takerAddress,
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18),
- });
-
- const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync(
- orderHashUtils.getOrderHashHex(signedOrder),
- );
- expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0);
-
- const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
- await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
-
- const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync(
- orderHashUtils.getOrderHashHex(signedOrder),
- );
- const expectedMakerAmountBoughtAfter = takerAssetFillAmount.add(takerAssetFilledAmountBefore);
- expect(makerAmountBoughtAfter).to.be.bignumber.equal(expectedMakerAmountBoughtAfter);
-
- const newBalances = await erc20Wrapper.getBalancesAsync();
-
- 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[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(makerFeePaid),
- );
- 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(takerFeePaid),
- );
- expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
- erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
- );
- });
-
- 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 exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
- takerAssetFillAmount: signedOrder.takerAssetAmount,
- });
- const log = res.logs[0] as LogWithDecodedArgs<FillContractEventArgs>;
- expect(log.args.takerAssetFilledAmount).to.be.bignumber.equal(
- signedOrder.takerAssetAmount.minus(takerAssetFillAmount),
- );
- const newBalances = await erc20Wrapper.getBalancesAsync();
-
- expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
- erc20Balances[makerAddress][defaultMakerAssetAddress].minus(signedOrder.makerAssetAmount),
- );
- expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
- erc20Balances[makerAddress][defaultTakerAssetAddress].add(signedOrder.takerAssetAmount),
- );
- expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
- erc20Balances[makerAddress][zrxToken.address].minus(signedOrder.makerFee),
- );
- expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
- erc20Balances[takerAddress][defaultTakerAssetAddress].minus(signedOrder.takerAssetAmount),
- );
- expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
- erc20Balances[takerAddress][defaultMakerAssetAddress].add(signedOrder.makerAssetAmount),
- );
- 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 log 1 event with the correct arguments when order has a feeRecipient', async () => {
- const divisor = 2;
- const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
- takerAssetFillAmount: signedOrder.takerAssetAmount.div(divisor),
- });
- expect(res.logs).to.have.length(1);
-
- 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);
-
- expect(signedOrder.makerAddress).to.be.equal(logArgs.makerAddress);
- expect(takerAddress).to.be.equal(logArgs.takerAddress);
- expect(takerAddress).to.be.equal(logArgs.senderAddress);
- 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(orderHashUtils.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash);
- });
-
- it('should throw when taker is specified and order is claimed by other', async () => {
- signedOrder = orderFactory.newSignedOrder({
- takerAddress: feeRecipientAddress,
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18),
- });
- return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- );
- });
it('should throw if signature is invalid', async () => {
signedOrder = orderFactory.newSignedOrder({
@@ -432,98 +144,18 @@ describe('Exchange core', () => {
const invalidSigBuff = Buffer.concat([v, invalidR, invalidS, signatureType]);
const invalidSigHex = `0x${invalidSigBuff.toString('hex')}`;
signedOrder.signature = invalidSigHex;
- return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- );
- });
-
- it('should throw if makerAssetAmount is 0', async () => {
- signedOrder = orderFactory.newSignedOrder({
- makerAssetAmount: new BigNumber(0),
- });
-
- return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- );
- });
-
- it('should throw if takerAssetAmount is 0', async () => {
- signedOrder = orderFactory.newSignedOrder({
- takerAssetAmount: new BigNumber(0),
- });
-
- return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- );
- });
-
- it('should throw if takerAssetFillAmount is 0', async () => {
- signedOrder = orderFactory.newSignedOrder();
-
- return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
- takerAssetFillAmount: new BigNumber(0),
- }),
- );
- });
-
- it('should throw if maker erc20Balances are too low to fill order', async () => {
- signedOrder = orderFactory.newSignedOrder({
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18),
- });
-
- return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- );
- });
-
- it('should throw if taker erc20Balances are too low to fill order', async () => {
- signedOrder = orderFactory.newSignedOrder({
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18),
- });
- return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- );
- });
-
- it('should throw if maker allowances are too low to fill order', async () => {
- await web3Wrapper.awaitTransactionSuccessAsync(
- await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), {
- from: makerAddress,
- }),
- constants.AWAIT_TRANSACTION_MINED_MS,
- );
- return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- );
- });
-
- it('should throw if taker allowances are too low to fill order', async () => {
- await web3Wrapper.awaitTransactionSuccessAsync(
- await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), {
- from: takerAddress,
- }),
- constants.AWAIT_TRANSACTION_MINED_MS,
- );
- return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- );
- });
-
- it('should throw if an order is expired', async () => {
- signedOrder = orderFactory.newSignedOrder({
- expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
- });
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
+ RevertReason.InvalidOrderSignature,
);
});
it('should throw if no value is filled', async () => {
signedOrder = orderFactory.newSignedOrder();
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
+ RevertReason.OrderUnfillable,
);
});
});
@@ -535,8 +167,9 @@ describe('Exchange core', () => {
});
it('should throw if not sent by maker', async () => {
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.cancelOrderAsync(signedOrder, takerAddress),
+ RevertReason.InvalidMaker,
);
});
@@ -545,8 +178,9 @@ describe('Exchange core', () => {
makerAssetAmount: new BigNumber(0),
});
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress),
+ RevertReason.OrderUnfillable,
);
});
@@ -555,17 +189,19 @@ describe('Exchange core', () => {
takerAssetAmount: new BigNumber(0),
});
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress),
+ RevertReason.OrderUnfillable,
);
});
it('should be able to cancel a full order', async () => {
await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
takerAssetFillAmount: signedOrder.takerAssetAmount.div(2),
}),
+ RevertReason.OrderUnfillable,
);
});
@@ -586,8 +222,9 @@ describe('Exchange core', () => {
it('should throw if already cancelled', async () => {
await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress),
+ RevertReason.OrderUnfillable,
);
});
@@ -595,8 +232,9 @@ describe('Exchange core', () => {
signedOrder = orderFactory.newSignedOrder({
expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
});
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress),
+ RevertReason.OrderUnfillable,
);
});
@@ -612,10 +250,11 @@ describe('Exchange core', () => {
});
const fillTakerAssetAmount2 = new BigNumber(1);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
takerAssetFillAmount: fillTakerAssetAmount2,
}),
+ RevertReason.RoundingError,
);
});
});
@@ -625,16 +264,18 @@ describe('Exchange core', () => {
const orderEpoch = new BigNumber(1);
await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress);
const lesserOrderEpoch = new BigNumber(0);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.cancelOrdersUpToAsync(lesserOrderEpoch, makerAddress),
+ RevertReason.InvalidNewOrderEpoch,
);
});
it('should fail to set orderEpoch equal to existing orderEpoch', async () => {
const orderEpoch = new BigNumber(1);
await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress),
+ RevertReason.InvalidNewOrderEpoch,
);
});
@@ -705,32 +346,6 @@ describe('Exchange core', () => {
});
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.encodeERC721AssetData(erc721Token.address, makerAssetId),
- takerAssetData: assetProxyUtils.encodeERC721AssetData(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;
- // tslint:disable-next-line:no-unused-variable
- 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];
@@ -748,8 +363,9 @@ describe('Exchange core', () => {
expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
// Call Exchange
const takerAssetFillAmount = signedOrder.takerAssetAmount;
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
+ RevertReason.TransferFailed,
);
});
@@ -770,8 +386,9 @@ describe('Exchange core', () => {
expect(initialOwnerTakerAsset).to.be.bignumber.not.equal(takerAddress);
// Call Exchange
const takerAssetFillAmount = signedOrder.takerAssetAmount;
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
+ RevertReason.TransferFailed,
);
});
@@ -792,8 +409,9 @@ describe('Exchange core', () => {
expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
// Call Exchange
const takerAssetFillAmount = signedOrder.takerAssetAmount;
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
+ RevertReason.InvalidAmount,
);
});
@@ -814,110 +432,52 @@ describe('Exchange core', () => {
expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
// Call Exchange
const takerAssetFillAmount = signedOrder.takerAssetAmount;
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
+ RevertReason.InvalidAmount,
);
});
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.encodeERC721AssetData(erc721Token.address, makerAssetId),
- takerAssetData: assetProxyUtils.encodeERC721AssetData(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 expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
- );
- });
-
- 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: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
takerAssetData: assetProxyUtils.encodeERC20AssetData(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),
- ),
+ const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
+ exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
+ RevertReason.RoundingError,
);
});
- it('should successfully fill order when makerAsset is ERC20 and takerAsset is ERC721', async () => {
+ it('should throw if assetData has a length < 132', async () => {
// Construct Exchange parameters
+ const makerAssetId = erc721MakerAssetIds[0];
const takerAssetId = erc721TakerAssetIds[0];
+ const makerAssetData = assetProxyUtils
+ .encodeERC721AssetData(erc721Token.address, makerAssetId)
+ .slice(0, -2);
signedOrder = orderFactory.newSignedOrder({
+ makerAssetAmount: new BigNumber(1),
takerAssetAmount: new BigNumber(1),
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
+ makerAssetData,
takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId),
- makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultMakerAssetAddress),
});
// 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
- 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),
- ),
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
+ exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
+ RevertReason.LengthGreaterThan131Required,
);
});
});
diff --git a/packages/contracts/test/exchange/dispatcher.ts b/packages/contracts/test/exchange/dispatcher.ts
index abbfd7ac7..e35cca845 100644
--- a/packages/contracts/test/exchange/dispatcher.ts
+++ b/packages/contracts/test/exchange/dispatcher.ts
@@ -1,15 +1,16 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { assetProxyUtils } from '@0xproject/order-utils';
-import { AssetProxyId } from '@0xproject/types';
+import { AssetProxyId, RevertReason } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
+import * as _ from 'lodash';
import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token';
import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy';
import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy';
import { TestAssetProxyDispatcherContract } from '../../src/generated_contract_wrappers/test_asset_proxy_dispatcher';
import { artifacts } from '../../src/utils/artifacts';
-import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
+import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
import { chaiSetup } from '../../src/utils/chai_setup';
import { constants } from '../../src/utils/constants';
import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
@@ -43,12 +44,13 @@ describe('AssetProxyDispatcher', () => {
before(async () => {
// Setup accounts & addresses
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- const usedAddresses = ([owner, notOwner, makerAddress, takerAddress] = accounts);
+ const usedAddresses = ([owner, notOwner, makerAddress, takerAddress] = _.slice(accounts, 0, 4));
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
- [zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ const numDummyErc20ToDeploy = 1;
+ [zrxToken] = await erc20Wrapper.deployDummyTokensAsync(numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS);
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
@@ -175,13 +177,14 @@ describe('AssetProxyDispatcher', () => {
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 constants.NULL_ADDRESS
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
AssetProxyId.ERC20,
erc20Proxy.address,
constants.NULL_ADDRESS,
{ from: owner },
),
+ RevertReason.AssetProxyMismatch,
);
});
@@ -216,25 +219,27 @@ describe('AssetProxyDispatcher', () => {
it('should throw if requesting address is not owner', async () => {
const prevProxyAddress = constants.NULL_ADDRESS;
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
AssetProxyId.ERC20,
erc20Proxy.address,
prevProxyAddress,
{ from: notOwner },
),
+ RevertReason.OnlyContractOwner,
);
});
it('should throw if attempting to register a proxy to the incorrect id', async () => {
const prevProxyAddress = constants.NULL_ADDRESS;
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(
AssetProxyId.ERC721,
erc20Proxy.address,
prevProxyAddress,
{ from: owner },
),
+ RevertReason.AssetProxyIdMismatch,
);
});
});
@@ -276,14 +281,13 @@ describe('AssetProxyDispatcher', () => {
);
// Construct metadata for ERC20 proxy
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
+
// Perform a transfer from makerAddress to takerAddress
const erc20Balances = await erc20Wrapper.getBalancesAsync();
const amount = new BigNumber(10);
await web3Wrapper.awaitTransactionSuccessAsync(
await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync(
- encodedAssetDataWithoutProxyId,
- AssetProxyId.ERC20,
+ encodedAssetData,
makerAddress,
takerAddress,
amount,
@@ -304,18 +308,17 @@ describe('AssetProxyDispatcher', () => {
it('should throw if dispatching to unregistered proxy', async () => {
// Construct metadata for ERC20 proxy
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
- const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Perform a transfer from makerAddress to takerAddress
const amount = new BigNumber(10);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync(
- encodedAssetDataWithoutProxyId,
- AssetProxyId.ERC20,
+ encodedAssetData,
makerAddress,
takerAddress,
amount,
{ from: owner },
),
+ RevertReason.AssetProxyDoesNotExist,
);
});
});
diff --git a/packages/contracts/test/exchange/fill_order.ts b/packages/contracts/test/exchange/fill_order.ts
new file mode 100644
index 000000000..d65ab2f9a
--- /dev/null
+++ b/packages/contracts/test/exchange/fill_order.ts
@@ -0,0 +1,306 @@
+import { BlockchainLifecycle } from '@0xproject/dev-utils';
+import * as _ from 'lodash';
+
+import { chaiSetup } from '../../src/utils/chai_setup';
+import { CoreCombinatorialUtils, coreCombinatorialUtilsFactoryAsync } from '../../src/utils/core_combinatorial_utils';
+import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
+
+import {
+ AllowanceAmountScenario,
+ AssetDataScenario,
+ BalanceAmountScenario,
+ ExpirationTimeSecondsScenario,
+ FeeRecipientAddressScenario,
+ FillScenario,
+ OrderAssetAmountScenario,
+ TakerAssetFillAmountScenario,
+ TakerScenario,
+} from '../../src/utils/types';
+
+chaiSetup.configure();
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+const defaultFillScenario = {
+ orderScenario: {
+ takerScenario: TakerScenario.Unspecified,
+ feeRecipientScenario: FeeRecipientAddressScenario.EthUserAddress,
+ makerAssetAmountScenario: OrderAssetAmountScenario.Large,
+ takerAssetAmountScenario: OrderAssetAmountScenario.Large,
+ makerFeeScenario: OrderAssetAmountScenario.Large,
+ takerFeeScenario: OrderAssetAmountScenario.Large,
+ expirationTimeSecondsScenario: ExpirationTimeSecondsScenario.InFuture,
+ makerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
+ takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount,
+ makerStateScenario: {
+ traderAssetBalance: BalanceAmountScenario.Higher,
+ traderAssetAllowance: AllowanceAmountScenario.Higher,
+ zrxFeeBalance: BalanceAmountScenario.Higher,
+ zrxFeeAllowance: AllowanceAmountScenario.Higher,
+ },
+ takerStateScenario: {
+ traderAssetBalance: BalanceAmountScenario.Higher,
+ traderAssetAllowance: AllowanceAmountScenario.Higher,
+ zrxFeeBalance: BalanceAmountScenario.Higher,
+ zrxFeeAllowance: AllowanceAmountScenario.Higher,
+ },
+};
+
+describe('FillOrder Tests', () => {
+ let coreCombinatorialUtils: CoreCombinatorialUtils;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ coreCombinatorialUtils = await coreCombinatorialUtilsFactoryAsync(web3Wrapper, txDefaults);
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('fillOrder', () => {
+ const test = (fillScenarios: FillScenario[]) => {
+ _.forEach(fillScenarios, fillScenario => {
+ const orderScenario = fillScenario.orderScenario;
+ const description = `Combinatorial OrderFill: ${orderScenario.feeRecipientScenario} ${
+ orderScenario.makerAssetAmountScenario
+ } ${orderScenario.takerAssetAmountScenario} ${orderScenario.makerFeeScenario} ${
+ orderScenario.takerFeeScenario
+ } ${orderScenario.expirationTimeSecondsScenario} ${orderScenario.makerAssetDataScenario} ${
+ orderScenario.takerAssetDataScenario
+ }`;
+ it(description, async () => {
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ });
+ };
+
+ const allFillScenarios = CoreCombinatorialUtils.generateFillOrderCombinations();
+ describe('Combinatorially generated fills orders', () => test(allFillScenarios));
+
+ it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ takerAssetAmountScenario: OrderAssetAmountScenario.Small,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ makerAssetAmountScenario: OrderAssetAmountScenario.Small,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ takerScenario: TakerScenario.CorrectlySpecified,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ it('should fill remaining value if takerAssetFillAmount > remaining takerAssetAmount', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ it('should throw when taker is specified and order is claimed by other', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ takerScenario: TakerScenario.IncorrectlySpecified,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if makerAssetAmount is 0', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ makerAssetAmountScenario: OrderAssetAmountScenario.Zero,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if takerAssetAmount is 0', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ takerAssetAmountScenario: OrderAssetAmountScenario.Zero,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if takerAssetFillAmount is 0', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.Zero,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if an order is expired', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ expirationTimeSecondsScenario: ExpirationTimeSecondsScenario.InPast,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if maker erc20Balances are too low to fill order', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ makerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetBalance: BalanceAmountScenario.TooLow,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if taker erc20Balances are too low to fill order', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ takerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetBalance: BalanceAmountScenario.TooLow,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if maker allowances are too low to fill order', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ makerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetAllowance: AllowanceAmountScenario.TooLow,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if taker allowances are too low to fill order', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ takerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetAllowance: AllowanceAmountScenario.TooLow,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ });
+
+ describe('Testing Exchange of ERC721 Tokens', () => {
+ it('should successfully exchange a single token between the maker and taker (via fillOrder)', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ makerAssetDataScenario: AssetDataScenario.ERC721,
+ takerAssetDataScenario: AssetDataScenario.ERC721,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should successfully fill order when makerAsset is ERC721 and takerAsset is ERC20', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ makerAssetDataScenario: AssetDataScenario.ERC721,
+ takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario, true);
+ });
+
+ it('should successfully fill order when makerAsset is ERC20 and takerAsset is ERC721', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ makerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
+ takerAssetDataScenario: AssetDataScenario.ERC721,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should successfully fill order when makerAsset is ERC721 and approveAll is set for it', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ makerAssetDataScenario: AssetDataScenario.ERC721,
+ takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
+ makerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetAllowance: AllowanceAmountScenario.Unlimited,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should successfully fill order when makerAsset and takerAsset are ERC721 and approveAll is set for them', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ makerAssetDataScenario: AssetDataScenario.ERC721,
+ takerAssetDataScenario: AssetDataScenario.ERC721,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
+ makerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetAllowance: AllowanceAmountScenario.Unlimited,
+ },
+ takerStateScenario: {
+ ...defaultFillScenario.takerStateScenario,
+ traderAssetAllowance: AllowanceAmountScenario.Unlimited,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ });
+});
diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts
index b8dca04fd..e23ab9851 100644
--- a/packages/contracts/test/exchange/match_orders.ts
+++ b/packages/contracts/test/exchange/match_orders.ts
@@ -1,6 +1,6 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { assetProxyUtils } from '@0xproject/order-utils';
-import { AssetProxyId } from '@0xproject/types';
+import { AssetProxyId, RevertReason } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as chai from 'chai';
@@ -12,7 +12,7 @@ import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c2
import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy';
import { ExchangeContract } from '../../src/generated_contract_wrappers/exchange';
import { artifacts } from '../../src/utils/artifacts';
-import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
+import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
import { chaiSetup } from '../../src/utils/chai_setup';
import { constants } from '../../src/utils/constants';
import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
@@ -76,12 +76,16 @@ describe('matchOrders', () => {
takerAddress,
feeRecipientAddressLeft,
feeRecipientAddressRight,
- ] = accounts);
+ ] = _.slice(accounts, 0, 6));
// 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();
+ const numDummyErc20ToDeploy = 3;
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
// Deploy ERC721 token and proxy
@@ -96,7 +100,7 @@ describe('matchOrders', () => {
artifacts.Exchange,
provider,
txDefaults,
- zrxToken.address,
+ assetProxyUtils.encodeERC20AssetData(zrxToken.address),
);
exchangeWrapper = new ExchangeWrapper(exchange, provider);
await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
@@ -598,8 +602,9 @@ describe('matchOrders', () => {
// Cancel left order
await exchangeWrapper.cancelOrderAsync(signedOrderLeft, signedOrderLeft.makerAddress);
// Match orders
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress),
+ RevertReason.OrderUnfillable,
);
});
@@ -622,8 +627,9 @@ describe('matchOrders', () => {
// Cancel right order
await exchangeWrapper.cancelOrderAsync(signedOrderRight, signedOrderRight.makerAddress);
// Match orders
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress),
+ RevertReason.OrderUnfillable,
);
});
@@ -644,7 +650,7 @@ describe('matchOrders', () => {
feeRecipientAddress: feeRecipientAddressRight,
});
// Match orders
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
matchOrderTester.matchOrdersAndVerifyBalancesAsync(
signedOrderLeft,
signedOrderRight,
@@ -652,6 +658,7 @@ describe('matchOrders', () => {
erc20BalancesByOwner,
erc721TokenIdsByOwner,
),
+ RevertReason.NegativeSpreadRequired,
);
});
@@ -672,7 +679,7 @@ describe('matchOrders', () => {
feeRecipientAddress: feeRecipientAddressRight,
});
// Match orders
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
matchOrderTester.matchOrdersAndVerifyBalancesAsync(
signedOrderLeft,
signedOrderRight,
@@ -680,6 +687,11 @@ describe('matchOrders', () => {
erc20BalancesByOwner,
erc721TokenIdsByOwner,
),
+ // We are assuming assetData fields of the right order are the
+ // reverse of the left order, rather than checking equality. This
+ // saves a bunch of gas, but as a result if the assetData fields are
+ // off then the failure ends up happening at signature validation
+ RevertReason.InvalidOrderSignature,
);
});
@@ -702,7 +714,7 @@ describe('matchOrders', () => {
feeRecipientAddress: feeRecipientAddressRight,
});
// Match orders
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
matchOrderTester.matchOrdersAndVerifyBalancesAsync(
signedOrderLeft,
signedOrderRight,
@@ -710,6 +722,7 @@ describe('matchOrders', () => {
erc20BalancesByOwner,
erc721TokenIdsByOwner,
),
+ RevertReason.InvalidOrderSignature,
);
});
diff --git a/packages/contracts/test/exchange/signature_validator.ts b/packages/contracts/test/exchange/signature_validator.ts
index 8e221e3f1..21cc343b3 100644
--- a/packages/contracts/test/exchange/signature_validator.ts
+++ b/packages/contracts/test/exchange/signature_validator.ts
@@ -1,6 +1,6 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { addSignedMessagePrefix, assetProxyUtils, MessagePrefixType, orderHashUtils } from '@0xproject/order-utils';
-import { SignatureType, SignedOrder } from '@0xproject/types';
+import { RevertReason, SignatureType, SignedOrder } from '@0xproject/types';
import * as chai from 'chai';
import { LogWithDecodedArgs } from 'ethereum-types';
import ethUtil = require('ethereumjs-util');
@@ -107,7 +107,7 @@ describe('MixinSignatureValidator', () => {
signedOrder.makerAddress,
emptySignature,
),
- constants.EXCHANGE_LENGTH_GREATER_THAN_0_REQUIRED,
+ RevertReason.LengthGreaterThan0Required,
);
});
@@ -121,7 +121,7 @@ describe('MixinSignatureValidator', () => {
signedOrder.makerAddress,
unsupportedSignatureHex,
),
- constants.EXCHANGE_SIGNATURE_UNSUPPORTED,
+ RevertReason.SignatureUnsupported,
);
});
@@ -134,7 +134,7 @@ describe('MixinSignatureValidator', () => {
signedOrder.makerAddress,
unsupportedSignatureHex,
),
- constants.EXCHANGE_SIGNATURE_ILLEGAL,
+ RevertReason.SignatureIllegal,
);
});
@@ -161,7 +161,7 @@ describe('MixinSignatureValidator', () => {
signedOrder.makerAddress,
signatureHex,
),
- constants.EXCHANGE_LENGTH_0_REQUIRED,
+ RevertReason.Length0Required,
);
});
diff --git a/packages/contracts/test/exchange/transactions.ts b/packages/contracts/test/exchange/transactions.ts
index 9a625880c..c4de58bb9 100644
--- a/packages/contracts/test/exchange/transactions.ts
+++ b/packages/contracts/test/exchange/transactions.ts
@@ -1,8 +1,9 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils';
-import { AssetProxyId, OrderWithoutExchangeAddress, SignedOrder } from '@0xproject/types';
+import { AssetProxyId, OrderWithoutExchangeAddress, RevertReason, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
+import * as _ from 'lodash';
import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token';
import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy';
@@ -10,7 +11,7 @@ import { ExchangeContract } from '../../src/generated_contract_wrappers/exchange
import { ExchangeWrapperContract } from '../../src/generated_contract_wrappers/exchange_wrapper';
import { WhitelistContract } from '../../src/generated_contract_wrappers/whitelist';
import { artifacts } from '../../src/utils/artifacts';
-import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
+import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
import { chaiSetup } from '../../src/utils/chai_setup';
import { constants } from '../../src/utils/constants';
import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
@@ -59,13 +60,27 @@ describe('Exchange transactions', () => {
after(async () => {
await blockchainLifecycle.revertAsync();
});
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- const usedAddresses = ([owner, senderAddress, makerAddress, takerAddress, feeRecipientAddress] = accounts);
+ const usedAddresses = ([owner, senderAddress, makerAddress, takerAddress, feeRecipientAddress] = _.slice(
+ accounts,
+ 0,
+ 5,
+ ));
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
- [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ const numDummyErc20ToDeploy = 3;
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
@@ -73,7 +88,7 @@ describe('Exchange transactions', () => {
artifacts.Exchange,
provider,
txDefaults,
- zrxToken.address,
+ assetProxyUtils.encodeERC20AssetData(zrxToken.address),
);
exchangeWrapper = new ExchangeWrapper(exchange, provider);
await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
@@ -101,13 +116,6 @@ describe('Exchange transactions', () => {
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;
@@ -126,8 +134,9 @@ describe('Exchange transactions', () => {
});
it('should throw if not called by specified sender', async () => {
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.executeTransactionAsync(signedTx, takerAddress),
+ RevertReason.FailedExecution,
);
});
@@ -168,8 +177,9 @@ describe('Exchange transactions', () => {
it('should throw if the a 0x transaction with the same transactionHash has already been executed', async () => {
await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.executeTransactionAsync(signedTx, senderAddress),
+ RevertReason.InvalidTxHash,
);
});
@@ -187,15 +197,17 @@ describe('Exchange transactions', () => {
});
it('should throw if not called by specified sender', async () => {
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.executeTransactionAsync(signedTx, makerAddress),
+ RevertReason.FailedExecution,
);
});
it('should cancel the order when signed by maker and called by sender', async () => {
await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.fillOrderAsync(signedOrder, senderAddress),
+ RevertReason.OrderUnfillable,
);
});
});
@@ -238,7 +250,7 @@ describe('Exchange transactions', () => {
signedOrder.signature,
);
const signedFillTx = takerTransactionFactory.newSignedTransaction(fillData);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapperContract.fillOrder.sendTransactionAsync(
orderWithoutExchangeAddress,
takerAssetFillAmount,
@@ -247,6 +259,7 @@ describe('Exchange transactions', () => {
signedFillTx.signature,
{ from: takerAddress },
),
+ RevertReason.FailedExecution,
);
});
@@ -357,7 +370,7 @@ describe('Exchange transactions', () => {
orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder);
const takerAssetFillAmount = signedOrder.takerAssetAmount;
const salt = generatePseudoRandomSalt();
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
whitelist.fillOrderIfWhitelisted.sendTransactionAsync(
orderWithoutExchangeAddress,
takerAssetFillAmount,
@@ -365,6 +378,7 @@ describe('Exchange transactions', () => {
signedOrder.signature,
{ from: takerAddress },
),
+ RevertReason.MakerNotWhitelisted,
);
});
@@ -378,7 +392,7 @@ describe('Exchange transactions', () => {
orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder);
const takerAssetFillAmount = signedOrder.takerAssetAmount;
const salt = generatePseudoRandomSalt();
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
whitelist.fillOrderIfWhitelisted.sendTransactionAsync(
orderWithoutExchangeAddress,
takerAssetFillAmount,
@@ -386,6 +400,7 @@ describe('Exchange transactions', () => {
signedOrder.signature,
{ from: takerAddress },
),
+ RevertReason.TakerNotWhitelisted,
);
});
diff --git a/packages/contracts/test/exchange/wrapper.ts b/packages/contracts/test/exchange/wrapper.ts
index 703f644b8..1af8bde9d 100644
--- a/packages/contracts/test/exchange/wrapper.ts
+++ b/packages/contracts/test/exchange/wrapper.ts
@@ -1,6 +1,6 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { assetProxyUtils } from '@0xproject/order-utils';
-import { AssetProxyId, SignedOrder } from '@0xproject/types';
+import { AssetProxyId, RevertReason, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as chai from 'chai';
@@ -12,7 +12,7 @@ import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c2
import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy';
import { ExchangeContract } from '../../src/generated_contract_wrappers/exchange';
import { artifacts } from '../../src/utils/artifacts';
-import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
+import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
import { chaiSetup } from '../../src/utils/chai_setup';
import { constants } from '../../src/utils/constants';
import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
@@ -60,12 +60,16 @@ describe('Exchange wrappers', () => {
});
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts);
+ const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = _.slice(accounts, 0, 4));
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
- [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ const numDummyErc20ToDeploy = 3;
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
@@ -80,7 +84,7 @@ describe('Exchange wrappers', () => {
artifacts.Exchange,
provider,
txDefaults,
- zrxToken.address,
+ assetProxyUtils.encodeERC20AssetData(zrxToken.address),
);
exchangeWrapper = new ExchangeWrapper(exchange, provider);
await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
@@ -165,13 +169,14 @@ describe('Exchange wrappers', () => {
);
});
- it('should throw if an signedOrder is expired', async () => {
+ it('should throw if a signedOrder is expired', async () => {
const signedOrder = orderFactory.newSignedOrder({
expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
});
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress),
+ RevertReason.OrderUnfillable,
);
});
@@ -182,8 +187,9 @@ describe('Exchange wrappers', () => {
takerAssetFillAmount: signedOrder.takerAssetAmount.div(2),
});
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress),
+ RevertReason.CompleteFillFailed,
);
});
});
@@ -368,7 +374,7 @@ describe('Exchange wrappers', () => {
// HACK(albrow): We need to hardcode the gas estimate here because
// the Geth gas estimator doesn't work with the way we use
// delegatecall and swallow errors.
- gas: 270000,
+ gas: 280000,
});
// Verify post-conditions
const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
@@ -494,10 +500,11 @@ describe('Exchange wrappers', () => {
await exchangeWrapper.fillOrKillOrderAsync(signedOrders[0], takerAddress);
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.batchFillOrKillOrdersAsync(signedOrders, takerAddress, {
takerAssetFillAmounts,
}),
+ RevertReason.OrderUnfillable,
);
});
});
@@ -687,7 +694,7 @@ describe('Exchange wrappers', () => {
expect(newBalances).to.be.deep.equal(erc20Balances);
});
- it('should throw when an signedOrder does not use the same takerAssetAddress', async () => {
+ it('should throw when a signedOrder does not use the same takerAssetAddress', async () => {
signedOrders = [
orderFactory.newSignedOrder(),
orderFactory.newSignedOrder({
@@ -696,10 +703,13 @@ describe('Exchange wrappers', () => {
orderFactory.newSignedOrder(),
];
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, {
takerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18),
}),
+ // We simply use the takerAssetData from the first order for all orders.
+ // If they are not the same, the contract throws when validating the order signature
+ RevertReason.InvalidOrderSignature,
);
});
});
@@ -902,7 +912,7 @@ describe('Exchange wrappers', () => {
expect(newBalances).to.be.deep.equal(erc20Balances);
});
- it('should throw when an signedOrder does not use the same makerAssetAddress', async () => {
+ it('should throw when a signedOrder does not use the same makerAssetAddress', async () => {
signedOrders = [
orderFactory.newSignedOrder(),
orderFactory.newSignedOrder({
@@ -911,10 +921,11 @@ describe('Exchange wrappers', () => {
orderFactory.newSignedOrder(),
];
- return expectRevertOrAlwaysFailingTransactionAsync(
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, {
makerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18),
}),
+ RevertReason.InvalidOrderSignature,
);
});
});
diff --git a/packages/contracts/test/libraries/lib_bytes.ts b/packages/contracts/test/libraries/lib_bytes.ts
index 56f1dc2bc..3d4058cbc 100644
--- a/packages/contracts/test/libraries/lib_bytes.ts
+++ b/packages/contracts/test/libraries/lib_bytes.ts
@@ -1,5 +1,6 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils';
+import { RevertReason } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import BN = require('bn.js');
import * as chai from 'chai';
@@ -101,7 +102,7 @@ describe('LibBytes', () => {
it('should revert if length is 0', async () => {
return expectRevertOrOtherErrorAsync(
libBytes.publicPopLastByte.callAsync(constants.NULL_BYTES),
- constants.LIB_BYTES_GREATER_THAN_ZERO_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterThanZeroLengthRequired,
);
});
it('should pop the last byte from the input and return it', async () => {
@@ -117,7 +118,7 @@ describe('LibBytes', () => {
it('should revert if length is less than 20', async () => {
return expectRevertOrOtherErrorAsync(
libBytes.publicPopLast20Bytes.callAsync(byteArrayShorterThan20Bytes),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
);
});
it('should pop the last 20 bytes from the input and return it', async () => {
@@ -185,7 +186,7 @@ describe('LibBytes', () => {
it('should revert if dest is shorter than source', async () => {
return expectRevertOrOtherErrorAsync(
libBytes.publicDeepCopyBytes.callAsync(byteArrayShorterThan32Bytes, byteArrayLongerThan32Bytes),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualToSourceBytesLengthRequired,
);
});
it('should overwrite dest with source if source and dest have equal length', async () => {
@@ -238,7 +239,7 @@ describe('LibBytes', () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
libBytes.publicReadAddress.callAsync(shortByteArray, offset),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold an address', async () => {
@@ -246,7 +247,7 @@ describe('LibBytes', () => {
const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength);
return expectRevertOrOtherErrorAsync(
libBytes.publicReadAddress.callAsync(byteArray, badOffset),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
);
});
});
@@ -282,7 +283,7 @@ describe('LibBytes', () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
libBytes.publicWriteAddress.callAsync(byteArrayShorterThan20Bytes, offset, testAddress),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold an address', async () => {
@@ -290,7 +291,7 @@ describe('LibBytes', () => {
const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength);
return expectRevertOrOtherErrorAsync(
libBytes.publicWriteAddress.callAsync(byteArray, badOffset, testAddress),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
);
});
});
@@ -314,14 +315,14 @@ describe('LibBytes', () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
libBytes.publicReadBytes32.callAsync(byteArrayShorterThan32Bytes, offset),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
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 expectRevertOrOtherErrorAsync(
libBytes.publicReadBytes32.callAsync(testBytes32, badOffset),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
});
@@ -357,7 +358,7 @@ describe('LibBytes', () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
libBytes.publicWriteBytes32.callAsync(byteArrayShorterThan32Bytes, offset, testBytes32),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold a bytes32', async () => {
@@ -365,7 +366,7 @@ describe('LibBytes', () => {
const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength);
return expectRevertOrOtherErrorAsync(
libBytes.publicWriteBytes32.callAsync(byteArray, badOffset, testBytes32),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
});
@@ -393,7 +394,7 @@ describe('LibBytes', () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
libBytes.publicReadUint256.callAsync(byteArrayShorterThan32Bytes, offset),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold a uint256', async () => {
@@ -403,7 +404,7 @@ describe('LibBytes', () => {
const badOffset = new BigNumber(testUint256AsBuffer.byteLength);
return expectRevertOrOtherErrorAsync(
libBytes.publicReadUint256.callAsync(byteArray, badOffset),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
});
@@ -443,7 +444,7 @@ describe('LibBytes', () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
libBytes.publicWriteUint256.callAsync(byteArrayShorterThan32Bytes, offset, testUint256),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold a uint256', async () => {
@@ -451,7 +452,7 @@ describe('LibBytes', () => {
const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength);
return expectRevertOrOtherErrorAsync(
libBytes.publicWriteUint256.callAsync(byteArray, badOffset, testUint256),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
});
@@ -462,7 +463,7 @@ describe('LibBytes', () => {
const byteArrayLessThan4Bytes = '0x010101';
return expectRevertOrOtherErrorAsync(
libBytes.publicReadBytes4.callAsync(byteArrayLessThan4Bytes, new BigNumber(0)),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo4LengthRequired,
);
});
it('should return the first 4 bytes of a byte array of arbitrary length', async () => {
@@ -517,28 +518,28 @@ describe('LibBytes', () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
libBytes.publicReadBytesWithLength.callAsync(byteArrayShorterThan32Bytes, offset),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
it('should fail if we store a nested byte array length, without a nested byte array', async () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
libBytes.publicReadBytesWithLength.callAsync(testBytes32, offset),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualToNestedBytesLengthRequired,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold the length of a nested byte array', async () => {
const badOffset = new BigNumber(ethUtil.toBuffer(byteArrayShorterThan32Bytes).byteLength);
return expectRevertOrOtherErrorAsync(
libBytes.publicReadBytesWithLength.callAsync(byteArrayShorterThan32Bytes, badOffset),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold the nested byte array', async () => {
const badOffset = new BigNumber(ethUtil.toBuffer(testBytes32).byteLength);
return expectRevertOrOtherErrorAsync(
libBytes.publicReadBytesWithLength.callAsync(testBytes32, badOffset),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
});
@@ -650,7 +651,7 @@ describe('LibBytes', () => {
const emptyByteArray = ethUtil.bufferToHex(new Buffer(1));
return expectRevertOrOtherErrorAsync(
libBytes.publicWriteBytesWithLength.callAsync(emptyByteArray, offset, longData),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualToNestedBytesLengthRequired,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold the length of a nested byte array)', async () => {
@@ -658,7 +659,7 @@ describe('LibBytes', () => {
const badOffset = new BigNumber(ethUtil.toBuffer(shortTestBytesAsBuffer).byteLength);
return expectRevertOrOtherErrorAsync(
libBytes.publicWriteBytesWithLength.callAsync(emptyByteArray, badOffset, shortData),
- constants.LIB_BYTES_GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED,
+ RevertReason.LibBytesGreaterOrEqualToNestedBytesLengthRequired,
);
});
});
diff --git a/packages/contracts/test/unlimited_allowance_token.ts b/packages/contracts/test/unlimited_allowance_token.ts
index 7132c57bf..0eb82b5fe 100644
--- a/packages/contracts/test/unlimited_allowance_token.ts
+++ b/packages/contracts/test/unlimited_allowance_token.ts
@@ -1,4 +1,5 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
+import { RevertReason } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
@@ -55,7 +56,7 @@ describe('UnlimitedAllowanceToken', () => {
const amountToTransfer = ownerBalance.plus(1);
return expectRevertOrOtherErrorAsync(
token.transfer.callAsync(spender, amountToTransfer, { from: owner }),
- constants.ERC20_INSUFFICIENT_BALANCE,
+ RevertReason.Erc20InsufficientBalance,
);
});
@@ -96,7 +97,7 @@ describe('UnlimitedAllowanceToken', () => {
token.transferFrom.callAsync(owner, spender, amountToTransfer, {
from: spender,
}),
- constants.ERC20_INSUFFICIENT_BALANCE,
+ RevertReason.Erc20InsufficientBalance,
);
});
@@ -112,7 +113,7 @@ describe('UnlimitedAllowanceToken', () => {
token.transferFrom.callAsync(owner, spender, amountToTransfer, {
from: spender,
}),
- constants.ERC20_INSUFFICIENT_ALLOWANCE,
+ RevertReason.Erc20InsufficientAllowance,
);
});