feat: Multiplex + MetaTransaction integration and MetaTransaction Multi-Fee Support [RFQ-795] [LIT-870] (#665)

* MetaTransactionData changes

* MetaTransactionV2 creation and forge tests

* MetaTransactionData changes

* MetaTransactionV2 creation and forge tests

* add multiplexBatchSellTokenForToken, multiplexMultiHopSellTokenForToken, multiplex TokenForEth functions to metatransactions, add msgSender field to multiplex params

* Ran prettier to clean up

* More linting

* Fixing issues with EIP 712 signature, adding test case against MetaMask, and fixing lint issues

* Addressing suggestions from PR reviewers

* Complex rebase of test code based on changes in #655

* Fixing multiplex test failure

* add some tests for multiplex metatransactions

* prettier

* minor test fix

* cleaning up and adding batchExecuteMetaTransaction tests

* Removing ZERO_ADDRESS

* add multiHopBatchSellOtc to MultiplexFeature, fix _computeHopTarget for MultiplexSubcall.OTC [#667]

* fix _computeHopTarget for otc subcalls

* Fixing multiHopSellOtcOrder when params.useSelfBalance is true

* Making executeMetaTransactionV2 nonpayable and addressing a few other minor issues

* Forge update

* Add MetaTransactionsFeatureV2 to exported contracts

---------

Co-authored-by: abls <112491550+abls@users.noreply.github.com>
Co-authored-by: Duncan Townsend <git@duncancmt.com>
This commit is contained in:
Patrick Dowell 2023-04-19 15:20:28 -07:00 committed by GitHub
parent 8a2305c7b9
commit ff104e7505
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 2111 additions and 51 deletions

@ -1 +1 @@
Subproject commit a2edd39db95df7e9dd3f9ef9edc8c55fefddb6df Subproject commit fc560fa34fa12a335a50c35d92e55a6628ca467c

View File

@ -31,6 +31,7 @@
"./contracts/src/features/FundRecoveryFeature.sol", "./contracts/src/features/FundRecoveryFeature.sol",
"./contracts/src/features/LiquidityProviderFeature.sol", "./contracts/src/features/LiquidityProviderFeature.sol",
"./contracts/src/features/MetaTransactionsFeature.sol", "./contracts/src/features/MetaTransactionsFeature.sol",
"./contracts/src/features/MetaTransactionsFeatureV2.sol",
"./contracts/src/features/NativeOrdersFeature.sol", "./contracts/src/features/NativeOrdersFeature.sol",
"./contracts/src/features/OtcOrdersFeature.sol", "./contracts/src/features/OtcOrdersFeature.sol",
"./contracts/src/features/OwnableFeature.sol", "./contracts/src/features/OwnableFeature.sol",
@ -48,6 +49,7 @@
"./contracts/src/features/interfaces/IFundRecoveryFeature.sol", "./contracts/src/features/interfaces/IFundRecoveryFeature.sol",
"./contracts/src/features/interfaces/ILiquidityProviderFeature.sol", "./contracts/src/features/interfaces/ILiquidityProviderFeature.sol",
"./contracts/src/features/interfaces/IMetaTransactionsFeature.sol", "./contracts/src/features/interfaces/IMetaTransactionsFeature.sol",
"./contracts/src/features/interfaces/IMetaTransactionsFeatureV2.sol",
"./contracts/src/features/interfaces/IMultiplexFeature.sol", "./contracts/src/features/interfaces/IMultiplexFeature.sol",
"./contracts/src/features/interfaces/INativeOrdersEvents.sol", "./contracts/src/features/interfaces/INativeOrdersEvents.sol",
"./contracts/src/features/interfaces/INativeOrdersFeature.sol", "./contracts/src/features/interfaces/INativeOrdersFeature.sol",

@ -1 +1 @@
Subproject commit a2edd39db95df7e9dd3f9ef9edc8c55fefddb6df Subproject commit fc560fa34fa12a335a50c35d92e55a6628ca467c

View File

@ -20,6 +20,7 @@ import "./features/interfaces/ISimpleFunctionRegistryFeature.sol";
import "./features/interfaces/ITokenSpenderFeature.sol"; import "./features/interfaces/ITokenSpenderFeature.sol";
import "./features/interfaces/ITransformERC20Feature.sol"; import "./features/interfaces/ITransformERC20Feature.sol";
import "./features/interfaces/IMetaTransactionsFeature.sol"; import "./features/interfaces/IMetaTransactionsFeature.sol";
import "./features/interfaces/IMetaTransactionsFeatureV2.sol";
import "./features/interfaces/IUniswapFeature.sol"; import "./features/interfaces/IUniswapFeature.sol";
import "./features/interfaces/IUniswapV3Feature.sol"; import "./features/interfaces/IUniswapV3Feature.sol";
import "./features/interfaces/IPancakeSwapFeature.sol"; import "./features/interfaces/IPancakeSwapFeature.sol";
@ -39,6 +40,7 @@ interface IZeroEx is
ISimpleFunctionRegistryFeature, ISimpleFunctionRegistryFeature,
ITransformERC20Feature, ITransformERC20Feature,
IMetaTransactionsFeature, IMetaTransactionsFeature,
IMetaTransactionsFeatureV2,
IUniswapFeature, IUniswapFeature,
IUniswapV3Feature, IUniswapV3Feature,
IPancakeSwapFeature, IPancakeSwapFeature,

View File

@ -0,0 +1,616 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2023 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.6.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/src/IEtherToken.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/LibSafeMathV06.sol";
import "../errors/LibMetaTransactionsRichErrors.sol";
import "../fixins/FixinCommon.sol";
import "../fixins/FixinReentrancyGuard.sol";
import "../fixins/FixinTokenSpender.sol";
import "../fixins/FixinEIP712.sol";
import "../migrations/LibMigrate.sol";
import "../storage/LibMetaTransactionsV2Storage.sol";
import "./interfaces/IFeature.sol";
import "./interfaces/IMetaTransactionsFeatureV2.sol";
import "./interfaces/IMultiplexFeature.sol";
import "./interfaces/INativeOrdersFeature.sol";
import "./interfaces/ITransformERC20Feature.sol";
import "./libs/LibSignature.sol";
/// @dev MetaTransactions feature.
contract MetaTransactionsFeatureV2 is
IFeature,
IMetaTransactionsFeatureV2,
FixinCommon,
FixinReentrancyGuard,
FixinEIP712,
FixinTokenSpender
{
using LibBytesV06 for bytes;
using LibRichErrorsV06 for bytes;
/// @dev Describes the state of a meta transaction.
struct ExecuteState {
// Sender of the meta-transaction.
address sender;
// Hash of the meta-transaction data.
bytes32 hash;
// The meta-transaction data.
MetaTransactionDataV2 mtx;
// The meta-transaction signature (by `mtx.signer`).
LibSignature.Signature signature;
// The selector of the function being called.
bytes4 selector;
// The ETH balance of this contract before performing the call.
uint256 selfBalance;
// The block number at which the meta-transaction was executed.
uint256 executedBlockNumber;
}
/// @dev Arguments for a `TransformERC20.transformERC20()` call.
struct ExternalTransformERC20Args {
IERC20Token inputToken;
IERC20Token outputToken;
uint256 inputTokenAmount;
uint256 minOutputTokenAmount;
ITransformERC20Feature.Transformation[] transformations;
}
/// @dev Name of this feature.
string public constant override FEATURE_NAME = "MetaTransactionsV2";
/// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
/// @dev EIP712 typehash of the `MetaTransactionData` struct.
bytes32 public immutable MTX_EIP712_TYPEHASH =
keccak256(
"MetaTransactionDataV2("
"address signer,"
"address sender,"
"uint256 expirationTimeSeconds,"
"uint256 salt,"
"bytes callData,"
"address feeToken,"
"MetaTransactionFeeData[] fees"
")"
"MetaTransactionFeeData("
"address recipient,"
"uint256 amount"
")"
);
bytes32 public immutable MTX_FEE_TYPEHASH =
keccak256(
"MetaTransactionFeeData("
"address recipient,"
"uint256 amount"
")"
);
/// @dev The WETH token contract.
IEtherToken private immutable WETH;
/// @dev Ensures that the ETH balance of `this` does not go below the
/// initial ETH balance before the call (excluding ETH attached to the call).
modifier doesNotReduceEthBalance() {
uint256 initialBalance = address(this).balance;
_;
require(initialBalance <= address(this).balance, "MetaTransactionsFeatureV2/ETH_LEAK");
}
constructor(address zeroExAddress, IEtherToken weth) public FixinCommon() FixinEIP712(zeroExAddress) {
WETH = weth;
}
/// @dev Initialize and register this feature.
/// Should be delegatecalled by `Migrate.migrate()`.
/// @return success `LibMigrate.SUCCESS` on success.
function migrate() external returns (bytes4 success) {
_registerFeatureFunction(this.executeMetaTransactionV2.selector);
_registerFeatureFunction(this.batchExecuteMetaTransactionsV2.selector);
_registerFeatureFunction(this.getMetaTransactionV2ExecutedBlock.selector);
_registerFeatureFunction(this.getMetaTransactionV2HashExecutedBlock.selector);
_registerFeatureFunction(this.getMetaTransactionV2Hash.selector);
return LibMigrate.MIGRATE_SUCCESS;
}
/// @dev Execute a single meta-transaction.
/// @param mtx The meta-transaction.
/// @param signature The signature by `mtx.signer`.
/// @return returnResult The ABI-encoded result of the underlying call.
function executeMetaTransactionV2(
MetaTransactionDataV2 memory mtx,
LibSignature.Signature memory signature
) public override nonReentrant(REENTRANCY_MTX) doesNotReduceEthBalance returns (bytes memory returnResult) {
ExecuteState memory state;
state.sender = msg.sender;
state.mtx = mtx;
state.hash = getMetaTransactionV2Hash(mtx);
state.signature = signature;
returnResult = _executeMetaTransactionPrivate(state);
}
/// @dev Execute multiple meta-transactions.
/// @param mtxs The meta-transactions.
/// @param signatures The signature by each respective `mtx.signer`.
/// @return returnResults The ABI-encoded results of the underlying calls.
function batchExecuteMetaTransactionsV2(
MetaTransactionDataV2[] memory mtxs,
LibSignature.Signature[] memory signatures
) public override nonReentrant(REENTRANCY_MTX) doesNotReduceEthBalance returns (bytes[] memory returnResults) {
if (mtxs.length != signatures.length) {
LibMetaTransactionsRichErrors
.InvalidMetaTransactionsArrayLengthsError(mtxs.length, signatures.length)
.rrevert();
}
returnResults = new bytes[](mtxs.length);
for (uint256 i = 0; i < mtxs.length; ++i) {
ExecuteState memory state;
state.sender = msg.sender;
state.mtx = mtxs[i];
state.hash = getMetaTransactionV2Hash(mtxs[i]);
state.signature = signatures[i];
returnResults[i] = _executeMetaTransactionPrivate(state);
}
}
/// @dev Get the block at which a meta-transaction has been executed.
/// @param mtx The meta-transaction.
/// @return blockNumber The block height when the meta-transactioin was executed.
function getMetaTransactionV2ExecutedBlock(
MetaTransactionDataV2 memory mtx
) public view override returns (uint256 blockNumber) {
return getMetaTransactionV2HashExecutedBlock(getMetaTransactionV2Hash(mtx));
}
/// @dev Get the block at which a meta-transaction hash has been executed.
/// @param mtxHash The meta-transaction hash.
/// @return blockNumber The block height when the meta-transactioin was executed.
function getMetaTransactionV2HashExecutedBlock(bytes32 mtxHash) public view override returns (uint256 blockNumber) {
return LibMetaTransactionsV2Storage.getStorage().mtxHashToExecutedBlockNumber[mtxHash];
}
/// @dev Get the EIP712 hash of a meta-transaction.
/// @param mtx The meta-transaction.
/// @return mtxHash The EIP712 hash of `mtx`.
function getMetaTransactionV2Hash(MetaTransactionDataV2 memory mtx) public view override returns (bytes32 mtxHash) {
bytes32[] memory feeHashes = new bytes32[](mtx.fees.length);
for (uint256 i = 0; i < mtx.fees.length; ++i) {
feeHashes[i] = keccak256(abi.encode(MTX_FEE_TYPEHASH, mtx.fees[i]));
}
return
_getEIP712Hash(
keccak256(
abi.encode(
MTX_EIP712_TYPEHASH,
mtx.signer,
mtx.sender,
mtx.expirationTimeSeconds,
mtx.salt,
keccak256(mtx.callData),
mtx.feeToken,
keccak256(abi.encodePacked(feeHashes))
)
)
);
}
/// @dev Execute a meta-transaction by `sender`. Low-level, hidden variant.
/// @param state The `ExecuteState` for this metatransaction, with `sender`,
/// `hash`, `mtx`, and `signature` fields filled.
/// @return returnResult The ABI-encoded result of the underlying call.
function _executeMetaTransactionPrivate(ExecuteState memory state) private returns (bytes memory returnResult) {
_validateMetaTransaction(state);
// Mark the transaction executed by storing the block at which it was executed.
// Currently the block number just indicates that the mtx was executed and
// serves no other purpose from within this contract.
LibMetaTransactionsV2Storage.getStorage().mtxHashToExecutedBlockNumber[state.hash] = block.number;
// Pay the fees to the fee recipients.
for (uint256 i = 0; i < state.mtx.fees.length; ++i) {
_transferERC20TokensFrom(
state.mtx.feeToken,
state.mtx.signer,
state.mtx.fees[i].recipient,
state.mtx.fees[i].amount
);
}
// Execute the call based on the selector.
state.selector = state.mtx.callData.readBytes4(0);
if (state.selector == ITransformERC20Feature.transformERC20.selector) {
returnResult = _executeTransformERC20Call(state);
} else if (state.selector == INativeOrdersFeature.fillLimitOrder.selector) {
returnResult = _executeFillLimitOrderCall(state);
} else if (state.selector == INativeOrdersFeature.fillRfqOrder.selector) {
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 {
LibMetaTransactionsRichErrors.MetaTransactionUnsupportedFunctionError(state.hash, state.selector).rrevert();
}
emit MetaTransactionExecuted(state.hash, state.selector, state.mtx.signer, state.mtx.sender);
}
/// @dev Validate that a meta-transaction is executable.
function _validateMetaTransaction(ExecuteState memory state) private view {
// Must be from the required sender, if set.
if (state.mtx.sender != address(0) && state.mtx.sender != state.sender) {
LibMetaTransactionsRichErrors
.MetaTransactionWrongSenderError(state.hash, state.sender, state.mtx.sender)
.rrevert();
}
// Must not be expired.
if (state.mtx.expirationTimeSeconds <= block.timestamp) {
LibMetaTransactionsRichErrors
.MetaTransactionExpiredError(state.hash, block.timestamp, state.mtx.expirationTimeSeconds)
.rrevert();
}
if (LibSignature.getSignerOfHash(state.hash, state.signature) != state.mtx.signer) {
LibSignatureRichErrors
.SignatureValidationError(
LibSignatureRichErrors.SignatureValidationErrorCodes.WRONG_SIGNER,
state.hash,
state.mtx.signer,
// TODO: Remove this field from SignatureValidationError
// when rich reverts are part of the protocol repo.
""
)
.rrevert();
}
// Transaction must not have been already executed.
state.executedBlockNumber = LibMetaTransactionsV2Storage.getStorage().mtxHashToExecutedBlockNumber[state.hash];
if (state.executedBlockNumber != 0) {
LibMetaTransactionsRichErrors
.MetaTransactionAlreadyExecutedError(state.hash, state.executedBlockNumber)
.rrevert();
}
}
/// @dev Execute a `ITransformERC20Feature.transformERC20()` meta-transaction call
/// by decoding the call args and translating the call to the internal
/// `ITransformERC20Feature._transformERC20()` variant, where we can override
/// the taker address.
function _executeTransformERC20Call(ExecuteState memory state) private returns (bytes memory returnResult) {
// HACK(dorothy-zbornak): `abi.decode()` with the individual args
// will cause a stack overflow. But we can prefix the call data with an
// offset to transform it into the encoding for the equivalent single struct arg,
// since decoding a single struct arg consumes far less stack space than
// decoding multiple struct args.
// Where the encoding for multiple args (with the selector ommitted)
// would typically look like:
// | argument | offset |
// |--------------------------|---------|
// | inputToken | 0 |
// | outputToken | 32 |
// | inputTokenAmount | 64 |
// | minOutputTokenAmount | 96 |
// | transformations (offset) | 128 | = 32
// | transformations (data) | 160 |
// We will ABI-decode a single struct arg copy with the layout:
// | argument | offset |
// |--------------------------|---------|
// | (arg 1 offset) | 0 | = 32
// | inputToken | 32 |
// | outputToken | 64 |
// | inputTokenAmount | 96 |
// | minOutputTokenAmount | 128 |
// | transformations (offset) | 160 | = 32
// | transformations (data) | 192 |
ExternalTransformERC20Args memory args;
{
bytes memory encodedStructArgs = new bytes(state.mtx.callData.length - 4 + 32);
// Copy the args data from the original, after the new struct offset prefix.
bytes memory fromCallData = state.mtx.callData;
assert(fromCallData.length >= 160);
uint256 fromMem;
uint256 toMem;
assembly {
// Prefix the calldata with a struct offset,
// which points to just one word over.
mstore(add(encodedStructArgs, 32), 32)
// Copy everything after the selector.
fromMem := add(fromCallData, 36)
// Start copying after the struct offset.
toMem := add(encodedStructArgs, 64)
}
LibBytesV06.memCopy(toMem, fromMem, fromCallData.length - 4);
// Decode call args for `ITransformERC20Feature.transformERC20()` as a struct.
args = abi.decode(encodedStructArgs, (ExternalTransformERC20Args));
}
// Call `ITransformERC20Feature._transformERC20()` (internal variant).
return
_callSelf(
state.hash,
abi.encodeWithSelector(
ITransformERC20Feature._transformERC20.selector,
ITransformERC20Feature.TransformERC20Args({
taker: state.mtx.signer, // taker is mtx signer
inputToken: args.inputToken,
outputToken: args.outputToken,
inputTokenAmount: args.inputTokenAmount,
minOutputTokenAmount: args.minOutputTokenAmount,
transformations: args.transformations,
useSelfBalance: false,
recipient: state.mtx.signer
})
)
);
}
/// @dev Extract arguments from call data by copying everything after the
/// 4-byte selector into a new byte array.
/// @param callData The call data from which arguments are to be extracted.
/// @return args The extracted arguments as a byte array.
function _extractArgumentsFromCallData(bytes memory callData) private pure returns (bytes memory args) {
args = new bytes(callData.length - 4);
uint256 fromMem;
uint256 toMem;
assembly {
fromMem := add(callData, 36) // skip length and 4-byte selector
toMem := add(args, 32) // write after length prefix
}
LibBytesV06.memCopy(toMem, fromMem, args.length);
return args;
}
/// @dev Execute a `INativeOrdersFeature.fillLimitOrder()` meta-transaction call
/// by decoding the call args and translating the call to the internal
/// `INativeOrdersFeature._fillLimitOrder()` variant, where we can override
/// the taker address.
function _executeFillLimitOrderCall(ExecuteState memory state) private returns (bytes memory returnResult) {
LibNativeOrder.LimitOrder memory order;
LibSignature.Signature memory signature;
uint128 takerTokenFillAmount;
bytes memory args = _extractArgumentsFromCallData(state.mtx.callData);
(order, signature, takerTokenFillAmount) = abi.decode(
args,
(LibNativeOrder.LimitOrder, LibSignature.Signature, uint128)
);
return
_callSelf(
state.hash,
abi.encodeWithSelector(
INativeOrdersFeature._fillLimitOrder.selector,
order,
signature,
takerTokenFillAmount,
state.mtx.signer, // taker is mtx signer
msg.sender
)
);
}
/// @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 override
/// the taker address.
function _executeFillRfqOrderCall(ExecuteState memory state) private returns (bytes memory returnResult) {
LibNativeOrder.RfqOrder memory order;
LibSignature.Signature memory signature;
uint128 takerTokenFillAmount;
bytes memory args = _extractArgumentsFromCallData(state.mtx.callData);
(order, signature, takerTokenFillAmount) = abi.decode(
args,
(LibNativeOrder.RfqOrder, LibSignature.Signature, uint128)
);
return
_callSelf(
state.hash,
abi.encodeWithSelector(
INativeOrdersFeature._fillRfqOrder.selector,
order,
signature,
takerTokenFillAmount,
state.mtx.signer, // taker is mtx signer
false,
state.mtx.signer
)
);
}
/// @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
/// payer address.
function _executeMultiplexBatchSellTokenForTokenCall(
ExecuteState memory state
) private returns (bytes memory returnResult) {
IERC20Token inputToken;
IERC20Token outputToken;
IMultiplexFeature.BatchSellSubcall[] memory calls;
uint256 sellAmount;
uint256 minBuyAmount;
bytes memory args = _extractArgumentsFromCallData(state.mtx.callData);
(inputToken, outputToken, calls, sellAmount, minBuyAmount) = abi.decode(
args,
(IERC20Token, IERC20Token, 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,
payer: state.mtx.signer
}),
minBuyAmount
)
);
}
/// @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
/// payer address.
function _executeMultiplexBatchSellTokenForEthCall(
ExecuteState memory state
) private returns (bytes memory returnResult) {
IERC20Token inputToken;
IMultiplexFeature.BatchSellSubcall[] memory calls;
uint256 sellAmount;
uint256 minBuyAmount;
bytes memory args = _extractArgumentsFromCallData(state.mtx.callData);
(inputToken, calls, sellAmount, minBuyAmount) = abi.decode(
args,
(IERC20Token, IMultiplexFeature.BatchSellSubcall[], uint256, uint256)
);
returnResult = _callSelf(
state.hash,
abi.encodeWithSelector(
IMultiplexFeature._multiplexBatchSell.selector,
IMultiplexFeature.BatchSellParams({
inputToken: inputToken,
outputToken: IERC20Token(WETH),
sellAmount: sellAmount,
calls: calls,
useSelfBalance: false,
recipient: address(this),
payer: state.mtx.signer
}),
minBuyAmount
)
);
// 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
/// payer 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,
payer: state.mtx.signer
}),
minBuyAmount
)
);
}
/// @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
/// payer 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),
payer: state.mtx.signer
}),
minBuyAmount
)
);
// 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.
/// Warning: Do not let unadulterated `callData` into this function.
function _callSelf(bytes32 hash, bytes memory callData) private returns (bytes memory returnResult) {
bool success;
(success, returnResult) = address(this).call(callData);
if (!success) {
LibMetaTransactionsRichErrors.MetaTransactionCallFailedError(hash, callData, returnResult).rrevert();
}
}
}

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,16 @@ contract UniswapV3Feature is IFeature, IUniswapV3Feature, FixinCommon, FixinToke
} }
} }
// Convert null address values to alternative address.
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

