aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAmir Bandeali <abandeali1@gmail.com>2018-10-03 07:22:35 +0800
committerAmir Bandeali <abandeali1@gmail.com>2018-12-21 01:13:36 +0800
commitb786836db61de22fa290d94ac3f12fe5342aecc7 (patch)
tree214af7bf913a51ae7a3c1853ae3fa9bff49d96b1
parentace968a4fc5281d6688ac6de931a46728455c693 (diff)
downloaddexon-sol-tools-b786836db61de22fa290d94ac3f12fe5342aecc7.tar
dexon-sol-tools-b786836db61de22fa290d94ac3f12fe5342aecc7.tar.gz
dexon-sol-tools-b786836db61de22fa290d94ac3f12fe5342aecc7.tar.bz2
dexon-sol-tools-b786836db61de22fa290d94ac3f12fe5342aecc7.tar.lz
dexon-sol-tools-b786836db61de22fa290d94ac3f12fe5342aecc7.tar.xz
dexon-sol-tools-b786836db61de22fa290d94ac3f12fe5342aecc7.tar.zst
dexon-sol-tools-b786836db61de22fa290d94ac3f12fe5342aecc7.zip
feat: Add OrderMatcher contract that takes spread in multiple assets by calling `matchOrders` followed by `fillOrder`
-rw-r--r--contracts/extensions/compiler.json2
-rw-r--r--contracts/extensions/contracts/OrderMatcher/MixinAssets.sol192
-rw-r--r--contracts/extensions/contracts/OrderMatcher/MixinMatchOrders.sol323
-rw-r--r--contracts/extensions/contracts/OrderMatcher/OrderMatcher.sol37
-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.sol53
-rw-r--r--contracts/extensions/contracts/OrderMatcher/mixins/MAssets.sol71
-rw-r--r--contracts/protocol/src/artifacts/index.ts2
10 files changed, 796 insertions, 1 deletions
diff --git a/contracts/extensions/compiler.json b/contracts/extensions/compiler.json
index e6ed0c215..1e21e6e6a 100644
--- a/contracts/extensions/compiler.json
+++ b/contracts/extensions/compiler.json
@@ -18,5 +18,5 @@
}
}
},
- "contracts": ["BalanceThresholdFilter", "DutchAuction", "Forwarder"]
+ "contracts": ["BalanceThresholdFilter", "DutchAuction", "Forwarder", "OrderMatcher"]
}
diff --git a/contracts/extensions/contracts/OrderMatcher/MixinAssets.sol b/contracts/extensions/contracts/OrderMatcher/MixinAssets.sol
new file mode 100644
index 000000000..a2bc95e4b
--- /dev/null
+++ b/contracts/extensions/contracts/OrderMatcher/MixinAssets.sol
@@ -0,0 +1,192 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity 0.4.24;
+
+import "../../utils/LibBytes/LibBytes.sol";
+import "../../utils/Ownable/Ownable.sol";
+import "../../tokens/ERC20Token/IERC20Token.sol";
+import "../../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
+ {
+ 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.
+ address token = assetData.readAddress(16);
+ 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..1c7cce675
--- /dev/null
+++ b/contracts/extensions/contracts/OrderMatcher/MixinMatchOrders.sol
@@ -0,0 +1,323 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity 0.4.24;
+
+import "../../utils/Ownable/Ownable.sol";
+import "./libs/LibConstants.sol";
+
+
+contract MixinMatchOrders is
+ Ownable,
+ LibConstants
+{
+ // The below assembly in the fallback function is functionaly equivalent to the following Solidity code:
+ /*
+ /// @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
+ );
+
+ // If a spread was taken, use the spread to fill remaining amount of `rightOrder`
+ // Only attempt to fill `rightOrder` if a spread was taken and if not already completely filled
+ uint256 leftMakerAssetSpreadAmount = matchedFillResults.leftMakerAssetSpreadAmount;
+ if (leftMakerAssetSpreadAmount > 0 && matchedFillResults.right.takerAssetFilledAmount < rightOrder.takerAssetAmount) {
+ // 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;
+
+ // We do not need to pass in a signature since it was already validated in the `matchOrders` call
+ EXCHANGE.fillOrder(
+ rightOrder,
+ leftMakerAssetSpreadAmount,
+ ""
+ );
+ }
+ }
+ */
+ // solhint-disable-next-line payable-fallback
+ function ()
+ external
+ {
+ assembly {
+ // The first 4 bytes of calldata holds the function selector
+ // `matchOrders` selector = 0x3c28d861
+ if eq(
+ and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000),
+ 0x3c28d86100000000000000000000000000000000000000000000000000000000
+ ) {
+
+ // Load address of `owner`
+ let owner := sload(owner_slot)
+
+ // Revert if `msg.sender` != `owner`
+ if iszero(eq(owner, caller)) {
+ // Revert with `Error("ONLY_CONTRACT_OWNER")`
+ mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
+ mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
+ mstore(64, 0x000000134f4e4c595f434f4e54524143545f4f574e4552000000000000000000)
+ mstore(96, 0)
+ revert(0, 100)
+ }
+
+ // Load address of Exchange contract
+ let exchange := sload(EXCHANGE_slot)
+
+ // Copy calldata to memory
+ // The calldata should be identical to the the ABIv2 encoded data required to call `EXCHANGE.matchOrders`
+ calldatacopy(0, 0, calldatasize())
+
+ // At this point, calldata and memory have the following layout:
+
+ // | Offset | Length | Contents |
+ // |---------------------------|---------|-------------------------------------------- |
+ // | 0 | 4 | `matchOrders` function selector |
+ // | | 4 * 32 | function parameters: |
+ // | 4 | | 1. offset to leftOrder (*) |
+ // | 36 | | 2. offset to rightOrder (*) |
+ // | 68 | | 3. offset to leftSignature (*) |
+ // | 100 | | 4. offset to rightSignature (*) |
+ // | | 12 * 32 | leftOrder: |
+ // | 132 | | 1. senderAddress |
+ // | 164 | | 2. makerAddress |
+ // | 196 | | 3. takerAddress |
+ // | 228 | | 4. feeRecipientAddress |
+ // | 260 | | 5. makerAssetAmount |
+ // | 292 | | 6. takerAssetAmount |
+ // | 324 | | 7. makerFeeAmount |
+ // | 356 | | 8. takerFeeAmount |
+ // | 388 | | 9. expirationTimeSeconds |
+ // | 420 | | 10. salt |
+ // | 452 | | 11. offset to leftMakerAssetData (*) |
+ // | 484 | | 12. offset to leftTakerAssetData (*) |
+ // | | 12 * 32 | rightOrder: |
+ // | 516 | | 1. senderAddress |
+ // | 548 | | 2. makerAddress |
+ // | 580 | | 3. takerAddress |
+ // | 612 | | 4. feeRecipientAddress |
+ // | 644 | | 5. makerAssetAmount |
+ // | 676 | | 6. takerAssetAmount |
+ // | 708 | | 7. makerFeeAmount |
+ // | 740 | | 8. takerFeeAmount |
+ // | 772 | | 9. expirationTimeSeconds |
+ // | 804 | | 10. salt |
+ // | 836 | | 11. offset to rightMakerAssetData (*) |
+ // | 868 | | 12. offset to rightTakerAssetData (*) |
+ // | 900 | 32 | leftMakerAssetData Length |
+ // | 932 | a | leftMakerAssetData Contents |
+ // | 932 + a | 32 | leftTakerAssetData Length |
+ // | 964 + a | b | leftTakerAssetData Contents |
+ // | 964 + a + b | 32 | rightMakerAssetData Length |
+ // | 996 + a + b | c | rightMakerAssetData Contents |
+ // | 996 + a + b + c | 32 | rightTakerAssetData Length |
+ // | 1028 + a + b + c | d | rightTakerAssetData Contents |
+ // | 1028 + a + b + c + d | 32 | leftSignature Length |
+ // | 1060 + a + b + c + d | e | leftSignature Contents |
+ // | 1060 + a + b + c + d + e | 32 | rightSignature Length |
+ // | 1092 + a + b + c + d + e | f | rightSignature Contents |
+
+ // Call `EXCHANGE.matchOrders`
+ let matchOrdersSuccess := call(
+ gas, // forward all gas
+ exchange, // call address of Exchange contract
+ 0, // transfer 0 wei
+ 0, // input starts at 0
+ calldatasize(), // length of input
+ 0, // write output over output
+ 288 // length of output is 288 bytes
+ )
+
+ if iszero(matchOrdersSuccess) {
+ // Revert with reason if `matchOrders` call was unsuccessful
+ revert(0, returndatasize())
+ }
+
+ // After calling `matchOrders`, the relevant parts of memory are:
+
+ // | Offset | Length | Contents |
+ // |---------------------------|---------|-------------------------------------------- |
+ // | | 9 * 32 | matchedFillResults |
+ // | 0 | | 1. left.makerAssetFilledAmount |
+ // | 32 | | 2. left.takerAssetFilledAmount |
+ // | 64 | | 3. left.makerFeePaid |
+ // | 96 | | 4. left.takerFeePaid |
+ // | 128 | | 5. right.makerAssetFilledAmount |
+ // | 160 | | 6. right.takerAssetFilledAmount |
+ // | 192 | | 7. right.makerFeePaid |
+ // | 224 | | 8. right.takerFeePaid |
+ // | 256 | | 9. leftMakerAssetSpreadAmount |
+ // | | 12 * 32 | rightOrder: |
+ // | 516 | | 1. senderAddress |
+ // | 548 | | 2. makerAddress |
+ // | 580 | | 3. takerAddress |
+ // | 612 | | 4. feeRecipientAddress |
+ // | 644 | | 5. makerAssetAmount |
+ // | 676 | | 6. takerAssetAmount |
+ // | 708 | | 7. makerFeeAmount |
+ // | 740 | | 8. takerFeeAmount |
+ // | 772 | | 9. expirationTimeSeconds |
+ // | 804 | | 10. salt |
+ // | 836 | | 11. offset to rightMakerAssetData (*) |
+ // | 868 | | 12. offset to rightTakerAssetData (*) |
+
+ let rightOrderStart := add(calldataload(36), 4)
+
+ // Only call `fillOrder` if a spread was taken and `rightOrder` has not been completely filled
+ if and(
+ gt(mload(256), 0), // gt(leftMakerAssetSpreadAmount, 0)
+ lt(mload(160), calldataload(add(rightOrderStart, 160))) // lt(rightOrderTakerAssetFilledAmount, rightOrderTakerAssetAmount)
+ ) {
+
+ // We want the following layout in memory before calling `fillOrder`:
+
+ // | Offset | Length | Contents |
+ // |---------------------------|---------|-------------------------------------------- |
+ // | 416 | 4 | `fillOrder` function selector |
+ // | | 3 * 32 | function parameters: |
+ // | 420 | | 1. offset to rightOrder (*) |
+ // | 452 | | 2. takerAssetFillAmount |
+ // | 484 | | 3. offset to rightSignature (*) |
+ // | | 12 * 32 | rightOrder: |
+ // | 516 | | 1. senderAddress |
+ // | 548 | | 2. makerAddress |
+ // | 580 | | 3. takerAddress |
+ // | 612 | | 4. feeRecipientAddress |
+ // | 644 | | 5. makerAssetAmount |
+ // | 676 | | 6. takerAssetAmount |
+ // | 708 | | 7. makerFeeAmount |
+ // | 740 | | 8. takerFeeAmount |
+ // | 772 | | 9. expirationTimeSeconds |
+ // | 804 | | 10. salt |
+ // | 836 | | 11. offset to rightMakerAssetData (*) |
+ // | 868 | | 12. offset to rightTakerAssetData (*) |
+ // | 900 | 32 | rightMakerAssetData Length |
+ // | 932 | a | rightMakerAssetData Contents |
+ // | 932 + a | 32 | rightTakerAssetData Length |
+ // | 964 | b | rightTakerAssetData Contents |
+ // | 964 + b | 32 | rightSigature Length (always 0) |
+
+ // We assume that `leftOrder.makerAssetData == rightOrder.takerAssetData` and `leftOrder.takerAssetData == rightOrder.makerAssetData`
+ // `EXCHANGE.matchOrders` already makes this assumption, so it is likely
+ // that the `rightMakerAssetData` and `rightTakerAssetData` in calldata are empty
+
+ let leftOrderStart := add(calldataload(4), 4)
+
+ // Calculate locations of `leftMakerAssetData` and `leftTakerAssetData` in calldata
+ let leftMakerAssetDataStart := add(
+ leftOrderStart,
+ calldataload(add(leftOrderStart, 320)) // offset to `leftMakerAssetData`
+ )
+ let leftTakerAssetDataStart := add(
+ leftOrderStart,
+ calldataload(add(leftOrderStart, 352)) // offset to `leftTakerAssetData`
+ )
+
+ // Load lengths of `leftMakerAssetData` and `leftTakerAssetData`
+ let leftMakerAssetDataLen := calldataload(leftMakerAssetDataStart)
+ let leftTakerAssetDataLen := calldataload(leftTakerAssetDataStart)
+
+ // Write offset to `rightMakerAssetData`
+ mstore(add(rightOrderStart, 320), 384)
+
+ // Write offset to `rightTakerAssetData`
+ let rightTakerAssetDataOffset := add(416, leftTakerAssetDataLen)
+ mstore(add(rightOrderStart, 352), rightTakerAssetDataOffset)
+
+ // Copy `leftTakerAssetData` from calldata onto `rightMakerAssetData` in memory
+ calldatacopy(
+ add(rightOrderStart, 384), // `rightMakerAssetDataStart`
+ leftTakerAssetDataStart,
+ add(leftTakerAssetDataLen, 32)
+ )
+
+ // Copy `leftMakerAssetData` from calldata onto `rightTakerAssetData` in memory
+ calldatacopy(
+ add(rightOrderStart, rightTakerAssetDataOffset), // `rightTakerAssetDataStart`
+ leftMakerAssetDataStart,
+ add(leftMakerAssetDataLen, 32)
+ )
+
+ // Write length of signature (always 0 since signature was previously validated)
+ let rightSignatureStart := add(
+ add(rightOrderStart, rightTakerAssetDataOffset), // `rightTakerAssetDataStart`
+ add(leftMakerAssetDataLen, 32)
+ )
+ mstore(rightSignatureStart, 0)
+
+ let cdStart := sub(rightOrderStart, 100)
+
+ // `fillOrder` selector = 0xb4be83d5
+ mstore(cdStart, 0xb4be83d500000000000000000000000000000000000000000000000000000000)
+
+ // Write offset to `rightOrder`
+ mstore(add(cdStart, 4), 96)
+
+ // Write `takerAssetFillAmount`
+ mstore(add(cdStart, 36), mload(256))
+
+ // Write offset to `rightSignature`
+ mstore(add(cdStart, 68), sub(rightSignatureStart, add(cdStart, 4)))
+
+ let fillOrderSuccess := call(
+ gas, // forward all gas
+ exchange, // call address of Exchange contract
+ 0, // transfer 0 wei
+ cdStart, // start of input
+ sub(add(rightSignatureStart, 32), cdStart), // length of input is end - start
+ 0, // write output over input
+ 128 // length of output is 128 bytes
+ )
+
+ if fillOrderSuccess {
+ return(0, 0)
+ }
+
+ // Revert with reason if `fillOrder` call was unsuccessful
+ revert(0, returndatasize())
+ }
+
+ // Return if `matchOrders` call successful and `fillOrder` was not called
+ return(0, 0)
+ }
+
+ // Revert if undefined function is called
+ revert(0, 0)
+ }
+ }
+}
diff --git a/contracts/extensions/contracts/OrderMatcher/OrderMatcher.sol b/contracts/extensions/contracts/OrderMatcher/OrderMatcher.sol
new file mode 100644
index 000000000..b6f1566cf
--- /dev/null
+++ b/contracts/extensions/contracts/OrderMatcher/OrderMatcher.sol
@@ -0,0 +1,37 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity 0.4.24;
+
+import "../../utils/Ownable/Ownable.sol";
+import "./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..a0b3aa4c7
--- /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..455259238
--- /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 "../../../protocol/Exchange/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..ed4aee925
--- /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 "../../../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..a7c170b42
--- /dev/null
+++ b/contracts/extensions/contracts/OrderMatcher/libs/LibConstants.sol
@@ -0,0 +1,53 @@
+/*
+
+ 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 "../../../protocol/Exchange/interfaces/IExchange.sol";
+
+
+contract LibConstants {
+
+ bytes4 constant internal ERC20_TRANSFER_SELECTOR = bytes4(keccak256("transfer(address,uint256)"));
+ bytes4 constant internal ERC20_DATA_ID = bytes4(keccak256("ERC20Token(address)"));
+ bytes4 constant internal ERC721_DATA_ID = bytes4(keccak256("ERC721Token(address,uint256)"));
+
+ // 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..32cfddf1c
--- /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/protocol/src/artifacts/index.ts b/contracts/protocol/src/artifacts/index.ts
index 1d53ceb04..2a9de78d2 100644
--- a/contracts/protocol/src/artifacts/index.ts
+++ b/contracts/protocol/src/artifacts/index.ts
@@ -6,6 +6,7 @@ import * as ERC721Proxy from '../../generated-artifacts/ERC721Proxy.json';
import * as Exchange from '../../generated-artifacts/Exchange.json';
import * as MixinAuthorizable from '../../generated-artifacts/MixinAuthorizable.json';
import * as MultiAssetProxy from '../../generated-artifacts/MultiAssetProxy.json';
+import * as OrderMatcher from '../../generated-artifacts/OrderMatcher.json';
import * as OrderValidator from '../../generated-artifacts/OrderValidator.json';
import * as TestAssetProxyDispatcher from '../../generated-artifacts/TestAssetProxyDispatcher.json';
import * as TestAssetProxyOwner from '../../generated-artifacts/TestAssetProxyOwner.json';
@@ -20,6 +21,7 @@ export const artifacts = {
Exchange: Exchange as ContractArtifact,
MixinAuthorizable: MixinAuthorizable as ContractArtifact,
MultiAssetProxy: MultiAssetProxy as ContractArtifact,
+ OrderMatcher: OrderMatcher as ContractArtifact,
OrderValidator: OrderValidator as ContractArtifact,
TestAssetProxyDispatcher: TestAssetProxyDispatcher as ContractArtifact,
TestAssetProxyOwner: TestAssetProxyOwner as ContractArtifact,