Compare commits

...

4 Commits

11 changed files with 322 additions and 45 deletions

View File

@@ -15,6 +15,7 @@
pragma solidity ^0.6.5; pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol"; import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
@@ -27,6 +28,7 @@ import "../migrations/LibMigrate.sol";
import "../storage/LibMetaTransactionsStorage.sol"; import "../storage/LibMetaTransactionsStorage.sol";
import "./interfaces/IFeature.sol"; import "./interfaces/IFeature.sol";
import "./interfaces/IMetaTransactionsFeature.sol"; import "./interfaces/IMetaTransactionsFeature.sol";
import "./interfaces/IMultiplexFeature.sol";
import "./interfaces/INativeOrdersFeature.sol"; import "./interfaces/INativeOrdersFeature.sol";
import "./interfaces/ITransformERC20Feature.sol"; import "./interfaces/ITransformERC20Feature.sol";
import "./libs/LibSignature.sol"; import "./libs/LibSignature.sol";
@@ -91,6 +93,9 @@ contract MetaTransactionsFeature is
")" ")"
); );
/// @dev The WETH token contract.
IEtherTokenV06 private immutable WETH;
/// @dev Refunds up to `msg.value` leftover ETH at the end of the call. /// @dev Refunds up to `msg.value` leftover ETH at the end of the call.
modifier refundsAttachedEth() { modifier refundsAttachedEth() {
_; _;
@@ -108,7 +113,12 @@ contract MetaTransactionsFeature is
require(initialBalance <= address(this).balance, "MetaTransactionsFeature/ETH_LEAK"); require(initialBalance <= address(this).balance, "MetaTransactionsFeature/ETH_LEAK");
} }
constructor(address zeroExAddress) public FixinCommon() FixinEIP712(zeroExAddress) {} constructor(
address zeroExAddress,
IEtherTokenV06 weth
) public FixinCommon() FixinEIP712(zeroExAddress) {
WETH = weth;
}
/// @dev Initialize and register this feature. /// @dev Initialize and register this feature.
/// Should be delegatecalled by `Migrate.migrate()`. /// Should be delegatecalled by `Migrate.migrate()`.
@@ -245,6 +255,14 @@ contract MetaTransactionsFeature is
returnResult = _executeFillLimitOrderCall(state); returnResult = _executeFillLimitOrderCall(state);
} else if (state.selector == INativeOrdersFeature.fillRfqOrder.selector) { } else if (state.selector == INativeOrdersFeature.fillRfqOrder.selector) {
returnResult = _executeFillRfqOrderCall(state); returnResult = _executeFillRfqOrderCall(state);
} else if (state.selector == IMultiplexFeature.multiplexBatchSellTokenForToken.selector) {
returnResult = _executeMultiplexBatchSellTokenForTokenCall(state);
} else if (state.selector == IMultiplexFeature.multiplexBatchSellTokenForEth.selector) {
returnResult = _executeMultiplexBatchSellTokenForEthCall(state);
} else if (state.selector == IMultiplexFeature.multiplexMultiHopSellTokenForToken.selector) {
returnResult = _executeMultiplexMultiHopSellTokenForTokenCall(state);
} else if (state.selector == IMultiplexFeature.multiplexMultiHopSellTokenForEth.selector) {
returnResult = _executeMultiplexMultiHopSellTokenForEthCall(state);
} else { } else {
LibMetaTransactionsRichErrors.MetaTransactionUnsupportedFunctionError(state.hash, state.selector).rrevert(); LibMetaTransactionsRichErrors.MetaTransactionUnsupportedFunctionError(state.hash, state.selector).rrevert();
} }
@@ -426,7 +444,7 @@ contract MetaTransactionsFeature is
/// @dev Execute a `INativeOrdersFeature.fillRfqOrder()` meta-transaction call /// @dev Execute a `INativeOrdersFeature.fillRfqOrder()` meta-transaction call
/// by decoding the call args and translating the call to the internal /// 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. /// the taker address.
function _executeFillRfqOrderCall(ExecuteState memory state) private returns (bytes memory returnResult) { function _executeFillRfqOrderCall(ExecuteState memory state) private returns (bytes memory returnResult) {
LibNativeOrder.RfqOrder memory order; LibNativeOrder.RfqOrder memory order;
@@ -455,6 +473,162 @@ contract MetaTransactionsFeature is
); );
} }
/// @dev Execute a `IMultiplexFeature.multiplexBatchSellTokenForToken()` meta-transaction
/// call by decoding the call args and translating the call to the internal
/// `IMultiplexFeature._multiplexBatchSell()` variant, where we can override the
/// msgSender address.
function _executeMultiplexBatchSellTokenForTokenCall(ExecuteState memory state) private returns (bytes memory returnResult) {
IERC20TokenV06 inputToken;
IERC20TokenV06 outputToken;
IMultiplexFeature.BatchSellSubcall[] memory calls;
uint256 sellAmount;
uint256 minBuyAmount;
bytes memory args = _extractArgumentsFromCallData(state.mtx.callData);
(inputToken, outputToken, calls, sellAmount, minBuyAmount) = abi.decode(
args,
(IERC20TokenV06, IERC20TokenV06, IMultiplexFeature.BatchSellSubcall[], uint256, uint256)
);
return
_callSelf(
state.hash,
abi.encodeWithSelector(
IMultiplexFeature._multiplexBatchSell.selector,
IMultiplexFeature.BatchSellParams({
inputToken: inputToken,
outputToken: outputToken,
sellAmount: sellAmount,
calls: calls,
useSelfBalance: false,
recipient: state.mtx.signer,
msgSender: state.mtx.signer
}),
minBuyAmount
),
state.mtx.value
);
}
/// @dev Execute a `IMultiplexFeature.multiplexBatchSellTokenForEth()` meta-transaction
/// call by decoding the call args and translating the call to the internal
/// `IMultiplexFeature._multiplexBatchSellTokenForEth()` variant, where we can override the
/// msgSender address.
function _executeMultiplexBatchSellTokenForEthCall(ExecuteState memory state) private returns (bytes memory returnResult) {
IERC20TokenV06 inputToken;
IMultiplexFeature.BatchSellSubcall[] memory calls;
uint256 sellAmount;
uint256 minBuyAmount;
bytes memory args = _extractArgumentsFromCallData(state.mtx.callData);
(inputToken, calls, sellAmount, minBuyAmount) = abi.decode(
args,
(IERC20TokenV06, IMultiplexFeature.BatchSellSubcall[], uint256, uint256)
);
returnResult = _callSelf(
state.hash,
abi.encodeWithSelector(
IMultiplexFeature._multiplexBatchSell.selector,
IMultiplexFeature.BatchSellParams({
inputToken: inputToken,
outputToken: IERC20TokenV06(WETH),
sellAmount: sellAmount,
calls: calls,
useSelfBalance: false,
recipient: address(this),
msgSender: state.mtx.signer
}),
minBuyAmount
),
state.mtx.value
);
// Unwrap and transfer WETH
uint256 boughtAmount = abi.decode(returnResult, (uint256));
WETH.withdraw(boughtAmount);
_transferEth(state.mtx.signer, boughtAmount);
}
/// @dev Execute a `IMultiplexFeature.multiplexMultiHopSellTokenForToken()` meta-transaction
/// call by decoding the call args and translating the call to the internal
/// `IMultiplexFeature._multiplexMultiHopSell()` variant, where we can override the
/// msgSender address.
function _executeMultiplexMultiHopSellTokenForTokenCall(ExecuteState memory state) private returns (bytes memory returnResult) {
address[] memory tokens;
IMultiplexFeature.MultiHopSellSubcall[] memory calls;
uint256 sellAmount;
uint256 minBuyAmount;
bytes memory args = _extractArgumentsFromCallData(state.mtx.callData);
(tokens, calls, sellAmount, minBuyAmount) = abi.decode(
args,
(address[], IMultiplexFeature.MultiHopSellSubcall[], uint256, uint256)
);
return
_callSelf(
state.hash,
abi.encodeWithSelector(
IMultiplexFeature._multiplexMultiHopSell.selector,
IMultiplexFeature.MultiHopSellParams({
tokens: tokens,
sellAmount: sellAmount,
calls: calls,
useSelfBalance: false,
recipient: state.mtx.signer,
msgSender: state.mtx.signer
}),
minBuyAmount
),
state.mtx.value
);
}
/// @dev Execute a `IMultiplexFeature.multiplexMultiHopSellTokenForEth()` meta-transaction
/// call by decoding the call args and translating the call to the internal
/// `IMultiplexFeature._multiplexMultiHopSellTokenForEth()` variant, where we can override the
/// msgSender address.
function _executeMultiplexMultiHopSellTokenForEthCall(ExecuteState memory state) private returns (bytes memory returnResult) {
address[] memory tokens;
IMultiplexFeature.MultiHopSellSubcall[] memory calls;
uint256 sellAmount;
uint256 minBuyAmount;
bytes memory args = _extractArgumentsFromCallData(state.mtx.callData);
(tokens, calls, sellAmount, minBuyAmount) = abi.decode(
args,
(address[], IMultiplexFeature.MultiHopSellSubcall[], uint256, uint256)
);
require(
tokens[tokens.length - 1] == address(WETH),
"MetaTransactionsFeature::multiplexMultiHopSellTokenForEth/NOT_WETH"
);
returnResult = _callSelf(
state.hash,
abi.encodeWithSelector(
IMultiplexFeature._multiplexMultiHopSell.selector,
IMultiplexFeature.MultiHopSellParams({
tokens: tokens,
sellAmount: sellAmount,
calls: calls,
useSelfBalance: false,
recipient: address(this),
msgSender: state.mtx.signer
}),
minBuyAmount
),
state.mtx.value
);
// Unwrap and transfer WETH
uint256 boughtAmount = abi.decode(returnResult, (uint256));
WETH.withdraw(boughtAmount);
_transferEth(state.mtx.signer, boughtAmount);
}
/// @dev Make an arbitrary internal, meta-transaction call. /// @dev Make an arbitrary internal, meta-transaction call.
/// Warning: Do not let unadulterated `callData` into this function. /// Warning: Do not let unadulterated `callData` into this function.
function _callSelf(bytes32 hash, bytes memory callData, uint256 value) private returns (bytes memory returnResult) { function _callSelf(bytes32 hash, bytes memory callData, uint256 value) private returns (bytes memory returnResult) {

View File

@@ -70,6 +70,7 @@ contract UniswapV3Feature is IFeature, IUniswapV3Feature, FixinCommon, FixinToke
_registerFeatureFunction(this.sellEthForTokenToUniswapV3.selector); _registerFeatureFunction(this.sellEthForTokenToUniswapV3.selector);
_registerFeatureFunction(this.sellTokenForEthToUniswapV3.selector); _registerFeatureFunction(this.sellTokenForEthToUniswapV3.selector);
_registerFeatureFunction(this.sellTokenForTokenToUniswapV3.selector); _registerFeatureFunction(this.sellTokenForTokenToUniswapV3.selector);
_registerFeatureFunction(this._sellTokenForTokenToUniswapV3.selector);
_registerFeatureFunction(this._sellHeldTokenForTokenToUniswapV3.selector); _registerFeatureFunction(this._sellHeldTokenForTokenToUniswapV3.selector);
_registerFeatureFunction(this.uniswapV3SwapCallback.selector); _registerFeatureFunction(this.uniswapV3SwapCallback.selector);
return LibMigrate.MIGRATE_SUCCESS; return LibMigrate.MIGRATE_SUCCESS;
@@ -139,6 +140,23 @@ contract UniswapV3Feature is IFeature, IUniswapV3Feature, FixinCommon, FixinToke
buyAmount = _swap(encodedPath, sellAmount, minBuyAmount, msg.sender, _normalizeRecipient(recipient)); buyAmount = _swap(encodedPath, sellAmount, minBuyAmount, msg.sender, _normalizeRecipient(recipient));
} }
/// @dev Sell a token for another token directly against uniswap v3. Internal variant.
/// @param encodedPath Uniswap-encoded path.
/// @param sellAmount amount of the first token in the path to sell.
/// @param minBuyAmount Minimum amount of the last token in the path to buy.
/// @param recipient The recipient of the bought tokens. Can be zero for payer.
/// @param payer The address to pull the sold tokens from.
/// @return buyAmount Amount of the last token in the path bought.
function _sellTokenForTokenToUniswapV3(
bytes memory encodedPath,
uint256 sellAmount,
uint256 minBuyAmount,
address recipient,
address payer
) public override onlySelf returns (uint256 buyAmount) {
buyAmount = _swap(encodedPath, sellAmount, minBuyAmount, payer, _normalizeRecipient(recipient, payer));
}
/// @dev Sell a token for another token directly against uniswap v3. /// @dev Sell a token for another token directly against uniswap v3.
/// Private variant, uses tokens held by `address(this)`. /// Private variant, uses tokens held by `address(this)`.
/// @param encodedPath Uniswap-encoded path. /// @param encodedPath Uniswap-encoded path.
@@ -337,8 +355,13 @@ contract UniswapV3Feature is IFeature, IUniswapV3Feature, FixinCommon, FixinToke
} }
} }
// Convert null address values to fallback.
function _normalizeRecipient(address recipient, address alternative) private pure returns (address payable normalizedRecipient) {
return recipient == address(0) ? payable(alternative) : payable(recipient);
}
// Convert null address values to msg.sender. // Convert null address values to msg.sender.
function _normalizeRecipient(address recipient) private view returns (address payable normalizedRecipient) { function _normalizeRecipient(address recipient) private view returns (address payable normalizedRecipient) {
return recipient == address(0) ? msg.sender : payable(recipient); return _normalizeRecipient(recipient, msg.sender);
} }
} }