@ -0,0 +1,90 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2023 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.6.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/src/IERC20Token.sol";
import "../libs/LibSignature.sol";
/// @dev Meta-transactions feature.
interface IMetaTransactionsFeatureV2 {
/// @dev Describes an exchange proxy meta transaction.
struct MetaTransactionFeeData {
// ERC20 fee recipient
address recipient;
// ERC20 fee amount
uint256 amount;
}
struct MetaTransactionDataV2 {
// Signer of meta-transaction. On whose behalf to execute the MTX.
address payable signer;
// Required sender, or NULL for anyone.
address sender;
// MTX is invalid after this time.
uint256 expirationTimeSeconds;
// Nonce to make this MTX unique.
uint256 salt;
// Encoded call data to a function on the exchange proxy.
bytes callData;
// ERC20 fee `signer` pays `sender`.
IERC20Token feeToken;
// ERC20 fees.
MetaTransactionFeeData[] fees;
}
/// @dev Emitted whenever a meta-transaction is executed via
/// `executeMetaTransaction()` or `executeMetaTransactions()`.
/// @param hash The EIP712 hash of the MetaTransactionDataV2 struct.
/// @param selector The selector of the function being executed.
/// @param signer Who to execute the meta-transaction on behalf of.
/// @param sender Who executed the meta-transaction.
event MetaTransactionExecuted(bytes32 hash, bytes4 indexed selector, address signer, address sender);
/// @dev Execute a single meta-transaction.
/// @param mtx The meta-transaction.
/// @param signature The signature by `mtx.signer`.
/// @return returnResult The ABI-encoded result of the underlying call.
function executeMetaTransactionV2(
MetaTransactionDataV2 calldata mtx,
LibSignature.Signature calldata signature
) external returns (bytes memory returnResult);
/// @dev Execute multiple meta-transactions.
/// @param mtxs The meta-transactions.
/// @param signatures The signature by each respective `mtx.signer`.
/// @return returnResults The ABI-encoded results of the underlying calls.
function batchExecuteMetaTransactionsV2(
MetaTransactionDataV2[] calldata mtxs,
LibSignature.Signature[] calldata signatures
) external returns (bytes[] memory returnResults);
/// @dev Get the block at which a meta-transaction has been executed.
/// @param mtx The meta-transaction.
/// @return blockNumber The block height when the meta-transactioin was executed.
function getMetaTransactionV2ExecutedBlock(
MetaTransactionDataV2 calldata mtx
) external view returns (uint256 blockNumber);
/// @dev Get the block at which a meta-transaction hash has been executed.
/// @param mtxHash The EIP712 hash of the MetaTransactionDataV2 struct.
/// @return blockNumber The block height when the meta-transactioin was executed.
function getMetaTransactionV2HashExecutedBlock(bytes32 mtxHash) external view returns (uint256 blockNumber);
/// @dev Get the EIP712 hash of a meta-transaction.
/// @param mtx The meta-transaction.
/// @return mtxHash The EIP712 hash of `mtx`.
function getMetaTransactionV2Hash(MetaTransactionDataV2 calldata mtx) external view returns (bytes32 mtxHash);
}

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 input tokens.
address payer;
} }
// 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 input tokens.
address payer;
} }
// 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

