Compare commits

...

1 Commits

Author SHA1 Message Date
Michael Zhu
7f240ffc89 Update Multiplex and NativeOrders contracts to fill RFQ orders using ETH 2021-05-04 18:27:18 -07:00
7 changed files with 201 additions and 41 deletions

View File

@@ -478,7 +478,7 @@ contract MetaTransactionsFeature is
/// @dev Execute a `INativeOrdersFeature.fillRfqOrder()` meta-transaction call
/// by decoding the call args and translating the call to the internal
/// `INativeOrdersFeature._fillRfqOrder()` variant, where we can overrideunimpleme
/// `INativeOrdersFeature._fillRfqOrder()` variant, where we can override
/// the taker address.
function _executeFillRfqOrderCall(ExecuteState memory state)
private

View File

@@ -55,7 +55,7 @@ contract MultiplexFeature is
/// @dev Name of this feature.
string public constant override FEATURE_NAME = "MultiplexFeature";
/// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 1);
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 1, 0);
/// @dev The WETH token contract.
IEtherTokenV06 private immutable weth;
@@ -231,26 +231,55 @@ contract MultiplexFeature is
);
continue;
}
require(
order.takerToken == fillData.inputToken &&
order.makerToken == fillData.outputToken,
"MultiplexFeature::_batchFill/RFQ_ORDER_INVALID_TOKENS"
);
// Try filling the RFQ order. Swallows reverts.
try
INativeOrdersFeature(address(this))._fillRfqOrder
(
order,
signature,
inputTokenAmount.safeDowncastToUint128(),
msg.sender
)
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
// Increment the sold and bought amounts.
soldAmount = soldAmount.safeAdd(takerTokenFilledAmount);
outputTokenAmount = outputTokenAmount.safeAdd(makerTokenFilledAmount);
} catch {}
if (fillData.inputToken.isTokenETH()) {
require(
order.takerToken == weth &&
order.makerToken == fillData.outputToken,
"MultiplexFeature::_batchFill/RFQ_ORDER_INVALID_TOKENS"
);
inputTokenAmount = LibSafeMathV06.min256(
inputTokenAmount,
remainingEth
);
// Try filling the RFQ order. Swallows reverts.
try
INativeOrdersFeature(address(this))._fillRfqOrderWithEth
{value: inputTokenAmount}
(
order,
signature,
inputTokenAmount.safeDowncastToUint128(),
msg.sender
)
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
// Increment the sold and bought amounts.
soldAmount = soldAmount.safeAdd(takerTokenFilledAmount);
outputTokenAmount = outputTokenAmount.safeAdd(makerTokenFilledAmount);
remainingEth = remainingEth.safeSub(takerTokenFilledAmount);
} catch {}
} else {
require(
order.takerToken == fillData.inputToken &&
order.makerToken == fillData.outputToken,
"MultiplexFeature::_batchFill/RFQ_ORDER_INVALID_TOKENS"
);
// Try filling the RFQ order. Swallows reverts.
try
INativeOrdersFeature(address(this))._fillRfqOrder
(
order,
signature,
inputTokenAmount.safeDowncastToUint128(),
msg.sender
)
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
// Increment the sold and bought amounts.
soldAmount = soldAmount.safeAdd(takerTokenFilledAmount);
outputTokenAmount = outputTokenAmount.safeAdd(makerTokenFilledAmount);
} catch {}
}
} else if (wrappedCall.selector == this._sellToUniswap.selector) {
(address[] memory tokens, bool isSushi) = abi.decode(
wrappedCall.data,

View File

@@ -34,7 +34,7 @@ contract NativeOrdersFeature is
/// @dev Name of this feature.
string public constant override FEATURE_NAME = "LimitOrders";
/// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 2, 0);
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 3, 0);
constructor(
address zeroExAddress,
@@ -69,6 +69,7 @@ contract NativeOrdersFeature is
_registerFeatureFunction(this.fillOrKillRfqOrder.selector);
_registerFeatureFunction(this._fillLimitOrder.selector);
_registerFeatureFunction(this._fillRfqOrder.selector);
_registerFeatureFunction(this._fillRfqOrderWithEth.selector);
_registerFeatureFunction(this.cancelLimitOrder.selector);
_registerFeatureFunction(this.cancelRfqOrder.selector);
_registerFeatureFunction(this.batchCancelLimitOrders.selector);

View File

@@ -137,6 +137,24 @@ interface INativeOrdersFeature is
external
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount);
/// @dev Fill an RFQ order using ETH. Taker token must be WETH.
/// Internal variant.
/// @param order The RFQ order.
/// @param signature The order signature.
/// @param takerTokenFillAmount Maximum taker token to fill this order with.
/// @param taker The order taker.
/// @return takerTokenFilledAmount How much maker token was filled.
/// @return makerTokenFilledAmount How much maker token was filled.
function _fillRfqOrderWithEth(
LibNativeOrder.RfqOrder calldata order,
LibSignature.Signature calldata signature,
uint128 takerTokenFillAmount,
address taker
)
external
payable
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount);
/// @dev Cancel a single limit order. The caller must be the maker or a valid order signer.
/// Silently succeeds if the order has already been cancelled.
/// @param order The limit order.

