Allow v2 orders to be filled if their makerAssetFeeData field uses a v2 order id

This commit is contained in:
Amir Bandeali 2019-12-08 21:02:17 -08:00 committed by Amir
parent 2113fb490d
commit fe9fc6b459
5 changed files with 188 additions and 16 deletions

View File

@ -32,12 +32,14 @@ contract Forwarder is
{
constructor (
address _exchange,
address _exchangeV2,
address _weth
)
public
Ownable()
LibConstants(
_exchange,
_exchangeV2,
_weth
)
MixinForwarderCore()

View File

@ -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.

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -31,6 +31,7 @@ contract TestForwarder is
constructor ()
public
LibConstants(
address(0),
address(0),
address(0)
)