@ -54,6 +54,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,
payer: 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),
payer: msg.sender
}), }),
minBuyAmount minBuyAmount
); );
@ -167,26 +171,40 @@ 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,
payer: 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 +244,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,
payer: msg.sender
}), }),
minBuyAmount minBuyAmount
); );
@ -262,13 +281,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),
payer: msg.sender
}), }),
minBuyAmount minBuyAmount
); );
@ -297,25 +317,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,
payer: 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 +420,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. // `payer`, `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 `payer` 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.payer) {
_transferERC20TokensFrom(IERC20Token(params.tokens[0]), msg.sender, state.from, params.sellAmount); _transferERC20TokensFrom(IERC20Token(params.tokens[0]), params.payer, 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,11 +444,13 @@ 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) {
_nestedBatchSell(state, params, subcall.data); _nestedBatchSell(state, params, subcall.data);
} else if (subcall.id == MultiplexSubcall.OTC) {
_multiHopSellOtcOrder(state, params, subcall.data);
} else { } else {
revert("MultiplexFeature::_executeMultiHopSell/INVALID_SUBCALL"); revert("MultiplexFeature::_executeMultiHopSell/INVALID_SUBCALL");
} }
@ -443,6 +478,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 payer is the same too.
multiHopParams.payer = params.payer;
// 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 +506,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 `payer` (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 +514,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;
// payer shound be the same too.
batchSellParams.payer = params.payer;
// Execute the nested batch sell. // Execute the nested batch sell.
state.outputTokenAmount = _executeBatchSell(batchSellParams).boughtAmount; state.outputTokenAmount = _executeBatchSell(batchSellParams).boughtAmount;
} }
@ -505,29 +544,33 @@ contract MultiplexFeature is
// is executed, so we the target is the address encoded // is executed, so we the target is the address encoded
// in the subcall data. // in the subcall data.
(target, ) = abi.decode(subcall.data, (address, bytes)); (target, ) = abi.decode(subcall.data, (address, bytes));
} else if (subcall.id == MultiplexSubcall.UniswapV3 || subcall.id == MultiplexSubcall.BatchSell) { } else if (
subcall.id == MultiplexSubcall.UniswapV3 ||
subcall.id == MultiplexSubcall.BatchSell ||
subcall.id == MultiplexSubcall.OTC
) {
// 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 `payer` 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 `payer`, 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 `payer`,
// 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 `payer` 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.payer;
} else { } else {
target = address(this); target = address(this);
} }

View File

@ -67,7 +67,7 @@ abstract contract MultiplexLiquidityProvider is FixinCommon, FixinTokenSpender {
_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 `msg.sender`.
_transferERC20TokensFrom(params.inputToken, msg.sender, provider, sellAmount); _transferERC20TokensFrom(params.inputToken, params.payer, 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.payer,
params.useSelfBalance, params.useSelfBalance,
params.recipient params.recipient
) )
@ -65,4 +65,34 @@ abstract contract MultiplexOtc is FixinEIP712 {
state.boughtAmount = state.boughtAmount.safeAdd(makerTokenFilledAmount); state.boughtAmount = state.boughtAmount.safeAdd(makerTokenFilledAmount);
} catch {} } catch {}
} }
function _multiHopSellOtcOrder(
IMultiplexFeature.MultiHopSellState memory state,
IMultiplexFeature.MultiHopSellParams memory params,
bytes memory wrappedCallData
) internal {
// Decode the Otc order, and signature.
(LibNativeOrder.OtcOrder memory order, LibSignature.Signature memory signature) = abi.decode(
wrappedCallData,
(LibNativeOrder.OtcOrder, LibSignature.Signature)
);
//Make sure that the otc orders maker and taker tokens match the fill sequence in params.tokens[]
require(
address(order.takerToken) == params.tokens[state.hopIndex] &&
address(order.makerToken) == params.tokens[state.hopIndex + 1],
"MultiplexOtcOrder::_multiHopSellOtcOrder/INVALID_TOKENS"
);
// Try filling the Otc order. Bubble up reverts.
(uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount) = IOtcOrdersFeature(address(this))
._fillOtcOrder(
order,
signature,
state.outputTokenAmount.safeDowncastToUint128(),
state.from,
params.useSelfBalance,
state.to
);
//store the bought amount for the next hop
state.outputTokenAmount = makerTokenFilledAmount;
}
} }

View File

@ -54,7 +54,7 @@ abstract contract MultiplexRfq is FixinEIP712 {
order, order,
signature, signature,
sellAmount.safeDowncastToUint128(), sellAmount.safeDowncastToUint128(),
msg.sender, params.payer,
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. // `payer` as the taker.
args.taker = msg.sender; args.taker = payable(params.payer);
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(IERC20Token(tokens[0]), firstPairAddress, sellAmount); _transferERC20Tokens(IERC20Token(tokens[0]), firstPairAddress, sellAmount);
} else { } else {
_transferERC20TokensFrom(IERC20Token(tokens[0]), msg.sender, firstPairAddress, sellAmount); _transferERC20TokensFrom(IERC20Token(tokens[0]), params.payer, 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

@ -45,16 +45,16 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
) )
); );
} else { } else {
// Otherwise, we self-delegatecall the normal variant // Otherwise, we self-call `_sellTokenForTokenToUniswapV3`,
// `sellTokenForTokenToUniswapV3`, which pulls the input token // which pulls the input token from a specified `payer`.
// from `msg.sender`. (success, resultData) = address(this).call(
(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.payer
) )
); );
} }
@ -69,6 +69,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;
@ -87,16 +88,16 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
) )
); );
} else { } else {
// Otherwise, we self-delegatecall the normal variant // Otherwise, we self-call `_sellTokenForTokenToUniswapV3`,
// `sellTokenForTokenToUniswapV3`, which pulls the input token // which pulls the input token from `payer`.
// from `msg.sender`. (success, resultData) = address(this).call(
(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.payer
) )
); );
} }

View File

@ -0,0 +1,38 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2023 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.6.5;
pragma experimental ABIEncoderV2;
import "./LibStorage.sol";
/// @dev Storage helpers for the `MetaTransactions` feature.
library LibMetaTransactionsV2Storage {
/// @dev Storage bucket for this feature.
struct Storage {
// The block number when a hash was executed.
mapping(bytes32 => uint256) mtxHashToExecutedBlockNumber;
}
/// @dev Get the storage bucket for this contract.
function getStorage() internal pure returns (Storage storage stor) {
uint256 storageSlot = LibStorage.getStorageSlot(LibStorage.StorageId.MetaTransactionsV2);
// Dip into assembly to change the slot pointed to by the local variable `stor`.
// solhint-disable-next-line max-line-length
// See https://solidity.readthedocs.io/en/v0.6.8/assembly.html?highlight=slot#access-to-external-variables-functions-and-libraries
assembly {
stor_slot := storageSlot
}
}
}

View File

@ -34,7 +34,8 @@ library LibStorage {
NativeOrders, NativeOrders,
OtcOrders, OtcOrders,
ERC721Orders, ERC721Orders,
ERC1155Orders ERC1155Orders,
MetaTransactionsV2
} }
/// @dev Get the storage slot given a storage ID. We assign unique, well-spaced slots to storage bucket variables /// @dev Get the storage slot given a storage ID. We assign unique, well-spaced slots to storage bucket variables

View File