View File

@@ -66,6 +66,8 @@ abstract contract NativeOrdersSettlement is
uint128 takerTokenFillAmount;
// How much taker token amount has already been filled in this order.
uint128 takerTokenFilledAmount;
bool useEthBalance;
}
/// @dev Params for `_fillLimitOrderPrivate()`
@@ -158,7 +160,8 @@ abstract contract NativeOrdersSettlement is
order,
signature,
takerTokenFillAmount,
msg.sender
msg.sender,
false
);
(takerTokenFilledAmount, makerTokenFilledAmount) = (
results.takerTokenFilledAmount,
@@ -224,7 +227,8 @@ abstract contract NativeOrdersSettlement is
order,
signature,
takerTokenFillAmount,
msg.sender
msg.sender,
false
);
// Must have filled exactly the amount requested.
if (results.takerTokenFilledAmount < takerTokenFillAmount) {
@@ -273,9 +277,7 @@ abstract contract NativeOrdersSettlement is
);
}
/// @dev Fill an RFQ order. Internal variant. ETH protocol fees can be
/// attached to this call. Any unspent ETH will be refunded to
/// `msg.sender` (not `sender`).
/// @dev Fill an RFQ order. Internal variant.
/// @param order The RFQ order.
/// @param signature The order signature.
/// @param takerTokenFillAmount Maximum taker token to fill this order with.
@@ -298,7 +300,8 @@ abstract contract NativeOrdersSettlement is
order,
signature,
takerTokenFillAmount,
taker
taker,
false
);
(takerTokenFilledAmount, makerTokenFilledAmount) = (
results.takerTokenFilledAmount,
@@ -306,6 +309,49 @@ abstract contract NativeOrdersSettlement is
);
}
/// @dev Fill an RFQ order using ETH. Taker token must be WETH.
/// Internal variant.
/// @param order The RFQ order.
/// @param signature The order signature.
/// @param takerTokenFillAmount Maximum taker token to fill this order with.
/// @param taker The order taker.
/// @return takerTokenFilledAmount How much maker token was filled.
/// @return makerTokenFilledAmount How much maker token was filled.
function _fillRfqOrderWithEth(
LibNativeOrder.RfqOrder memory order,
LibSignature.Signature memory signature,
uint128 takerTokenFillAmount,
address taker
)
public
virtual
payable
onlySelf
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
require(
msg.value == takerTokenFillAmount,
"NativeOrdersFeature/ETH_FILL_AMOUNT_MISMATCH"
);
FillNativeOrderResults memory results =
_fillRfqOrderPrivate(
order,
signature,
takerTokenFillAmount,
taker,
true
);
(takerTokenFilledAmount, makerTokenFilledAmount) = (
results.takerTokenFilledAmount,
results.makerTokenFilledAmount
);
if (takerTokenFilledAmount < msg.value) {
uint256 refundAmount = msg.value.safeSub(takerTokenFilledAmount);
(bool success,) = msg.sender.call{value: refundAmount}("");
require(success, "NativeOrdersFeature/ETH_REFUND_FAILED");
}
}
/// @dev Mark what tx.origin addresses are allowed to fill an order that
/// specifies the message sender as its txOrigin.
/// @param origins An array of origin addresses to update.
@@ -393,7 +439,8 @@ abstract contract NativeOrdersSettlement is
makerAmount: params.order.makerAmount,
takerAmount: params.order.takerAmount,
takerTokenFillAmount: params.takerTokenFillAmount,
takerTokenFilledAmount: orderInfo.takerTokenFilledAmount
takerTokenFilledAmount: orderInfo.takerTokenFilledAmount,
useEthBalance: false
})
);
@@ -437,7 +484,8 @@ abstract contract NativeOrdersSettlement is
LibNativeOrder.RfqOrder memory order,
LibSignature.Signature memory signature,
uint128 takerTokenFillAmount,
address taker
address taker,
bool useEthBalance
)
private
returns (FillNativeOrderResults memory results)
@@ -498,7 +546,8 @@ abstract contract NativeOrdersSettlement is
makerAmount: order.makerAmount,
takerAmount: order.takerAmount,
takerTokenFillAmount: takerTokenFillAmount,
takerTokenFilledAmount: orderInfo.takerTokenFilledAmount
takerTokenFilledAmount: orderInfo.takerTokenFilledAmount,
useEthBalance: useEthBalance
})
);
@@ -549,13 +598,22 @@ abstract contract NativeOrdersSettlement is
// function if the order is cancelled.
settleInfo.takerTokenFilledAmount.safeAdd128(takerTokenFilledAmount);
// Transfer taker -> maker.
_transferERC20Tokens(
settleInfo.takerToken,
settleInfo.taker,
settleInfo.maker,
takerTokenFilledAmount
);
if (settleInfo.useEthBalance) {
require(
settleInfo.takerToken == WETH,
"NativeOrdersFeature/USE_ETH_BALANCE_INVALID"
);
WETH.deposit{value: takerTokenFilledAmount}();
WETH.transfer(settleInfo.maker, takerTokenFilledAmount);
} else {
// Transfer taker -> maker.
_transferERC20Tokens(
settleInfo.takerToken,
settleInfo.taker,
settleInfo.maker,
takerTokenFilledAmount
);
}
// Transfer maker -> taker.
_transferERC20Tokens(

View File

@@ -32,12 +32,12 @@ abstract contract FixinProtocolFees {
/// @dev The protocol fee multiplier.
uint32 public immutable PROTOCOL_FEE_MULTIPLIER;
/// @dev The WETH token contract.
IEtherTokenV06 internal immutable WETH;
/// @dev The `FeeCollectorController` contract.
FeeCollectorController private immutable FEE_COLLECTOR_CONTROLLER;
/// @dev Hash of the fee collector init code.
bytes32 private immutable FEE_COLLECTOR_INIT_CODE_HASH;
/// @dev The WETH token contract.
IEtherTokenV06 private immutable WETH;
/// @dev The staking contract.
IStaking private immutable STAKING;

View File

@@ -87,6 +87,60 @@ abstract contract FixinTokenSpender {
}
}
/// @dev Transfers ERC20 tokens from `address(this)` to `to`.
/// @param token The token to spend.
/// @param to The recipient of the tokens.
/// @param amount The amount of `token` to transfer.
function _transferERC20Tokens(
IERC20TokenV06 token,
address to,
uint256 amount
)
internal
{
require(address(token) != address(this), "FixinTokenSpender/CANNOT_INVOKE_SELF");
assembly {
let ptr := mload(0x40) // free memory pointer
// selector for transfer(address,uint256)
mstore(ptr, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(ptr, 0x04), and(to, ADDRESS_MASK))
mstore(add(ptr, 0x24), amount)
let success := call(
gas(),
and(token, ADDRESS_MASK),
0,
ptr,
0x44,
ptr,
32
)
let rdsize := returndatasize()
// Check for ERC20 success. ERC20 tokens should return a boolean,
// but some don't. We accept 0-length return data as success, or at
// least 32 bytes that starts with a 32-byte boolean true.
success := and(
success, // call itself succeeded
or(
iszero(rdsize), // no return data, or
and(
iszero(lt(rdsize, 32)), // at least 32 bytes
eq(mload(ptr), 1) // starts with uint256(1)
)
)
)
if iszero(success) {
returndatacopy(ptr, 0, rdsize)
revert(ptr, rdsize)
}
}
}
/// @dev Gets the maximum amount of an ERC20 token `token` that can be
/// pulled from `owner` by this address.
/// @param token The token to spend.