View File

@@ -46,6 +46,8 @@ interface IMultiplexFeature {
bool useSelfBalance; bool useSelfBalance;
// The recipient of the bought output tokens. // The recipient of the bought output tokens.
address recipient; address recipient;
// The sender of the transaction.
address msgSender;
} }
// Represents a constituent call of a batch sell. // Represents a constituent call of a batch sell.
@@ -75,6 +77,8 @@ interface IMultiplexFeature {
bool useSelfBalance; bool useSelfBalance;
// The recipient of the bought output tokens. // The recipient of the bought output tokens.
address recipient; address recipient;
// The sender of the transaction.
address msgSender;
} }
// Represents a constituent call of a multi-hop sell. // Represents a constituent call of a multi-hop sell.
@@ -153,6 +157,17 @@ interface IMultiplexFeature {
uint256 minBuyAmount uint256 minBuyAmount
) external returns (uint256 boughtAmount); ) external returns (uint256 boughtAmount);
/// @dev Executes a multiplex BatchSell using the given
/// parameters. Internal only.
/// @param params The parameters for the BatchSell.
/// @param minBuyAmount The minimum amount of `params.outputToken`
/// that must be bought for this function to not revert.
/// @return boughtAmount The amount of `params.outputToken` bought.
function _multiplexBatchSell(
BatchSellParams memory params,
uint256 minBuyAmount
) external returns (uint256 boughtAmount);
/// @dev Sells attached ETH via the given sequence of tokens /// @dev Sells attached ETH via the given sequence of tokens
/// and calls. `tokens[0]` must be WETH. /// and calls. `tokens[0]` must be WETH.
/// The last token in `tokens` is the output token that /// The last token in `tokens` is the output token that
@@ -204,4 +219,15 @@ interface IMultiplexFeature {
uint256 sellAmount, uint256 sellAmount,
uint256 minBuyAmount uint256 minBuyAmount
) external returns (uint256 boughtAmount); ) external returns (uint256 boughtAmount);
/// @dev Executes a multiplex MultiHopSell using the given
/// parameters. Internal only.
/// @param params The parameters for the MultiHopSell.
/// @param minBuyAmount The minimum amount of the output token
/// that must be bought for this function to not revert.
/// @return boughtAmount The amount of the output token bought.
function _multiplexMultiHopSell(
MultiHopSellParams memory params,
uint256 minBuyAmount
) external returns (uint256 boughtAmount);
} }

