Compare commits

...

22 Commits

Author SHA1 Message Date
Duncan Townsend
ac563f5164
Zero out missing UniswapV2 on Polygon 2023-04-10 12:08:56 -04:00
Patrick Dowell
ca7393641f Deploy script for MTX V2 and multiplex 2023-04-10 03:55:09 -07:00
Patrick Dowell
6e926af41c Making executeMetaTransactionV2 nonpayable and addressing a few other minor issues 2023-03-21 14:42:45 -07:00
Patrick Dowell
172822044b Fixing multiHopSellOtcOrder when params.useSelfBalance is true 2023-02-24 10:45:49 -08:00
abls
a0485ca3e0 fix _computeHopTarget for otc subcalls 2023-02-24 05:14:03 -08:00
Patrick Dowell
f32e834e11 add multiHopBatchSellOtc to MultiplexFeature, fix _computeHopTarget for MultiplexSubcall.OTC [#667] 2023-02-23 23:27:35 -08:00
Patrick Dowell
b5c18c2a9e Removing ZERO_ADDRESS 2023-02-23 17:54:00 -08:00
Patrick Dowell
217348f31b cleaning up and adding batchExecuteMetaTransaction tests 2023-02-23 17:47:56 -08:00
Patrick Dowell
65a2024285 minor test fix 2023-02-23 17:12:57 -08:00
abls
b942551e33 prettier 2023-02-23 16:50:30 -08:00
abls
4c837110f2 add some tests for multiplex metatransactions 2023-02-23 16:39:07 -08:00
Patrick Dowell
88c96659a1 Fixing multiplex test failure 2023-02-23 13:26:20 -08:00
Patrick Dowell
152accf9c1 Complex rebase of test code based on changes in #655 2023-02-23 02:38:37 -08:00
Patrick Dowell
0f5d832daf Addressing suggestions from PR reviewers 2023-02-23 02:07:25 -08:00
Patrick Dowell
13dd95688d Fixing issues with EIP 712 signature, adding test case against MetaMask, and fixing lint issues 2023-02-23 02:07:25 -08:00
Patrick Dowell
15f79feb81 More linting 2023-02-23 02:07:25 -08:00
Patrick Dowell
3b55a88fc5 Ran prettier to clean up 2023-02-23 02:07:25 -08:00
Patrick Dowell
cdb94d1780 add multiplexBatchSellTokenForToken, multiplexMultiHopSellTokenForToken, multiplex TokenForEth functions to metatransactions, add msgSender field to multiplex params 2023-02-23 02:07:25 -08:00
Patrick Dowell
865c1f05db MetaTransactionV2 creation and forge tests 2023-02-23 02:07:25 -08:00
Patrick Dowell
2bbf3956f3 MetaTransactionData changes 2023-02-23 02:07:25 -08:00
Patrick Dowell
5cf4cbe4b5 MetaTransactionV2 creation and forge tests 2023-02-23 02:07:25 -08:00
Patrick Dowell
f402c96053 MetaTransactionData changes 2023-02-23 02:06:19 -08:00
23 changed files with 2190 additions and 48 deletions

@ -1 +1 @@
Subproject commit a2edd39db95df7e9dd3f9ef9edc8c55fefddb6df
Subproject commit 058d2004ac10cc8f194625fb107fb7a87c4e702d

View File

@ -20,6 +20,7 @@ import "./features/interfaces/ISimpleFunctionRegistryFeature.sol";
import "./features/interfaces/ITokenSpenderFeature.sol";
import "./features/interfaces/ITransformERC20Feature.sol";
import "./features/interfaces/IMetaTransactionsFeature.sol";
import "./features/interfaces/IMetaTransactionsFeatureV2.sol";
import "./features/interfaces/IUniswapFeature.sol";
import "./features/interfaces/IUniswapV3Feature.sol";
import "./features/interfaces/IPancakeSwapFeature.sol";
@ -39,6 +40,7 @@ interface IZeroEx is
ISimpleFunctionRegistryFeature,
ITransformERC20Feature,
IMetaTransactionsFeature,
IMetaTransactionsFeatureV2,
IUniswapFeature,
IUniswapV3Feature,
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.sellTokenForEthToUniswapV3.selector);
_registerFeatureFunction(this.sellTokenForTokenToUniswapV3.selector);
_registerFeatureFunction(this._sellTokenForTokenToUniswapV3.selector);
_registerFeatureFunction(this._sellHeldTokenForTokenToUniswapV3.selector);
_registerFeatureFunction(this.uniswapV3SwapCallback.selector);
return LibMigrate.MIGRATE_SUCCESS;
@ -139,6 +140,23 @@ contract UniswapV3Feature is IFeature, IUniswapV3Feature, FixinCommon, FixinToke
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.
/// Private variant, uses tokens held by `address(this)`.
/// @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.
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;
// The recipient of the bought output tokens.
address recipient;
// The sender of the input tokens.
address payer;
}
// Represents a constituent call of a batch sell.
@ -75,6 +77,8 @@ interface IMultiplexFeature {
bool useSelfBalance;
// The recipient of the bought output tokens.
address recipient;
// The sender of the input tokens.
address payer;
}
// Represents a constituent call of a multi-hop sell.
@ -153,6 +157,17 @@ interface IMultiplexFeature {
uint256 minBuyAmount
) 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
/// and calls. `tokens[0]` must be WETH.
/// The last token in `tokens` is the output token that
@ -204,4 +219,15 @@ interface IMultiplexFeature {
uint256 sellAmount,
uint256 minBuyAmount
) 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
) 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.
/// Private variant, uses tokens held by `address(this)`.
/// @param encodedPath Uniswap-encoded path.

