aboutsummaryrefslogtreecommitdiffstats
path: root/contracts/extensions
diff options
context:
space:
mode:
authorSteve Klebanoff <steve.klebanoff@gmail.com>2018-12-22 06:57:16 +0800
committerSteve Klebanoff <steve.klebanoff@gmail.com>2018-12-22 06:57:16 +0800
commite144ebbb936c74b4af0e1c1776578208ea148de4 (patch)
treec5ab2823603b25d11d46493e9c8f79801bef0dcd /contracts/extensions
parentaa9aa1f58a4e63b5e5c7863f3b7afb020d7712c5 (diff)
parent9b540fd8e52e7578d3749e6d9ef9cd97d602ffb3 (diff)
downloaddexon-sol-tools-e144ebbb936c74b4af0e1c1776578208ea148de4.tar
dexon-sol-tools-e144ebbb936c74b4af0e1c1776578208ea148de4.tar.gz
dexon-sol-tools-e144ebbb936c74b4af0e1c1776578208ea148de4.tar.bz2
dexon-sol-tools-e144ebbb936c74b4af0e1c1776578208ea148de4.tar.lz
dexon-sol-tools-e144ebbb936c74b4af0e1c1776578208ea148de4.tar.xz
dexon-sol-tools-e144ebbb936c74b4af0e1c1776578208ea148de4.tar.zst
dexon-sol-tools-e144ebbb936c74b4af0e1c1776578208ea148de4.zip
Merge branch 'development' into feature/instant/tell-amount-available
Diffstat (limited to 'contracts/extensions')
-rw-r--r--contracts/extensions/CHANGELOG.json17
-rw-r--r--contracts/extensions/compiler.json2
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol45
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol135
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol103
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol55
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol31
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol54
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol56
-rw-r--r--contracts/extensions/contracts/Forwarder/MixinAssets.sol2
-rw-r--r--contracts/extensions/contracts/Forwarder/MixinExchangeWrapper.sol2
-rw-r--r--contracts/extensions/contracts/Forwarder/MixinForwarderCore.sol2
-rw-r--r--contracts/extensions/contracts/Forwarder/MixinWeth.sol2
-rw-r--r--contracts/extensions/contracts/Forwarder/interfaces/IAssets.sol2
-rw-r--r--contracts/extensions/contracts/Forwarder/interfaces/IForwarder.sol2
-rw-r--r--contracts/extensions/contracts/Forwarder/interfaces/IForwarderCore.sol2
-rw-r--r--contracts/extensions/contracts/Forwarder/libs/LibConstants.sol2
-rw-r--r--contracts/extensions/contracts/Forwarder/libs/LibForwarderErrors.sol2
-rw-r--r--contracts/extensions/contracts/Forwarder/mixins/MAssets.sol2
-rw-r--r--contracts/extensions/contracts/Forwarder/mixins/MExchangeWrapper.sol2
-rw-r--r--contracts/extensions/contracts/Forwarder/mixins/MWeth.sol2
-rw-r--r--contracts/extensions/contracts/OrderMatcher/MixinAssets.sol195
-rw-r--r--contracts/extensions/contracts/OrderMatcher/MixinMatchOrders.sol86
-rw-r--r--contracts/extensions/contracts/OrderMatcher/OrderMatcher.sol38
-rw-r--r--contracts/extensions/contracts/OrderMatcher/interfaces/IAssets.sol43
-rw-r--r--contracts/extensions/contracts/OrderMatcher/interfaces/IMatchOrders.sol43
-rw-r--r--contracts/extensions/contracts/OrderMatcher/interfaces/IOrderMatcher.sol31
-rw-r--r--contracts/extensions/contracts/OrderMatcher/libs/LibConstants.sol56
-rw-r--r--contracts/extensions/contracts/OrderMatcher/mixins/MAssets.sol71
-rw-r--r--contracts/extensions/contracts/OrderValidator/OrderValidator.sol218
-rw-r--r--contracts/extensions/package.json5
-rw-r--r--contracts/extensions/src/artifacts/index.ts6
-rw-r--r--contracts/extensions/src/wrappers/index.ts3
-rw-r--r--contracts/extensions/test/extensions/balance_threshold_filter.ts1644
-rw-r--r--contracts/extensions/test/extensions/forwarder.ts3
-rw-r--r--contracts/extensions/test/extensions/order_matcher.ts818
-rw-r--r--contracts/extensions/test/extensions/order_validator.ts609
-rw-r--r--contracts/extensions/test/utils/balance_threshold_wrapper.ts283
-rw-r--r--contracts/extensions/tsconfig.json8
39 files changed, 4664 insertions, 18 deletions
diff --git a/contracts/extensions/CHANGELOG.json b/contracts/extensions/CHANGELOG.json
index 19ac770af..083ad33fb 100644
--- a/contracts/extensions/CHANGELOG.json
+++ b/contracts/extensions/CHANGELOG.json
@@ -1,5 +1,22 @@
[
{
+ "version": "1.1.0",
+ "changes": [
+ {
+ "note": "Added Balance Threshold Filter",
+ "pr": 1383
+ },
+ {
+ "note": "Add OrderMatcher",
+ "pr": 1117
+ },
+ {
+ "note": "Add OrderValidator",
+ "pr": 1464
+ }
+ ]
+ },
+ {
"timestamp": 1544741676,
"version": "1.0.2",
"changes": [
diff --git a/contracts/extensions/compiler.json b/contracts/extensions/compiler.json
index 69d607b3e..2bb468724 100644
--- a/contracts/extensions/compiler.json
+++ b/contracts/extensions/compiler.json
@@ -18,5 +18,5 @@
}
}
},
- "contracts": ["DutchAuction", "Forwarder"]
+ "contracts": ["BalanceThresholdFilter", "DutchAuction", "Forwarder", "OrderMatcher", "OrderValidator"]
}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol b/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol
new file mode 100644
index 000000000..16cacd461
--- /dev/null
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol
@@ -0,0 +1,45 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity 0.4.24;
+
+import "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchange.sol";
+import "./interfaces/IThresholdAsset.sol";
+import "./MixinBalanceThresholdFilterCore.sol";
+
+
+contract BalanceThresholdFilter is
+ MixinBalanceThresholdFilterCore
+{
+
+ /// @dev Constructs BalanceThresholdFilter.
+ /// @param exchange Address of 0x exchange.
+ /// @param thresholdAsset The asset that must be held by makers/takers.
+ /// @param balanceThreshold The minimum balance of `thresholdAsset` that must be held by makers/takers.
+ constructor(
+ address exchange,
+ address thresholdAsset,
+ uint256 balanceThreshold
+ )
+ public
+ {
+ EXCHANGE = IExchange(exchange);
+ THRESHOLD_ASSET = IThresholdAsset(thresholdAsset);
+ BALANCE_THRESHOLD = balanceThreshold;
+ }
+}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol
new file mode 100644
index 000000000..da050bf72
--- /dev/null
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol
@@ -0,0 +1,135 @@
+/*
+
+ 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/LICENSE2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "@0x/contracts-libs/contracts/libs/LibExchangeSelectors.sol";
+import "@0x/contracts-libs/contracts/libs/LibOrder.sol";
+import "./mixins/MBalanceThresholdFilterCore.sol";
+import "./MixinExchangeCalldata.sol";
+
+
+contract MixinBalanceThresholdFilterCore is
+ MBalanceThresholdFilterCore,
+ MixinExchangeCalldata,
+ LibOrder,
+ LibExchangeSelectors
+{
+
+ /// @dev Executes an Exchange transaction iff the maker and taker meet
+ /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR
+ /// the exchange function is a cancellation.
+ /// Supported Exchange functions:
+ /// batchFillOrders
+ /// batchFillOrdersNoThrow
+ /// batchFillOrKillOrders
+ /// fillOrder
+ /// fillOrderNoThrow
+ /// fillOrKillOrder
+ /// marketBuyOrders
+ /// marketBuyOrdersNoThrow
+ /// marketSellOrders
+ /// marketSellOrdersNoThrow
+ /// matchOrders
+ /// cancelOrder
+ /// batchCancelOrders
+ /// cancelOrdersUpTo
+ /// Trying to call any other exchange function will throw.
+ /// @param salt Arbitrary number to ensure uniqueness of transaction hash.
+ /// @param signerAddress Address of transaction signer.
+ /// @param signedExchangeTransaction AbiV2 encoded calldata.
+ /// @param signature Proof of signer transaction by signer.
+ function executeTransaction(
+ uint256 salt,
+ address signerAddress,
+ bytes signedExchangeTransaction,
+ bytes signature
+ )
+ external
+ {
+ // Get accounts whose balances must be validated
+ address[] memory addressesToValidate = getAddressesToValidate(signerAddress);
+
+ // Validate account balances
+ uint256 balanceThreshold = BALANCE_THRESHOLD;
+ IThresholdAsset thresholdAsset = THRESHOLD_ASSET;
+ for (uint256 i = 0; i < addressesToValidate.length; ++i) {
+ uint256 addressBalance = thresholdAsset.balanceOf(addressesToValidate[i]);
+ require(
+ addressBalance >= balanceThreshold,
+ "AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD"
+ );
+ }
+ emit ValidatedAddresses(addressesToValidate);
+
+ // All addresses are valid. Execute exchange function.
+ EXCHANGE.executeTransaction(
+ salt,
+ signerAddress,
+ signedExchangeTransaction,
+ signature
+ );
+ }
+
+ /// @dev Constructs an array of addresses to be validated.
+ /// Addresses depend on which Exchange function is to be called
+ /// (defined by `signedExchangeTransaction` above).
+ /// @param signerAddress Address of transaction signer.
+ /// @return addressesToValidate Array of addresses to validate.
+ function getAddressesToValidate(address signerAddress)
+ internal pure
+ returns (address[] memory addressesToValidate)
+ {
+ bytes4 exchangeFunctionSelector = bytes4(exchangeCalldataload(0));
+ // solhint-disable expression-indent
+ if (
+ exchangeFunctionSelector == BATCH_FILL_ORDERS_SELECTOR ||
+ exchangeFunctionSelector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR ||
+ exchangeFunctionSelector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR ||
+ exchangeFunctionSelector == MARKET_BUY_ORDERS_SELECTOR ||
+ exchangeFunctionSelector == MARKET_BUY_ORDERS_NO_THROW_SELECTOR ||
+ exchangeFunctionSelector == MARKET_SELL_ORDERS_SELECTOR ||
+ exchangeFunctionSelector == MARKET_SELL_ORDERS_NO_THROW_SELECTOR
+ ) {
+ addressesToValidate = loadMakerAddressesFromOrderArray(0);
+ addressesToValidate = addressesToValidate.append(signerAddress);
+ } else if (
+ exchangeFunctionSelector == FILL_ORDER_SELECTOR ||
+ exchangeFunctionSelector == FILL_ORDER_NO_THROW_SELECTOR ||
+ exchangeFunctionSelector == FILL_OR_KILL_ORDER_SELECTOR
+ ) {
+ address makerAddress = loadMakerAddressFromOrder(0);
+ addressesToValidate = addressesToValidate.append(makerAddress);
+ addressesToValidate = addressesToValidate.append(signerAddress);
+ } else if (exchangeFunctionSelector == MATCH_ORDERS_SELECTOR) {
+ address leftMakerAddress = loadMakerAddressFromOrder(0);
+ addressesToValidate = addressesToValidate.append(leftMakerAddress);
+ address rightMakerAddress = loadMakerAddressFromOrder(1);
+ addressesToValidate = addressesToValidate.append(rightMakerAddress);
+ addressesToValidate = addressesToValidate.append(signerAddress);
+ } else if (
+ exchangeFunctionSelector != CANCEL_ORDER_SELECTOR &&
+ exchangeFunctionSelector != BATCH_CANCEL_ORDERS_SELECTOR &&
+ exchangeFunctionSelector != CANCEL_ORDERS_UP_TO_SELECTOR
+ ) {
+ revert("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR");
+ }
+ // solhint-enable expression-indent
+ return addressesToValidate;
+ }
+}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol
new file mode 100644
index 000000000..a15e95a9d
--- /dev/null
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol
@@ -0,0 +1,103 @@
+
+ /*
+
+ 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/LICENSE2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "./mixins/MExchangeCalldata.sol";
+import "@0x/contracts-libs/contracts/libs/LibAddressArray.sol";
+
+
+contract MixinExchangeCalldata is
+ MExchangeCalldata
+{
+
+ using LibAddressArray for address[];
+
+ /// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata,
+ /// which is accessed through `signedExchangeTransaction`.
+ /// @param offset Offset into the Exchange calldata.
+ /// @return value Corresponding 32 byte value stored at `offset`.
+ function exchangeCalldataload(uint256 offset)
+ internal pure
+ returns (bytes32 value)
+ {
+ assembly {
+ // Pointer to exchange transaction
+ // 0x04 for calldata selector
+ // 0x40 to access `signedExchangeTransaction`, which is the third parameter
+ let exchangeTxPtr := calldataload(0x44)
+
+ // Offset into Exchange calldata
+ // We compute this by adding 0x24 to the `exchangeTxPtr` computed above.
+ // 0x04 for calldata selector
+ // 0x20 for length field of `signedExchangeTransaction`
+ let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset))
+ value := calldataload(exchangeCalldataOffset)
+ }
+ return value;
+ }
+
+ /// @dev Convenience function that skips the 4 byte selector when loading
+ /// from the embedded Exchange calldata.
+ /// @param offset Offset into the Exchange calldata (minus the 4 byte selector)
+ /// @return value Corresponding 32 byte value stored at `offset` + 4.
+ function loadExchangeData(uint256 offset)
+ internal pure
+ returns (bytes32 value)
+ {
+ value = exchangeCalldataload(offset + 4);
+ return value;
+ }
+
+ /// @dev Extracts the maker address from an order stored in the Exchange calldata
+ /// (which is embedded in `signedExchangeTransaction`).
+ /// @param orderParamIndex Index of the order in the Exchange function's signature.
+ /// @return makerAddress The extracted maker address.
+ function loadMakerAddressFromOrder(uint256 orderParamIndex)
+ internal pure
+ returns (address makerAddress)
+ {
+ uint256 orderOffsetInBytes = orderParamIndex * 32;
+ uint256 orderPtr = uint256(loadExchangeData(orderOffsetInBytes));
+ makerAddress = address(loadExchangeData(orderPtr));
+ return makerAddress;
+ }
+
+ /// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata
+ /// (which is embedded in `signedExchangeTransaction`).
+ /// @param orderArrayParamIndex Index of the order array in the Exchange function's signature
+ /// @return makerAddresses The extracted maker addresses.
+ function loadMakerAddressesFromOrderArray(uint256 orderArrayParamIndex)
+ internal pure
+ returns (address[] makerAddresses)
+ {
+ uint256 orderArrayOffsetInBytes = orderArrayParamIndex * 32;
+ uint256 orderArrayPtr = uint256(loadExchangeData(orderArrayOffsetInBytes));
+ uint256 orderArrayLength = uint256(loadExchangeData(orderArrayPtr));
+ uint256 orderArrayLengthInBytes = orderArrayLength * 32;
+ uint256 orderArrayElementPtr = orderArrayPtr + 32;
+ uint256 orderArrayElementEndPtr = orderArrayElementPtr + orderArrayLengthInBytes;
+ for (uint orderPtrOffset = orderArrayElementPtr; orderPtrOffset < orderArrayElementEndPtr; orderPtrOffset += 32) {
+ uint256 orderPtr = uint256(loadExchangeData(orderPtrOffset));
+ address makerAddress = address(loadExchangeData(orderPtr + orderArrayElementPtr));
+ makerAddresses = makerAddresses.append(makerAddress);
+ }
+ return makerAddresses;
+ }
+}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol
new file mode 100644
index 000000000..4a1bf1fb2
--- /dev/null
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol
@@ -0,0 +1,55 @@
+
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+
+contract IBalanceThresholdFilterCore {
+
+ /// @dev Executes an Exchange transaction iff the maker and taker meet
+ /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR
+ /// the exchange function is a cancellation.
+ /// Supported Exchange functions:
+ /// - batchFillOrders
+ /// - batchFillOrdersNoThrow
+ /// - batchFillOrKillOrders
+ /// - fillOrder
+ /// - fillOrderNoThrow
+ /// - fillOrKillOrder
+ /// - marketBuyOrders
+ /// - marketBuyOrdersNoThrow
+ /// - marketSellOrders
+ /// - marketSellOrdersNoThrow
+ /// - matchOrders
+ /// - cancelOrder
+ /// - batchCancelOrders
+ /// - cancelOrdersUpTo
+ /// Trying to call any other exchange function will throw.
+ /// @param salt Arbitrary number to ensure uniqueness of transaction hash.
+ /// @param signerAddress Address of transaction signer.
+ /// @param signedExchangeTransaction AbiV2 encoded calldata.
+ /// @param signature Proof of signer transaction by signer.
+ function executeTransaction(
+ uint256 salt,
+ address signerAddress,
+ bytes signedExchangeTransaction,
+ bytes signature
+ )
+ external;
+}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol
new file mode 100644
index 000000000..f78f9c2de
--- /dev/null
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol
@@ -0,0 +1,31 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+
+contract IThresholdAsset {
+
+ /// @param _owner The address from which the balance will be retrieved
+ /// @return Balance of owner
+ function balanceOf(address _owner)
+ external
+ view
+ returns (uint256);
+
+}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol
new file mode 100644
index 000000000..074686e8d
--- /dev/null
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol
@@ -0,0 +1,54 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchange.sol";
+import "../interfaces/IThresholdAsset.sol";
+import "../interfaces/IBalanceThresholdFilterCore.sol";
+
+
+contract MBalanceThresholdFilterCore is
+ IBalanceThresholdFilterCore
+{
+
+ // Points to 0x exchange contract
+ // solhint-disable var-name-mixedcase
+ IExchange internal EXCHANGE;
+
+ // The asset that must be held by makers/takers
+ IThresholdAsset internal THRESHOLD_ASSET;
+
+ // The minimum balance of `THRESHOLD_ASSET` that must be held by makers/takers
+ uint256 internal BALANCE_THRESHOLD;
+ // solhint-enable var-name-mixedcase
+
+ // Addresses that hold at least `BALANCE_THRESHOLD` of `THRESHOLD_ASSET`
+ event ValidatedAddresses (
+ address[] addresses
+ );
+
+ /// @dev Constructs an array of addresses to be validated.
+ /// Addresses depend on which Exchange function is to be called
+ /// (defined by `signedExchangeTransaction` above).
+ /// @param signerAddress Address of transaction signer.
+ /// @return addressesToValidate Array of addresses to validate.
+ function getAddressesToValidate(address signerAddress)
+ internal pure
+ returns (address[] memory addressesToValidate);
+}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol
new file mode 100644
index 000000000..40536d820
--- /dev/null
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol
@@ -0,0 +1,56 @@
+
+ /*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+
+contract MExchangeCalldata {
+
+ /// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata,
+ /// which is accessed through `signedExchangeTransaction`.
+ /// @param offset Offset into the Exchange calldata.
+ /// @return value Corresponding 32 byte value stored at `offset`.
+ function exchangeCalldataload(uint256 offset)
+ internal pure
+ returns (bytes32 value);
+
+ /// @dev Convenience function that skips the 4 byte selector when loading
+ /// from the embedded Exchange calldata.
+ /// @param offset Offset into the Exchange calldata (minus the 4 byte selector)
+ /// @return value Corresponding 32 byte value stored at `offset` + 4.
+ function loadExchangeData(uint256 offset)
+ internal pure
+ returns (bytes32 value);
+
+ /// @dev Extracts the maker address from an order stored in the Exchange calldata
+ /// (which is embedded in `signedExchangeTransaction`).
+ /// @param orderParamIndex Index of the order in the Exchange function's signature.
+ /// @return makerAddress The extracted maker address.
+ function loadMakerAddressFromOrder(uint256 orderParamIndex)
+ internal pure
+ returns (address makerAddress);
+
+ /// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata
+ /// (which is embedded in `signedExchangeTransaction`).
+ /// @param orderArrayParamIndex Index of the order array in the Exchange function's signature
+ /// @return makerAddresses The extracted maker addresses.
+ function loadMakerAddressesFromOrderArray(uint256 orderArrayParamIndex)
+ internal pure
+ returns (address[] makerAddresses);
+}
diff --git a/contracts/extensions/contracts/Forwarder/MixinAssets.sol b/contracts/extensions/contracts/Forwarder/MixinAssets.sol
index 3ebf75161..116cdf267 100644
--- a/contracts/extensions/contracts/Forwarder/MixinAssets.sol
+++ b/contracts/extensions/contracts/Forwarder/MixinAssets.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
import "@0x/contracts-utils/contracts/utils/LibBytes/LibBytes.sol";
import "@0x/contracts-utils/contracts/utils/Ownable/Ownable.sol";
diff --git a/contracts/extensions/contracts/Forwarder/MixinExchangeWrapper.sol b/contracts/extensions/contracts/Forwarder/MixinExchangeWrapper.sol
index 210eb14c2..cab26741d 100644
--- a/contracts/extensions/contracts/Forwarder/MixinExchangeWrapper.sol
+++ b/contracts/extensions/contracts/Forwarder/MixinExchangeWrapper.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "./libs/LibConstants.sol";
diff --git a/contracts/extensions/contracts/Forwarder/MixinForwarderCore.sol b/contracts/extensions/contracts/Forwarder/MixinForwarderCore.sol
index bab78d79b..11c0147a5 100644
--- a/contracts/extensions/contracts/Forwarder/MixinForwarderCore.sol
+++ b/contracts/extensions/contracts/Forwarder/MixinForwarderCore.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "./libs/LibConstants.sol";
diff --git a/contracts/extensions/contracts/Forwarder/MixinWeth.sol b/contracts/extensions/contracts/Forwarder/MixinWeth.sol
index 2a281f3ae..25a35f47b 100644
--- a/contracts/extensions/contracts/Forwarder/MixinWeth.sol
+++ b/contracts/extensions/contracts/Forwarder/MixinWeth.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
import "@0x/contracts-libs/contracts/libs/LibMath.sol";
import "./libs/LibConstants.sol";
diff --git a/contracts/extensions/contracts/Forwarder/interfaces/IAssets.sol b/contracts/extensions/contracts/Forwarder/interfaces/IAssets.sol
index 1e034c003..cebfd3706 100644
--- a/contracts/extensions/contracts/Forwarder/interfaces/IAssets.sol
+++ b/contracts/extensions/contracts/Forwarder/interfaces/IAssets.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
contract IAssets {
diff --git a/contracts/extensions/contracts/Forwarder/interfaces/IForwarder.sol b/contracts/extensions/contracts/Forwarder/interfaces/IForwarder.sol
index f5a26e2ba..6ce8a1d31 100644
--- a/contracts/extensions/contracts/Forwarder/interfaces/IForwarder.sol
+++ b/contracts/extensions/contracts/Forwarder/interfaces/IForwarder.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "./IForwarderCore.sol";
diff --git a/contracts/extensions/contracts/Forwarder/interfaces/IForwarderCore.sol b/contracts/extensions/contracts/Forwarder/interfaces/IForwarderCore.sol
index eede20bb8..7f62722e7 100644
--- a/contracts/extensions/contracts/Forwarder/interfaces/IForwarderCore.sol
+++ b/contracts/extensions/contracts/Forwarder/interfaces/IForwarderCore.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "@0x/contracts-libs/contracts/libs/LibOrder.sol";
diff --git a/contracts/extensions/contracts/Forwarder/libs/LibConstants.sol b/contracts/extensions/contracts/Forwarder/libs/LibConstants.sol
index 4a81abf76..0d2f6e36d 100644
--- a/contracts/extensions/contracts/Forwarder/libs/LibConstants.sol
+++ b/contracts/extensions/contracts/Forwarder/libs/LibConstants.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
import "@0x/contracts-utils/contracts/utils/LibBytes/LibBytes.sol";
import "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchange.sol";
diff --git a/contracts/extensions/contracts/Forwarder/libs/LibForwarderErrors.sol b/contracts/extensions/contracts/Forwarder/libs/LibForwarderErrors.sol
index fb3ade1db..7a95b78a0 100644
--- a/contracts/extensions/contracts/Forwarder/libs/LibForwarderErrors.sol
+++ b/contracts/extensions/contracts/Forwarder/libs/LibForwarderErrors.sol
@@ -17,7 +17,7 @@
*/
// solhint-disable
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons.
diff --git a/contracts/extensions/contracts/Forwarder/mixins/MAssets.sol b/contracts/extensions/contracts/Forwarder/mixins/MAssets.sol
index 9e7f80d97..1757b37fb 100644
--- a/contracts/extensions/contracts/Forwarder/mixins/MAssets.sol
+++ b/contracts/extensions/contracts/Forwarder/mixins/MAssets.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
import "../interfaces/IAssets.sol";
diff --git a/contracts/extensions/contracts/Forwarder/mixins/MExchangeWrapper.sol b/contracts/extensions/contracts/Forwarder/mixins/MExchangeWrapper.sol
index d9e71786a..143f888b1 100644
--- a/contracts/extensions/contracts/Forwarder/mixins/MExchangeWrapper.sol
+++ b/contracts/extensions/contracts/Forwarder/mixins/MExchangeWrapper.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "@0x/contracts-libs/contracts/libs/LibOrder.sol";
diff --git a/contracts/extensions/contracts/Forwarder/mixins/MWeth.sol b/contracts/extensions/contracts/Forwarder/mixins/MWeth.sol
index 88e77be4e..15d66942f 100644
--- a/contracts/extensions/contracts/Forwarder/mixins/MWeth.sol
+++ b/contracts/extensions/contracts/Forwarder/mixins/MWeth.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
contract MWeth {
diff --git a/contracts/extensions/contracts/OrderMatcher/MixinAssets.sol b/contracts/extensions/contracts/OrderMatcher/MixinAssets.sol
new file mode 100644
index 000000000..f0f91cfc0
--- /dev/null
+++ b/contracts/extensions/contracts/OrderMatcher/MixinAssets.sol
@@ -0,0 +1,195 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "@0x/contracts-utils/contracts/utils/LibBytes/LibBytes.sol";
+import "@0x/contracts-utils/contracts/utils/Ownable/Ownable.sol";
+import "@0x/contracts-tokens/contracts/tokens/ERC20Token/IERC20Token.sol";
+import "@0x/contracts-tokens/contracts/tokens/ERC721Token/IERC721Token.sol";
+import "./mixins/MAssets.sol";
+import "./libs/LibConstants.sol";
+
+
+contract MixinAssets is
+ MAssets,
+ Ownable,
+ LibConstants
+{
+ using LibBytes for bytes;
+
+ /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to
+ /// function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be
+ /// used to withdraw assets that were accidentally sent to this contract.
+ /// @param assetData Byte array encoded for the respective asset proxy.
+ /// @param amount Amount of asset to withdraw.
+ function withdrawAsset(
+ bytes assetData,
+ uint256 amount
+ )
+ external
+ onlyOwner
+ {
+ transferAssetToSender(assetData, amount);
+ }
+
+ /// @dev Approves or disapproves an AssetProxy to spend asset.
+ /// @param assetData Byte array encoded for the respective asset proxy.
+ /// @param amount Amount of asset to approve for respective proxy.
+ function approveAssetProxy(
+ bytes assetData,
+ uint256 amount
+ )
+ external
+ onlyOwner
+ {
+ bytes4 proxyId = assetData.readBytes4(0);
+
+ if (proxyId == ERC20_DATA_ID) {
+ approveERC20Token(assetData, amount);
+ } else if (proxyId == ERC721_DATA_ID) {
+ approveERC721Token(assetData, amount);
+ } else {
+ revert("UNSUPPORTED_ASSET_PROXY");
+ }
+ }
+
+ /// @dev Transfers given amount of asset to sender.
+ /// @param assetData Byte array encoded for the respective asset proxy.
+ /// @param amount Amount of asset to transfer to sender.
+ function transferAssetToSender(
+ bytes memory assetData,
+ uint256 amount
+ )
+ internal
+ {
+ bytes4 proxyId = assetData.readBytes4(0);
+
+ if (proxyId == ERC20_DATA_ID) {
+ transferERC20Token(assetData, amount);
+ } else if (proxyId == ERC721_DATA_ID) {
+ transferERC721Token(assetData, amount);
+ } else {
+ revert("UNSUPPORTED_ASSET_PROXY");
+ }
+ }
+
+ /// @dev Decodes ERC20 assetData and transfers given amount to sender.
+ /// @param assetData Byte array encoded for the respective asset proxy.
+ /// @param amount Amount of asset to transfer to sender.
+ function transferERC20Token(
+ bytes memory assetData,
+ uint256 amount
+ )
+ internal
+ {
+ // 4 byte id + 12 0 bytes before ABI encoded token address.
+ address token = assetData.readAddress(16);
+
+ // Transfer tokens.
+ // We do a raw call so we can check the success separate
+ // from the return data.
+ bool success = token.call(abi.encodeWithSelector(
+ ERC20_TRANSFER_SELECTOR,
+ msg.sender,
+ amount
+ ));
+ require(
+ success,
+ "TRANSFER_FAILED"
+ );
+
+ // Check return data.
+ // If there is no return data, we assume the token incorrectly
+ // does not return a bool. In this case we expect it to revert
+ // on failure, which was handled above.
+ // If the token does return data, we require that it is a single
+ // value that evaluates to true.
+ assembly {
+ if returndatasize {
+ success := 0
+ if eq(returndatasize, 32) {
+ // First 64 bytes of memory are reserved scratch space
+ returndatacopy(0, 0, 32)
+ success := mload(0)
+ }
+ }
+ }
+ require(
+ success,
+ "TRANSFER_FAILED"
+ );
+ }
+
+ /// @dev Decodes ERC721 assetData and transfers given amount to sender.
+ /// @param assetData Byte array encoded for the respective asset proxy.
+ /// @param amount Amount of asset to transfer to sender.
+ function transferERC721Token(
+ bytes memory assetData,
+ uint256 amount
+ )
+ internal
+ {
+ require(
+ amount == 1,
+ "INVALID_AMOUNT"
+ );
+ // Decode asset data.
+ // 4 byte id + 12 0 bytes before ABI encoded token address.
+ address token = assetData.readAddress(16);
+ // 4 byte id + 32 byte ABI encoded token address before token id.
+ uint256 tokenId = assetData.readUint256(36);
+
+ // Perform transfer.
+ IERC721Token(token).transferFrom(
+ address(this),
+ msg.sender,
+ tokenId
+ );
+ }
+
+ /// @dev Sets approval for ERC20 AssetProxy.
+ /// @param assetData Byte array encoded for the respective asset proxy.
+ /// @param amount Amount of asset to approve for respective proxy.
+ function approveERC20Token(
+ bytes memory assetData,
+ uint256 amount
+ )
+ internal
+ {
+ address token = assetData.readAddress(16);
+ require(
+ IERC20Token(token).approve(ERC20_PROXY_ADDRESS, amount),
+ "APPROVAL_FAILED"
+ );
+ }
+
+ /// @dev Sets approval for ERC721 AssetProxy.
+ /// @param assetData Byte array encoded for the respective asset proxy.
+ /// @param amount Amount of asset to approve for respective proxy.
+ function approveERC721Token(
+ bytes memory assetData,
+ uint256 amount
+ )
+ internal
+ {
+ address token = assetData.readAddress(16);
+ bool approval = amount >= 1;
+ IERC721Token(token).setApprovalForAll(ERC721_PROXY_ADDRESS, approval);
+ }
+}
diff --git a/contracts/extensions/contracts/OrderMatcher/MixinMatchOrders.sol b/contracts/extensions/contracts/OrderMatcher/MixinMatchOrders.sol
new file mode 100644
index 000000000..1787deb59
--- /dev/null
+++ b/contracts/extensions/contracts/OrderMatcher/MixinMatchOrders.sol
@@ -0,0 +1,86 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "./libs/LibConstants.sol";
+import "@0x/contracts-libs/contracts/libs/LibOrder.sol";
+import "@0x/contracts-libs/contracts/libs/LibFillResults.sol";
+import "@0x/contracts-utils/contracts/utils/Ownable/Ownable.sol";
+
+
+contract MixinMatchOrders is
+ Ownable,
+ LibConstants
+{
+ /// @dev Match two complementary orders that have a profitable spread.
+ /// Each order is filled at their respective price point. However, the calculations are
+ /// carried out as though the orders are both being filled at the right order's price point.
+ /// The profit made by the left order is then used to fill the right order as much as possible.
+ /// This results in a spread being taken in terms of both assets. The spread is held within this contract.
+ /// @param leftOrder First order to match.
+ /// @param rightOrder Second order to match.
+ /// @param leftSignature Proof that order was created by the left maker.
+ /// @param rightSignature Proof that order was created by the right maker.
+ function matchOrders(
+ LibOrder.Order memory leftOrder,
+ LibOrder.Order memory rightOrder,
+ bytes memory leftSignature,
+ bytes memory rightSignature
+ )
+ public
+ onlyOwner
+ {
+ // Match orders, maximally filling `leftOrder`
+ LibFillResults.MatchedFillResults memory matchedFillResults = EXCHANGE.matchOrders(
+ leftOrder,
+ rightOrder,
+ leftSignature,
+ rightSignature
+ );
+
+ uint256 leftMakerAssetSpreadAmount = matchedFillResults.leftMakerAssetSpreadAmount;
+ uint256 rightOrderTakerAssetAmount = rightOrder.takerAssetAmount;
+
+ // Do not attempt to call `fillOrder` if no spread was taken or `rightOrder` has been completely filled
+ if (leftMakerAssetSpreadAmount == 0 || matchedFillResults.right.takerAssetFilledAmount == rightOrderTakerAssetAmount) {
+ return;
+ }
+
+ // The `assetData` fields of the `rightOrder` could have been null for the `matchOrders` call. We reassign them before calling `fillOrder`.
+ rightOrder.makerAssetData = leftOrder.takerAssetData;
+ rightOrder.takerAssetData = leftOrder.makerAssetData;
+
+ // Query `rightOrder` info to check if it has been completely filled
+ // We need to make this check in case the `rightOrder` was partially filled before the `matchOrders` call
+ LibOrder.OrderInfo memory orderInfo = EXCHANGE.getOrderInfo(rightOrder);
+
+ // Do not attempt to call `fillOrder` if order has been completely filled
+ if (orderInfo.orderTakerAssetFilledAmount == rightOrderTakerAssetAmount) {
+ return;
+ }
+
+ // We do not need to pass in a signature since it was already validated in the `matchOrders` call
+ EXCHANGE.fillOrder(
+ rightOrder,
+ leftMakerAssetSpreadAmount,
+ ""
+ );
+ }
+}
diff --git a/contracts/extensions/contracts/OrderMatcher/OrderMatcher.sol b/contracts/extensions/contracts/OrderMatcher/OrderMatcher.sol
new file mode 100644
index 000000000..4879b7bca
--- /dev/null
+++ b/contracts/extensions/contracts/OrderMatcher/OrderMatcher.sol
@@ -0,0 +1,38 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity 0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "@0x/contracts-utils/contracts/utils/Ownable/Ownable.sol";
+import "./libs/LibConstants.sol";
+import "./MixinMatchOrders.sol";
+import "./MixinAssets.sol";
+
+
+// solhint-disable no-empty-blocks
+contract OrderMatcher is
+ MixinMatchOrders,
+ MixinAssets
+{
+ constructor (address _exchange)
+ public
+ LibConstants(_exchange)
+ Ownable()
+ {}
+}
diff --git a/contracts/extensions/contracts/OrderMatcher/interfaces/IAssets.sol b/contracts/extensions/contracts/OrderMatcher/interfaces/IAssets.sol
new file mode 100644
index 000000000..9fcc0023a
--- /dev/null
+++ b/contracts/extensions/contracts/OrderMatcher/interfaces/IAssets.sol
@@ -0,0 +1,43 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+
+contract IAssets {
+
+ /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to
+ /// function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be
+ /// used to withdraw assets that were accidentally sent to this contract.
+ /// @param assetData Byte array encoded for the respective asset proxy.
+ /// @param amount Amount of asset to withdraw.
+ function withdrawAsset(
+ bytes assetData,
+ uint256 amount
+ )
+ external;
+
+ /// @dev Approves or disapproves an AssetProxy to spend asset.
+ /// @param assetData Byte array encoded for the respective asset proxy.
+ /// @param amount Amount of asset to approve for respective proxy.
+ function approveAssetProxy(
+ bytes assetData,
+ uint256 amount
+ )
+ external;
+}
diff --git a/contracts/extensions/contracts/OrderMatcher/interfaces/IMatchOrders.sol b/contracts/extensions/contracts/OrderMatcher/interfaces/IMatchOrders.sol
new file mode 100644
index 000000000..1443c73b9
--- /dev/null
+++ b/contracts/extensions/contracts/OrderMatcher/interfaces/IMatchOrders.sol
@@ -0,0 +1,43 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "@0x/contracts-libs/contracts/libs/LibOrder.sol";
+
+
+contract IMatchOrders {
+
+ /// @dev Match two complementary orders that have a profitable spread.
+ /// Each order is filled at their respective price point. However, the calculations are
+ /// carried out as though the orders are both being filled at the right order's price point.
+ /// The profit made by the left order is then used to fill the right order as much as possible.
+ /// This results in a spread being taken in terms of both assets. The spread is held within this contract.
+ /// @param leftOrder First order to match.
+ /// @param rightOrder Second order to match.
+ /// @param leftSignature Proof that order was created by the left maker.
+ /// @param rightSignature Proof that order was created by the right maker.
+ function matchOrders(
+ LibOrder.Order memory leftOrder,
+ LibOrder.Order memory rightOrder,
+ bytes memory leftSignature,
+ bytes memory rightSignature
+ )
+ public;
+}
diff --git a/contracts/extensions/contracts/OrderMatcher/interfaces/IOrderMatcher.sol b/contracts/extensions/contracts/OrderMatcher/interfaces/IOrderMatcher.sol
new file mode 100644
index 000000000..75f26dca6
--- /dev/null
+++ b/contracts/extensions/contracts/OrderMatcher/interfaces/IOrderMatcher.sol
@@ -0,0 +1,31 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "@0x/contract-utils/contracts/utils/Ownable/IOwnable.sol";
+import "./IMatchOrders.sol";
+import "./IAssets.sol";
+
+
+// solhint-disable no-empty-blocks
+contract IOrderMatcher is
+ IOwnable,
+ IMatchOrders,
+ IAssets
+{}
diff --git a/contracts/extensions/contracts/OrderMatcher/libs/LibConstants.sol b/contracts/extensions/contracts/OrderMatcher/libs/LibConstants.sol
new file mode 100644
index 000000000..c1a86a9c7
--- /dev/null
+++ b/contracts/extensions/contracts/OrderMatcher/libs/LibConstants.sol
@@ -0,0 +1,56 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchange.sol";
+
+
+contract LibConstants {
+
+ // bytes4(keccak256("transfer(address,uint256)"))
+ bytes4 constant internal ERC20_TRANSFER_SELECTOR = 0xa9059cbb;
+ // bytes4(keccak256("ERC20Token(address)"))
+ bytes4 constant internal ERC20_DATA_ID = 0xf47261b0;
+ // bytes4(keccak256("ERC721Token(address,uint256)"))
+ bytes4 constant internal ERC721_DATA_ID = 0x02571792;
+
+ // solhint-disable var-name-mixedcase
+ IExchange internal EXCHANGE;
+ address internal ERC20_PROXY_ADDRESS;
+ address internal ERC721_PROXY_ADDRESS;
+ // solhint-enable var-name-mixedcase
+
+ constructor (address _exchange)
+ public
+ {
+ EXCHANGE = IExchange(_exchange);
+
+ ERC20_PROXY_ADDRESS = EXCHANGE.getAssetProxy(ERC20_DATA_ID);
+ require(
+ ERC20_PROXY_ADDRESS != address(0),
+ "UNREGISTERED_ASSET_PROXY"
+ );
+
+ ERC721_PROXY_ADDRESS = EXCHANGE.getAssetProxy(ERC721_DATA_ID);
+ require(
+ ERC721_PROXY_ADDRESS != address(0),
+ "UNREGISTERED_ASSET_PROXY"
+ );
+ }
+}
diff --git a/contracts/extensions/contracts/OrderMatcher/mixins/MAssets.sol b/contracts/extensions/contracts/OrderMatcher/mixins/MAssets.sol
new file mode 100644
index 000000000..6ea7a3290
--- /dev/null
+++ b/contracts/extensions/contracts/OrderMatcher/mixins/MAssets.sol
@@ -0,0 +1,71 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "../interfaces/IAssets.sol";
+
+
+contract MAssets is
+ IAssets
+{
+ /// @dev Transfers given amount of asset to sender.
+ /// @param assetData Byte array encoded for the respective asset proxy.
+ /// @param amount Amount of asset to transfer to sender.
+ function transferAssetToSender(
+ bytes memory assetData,
+ uint256 amount
+ )
+ internal;
+
+ /// @dev Decodes ERC20 assetData and transfers given amount to sender.
+ /// @param assetData Byte array encoded for the respective asset proxy.
+ /// @param amount Amount of asset to transfer to sender.
+ function transferERC20Token(
+ bytes memory assetData,
+ uint256 amount
+ )
+ internal;
+
+ /// @dev Decodes ERC721 assetData and transfers given amount to sender.
+ /// @param assetData Byte array encoded for the respective asset proxy.
+ /// @param amount Amount of asset to transfer to sender.
+ function transferERC721Token(
+ bytes memory assetData,
+ uint256 amount
+ )
+ internal;
+
+ /// @dev Sets approval for ERC20 AssetProxy.
+ /// @param assetData Byte array encoded for the respective asset proxy.
+ /// @param amount Amount of asset to approve for respective proxy.
+ function approveERC20Token(
+ bytes memory assetData,
+ uint256 amount
+ )
+ internal;
+
+ /// @dev Sets approval for ERC721 AssetProxy.
+ /// @param assetData Byte array encoded for the respective asset proxy.
+ /// @param amount Amount of asset to approve for respective proxy.
+ function approveERC721Token(
+ bytes memory assetData,
+ uint256 amount
+ )
+ internal;
+}
diff --git a/contracts/extensions/contracts/OrderValidator/OrderValidator.sol b/contracts/extensions/contracts/OrderValidator/OrderValidator.sol
new file mode 100644
index 000000000..33dd1326c
--- /dev/null
+++ b/contracts/extensions/contracts/OrderValidator/OrderValidator.sol
@@ -0,0 +1,218 @@
+/*
+
+ 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 "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchange.sol";
+import "@0x/contracts-libs/contracts/libs/LibOrder.sol";
+import "@0x/contracts-tokens/contracts/tokens/ERC20Token/IERC20Token.sol";
+import "@0x/contracts-tokens/contracts/tokens/ERC721Token/IERC721Token.sol";
+import "@0x/contracts-utils/contracts/utils/LibBytes/LibBytes.sol";
+
+
+contract OrderValidator {
+
+ using LibBytes for bytes;
+
+ bytes4 constant internal ERC20_DATA_ID = bytes4(keccak256("ERC20Token(address)"));
+ bytes4 constant internal ERC721_DATA_ID = bytes4(keccak256("ERC721Token(address,uint256)"));
+
+ struct TraderInfo {
+ uint256 makerBalance; // Maker's balance of makerAsset
+ uint256 makerAllowance; // Maker's allowance to corresponding AssetProxy
+ uint256 takerBalance; // Taker's balance of takerAsset
+ uint256 takerAllowance; // Taker's allowance to corresponding AssetProxy
+ uint256 makerZrxBalance; // Maker's balance of ZRX
+ uint256 makerZrxAllowance; // Maker's allowance of ZRX to ERC20Proxy
+ uint256 takerZrxBalance; // Taker's balance of ZRX
+ uint256 takerZrxAllowance; // Taker's allowance of ZRX to ERC20Proxy
+ }
+
+ // solhint-disable var-name-mixedcase
+ IExchange internal EXCHANGE;
+ bytes internal ZRX_ASSET_DATA;
+ // solhint-enable var-name-mixedcase
+
+ constructor (address _exchange, bytes memory _zrxAssetData)
+ public
+ {
+ EXCHANGE = IExchange(_exchange);
+ ZRX_ASSET_DATA = _zrxAssetData;
+ }
+
+ /// @dev Fetches information for order and maker/taker of order.
+ /// @param order The order structure.
+ /// @param takerAddress Address that will be filling the order.
+ /// @return OrderInfo and TraderInfo instances for given order.
+ function getOrderAndTraderInfo(LibOrder.Order memory order, address takerAddress)
+ public
+ view
+ returns (LibOrder.OrderInfo memory orderInfo, TraderInfo memory traderInfo)
+ {
+ orderInfo = EXCHANGE.getOrderInfo(order);
+ traderInfo = getTraderInfo(order, takerAddress);
+ return (orderInfo, traderInfo);
+ }
+
+ /// @dev Fetches information for all passed in orders and the makers/takers of each order.
+ /// @param orders Array of order specifications.
+ /// @param takerAddresses Array of taker addresses corresponding to each order.
+ /// @return Arrays of OrderInfo and TraderInfo instances that correspond to each order.
+ function getOrdersAndTradersInfo(LibOrder.Order[] memory orders, address[] memory takerAddresses)
+ public
+ view
+ returns (LibOrder.OrderInfo[] memory ordersInfo, TraderInfo[] memory tradersInfo)
+ {
+ ordersInfo = EXCHANGE.getOrdersInfo(orders);
+ tradersInfo = getTradersInfo(orders, takerAddresses);
+ return (ordersInfo, tradersInfo);
+ }
+
+ /// @dev Fetches balance and allowances for maker and taker of order.
+ /// @param order The order structure.
+ /// @param takerAddress Address that will be filling the order.
+ /// @return Balances and allowances of maker and taker of order.
+ function getTraderInfo(LibOrder.Order memory order, address takerAddress)
+ public
+ view
+ returns (TraderInfo memory traderInfo)
+ {
+ (traderInfo.makerBalance, traderInfo.makerAllowance) = getBalanceAndAllowance(order.makerAddress, order.makerAssetData);
+ (traderInfo.takerBalance, traderInfo.takerAllowance) = getBalanceAndAllowance(takerAddress, order.takerAssetData);
+ bytes memory zrxAssetData = ZRX_ASSET_DATA;
+ (traderInfo.makerZrxBalance, traderInfo.makerZrxAllowance) = getBalanceAndAllowance(order.makerAddress, zrxAssetData);
+ (traderInfo.takerZrxBalance, traderInfo.takerZrxAllowance) = getBalanceAndAllowance(takerAddress, zrxAssetData);
+ return traderInfo;
+ }
+
+ /// @dev Fetches balances and allowances of maker and taker for each provided order.
+ /// @param orders Array of order specifications.
+ /// @param takerAddresses Array of taker addresses corresponding to each order.
+ /// @return Array of balances and allowances for maker and taker of each order.
+ function getTradersInfo(LibOrder.Order[] memory orders, address[] memory takerAddresses)
+ public
+ view
+ returns (TraderInfo[] memory)
+ {
+ uint256 ordersLength = orders.length;
+ TraderInfo[] memory tradersInfo = new TraderInfo[](ordersLength);
+ for (uint256 i = 0; i != ordersLength; i++) {
+ tradersInfo[i] = getTraderInfo(orders[i], takerAddresses[i]);
+ }
+ return tradersInfo;
+ }
+
+ /// @dev Fetches token balances and allowances of an address to given assetProxy. Supports ERC20 and ERC721.
+ /// @param target Address to fetch balances and allowances of.
+ /// @param assetData Encoded data that can be decoded by a specified proxy contract when transferring asset.
+ /// @return Balance of asset and allowance set to given proxy of asset.
+ /// For ERC721 tokens, these values will always be 1 or 0.
+ function getBalanceAndAllowance(address target, bytes memory assetData)
+ public
+ view
+ returns (uint256 balance, uint256 allowance)
+ {
+ bytes4 assetProxyId = assetData.readBytes4(0);
+ address token = assetData.readAddress(16);
+ address assetProxy = EXCHANGE.getAssetProxy(assetProxyId);
+
+ if (assetProxyId == ERC20_DATA_ID) {
+ // Query balance
+ balance = IERC20Token(token).balanceOf(target);
+
+ // Query allowance
+ allowance = IERC20Token(token).allowance(target, assetProxy);
+ } else if (assetProxyId == ERC721_DATA_ID) {
+ uint256 tokenId = assetData.readUint256(36);
+
+ // Query owner of tokenId
+ address owner = getERC721TokenOwner(token, tokenId);
+
+ // Set balance to 1 if tokenId is owned by target
+ balance = target == owner ? 1 : 0;
+
+ // Check if ERC721Proxy is approved to spend tokenId
+ bool isApproved = IERC721Token(token).isApprovedForAll(target, assetProxy);
+
+ // Set alowance to 1 if ERC721Proxy is approved to spend tokenId
+ allowance = isApproved ? 1 : 0;
+ } else {
+ revert("UNSUPPORTED_ASSET_PROXY");
+ }
+ return (balance, allowance);
+ }
+
+ /// @dev Fetches token balances and allowances of an address for each given assetProxy. Supports ERC20 and ERC721.
+ /// @param target Address to fetch balances and allowances of.
+ /// @param assetData Array of encoded byte arrays that can be decoded by a specified proxy contract when transferring asset.
+ /// @return Balances and allowances of assets.
+ /// For ERC721 tokens, these values will always be 1 or 0.
+ function getBalancesAndAllowances(address target, bytes[] memory assetData)
+ public
+ view
+ returns (uint256[] memory, uint256[] memory)
+ {
+ uint256 length = assetData.length;
+ uint256[] memory balances = new uint256[](length);
+ uint256[] memory allowances = new uint256[](length);
+ for (uint256 i = 0; i != length; i++) {
+ (balances[i], allowances[i]) = getBalanceAndAllowance(target, assetData[i]);
+ }
+ return (balances, allowances);
+ }
+
+ /// @dev Calls `token.ownerOf(tokenId)`, but returns a null owner instead of reverting on an unowned token.
+ /// @param token Address of ERC721 token.
+ /// @param tokenId The identifier for the specific NFT.
+ /// @return Owner of tokenId or null address if unowned.
+ function getERC721TokenOwner(address token, uint256 tokenId)
+ public
+ view
+ returns (address owner)
+ {
+ assembly {
+ // load free memory pointer
+ let cdStart := mload(64)
+
+ // bytes4(keccak256(ownerOf(uint256))) = 0x6352211e
+ mstore(cdStart, 0x6352211e00000000000000000000000000000000000000000000000000000000)
+ mstore(add(cdStart, 4), tokenId)
+
+ // staticcall `ownerOf(tokenId)`
+ // `ownerOf` will revert if tokenId is not owned
+ let success := staticcall(
+ gas, // forward all gas
+ token, // call token contract
+ cdStart, // start of calldata
+ 36, // length of input is 36 bytes
+ cdStart, // write output over input
+ 32 // size of output is 32 bytes
+ )
+
+ // Success implies that tokenId is owned
+ // Copy owner from return data if successful
+ if success {
+ owner := mload(cdStart)
+ }
+ }
+
+ // Owner initialized to address(0), no need to modify if call is unsuccessful
+ return owner;
+ }
+}
diff --git a/contracts/extensions/package.json b/contracts/extensions/package.json
index 938e1138c..069b5d2d5 100644
--- a/contracts/extensions/package.json
+++ b/contracts/extensions/package.json
@@ -19,7 +19,8 @@
"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",
- "compile": "sol-compiler --contracts-dir contracts",
+ "compile": "sol-compiler",
+ "watch": "sol-compiler -w",
"clean": "shx rm -rf lib generated-artifacts generated-wrappers",
"generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../../node_modules/@0x/abi-gen-templates/contract.handlebars --partials '../../node_modules/@0x/abi-gen-templates/partials/**/*.handlebars' --output generated-wrappers --backend ethers",
"lint": "tslint --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts",
@@ -31,7 +32,7 @@
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
},
"config": {
- "abis": "generated-artifacts/@(DutchAuction|Forwarder).json"
+ "abis": "generated-artifacts/@(BalanceThresholdFilter|DutchAuction|Forwarder|OrderMatcher|OrderValidator).json"
},
"repository": {
"type": "git",
diff --git a/contracts/extensions/src/artifacts/index.ts b/contracts/extensions/src/artifacts/index.ts
index 7588178f0..329d0f3f3 100644
--- a/contracts/extensions/src/artifacts/index.ts
+++ b/contracts/extensions/src/artifacts/index.ts
@@ -1,9 +1,15 @@
import { ContractArtifact } from 'ethereum-types';
+import * as BalanceThresholdFilter from '../../generated-artifacts/BalanceThresholdFilter.json';
import * as DutchAuction from '../../generated-artifacts/DutchAuction.json';
import * as Forwarder from '../../generated-artifacts/Forwarder.json';
+import * as OrderMatcher from '../../generated-artifacts/OrderMatcher.json';
+import * as OrderValidator from '../../generated-artifacts/OrderValidator.json';
export const artifacts = {
+ BalanceThresholdFilter: BalanceThresholdFilter as ContractArtifact,
DutchAuction: DutchAuction as ContractArtifact,
Forwarder: Forwarder as ContractArtifact,
+ OrderMatcher: OrderMatcher as ContractArtifact,
+ OrderValidator: OrderValidator as ContractArtifact,
};
diff --git a/contracts/extensions/src/wrappers/index.ts b/contracts/extensions/src/wrappers/index.ts
index 90880e37f..65aec3ccd 100644
--- a/contracts/extensions/src/wrappers/index.ts
+++ b/contracts/extensions/src/wrappers/index.ts
@@ -1,2 +1,5 @@
+export * from '../../generated-wrappers/balance_threshold_filter';
export * from '../../generated-wrappers/dutch_auction';
export * from '../../generated-wrappers/forwarder';
+export * from '../../generated-wrappers/order_matcher';
+export * from '../../generated-wrappers/order_validator';
diff --git a/contracts/extensions/test/extensions/balance_threshold_filter.ts b/contracts/extensions/test/extensions/balance_threshold_filter.ts
new file mode 100644
index 000000000..07199d60b
--- /dev/null
+++ b/contracts/extensions/test/extensions/balance_threshold_filter.ts
@@ -0,0 +1,1644 @@
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { assetDataUtils } from '@0x/order-utils';
+import { Order, RevertReason, SignedOrder } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import * as chai from 'chai';
+import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import {
+ artifacts as protocolArtifacts,
+ ERC20Wrapper,
+ ERC721Wrapper,
+ ExchangeContract,
+ ExchangeWrapper,
+} from '@0x/contracts-protocol';
+import {
+ chaiSetup,
+ constants,
+ ContractName,
+ ERC20BalancesByOwner,
+ expectTransactionFailedAsync,
+ OrderFactory,
+ OrderStatus,
+ provider,
+ TransactionFactory,
+ txDefaults,
+ web3Wrapper,
+} from '@0x/contracts-test-utils';
+import { DummyERC20TokenContract } from '@0x/contracts-tokens';
+
+import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter';
+import { artifacts } from '../../src/artifacts';
+import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+const DECIMALS_DEFAULT = 18;
+
+interface ValidatedAddressesLog {
+ args: { addresses: string[] };
+}
+
+describe(ContractName.BalanceThresholdFilter, () => {
+ const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT);
+ const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT);
+ const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT);
+
+ let validMakerAddress: string;
+ let validMakerAddress2: string;
+ let owner: string;
+ let validTakerAddress: string;
+ let feeRecipientAddress: string;
+ let invalidAddress: string;
+ let defaultMakerAssetAddress: string;
+ let defaultTakerAssetAddress: string;
+ let zrxAssetData: string;
+ let zrxToken: DummyERC20TokenContract;
+ let exchangeInstance: ExchangeContract;
+ let exchangeWrapper: ExchangeWrapper;
+
+ let orderFactory: OrderFactory;
+ let orderFactory2: OrderFactory;
+ let invalidOrderFactory: OrderFactory;
+ let erc20Wrapper: ERC20Wrapper;
+ let erc20Balances: ERC20BalancesByOwner;
+ let erc20TakerBalanceThresholdWrapper: BalanceThresholdWrapper;
+ let erc721TakerBalanceThresholdWrapper: BalanceThresholdWrapper;
+ let erc721MakerBalanceThresholdWrapper: BalanceThresholdWrapper;
+ let erc721NonValidBalanceThresholdWrapper: BalanceThresholdWrapper;
+
+ let defaultOrderParams: Partial<Order>;
+ let validSignedOrder: SignedOrder;
+ let validSignedOrder2: SignedOrder;
+
+ let erc721BalanceThresholdFilterInstance: BalanceThresholdFilterContract;
+ let erc20BalanceThresholdFilterInstance: BalanceThresholdFilterContract;
+
+ const assertValidatedAddressesLog = async (
+ txReceipt: TransactionReceiptWithDecodedLogs,
+ expectedValidatedAddresses: string[],
+ ) => {
+ expect(txReceipt.logs.length).to.be.gte(1);
+ const validatedAddressesLog = (txReceipt.logs[0] as any) as ValidatedAddressesLog;
+ const validatedAddresses = validatedAddressesLog.args.addresses;
+ // @HACK-hysz: Nested addresses are not translated to lower-case but this will change once
+ // the new ABI Encoder/Decoder is used by the contract templates.
+ const validatedAddressesNormalized: string[] = [];
+ _.each(validatedAddresses, address => {
+ const normalizedAddress = _.toLower(address);
+ validatedAddressesNormalized.push(normalizedAddress);
+ });
+ expect(validatedAddressesNormalized).to.be.deep.equal(expectedValidatedAddresses);
+ };
+
+ before(async () => {
+ // Create accounts
+ await blockchainLifecycle.startAsync();
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ const usedAddresses = ([
+ owner,
+ validMakerAddress,
+ validMakerAddress2,
+ validTakerAddress,
+ feeRecipientAddress,
+ invalidAddress,
+ ] = accounts);
+ const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(validTakerAddress)];
+ const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(validMakerAddress)];
+ const secondMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(validMakerAddress2)];
+ const invalidAddressPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(invalidAddress)];
+ // Create wrappers
+ erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+ const validAddresses = _.cloneDeepWith(usedAddresses);
+ _.remove(validAddresses, (address: string) => {
+ return address === invalidAddress;
+ });
+ const erc721Wrapper = new ERC721Wrapper(provider, validAddresses, owner);
+ // Deploy ERC20 tokens
+ const numDummyErc20ToDeploy = 4;
+ let erc20TokenA: DummyERC20TokenContract;
+ let erc20TokenB: DummyERC20TokenContract;
+ let erc20BalanceThresholdAsset: DummyERC20TokenContract;
+ [erc20TokenA, erc20TokenB, zrxToken, erc20BalanceThresholdAsset] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
+ defaultMakerAssetAddress = erc20TokenA.address;
+ defaultTakerAssetAddress = erc20TokenB.address;
+ zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
+ // Create proxies
+ const erc20Proxy = await erc20Wrapper.deployProxyAsync();
+ await erc20Wrapper.setBalancesAndAllowancesAsync();
+ // Deploy Exchange contract
+ exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync(
+ protocolArtifacts.Exchange,
+ provider,
+ txDefaults,
+ zrxAssetData,
+ );
+ exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider);
+ // Register proxies
+ await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner);
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, {
+ from: owner,
+ });
+ // Deploy Balance Threshold Filters
+ // One uses an ERC721 token as its balance threshold asset; the other uses an ERC20
+ const erc721alanceThreshold = new BigNumber(1);
+ await erc721Wrapper.deployProxyAsync();
+ const [erc721BalanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync();
+ await erc721Wrapper.setBalancesAndAllowancesAsync();
+ erc721BalanceThresholdFilterInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync(
+ artifacts.BalanceThresholdFilter,
+ provider,
+ txDefaults,
+ exchangeInstance.address,
+ erc721BalanceThresholdAsset.address,
+ erc721alanceThreshold,
+ );
+ const erc20BalanceThreshold = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 10);
+ erc20BalanceThresholdFilterInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync(
+ artifacts.BalanceThresholdFilter,
+ provider,
+ txDefaults,
+ exchangeInstance.address,
+ erc20BalanceThresholdAsset.address,
+ erc20BalanceThreshold,
+ );
+ // Default order parameters
+ defaultOrderParams = {
+ exchangeAddress: exchangeInstance.address,
+ feeRecipientAddress,
+ makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
+ takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress),
+ makerAssetAmount,
+ takerAssetAmount,
+ makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT),
+ takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT),
+ senderAddress: erc721BalanceThresholdFilterInstance.address,
+ };
+ // Create two order factories with valid makers (who meet the threshold balance), and
+ // one factory for an invalid address (that does not meet the threshold balance)
+ // Valid order factory #1
+ const defaultOrderParams1 = {
+ makerAddress: validMakerAddress,
+ ...defaultOrderParams,
+ };
+ orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams1);
+ // Valid order factory #2
+ const defaultOrderParams2 = {
+ makerAddress: validMakerAddress2,
+ ...defaultOrderParams,
+ };
+ orderFactory2 = new OrderFactory(secondMakerPrivateKey, defaultOrderParams2);
+ // Invalid order factory
+ const defaultNonValidOrderParams = {
+ makerAddress: invalidAddress,
+ ...defaultOrderParams,
+ };
+ invalidOrderFactory = new OrderFactory(invalidAddressPrivateKey, defaultNonValidOrderParams);
+ // Create Balance Thresold Wrappers
+ erc20TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(
+ erc20BalanceThresholdFilterInstance,
+ exchangeInstance,
+ new TransactionFactory(takerPrivateKey, exchangeInstance.address),
+ provider,
+ );
+ erc721TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(
+ erc721BalanceThresholdFilterInstance,
+ exchangeInstance,
+ new TransactionFactory(takerPrivateKey, exchangeInstance.address),
+ provider,
+ );
+ erc721MakerBalanceThresholdWrapper = new BalanceThresholdWrapper(
+ erc721BalanceThresholdFilterInstance,
+ exchangeInstance,
+ new TransactionFactory(makerPrivateKey, exchangeInstance.address),
+ provider,
+ );
+ erc721NonValidBalanceThresholdWrapper = new BalanceThresholdWrapper(
+ erc721BalanceThresholdFilterInstance,
+ exchangeInstance,
+ new TransactionFactory(invalidAddressPrivateKey, exchangeInstance.address),
+ provider,
+ );
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ describe('General Sanity Checks', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both maker/taker when both maker and taker exceed the balance threshold of an ERC20 token', async () => {
+ const validSignedOrderERC20Sender = await orderFactory.newSignedOrderAsync({
+ ...defaultOrderParams,
+ makerAddress: validMakerAddress,
+ senderAddress: erc20TakerBalanceThresholdWrapper.getBalanceThresholdAddress(),
+ });
+ // Execute a valid fill
+ const txReceipt = await erc20TakerBalanceThresholdWrapper.fillOrderAsync(
+ validSignedOrderERC20Sender,
+ validTakerAddress,
+ { takerAssetFillAmount },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [validSignedOrder.makerAddress, validTakerAddress];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid = validSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
+ );
+ });
+ it('should revert if the Exchange transaction function is not supported', async () => {
+ // Create signed order without the fillOrder function selector
+ const salt = new BigNumber(0);
+ const badSelectorHex = '0x00000000';
+ const signatureHex = '0x';
+ // Call valid forwarder
+ return expectTransactionFailedAsync(
+ erc721BalanceThresholdFilterInstance.executeTransaction.sendTransactionAsync(
+ salt,
+ validTakerAddress,
+ badSelectorHex,
+ signatureHex,
+ ),
+ RevertReason.InvalidOrBlockedExchangeSelector,
+ );
+ });
+ it('should revert if senderAddress is not set to the valid forwarding contract', async () => {
+ // Create signed order with incorrect senderAddress
+ const notBalanceThresholdFilterAddress = zrxToken.address;
+ const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: notBalanceThresholdFilterAddress,
+ });
+ // Call valid forwarder
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.fillOrderAsync(signedOrderWithBadSenderAddress, validTakerAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.FailedExecution,
+ );
+ });
+ });
+
+ describe('batchFillOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const orders = [validSignedOrder, validSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(orders, validTakerAddress, {
+ takerAssetFillAmounts,
+ });
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ validSignedOrder.makerAddress,
+ validSignedOrder2.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2);
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid = validSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount)
+ .times(2);
+ // Maker #1
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ // Maker #2
+ expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid),
+ );
+ // Taker
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-valid maker address
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: invalidAddress,
+ });
+ const orders = [validSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(orders, validTakerAddress, {
+ takerAssetFillAmounts,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [validSignedOrder, validSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.batchFillOrdersAsync(orders, invalidAddress, {
+ takerAssetFillAmounts,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('batchFillOrdersNoThrow', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const orders = [validSignedOrder, validSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(
+ orders,
+ validTakerAddress,
+ {
+ takerAssetFillAmounts,
+ // 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: 600000,
+ },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ validSignedOrder.makerAddress,
+ validSignedOrder2.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2);
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid = validSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount)
+ .times(2);
+ // Maker #1
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ // Maker #2
+ expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid),
+ );
+ // Taker
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-valid maker address
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: invalidAddress,
+ });
+ const orders = [validSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, validTakerAddress, {
+ takerAssetFillAmounts,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [validSignedOrder, validSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, invalidAddress, {
+ takerAssetFillAmounts,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('batchFillOrKillOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const orders = [validSignedOrder, validSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(
+ orders,
+ validTakerAddress,
+ { takerAssetFillAmounts },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ validSignedOrder.makerAddress,
+ validSignedOrder2.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2);
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid = validSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount)
+ .times(2);
+ // Maker #1
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ // Maker #2
+ expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid),
+ );
+ // Taker
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-valid maker address
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: invalidAddress,
+ });
+ const orders = [validSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, validTakerAddress, {
+ takerAssetFillAmounts,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [validSignedOrder, validSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, invalidAddress, {
+ takerAssetFillAmounts,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if one takerAssetFillAmount is not fully filled', async () => {
+ const tooBigTakerAssetFillAmount = validSignedOrder.takerAssetAmount.times(2);
+ const orders = [validSignedOrder, validSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount];
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, validTakerAddress, {
+ takerAssetFillAmounts,
+ }),
+ RevertReason.FailedExecution,
+ );
+ });
+ });
+
+ describe('fillOrder', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderAsync(
+ validSignedOrder,
+ validTakerAddress,
+ { takerAssetFillAmount },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [validSignedOrder.makerAddress, validTakerAddress];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid = validSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
+ );
+ });
+ it('should revert if maker does not meet the balance threshold', async () => {
+ // Create signed order with non-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721BalanceThresholdFilterInstance.address,
+ makerAddress: invalidAddress,
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.fillOrderAsync(signedOrderWithBadMakerAddress, validTakerAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.fillOrderAsync(validSignedOrder, invalidAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('fillOrderNoThrow', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync(
+ validSignedOrder,
+ validTakerAddress,
+ {
+ takerAssetFillAmount,
+ // 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: 600000,
+ },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [validSignedOrder.makerAddress, validTakerAddress];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid = validSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
+ );
+ });
+ it('should revert if maker does not meet the balance threshold', async () => {
+ // Create signed order with non-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721BalanceThresholdFilterInstance.address,
+ makerAddress: invalidAddress,
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync(
+ signedOrderWithBadMakerAddress,
+ validTakerAddress,
+ { takerAssetFillAmount },
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.fillOrderNoThrowAsync(validSignedOrder, invalidAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('fillOrKillOrder', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const takerAssetFillAmount_ = validSignedOrder.takerAssetAmount;
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(
+ validSignedOrder,
+ validTakerAddress,
+ { takerAssetFillAmount: takerAssetFillAmount_ },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [validSignedOrder.makerAddress, validTakerAddress];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount = takerAssetFillAmount_
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid = validSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
+ );
+ });
+ it('should revert if maker does not meet the balance threshold', async () => {
+ // Create signed order with non-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721BalanceThresholdFilterInstance.address,
+ makerAddress: invalidAddress,
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(
+ signedOrderWithBadMakerAddress,
+ validTakerAddress,
+ { takerAssetFillAmount },
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.fillOrKillOrderAsync(validSignedOrder, invalidAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if takerAssetFillAmount is not fully filled', async () => {
+ const tooBigTakerAssetFillAmount = validSignedOrder.takerAssetAmount.times(2);
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(validSignedOrder, validTakerAddress, {
+ takerAssetFillAmount: tooBigTakerAssetFillAmount,
+ }),
+ RevertReason.FailedExecution,
+ );
+ });
+ });
+
+ describe('marketSellOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const orders = [validSignedOrder, validSignedOrder2];
+ const cumulativeTakerAssetFillAmount = validSignedOrder.takerAssetAmount.plus(takerAssetFillAmount);
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync(
+ orders,
+ validTakerAddress,
+ { takerAssetFillAmount: cumulativeTakerAssetFillAmount },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ validSignedOrder.makerAddress,
+ validSignedOrder2.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount2 = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid2 = validSignedOrder2.makerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid2 = validSignedOrder2.takerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee.plus(takerFeePaid2);
+ const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2);
+ // Maker #1
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(validSignedOrder.makerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(validSignedOrder.takerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee),
+ );
+ // Maker #2
+ expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2),
+ );
+ expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid2),
+ );
+ // Taker
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address]
+ .add(validSignedOrder.makerFee)
+ .add(makerFeePaid2)
+ .add(takerFeePaid),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: invalidAddress,
+ });
+ const orders = [validSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync(orders, validTakerAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [validSignedOrder, validSignedOrder2];
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.marketSellOrdersAsync(orders, invalidAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('marketSellOrdersNoThrow', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const orders = [validSignedOrder, validSignedOrder2];
+ const cumulativeTakerAssetFillAmount = validSignedOrder.takerAssetAmount.plus(takerAssetFillAmount);
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(
+ orders,
+ validTakerAddress,
+ {
+ takerAssetFillAmount: cumulativeTakerAssetFillAmount,
+ // 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: 600000,
+ },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ validSignedOrder.makerAddress,
+ validSignedOrder2.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount2 = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid2 = validSignedOrder2.makerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid2 = validSignedOrder2.takerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee.plus(takerFeePaid2);
+ const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2);
+ // Maker #1
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(validSignedOrder.makerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(validSignedOrder.takerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee),
+ );
+ // Maker #2
+ expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2),
+ );
+ expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid2),
+ );
+ // Taker
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address]
+ .add(validSignedOrder.makerFee)
+ .add(makerFeePaid2)
+ .add(takerFeePaid),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: invalidAddress,
+ });
+ const orders = [validSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, validTakerAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [validSignedOrder, validSignedOrder2];
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, invalidAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('marketBuyOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const orders = [validSignedOrder, validSignedOrder2];
+ const cumulativeTakerAssetFillAmount = validSignedOrder.takerAssetAmount.plus(takerAssetFillAmount);
+ const makerAssetFillAmount2 = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2);
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, validTakerAddress, {
+ makerAssetFillAmount: cumulativeMakerAssetFillAmount,
+ });
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ validSignedOrder.makerAddress,
+ validSignedOrder2.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerFeePaid2 = validSignedOrder2.makerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid2 = validSignedOrder2.takerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee.plus(takerFeePaid2);
+ // Maker #1
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(validSignedOrder.makerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(validSignedOrder.takerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee),
+ );
+ // Maker #2
+ expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2),
+ );
+ expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid2),
+ );
+ // Taker
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address]
+ .add(validSignedOrder.makerFee)
+ .add(makerFeePaid2)
+ .add(takerFeePaid),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: invalidAddress,
+ });
+ const orders = [validSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ const dummyMakerAssetFillAmount = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, validTakerAddress, {
+ makerAssetFillAmount: dummyMakerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [validSignedOrder, validSignedOrder2];
+ const dummyMakerAssetFillAmount = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.marketBuyOrdersAsync(orders, invalidAddress, {
+ makerAssetFillAmount: dummyMakerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('marketBuyOrdersNoThrowAsync', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const orders = [validSignedOrder, validSignedOrder2];
+ const cumulativeTakerAssetFillAmount = validSignedOrder.takerAssetAmount.plus(takerAssetFillAmount);
+ const makerAssetFillAmount2 = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2);
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(
+ orders,
+ validTakerAddress,
+ {
+ makerAssetFillAmount: cumulativeMakerAssetFillAmount,
+ // 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: 600000,
+ },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ validSignedOrder.makerAddress,
+ validSignedOrder2.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerFeePaid2 = validSignedOrder2.makerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid2 = validSignedOrder2.takerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee.plus(takerFeePaid2);
+ // Maker #1
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(validSignedOrder.makerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(validSignedOrder.takerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee),
+ );
+ // Maker #2
+ expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2),
+ );
+ expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid2),
+ );
+ // Taker
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address]
+ .add(validSignedOrder.makerFee)
+ .add(makerFeePaid2)
+ .add(takerFeePaid),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: invalidAddress,
+ });
+ const orders = [validSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ const dummyMakerAssetFillAmount = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, validTakerAddress, {
+ makerAssetFillAmount: dummyMakerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [validSignedOrder, validSignedOrder2];
+ const dummyMakerAssetFillAmount = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, invalidAddress, {
+ makerAssetFillAmount: dummyMakerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('matchOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('Should transfer correct amounts when both makers and taker meet the balance threshold', async () => {
+ // Test values/results taken from Match Orders test:
+ // 'Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorFloor but fail isRoundingErrorCeil'
+ // Create orders to match
+ const signedOrderLeft = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(17), 0),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(98), 0),
+ makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18),
+ takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18),
+ feeRecipientAddress,
+ });
+ const signedOrderRight = await orderFactory2.newSignedOrderAsync({
+ makerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress),
+ takerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0),
+ makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18),
+ takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18),
+ feeRecipientAddress,
+ });
+ // Compute expected transfer amounts
+ const expectedTransferAmounts = {
+ // Left Maker
+ amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0),
+ amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0),
+ feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), // 76.47%
+ // Right Maker
+ amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0),
+ amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0),
+ feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
+ // Taker
+ amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 0),
+ feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53%
+ feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
+ };
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.matchOrdersAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ validTakerAddress,
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ signedOrderLeft.makerAddress,
+ signedOrderRight.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(
+ newBalances[signedOrderLeft.makerAddress][defaultMakerAssetAddress],
+ 'Checking left maker egress ERC20 account balance',
+ ).to.be.bignumber.equal(
+ erc20Balances[signedOrderLeft.makerAddress][defaultMakerAssetAddress].sub(
+ expectedTransferAmounts.amountSoldByLeftMaker,
+ ),
+ );
+ expect(
+ newBalances[signedOrderRight.makerAddress][defaultTakerAssetAddress],
+ 'Checking right maker ingress ERC20 account balance',
+ ).to.be.bignumber.equal(
+ erc20Balances[signedOrderRight.makerAddress][defaultTakerAssetAddress].sub(
+ expectedTransferAmounts.amountSoldByRightMaker,
+ ),
+ );
+ expect(
+ newBalances[validTakerAddress][defaultMakerAssetAddress],
+ 'Checking taker ingress ERC20 account balance',
+ ).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(
+ expectedTransferAmounts.amountReceivedByTaker,
+ ),
+ );
+ expect(
+ newBalances[signedOrderLeft.makerAddress][defaultTakerAssetAddress],
+ 'Checking left maker ingress ERC20 account balance',
+ ).to.be.bignumber.equal(
+ erc20Balances[signedOrderLeft.makerAddress][defaultTakerAssetAddress].add(
+ expectedTransferAmounts.amountBoughtByLeftMaker,
+ ),
+ );
+ expect(
+ newBalances[signedOrderRight.makerAddress][defaultMakerAssetAddress],
+ 'Checking right maker egress ERC20 account balance',
+ ).to.be.bignumber.equal(
+ erc20Balances[signedOrderRight.makerAddress][defaultMakerAssetAddress].add(
+ expectedTransferAmounts.amountBoughtByRightMaker,
+ ),
+ );
+ // Paid fees
+ expect(
+ newBalances[signedOrderLeft.makerAddress][zrxToken.address],
+ 'Checking left maker egress ERC20 account fees',
+ ).to.be.bignumber.equal(
+ erc20Balances[signedOrderLeft.makerAddress][zrxToken.address].minus(
+ expectedTransferAmounts.feePaidByLeftMaker,
+ ),
+ );
+ expect(
+ newBalances[signedOrderRight.makerAddress][zrxToken.address],
+ 'Checking right maker egress ERC20 account fees',
+ ).to.be.bignumber.equal(
+ erc20Balances[signedOrderRight.makerAddress][zrxToken.address].minus(
+ expectedTransferAmounts.feePaidByRightMaker,
+ ),
+ );
+ expect(
+ newBalances[validTakerAddress][zrxToken.address],
+ 'Checking taker egress ERC20 account fees',
+ ).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address]
+ .minus(expectedTransferAmounts.feePaidByTakerLeft)
+ .sub(expectedTransferAmounts.feePaidByTakerRight),
+ );
+ // Received fees
+ expect(
+ newBalances[signedOrderLeft.feeRecipientAddress][zrxToken.address],
+ 'Checking left fee recipient ingress ERC20 account fees',
+ ).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address]
+ .add(expectedTransferAmounts.feePaidByLeftMaker)
+ .add(expectedTransferAmounts.feePaidByRightMaker)
+ .add(expectedTransferAmounts.feePaidByTakerLeft)
+ .add(expectedTransferAmounts.feePaidByTakerRight),
+ );
+ });
+ it('should revert if left maker does not meet the balance threshold', async () => {
+ // Create signed order with non-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721BalanceThresholdFilterInstance.address,
+ makerAddress: invalidAddress,
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.matchOrdersAsync(
+ validSignedOrder,
+ signedOrderWithBadMakerAddress,
+ validTakerAddress,
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if right maker does not meet the balance threshold', async () => {
+ // Create signed order with non-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721BalanceThresholdFilterInstance.address,
+ makerAddress: invalidAddress,
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.matchOrdersAsync(
+ signedOrderWithBadMakerAddress,
+ validSignedOrder,
+ validTakerAddress,
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.matchOrdersAsync(
+ validSignedOrder,
+ validSignedOrder,
+ invalidAddress,
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('cancelOrder', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('Should successfully cancel order if maker meets balance threshold', async () => {
+ // Verify order is not cancelled
+ const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(
+ validSignedOrder,
+ );
+ expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ // Cancel
+ const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrderAsync(
+ validSignedOrder,
+ validSignedOrder.makerAddress,
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(
+ validSignedOrder,
+ );
+ expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED);
+ });
+ it('Should successfully cancel order if maker does not meet balance threshold', async () => {
+ // Create order where maker does not meet balance threshold
+ const signedOrderWithBadMakerAddress = await invalidOrderFactory.newSignedOrderAsync({});
+ // Verify order is not cancelled
+ const orderInfoBeforeCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrderWithBadMakerAddress,
+ );
+ expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ // Cancel
+ const txReceipt = await erc721NonValidBalanceThresholdWrapper.cancelOrderAsync(
+ signedOrderWithBadMakerAddress,
+ signedOrderWithBadMakerAddress.makerAddress,
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrderWithBadMakerAddress,
+ );
+ expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED);
+ });
+ });
+
+ describe('batchCancelOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ });
+ it('Should successfully batch cancel orders if maker meets balance threshold', async () => {
+ // Create orders to cancel
+ const validSignedOrders = [
+ await orderFactory.newSignedOrderAsync(),
+ await orderFactory.newSignedOrderAsync(),
+ await orderFactory.newSignedOrderAsync(),
+ ];
+ // Verify orders are not cancelled
+ _.each(validSignedOrders, async signedOrder => {
+ const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ });
+ // Cancel
+ const txReceipt = await erc721MakerBalanceThresholdWrapper.batchCancelOrdersAsync(
+ validSignedOrders,
+ validSignedOrders[0].makerAddress,
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ _.each(validSignedOrders, async signedOrder => {
+ const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED);
+ });
+ });
+ it('Should successfully batch cancel order if maker does not meet balance threshold', async () => {
+ // Create orders to cancel
+ const invalidSignedOrders = [
+ await invalidOrderFactory.newSignedOrderAsync(),
+ await invalidOrderFactory.newSignedOrderAsync(),
+ await invalidOrderFactory.newSignedOrderAsync(),
+ ];
+ // Verify orders are not cancelled
+ _.each(invalidSignedOrders, async signedOrder => {
+ const orderInfoBeforeCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ });
+ // Cancel
+ const txReceipt = await erc721NonValidBalanceThresholdWrapper.batchCancelOrdersAsync(
+ invalidSignedOrders,
+ invalidAddress,
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ _.each(invalidSignedOrders, async signedOrder => {
+ const orderInfoAfterCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED);
+ });
+ });
+ });
+
+ describe('cancelOrdersUpTo', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ });
+ it('Should successfully batch cancel orders if maker meets balance threshold', async () => {
+ // Create orders to cancel
+ const validSignedOrders = [
+ await orderFactory.newSignedOrderAsync({ salt: new BigNumber(0) }),
+ await orderFactory.newSignedOrderAsync({ salt: new BigNumber(1) }),
+ await orderFactory.newSignedOrderAsync({ salt: new BigNumber(2) }),
+ ];
+ // Verify orders are not cancelled
+ _.each(validSignedOrders, async signedOrder => {
+ const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ });
+ // Cancel
+ const cancelOrdersUpToThisSalt = new BigNumber(1);
+ const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrdersUpToAsync(
+ cancelOrdersUpToThisSalt,
+ validSignedOrders[0].makerAddress,
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ _.each(validSignedOrders, async (signedOrder, salt: number) => {
+ const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ const saltAsBigNumber = new BigNumber(salt);
+ if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) {
+ return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED);
+ } else {
+ return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ }
+ });
+ });
+ it('Should successfully batch cancel order if maker does not meet balance threshold', async () => {
+ // Create orders to cancel
+ const invalidSignedOrders = [
+ await invalidOrderFactory.newSignedOrderAsync({ salt: new BigNumber(0) }),
+ await invalidOrderFactory.newSignedOrderAsync({ salt: new BigNumber(1) }),
+ await invalidOrderFactory.newSignedOrderAsync({ salt: new BigNumber(2) }),
+ ];
+ // Verify orders are not cancelled
+ _.each(invalidSignedOrders, async signedOrder => {
+ const orderInfoBeforeCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ });
+ // Cancel
+ const cancelOrdersUpToThisSalt = new BigNumber(1);
+ const txReceipt = await erc721NonValidBalanceThresholdWrapper.cancelOrdersUpToAsync(
+ cancelOrdersUpToThisSalt,
+ invalidAddress,
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ _.each(invalidSignedOrders, async (signedOrder, salt: number) => {
+ const orderInfoAfterCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ const saltAsBigNumber = new BigNumber(salt);
+ if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) {
+ return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED);
+ } else {
+ return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ }
+ });
+ });
+ });
+});
+// tslint:disable:max-file-line-count
+// tslint:enable:no-unnecessary-type-assertion
diff --git a/contracts/extensions/test/extensions/forwarder.ts b/contracts/extensions/test/extensions/forwarder.ts
index 4027f493d..69939ed04 100644
--- a/contracts/extensions/test/extensions/forwarder.ts
+++ b/contracts/extensions/test/extensions/forwarder.ts
@@ -48,7 +48,6 @@ describe(ContractName.Forwarder, () => {
let owner: string;
let takerAddress: string;
let feeRecipientAddress: string;
- let otherAddress: string;
let defaultMakerAssetAddress: string;
let zrxAssetData: string;
let wethAssetData: string;
@@ -78,7 +77,7 @@ describe(ContractName.Forwarder, () => {
before(async () => {
await blockchainLifecycle.startAsync();
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress, otherAddress] = accounts);
+ const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts);
const txHash = await web3Wrapper.sendTransactionAsync({ from: accounts[0], to: accounts[0], value: 0 });
const transaction = await web3Wrapper.getTransactionByHashAsync(txHash);
diff --git a/contracts/extensions/test/extensions/order_matcher.ts b/contracts/extensions/test/extensions/order_matcher.ts
new file mode 100644
index 000000000..acb46ced4
--- /dev/null
+++ b/contracts/extensions/test/extensions/order_matcher.ts
@@ -0,0 +1,818 @@
+import {
+ artifacts as protocolArtifacts,
+ ERC20ProxyContract,
+ ERC20Wrapper,
+ ERC721ProxyContract,
+ ExchangeContract,
+ ExchangeFillEventArgs,
+ ExchangeWrapper,
+} from '@0x/contracts-protocol';
+import {
+ chaiSetup,
+ constants,
+ ERC20BalancesByOwner,
+ expectContractCreationFailedAsync,
+ expectTransactionFailedAsync,
+ LogDecoder,
+ OrderFactory,
+ provider,
+ sendTransactionResult,
+ txDefaults,
+ web3Wrapper,
+} from '@0x/contracts-test-utils';
+import { artifacts as tokenArtifacts, DummyERC20TokenContract, DummyERC721TokenContract } from '@0x/contracts-tokens';
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { assetDataUtils } from '@0x/order-utils';
+import { RevertReason } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import * as chai from 'chai';
+import { LogWithDecodedArgs } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { OrderMatcherContract } from '../../generated-wrappers/order_matcher';
+import { artifacts } from '../../src/artifacts';
+
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+chaiSetup.configure();
+const expect = chai.expect;
+// tslint:disable:no-unnecessary-type-assertion
+describe('OrderMatcher', () => {
+ let makerAddressLeft: string;
+ let makerAddressRight: string;
+ let owner: string;
+ let takerAddress: string;
+ let feeRecipientAddressLeft: string;
+ let feeRecipientAddressRight: string;
+
+ let erc20TokenA: DummyERC20TokenContract;
+ let erc20TokenB: DummyERC20TokenContract;
+ let zrxToken: DummyERC20TokenContract;
+ let exchange: ExchangeContract;
+ let erc20Proxy: ERC20ProxyContract;
+ let erc721Proxy: ERC721ProxyContract;
+ let orderMatcher: OrderMatcherContract;
+
+ let erc20BalancesByOwner: ERC20BalancesByOwner;
+ let exchangeWrapper: ExchangeWrapper;
+ let erc20Wrapper: ERC20Wrapper;
+ let orderFactoryLeft: OrderFactory;
+ let orderFactoryRight: OrderFactory;
+
+ let leftMakerAssetData: string;
+ let leftTakerAssetData: string;
+ let defaultERC20MakerAssetAddress: string;
+ let defaultERC20TakerAssetAddress: string;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ // Create accounts
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ // Hack(albrow): Both Prettier and TSLint insert a trailing comma below
+ // but that is invalid syntax as of TypeScript version >= 2.8. We don't
+ // have the right fine-grained configuration options in TSLint,
+ // Prettier, or TypeScript, to reconcile this, so we will just have to
+ // wait for them to sort it out. We disable TSLint and Prettier for
+ // this part of the code for now. This occurs several times in this
+ // file. See https://github.com/prettier/prettier/issues/4624.
+ // prettier-ignore
+ const usedAddresses = ([
+ owner,
+ makerAddressLeft,
+ makerAddressRight,
+ takerAddress,
+ feeRecipientAddressLeft,
+ // tslint:disable-next-line:trailing-comma
+ feeRecipientAddressRight
+ ] = _.slice(accounts, 0, 6));
+ // Create wrappers
+ erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+ // Deploy ERC20 token & ERC20 proxy
+ const numDummyErc20ToDeploy = 3;
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
+ erc20Proxy = await erc20Wrapper.deployProxyAsync();
+ await erc20Wrapper.setBalancesAndAllowancesAsync();
+ // Deploy ERC721 proxy
+ erc721Proxy = await ERC721ProxyContract.deployFrom0xArtifactAsync(
+ protocolArtifacts.ERC721Proxy,
+ provider,
+ txDefaults,
+ );
+ // Depoy exchange
+ exchange = await ExchangeContract.deployFrom0xArtifactAsync(
+ protocolArtifacts.Exchange,
+ provider,
+ txDefaults,
+ assetDataUtils.encodeERC20AssetData(zrxToken.address),
+ );
+ exchangeWrapper = new ExchangeWrapper(exchange, provider);
+ await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner);
+ await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner);
+ // Authorize ERC20 trades by exchange
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Deploy OrderMatcher
+ orderMatcher = await OrderMatcherContract.deployFrom0xArtifactAsync(
+ artifacts.OrderMatcher,
+ provider,
+ txDefaults,
+ exchange.address,
+ );
+ // Set default addresses
+ defaultERC20MakerAssetAddress = erc20TokenA.address;
+ defaultERC20TakerAssetAddress = erc20TokenB.address;
+ leftMakerAssetData = assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress);
+ leftTakerAssetData = assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress);
+ // Set OrderMatcher balances and allowances
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20TokenA.setBalance.sendTransactionAsync(orderMatcher.address, constants.INITIAL_ERC20_BALANCE, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20TokenB.setBalance.sendTransactionAsync(orderMatcher.address, constants.INITIAL_ERC20_BALANCE, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.approveAssetProxy.sendTransactionAsync(
+ leftMakerAssetData,
+ constants.INITIAL_ERC20_ALLOWANCE,
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.approveAssetProxy.sendTransactionAsync(
+ leftTakerAssetData,
+ constants.INITIAL_ERC20_ALLOWANCE,
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Create default order parameters
+ const defaultOrderParamsLeft = {
+ ...constants.STATIC_ORDER_PARAMS,
+ makerAddress: makerAddressLeft,
+ exchangeAddress: exchange.address,
+ makerAssetData: leftMakerAssetData,
+ takerAssetData: leftTakerAssetData,
+ feeRecipientAddress: feeRecipientAddressLeft,
+ makerFee: constants.ZERO_AMOUNT,
+ takerFee: constants.ZERO_AMOUNT,
+ };
+ const defaultOrderParamsRight = {
+ ...constants.STATIC_ORDER_PARAMS,
+ makerAddress: makerAddressRight,
+ exchangeAddress: exchange.address,
+ makerAssetData: leftTakerAssetData,
+ takerAssetData: leftMakerAssetData,
+ feeRecipientAddress: feeRecipientAddressRight,
+ makerFee: constants.ZERO_AMOUNT,
+ takerFee: constants.ZERO_AMOUNT,
+ };
+ const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)];
+ orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft);
+ const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)];
+ orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight);
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('constructor', () => {
+ it('should revert if assetProxy is unregistered', async () => {
+ const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync(
+ protocolArtifacts.Exchange,
+ provider,
+ txDefaults,
+ constants.NULL_BYTES,
+ );
+ return expectContractCreationFailedAsync(
+ (OrderMatcherContract.deployFrom0xArtifactAsync(
+ artifacts.OrderMatcher,
+ provider,
+ txDefaults,
+ exchangeInstance.address,
+ ) as any) as sendTransactionResult,
+ RevertReason.UnregisteredAssetProxy,
+ );
+ });
+ });
+ describe('matchOrders', () => {
+ beforeEach(async () => {
+ erc20BalancesByOwner = await erc20Wrapper.getBalancesAsync();
+ });
+ it('should revert if not called by owner', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
+ });
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: takerAddress,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ RevertReason.OnlyContractOwner,
+ );
+ });
+ it('should transfer the correct amounts when orders completely fill each other', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
+ });
+ // Match signedOrderLeft with signedOrderRight
+ const expectedTransferAmounts = {
+ // Left Maker
+ amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
+ amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
+ // Right Maker
+ amountSoldByRightMaker: signedOrderRight.makerAssetAmount,
+ amountBoughtByRightMaker: signedOrderRight.takerAssetAmount,
+ // Taker
+ leftMakerAssetSpreadAmount: signedOrderLeft.makerAssetAmount.minus(signedOrderRight.takerAssetAmount),
+ };
+ const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const newErc20Balances = await erc20Wrapper.getBalancesAsync();
+ expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByRightMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByRightMaker,
+ ),
+ );
+ expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(
+ initialLeftMakerAssetTakerBalance.plus(expectedTransferAmounts.leftMakerAssetSpreadAmount),
+ );
+ });
+ it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ });
+ // Match signedOrderLeft with signedOrderRight
+ const expectedTransferAmounts = {
+ // Left Maker
+ amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
+ amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
+ // Right Maker
+ amountSoldByRightMaker: signedOrderRight.makerAssetAmount,
+ amountBoughtByRightMaker: signedOrderRight.takerAssetAmount,
+ };
+ const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const newErc20Balances = await erc20Wrapper.getBalancesAsync();
+ expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByRightMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByRightMaker,
+ ),
+ );
+ expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(initialLeftMakerAssetTakerBalance);
+ });
+ it('should transfer the correct amounts when left order is completely filled and right order would be partially filled', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18),
+ });
+ // Match signedOrderLeft with signedOrderRight
+ const expectedTransferAmounts = {
+ // Left Maker
+ amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
+ amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
+ // Right Maker
+ amountSoldByRightMaker: signedOrderRight.makerAssetAmount,
+ amountBoughtByRightMaker: signedOrderRight.takerAssetAmount,
+ // Taker
+ leftMakerAssetSpreadAmount: signedOrderLeft.makerAssetAmount.minus(signedOrderRight.takerAssetAmount),
+ leftTakerAssetSpreadAmount: signedOrderRight.makerAssetAmount.minus(signedOrderLeft.takerAssetAmount),
+ };
+ const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const initialLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf.callAsync(orderMatcher.address);
+ // Match signedOrderLeft with signedOrderRight
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const newLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf.callAsync(orderMatcher.address);
+ const newErc20Balances = await erc20Wrapper.getBalancesAsync();
+ expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByRightMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByRightMaker,
+ ),
+ );
+ expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(
+ initialLeftMakerAssetTakerBalance.plus(expectedTransferAmounts.leftMakerAssetSpreadAmount),
+ );
+ expect(newLeftTakerAssetTakerBalance).to.be.bignumber.equal(
+ initialLeftTakerAssetTakerBalance.plus(expectedTransferAmounts.leftTakerAssetSpreadAmount),
+ );
+ });
+ it('should not call fillOrder when rightOrder is completely filled after matchOrders call and orders were never partially filled', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
+ });
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ const logDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...tokenArtifacts, ...protocolArtifacts });
+ const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
+ await web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ );
+ const fillLogs = _.filter(
+ txReceipt.logs,
+ log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
+ );
+ // Only 2 Fill logs should exist for `matchOrders` call. `fillOrder` should not have been called and should not have emitted a Fill event.
+ expect(fillLogs.length).to.be.equal(2);
+ });
+ it('should not call fillOrder when rightOrder is completely filled after matchOrders call and orders were initially partially filled', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
+ });
+ await exchangeWrapper.fillOrderAsync(signedOrderLeft, takerAddress, {
+ takerAssetFillAmount: signedOrderLeft.takerAssetAmount.dividedToIntegerBy(5),
+ });
+ await exchangeWrapper.fillOrderAsync(signedOrderRight, takerAddress, {
+ takerAssetFillAmount: signedOrderRight.takerAssetAmount.dividedToIntegerBy(5),
+ });
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ const logDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...tokenArtifacts, ...protocolArtifacts });
+ const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
+ await web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ );
+ const fillLogs = _.filter(
+ txReceipt.logs,
+ log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
+ );
+ // Only 2 Fill logs should exist for `matchOrders` call. `fillOrder` should not have been called and should not have emitted a Fill event.
+ expect(fillLogs.length).to.be.equal(2);
+ });
+ it('should only take a spread in rightMakerAsset if entire leftMakerAssetSpread amount can be used to fill rightOrder after matchOrders call', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.9), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(990), 18),
+ });
+ const initialLeftMakerAssetSpreadAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1.09), 18);
+ const leftTakerAssetSpreadAmount = initialLeftMakerAssetSpreadAmount
+ .times(signedOrderRight.makerAssetAmount)
+ .dividedToIntegerBy(signedOrderRight.takerAssetAmount);
+ // Match signedOrderLeft with signedOrderRight
+ const expectedTransferAmounts = {
+ // Left Maker
+ amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
+ amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
+ // Right Maker
+ amountSoldByRightMaker: signedOrderLeft.takerAssetAmount.plus(leftTakerAssetSpreadAmount),
+ amountBoughtByRightMaker: signedOrderLeft.makerAssetAmount,
+ // Taker
+ leftMakerAssetSpreadAmount: constants.ZERO_AMOUNT,
+ leftTakerAssetSpreadAmount,
+ };
+ const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const initialLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf.callAsync(orderMatcher.address);
+ // Match signedOrderLeft with signedOrderRight
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const newLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf.callAsync(orderMatcher.address);
+ const newErc20Balances = await erc20Wrapper.getBalancesAsync();
+ expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByRightMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByRightMaker,
+ ),
+ );
+ expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(
+ initialLeftMakerAssetTakerBalance.plus(expectedTransferAmounts.leftMakerAssetSpreadAmount),
+ );
+ expect(newLeftTakerAssetTakerBalance).to.be.bignumber.equal(
+ initialLeftTakerAssetTakerBalance.plus(expectedTransferAmounts.leftTakerAssetSpreadAmount),
+ );
+ });
+ it("should succeed if rightOrder's makerAssetData and takerAssetData are not provided", async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18),
+ });
+ // Match signedOrderLeft with signedOrderRight
+ const expectedTransferAmounts = {
+ // Left Maker
+ amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
+ amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
+ // Right Maker
+ amountSoldByRightMaker: signedOrderRight.makerAssetAmount,
+ amountBoughtByRightMaker: signedOrderRight.takerAssetAmount,
+ // Taker
+ leftMakerAssetSpreadAmount: signedOrderLeft.makerAssetAmount.minus(signedOrderRight.takerAssetAmount),
+ leftTakerAssetSpreadAmount: signedOrderRight.makerAssetAmount.minus(signedOrderLeft.takerAssetAmount),
+ };
+ const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const initialLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf.callAsync(orderMatcher.address);
+ // Match signedOrderLeft with signedOrderRight
+ signedOrderRight.makerAssetData = constants.NULL_BYTES;
+ signedOrderRight.takerAssetData = constants.NULL_BYTES;
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const newLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf.callAsync(orderMatcher.address);
+ const newErc20Balances = await erc20Wrapper.getBalancesAsync();
+ expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByRightMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByRightMaker,
+ ),
+ );
+ expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(
+ initialLeftMakerAssetTakerBalance.plus(expectedTransferAmounts.leftMakerAssetSpreadAmount),
+ );
+ expect(newLeftTakerAssetTakerBalance).to.be.bignumber.equal(
+ initialLeftTakerAssetTakerBalance.plus(expectedTransferAmounts.leftTakerAssetSpreadAmount),
+ );
+ });
+ it('should revert with the correct reason if matchOrders call reverts', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ });
+ signedOrderRight.signature = `0xff${signedOrderRight.signature.slice(4)}`;
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ RevertReason.InvalidOrderSignature,
+ );
+ });
+ it('should revert with the correct reason if fillOrder call reverts', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18),
+ });
+ // Matcher will not have enough allowance to fill rightOrder
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.approveAssetProxy.sendTransactionAsync(leftMakerAssetData, constants.ZERO_AMOUNT, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ RevertReason.TransferFailed,
+ );
+ });
+ });
+ describe('withdrawAsset', () => {
+ it('should allow owner to withdraw ERC20 tokens', async () => {
+ const erc20AWithdrawAmount = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ expect(erc20AWithdrawAmount).to.be.bignumber.gt(constants.ZERO_AMOUNT);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.withdrawAsset.sendTransactionAsync(leftMakerAssetData, erc20AWithdrawAmount, {
+ from: owner,
+ }),
+ );
+ const newBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ expect(newBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should allow owner to withdraw ERC721 tokens', async () => {
+ const erc721Token = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
+ tokenArtifacts.DummyERC721Token,
+ provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ );
+ const tokenId = new BigNumber(1);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(orderMatcher.address, tokenId, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const assetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId);
+ const withdrawAmount = new BigNumber(1);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.withdrawAsset.sendTransactionAsync(assetData, withdrawAmount, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const erc721Owner = await erc721Token.ownerOf.callAsync(tokenId);
+ expect(erc721Owner).to.be.equal(owner);
+ });
+ it('should revert if not called by owner', async () => {
+ const erc20AWithdrawAmount = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ expect(erc20AWithdrawAmount).to.be.bignumber.gt(constants.ZERO_AMOUNT);
+ await expectTransactionFailedAsync(
+ orderMatcher.withdrawAsset.sendTransactionAsync(leftMakerAssetData, erc20AWithdrawAmount, {
+ from: takerAddress,
+ }),
+ RevertReason.OnlyContractOwner,
+ );
+ });
+ });
+ describe('approveAssetProxy', () => {
+ it('should be able to set an allowance for ERC20 tokens', async () => {
+ const allowance = new BigNumber(55465465426546);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.approveAssetProxy.sendTransactionAsync(leftMakerAssetData, allowance, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const newAllowance = await erc20TokenA.allowance.callAsync(orderMatcher.address, erc20Proxy.address);
+ expect(newAllowance).to.be.bignumber.equal(allowance);
+ });
+ it('should be able to approve an ERC721 token by passing in allowance = 1', async () => {
+ const erc721Token = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
+ tokenArtifacts.DummyERC721Token,
+ provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ );
+ const assetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, constants.ZERO_AMOUNT);
+ const allowance = new BigNumber(1);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.approveAssetProxy.sendTransactionAsync(assetData, allowance, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isApproved = await erc721Token.isApprovedForAll.callAsync(orderMatcher.address, erc721Proxy.address);
+ expect(isApproved).to.be.equal(true);
+ });
+ it('should be able to approve an ERC721 token by passing in allowance > 1', async () => {
+ const erc721Token = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
+ tokenArtifacts.DummyERC721Token,
+ provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ );
+ const assetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, constants.ZERO_AMOUNT);
+ const allowance = new BigNumber(2);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.approveAssetProxy.sendTransactionAsync(assetData, allowance, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isApproved = await erc721Token.isApprovedForAll.callAsync(orderMatcher.address, erc721Proxy.address);
+ expect(isApproved).to.be.equal(true);
+ });
+ it('should revert if not called by owner', async () => {
+ const approval = new BigNumber(1);
+ await expectTransactionFailedAsync(
+ orderMatcher.approveAssetProxy.sendTransactionAsync(leftMakerAssetData, approval, {
+ from: takerAddress,
+ }),
+ RevertReason.OnlyContractOwner,
+ );
+ });
+ });
+});
+// tslint:disable:max-file-line-count
+// tslint:enable:no-unnecessary-type-assertion
diff --git a/contracts/extensions/test/extensions/order_validator.ts b/contracts/extensions/test/extensions/order_validator.ts
new file mode 100644
index 000000000..82a6b937f
--- /dev/null
+++ b/contracts/extensions/test/extensions/order_validator.ts
@@ -0,0 +1,609 @@
+import {
+ artifacts as protocolArtifacts,
+ ERC20ProxyContract,
+ ERC20Wrapper,
+ ERC721ProxyContract,
+ ERC721Wrapper,
+ ExchangeContract,
+ ExchangeWrapper,
+} from '@0x/contracts-protocol';
+import {
+ chaiSetup,
+ constants,
+ OrderFactory,
+ OrderStatus,
+ provider,
+ txDefaults,
+ web3Wrapper,
+} from '@0x/contracts-test-utils';
+import { DummyERC20TokenContract, DummyERC721TokenContract } from '@0x/contracts-tokens';
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
+import { SignedOrder } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import * as chai from 'chai';
+import * as _ from 'lodash';
+
+import { OrderValidatorContract } from '../../generated-wrappers/order_validator';
+import { artifacts } from '../../src/artifacts/index';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('OrderValidator', () => {
+ let makerAddress: string;
+ let owner: string;
+ let takerAddress: string;
+ let erc20AssetData: string;
+ let erc721AssetData: string;
+
+ let erc20Token: DummyERC20TokenContract;
+ let zrxToken: DummyERC20TokenContract;
+ let erc721Token: DummyERC721TokenContract;
+ let exchange: ExchangeContract;
+ let orderValidator: OrderValidatorContract;
+ let erc20Proxy: ERC20ProxyContract;
+ let erc721Proxy: ERC721ProxyContract;
+
+ let signedOrder: SignedOrder;
+ let signedOrder2: SignedOrder;
+ let orderFactory: OrderFactory;
+
+ const tokenId = new BigNumber(123456789);
+ const tokenId2 = new BigNumber(987654321);
+ const ERC721_BALANCE = new BigNumber(1);
+ const ERC721_ALLOWANCE = new BigNumber(1);
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ const usedAddresses = ([owner, makerAddress, takerAddress] = _.slice(accounts, 0, 3));
+
+ const erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+ const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
+
+ const numDummyErc20ToDeploy = 2;
+ [erc20Token, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
+ erc20Proxy = await erc20Wrapper.deployProxyAsync();
+
+ [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
+ erc721Proxy = await erc721Wrapper.deployProxyAsync();
+
+ const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
+ exchange = await ExchangeContract.deployFrom0xArtifactAsync(
+ protocolArtifacts.Exchange,
+ provider,
+ txDefaults,
+ zrxAssetData,
+ );
+ const exchangeWrapper = new ExchangeWrapper(exchange, provider);
+ await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner);
+ await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner);
+
+ orderValidator = await OrderValidatorContract.deployFrom0xArtifactAsync(
+ artifacts.OrderValidator,
+ provider,
+ txDefaults,
+ exchange.address,
+ zrxAssetData,
+ );
+
+ erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20Token.address);
+ erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId);
+ const defaultOrderParams = {
+ ...constants.STATIC_ORDER_PARAMS,
+ exchangeAddress: exchange.address,
+ makerAddress,
+ feeRecipientAddress: constants.NULL_ADDRESS,
+ makerAssetData: erc20AssetData,
+ takerAssetData: erc721AssetData,
+ };
+ const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
+ orderFactory = new OrderFactory(privateKey, defaultOrderParams);
+ });
+
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ describe('getBalanceAndAllowance', () => {
+ describe('getERC721TokenOwner', async () => {
+ it('should return the null address when tokenId is not owned', async () => {
+ const tokenOwner = await orderValidator.getERC721TokenOwner.callAsync(makerAddress, tokenId);
+ expect(tokenOwner).to.be.equal(constants.NULL_ADDRESS);
+ });
+ it('should return the owner address when tokenId is owned', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const tokenOwner = await orderValidator.getERC721TokenOwner.callAsync(erc721Token.address, tokenId);
+ expect(tokenOwner).to.be.equal(makerAddress);
+ });
+ });
+ describe('ERC20 assetData', () => {
+ it('should return the correct balances and allowances when both values are 0', async () => {
+ const [newBalance, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync(
+ makerAddress,
+ erc20AssetData,
+ );
+ expect(constants.ZERO_AMOUNT).to.be.bignumber.equal(newBalance);
+ expect(constants.ZERO_AMOUNT).to.be.bignumber.equal(newAllowance);
+ });
+ it('should return the correct balance and allowance when both values are non-zero', async () => {
+ const balance = new BigNumber(123);
+ const allowance = new BigNumber(456);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.setBalance.sendTransactionAsync(makerAddress, balance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const [newBalance, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync(
+ makerAddress,
+ erc20AssetData,
+ );
+ expect(balance).to.be.bignumber.equal(newBalance);
+ expect(allowance).to.be.bignumber.equal(newAllowance);
+ });
+ });
+ describe('ERC721 assetData', () => {
+ it('should return a balance of 0 when the tokenId is not owned by target', async () => {
+ const [newBalance] = await orderValidator.getBalanceAndAllowance.callAsync(
+ makerAddress,
+ erc721AssetData,
+ );
+ expect(newBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should return an allowance of 0 when no approval is set', async () => {
+ const [, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync(
+ makerAddress,
+ erc721AssetData,
+ );
+ expect(newAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should return a balance of 1 when the tokenId is owned by target', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const [newBalance] = await orderValidator.getBalanceAndAllowance.callAsync(
+ makerAddress,
+ erc721AssetData,
+ );
+ expect(newBalance).to.be.bignumber.equal(ERC721_BALANCE);
+ });
+ it('should return an allowance of 1 when ERC721Proxy is approved for all', async () => {
+ const isApproved = true;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const [, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync(
+ makerAddress,
+ erc721AssetData,
+ );
+ expect(newAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ });
+ it('should return an allowance of 0 when ERC721Proxy is approved for specific tokenId', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.approve.sendTransactionAsync(erc721Proxy.address, tokenId, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const [, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync(
+ makerAddress,
+ erc721AssetData,
+ );
+ expect(newAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ });
+ });
+ describe('getBalancesAndAllowances', () => {
+ it('should return the correct balances and allowances when all values are 0', async () => {
+ const [
+ [erc20Balance, erc721Balance],
+ [erc20Allowance, erc721Allowance],
+ ] = await orderValidator.getBalancesAndAllowances.callAsync(makerAddress, [
+ erc20AssetData,
+ erc721AssetData,
+ ]);
+ expect(erc20Balance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(erc721Balance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(erc20Allowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(erc721Allowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should return the correct balances and allowances when balances and allowances are non-zero', async () => {
+ const balance = new BigNumber(123);
+ const allowance = new BigNumber(456);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.setBalance.sendTransactionAsync(makerAddress, balance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isApproved = true;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const [
+ [erc20Balance, erc721Balance],
+ [erc20Allowance, erc721Allowance],
+ ] = await orderValidator.getBalancesAndAllowances.callAsync(makerAddress, [
+ erc20AssetData,
+ erc721AssetData,
+ ]);
+ expect(erc20Balance).to.be.bignumber.equal(balance);
+ expect(erc721Balance).to.be.bignumber.equal(ERC721_BALANCE);
+ expect(erc20Allowance).to.be.bignumber.equal(allowance);
+ expect(erc721Allowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ });
+ });
+ describe('getTraderInfo', () => {
+ beforeEach(async () => {
+ signedOrder = await orderFactory.newSignedOrderAsync();
+ });
+ it('should return the correct info when no balances or allowances are set', async () => {
+ const traderInfo = await orderValidator.getTraderInfo.callAsync(signedOrder, takerAddress);
+ expect(traderInfo.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should return the correct info when balances and allowances are set', async () => {
+ const makerBalance = new BigNumber(123);
+ const makerAllowance = new BigNumber(456);
+ const makerZrxBalance = new BigNumber(789);
+ const takerZrxAllowance = new BigNumber(987);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.setBalance.sendTransactionAsync(makerAddress, makerBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, makerAllowance, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.setBalance.sendTransactionAsync(makerAddress, makerZrxBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, takerZrxAllowance, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isApproved = true;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const traderInfo = await orderValidator.getTraderInfo.callAsync(signedOrder, takerAddress);
+ expect(traderInfo.makerBalance).to.be.bignumber.equal(makerBalance);
+ expect(traderInfo.makerAllowance).to.be.bignumber.equal(makerAllowance);
+ expect(traderInfo.takerBalance).to.be.bignumber.equal(ERC721_BALANCE);
+ expect(traderInfo.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance);
+ expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance);
+ });
+ });
+ describe('getTradersInfo', () => {
+ beforeEach(async () => {
+ signedOrder = await orderFactory.newSignedOrderAsync();
+ signedOrder2 = await orderFactory.newSignedOrderAsync({
+ takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId2),
+ });
+ });
+ it('should return the correct info when no balances or allowances have been set', async () => {
+ const orders = [signedOrder, signedOrder2];
+ const takers = [takerAddress, takerAddress];
+ const [traderInfo1, traderInfo2] = await orderValidator.getTradersInfo.callAsync(orders, takers);
+ expect(traderInfo1.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should return the correct info when balances and allowances are set', async () => {
+ const makerBalance = new BigNumber(123);
+ const makerAllowance = new BigNumber(456);
+ const makerZrxBalance = new BigNumber(789);
+ const takerZrxAllowance = new BigNumber(987);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.setBalance.sendTransactionAsync(makerAddress, makerBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, makerAllowance, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.setBalance.sendTransactionAsync(makerAddress, makerZrxBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, takerZrxAllowance, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isApproved = true;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId2),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const orders = [signedOrder, signedOrder2];
+ const takers = [takerAddress, takerAddress];
+ const [traderInfo1, traderInfo2] = await orderValidator.getTradersInfo.callAsync(orders, takers);
+
+ expect(traderInfo1.makerBalance).to.be.bignumber.equal(makerBalance);
+ expect(traderInfo1.makerAllowance).to.be.bignumber.equal(makerAllowance);
+ expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance);
+ expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance);
+ expect(traderInfo2.makerBalance).to.be.bignumber.equal(makerBalance);
+ expect(traderInfo2.makerAllowance).to.be.bignumber.equal(makerAllowance);
+ expect(traderInfo2.takerBalance).to.be.bignumber.equal(ERC721_BALANCE);
+ expect(traderInfo2.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance);
+ expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance);
+ });
+ });
+ describe('getOrderAndTraderInfo', () => {
+ beforeEach(async () => {
+ signedOrder = await orderFactory.newSignedOrderAsync();
+ });
+ it('should return the correct info when no balances or allowances are set', async () => {
+ const [orderInfo, traderInfo] = await orderValidator.getOrderAndTraderInfo.callAsync(
+ signedOrder,
+ takerAddress,
+ );
+ const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder);
+ expect(orderInfo.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ expect(orderInfo.orderHash).to.be.equal(expectedOrderHash);
+ expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should return the correct info when balances and allowances are set', async () => {
+ const makerBalance = new BigNumber(123);
+ const makerAllowance = new BigNumber(456);
+ const makerZrxBalance = new BigNumber(789);
+ const takerZrxAllowance = new BigNumber(987);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.setBalance.sendTransactionAsync(makerAddress, makerBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, makerAllowance, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.setBalance.sendTransactionAsync(makerAddress, makerZrxBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, takerZrxAllowance, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isApproved = true;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const [orderInfo, traderInfo] = await orderValidator.getOrderAndTraderInfo.callAsync(
+ signedOrder,
+ takerAddress,
+ );
+ const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder);
+ expect(orderInfo.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ expect(orderInfo.orderHash).to.be.equal(expectedOrderHash);
+ expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerBalance).to.be.bignumber.equal(makerBalance);
+ expect(traderInfo.makerAllowance).to.be.bignumber.equal(makerAllowance);
+ expect(traderInfo.takerBalance).to.be.bignumber.equal(ERC721_BALANCE);
+ expect(traderInfo.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance);
+ expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance);
+ });
+ });
+ describe('getOrdersAndTradersInfo', () => {
+ beforeEach(async () => {
+ signedOrder = await orderFactory.newSignedOrderAsync();
+ signedOrder2 = await orderFactory.newSignedOrderAsync({
+ takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId2),
+ });
+ });
+ it('should return the correct info when no balances or allowances have been set', async () => {
+ const orders = [signedOrder, signedOrder2];
+ const takers = [takerAddress, takerAddress];
+ const [
+ [orderInfo1, orderInfo2],
+ [traderInfo1, traderInfo2],
+ ] = await orderValidator.getOrdersAndTradersInfo.callAsync(orders, takers);
+ const expectedOrderHash1 = orderHashUtils.getOrderHashHex(signedOrder);
+ const expectedOrderHash2 = orderHashUtils.getOrderHashHex(signedOrder2);
+ expect(orderInfo1.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ expect(orderInfo1.orderHash).to.be.equal(expectedOrderHash1);
+ expect(orderInfo1.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(orderInfo2.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ expect(orderInfo2.orderHash).to.be.equal(expectedOrderHash2);
+ expect(orderInfo2.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should return the correct info when balances and allowances are set', async () => {
+ const makerBalance = new BigNumber(123);
+ const makerAllowance = new BigNumber(456);
+ const makerZrxBalance = new BigNumber(789);
+ const takerZrxAllowance = new BigNumber(987);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.setBalance.sendTransactionAsync(makerAddress, makerBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, makerAllowance, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.setBalance.sendTransactionAsync(makerAddress, makerZrxBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, takerZrxAllowance, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isApproved = true;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId2),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const orders = [signedOrder, signedOrder2];
+ const takers = [takerAddress, takerAddress];
+ const [
+ [orderInfo1, orderInfo2],
+ [traderInfo1, traderInfo2],
+ ] = await orderValidator.getOrdersAndTradersInfo.callAsync(orders, takers);
+ const expectedOrderHash1 = orderHashUtils.getOrderHashHex(signedOrder);
+ const expectedOrderHash2 = orderHashUtils.getOrderHashHex(signedOrder2);
+ expect(orderInfo1.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ expect(orderInfo1.orderHash).to.be.equal(expectedOrderHash1);
+ expect(orderInfo1.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(orderInfo2.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ expect(orderInfo2.orderHash).to.be.equal(expectedOrderHash2);
+ expect(orderInfo2.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerBalance).to.be.bignumber.equal(makerBalance);
+ expect(traderInfo1.makerAllowance).to.be.bignumber.equal(makerAllowance);
+ expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance);
+ expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance);
+ expect(traderInfo2.makerBalance).to.be.bignumber.equal(makerBalance);
+ expect(traderInfo2.makerAllowance).to.be.bignumber.equal(makerAllowance);
+ expect(traderInfo2.takerBalance).to.be.bignumber.equal(ERC721_BALANCE);
+ expect(traderInfo2.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance);
+ expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance);
+ });
+ });
+});
+// tslint:disable:max-file-line-count
diff --git a/contracts/extensions/test/utils/balance_threshold_wrapper.ts b/contracts/extensions/test/utils/balance_threshold_wrapper.ts
new file mode 100644
index 000000000..28a4ef011
--- /dev/null
+++ b/contracts/extensions/test/utils/balance_threshold_wrapper.ts
@@ -0,0 +1,283 @@
+import { artifacts as protocolArtifacts, ExchangeContract } from '@0x/contracts-protocol';
+import {
+ FillResults,
+ formatters,
+ LogDecoder,
+ OrderInfo,
+ orderUtils,
+ TransactionFactory,
+} from '@0x/contracts-test-utils';
+import { artifacts as tokensArtifacts } from '@0x/contracts-tokens';
+import { SignedOrder } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter';
+import { artifacts } from '../../src/artifacts';
+
+export class BalanceThresholdWrapper {
+ private readonly _balanceThresholdFilter: BalanceThresholdFilterContract;
+ private readonly _signerTransactionFactory: TransactionFactory;
+ private readonly _exchange: ExchangeContract;
+ private readonly _web3Wrapper: Web3Wrapper;
+ private readonly _logDecoder: LogDecoder;
+ constructor(
+ balanceThresholdFilter: BalanceThresholdFilterContract,
+ exchangeContract: ExchangeContract,
+ signerTransactionFactory: TransactionFactory,
+ provider: Provider,
+ ) {
+ this._balanceThresholdFilter = balanceThresholdFilter;
+ this._exchange = exchangeContract;
+ this._signerTransactionFactory = signerTransactionFactory;
+ this._web3Wrapper = new Web3Wrapper(provider);
+ this._logDecoder = new LogDecoder(this._web3Wrapper, {
+ ...artifacts,
+ ...tokensArtifacts,
+ ...protocolArtifacts,
+ });
+ }
+ public async fillOrderAsync(
+ signedOrder: SignedOrder,
+ from: string,
+ opts: { takerAssetFillAmount?: BigNumber } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
+ const data = this._exchange.fillOrder.getABIEncodedTransactionData(
+ params.order,
+ params.takerAssetFillAmount,
+ params.signature,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async fillOrKillOrderAsync(
+ signedOrder: SignedOrder,
+ from: string,
+ opts: { takerAssetFillAmount?: BigNumber } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
+ const data = this._exchange.fillOrKillOrder.getABIEncodedTransactionData(
+ params.order,
+ params.takerAssetFillAmount,
+ params.signature,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async fillOrderNoThrowAsync(
+ signedOrder: SignedOrder,
+ from: string,
+ opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
+ const data = this._exchange.fillOrderNoThrow.getABIEncodedTransactionData(
+ params.order,
+ params.takerAssetFillAmount,
+ params.signature,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
+ return txReceipt;
+ }
+ public async batchFillOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmounts?: BigNumber[] } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
+ const data = this._exchange.batchFillOrders.getABIEncodedTransactionData(
+ params.orders,
+ params.takerAssetFillAmounts,
+ params.signatures,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async batchFillOrKillOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmounts?: BigNumber[] } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
+ const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData(
+ params.orders,
+ params.takerAssetFillAmounts,
+ params.signatures,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async batchFillOrdersNoThrowAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
+ const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData(
+ params.orders,
+ params.takerAssetFillAmounts,
+ params.signatures,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
+ return txReceipt;
+ }
+ public async marketSellOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmount: BigNumber },
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount);
+ const data = this._exchange.marketSellOrders.getABIEncodedTransactionData(
+ params.orders,
+ params.takerAssetFillAmount,
+ params.signatures,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async marketSellOrdersNoThrowAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmount: BigNumber; gas?: number },
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount);
+ const data = this._exchange.marketSellOrdersNoThrow.getABIEncodedTransactionData(
+ params.orders,
+ params.takerAssetFillAmount,
+ params.signatures,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
+ return txReceipt;
+ }
+ public async marketBuyOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { makerAssetFillAmount: BigNumber },
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount);
+ const data = this._exchange.marketBuyOrders.getABIEncodedTransactionData(
+ params.orders,
+ params.makerAssetFillAmount,
+ params.signatures,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async marketBuyOrdersNoThrowAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { makerAssetFillAmount: BigNumber; gas?: number },
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount);
+ const data = this._exchange.marketBuyOrdersNoThrow.getABIEncodedTransactionData(
+ params.orders,
+ params.makerAssetFillAmount,
+ params.signatures,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
+ return txReceipt;
+ }
+ public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createCancel(signedOrder);
+ const data = this._exchange.cancelOrder.getABIEncodedTransactionData(params.order);
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async batchCancelOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createBatchCancel(orders);
+ const data = this._exchange.batchCancelOrders.getABIEncodedTransactionData(params.orders);
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise<TransactionReceiptWithDecodedLogs> {
+ const data = this._exchange.cancelOrdersUpTo.getABIEncodedTransactionData(salt);
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise<BigNumber> {
+ const filledAmount = 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 getOrderEpochAsync(makerAddress: string, senderAddress: string): Promise<BigNumber> {
+ const orderEpoch = await this._exchange.orderEpoch.callAsync(makerAddress, senderAddress);
+ return orderEpoch;
+ }
+ public async getOrderInfoAsync(signedOrder: SignedOrder): Promise<OrderInfo> {
+ const orderInfo = await this._exchange.getOrderInfo.callAsync(signedOrder);
+ return orderInfo;
+ }
+ public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise<OrderInfo[]> {
+ const ordersInfo = (await this._exchange.getOrdersInfo.callAsync(signedOrders)) as OrderInfo[];
+ return ordersInfo;
+ }
+ public async matchOrdersAsync(
+ signedOrderLeft: SignedOrder,
+ signedOrderRight: SignedOrder,
+ from: string,
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight);
+ const data = await this._exchange.matchOrders.getABIEncodedTransactionData(
+ params.left,
+ params.right,
+ params.leftSignature,
+ params.rightSignature,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async getFillOrderResultsAsync(
+ signedOrder: SignedOrder,
+ from: string,
+ opts: { takerAssetFillAmount?: BigNumber } = {},
+ ): Promise<FillResults> {
+ const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
+ const fillResults = await this._exchange.fillOrder.callAsync(
+ params.order,
+ params.takerAssetFillAmount,
+ params.signature,
+ { from },
+ );
+ return fillResults;
+ }
+ public abiEncodeFillOrder(signedOrder: SignedOrder, opts: { takerAssetFillAmount?: BigNumber } = {}): string {
+ const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
+ const data = this._exchange.fillOrder.getABIEncodedTransactionData(
+ params.order,
+ params.takerAssetFillAmount,
+ params.signature,
+ );
+ return data;
+ }
+ public getBalanceThresholdAddress(): string {
+ return this._balanceThresholdFilter.address;
+ }
+ public getExchangeAddress(): string {
+ return this._exchange.address;
+ }
+ private async _executeTransactionAsync(
+ abiEncodedExchangeTxData: string,
+ from: string,
+ gas?: number,
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const signedExchangeTx = this._signerTransactionFactory.newSignedTransaction(abiEncodedExchangeTxData);
+ const txOpts = _.isUndefined(gas) ? { from } : { from, gas };
+ const txHash = await this._balanceThresholdFilter.executeTransaction.sendTransactionAsync(
+ signedExchangeTx.salt,
+ signedExchangeTx.signerAddress,
+ signedExchangeTx.data,
+ signedExchangeTx.signature,
+ txOpts,
+ );
+ const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
+ return txReceipt;
+ }
+}
diff --git a/contracts/extensions/tsconfig.json b/contracts/extensions/tsconfig.json
index a4ce1e002..ed9b4fbe1 100644
--- a/contracts/extensions/tsconfig.json
+++ b/contracts/extensions/tsconfig.json
@@ -6,6 +6,12 @@
"resolveJsonModule": true
},
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
- "files": ["./generated-artifacts/DutchAuction.json", "./generated-artifacts/Forwarder.json"],
+ "files": [
+ "./generated-artifacts/BalanceThresholdFilter.json",
+ "./generated-artifacts/DutchAuction.json",
+ "./generated-artifacts/Forwarder.json",
+ "./generated-artifacts/OrderMatcher.json",
+ "./generated-artifacts/OrderValidator.json"
+ ],
"exclude": ["./deploy/solc/solc_bin"]
}