View File

@@ -56,6 +56,21 @@ interface IUniswapV3Feature {
address recipient address recipient
) external returns (uint256 buyAmount); ) external returns (uint256 buyAmount);
/// @dev Sell a token for another token directly against uniswap v3. Internal variant.
/// @param encodedPath Uniswap-encoded path.
/// @param sellAmount amount of the first token in the path to sell.
/// @param minBuyAmount Minimum amount of the last token in the path to buy.
/// @param recipient The recipient of the bought tokens. Can be zero for payer.
/// @param payer The address to pull the sold tokens from.
/// @return buyAmount Amount of the last token in the path bought.
function _sellTokenForTokenToUniswapV3(
bytes memory encodedPath,
uint256 sellAmount,
uint256 minBuyAmount,
address recipient,
address payer
) external returns (uint256 buyAmount);
/// @dev Sell a token for another token directly against uniswap v3. /// @dev Sell a token for another token directly against uniswap v3.
/// Private variant, uses tokens held by `address(this)`. /// Private variant, uses tokens held by `address(this)`.
/// @param encodedPath Uniswap-encoded path. /// @param encodedPath Uniswap-encoded path.

View File

@@ -80,9 +80,11 @@ contract MultiplexFeature is
_registerFeatureFunction(this.multiplexBatchSellEthForToken.selector); _registerFeatureFunction(this.multiplexBatchSellEthForToken.selector);
_registerFeatureFunction(this.multiplexBatchSellTokenForEth.selector); _registerFeatureFunction(this.multiplexBatchSellTokenForEth.selector);
_registerFeatureFunction(this.multiplexBatchSellTokenForToken.selector); _registerFeatureFunction(this.multiplexBatchSellTokenForToken.selector);
_registerFeatureFunction(this._multiplexBatchSell.selector);
_registerFeatureFunction(this.multiplexMultiHopSellEthForToken.selector); _registerFeatureFunction(this.multiplexMultiHopSellEthForToken.selector);
_registerFeatureFunction(this.multiplexMultiHopSellTokenForEth.selector); _registerFeatureFunction(this.multiplexMultiHopSellTokenForEth.selector);
_registerFeatureFunction(this.multiplexMultiHopSellTokenForToken.selector); _registerFeatureFunction(this.multiplexMultiHopSellTokenForToken.selector);
_registerFeatureFunction(this._multiplexMultiHopSell.selector);
return LibMigrate.MIGRATE_SUCCESS; return LibMigrate.MIGRATE_SUCCESS;
} }
@@ -103,14 +105,15 @@ contract MultiplexFeature is
// WETH is now held by this contract, // WETH is now held by this contract,
// so `useSelfBalance` is true. // so `useSelfBalance` is true.
return return
_multiplexBatchSell( _multiplexBatchSellPrivate(
BatchSellParams({ BatchSellParams({
inputToken: WETH, inputToken: WETH,
outputToken: outputToken, outputToken: outputToken,
sellAmount: msg.value, sellAmount: msg.value,
calls: calls, calls: calls,
useSelfBalance: true, useSelfBalance: true,
recipient: msg.sender recipient: msg.sender,
msgSender: msg.sender
}), }),
minBuyAmount minBuyAmount
); );
@@ -133,14 +136,15 @@ contract MultiplexFeature is
// The outputToken is implicitly WETH. The `recipient` // The outputToken is implicitly WETH. The `recipient`
// of the WETH is set to this contract, since we // of the WETH is set to this contract, since we
// must unwrap the WETH and transfer the resulting ETH. // must unwrap the WETH and transfer the resulting ETH.
boughtAmount = _multiplexBatchSell( boughtAmount = _multiplexBatchSellPrivate(
BatchSellParams({ BatchSellParams({
inputToken: inputToken, inputToken: inputToken,
outputToken: WETH, outputToken: WETH,
sellAmount: sellAmount, sellAmount: sellAmount,
calls: calls, calls: calls,
useSelfBalance: false, useSelfBalance: false,
recipient: address(this) recipient: address(this),
msgSender: msg.sender
}), }),
minBuyAmount minBuyAmount
); );
@@ -167,26 +171,41 @@ contract MultiplexFeature is
uint256 minBuyAmount uint256 minBuyAmount
) public override returns (uint256 boughtAmount) { ) public override returns (uint256 boughtAmount) {
return return
_multiplexBatchSell( _multiplexBatchSellPrivate(
BatchSellParams({ BatchSellParams({
inputToken: inputToken, inputToken: inputToken,
outputToken: outputToken, outputToken: outputToken,
sellAmount: sellAmount, sellAmount: sellAmount,
calls: calls, calls: calls,
useSelfBalance: false, useSelfBalance: false,
recipient: msg.sender recipient: msg.sender,
msgSender: msg.sender
}), }),
minBuyAmount minBuyAmount
); );
} }
/// @dev Executes a batch sell and checks that at least
/// `minBuyAmount` of `outputToken` was bought. Internal
/// variant.
/// @param params Batch sell parameters.
/// @param minBuyAmount The minimum amount of `outputToken` that
/// must be bought for this function to not revert.
/// @return boughtAmount The amount of `outputToken` bought.
function _multiplexBatchSell(
BatchSellParams memory params,
uint256 minBuyAmount
) public override onlySelf returns (uint256 boughtAmount) {
return _multiplexBatchSellPrivate(params, minBuyAmount);
}
/// @dev Executes a batch sell and checks that at least /// @dev Executes a batch sell and checks that at least
/// `minBuyAmount` of `outputToken` was bought. /// `minBuyAmount` of `outputToken` was bought.
/// @param params Batch sell parameters. /// @param params Batch sell parameters.
/// @param minBuyAmount The minimum amount of `outputToken` that /// @param minBuyAmount The minimum amount of `outputToken` that
/// must be bought for this function to not revert. /// must be bought for this function to not revert.
/// @return boughtAmount The amount of `outputToken` bought. /// @return boughtAmount The amount of `outputToken` bought.
function _multiplexBatchSell( function _multiplexBatchSellPrivate(
BatchSellParams memory params, BatchSellParams memory params,
uint256 minBuyAmount uint256 minBuyAmount
) private returns (uint256 boughtAmount) { ) private returns (uint256 boughtAmount) {
@@ -226,13 +245,14 @@ contract MultiplexFeature is
// WETH is now held by this contract, // WETH is now held by this contract,
// so `useSelfBalance` is true. // so `useSelfBalance` is true.
return return
_multiplexMultiHopSell( _multiplexMultiHopSellPrivate(
MultiHopSellParams({ MultiHopSellParams({
tokens: tokens, tokens: tokens,
sellAmount: msg.value, sellAmount: msg.value,
calls: calls, calls: calls,
useSelfBalance: true, useSelfBalance: true,
recipient: msg.sender recipient: msg.sender,
msgSender: msg.sender
}), }),
minBuyAmount minBuyAmount
); );
@@ -262,13 +282,14 @@ contract MultiplexFeature is
); );
// The `recipient of the WETH is set to this contract, since // The `recipient of the WETH is set to this contract, since
// we must unwrap the WETH and transfer the resulting ETH. // we must unwrap the WETH and transfer the resulting ETH.
boughtAmount = _multiplexMultiHopSell( boughtAmount = _multiplexMultiHopSellPrivate(
MultiHopSellParams({ MultiHopSellParams({
tokens: tokens, tokens: tokens,
sellAmount: sellAmount, sellAmount: sellAmount,
calls: calls, calls: calls,
useSelfBalance: false, useSelfBalance: false,
recipient: address(this) recipient: address(this),
msgSender: msg.sender
}), }),
minBuyAmount minBuyAmount
); );
@@ -297,25 +318,38 @@ contract MultiplexFeature is
uint256 minBuyAmount uint256 minBuyAmount
) public override returns (uint256 boughtAmount) { ) public override returns (uint256 boughtAmount) {
return return
_multiplexMultiHopSell( _multiplexMultiHopSellPrivate(
MultiHopSellParams({ MultiHopSellParams({
tokens: tokens, tokens: tokens,
sellAmount: sellAmount, sellAmount: sellAmount,
calls: calls, calls: calls,
useSelfBalance: false, useSelfBalance: false,
recipient: msg.sender recipient: msg.sender,
msgSender: msg.sender
}), }),
minBuyAmount minBuyAmount
); );
} }
/// @dev Executes a multi-hop sell. Internal variant.
/// @param params Multi-hop sell parameters.
/// @param minBuyAmount The minimum amount of output tokens that
/// must be bought for this function to not revert.
/// @return boughtAmount The amount of output tokens bought.
function _multiplexMultiHopSell(
MultiHopSellParams memory params,
uint256 minBuyAmount
) public override onlySelf returns (uint256 boughtAmount) {
return _multiplexMultiHopSellPrivate(params, minBuyAmount);
}
/// @dev Executes a multi-hop sell and checks that at least /// @dev Executes a multi-hop sell and checks that at least
/// `minBuyAmount` of output tokens were bought. /// `minBuyAmount` of output tokens were bought.
/// @param params Multi-hop sell parameters. /// @param params Multi-hop sell parameters.
/// @param minBuyAmount The minimum amount of output tokens that /// @param minBuyAmount The minimum amount of output tokens that
/// must be bought for this function to not revert. /// must be bought for this function to not revert.
/// @return boughtAmount The amount of output tokens bought. /// @return boughtAmount The amount of output tokens bought.
function _multiplexMultiHopSell( function _multiplexMultiHopSellPrivate(
MultiHopSellParams memory params, MultiHopSellParams memory params,
uint256 minBuyAmount uint256 minBuyAmount
) private returns (uint256 boughtAmount) { ) private returns (uint256 boughtAmount) {
@@ -387,14 +421,14 @@ contract MultiplexFeature is
// amount of the multi-hop fill. // amount of the multi-hop fill.
state.outputTokenAmount = params.sellAmount; state.outputTokenAmount = params.sellAmount;
// The first call may expect the input tokens to be held by // The first call may expect the input tokens to be held by
// `msg.sender`, `address(this)`, or some other address. // `msgSender`, `address(this)`, or some other address.
// Compute the expected address and transfer the input tokens // Compute the expected address and transfer the input tokens
// there if necessary. // there if necessary.
state.from = _computeHopTarget(params, 0); state.from = _computeHopTarget(params, 0);
// If the input tokens are currently held by `msg.sender` but // If the input tokens are currently held by `msgSender` but
// the first hop expects them elsewhere, perform a `transferFrom`. // the first hop expects them elsewhere, perform a `transferFrom`.
if (!params.useSelfBalance && state.from != msg.sender) { if (!params.useSelfBalance && state.from != params.msgSender) {
_transferERC20TokensFrom(IERC20TokenV06(params.tokens[0]), msg.sender, state.from, params.sellAmount); _transferERC20TokensFrom(IERC20TokenV06(params.tokens[0]), params.msgSender, state.from, params.sellAmount);
} }
// If the input tokens are currently held by `address(this)` but // If the input tokens are currently held by `address(this)` but
// the first hop expects them elsewhere, perform a `transfer`. // the first hop expects them elsewhere, perform a `transfer`.
@@ -411,7 +445,7 @@ contract MultiplexFeature is
if (subcall.id == MultiplexSubcall.UniswapV2) { if (subcall.id == MultiplexSubcall.UniswapV2) {
_multiHopSellUniswapV2(state, params, subcall.data); _multiHopSellUniswapV2(state, params, subcall.data);
} else if (subcall.id == MultiplexSubcall.UniswapV3) { } else if (subcall.id == MultiplexSubcall.UniswapV3) {
_multiHopSellUniswapV3(state, subcall.data); _multiHopSellUniswapV3(state, params, subcall.data);
} else if (subcall.id == MultiplexSubcall.LiquidityProvider) { } else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
_multiHopSellLiquidityProvider(state, params, subcall.data); _multiHopSellLiquidityProvider(state, params, subcall.data);
} else if (subcall.id == MultiplexSubcall.BatchSell) { } else if (subcall.id == MultiplexSubcall.BatchSell) {
@@ -443,6 +477,8 @@ contract MultiplexFeature is
// Likewise, the recipient of the multi-hop sell is // Likewise, the recipient of the multi-hop sell is
// equal to the recipient of its containing batch sell. // equal to the recipient of its containing batch sell.
multiHopParams.recipient = params.recipient; multiHopParams.recipient = params.recipient;
// The msgSender is the same too.
multiHopParams.msgSender = params.msgSender;
// Execute the nested multi-hop sell. // Execute the nested multi-hop sell.
uint256 outputTokenAmount = _executeMultiHopSell(multiHopParams).outputTokenAmount; uint256 outputTokenAmount = _executeMultiHopSell(multiHopParams).outputTokenAmount;
// Increment the sold and bought amounts. // Increment the sold and bought amounts.
@@ -469,7 +505,7 @@ contract MultiplexFeature is
// If the nested batch sell is the first hop // If the nested batch sell is the first hop
// and `useSelfBalance` for the containing multi- // and `useSelfBalance` for the containing multi-
// hop sell is false, the nested batch sell should // hop sell is false, the nested batch sell should
// pull tokens from `msg.sender` (so `batchSellParams.useSelfBalance` // pull tokens from `msgSender` (so `batchSellParams.useSelfBalance`
// should be false). Otherwise `batchSellParams.useSelfBalance` // should be false). Otherwise `batchSellParams.useSelfBalance`
// should be true. // should be true.
batchSellParams.useSelfBalance = state.hopIndex > 0 || params.useSelfBalance; batchSellParams.useSelfBalance = state.hopIndex > 0 || params.useSelfBalance;
@@ -477,6 +513,8 @@ contract MultiplexFeature is
// that should receive the output tokens of the // that should receive the output tokens of the
// batch sell. // batch sell.
batchSellParams.recipient = state.to; batchSellParams.recipient = state.to;
// msgSender shound be the same too.
batchSellParams.msgSender = params.msgSender;
// Execute the nested batch sell. // Execute the nested batch sell.
state.outputTokenAmount = _executeBatchSell(batchSellParams).boughtAmount; state.outputTokenAmount = _executeBatchSell(batchSellParams).boughtAmount;
} }
@@ -509,25 +547,25 @@ contract MultiplexFeature is
// UniswapV3 uses a callback to pull in the tokens being // UniswapV3 uses a callback to pull in the tokens being
// sold to it. The callback implemented in `UniswapV3Feature` // sold to it. The callback implemented in `UniswapV3Feature`
// can either: // can either:
// - call `transferFrom` to move tokens from `msg.sender` to the // - call `transferFrom` to move tokens from `msgSender` to the
// UniswapV3 pool, or // UniswapV3 pool, or
// - call `transfer` to move tokens from `address(this)` to the // - call `transfer` to move tokens from `address(this)` to the
// UniswapV3 pool. // UniswapV3 pool.
// A nested batch sell is similar, in that it can either: // A nested batch sell is similar, in that it can either:
// - use tokens from `msg.sender`, or // - use tokens from `msgSender`, or
// - use tokens held by `address(this)`. // - use tokens held by `address(this)`.
// Suppose UniswapV3/BatchSell is the first call in the multi-hop // Suppose UniswapV3/BatchSell is the first call in the multi-hop
// path. The input tokens are either held by `msg.sender`, // path. The input tokens are either held by `msgSender`,
// or in the case of `multiplexMultiHopSellEthForToken` WETH is // or in the case of `multiplexMultiHopSellEthForToken` WETH is
// held by `address(this)`. The target is set accordingly. // held by `address(this)`. The target is set accordingly.
// If this is _not_ the first call in the multi-hop path, we // If this is _not_ the first call in the multi-hop path, we
// are dealing with an "intermediate" token in the multi-hop path, // are dealing with an "intermediate" token in the multi-hop path,
// which `msg.sender` may not have an allowance set for. Thus // which `msgSender` may not have an allowance set for. Thus
// target must be set to `address(this)` for `i > 0`. // target must be set to `address(this)` for `i > 0`.
if (i == 0 && !params.useSelfBalance) { if (i == 0 && !params.useSelfBalance) {
target = msg.sender; target = params.msgSender;
} else { } else {
target = address(this); target = address(this);
} }

View File

@@ -66,8 +66,8 @@ abstract contract MultiplexLiquidityProvider is FixinCommon, FixinTokenSpender {
// held by `address(this)`. // held by `address(this)`.
_transferERC20Tokens(params.inputToken, provider, sellAmount); _transferERC20Tokens(params.inputToken, provider, sellAmount);
} else { } else {
// Otherwise, transfer the input tokens from `msg.sender`. // Otherwise, transfer the input tokens from `msgSender`.
_transferERC20TokensFrom(params.inputToken, msg.sender, provider, sellAmount); _transferERC20TokensFrom(params.inputToken, params.msgSender, provider, sellAmount);
} }
// Cache the recipient's balance of the output token. // Cache the recipient's balance of the output token.
uint256 balanceBefore = params.outputToken.balanceOf(params.recipient); uint256 balanceBefore = params.outputToken.balanceOf(params.recipient);