@ -36,9 +36,9 @@
"typechain": "typechain --target=ethers-v5 --out-dir='typechain-wrappers' './foundry-artifacts/**/*.json'" "typechain": "typechain --target=ethers-v5 --out-dir='typechain-wrappers' './foundry-artifacts/**/*.json'"
}, },
"config": { "config": {
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature,AvalancheBridgeAdapter,BSCBridgeAdapter,CeloBridgeAdapter,EthereumBridgeAdapter,FantomBridgeAdapter,OptimismBridgeAdapter,PolygonBridgeAdapter", "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature,AvalancheBridgeAdapter,BSCBridgeAdapter,CeloBridgeAdapter,EthereumBridgeAdapter,FantomBridgeAdapter,OptimismBridgeAdapter,PolygonBridgeAdapter,MetaTransactionsFeatureV2",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(AbstractBridgeAdapter|AffiliateFeeTransformer|ArbitrumBridgeAdapter|AvalancheBridgeAdapter|BSCBridgeAdapter|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeProtocols|CeloBridgeAdapter|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|EthereumBridgeAdapter|FantomBridgeAdapter|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinERC1155Spender|FixinERC721Spender|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC1155OrdersFeature|IERC1155Token|IERC165Feature|IERC20Bridge|IERC20Transformer|IERC721OrdersFeature|IERC721Token|IFeature|IFeeRecipient|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|IPropertyValidator|ISimpleFunctionRegistryFeature|IStaking|ITakerCallback|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC1155OrdersStorage|LibERC20Transformer|LibERC721OrdersStorage|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNFTOrder|LibNFTOrdersRichErrors|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2Batch|MixinBancor|MixinBancorV3|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinGMX|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinPlatypus|MixinShell|MixinSolidly|MixinSynthetix|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OptimismBridgeAdapter|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PolygonBridgeAdapter|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFeeRecipient|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC1155Token|TestMintableERC20Token|TestMintableERC721Token|TestMooniswap|TestNFTOrderPresigner|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestPropertyValidator|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json" "abis": "./test/generated-artifacts/@(AbstractBridgeAdapter|AffiliateFeeTransformer|ArbitrumBridgeAdapter|AvalancheBridgeAdapter|BSCBridgeAdapter|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeProtocols|CeloBridgeAdapter|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|EthereumBridgeAdapter|FantomBridgeAdapter|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinERC1155Spender|FixinERC721Spender|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC1155OrdersFeature|IERC1155Token|IERC165Feature|IERC20Bridge|IERC20Transformer|IERC721OrdersFeature|IERC721Token|IFeature|IFeeRecipient|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMetaTransactionsFeatureV2|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|IPropertyValidator|ISimpleFunctionRegistryFeature|IStaking|ITakerCallback|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC1155OrdersStorage|LibERC20Transformer|LibERC721OrdersStorage|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNFTOrder|LibNFTOrdersRichErrors|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MetaTransactionsFeatureV2|MixinAaveV2|MixinBalancer|MixinBalancerV2Batch|MixinBancor|MixinBancorV3|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinGMX|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinPlatypus|MixinShell|MixinSolidly|MixinSynthetix|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OptimismBridgeAdapter|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PolygonBridgeAdapter|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFeeRecipient|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC1155Token|TestMintableERC20Token|TestMintableERC721Token|TestMooniswap|TestNFTOrderPresigner|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestPropertyValidator|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -32,6 +32,7 @@ import * as IZeroEx from '../generated-artifacts/IZeroEx.json';
import * as LiquidityProviderFeature from '../generated-artifacts/LiquidityProviderFeature.json'; import * as LiquidityProviderFeature from '../generated-artifacts/LiquidityProviderFeature.json';
import * as LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json'; import * as LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json';
import * as MetaTransactionsFeature from '../generated-artifacts/MetaTransactionsFeature.json'; import * as MetaTransactionsFeature from '../generated-artifacts/MetaTransactionsFeature.json';
import * as MetaTransactionsFeatureV2 from '../generated-artifacts/MetaTransactionsFeatureV2.json';
import * as MultiplexFeature from '../generated-artifacts/MultiplexFeature.json'; import * as MultiplexFeature from '../generated-artifacts/MultiplexFeature.json';
import * as NativeOrdersFeature from '../generated-artifacts/NativeOrdersFeature.json'; import * as NativeOrdersFeature from '../generated-artifacts/NativeOrdersFeature.json';
import * as OptimismBridgeAdapter from '../generated-artifacts/OptimismBridgeAdapter.json'; import * as OptimismBridgeAdapter from '../generated-artifacts/OptimismBridgeAdapter.json';
@ -84,4 +85,5 @@ export const artifacts = {
FantomBridgeAdapter: FantomBridgeAdapter as ContractArtifact, FantomBridgeAdapter: FantomBridgeAdapter as ContractArtifact,
OptimismBridgeAdapter: OptimismBridgeAdapter as ContractArtifact, OptimismBridgeAdapter: OptimismBridgeAdapter as ContractArtifact,
PolygonBridgeAdapter: PolygonBridgeAdapter as ContractArtifact, PolygonBridgeAdapter: PolygonBridgeAdapter as ContractArtifact,
MetaTransactionsFeatureV2: MetaTransactionsFeatureV2 as ContractArtifact,
}; };

View File

@ -30,6 +30,7 @@ export * from '../generated-wrappers/initial_migration';
export * from '../generated-wrappers/liquidity_provider_feature'; export * from '../generated-wrappers/liquidity_provider_feature';
export * from '../generated-wrappers/log_metadata_transformer'; export * from '../generated-wrappers/log_metadata_transformer';
export * from '../generated-wrappers/meta_transactions_feature'; export * from '../generated-wrappers/meta_transactions_feature';
export * from '../generated-wrappers/meta_transactions_feature_v2';
export * from '../generated-wrappers/multiplex_feature'; export * from '../generated-wrappers/multiplex_feature';
export * from '../generated-wrappers/native_orders_feature'; export * from '../generated-wrappers/native_orders_feature';
export * from '../generated-wrappers/optimism_bridge_adapter'; export * from '../generated-wrappers/optimism_bridge_adapter';

View File

@ -51,6 +51,7 @@ import * as ILiquidityProvider from '../test/generated-artifacts/ILiquidityProvi
import * as ILiquidityProviderFeature from '../test/generated-artifacts/ILiquidityProviderFeature.json'; import * as ILiquidityProviderFeature from '../test/generated-artifacts/ILiquidityProviderFeature.json';
import * as ILiquidityProviderSandbox from '../test/generated-artifacts/ILiquidityProviderSandbox.json'; import * as ILiquidityProviderSandbox from '../test/generated-artifacts/ILiquidityProviderSandbox.json';
import * as IMetaTransactionsFeature from '../test/generated-artifacts/IMetaTransactionsFeature.json'; import * as IMetaTransactionsFeature from '../test/generated-artifacts/IMetaTransactionsFeature.json';
import * as IMetaTransactionsFeatureV2 from '../test/generated-artifacts/IMetaTransactionsFeatureV2.json';
import * as IMooniswapPool from '../test/generated-artifacts/IMooniswapPool.json'; import * as IMooniswapPool from '../test/generated-artifacts/IMooniswapPool.json';
import * as IMultiplexFeature from '../test/generated-artifacts/IMultiplexFeature.json'; import * as IMultiplexFeature from '../test/generated-artifacts/IMultiplexFeature.json';
import * as INativeOrdersEvents from '../test/generated-artifacts/INativeOrdersEvents.json'; import * as INativeOrdersEvents from '../test/generated-artifacts/INativeOrdersEvents.json';
@ -104,6 +105,7 @@ import * as LiquidityProviderFeature from '../test/generated-artifacts/Liquidity
import * as LiquidityProviderSandbox from '../test/generated-artifacts/LiquidityProviderSandbox.json'; import * as LiquidityProviderSandbox from '../test/generated-artifacts/LiquidityProviderSandbox.json';
import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json'; import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json';
import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.json'; import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.json';
import * as MetaTransactionsFeatureV2 from '../test/generated-artifacts/MetaTransactionsFeatureV2.json';
import * as MixinAaveV2 from '../test/generated-artifacts/MixinAaveV2.json'; import * as MixinAaveV2 from '../test/generated-artifacts/MixinAaveV2.json';
import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json'; import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json';
import * as MixinBalancerV2Batch from '../test/generated-artifacts/MixinBalancerV2Batch.json'; import * as MixinBalancerV2Batch from '../test/generated-artifacts/MixinBalancerV2Batch.json';
@ -236,6 +238,7 @@ export const artifacts = {
FundRecoveryFeature: FundRecoveryFeature as ContractArtifact, FundRecoveryFeature: FundRecoveryFeature as ContractArtifact,
LiquidityProviderFeature: LiquidityProviderFeature as ContractArtifact, LiquidityProviderFeature: LiquidityProviderFeature as ContractArtifact,
MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact, MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
MetaTransactionsFeatureV2: MetaTransactionsFeatureV2 as ContractArtifact,
NativeOrdersFeature: NativeOrdersFeature as ContractArtifact, NativeOrdersFeature: NativeOrdersFeature as ContractArtifact,
OtcOrdersFeature: OtcOrdersFeature as ContractArtifact, OtcOrdersFeature: OtcOrdersFeature as ContractArtifact,
OwnableFeature: OwnableFeature as ContractArtifact, OwnableFeature: OwnableFeature as ContractArtifact,
@ -253,6 +256,7 @@ export const artifacts = {
IFundRecoveryFeature: IFundRecoveryFeature as ContractArtifact, IFundRecoveryFeature: IFundRecoveryFeature as ContractArtifact,
ILiquidityProviderFeature: ILiquidityProviderFeature as ContractArtifact, ILiquidityProviderFeature: ILiquidityProviderFeature as ContractArtifact,
IMetaTransactionsFeature: IMetaTransactionsFeature as ContractArtifact, IMetaTransactionsFeature: IMetaTransactionsFeature as ContractArtifact,
IMetaTransactionsFeatureV2: IMetaTransactionsFeatureV2 as ContractArtifact,
IMultiplexFeature: IMultiplexFeature as ContractArtifact, IMultiplexFeature: IMultiplexFeature as ContractArtifact,
INativeOrdersEvents: INativeOrdersEvents as ContractArtifact, INativeOrdersEvents: INativeOrdersEvents as ContractArtifact,
INativeOrdersFeature: INativeOrdersFeature as ContractArtifact, INativeOrdersFeature: INativeOrdersFeature as ContractArtifact,

View File

@ -49,6 +49,7 @@ export * from '../test/generated-wrappers/i_liquidity_provider';
export * from '../test/generated-wrappers/i_liquidity_provider_feature'; export * from '../test/generated-wrappers/i_liquidity_provider_feature';
export * from '../test/generated-wrappers/i_liquidity_provider_sandbox'; export * from '../test/generated-wrappers/i_liquidity_provider_sandbox';
export * from '../test/generated-wrappers/i_meta_transactions_feature'; export * from '../test/generated-wrappers/i_meta_transactions_feature';
export * from '../test/generated-wrappers/i_meta_transactions_feature_v2';
export * from '../test/generated-wrappers/i_mooniswap_pool'; export * from '../test/generated-wrappers/i_mooniswap_pool';
export * from '../test/generated-wrappers/i_multiplex_feature'; export * from '../test/generated-wrappers/i_multiplex_feature';
export * from '../test/generated-wrappers/i_native_orders_events'; export * from '../test/generated-wrappers/i_native_orders_events';
@ -102,6 +103,7 @@ export * from '../test/generated-wrappers/liquidity_provider_feature';
export * from '../test/generated-wrappers/liquidity_provider_sandbox'; export * from '../test/generated-wrappers/liquidity_provider_sandbox';
export * from '../test/generated-wrappers/log_metadata_transformer'; export * from '../test/generated-wrappers/log_metadata_transformer';
export * from '../test/generated-wrappers/meta_transactions_feature'; export * from '../test/generated-wrappers/meta_transactions_feature';
export * from '../test/generated-wrappers/meta_transactions_feature_v2';
export * from '../test/generated-wrappers/mixin_aave_v2'; export * from '../test/generated-wrappers/mixin_aave_v2';
export * from '../test/generated-wrappers/mixin_balancer'; export * from '../test/generated-wrappers/mixin_balancer';
export * from '../test/generated-wrappers/mixin_balancer_v2_batch'; export * from '../test/generated-wrappers/mixin_balancer_v2_batch';

View File

@ -0,0 +1,501 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2023 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.6.5;
pragma experimental ABIEncoderV2;
import "./utils/BaseTest.sol";
import "forge-std/Test.sol";
import "./utils/LocalTest.sol";
import "../contracts/src/features/MetaTransactionsFeatureV2.sol";
import "../contracts/src/features/interfaces/IMetaTransactionsFeatureV2.sol";
import "../contracts/src/features/interfaces/IMetaTransactionsFeature.sol";
import "../contracts/test/TestMintTokenERC20Transformer.sol";
import "../contracts/src/features/libs/LibSignature.sol";
import "src/features/libs/LibNativeOrder.sol";
import "../contracts/test/tokens/TestMintableERC20Token.sol";
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
import "@0x/contracts-erc20/src/IEtherToken.sol";
contract MetaTransactionTest is LocalTest {
address private constant USER_ADDRESS = 0x6dc3a54FeAE57B65d185A7B159c5d3FA7fD7FD0F;
uint256 private constant USER_KEY = 0x1fc1630343b31e60b7a197a53149ca571ed9d9791e2833337bbd8110c30710ec;
event MetaTransactionExecuted(bytes32 hash, bytes4 indexed selector, address signer, address sender);
function _mtxSignature(
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtx
) private returns (LibSignature.Signature memory) {
return _mtxSignatureWithSignerKey(mtx, USER_KEY);
}
function _mtxSignatureWithSignerKey(
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtx,
uint256 key
) private returns (LibSignature.Signature memory) {
// Mint fee to signer and approve
for (uint256 i = 0; i < mtx.fees.length; ++i) {
_mintTo(address(weth), mtx.signer, mtx.fees[i].amount);
}
vm.prank(mtx.signer);
mtx.feeToken.approve(address(zeroExDeployed.zeroEx), 1e18);
bytes32 mtxHash = zeroExDeployed.features.metaTransactionsFeatureV2.getMetaTransactionV2Hash(mtx);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(key, mtxHash);
LibSignature.Signature memory sig = LibSignature.Signature(LibSignature.SignatureType.EIP712, v, r, s);
return sig;
}
function _getMetaTransaction(
bytes memory callData
) private view returns (IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory) {
IMetaTransactionsFeatureV2.MetaTransactionFeeData[]
memory fees = new IMetaTransactionsFeatureV2.MetaTransactionFeeData[](1);
fees[0] = IMetaTransactionsFeatureV2.MetaTransactionFeeData({recipient: address(this), amount: 1});
return _getMetaTransactionWithFees(callData, fees);
}
function _getMetaTransactionWithFees(
bytes memory callData,
IMetaTransactionsFeatureV2.MetaTransactionFeeData[] memory fees
) private view returns (IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory) {
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtx = IMetaTransactionsFeatureV2.MetaTransactionDataV2({
signer: payable(USER_ADDRESS),
sender: address(this),
expirationTimeSeconds: block.timestamp + 60,
salt: 123,
callData: callData,
feeToken: weth,
fees: fees
});
return mtx;
}
function _badSelectorTransformERC20Call() private pure returns (bytes memory) {
return abi.encodeWithSelector(ITransformERC20Feature.createTransformWallet.selector);
}
function _badTokenTransformERC20Call() private returns (bytes memory) {
ITransformERC20Feature.Transformation[] memory transformations = new ITransformERC20Feature.Transformation[](1);
transformations[0] = ITransformERC20Feature.Transformation(
uint32(transformerNonce),
abi.encode(address(dai), address(weth), 0, 1e18, 0)
);
_mintTo(address(dai), USER_ADDRESS, 1e18);
vm.prank(USER_ADDRESS);
dai.approve(address(zeroExDeployed.zeroEx), 1e18);
return
abi.encodeWithSelector(
zeroExDeployed.zeroEx.transformERC20.selector, // 0x415565b0
dai,
weth,
1e18,
1e18,
transformations
);
}
function _makeTestRfqOrder(
IERC20Token makerToken,
IERC20Token takerToken,
address makerAddress,
address takerAddress,
uint256 makerKey
) internal returns (bytes memory callData) {
LibNativeOrder.RfqOrder memory order = LibNativeOrder.RfqOrder({
makerToken: makerToken,
takerToken: takerToken,
makerAmount: 1e18,
takerAmount: 1e18,
maker: makerAddress,
taker: address(0),
txOrigin: tx.origin,
pool: 0x0000000000000000000000000000000000000000000000000000000000000000,
expiry: uint64(block.timestamp + 60),
salt: 123
});
_mintTo(address(order.makerToken), order.maker, order.makerAmount);
vm.prank(order.maker);
order.makerToken.approve(address(zeroExDeployed.zeroEx), order.makerAmount);
_mintTo(address(order.takerToken), takerAddress, order.takerAmount);
vm.prank(takerAddress);
order.takerToken.approve(address(zeroExDeployed.zeroEx), order.takerAmount);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
makerKey,
zeroExDeployed.features.nativeOrdersFeature.getRfqOrderHash(order)
);
LibSignature.Signature memory sig = LibSignature.Signature(LibSignature.SignatureType.EIP712, v, r, s);
return
abi.encodeWithSelector(
INativeOrdersFeature.fillRfqOrder.selector, // 0xaa77476c
order, // RFQOrder
sig, // Order Signature
1e18 // Fill Amount
);
}
function _makeTestLimitOrder(
IERC20Token makerToken,
IERC20Token takerToken,
address makerAddress,
address takerAddress,
uint256 makerKey
) internal returns (bytes memory callData) {
LibNativeOrder.LimitOrder memory order = LibNativeOrder.LimitOrder({
makerToken: makerToken,
takerToken: takerToken,
makerAmount: 1e18,
takerAmount: 1e18,
maker: makerAddress,
taker: address(0),
sender: address(0),
takerTokenFeeAmount: 0,
feeRecipient: address(0),
pool: 0x0000000000000000000000000000000000000000000000000000000000000000,
expiry: uint64(block.timestamp + 60),
salt: 123
});
_mintTo(address(order.makerToken), order.maker, order.makerAmount);
vm.prank(order.maker);
order.makerToken.approve(address(zeroExDeployed.zeroEx), order.makerAmount);
_mintTo(address(order.takerToken), takerAddress, order.takerAmount);
vm.prank(takerAddress);
order.takerToken.approve(address(zeroExDeployed.zeroEx), order.takerAmount);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
makerKey,
zeroExDeployed.features.nativeOrdersFeature.getLimitOrderHash(order)
);
LibSignature.Signature memory sig = LibSignature.Signature(LibSignature.SignatureType.EIP712, v, r, s);
return
abi.encodeWithSelector(
INativeOrdersFeature.fillLimitOrder.selector, // 0xf6274f66
order, // LimitOrder
sig, // Order Signature
1e18 // Fill Amount
);
}
function _transformERC20Call(
IERC20Token makerToken,
IERC20Token takerToken,
address takerAddress
) internal returns (bytes memory) {
ITransformERC20Feature.Transformation[] memory transformations = new ITransformERC20Feature.Transformation[](1);
transformations[0] = ITransformERC20Feature.Transformation(
uint32(transformerNonce),
abi.encode(address(takerToken), address(makerToken), 0, 1e18, 0)
);
_mintTo(address(takerToken), takerAddress, 1e18);
vm.prank(takerAddress);
takerToken.approve(address(zeroExDeployed.zeroEx), 1e18);
return
abi.encodeWithSelector(
zeroExDeployed.zeroEx.transformERC20.selector, // 0x415565b0
takerToken,
makerToken,
1e18,
1e18,
transformations
);
}
function test_createHash() external {
bytes memory transformCallData = _transformERC20Call(zrx, dai, USER_ADDRESS);
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtxData = _getMetaTransaction(transformCallData);
bytes32 mtxHash = zeroExDeployed.features.metaTransactionsFeatureV2.getMetaTransactionV2Hash(mtxData);
assertTrue(mtxHash != bytes32(0));
}
function test_EIP_712_signature() external {
// metamask wallet signed data
bytes32 r_mm = 0xcd6c09d558e23803afae870ca53a8e7bfaf5564c64ee29f23dc4a19e7dd9e9b5;
bytes32 s_mm = 0x1ae68e89fadab4a7f4d01fd5543e5e0efd5697e87c993f045f671aba3e1f55ac;
uint8 v_mm = 0x1b;
IMetaTransactionsFeatureV2.MetaTransactionFeeData[]
memory fees = new IMetaTransactionsFeatureV2.MetaTransactionFeeData[](2);
fees[0] = IMetaTransactionsFeatureV2.MetaTransactionFeeData({recipient: address(0), amount: 1000000});
fees[1] = IMetaTransactionsFeatureV2.MetaTransactionFeeData({recipient: address(0), amount: 1000});
IERC20Token usdcToken = IERC20Token(address(0x2e234DAe75C793f67A35089C9d99245E1C58470b));
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtx = IMetaTransactionsFeatureV2.MetaTransactionDataV2({
signer: address(0),
sender: address(0),
expirationTimeSeconds: 99999999,
salt: 1234,
callData: new bytes(0),
feeToken: usdcToken,
fees: fees
});
bytes32 mtxHash = zeroExDeployed.features.metaTransactionsFeatureV2.getMetaTransactionV2Hash(mtx);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(USER_KEY, mtxHash);
//emit log_bytes(abi.encodePacked(r, s, bytes1(v)));
// Verify signature matches from what we generated using metamask
assertTrue(v == v_mm);
assertTrue(r == r_mm);
assertTrue(s == s_mm);
}
function test_transformERC20() external {
bytes memory transformCallData = _transformERC20Call(zrx, dai, USER_ADDRESS);
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtxData = _getMetaTransaction(transformCallData);
assertEq(dai.balanceOf(USER_ADDRESS), 1e18);
vm.expectEmit(true, false, false, true);
emit MetaTransactionExecuted(
zeroExDeployed.features.metaTransactionsFeatureV2.getMetaTransactionV2Hash(mtxData),
zeroExDeployed.zeroEx.transformERC20.selector, // 0x415565b0
USER_ADDRESS,
address(this)
);
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).executeMetaTransactionV2(
mtxData,
_mtxSignature(mtxData)
);
assertEq(zrx.balanceOf(USER_ADDRESS), 1e18);
assertEq(dai.balanceOf(USER_ADDRESS), 0);
assertEq(weth.balanceOf(address(this)), 1);
}
function test_rfqOrder() external {
bytes memory callData = _makeTestRfqOrder(zrx, dai, signerAddress, USER_ADDRESS, signerKey);
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtxData = _getMetaTransaction(callData);
assertEq(dai.balanceOf(USER_ADDRESS), 1e18);
vm.expectEmit(true, false, false, true);
emit MetaTransactionExecuted(
zeroExDeployed.features.metaTransactionsFeatureV2.getMetaTransactionV2Hash(mtxData),
INativeOrdersFeature.fillRfqOrder.selector, // 0xaa77476c
USER_ADDRESS,
address(this)
);
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).executeMetaTransactionV2(
mtxData,
_mtxSignature(mtxData)
);
assertEq(zrx.balanceOf(signerAddress), 0);
assertEq(zrx.balanceOf(USER_ADDRESS), 1e18);
assertEq(dai.balanceOf(USER_ADDRESS), 0);
assertEq(dai.balanceOf(signerAddress), 1e18);
assertEq(weth.balanceOf(address(this)), 1);
}
function test_fillLimitOrder() external {
bytes memory callData = _makeTestLimitOrder(zrx, dai, signerAddress, USER_ADDRESS, signerKey);
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtxData = _getMetaTransaction(callData);
assertEq(dai.balanceOf(USER_ADDRESS), 1e18);
vm.expectEmit(true, false, false, true);
emit MetaTransactionExecuted(
zeroExDeployed.features.metaTransactionsFeatureV2.getMetaTransactionV2Hash(mtxData),
INativeOrdersFeature.fillLimitOrder.selector, // 0xf6274f66
USER_ADDRESS,
address(this)
);
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).executeMetaTransactionV2(
mtxData,
_mtxSignature(mtxData)
);
assertEq(zrx.balanceOf(signerAddress), 0);
assertEq(zrx.balanceOf(USER_ADDRESS), 1e18);
assertEq(dai.balanceOf(USER_ADDRESS), 0);
assertEq(dai.balanceOf(signerAddress), 1e18);
assertEq(weth.balanceOf(address(this)), 1);
}
function test_transformERC20WithAnySender() external {
bytes memory transformCallData = _transformERC20Call(zrx, dai, USER_ADDRESS);
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtxData = _getMetaTransaction(transformCallData);
mtxData.sender = address(0);
assertEq(dai.balanceOf(USER_ADDRESS), 1e18);
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).executeMetaTransactionV2(
mtxData,
_mtxSignature(mtxData)
);
assertEq(zrx.balanceOf(USER_ADDRESS), 1e18);
assertEq(dai.balanceOf(USER_ADDRESS), 0);
assertEq(weth.balanceOf(address(this)), 1);
}
function test_transformERC20WithoutFee() external {
bytes memory transformCallData = _transformERC20Call(zrx, dai, USER_ADDRESS);
IMetaTransactionsFeatureV2.MetaTransactionFeeData[] memory fees;
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtxData = _getMetaTransactionWithFees(
transformCallData,
fees
);
assertEq(dai.balanceOf(USER_ADDRESS), 1e18);
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).executeMetaTransactionV2(
mtxData,
_mtxSignature(mtxData)
);
assertEq(zrx.balanceOf(USER_ADDRESS), 1e18);
assertEq(dai.balanceOf(USER_ADDRESS), 0);
assertEq(weth.balanceOf(address(this)), 0); // no fee paid out
}
function test_transformERC20MultipleFees() external {
bytes memory transformCallData = _transformERC20Call(zrx, dai, USER_ADDRESS);
IMetaTransactionsFeatureV2.MetaTransactionFeeData[]
memory fees = new IMetaTransactionsFeatureV2.MetaTransactionFeeData[](2);
fees[0] = IMetaTransactionsFeatureV2.MetaTransactionFeeData({recipient: address(this), amount: 10});
fees[1] = IMetaTransactionsFeatureV2.MetaTransactionFeeData({recipient: signerAddress, amount: 20});
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtxData = _getMetaTransactionWithFees(
transformCallData,
fees
);
assertEq(dai.balanceOf(USER_ADDRESS), 1e18);
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).executeMetaTransactionV2(
mtxData,
_mtxSignature(mtxData)
);
assertEq(zrx.balanceOf(USER_ADDRESS), 1e18);
assertEq(dai.balanceOf(USER_ADDRESS), 0);
assertEq(weth.balanceOf(address(this)), 10);
assertEq(weth.balanceOf(address(signerAddress)), 20);
}
function test_transformERC20TranslatedCallFail() external {
bytes memory transformCallData = _badTokenTransformERC20Call();
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtxData = _getMetaTransaction(transformCallData);
LibSignature.Signature memory sig = _mtxSignature(mtxData);
vm.expectRevert();
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).executeMetaTransactionV2(mtxData, sig);
}
function test_transformERC20UnsupportedFunction() external {
bytes memory transformCallData = _badSelectorTransformERC20Call();
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtxData = _getMetaTransaction(transformCallData);
LibSignature.Signature memory sig = _mtxSignature(mtxData);
vm.expectRevert();
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).executeMetaTransactionV2(mtxData, sig);
}
function test_transformERC20CantExecuteTwice() external {
bytes memory callData = _makeTestRfqOrder(zrx, dai, signerAddress, USER_ADDRESS, signerKey);
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtxData = _getMetaTransaction(callData);
LibSignature.Signature memory sig = _mtxSignature(mtxData);
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).executeMetaTransactionV2(mtxData, sig);
vm.expectRevert();
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).executeMetaTransactionV2(mtxData, sig);
}
function test_metaTxnFailsIfExpired() external {
bytes memory callData = _makeTestRfqOrder(zrx, dai, signerAddress, USER_ADDRESS, signerKey);
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtxData = _getMetaTransaction(callData);
mtxData.expirationTimeSeconds = block.timestamp - 1;
LibSignature.Signature memory sig = _mtxSignature(mtxData);
vm.expectRevert();
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).executeMetaTransactionV2(mtxData, sig);
}
function test_metaTxnFailsIfWrongSender() external {
bytes memory transformCallData = _transformERC20Call(zrx, dai, USER_ADDRESS);
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtxData = _getMetaTransaction(transformCallData);
mtxData.sender = USER_ADDRESS;
LibSignature.Signature memory sig = _mtxSignature(mtxData);
vm.expectRevert();
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).executeMetaTransactionV2(mtxData, sig);
}
function test_metaTxnFailsWrongSignature() external {
bytes memory transformCallData = _transformERC20Call(zrx, dai, USER_ADDRESS);
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtxData = _getMetaTransaction(transformCallData);
LibSignature.Signature memory sig = _mtxSignatureWithSignerKey(mtxData, signerKey);
vm.expectRevert();
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).executeMetaTransactionV2(mtxData, sig);
}
function test_batchExecuteMetaTransactionsMultipleTransactions() external {
bytes memory transformCallData = _transformERC20Call(zrx, dai, USER_ADDRESS);
bytes memory rfqCallData = _makeTestRfqOrder(zrx, shib, signerAddress, USER_ADDRESS, signerKey);
IMetaTransactionsFeatureV2.MetaTransactionDataV2[]
memory mtxns = new IMetaTransactionsFeatureV2.MetaTransactionDataV2[](2);
LibSignature.Signature[] memory sigs = new LibSignature.Signature[](2);
mtxns[0] = _getMetaTransaction(transformCallData);
sigs[0] = _mtxSignature(mtxns[0]);
mtxns[1] = _getMetaTransaction(rfqCallData);
sigs[1] = _mtxSignature(mtxns[1]);
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).batchExecuteMetaTransactionsV2(mtxns, sigs);
assertEq(zrx.balanceOf(USER_ADDRESS), 2e18);
assertEq(dai.balanceOf(USER_ADDRESS), 0);
assertEq(shib.balanceOf(USER_ADDRESS), 0);
}
function test_batchExecuteMetaTransactionsCantExecuteSameTxnTwice() external {
bytes memory transformCallData = _transformERC20Call(zrx, dai, USER_ADDRESS);
IMetaTransactionsFeatureV2.MetaTransactionDataV2[]
memory mtxns = new IMetaTransactionsFeatureV2.MetaTransactionDataV2[](2);
LibSignature.Signature[] memory sigs = new LibSignature.Signature[](2);
mtxns[0] = _getMetaTransaction(transformCallData);
sigs[0] = _mtxSignature(mtxns[0]);
mtxns[1] = mtxns[0];
sigs[1] = _mtxSignature(mtxns[1]);
vm.expectRevert();
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).batchExecuteMetaTransactionsV2(mtxns, sigs);
}
function test_batchExecuteMetaTransactionsFailsIfTransactionFails() external {
bytes memory transformCallData = _transformERC20Call(zrx, dai, USER_ADDRESS);
bytes memory badTransformCallData = _badTokenTransformERC20Call();
IMetaTransactionsFeatureV2.MetaTransactionDataV2[]
memory mtxns = new IMetaTransactionsFeatureV2.MetaTransactionDataV2[](2);
LibSignature.Signature[] memory sigs = new LibSignature.Signature[](2);
mtxns[0] = _getMetaTransaction(transformCallData);
sigs[0] = _mtxSignature(mtxns[0]);
mtxns[1] = _getMetaTransaction(badTransformCallData);
sigs[1] = _mtxSignature(mtxns[1]);
vm.expectRevert();
IMetaTransactionsFeatureV2(address(zeroExDeployed.zeroEx)).batchExecuteMetaTransactionsV2(mtxns, sigs);
}
}

