diff --git a/contracts/exchange-forwarder/contracts/src/Forwarder.sol b/contracts/exchange-forwarder/contracts/src/Forwarder.sol index dd83b57368..8ed8fa0b28 100644 --- a/contracts/exchange-forwarder/contracts/src/Forwarder.sol +++ b/contracts/exchange-forwarder/contracts/src/Forwarder.sol @@ -32,12 +32,14 @@ contract Forwarder is { constructor ( address _exchange, + address _exchangeV2, address _weth ) public Ownable() LibConstants( _exchange, + _exchangeV2, _weth ) MixinForwarderCore() diff --git a/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol b/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol index fa905835f0..08b89587e0 100644 --- a/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol +++ b/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol @@ -30,6 +30,7 @@ import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol"; import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; import "./libs/LibConstants.sol"; import "./libs/LibForwarderRichErrors.sol"; +import "./interfaces/IExchangeV2.sol"; import "./MixinAssets.sol"; @@ -54,23 +55,19 @@ contract MixinExchangeWrapper is internal returns (LibFillResults.FillResults memory fillResults) { - // ABI encode calldata for `fillOrder` - bytes memory fillOrderCalldata = abi.encodeWithSelector( - IExchange(address(0)).fillOrder.selector, + if (order.makerFeeAssetData.readBytes4(0) == EXCHANGE_V2_ORDER_ID) { + return _fillV2OrderNoThrow( + order, + takerAssetFillAmount, + signature + ); + } + + return _fillV3OrderNoThrow( order, takerAssetFillAmount, signature ); - - address exchange = address(EXCHANGE); - (bool didSucceed, bytes memory returnData) = exchange.call(fillOrderCalldata); - if (didSucceed) { - assert(returnData.length == 160); - fillResults = abi.decode(returnData, (LibFillResults.FillResults)); - } - - // fillResults values will be 0 by default if call was unsuccessful - return fillResults; } /// @dev Executes a single call of fillOrder according to the wethSellAmount and @@ -370,6 +367,98 @@ contract MixinExchangeWrapper is } } + /// @dev Fills the input ExchangeV2 order. The `makerFeeAssetData` must be + // equal to EXCHANGE_V2_ORDER_ID (0x770501f8). + /// Returns false if the transaction would otherwise revert. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function _fillV2OrderNoThrow( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + internal + returns (LibFillResults.FillResults memory fillResults) + { + // Strip v3 specific fields from order + IExchangeV2.Order memory v2Order = IExchangeV2.Order({ + makerAddress: order.makerAddress, + takerAddress: order.takerAddress, + feeRecipientAddress: order.feeRecipientAddress, + senderAddress: order.senderAddress, + makerAssetAmount: order.makerAssetAmount, + takerAssetAmount: order.takerAssetAmount, + makerFee: order.makerFee, + takerFee: order.makerFee, + expirationTimeSeconds: order.expirationTimeSeconds, + salt: order.salt, + makerAssetData: order.makerAssetData, + takerAssetData: order.takerAssetData + }); + + // ABI encode calldata for `fillOrder` + bytes memory fillOrderCalldata = abi.encodeWithSelector( + IExchangeV2(address(0)).fillOrder.selector, + v2Order, + takerAssetFillAmount, + signature + ); + + address exchange = address(EXCHANGE_V2); + (bool didSucceed, bytes memory returnData) = exchange.call(fillOrderCalldata); + if (didSucceed) { + assert(returnData.length == 128); + IExchangeV2.FillResults memory v2FillResults = abi.decode(returnData, (IExchangeV2.FillResults)); + + // Add `protocolFeePaid` field to v2 fill results + fillResults = LibFillResults.FillResults({ + makerAssetFilledAmount: v2FillResults.makerAssetFilledAmount, + takerAssetFilledAmount: v2FillResults.takerAssetFilledAmount, + makerFeePaid: v2FillResults.makerFeePaid, + takerFeePaid: v2FillResults.takerFeePaid, + protocolFeePaid: 0 + }); + } + + // fillResults values will be 0 by default if call was unsuccessful + return fillResults; + } + + /// @dev Fills the input ExchangeV3 order. + /// Returns false if the transaction would otherwise revert. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function _fillV3OrderNoThrow( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + internal + returns (LibFillResults.FillResults memory fillResults) + { + // ABI encode calldata for `fillOrder` + bytes memory fillOrderCalldata = abi.encodeWithSelector( + IExchange(address(0)).fillOrder.selector, + order, + takerAssetFillAmount, + signature + ); + + address exchange = address(EXCHANGE); + (bool didSucceed, bytes memory returnData) = exchange.call(fillOrderCalldata); + if (didSucceed) { + assert(returnData.length == 160); + fillResults = abi.decode(returnData, (LibFillResults.FillResults)); + } + + // fillResults values will be 0 by default if call was unsuccessful + return fillResults; + } + /// @dev Checks whether one asset is effectively equal to another asset. /// This is the case if they have the same ERC20Proxy/ERC20BridgeProxy asset data, or if /// one is the ERC20Bridge equivalent of the other. diff --git a/contracts/exchange-forwarder/contracts/src/interfaces/IExchangeV2.sol b/contracts/exchange-forwarder/contracts/src/interfaces/IExchangeV2.sol new file mode 100644 index 0000000000..ae7eb2dc3d --- /dev/null +++ b/contracts/exchange-forwarder/contracts/src/interfaces/IExchangeV2.sol @@ -0,0 +1,60 @@ +/* + + Copyright 2019 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.5.9; +pragma experimental ABIEncoderV2; + + +contract IExchangeV2 { + + // solhint-disable max-line-length + struct Order { + address makerAddress; // Address that created the order. + address takerAddress; // Address that is allowed to fill the order. If set to 0, any address is allowed to fill the order. + address feeRecipientAddress; // Address that will recieve fees when order is filled. + address senderAddress; // Address that is allowed to call Exchange contract methods that affect this order. If set to 0, any address is allowed to call these methods. + uint256 makerAssetAmount; // Amount of makerAsset being offered by maker. Must be greater than 0. + uint256 takerAssetAmount; // Amount of takerAsset being bid on by maker. Must be greater than 0. + uint256 makerFee; // Amount of ZRX paid to feeRecipient by maker when order is filled. If set to 0, no transfer of ZRX from maker to feeRecipient will be attempted. + uint256 takerFee; // Amount of ZRX paid to feeRecipient by taker when order is filled. If set to 0, no transfer of ZRX from taker to feeRecipient will be attempted. + uint256 expirationTimeSeconds; // Timestamp in seconds at which order expires. + uint256 salt; // Arbitrary number to facilitate uniqueness of the order's hash. + bytes makerAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring makerAsset. The last byte references the id of this proxy. + bytes takerAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring takerAsset. The last byte references the id of this proxy. + } + // solhint-enable max-line-length + + struct FillResults { + uint256 makerAssetFilledAmount; // Total amount of makerAsset(s) filled. + uint256 takerAssetFilledAmount; // Total amount of takerAsset(s) filled. + uint256 makerFeePaid; // Total amount of ZRX paid by maker(s) to feeRecipient(s). + uint256 takerFeePaid; // Total amount of ZRX paid by taker to feeRecipients(s). + } + + /// @dev Fills the input order. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function fillOrder( + Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + public; +} diff --git a/contracts/exchange-forwarder/contracts/src/libs/LibConstants.sol b/contracts/exchange-forwarder/contracts/src/libs/LibConstants.sol index 6b40475363..b167b665de 100644 --- a/contracts/exchange-forwarder/contracts/src/libs/LibConstants.sol +++ b/contracts/exchange-forwarder/contracts/src/libs/LibConstants.sol @@ -18,29 +18,49 @@ pragma solidity ^0.5.9; -import "@0x/contracts-utils/contracts/src/LibBytes.sol"; import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol"; +import "../interfaces/IExchangeV2.sol"; contract LibConstants { - using LibBytes for bytes; + uint256 constant internal MAX_UINT = uint256(-1); - uint256 constant internal MAX_UINT = 2**256 - 1; + // The v2 order id is the first 4 bytes of the ExchangeV2 order shema hash. + // bytes4(keccak256(abi.encodePacked( + // "Order(", + // "address makerAddress,", + // "address takerAddress,", + // "address feeRecipientAddress,", + // "address senderAddress,", + // "uint256 makerAssetAmount,", + // "uint256 takerAssetAmount,", + // "uint256 makerFee,", + // "uint256 takerFee,", + // "uint256 expirationTimeSeconds,", + // "uint256 salt,", + // "bytes makerAssetData,", + // "bytes takerAssetData", + // ")" + // ))); + bytes4 constant public EXCHANGE_V2_ORDER_ID = 0x770501f8; // solhint-disable var-name-mixedcase IExchange internal EXCHANGE; + IExchangeV2 internal EXCHANGE_V2; IEtherToken internal ETHER_TOKEN; // solhint-enable var-name-mixedcase constructor ( address _exchange, + address _exchangeV2, address _weth ) public { EXCHANGE = IExchange(_exchange); + EXCHANGE_V2 = IExchangeV2(_exchangeV2); ETHER_TOKEN = IEtherToken(_weth); } } diff --git a/contracts/exchange-forwarder/contracts/test/TestForwarder.sol b/contracts/exchange-forwarder/contracts/test/TestForwarder.sol index 593597da32..fcbfb44b03 100644 --- a/contracts/exchange-forwarder/contracts/test/TestForwarder.sol +++ b/contracts/exchange-forwarder/contracts/test/TestForwarder.sol @@ -31,6 +31,7 @@ contract TestForwarder is constructor () public LibConstants( + address(0), address(0), address(0) )