View File

@@ -55,7 +55,7 @@ abstract contract MultiplexOtc is FixinEIP712 {
order, order,
signature, signature,
sellAmount.safeDowncastToUint128(), sellAmount.safeDowncastToUint128(),
msg.sender, params.msgSender,
params.useSelfBalance, params.useSelfBalance,
params.recipient params.recipient
) )

View File

@@ -54,7 +54,7 @@ abstract contract MultiplexRfq is FixinEIP712 {
order, order,
signature, signature,
sellAmount.safeDowncastToUint128(), sellAmount.safeDowncastToUint128(),
msg.sender, params.msgSender,
params.useSelfBalance, params.useSelfBalance,
params.recipient params.recipient
) )

View File

@@ -30,8 +30,8 @@ abstract contract MultiplexTransformERC20 {
) internal { ) internal {
ITransformERC20Feature.TransformERC20Args memory args; ITransformERC20Feature.TransformERC20Args memory args;
// We want the TransformedERC20 event to have // We want the TransformedERC20 event to have
// `msg.sender` as the taker. // `msgSender` as the taker.
args.taker = msg.sender; args.taker = payable(params.msgSender);
args.inputToken = params.inputToken; args.inputToken = params.inputToken;
args.outputToken = params.outputToken; args.outputToken = params.outputToken;
args.inputTokenAmount = sellAmount; args.inputTokenAmount = sellAmount;

View File

@@ -77,7 +77,7 @@ abstract contract MultiplexUniswapV2 is FixinCommon, FixinTokenSpender {
if (params.useSelfBalance) { if (params.useSelfBalance) {
_transferERC20Tokens(IERC20TokenV06(tokens[0]), firstPairAddress, sellAmount); _transferERC20Tokens(IERC20TokenV06(tokens[0]), firstPairAddress, sellAmount);
} else { } else {
_transferERC20TokensFrom(IERC20TokenV06(tokens[0]), msg.sender, firstPairAddress, sellAmount); _transferERC20TokensFrom(IERC20TokenV06(tokens[0]), params.msgSender, firstPairAddress, sellAmount);
} }
// Execute the Uniswap/Sushiswap trade. // Execute the Uniswap/Sushiswap trade.
return _sellToUniswapV2(tokens, sellAmount, isSushi, firstPairAddress, params.recipient); return _sellToUniswapV2(tokens, sellAmount, isSushi, firstPairAddress, params.recipient);

View File

@@ -46,16 +46,16 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
) )
); );
} else { } else {
// Otherwise, we self-delegatecall the normal variant // Otherwise, we self-delegatecall `_sellTokenForTokenToUniswapV3`,
// `sellTokenForTokenToUniswapV3`, which pulls the input token // which pulls the input token from a specified `payer`.
// from `msg.sender`.
(success, resultData) = address(this).delegatecall( (success, resultData) = address(this).delegatecall(
abi.encodeWithSelector( abi.encodeWithSelector(
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector, IUniswapV3Feature._sellTokenForTokenToUniswapV3.selector,
wrappedCallData, wrappedCallData,
sellAmount, sellAmount,
0, 0,
params.recipient params.recipient,
params.msgSender
) )
); );
} }
@@ -70,6 +70,7 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
function _multiHopSellUniswapV3( function _multiHopSellUniswapV3(
IMultiplexFeature.MultiHopSellState memory state, IMultiplexFeature.MultiHopSellState memory state,
IMultiplexFeature.MultiHopSellParams memory params,
bytes memory wrappedCallData bytes memory wrappedCallData
) internal { ) internal {
bool success; bool success;
@@ -88,16 +89,16 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
) )
); );
} else { } else {
// Otherwise, we self-delegatecall the normal variant // Otherwise, we self-delegatecall `_sellTokenForTokenToUniswapV3`,
// `sellTokenForTokenToUniswapV3`, which pulls the input token // which pulls the input token from `msgSender`.
// from `msg.sender`.
(success, resultData) = address(this).delegatecall( (success, resultData) = address(this).delegatecall(
abi.encodeWithSelector( abi.encodeWithSelector(
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector, IUniswapV3Feature._sellTokenForTokenToUniswapV3.selector,
wrappedCallData, wrappedCallData,
state.outputTokenAmount, state.outputTokenAmount,
0, 0,
state.to state.to,
params.msgSender
) )
); );
} }