View File

@ -0,0 +1,407 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import {LocalTest} from "utils/LocalTest.sol";
import {MultiplexUtils} from "utils/MultiplexUtils.sol";
import {LibSignature} from "src/features/libs/LibSignature.sol";
import {LibNativeOrder} from "src/features/libs/LibNativeOrder.sol";
import {IMetaTransactionsFeatureV2} from "src/features/interfaces/IMetaTransactionsFeatureV2.sol";
contract MultiplexMetaTransactionsV2 is LocalTest, MultiplexUtils {
function _makeMetaTransactionV2(
bytes memory callData
) private view returns (IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory, LibSignature.Signature memory) {
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtx = IMetaTransactionsFeatureV2.MetaTransactionDataV2({
signer: payable(otherSignerAddress),
sender: address(0),
expirationTimeSeconds: block.timestamp + 600,
salt: 123,
callData: callData,
feeToken: dai,
fees: new IMetaTransactionsFeatureV2.MetaTransactionFeeData[](0)
});
bytes32 mtxHash = zeroExDeployed.zeroEx.getMetaTransactionV2Hash(mtx);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(otherSignerKey, mtxHash);
LibSignature.Signature memory sig = LibSignature.Signature(LibSignature.SignatureType.EIP712, v, r, s);
return (mtx, sig);
}
function _executeMetaTransaction(bytes memory callData) private {
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtx;
LibSignature.Signature memory sig;
(mtx, sig) = _makeMetaTransactionV2(callData);
zeroExDeployed.zeroEx.executeMetaTransactionV2(mtx, sig);
}
// batch
function test_metaTransaction_multiplexBatchSellTokenForToken_rfqOrder() external {
LibNativeOrder.RfqOrder memory rfqOrder = _makeTestRfqOrder();
rfqOrder.taker = otherSignerAddress;
_mintTo(address(rfqOrder.takerToken), otherSignerAddress, rfqOrder.takerAmount);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken.selector,
dai,
zrx,
_makeArray(_makeRfqSubcall(rfqOrder)),
rfqOrder.takerAmount,
rfqOrder.makerAmount
)
);
}
function test_metaTransaction_multiplexBatchSellTokenForToken_otcOrder() external {
LibNativeOrder.OtcOrder memory otcOrder = _makeTestOtcOrder();
otcOrder.taker = otherSignerAddress;
_mintTo(address(otcOrder.takerToken), otherSignerAddress, otcOrder.takerAmount);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken.selector,
dai,
zrx,
_makeArray(_makeOtcSubcall(otcOrder)),
otcOrder.takerAmount,
otcOrder.makerAmount
)
);
}
function test_metaTransaction_multiplexBatchSellTokenForToken_uniswapV2() external {
_createUniswapV2Pool(uniV2Factory, dai, zrx, 10e18, 10e18);
_mintTo(address(dai), otherSignerAddress, 1e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken.selector,
dai,
zrx,
_makeArray(_makeUniswapV2BatchSubcall(_makeArray(address(dai), address(zrx)), 1e18, false)),
1e18,
1
)
);
}
function test_metaTransaction_multiplexBatchSellTokenForToken_uniswapV3() external {
_createUniswapV3Pool(uniV3Factory, dai, zrx, 10e18, 10e18);
_mintTo(address(dai), otherSignerAddress, 1e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken.selector,
dai,
zrx,
_makeArray(_makeUniswapV3BatchSubcall(_makeArray(address(dai), address(zrx)), 1e18)),
1e18,
1
)
);
}
function test_metaTransaction_multiplexBatchSellTokenForToken_liquidityProvider() external {
_mintTo(address(dai), otherSignerAddress, 1e18);
_mintTo(address(zrx), address(liquidityProvider), 1e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken.selector,
dai,
zrx,
_makeArray(_makeMockLiquidityProviderBatchSubcall(1e18)),
1e18,
1
)
);
}
function test_metaTransaction_multiplexBatchSellTokenForToken_transformErc20() external {
_mintTo(address(dai), otherSignerAddress, 1e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken.selector,
dai,
zrx,
_makeArray(_makeMockTransformERC20Subcall(dai, zrx, 1e18, 1e18)),
1e18,
1
)
);
}
function test_metaTransaction_multiplexBatchSellTokenForToken_rfqOrderUniswapV3() external {
_createUniswapV3Pool(uniV3Factory, dai, zrx, 10e18, 10e18);
LibNativeOrder.RfqOrder memory rfqOrder = _makeTestRfqOrder();
rfqOrder.taker = otherSignerAddress;
_mintTo(address(dai), otherSignerAddress, 2e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken.selector,
dai,
zrx,
_makeArray(
_makeRfqSubcall(rfqOrder),
_makeUniswapV3BatchSubcall(_makeArray(address(dai), address(zrx)), 1e18)
),
2e18,
11e18
)
);
}
function test_metaTransaction_multiplexBatchSellTokenForToken_rfqOrderFallbackUniswapV3() external {
_createUniswapV3Pool(uniV3Factory, dai, zrx, 10e18, 10e18);
_mintTo(address(dai), otherSignerAddress, 2e18);
LibNativeOrder.RfqOrder memory rfqOrder = _makeTestRfqOrder();
rfqOrder.taker = otherSignerAddress;
rfqOrder.expiry = 1;
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken.selector,
dai,
zrx,
_makeArray(
_makeRfqSubcall(rfqOrder),
_makeUniswapV3BatchSubcall(_makeArray(address(dai), address(zrx)), 1e18)
),
1e18,
10e18
)
);
}
function test_metaTransaction_multiplexBatchSellTokenForToken_uniswapV3_revertsIfIncorrectAmount() external {
_createUniswapV3Pool(uniV3Factory, dai, zrx, 10e18, 10e18);
_mintTo(address(dai), otherSignerAddress, 1e18);
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtx;
LibSignature.Signature memory sig;
(mtx, sig) = _makeMetaTransactionV2(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken.selector,
dai,
zrx,
_makeArray(_makeUniswapV3BatchSubcall(_makeArray(address(dai), address(zrx)), 5e17)),
1e18,
1
)
);
vm.expectRevert();
zeroExDeployed.zeroEx.executeMetaTransactionV2(mtx, sig);
}
// multi hop
function test_metaTransaction_multiplexMultiHopSellTokenForToken_uniswapV2() external {
_createUniswapV2Pool(uniV2Factory, dai, zrx, 10e18, 10e18);
address[] memory tokens = _makeArray(address(dai), address(zrx));
_mintTo(address(dai), otherSignerAddress, 1e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForToken.selector,
tokens,
_makeArray(_makeUniswapV2MultiHopSubcall(tokens, false)),
1e18,
1
)
);
}
function test_metaTransaction_multiplexMultiHopSellTokenForToken_uniswapV3() external {
_createUniswapV3Pool(uniV3Factory, dai, zrx, 10e18, 10e18);
address[] memory tokens = _makeArray(address(dai), address(zrx));
_mintTo(address(dai), otherSignerAddress, 1e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForToken.selector,
tokens,
_makeArray(_makeUniswapV3MultiHopSubcall(tokens)),
1e18,
1
)
);
}
function test_metaTransaction_multiplexMultiHopSellTokenForToken_liquidityProvider() external {
_mintTo(address(dai), otherSignerAddress, 1e18);
_mintTo(address(zrx), address(liquidityProvider), 1e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForToken.selector,
_makeArray(address(dai), address(zrx)),
_makeArray(_makeMockLiquidityProviderMultiHopSubcall()),
1e18,
1
)
);
}
function test_metaTransaction_multiplexMultiHopSellTokenForToken_uniswapV2UniswapV3() external {
_createUniswapV2Pool(uniV2Factory, dai, shib, 10e18, 10e18);
_createUniswapV3Pool(uniV3Factory, shib, zrx, 10e18, 10e18);
_mintTo(address(dai), otherSignerAddress, 1e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForToken.selector,
_makeArray(address(dai), address(shib), address(zrx)),
_makeArray(
_makeUniswapV2MultiHopSubcall(_makeArray(address(dai), address(shib)), false),
_makeUniswapV3MultiHopSubcall(_makeArray(address(shib), address(zrx)))
),
1e18,
10e18
)
);
}
// batch for eth
function test_metaTransaction_multiplexBatchSellTokenForEth_uniswapV3() external {
_createUniswapV3Pool(uniV3Factory, dai, weth, 10e18, 10e18);
_mintTo(address(dai), otherSignerAddress, 1e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexBatchSellTokenForEth.selector,
dai,
_makeArray(_makeUniswapV3BatchSubcall(_makeArray(address(dai), address(weth)), 1e18)),
1e18,
1
)
);
}
function test_metaTransaction_multiplexBatchSellTokenForEth_rfqOrderUniswapV3() external {
_createUniswapV3Pool(uniV3Factory, dai, weth, 10e18, 10e18);
LibNativeOrder.RfqOrder memory rfqOrder = _makeTestRfqOrder();
rfqOrder.taker = otherSignerAddress;
rfqOrder.makerToken = weth;
_mintTo(address(weth), rfqOrder.maker, rfqOrder.makerAmount);
_mintTo(address(dai), otherSignerAddress, 2e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexBatchSellTokenForEth.selector,
dai,
_makeArray(
_makeRfqSubcall(rfqOrder),
_makeUniswapV3BatchSubcall(_makeArray(address(dai), address(weth)), 1e18)
),
2e18,
11e18
)
);
}
// nested
function test_metaTransaction_multiplexBatchSellTokenForToken_nestedUniswapV3() external {
_createUniswapV3Pool(uniV3Factory, dai, zrx, 10e18, 10e18);
address[] memory tokens = _makeArray(address(dai), address(zrx));
_mintTo(address(dai), otherSignerAddress, 1e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken.selector,
dai,
zrx,
_makeArray(
_makeNestedMultiHopSellSubcall(tokens, _makeArray(_makeUniswapV3MultiHopSubcall(tokens)), 1e18)
),
1e18,
1
)
);
}
function test_metaTransaction_multiplexMultiHopSellTokenForToken_nestedUniswapV3() external {
_createUniswapV3Pool(uniV3Factory, dai, zrx, 10e18, 10e18);
_mintTo(address(dai), otherSignerAddress, 1e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForToken.selector,
_makeArray(address(dai), address(zrx)),
_makeArray(
_makeNestedBatchSellSubcall(
_makeArray(_makeUniswapV3BatchSubcall(_makeArray(address(dai), address(zrx)), 1e18))
)
),
1e18,
1
)
);
}
// multi hop for eth
function test_metaTransaction_multiplexMultiHopSellTokenForEth_uniswapV3() external {
_createUniswapV3Pool(uniV3Factory, dai, weth, 10e18, 10e18);
address[] memory tokens = _makeArray(address(dai), address(weth));
_mintTo(address(dai), otherSignerAddress, 1e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForEth.selector,
tokens,
_makeArray(_makeUniswapV3MultiHopSubcall(tokens)),
1e18,
1
)
);
}
function test_metaTransaction_multiplexMultiHopSellTokenForEth_uniswapV3_revertsNotWeth() external {
_createUniswapV3Pool(uniV3Factory, dai, zrx, 10e18, 10e18);
address[] memory tokens = _makeArray(address(dai), address(zrx));
_mintTo(address(dai), otherSignerAddress, 1e18);
IMetaTransactionsFeatureV2.MetaTransactionDataV2 memory mtx;
LibSignature.Signature memory sig;
(mtx, sig) = _makeMetaTransactionV2(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForEth.selector,
tokens,
_makeArray(_makeUniswapV3MultiHopSubcall(tokens)),
1e18,
1
)
);
vm.expectRevert("MetaTransactionsFeature::multiplexMultiHopSellTokenForEth/NOT_WETH");
zeroExDeployed.zeroEx.executeMetaTransactionV2(mtx, sig);
}
function test_metaTransaction_multiplexMultiHopSellTokenForEth_uniswapV2UniswapV3() external {
_createUniswapV2Pool(uniV2Factory, dai, shib, 10e18, 10e18);
_createUniswapV3Pool(uniV3Factory, shib, weth, 10e18, 10e18);
_mintTo(address(dai), otherSignerAddress, 1e18);
_executeMetaTransaction(
abi.encodeWithSelector(
zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForToken.selector,
_makeArray(address(dai), address(shib), address(weth)),
_makeArray(
_makeUniswapV2MultiHopSubcall(_makeArray(address(dai), address(shib)), false),
_makeUniswapV3MultiHopSubcall(_makeArray(address(shib), address(weth)))
),
1e18,
10e18
)
);
}
}