View File

@ -80,9 +80,11 @@ contract MultiplexFeature is
_registerFeatureFunction(this.multiplexBatchSellEthForToken.selector);
_registerFeatureFunction(this.multiplexBatchSellTokenForEth.selector);
_registerFeatureFunction(this.multiplexBatchSellTokenForToken.selector);
_registerFeatureFunction(this._multiplexBatchSell.selector);
_registerFeatureFunction(this.multiplexMultiHopSellEthForToken.selector);
_registerFeatureFunction(this.multiplexMultiHopSellTokenForEth.selector);
_registerFeatureFunction(this.multiplexMultiHopSellTokenForToken.selector);
_registerFeatureFunction(this._multiplexMultiHopSell.selector);
return LibMigrate.MIGRATE_SUCCESS;
}
@ -103,14 +105,15 @@ contract MultiplexFeature is
// WETH is now held by this contract,
// so `useSelfBalance` is true.
return
_multiplexBatchSell(
_multiplexBatchSellPrivate(
BatchSellParams({
inputToken: WETH,
outputToken: outputToken,
sellAmount: msg.value,
calls: calls,
useSelfBalance: true,
recipient: msg.sender
recipient: msg.sender,
payer: msg.sender
}),
minBuyAmount
);
@ -133,14 +136,15 @@ contract MultiplexFeature is
// The outputToken is implicitly WETH. The `recipient`
// of the WETH is set to this contract, since we
// must unwrap the WETH and transfer the resulting ETH.
boughtAmount = _multiplexBatchSell(
boughtAmount = _multiplexBatchSellPrivate(
BatchSellParams({
inputToken: inputToken,
outputToken: WETH,
sellAmount: sellAmount,
calls: calls,
useSelfBalance: false,
recipient: address(this)
recipient: address(this),
payer: msg.sender
}),
minBuyAmount
);
@ -167,26 +171,40 @@ contract MultiplexFeature is
uint256 minBuyAmount
) public override returns (uint256 boughtAmount) {
return
_multiplexBatchSell(
_multiplexBatchSellPrivate(
BatchSellParams({
inputToken: inputToken,
outputToken: outputToken,
sellAmount: sellAmount,
calls: calls,
useSelfBalance: false,
recipient: msg.sender
recipient: msg.sender,
payer: msg.sender
}),
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
/// `minBuyAmount` of `outputToken` was bought.
/// @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(
function _multiplexBatchSellPrivate(
BatchSellParams memory params,
uint256 minBuyAmount
) private returns (uint256 boughtAmount) {
@ -226,13 +244,14 @@ contract MultiplexFeature is
// WETH is now held by this contract,
// so `useSelfBalance` is true.
return
_multiplexMultiHopSell(
_multiplexMultiHopSellPrivate(
MultiHopSellParams({
tokens: tokens,
sellAmount: msg.value,
calls: calls,
useSelfBalance: true,
recipient: msg.sender
recipient: msg.sender,
payer: msg.sender
}),
minBuyAmount
);
@ -262,13 +281,14 @@ contract MultiplexFeature is
);
// The `recipient of the WETH is set to this contract, since
// we must unwrap the WETH and transfer the resulting ETH.
boughtAmount = _multiplexMultiHopSell(
boughtAmount = _multiplexMultiHopSellPrivate(
MultiHopSellParams({
tokens: tokens,
sellAmount: sellAmount,
calls: calls,
useSelfBalance: false,
recipient: address(this)
recipient: address(this),
payer: msg.sender
}),
minBuyAmount
);
@ -297,25 +317,38 @@ contract MultiplexFeature is
uint256 minBuyAmount
) public override returns (uint256 boughtAmount) {
return
_multiplexMultiHopSell(
_multiplexMultiHopSellPrivate(
MultiHopSellParams({
tokens: tokens,
sellAmount: sellAmount,
calls: calls,
useSelfBalance: false,
recipient: msg.sender
recipient: msg.sender,
payer: msg.sender
}),
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
/// `minBuyAmount` of output tokens were bought.
/// @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(
function _multiplexMultiHopSellPrivate(
MultiHopSellParams memory params,
uint256 minBuyAmount
) private returns (uint256 boughtAmount) {
@ -387,14 +420,14 @@ contract MultiplexFeature is
// amount of the multi-hop fill.
state.outputTokenAmount = params.sellAmount;
// 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
// there if necessary.
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`.
if (!params.useSelfBalance && state.from != msg.sender) {
_transferERC20TokensFrom(IERC20Token(params.tokens[0]), msg.sender, state.from, params.sellAmount);
if (!params.useSelfBalance && state.from != params.payer) {
_transferERC20TokensFrom(IERC20Token(params.tokens[0]), params.payer, state.from, params.sellAmount);
}
// If the input tokens are currently held by `address(this)` but
// the first hop expects them elsewhere, perform a `transfer`.
@ -411,11 +444,13 @@ contract MultiplexFeature is
if (subcall.id == MultiplexSubcall.UniswapV2) {
_multiHopSellUniswapV2(state, params, subcall.data);
} else if (subcall.id == MultiplexSubcall.UniswapV3) {
_multiHopSellUniswapV3(state, subcall.data);
_multiHopSellUniswapV3(state, params, subcall.data);
} else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
_multiHopSellLiquidityProvider(state, params, subcall.data);
} else if (subcall.id == MultiplexSubcall.BatchSell) {
_nestedBatchSell(state, params, subcall.data);
} else if (subcall.id == MultiplexSubcall.OTC) {
_multiHopSellOtcOrder(state, params, subcall.data);
} else {
revert("MultiplexFeature::_executeMultiHopSell/INVALID_SUBCALL");
}
@ -443,6 +478,8 @@ contract MultiplexFeature is
// Likewise, the recipient of the multi-hop sell is
// equal to the recipient of its containing batch sell.
multiHopParams.recipient = params.recipient;
// The payer is the same too.
multiHopParams.payer = params.payer;
// Execute the nested multi-hop sell.
uint256 outputTokenAmount = _executeMultiHopSell(multiHopParams).outputTokenAmount;
// Increment the sold and bought amounts.
@ -469,7 +506,7 @@ contract MultiplexFeature is
// If the nested batch sell is the first hop
// and `useSelfBalance` for the containing multi-
// 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 true.
batchSellParams.useSelfBalance = state.hopIndex > 0 || params.useSelfBalance;
@ -477,6 +514,8 @@ contract MultiplexFeature is
// that should receive the output tokens of the
// batch sell.
batchSellParams.recipient = state.to;
// payer shound be the same too.
batchSellParams.payer = params.payer;
// Execute the nested batch sell.
state.outputTokenAmount = _executeBatchSell(batchSellParams).boughtAmount;
}
@ -505,29 +544,33 @@ contract MultiplexFeature is
// is executed, so we the target is the address encoded
// in the subcall data.
(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
// sold to it. The callback implemented in `UniswapV3Feature`
// can either:
// - call `transferFrom` to move tokens from `msg.sender` to the
// - call `transferFrom` to move tokens from `payer` to the
// UniswapV3 pool, or
// - call `transfer` to move tokens from `address(this)` to the
// UniswapV3 pool.
// 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)`.
// 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
// held by `address(this)`. The target is set accordingly.
// If this is _not_ the first call in the multi-hop path, we
// 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`.
if (i == 0 && !params.useSelfBalance) {
target = msg.sender;
target = params.payer;
} else {
target = address(this);
}

View File

@ -67,7 +67,7 @@ abstract contract MultiplexLiquidityProvider is FixinCommon, FixinTokenSpender {
_transferERC20Tokens(params.inputToken, provider, sellAmount);
} else {
// 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.
uint256 balanceBefore = params.outputToken.balanceOf(params.recipient);

View File

@ -55,7 +55,7 @@ abstract contract MultiplexOtc is FixinEIP712 {
order,
signature,
sellAmount.safeDowncastToUint128(),
msg.sender,
params.payer,
params.useSelfBalance,
params.recipient
)
@ -65,4 +65,34 @@ abstract contract MultiplexOtc is FixinEIP712 {
state.boughtAmount = state.boughtAmount.safeAdd(makerTokenFilledAmount);
} 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,
signature,
sellAmount.safeDowncastToUint128(),
msg.sender,
params.payer,
params.useSelfBalance,
params.recipient
)

View File

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

View File

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

View File

@ -45,16 +45,16 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
)
);
} else {
// Otherwise, we self-delegatecall the normal variant
// `sellTokenForTokenToUniswapV3`, which pulls the input token
// from `msg.sender`.
(success, resultData) = address(this).delegatecall(
// Otherwise, we self-call `_sellTokenForTokenToUniswapV3`,
// which pulls the input token from a specified `payer`.
(success, resultData) = address(this).call(
abi.encodeWithSelector(
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector,
IUniswapV3Feature._sellTokenForTokenToUniswapV3.selector,
wrappedCallData,
sellAmount,
0,
params.recipient
params.recipient,
params.payer
)
);
}
@ -69,6 +69,7 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
function _multiHopSellUniswapV3(
IMultiplexFeature.MultiHopSellState memory state,
IMultiplexFeature.MultiHopSellParams memory params,
bytes memory wrappedCallData
) internal {
bool success;
@ -87,16 +88,16 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
)
);
} else {
// Otherwise, we self-delegatecall the normal variant
// `sellTokenForTokenToUniswapV3`, which pulls the input token
// from `msg.sender`.
(success, resultData) = address(this).delegatecall(
// Otherwise, we self-call `_sellTokenForTokenToUniswapV3`,
// which pulls the input token from `payer`.
(success, resultData) = address(this).call(
abi.encodeWithSelector(
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector,
IUniswapV3Feature._sellTokenForTokenToUniswapV3.selector,
wrappedCallData,
state.outputTokenAmount,
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,
OtcOrders,
ERC721Orders,
ERC1155Orders
ERC1155Orders,
MetaTransactionsV2
}
/// @dev Get the storage slot given a storage ID. We assign unique, well-spaced slots to storage bucket variables

View File

@ -0,0 +1,96 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "forge-std/Script.sol";
import "forge-std/console.sol";
import "@0x/contracts-erc20/src/IEtherToken.sol";
import "../contracts/src/external/ILiquidityProviderSandbox.sol";
import '../contracts/src/features/interfaces/IMetaTransactionsFeatureV2.sol';
import '../contracts/src/features/interfaces/IMultiplexFeature.sol';
import '../contracts/src/features/interfaces/IUniswapV3Feature.sol';
import '../contracts/src/features/MetaTransactionsFeatureV2.sol';
import '../contracts/src/features/UniswapV3Feature.sol';
import '../contracts/src/features/multiplex/MultiplexFeature.sol';
contract ContractScript is Script {
struct ChainConfig {
address zeroExAddress;
address wethAddress;
address liquidityProviderSandbox;
address uniswapV3Factory;
address uniswapV2Factory;
address sushiswapFactory;
bytes32 uniswapV3PoolInitCodeHash;
bytes32 uniswapV2PairInitCodeHash;
bytes32 sushiswapPairInitCodeHash;
}
function _getChainConfig() internal pure returns (ChainConfig memory) {
uint256 chainId;
assembly {
chainId := chainid()
}
if (chainId == 1) { // Ethereum
ChainConfig memory chainConfig = ChainConfig(
0xDef1C0ded9bec7F1a1670819833240f027b25EfF, // 0x EP
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, // weth address
0x407B4128E9eCaD8769B2332312a9F655cB9F5F3A, // LP sandbox
0x1F98431c8aD98523631AE4a59f267346ea31F984, // uniswapv3
0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f, // uniswapv2
0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac, // sushiswap
bytes32(0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54),
bytes32(0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f),
bytes32(0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303)
);
return chainConfig;
}
if (chainId == 137) { // polygon
ChainConfig memory chainConfig = ChainConfig(
0xDef1C0ded9bec7F1a1670819833240f027b25EfF, // 0x EP
0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270, // wmatic address
0x4Dd97080aDf36103bD3db822f9d3c0e44890fd69, // LP sandbox
0x1F98431c8aD98523631AE4a59f267346ea31F984, // uniswapv3
address(0), // there is no uniswapv2 on polygon
0xc35DADB65012eC5796536bD9864eD8773aBc74C4, // sushiswap
bytes32(0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54),
bytes32(0), // there is no uniswapv2 on polygon
bytes32(0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303)
);
return chainConfig;
}
revert("unsupported chain");
}
function run() public {
ChainConfig memory chainConfig = _getChainConfig();
vm.startBroadcast();
MetaTransactionsFeatureV2 mtx = new MetaTransactionsFeatureV2(chainConfig.zeroExAddress, IEtherToken(chainConfig.wethAddress));
console.log("MetaTransactionsFeatureV2 address:", address(mtx));
UniswapV3Feature uni = new UniswapV3Feature(
IEtherToken(chainConfig.wethAddress),
chainConfig.uniswapV3Factory,
chainConfig.uniswapV3PoolInitCodeHash
);
console.log("UniswapV3Feature address:", address(uni));
MultiplexFeature multi = new MultiplexFeature(
chainConfig.zeroExAddress,
IEtherToken(chainConfig.wethAddress),
ILiquidityProviderSandbox(chainConfig.liquidityProviderSandbox),
chainConfig.uniswapV2Factory,
chainConfig.sushiswapFactory,
chainConfig.uniswapV2PairInitCodeHash,
chainConfig.sushiswapPairInitCodeHash
);
console.log("MultiplexFeature address:", address(multi));
vm.stopBroadcast();
}
}

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/OtcOrdersFeature.sol";
import "src/features/MetaTransactionsFeature.sol";
import "src/features/MetaTransactionsFeatureV2.sol";
import "src/features/nft_orders/ERC1155OrdersFeature.sol";
import "src/features/nft_orders/ERC721OrdersFeature.sol";
import "src/features/UniswapFeature.sol";
@ -68,6 +69,7 @@ contract DeployZeroEx is Test {
FundRecoveryFeature fundRecoveryFeature;
TransformERC20Feature transformERC20Feature;
MetaTransactionsFeature metaTransactionsFeature;
MetaTransactionsFeatureV2 metaTransactionsFeatureV2;
ERC1155OrdersFeature erc1155OrdersFeature;
ERC721OrdersFeature erc721OrdersFeature;
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("FundRecoveryFeature", address(ZERO_EX_DEPLOYED.features.fundRecoveryFeature));
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("ERC721OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc721OrdersFeature));
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.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(
address(ZERO_EX),
ZERO_EX_DEPLOYED.weth
@ -270,6 +280,11 @@ contract DeployZeroEx is Test {
abi.encodeWithSelector(MetaTransactionsFeature.migrate.selector),
address(this)
);
IZERO_EX.migrate(
address(ZERO_EX_DEPLOYED.features.metaTransactionsFeatureV2),
abi.encodeWithSelector(MetaTransactionsFeatureV2.migrate.selector),
address(this)
);
IZERO_EX.migrate(
address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature),
abi.encodeWithSelector(ERC1155OrdersFeature.migrate.selector),

View File

@ -54,6 +54,9 @@ contract LocalTest is Test, TestUtils {
address internal signerAddress;
uint256 internal signerKey;
address internal otherSignerAddress;
uint256 internal otherSignerKey;
function _infiniteApprovals() private {
shib.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()));
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();
vm.startPrank(signerAddress);
_infiniteApprovals();
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 {

View File

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