aboutsummaryrefslogtreecommitdiffstats
path: root/contracts/extensions
diff options
context:
space:
mode:
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/MixinBalanceThresholdFilterCore.sol2
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol2
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol2
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol2
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol2
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol2
-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.json6
-rw-r--r--contracts/extensions/src/artifacts/index.ts4
-rw-r--r--contracts/extensions/src/wrappers/index.ts2
-rw-r--r--contracts/extensions/test/extensions/dutch_auction.ts194
-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/dutch_auction_test_wrapper.ts62
-rw-r--r--contracts/extensions/tsconfig.json4
38 files changed, 2377 insertions, 161 deletions
diff --git a/contracts/extensions/CHANGELOG.json b/contracts/extensions/CHANGELOG.json
index da4d9c2ba..40e25565e 100644
--- a/contracts/extensions/CHANGELOG.json
+++ b/contracts/extensions/CHANGELOG.json
@@ -1,10 +1,27 @@
[
{
+ "version": "1.2.0",
+ "changes": [
+ {
+ "note": "Added Dutch Auction Wrapper",
+ "pr": 1465
+ }
+ ]
+ },
+ {
"version": "1.1.0",
"changes": [
{
"note": "Added Balance Threshold Filter",
"pr": 1383
+ },
+ {
+ "note": "Add OrderMatcher",
+ "pr": 1117
+ },
+ {
+ "note": "Add OrderValidator",
+ "pr": 1464
}
]
},
diff --git a/contracts/extensions/compiler.json b/contracts/extensions/compiler.json
index e6ed0c215..2bb468724 100644
--- a/contracts/extensions/compiler.json
+++ b/contracts/extensions/compiler.json
@@ -18,5 +18,5 @@
}
}
},
- "contracts": ["BalanceThresholdFilter", "DutchAuction", "Forwarder"]
+ "contracts": ["BalanceThresholdFilter", "DutchAuction", "Forwarder", "OrderMatcher", "OrderValidator"]
}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol
index df830f36e..da050bf72 100644
--- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
import "@0x/contracts-libs/contracts/libs/LibExchangeSelectors.sol";
import "@0x/contracts-libs/contracts/libs/LibOrder.sol";
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol
index bd26a468f..a15e95a9d 100644
--- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol
@@ -17,7 +17,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
import "./mixins/MExchangeCalldata.sol";
import "@0x/contracts-libs/contracts/libs/LibAddressArray.sol";
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol
index 3d8e2bbd1..4a1bf1fb2 100644
--- a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol
@@ -17,7 +17,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
contract IBalanceThresholdFilterCore {
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol
index 3e424b9f4..f78f9c2de 100644
--- a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
contract IThresholdAsset {
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol
index b8b67e6ee..074686e8d 100644
--- a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
import "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchange.sol";
import "../interfaces/IThresholdAsset.sol";
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol
index bf2940fe1..40536d820 100644
--- a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol
@@ -17,7 +17,7 @@
*/
-pragma solidity 0.4.24;
+pragma solidity ^0.4.24;
contract MExchangeCalldata {
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 2d9ed4dcd..aee995645 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/@(BalanceThresholdFilter|DutchAuction|Forwarder).json"
+ "abis": "generated-artifacts/@(BalanceThresholdFilter|DutchAuction|Forwarder|OrderMatcher|OrderValidator).json"
},
"repository": {
"type": "git",
@@ -44,6 +45,7 @@
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
"devDependencies": {
"@0x/abi-gen": "^1.0.19",
+ "@0x/contract-wrappers": "^4.1.3",
"@0x/contracts-test-utils": "^1.0.2",
"@0x/dev-utils": "^1.0.21",
"@0x/sol-compiler": "^1.1.16",
diff --git a/contracts/extensions/src/artifacts/index.ts b/contracts/extensions/src/artifacts/index.ts
index ebf0b8050..329d0f3f3 100644
--- a/contracts/extensions/src/artifacts/index.ts
+++ b/contracts/extensions/src/artifacts/index.ts
@@ -3,9 +3,13 @@ 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 8a8122caa..65aec3ccd 100644
--- a/contracts/extensions/src/wrappers/index.ts
+++ b/contracts/extensions/src/wrappers/index.ts
@@ -1,3 +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/dutch_auction.ts b/contracts/extensions/test/extensions/dutch_auction.ts
index 6c3b2f0f3..22b3caa16 100644
--- a/contracts/extensions/test/extensions/dutch_auction.ts
+++ b/contracts/extensions/test/extensions/dutch_auction.ts
@@ -1,3 +1,4 @@
+import { DutchAuctionWrapper } from '@0x/contract-wrappers';
import {
artifacts as protocolArtifacts,
ERC20Wrapper,
@@ -29,12 +30,11 @@ import { RevertReason, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as chai from 'chai';
-import ethAbi = require('ethereumjs-abi');
-import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
import { DutchAuctionContract } from '../../generated-wrappers/dutch_auction';
import { artifacts } from '../../src/artifacts';
+import { DutchAuctionTestWrapper } from '../utils/dutch_auction_test_wrapper';
chaiSetup.configure();
const expect = chai.expect;
@@ -68,19 +68,8 @@ describe(ContractName.DutchAuction, () => {
let erc721MakerAssetIds: BigNumber[];
const tenMinutesInSeconds = 10 * 60;
- function extendMakerAssetData(makerAssetData: string, beginTimeSeconds: BigNumber, beginAmount: BigNumber): string {
- return ethUtil.bufferToHex(
- Buffer.concat([
- ethUtil.toBuffer(makerAssetData),
- ethUtil.toBuffer(
- (ethAbi as any).rawEncode(
- ['uint256', 'uint256'],
- [beginTimeSeconds.toString(), beginAmount.toString()],
- ),
- ),
- ]),
- );
- }
+ let dutchAuctionTestWrapper: DutchAuctionTestWrapper;
+ let defaultERC20MakerAssetData: string;
before(async () => {
await blockchainLifecycle.startAsync();
@@ -136,6 +125,7 @@ describe(ContractName.DutchAuction, () => {
dutchAuctionInstance.address,
provider,
);
+ dutchAuctionTestWrapper = new DutchAuctionTestWrapper(dutchAuctionInstance, provider);
defaultMakerAssetAddress = erc20TokenA.address;
const defaultTakerAssetAddress = wethContract.address;
@@ -174,7 +164,7 @@ describe(ContractName.DutchAuction, () => {
feeRecipientAddress,
// taker address or sender address should be set to the ducth auction contract
takerAddress: dutchAuctionContract.address,
- makerAssetData: extendMakerAssetData(
+ makerAssetData: DutchAuctionWrapper.encodeDutchAuctionAssetData(
assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
auctionBeginTimeSeconds,
auctionBeginAmount,
@@ -199,6 +189,7 @@ describe(ContractName.DutchAuction, () => {
const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)];
sellerOrderFactory = new OrderFactory(makerPrivateKey, sellerDefaultOrderParams);
buyerOrderFactory = new OrderFactory(takerPrivateKey, buyerDefaultOrderParams);
+ defaultERC20MakerAssetData = assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress);
});
after(async () => {
await blockchainLifecycle.revertAsync();
@@ -215,49 +206,41 @@ describe(ContractName.DutchAuction, () => {
describe('matchOrders', () => {
it('should be worth the begin price at the begining of the auction', async () => {
auctionBeginTimeSeconds = new BigNumber(currentBlockTimestamp + 2);
- sellOrder = await sellerOrderFactory.newSignedOrderAsync({
- makerAssetData: extendMakerAssetData(
- assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
- auctionBeginTimeSeconds,
- auctionBeginAmount,
- ),
- });
- const auctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder);
+ const makerAssetData = DutchAuctionWrapper.encodeDutchAuctionAssetData(
+ defaultERC20MakerAssetData,
+ auctionBeginTimeSeconds,
+ auctionBeginAmount,
+ );
+ sellOrder = await sellerOrderFactory.newSignedOrderAsync({ makerAssetData });
+ const auctionDetails = await dutchAuctionTestWrapper.getAuctionDetailsAsync(sellOrder);
+ expect(auctionDetails.currentTimeSeconds).to.be.bignumber.lte(auctionBeginTimeSeconds);
expect(auctionDetails.currentAmount).to.be.bignumber.equal(auctionBeginAmount);
expect(auctionDetails.beginAmount).to.be.bignumber.equal(auctionBeginAmount);
});
it('should be be worth the end price at the end of the auction', async () => {
auctionBeginTimeSeconds = new BigNumber(currentBlockTimestamp - tenMinutesInSeconds * 2);
auctionEndTimeSeconds = new BigNumber(currentBlockTimestamp - tenMinutesInSeconds);
+ const makerAssetData = DutchAuctionWrapper.encodeDutchAuctionAssetData(
+ defaultERC20MakerAssetData,
+ auctionBeginTimeSeconds,
+ auctionBeginAmount,
+ );
sellOrder = await sellerOrderFactory.newSignedOrderAsync({
- makerAssetData: extendMakerAssetData(
- assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
- auctionBeginTimeSeconds,
- auctionBeginAmount,
- ),
+ makerAssetData,
expirationTimeSeconds: auctionEndTimeSeconds,
});
- const auctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder);
+ const auctionDetails = await dutchAuctionTestWrapper.getAuctionDetailsAsync(sellOrder);
+ expect(auctionDetails.currentTimeSeconds).to.be.bignumber.gte(auctionEndTimeSeconds);
expect(auctionDetails.currentAmount).to.be.bignumber.equal(auctionEndAmount);
expect(auctionDetails.beginAmount).to.be.bignumber.equal(auctionBeginAmount);
});
it('should match orders at current amount and send excess to buyer', async () => {
- const beforeAuctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder);
+ const beforeAuctionDetails = await dutchAuctionTestWrapper.getAuctionDetailsAsync(sellOrder);
buyOrder = await buyerOrderFactory.newSignedOrderAsync({
makerAssetAmount: beforeAuctionDetails.currentAmount.times(2),
});
- await web3Wrapper.awaitTransactionSuccessAsync(
- await dutchAuctionContract.matchOrders.sendTransactionAsync(
- buyOrder,
- sellOrder,
- buyOrder.signature,
- sellOrder.signature,
- {
- from: takerAddress,
- },
- ),
- );
- const afterAuctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder);
+ await dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress);
+ const afterAuctionDetails = await dutchAuctionTestWrapper.getAuctionDetailsAsync(sellOrder);
const newBalances = await erc20Wrapper.getBalancesAsync();
expect(newBalances[dutchAuctionContract.address][wethContract.address]).to.be.bignumber.equal(
constants.ZERO_AMOUNT,
@@ -276,17 +259,8 @@ describe(ContractName.DutchAuction, () => {
sellOrder = await sellerOrderFactory.newSignedOrderAsync({
makerFee: new BigNumber(1),
});
- const txHash = await dutchAuctionContract.matchOrders.sendTransactionAsync(
- buyOrder,
- sellOrder,
- buyOrder.signature,
- sellOrder.signature,
- {
- from: takerAddress,
- },
- );
- await web3Wrapper.awaitTransactionSuccessAsync(txHash);
- const afterAuctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder);
+ await dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress);
+ const afterAuctionDetails = await dutchAuctionTestWrapper.getAuctionDetailsAsync(sellOrder);
const newBalances = await erc20Wrapper.getBalancesAsync();
expect(newBalances[makerAddress][wethContract.address]).to.be.bignumber.gte(
erc20Balances[makerAddress][wethContract.address].plus(afterAuctionDetails.currentAmount),
@@ -299,18 +273,9 @@ describe(ContractName.DutchAuction, () => {
buyOrder = await buyerOrderFactory.newSignedOrderAsync({
makerFee: new BigNumber(1),
});
- const txHash = await dutchAuctionContract.matchOrders.sendTransactionAsync(
- buyOrder,
- sellOrder,
- buyOrder.signature,
- sellOrder.signature,
- {
- from: takerAddress,
- },
- );
- await web3Wrapper.awaitTransactionSuccessAsync(txHash);
+ await dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const afterAuctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder);
+ const afterAuctionDetails = await dutchAuctionTestWrapper.getAuctionDetailsAsync(sellOrder);
expect(newBalances[makerAddress][wethContract.address]).to.be.bignumber.gte(
erc20Balances[makerAddress][wethContract.address].plus(afterAuctionDetails.currentAmount),
);
@@ -321,24 +286,17 @@ describe(ContractName.DutchAuction, () => {
it('should revert when auction expires', async () => {
auctionBeginTimeSeconds = new BigNumber(currentBlockTimestamp - tenMinutesInSeconds * 2);
auctionEndTimeSeconds = new BigNumber(currentBlockTimestamp - tenMinutesInSeconds);
+ const makerAssetData = DutchAuctionWrapper.encodeDutchAuctionAssetData(
+ defaultERC20MakerAssetData,
+ auctionBeginTimeSeconds,
+ auctionBeginAmount,
+ );
sellOrder = await sellerOrderFactory.newSignedOrderAsync({
expirationTimeSeconds: auctionEndTimeSeconds,
- makerAssetData: extendMakerAssetData(
- assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
- auctionBeginTimeSeconds,
- auctionBeginAmount,
- ),
+ makerAssetData,
});
return expectTransactionFailedAsync(
- dutchAuctionContract.matchOrders.sendTransactionAsync(
- buyOrder,
- sellOrder,
- buyOrder.signature,
- sellOrder.signature,
- {
- from: takerAddress,
- },
- ),
+ dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress),
RevertReason.AuctionExpired,
);
});
@@ -347,15 +305,7 @@ describe(ContractName.DutchAuction, () => {
makerAssetAmount: sellOrder.takerAssetAmount,
});
return expectTransactionFailedAsync(
- dutchAuctionContract.matchOrders.sendTransactionAsync(
- buyOrder,
- sellOrder,
- buyOrder.signature,
- sellOrder.signature,
- {
- from: takerAddress,
- },
- ),
+ dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress),
RevertReason.AuctionInvalidAmount,
);
});
@@ -364,38 +314,23 @@ describe(ContractName.DutchAuction, () => {
takerAssetAmount: auctionBeginAmount.plus(1),
});
return expectTransactionFailedAsync(
- dutchAuctionContract.matchOrders.sendTransactionAsync(
- buyOrder,
- sellOrder,
- buyOrder.signature,
- sellOrder.signature,
- {
- from: takerAddress,
- },
- ),
+ dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress),
RevertReason.AuctionInvalidAmount,
);
});
it('begin time is less than end time', async () => {
auctionBeginTimeSeconds = new BigNumber(auctionEndTimeSeconds).plus(tenMinutesInSeconds);
+ const makerAssetData = DutchAuctionWrapper.encodeDutchAuctionAssetData(
+ defaultERC20MakerAssetData,
+ auctionBeginTimeSeconds,
+ auctionBeginAmount,
+ );
sellOrder = await sellerOrderFactory.newSignedOrderAsync({
expirationTimeSeconds: auctionEndTimeSeconds,
- makerAssetData: extendMakerAssetData(
- assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
- auctionBeginTimeSeconds,
- auctionBeginAmount,
- ),
+ makerAssetData,
});
return expectTransactionFailedAsync(
- dutchAuctionContract.matchOrders.sendTransactionAsync(
- buyOrder,
- sellOrder,
- buyOrder.signature,
- sellOrder.signature,
- {
- from: takerAddress,
- },
- ),
+ dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress),
RevertReason.AuctionInvalidBeginTime,
);
});
@@ -404,45 +339,30 @@ describe(ContractName.DutchAuction, () => {
makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
});
return expectTransactionFailedAsync(
- dutchAuctionContract.matchOrders.sendTransactionAsync(
- buyOrder,
- sellOrder,
- buyOrder.signature,
- sellOrder.signature,
- {
- from: takerAddress,
- },
- ),
+ dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress),
RevertReason.InvalidAssetData,
);
});
+
describe('ERC721', () => {
it('should match orders when ERC721', async () => {
const makerAssetId = erc721MakerAssetIds[0];
+ const erc721MakerAssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId);
+ const makerAssetData = DutchAuctionWrapper.encodeDutchAuctionAssetData(
+ erc721MakerAssetData,
+ auctionBeginTimeSeconds,
+ auctionBeginAmount,
+ );
sellOrder = await sellerOrderFactory.newSignedOrderAsync({
makerAssetAmount: new BigNumber(1),
- makerAssetData: extendMakerAssetData(
- assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- auctionBeginTimeSeconds,
- auctionBeginAmount,
- ),
+ makerAssetData,
});
buyOrder = await buyerOrderFactory.newSignedOrderAsync({
takerAssetAmount: new BigNumber(1),
takerAssetData: sellOrder.makerAssetData,
});
- await web3Wrapper.awaitTransactionSuccessAsync(
- await dutchAuctionContract.matchOrders.sendTransactionAsync(
- buyOrder,
- sellOrder,
- buyOrder.signature,
- sellOrder.signature,
- {
- from: takerAddress,
- },
- ),
- );
- const afterAuctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder);
+ await dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress);
+ const afterAuctionDetails = await dutchAuctionTestWrapper.getAuctionDetailsAsync(sellOrder);
const newBalances = await erc20Wrapper.getBalancesAsync();
// HACK gte used here due to a bug in ganache where the timestamp can change
// between multiple calls to the same block. Which can move the amount in our case
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/dutch_auction_test_wrapper.ts b/contracts/extensions/test/utils/dutch_auction_test_wrapper.ts
new file mode 100644
index 000000000..c1e2f2070
--- /dev/null
+++ b/contracts/extensions/test/utils/dutch_auction_test_wrapper.ts
@@ -0,0 +1,62 @@
+import { artifacts as protocolArtifacts } from '@0x/contracts-protocol';
+import { LogDecoder } from '@0x/contracts-test-utils';
+import { artifacts as tokensArtifacts } from '@0x/contracts-tokens';
+import { DutchAuctionDetails, SignedOrder } from '@0x/types';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { DutchAuctionContract } from '../../generated-wrappers/dutch_auction';
+import { artifacts } from '../../src/artifacts';
+
+export class DutchAuctionTestWrapper {
+ private readonly _dutchAuctionContract: DutchAuctionContract;
+ private readonly _web3Wrapper: Web3Wrapper;
+ private readonly _logDecoder: LogDecoder;
+
+ constructor(contractInstance: DutchAuctionContract, provider: Provider) {
+ this._dutchAuctionContract = contractInstance;
+ this._web3Wrapper = new Web3Wrapper(provider);
+ this._logDecoder = new LogDecoder(this._web3Wrapper, {
+ ...artifacts,
+ ...tokensArtifacts,
+ ...protocolArtifacts,
+ });
+ }
+ /**
+ * Matches the buy and sell orders at an amount given the following: the current block time, the auction
+ * start time and the auction begin amount. The sell order is a an order at the lowest amount
+ * at the end of the auction. Excess from the match is transferred to the seller.
+ * Over time the price moves from beginAmount to endAmount given the current block.timestamp.
+ * @param buyOrder The Buyer's order. This order is for the current expected price of the auction.
+ * @param sellOrder The Seller's order. This order is for the lowest amount (at the end of the auction).
+ * @param from Address the transaction is being sent from.
+ * @return Transaction receipt with decoded logs.
+ */
+ public async matchOrdersAsync(
+ buyOrder: SignedOrder,
+ sellOrder: SignedOrder,
+ from: string,
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const txHash = await this._dutchAuctionContract.matchOrders.sendTransactionAsync(
+ buyOrder,
+ sellOrder,
+ buyOrder.signature,
+ sellOrder.signature,
+ {
+ from,
+ },
+ );
+ const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
+ return tx;
+ }
+ /**
+ * Calculates the Auction Details for the given order
+ * @param sellOrder The Seller's order. This order is for the lowest amount (at the end of the auction).
+ * @return The dutch auction details.
+ */
+ public async getAuctionDetailsAsync(sellOrder: SignedOrder): Promise<DutchAuctionDetails> {
+ const auctionDetails = await this._dutchAuctionContract.getAuctionDetails.callAsync(sellOrder);
+ return auctionDetails;
+ }
+}
diff --git a/contracts/extensions/tsconfig.json b/contracts/extensions/tsconfig.json
index a303e3f5c..ed9b4fbe1 100644
--- a/contracts/extensions/tsconfig.json
+++ b/contracts/extensions/tsconfig.json
@@ -9,7 +9,9 @@
"files": [
"./generated-artifacts/BalanceThresholdFilter.json",
"./generated-artifacts/DutchAuction.json",
- "./generated-artifacts/Forwarder.json"
+ "./generated-artifacts/Forwarder.json",
+ "./generated-artifacts/OrderMatcher.json",
+ "./generated-artifacts/OrderValidator.json"
],
"exclude": ["./deploy/solc/solc_bin"]
}