View File

@ -0,0 +1,219 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2023 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.6;
pragma experimental ABIEncoderV2;
import "../utils/ForkUtils.sol";
import "../utils/TestUtils.sol";
import "src/IZeroEx.sol";
import "@0x/contracts-erc20/src/IEtherToken.sol";
import "src/features/TransformERC20Feature.sol";
import "src/features/multiplex/MultiplexFeature.sol";
import "src/external/TransformerDeployer.sol";
import "src/transformers/WethTransformer.sol";
import "src/transformers/FillQuoteTransformer.sol";
import "src/transformers/bridges/BridgeProtocols.sol";
import "src/features/OtcOrdersFeature.sol";
contract MultiplexRfqtTest is Test, ForkUtils, TestUtils {
using LibERC20TokenV06 for IERC20Token;
using LibERC20TokenV06 for IEtherToken;
function setUp() public {
_setup();
}
function test_swapEthForUSDTThroughFqtOtcs() public {
log_string("SwapEthForUSDTThroughFqtOtc");
/* */
for (uint256 i = 0; i < 1; i++) {
//skip fantom/avax failing test
if (i == 3 || i == 4) {
continue;
}
vm.selectFork(forkIds[chains[i]]);
log_named_string(" Selecting Fork On", chains[i]);
vm.deal(address(this), 1e18);
labelAddresses(
chains[i],
indexChainsByChain[chains[i]],
getTokens(i),
getContractAddresses(i),
getLiquiditySourceAddresses(i)
);
//redeploy and migrate multiplexFeature and OtcOrders for logging
MultiplexFeature multiplexFeature = new MultiplexFeature(
address(IZERO_EX),
IEtherToken(tokens.WrappedNativeToken),
ILiquidityProviderSandbox(addresses.exchangeProxyLiquidityProviderSandbox),
address(0), // uniswapFactory
address(0), // sushiswapFactory
bytes32(0), // uniswapPairInitCodeHash
bytes32(0) // sushiswapPairInitCodeHash
);
OtcOrdersFeature otcOrdersFeature = new OtcOrdersFeature(
address(addresses.exchangeProxy),
tokens.WrappedNativeToken
);
vm.label(address(multiplexFeature), "zeroEx/NewMultiplexFeature");
vm.label(address(otcOrdersFeature), "zeroEx/NewOtcOrdersFeature");
vm.prank(IZeroEx(addresses.exchangeProxy).owner());
IZeroEx(addresses.exchangeProxy).migrate(
address(otcOrdersFeature),
abi.encodeWithSelector(OtcOrdersFeature.migrate.selector),
address(addresses.exchangeProxy)
);
vm.prank(IZeroEx(addresses.exchangeProxy).owner());
IZeroEx(addresses.exchangeProxy).migrate(
address(multiplexFeature),
abi.encodeWithSelector(MultiplexFeature.migrate.selector),
address(addresses.exchangeProxy)
);
swapMultihopOtc(getTokens(i), getContractAddresses(i), getLiquiditySourceAddresses(i));
}
}
/* solhint-disable function-max-lines */
function swapMultihopOtc(
TokenAddresses memory tokens,
ContractAddresses memory addresses,
LiquiditySources memory sources
) public onlyForked {
IZERO_EX = IZeroEx(addresses.exchangeProxy);
address[] memory tradeTokens = new address[](3);
tradeTokens[0] = address(tokens.WrappedNativeToken);
tradeTokens[1] = address(tokens.USDC);
tradeTokens[2] = address(tokens.DAI);
deal(tradeTokens[0], address(this), 1e18);
tokens.WrappedNativeToken.approveIfBelow(addresses.exchangeProxy, uint(-1));
uint inputAmount = 1e18;
uint outputAmount = 5e17;
IMultiplexFeature.MultiHopSellSubcall[] memory subcalls = new IMultiplexFeature.MultiHopSellSubcall[](2);
IMultiplexFeature.MultiHopSellSubcall memory subcall1;
subcall1.id = IMultiplexFeature.MultiplexSubcall.OTC;
//subcall.data = abi.encode(address[], LibNativeOrder.OtcOrder, LibSignature.Signature);
(LibNativeOrder.OtcOrder memory order1, LibSignature.Signature memory signature1) = createOtcOrder(
tokens.WrappedNativeToken,
tokens.USDC,
1e18,
5e17,
0
);
subcall1.data = abi.encode(order1, signature1);
IMultiplexFeature.MultiHopSellSubcall memory subcall2;
subcall2.id = IMultiplexFeature.MultiplexSubcall.OTC;
(LibNativeOrder.OtcOrder memory order2, LibSignature.Signature memory signature2) = createOtcOrder(
tokens.USDC,
tokens.DAI,
5e17,
5e17,
1
);
subcall2.data = abi.encode(order2, signature2);
subcalls[0] = subcall1;
subcalls[1] = subcall2;
uint balanceBefore = tokens.DAI.balanceOf(address(this));
emit log_named_uint("DAI Balance Before", balanceBefore);
emit log_string("Multihop Rfqt: WETH->USDC->DAI");
/// @dev Sells `sellAmount` of the input token (`tokens[0]`)
/// via the given sequence of tokens and calls.
/// The last token in `tokens` is the output token that
/// will ultimately be sent to `msg.sender`
/// @param tokens The sequence of tokens to use for the sell,
/// i.e. `tokens[i]` will be sold for `tokens[i+1]` via
/// `calls[i]`.
/// @param calls The sequence of calls to use for the sell.
/// @param sellAmount The amount of `inputToken` to sell.
/// @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.
IZERO_EX.multiplexMultiHopSellTokenForToken(
// input token[] [input, intermediate, output]
tradeTokens,
//array of subcalls [{},{}]
subcalls,
// input token amount
inputAmount,
// min output token amount
outputAmount
);
uint balanceAfter = tokens.DAI.balanceOf(address(this));
emit log_named_uint("DAI Balance After", balanceAfter - balanceBefore);
require(balanceAfter >= 5e17, "Failed: UNDERBOUGHT");
}
function createOtcOrder(
IERC20Token inputToken,
IERC20Token ouputToken,
uint128 takerAmount,
uint128 makerAmount,
uint bump
) public returns (LibNativeOrder.OtcOrder memory order, LibSignature.Signature memory signature) {
LibNativeOrder.OtcOrder memory order;
LibSignature.Signature memory signature;
order.makerToken = ouputToken;
order.takerToken = inputToken;
order.takerAmount = takerAmount;
order.makerAmount = makerAmount;
uint privateKey;
(order.maker, privateKey) = _getSigner();
deal(address(order.makerToken), order.maker, 2e20);
deal(address(order.takerToken), order.maker, 2e20);
vm.startPrank(order.maker);
IERC20Token(order.makerToken).approveIfBelow(addresses.exchangeProxy, 2e20);
order.taker = address(0);
order.txOrigin = address(tx.origin);
order.expiryAndNonce = encodeExpiryAndNonce(order.maker, bump);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, IZERO_EX.getOtcOrderHash(order));
vm.stopPrank();
signature.signatureType = LibSignature.SignatureType.EIP712;
signature.v = v;
signature.r = r;
signature.s = s;
return (order, signature);
}
/* solhint-enable function-max-lines */
function encodeExpiryAndNonce(address maker, uint bump) public returns (uint256) {
uint256 expiry = (block.timestamp + 120) << 192;
uint256 bucket = (0 + bump) << 128;
uint256 nonce = vm.getNonce(maker);
return expiry | bucket | nonce;
}
}

View File

@ -28,6 +28,7 @@ import "src/features/FundRecoveryFeature.sol";
import "src/features/TransformERC20Feature.sol"; import "src/features/TransformERC20Feature.sol";
import "src/features/OtcOrdersFeature.sol"; import "src/features/OtcOrdersFeature.sol";
import "src/features/MetaTransactionsFeature.sol"; import "src/features/MetaTransactionsFeature.sol";
import "src/features/MetaTransactionsFeatureV2.sol";
import "src/features/nft_orders/ERC1155OrdersFeature.sol"; import "src/features/nft_orders/ERC1155OrdersFeature.sol";
import "src/features/nft_orders/ERC721OrdersFeature.sol"; import "src/features/nft_orders/ERC721OrdersFeature.sol";
import "src/features/UniswapFeature.sol"; import "src/features/UniswapFeature.sol";
@ -68,6 +69,7 @@ contract DeployZeroEx is Test {
FundRecoveryFeature fundRecoveryFeature; FundRecoveryFeature fundRecoveryFeature;
TransformERC20Feature transformERC20Feature; TransformERC20Feature transformERC20Feature;
MetaTransactionsFeature metaTransactionsFeature; MetaTransactionsFeature metaTransactionsFeature;
MetaTransactionsFeatureV2 metaTransactionsFeatureV2;
ERC1155OrdersFeature erc1155OrdersFeature; ERC1155OrdersFeature erc1155OrdersFeature;
ERC721OrdersFeature erc721OrdersFeature; ERC721OrdersFeature erc721OrdersFeature;
MultiplexFeature multiplexFeature; MultiplexFeature multiplexFeature;
@ -133,6 +135,10 @@ contract DeployZeroEx is Test {
emit log_named_address("UniswapV3Feature", address(ZERO_EX_DEPLOYED.features.uniswapV3Feature)); emit log_named_address("UniswapV3Feature", address(ZERO_EX_DEPLOYED.features.uniswapV3Feature));
emit log_named_address("FundRecoveryFeature", address(ZERO_EX_DEPLOYED.features.fundRecoveryFeature)); emit log_named_address("FundRecoveryFeature", address(ZERO_EX_DEPLOYED.features.fundRecoveryFeature));
emit log_named_address("MetaTransactionsFeature", address(ZERO_EX_DEPLOYED.features.metaTransactionsFeature)); emit log_named_address("MetaTransactionsFeature", address(ZERO_EX_DEPLOYED.features.metaTransactionsFeature));
emit log_named_address(
"MetaTransactionsFeatureV2",
address(ZERO_EX_DEPLOYED.features.metaTransactionsFeatureV2)
);
emit log_named_address("ERC1155OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature)); emit log_named_address("ERC1155OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature));
emit log_named_address("ERC721OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc721OrdersFeature)); emit log_named_address("ERC721OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc721OrdersFeature));
emit log_named_address("TransformERC20Feature", address(ZERO_EX_DEPLOYED.features.transformERC20Feature)); emit log_named_address("TransformERC20Feature", address(ZERO_EX_DEPLOYED.features.transformERC20Feature));
@ -201,6 +207,10 @@ contract DeployZeroEx is Test {
); );
ZERO_EX_DEPLOYED.features.fundRecoveryFeature = new FundRecoveryFeature(); ZERO_EX_DEPLOYED.features.fundRecoveryFeature = new FundRecoveryFeature();
ZERO_EX_DEPLOYED.features.metaTransactionsFeature = new MetaTransactionsFeature(address(ZERO_EX)); ZERO_EX_DEPLOYED.features.metaTransactionsFeature = new MetaTransactionsFeature(address(ZERO_EX));
ZERO_EX_DEPLOYED.features.metaTransactionsFeatureV2 = new MetaTransactionsFeatureV2(
address(ZERO_EX),
ZERO_EX_DEPLOYED.weth
);
ZERO_EX_DEPLOYED.features.erc1155OrdersFeature = new ERC1155OrdersFeature( ZERO_EX_DEPLOYED.features.erc1155OrdersFeature = new ERC1155OrdersFeature(
address(ZERO_EX), address(ZERO_EX),
ZERO_EX_DEPLOYED.weth ZERO_EX_DEPLOYED.weth
@ -270,6 +280,11 @@ contract DeployZeroEx is Test {
abi.encodeWithSelector(MetaTransactionsFeature.migrate.selector), abi.encodeWithSelector(MetaTransactionsFeature.migrate.selector),
address(this) address(this)
); );
IZERO_EX.migrate(
address(ZERO_EX_DEPLOYED.features.metaTransactionsFeatureV2),
abi.encodeWithSelector(MetaTransactionsFeatureV2.migrate.selector),
address(this)
);
IZERO_EX.migrate( IZERO_EX.migrate(
address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature), address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature),
abi.encodeWithSelector(ERC1155OrdersFeature.migrate.selector), abi.encodeWithSelector(ERC1155OrdersFeature.migrate.selector),

View File

@ -54,6 +54,9 @@ contract LocalTest is Test, TestUtils {
address internal signerAddress; address internal signerAddress;
uint256 internal signerKey; uint256 internal signerKey;
address internal otherSignerAddress;
uint256 internal otherSignerKey;
function _infiniteApprovals() private { function _infiniteApprovals() private {
shib.approve(address(zeroExDeployed.zeroEx), type(uint256).max); shib.approve(address(zeroExDeployed.zeroEx), type(uint256).max);
dai.approve(address(zeroExDeployed.zeroEx), type(uint256).max); dai.approve(address(zeroExDeployed.zeroEx), type(uint256).max);
@ -92,12 +95,23 @@ contract LocalTest is Test, TestUtils {
zrx = IERC20Token(address(new TestMintableERC20Token())); zrx = IERC20Token(address(new TestMintableERC20Token()));
weth = zeroExDeployed.weth; weth = zeroExDeployed.weth;
// TODO this should be somewhere else
string memory mnemonic = "conduct into noodle wreck before satisfy alarm vendor dose lunch vapor party";
otherSignerKey = vm.deriveKey(mnemonic, 0);
otherSignerAddress = vm.addr(otherSignerKey);
vm.label(otherSignerAddress, "zeroEx/OtherGuy");
_infiniteApprovals(); _infiniteApprovals();
vm.startPrank(signerAddress); vm.startPrank(signerAddress);
_infiniteApprovals(); _infiniteApprovals();
vm.stopPrank(); vm.stopPrank();
vm.deal(address(this), 10e18); vm.startPrank(otherSignerAddress);
_infiniteApprovals();
vm.stopPrank();
vm.deal(address(this), 20e18);
} }
function _mintTo(address token, address recipient, uint256 amount) internal { function _mintTo(address token, address recipient, uint256 amount) internal {

View File

@ -20,6 +20,8 @@ import "forge-std/Test.sol";
import "src/transformers/LibERC20Transformer.sol"; import "src/transformers/LibERC20Transformer.sol";
contract TestUtils is Test { contract TestUtils is Test {
address private constant ZERO_ADDRESS = 0x0000000000000000000000000000000000000000;
function _findTransformerNonce(address transformer, address deployer) internal pure returns (uint32) { function _findTransformerNonce(address transformer, address deployer) internal pure returns (uint32) {
address current; address current;
for (uint32 i = 0; i < 1024; i++) { for (uint32 i = 0; i < 1024; i++) {

View File

@ -30,6 +30,7 @@
"generated-artifacts/LiquidityProviderFeature.json", "generated-artifacts/LiquidityProviderFeature.json",
"generated-artifacts/LogMetadataTransformer.json", "generated-artifacts/LogMetadataTransformer.json",
"generated-artifacts/MetaTransactionsFeature.json", "generated-artifacts/MetaTransactionsFeature.json",
"generated-artifacts/MetaTransactionsFeatureV2.json",
"generated-artifacts/MultiplexFeature.json", "generated-artifacts/MultiplexFeature.json",
"generated-artifacts/NativeOrdersFeature.json", "generated-artifacts/NativeOrdersFeature.json",
"generated-artifacts/OptimismBridgeAdapter.json", "generated-artifacts/OptimismBridgeAdapter.json",
@ -88,6 +89,7 @@
"test/generated-artifacts/ILiquidityProviderFeature.json", "test/generated-artifacts/ILiquidityProviderFeature.json",
"test/generated-artifacts/ILiquidityProviderSandbox.json", "test/generated-artifacts/ILiquidityProviderSandbox.json",
"test/generated-artifacts/IMetaTransactionsFeature.json", "test/generated-artifacts/IMetaTransactionsFeature.json",
"test/generated-artifacts/IMetaTransactionsFeatureV2.json",
"test/generated-artifacts/IMooniswapPool.json", "test/generated-artifacts/IMooniswapPool.json",
"test/generated-artifacts/IMultiplexFeature.json", "test/generated-artifacts/IMultiplexFeature.json",
"test/generated-artifacts/INativeOrdersEvents.json", "test/generated-artifacts/INativeOrdersEvents.json",
@ -141,6 +143,7 @@
"test/generated-artifacts/LiquidityProviderSandbox.json", "test/generated-artifacts/LiquidityProviderSandbox.json",
"test/generated-artifacts/LogMetadataTransformer.json", "test/generated-artifacts/LogMetadataTransformer.json",
"test/generated-artifacts/MetaTransactionsFeature.json", "test/generated-artifacts/MetaTransactionsFeature.json",
"test/generated-artifacts/MetaTransactionsFeatureV2.json",
"test/generated-artifacts/MixinAaveV2.json", "test/generated-artifacts/MixinAaveV2.json",
"test/generated-artifacts/MixinBalancer.json", "test/generated-artifacts/MixinBalancer.json",
"test/generated-artifacts/MixinBalancerV2Batch.json", "test/generated-artifacts/MixinBalancerV2Batch.json",