feat: Multiplex + MetaTransaction integration and MetaTransaction Multi-Fee Support [RFQ-795] [LIT-870] (#665)
* MetaTransactionData changes * MetaTransactionV2 creation and forge tests * MetaTransactionData changes * MetaTransactionV2 creation and forge tests * add multiplexBatchSellTokenForToken, multiplexMultiHopSellTokenForToken, multiplex TokenForEth functions to metatransactions, add msgSender field to multiplex params * Ran prettier to clean up * More linting * Fixing issues with EIP 712 signature, adding test case against MetaMask, and fixing lint issues * Addressing suggestions from PR reviewers * Complex rebase of test code based on changes in #655 * Fixing multiplex test failure * add some tests for multiplex metatransactions * prettier * minor test fix * cleaning up and adding batchExecuteMetaTransaction tests * Removing ZERO_ADDRESS * add multiHopBatchSellOtc to MultiplexFeature, fix _computeHopTarget for MultiplexSubcall.OTC [#667] * fix _computeHopTarget for otc subcalls * Fixing multiHopSellOtcOrder when params.useSelfBalance is true * Making executeMetaTransactionV2 nonpayable and addressing a few other minor issues * Forge update * Add MetaTransactionsFeatureV2 to exported contracts --------- Co-authored-by: abls <112491550+abls@users.noreply.github.com> Co-authored-by: Duncan Townsend <git@duncancmt.com>
This commit is contained in:
parent
8a2305c7b9
commit
ff104e7505
@ -1 +1 @@
|
|||||||
Subproject commit a2edd39db95df7e9dd3f9ef9edc8c55fefddb6df
|
Subproject commit fc560fa34fa12a335a50c35d92e55a6628ca467c
|
@ -31,6 +31,7 @@
|
|||||||
"./contracts/src/features/FundRecoveryFeature.sol",
|
"./contracts/src/features/FundRecoveryFeature.sol",
|
||||||
"./contracts/src/features/LiquidityProviderFeature.sol",
|
"./contracts/src/features/LiquidityProviderFeature.sol",
|
||||||
"./contracts/src/features/MetaTransactionsFeature.sol",
|
"./contracts/src/features/MetaTransactionsFeature.sol",
|
||||||
|
"./contracts/src/features/MetaTransactionsFeatureV2.sol",
|
||||||
"./contracts/src/features/NativeOrdersFeature.sol",
|
"./contracts/src/features/NativeOrdersFeature.sol",
|
||||||
"./contracts/src/features/OtcOrdersFeature.sol",
|
"./contracts/src/features/OtcOrdersFeature.sol",
|
||||||
"./contracts/src/features/OwnableFeature.sol",
|
"./contracts/src/features/OwnableFeature.sol",
|
||||||
@ -48,6 +49,7 @@
|
|||||||
"./contracts/src/features/interfaces/IFundRecoveryFeature.sol",
|
"./contracts/src/features/interfaces/IFundRecoveryFeature.sol",
|
||||||
"./contracts/src/features/interfaces/ILiquidityProviderFeature.sol",
|
"./contracts/src/features/interfaces/ILiquidityProviderFeature.sol",
|
||||||
"./contracts/src/features/interfaces/IMetaTransactionsFeature.sol",
|
"./contracts/src/features/interfaces/IMetaTransactionsFeature.sol",
|
||||||
|
"./contracts/src/features/interfaces/IMetaTransactionsFeatureV2.sol",
|
||||||
"./contracts/src/features/interfaces/IMultiplexFeature.sol",
|
"./contracts/src/features/interfaces/IMultiplexFeature.sol",
|
||||||
"./contracts/src/features/interfaces/INativeOrdersEvents.sol",
|
"./contracts/src/features/interfaces/INativeOrdersEvents.sol",
|
||||||
"./contracts/src/features/interfaces/INativeOrdersFeature.sol",
|
"./contracts/src/features/interfaces/INativeOrdersFeature.sol",
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit a2edd39db95df7e9dd3f9ef9edc8c55fefddb6df
|
Subproject commit fc560fa34fa12a335a50c35d92e55a6628ca467c
|
@ -20,6 +20,7 @@ import "./features/interfaces/ISimpleFunctionRegistryFeature.sol";
|
|||||||
import "./features/interfaces/ITokenSpenderFeature.sol";
|
import "./features/interfaces/ITokenSpenderFeature.sol";
|
||||||
import "./features/interfaces/ITransformERC20Feature.sol";
|
import "./features/interfaces/ITransformERC20Feature.sol";
|
||||||
import "./features/interfaces/IMetaTransactionsFeature.sol";
|
import "./features/interfaces/IMetaTransactionsFeature.sol";
|
||||||
|
import "./features/interfaces/IMetaTransactionsFeatureV2.sol";
|
||||||
import "./features/interfaces/IUniswapFeature.sol";
|
import "./features/interfaces/IUniswapFeature.sol";
|
||||||
import "./features/interfaces/IUniswapV3Feature.sol";
|
import "./features/interfaces/IUniswapV3Feature.sol";
|
||||||
import "./features/interfaces/IPancakeSwapFeature.sol";
|
import "./features/interfaces/IPancakeSwapFeature.sol";
|
||||||
@ -39,6 +40,7 @@ interface IZeroEx is
|
|||||||
ISimpleFunctionRegistryFeature,
|
ISimpleFunctionRegistryFeature,
|
||||||
ITransformERC20Feature,
|
ITransformERC20Feature,
|
||||||
IMetaTransactionsFeature,
|
IMetaTransactionsFeature,
|
||||||
|
IMetaTransactionsFeatureV2,
|
||||||
IUniswapFeature,
|
IUniswapFeature,
|
||||||
IUniswapV3Feature,
|
IUniswapV3Feature,
|
||||||
IPancakeSwapFeature,
|
IPancakeSwapFeature,
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -70,6 +70,7 @@ contract UniswapV3Feature is IFeature, IUniswapV3Feature, FixinCommon, FixinToke
|
|||||||
_registerFeatureFunction(this.sellEthForTokenToUniswapV3.selector);
|
_registerFeatureFunction(this.sellEthForTokenToUniswapV3.selector);
|
||||||
_registerFeatureFunction(this.sellTokenForEthToUniswapV3.selector);
|
_registerFeatureFunction(this.sellTokenForEthToUniswapV3.selector);
|
||||||
_registerFeatureFunction(this.sellTokenForTokenToUniswapV3.selector);
|
_registerFeatureFunction(this.sellTokenForTokenToUniswapV3.selector);
|
||||||
|
_registerFeatureFunction(this._sellTokenForTokenToUniswapV3.selector);
|
||||||
_registerFeatureFunction(this._sellHeldTokenForTokenToUniswapV3.selector);
|
_registerFeatureFunction(this._sellHeldTokenForTokenToUniswapV3.selector);
|
||||||
_registerFeatureFunction(this.uniswapV3SwapCallback.selector);
|
_registerFeatureFunction(this.uniswapV3SwapCallback.selector);
|
||||||
return LibMigrate.MIGRATE_SUCCESS;
|
return LibMigrate.MIGRATE_SUCCESS;
|
||||||
@ -139,6 +140,23 @@ contract UniswapV3Feature is IFeature, IUniswapV3Feature, FixinCommon, FixinToke
|
|||||||
buyAmount = _swap(encodedPath, sellAmount, minBuyAmount, msg.sender, _normalizeRecipient(recipient));
|
buyAmount = _swap(encodedPath, sellAmount, minBuyAmount, msg.sender, _normalizeRecipient(recipient));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @dev Sell a token for another token directly against uniswap v3. Internal variant.
|
||||||
|
/// @param encodedPath Uniswap-encoded path.
|
||||||
|
/// @param sellAmount amount of the first token in the path to sell.
|
||||||
|
/// @param minBuyAmount Minimum amount of the last token in the path to buy.
|
||||||
|
/// @param recipient The recipient of the bought tokens. Can be zero for payer.
|
||||||
|
/// @param payer The address to pull the sold tokens from.
|
||||||
|
/// @return buyAmount Amount of the last token in the path bought.
|
||||||
|
function _sellTokenForTokenToUniswapV3(
|
||||||
|
bytes memory encodedPath,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 minBuyAmount,
|
||||||
|
address recipient,
|
||||||
|
address payer
|
||||||
|
) public override onlySelf returns (uint256 buyAmount) {
|
||||||
|
buyAmount = _swap(encodedPath, sellAmount, minBuyAmount, payer, _normalizeRecipient(recipient, payer));
|
||||||
|
}
|
||||||
|
|
||||||
/// @dev Sell a token for another token directly against uniswap v3.
|
/// @dev Sell a token for another token directly against uniswap v3.
|
||||||
/// Private variant, uses tokens held by `address(this)`.
|
/// Private variant, uses tokens held by `address(this)`.
|
||||||
/// @param encodedPath Uniswap-encoded path.
|
/// @param encodedPath Uniswap-encoded path.
|
||||||
@ -337,8 +355,16 @@ contract UniswapV3Feature is IFeature, IUniswapV3Feature, FixinCommon, FixinToke
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert null address values to alternative address.
|
||||||
|
function _normalizeRecipient(
|
||||||
|
address recipient,
|
||||||
|
address alternative
|
||||||
|
) private pure returns (address payable normalizedRecipient) {
|
||||||
|
return recipient == address(0) ? payable(alternative) : payable(recipient);
|
||||||
|
}
|
||||||
|
|
||||||
// Convert null address values to msg.sender.
|
// Convert null address values to msg.sender.
|
||||||
function _normalizeRecipient(address recipient) private view returns (address payable normalizedRecipient) {
|
function _normalizeRecipient(address recipient) private view returns (address payable normalizedRecipient) {
|
||||||
return recipient == address(0) ? msg.sender : payable(recipient);
|
return _normalizeRecipient(recipient, msg.sender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
@ -46,6 +46,8 @@ interface IMultiplexFeature {
|
|||||||
bool useSelfBalance;
|
bool useSelfBalance;
|
||||||
// The recipient of the bought output tokens.
|
// The recipient of the bought output tokens.
|
||||||
address recipient;
|
address recipient;
|
||||||
|
// The sender of the input tokens.
|
||||||
|
address payer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represents a constituent call of a batch sell.
|
// Represents a constituent call of a batch sell.
|
||||||
@ -75,6 +77,8 @@ interface IMultiplexFeature {
|
|||||||
bool useSelfBalance;
|
bool useSelfBalance;
|
||||||
// The recipient of the bought output tokens.
|
// The recipient of the bought output tokens.
|
||||||
address recipient;
|
address recipient;
|
||||||
|
// The sender of the input tokens.
|
||||||
|
address payer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represents a constituent call of a multi-hop sell.
|
// Represents a constituent call of a multi-hop sell.
|
||||||
@ -153,6 +157,17 @@ interface IMultiplexFeature {
|
|||||||
uint256 minBuyAmount
|
uint256 minBuyAmount
|
||||||
) external returns (uint256 boughtAmount);
|
) external returns (uint256 boughtAmount);
|
||||||
|
|
||||||
|
/// @dev Executes a multiplex BatchSell using the given
|
||||||
|
/// parameters. Internal only.
|
||||||
|
/// @param params The parameters for the BatchSell.
|
||||||
|
/// @param minBuyAmount The minimum amount of `params.outputToken`
|
||||||
|
/// that must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of `params.outputToken` bought.
|
||||||
|
function _multiplexBatchSell(
|
||||||
|
BatchSellParams memory params,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
) external returns (uint256 boughtAmount);
|
||||||
|
|
||||||
/// @dev Sells attached ETH via the given sequence of tokens
|
/// @dev Sells attached ETH via the given sequence of tokens
|
||||||
/// and calls. `tokens[0]` must be WETH.
|
/// and calls. `tokens[0]` must be WETH.
|
||||||
/// The last token in `tokens` is the output token that
|
/// The last token in `tokens` is the output token that
|
||||||
@ -204,4 +219,15 @@ interface IMultiplexFeature {
|
|||||||
uint256 sellAmount,
|
uint256 sellAmount,
|
||||||
uint256 minBuyAmount
|
uint256 minBuyAmount
|
||||||
) external returns (uint256 boughtAmount);
|
) external returns (uint256 boughtAmount);
|
||||||
|
|
||||||
|
/// @dev Executes a multiplex MultiHopSell using the given
|
||||||
|
/// parameters. Internal only.
|
||||||
|
/// @param params The parameters for the MultiHopSell.
|
||||||
|
/// @param minBuyAmount The minimum amount of the output token
|
||||||
|
/// that must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of the output token bought.
|
||||||
|
function _multiplexMultiHopSell(
|
||||||
|
MultiHopSellParams memory params,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
) external returns (uint256 boughtAmount);
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,21 @@ interface IUniswapV3Feature {
|
|||||||
address recipient
|
address recipient
|
||||||
) external returns (uint256 buyAmount);
|
) external returns (uint256 buyAmount);
|
||||||
|
|
||||||
|
/// @dev Sell a token for another token directly against uniswap v3. Internal variant.
|
||||||
|
/// @param encodedPath Uniswap-encoded path.
|
||||||
|
/// @param sellAmount amount of the first token in the path to sell.
|
||||||
|
/// @param minBuyAmount Minimum amount of the last token in the path to buy.
|
||||||
|
/// @param recipient The recipient of the bought tokens. Can be zero for payer.
|
||||||
|
/// @param payer The address to pull the sold tokens from.
|
||||||
|
/// @return buyAmount Amount of the last token in the path bought.
|
||||||
|
function _sellTokenForTokenToUniswapV3(
|
||||||
|
bytes memory encodedPath,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 minBuyAmount,
|
||||||
|
address recipient,
|
||||||
|
address payer
|
||||||
|
) external returns (uint256 buyAmount);
|
||||||
|
|
||||||
/// @dev Sell a token for another token directly against uniswap v3.
|
/// @dev Sell a token for another token directly against uniswap v3.
|
||||||
/// Private variant, uses tokens held by `address(this)`.
|
/// Private variant, uses tokens held by `address(this)`.
|
||||||
/// @param encodedPath Uniswap-encoded path.
|
/// @param encodedPath Uniswap-encoded path.
|
||||||
|
@ -80,9 +80,11 @@ contract MultiplexFeature is
|
|||||||
_registerFeatureFunction(this.multiplexBatchSellEthForToken.selector);
|
_registerFeatureFunction(this.multiplexBatchSellEthForToken.selector);
|
||||||
_registerFeatureFunction(this.multiplexBatchSellTokenForEth.selector);
|
_registerFeatureFunction(this.multiplexBatchSellTokenForEth.selector);
|
||||||
_registerFeatureFunction(this.multiplexBatchSellTokenForToken.selector);
|
_registerFeatureFunction(this.multiplexBatchSellTokenForToken.selector);
|
||||||
|
_registerFeatureFunction(this._multiplexBatchSell.selector);
|
||||||
_registerFeatureFunction(this.multiplexMultiHopSellEthForToken.selector);
|
_registerFeatureFunction(this.multiplexMultiHopSellEthForToken.selector);
|
||||||
_registerFeatureFunction(this.multiplexMultiHopSellTokenForEth.selector);
|
_registerFeatureFunction(this.multiplexMultiHopSellTokenForEth.selector);
|
||||||
_registerFeatureFunction(this.multiplexMultiHopSellTokenForToken.selector);
|
_registerFeatureFunction(this.multiplexMultiHopSellTokenForToken.selector);
|
||||||
|
_registerFeatureFunction(this._multiplexMultiHopSell.selector);
|
||||||
return LibMigrate.MIGRATE_SUCCESS;
|
return LibMigrate.MIGRATE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,14 +105,15 @@ contract MultiplexFeature is
|
|||||||
// WETH is now held by this contract,
|
// WETH is now held by this contract,
|
||||||
// so `useSelfBalance` is true.
|
// so `useSelfBalance` is true.
|
||||||
return
|
return
|
||||||
_multiplexBatchSell(
|
_multiplexBatchSellPrivate(
|
||||||
BatchSellParams({
|
BatchSellParams({
|
||||||
inputToken: WETH,
|
inputToken: WETH,
|
||||||
outputToken: outputToken,
|
outputToken: outputToken,
|
||||||
sellAmount: msg.value,
|
sellAmount: msg.value,
|
||||||
calls: calls,
|
calls: calls,
|
||||||
useSelfBalance: true,
|
useSelfBalance: true,
|
||||||
recipient: msg.sender
|
recipient: msg.sender,
|
||||||
|
payer: msg.sender
|
||||||
}),
|
}),
|
||||||
minBuyAmount
|
minBuyAmount
|
||||||
);
|
);
|
||||||
@ -133,14 +136,15 @@ contract MultiplexFeature is
|
|||||||
// The outputToken is implicitly WETH. The `recipient`
|
// The outputToken is implicitly WETH. The `recipient`
|
||||||
// of the WETH is set to this contract, since we
|
// of the WETH is set to this contract, since we
|
||||||
// must unwrap the WETH and transfer the resulting ETH.
|
// must unwrap the WETH and transfer the resulting ETH.
|
||||||
boughtAmount = _multiplexBatchSell(
|
boughtAmount = _multiplexBatchSellPrivate(
|
||||||
BatchSellParams({
|
BatchSellParams({
|
||||||
inputToken: inputToken,
|
inputToken: inputToken,
|
||||||
outputToken: WETH,
|
outputToken: WETH,
|
||||||
sellAmount: sellAmount,
|
sellAmount: sellAmount,
|
||||||
calls: calls,
|
calls: calls,
|
||||||
useSelfBalance: false,
|
useSelfBalance: false,
|
||||||
recipient: address(this)
|
recipient: address(this),
|
||||||
|
payer: msg.sender
|
||||||
}),
|
}),
|
||||||
minBuyAmount
|
minBuyAmount
|
||||||
);
|
);
|
||||||
@ -167,26 +171,40 @@ contract MultiplexFeature is
|
|||||||
uint256 minBuyAmount
|
uint256 minBuyAmount
|
||||||
) public override returns (uint256 boughtAmount) {
|
) public override returns (uint256 boughtAmount) {
|
||||||
return
|
return
|
||||||
_multiplexBatchSell(
|
_multiplexBatchSellPrivate(
|
||||||
BatchSellParams({
|
BatchSellParams({
|
||||||
inputToken: inputToken,
|
inputToken: inputToken,
|
||||||
outputToken: outputToken,
|
outputToken: outputToken,
|
||||||
sellAmount: sellAmount,
|
sellAmount: sellAmount,
|
||||||
calls: calls,
|
calls: calls,
|
||||||
useSelfBalance: false,
|
useSelfBalance: false,
|
||||||
recipient: msg.sender
|
recipient: msg.sender,
|
||||||
|
payer: msg.sender
|
||||||
}),
|
}),
|
||||||
minBuyAmount
|
minBuyAmount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @dev Executes a batch sell and checks that at least
|
||||||
|
/// `minBuyAmount` of `outputToken` was bought. Internal variant.
|
||||||
|
/// @param params Batch sell parameters.
|
||||||
|
/// @param minBuyAmount The minimum amount of `outputToken` that
|
||||||
|
/// must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of `outputToken` bought.
|
||||||
|
function _multiplexBatchSell(
|
||||||
|
BatchSellParams memory params,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
) public override onlySelf returns (uint256 boughtAmount) {
|
||||||
|
return _multiplexBatchSellPrivate(params, minBuyAmount);
|
||||||
|
}
|
||||||
|
|
||||||
/// @dev Executes a batch sell and checks that at least
|
/// @dev Executes a batch sell and checks that at least
|
||||||
/// `minBuyAmount` of `outputToken` was bought.
|
/// `minBuyAmount` of `outputToken` was bought.
|
||||||
/// @param params Batch sell parameters.
|
/// @param params Batch sell parameters.
|
||||||
/// @param minBuyAmount The minimum amount of `outputToken` that
|
/// @param minBuyAmount The minimum amount of `outputToken` that
|
||||||
/// must be bought for this function to not revert.
|
/// must be bought for this function to not revert.
|
||||||
/// @return boughtAmount The amount of `outputToken` bought.
|
/// @return boughtAmount The amount of `outputToken` bought.
|
||||||
function _multiplexBatchSell(
|
function _multiplexBatchSellPrivate(
|
||||||
BatchSellParams memory params,
|
BatchSellParams memory params,
|
||||||
uint256 minBuyAmount
|
uint256 minBuyAmount
|
||||||
) private returns (uint256 boughtAmount) {
|
) private returns (uint256 boughtAmount) {
|
||||||
@ -226,13 +244,14 @@ contract MultiplexFeature is
|
|||||||
// WETH is now held by this contract,
|
// WETH is now held by this contract,
|
||||||
// so `useSelfBalance` is true.
|
// so `useSelfBalance` is true.
|
||||||
return
|
return
|
||||||
_multiplexMultiHopSell(
|
_multiplexMultiHopSellPrivate(
|
||||||
MultiHopSellParams({
|
MultiHopSellParams({
|
||||||
tokens: tokens,
|
tokens: tokens,
|
||||||
sellAmount: msg.value,
|
sellAmount: msg.value,
|
||||||
calls: calls,
|
calls: calls,
|
||||||
useSelfBalance: true,
|
useSelfBalance: true,
|
||||||
recipient: msg.sender
|
recipient: msg.sender,
|
||||||
|
payer: msg.sender
|
||||||
}),
|
}),
|
||||||
minBuyAmount
|
minBuyAmount
|
||||||
);
|
);
|
||||||
@ -262,13 +281,14 @@ contract MultiplexFeature is
|
|||||||
);
|
);
|
||||||
// The `recipient of the WETH is set to this contract, since
|
// The `recipient of the WETH is set to this contract, since
|
||||||
// we must unwrap the WETH and transfer the resulting ETH.
|
// we must unwrap the WETH and transfer the resulting ETH.
|
||||||
boughtAmount = _multiplexMultiHopSell(
|
boughtAmount = _multiplexMultiHopSellPrivate(
|
||||||
MultiHopSellParams({
|
MultiHopSellParams({
|
||||||
tokens: tokens,
|
tokens: tokens,
|
||||||
sellAmount: sellAmount,
|
sellAmount: sellAmount,
|
||||||
calls: calls,
|
calls: calls,
|
||||||
useSelfBalance: false,
|
useSelfBalance: false,
|
||||||
recipient: address(this)
|
recipient: address(this),
|
||||||
|
payer: msg.sender
|
||||||
}),
|
}),
|
||||||
minBuyAmount
|
minBuyAmount
|
||||||
);
|
);
|
||||||
@ -297,25 +317,38 @@ contract MultiplexFeature is
|
|||||||
uint256 minBuyAmount
|
uint256 minBuyAmount
|
||||||
) public override returns (uint256 boughtAmount) {
|
) public override returns (uint256 boughtAmount) {
|
||||||
return
|
return
|
||||||
_multiplexMultiHopSell(
|
_multiplexMultiHopSellPrivate(
|
||||||
MultiHopSellParams({
|
MultiHopSellParams({
|
||||||
tokens: tokens,
|
tokens: tokens,
|
||||||
sellAmount: sellAmount,
|
sellAmount: sellAmount,
|
||||||
calls: calls,
|
calls: calls,
|
||||||
useSelfBalance: false,
|
useSelfBalance: false,
|
||||||
recipient: msg.sender
|
recipient: msg.sender,
|
||||||
|
payer: msg.sender
|
||||||
}),
|
}),
|
||||||
minBuyAmount
|
minBuyAmount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @dev Executes a multi-hop sell. Internal variant.
|
||||||
|
/// @param params Multi-hop sell parameters.
|
||||||
|
/// @param minBuyAmount The minimum amount of output tokens that
|
||||||
|
/// must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of output tokens bought.
|
||||||
|
function _multiplexMultiHopSell(
|
||||||
|
MultiHopSellParams memory params,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
) public override onlySelf returns (uint256 boughtAmount) {
|
||||||
|
return _multiplexMultiHopSellPrivate(params, minBuyAmount);
|
||||||
|
}
|
||||||
|
|
||||||
/// @dev Executes a multi-hop sell and checks that at least
|
/// @dev Executes a multi-hop sell and checks that at least
|
||||||
/// `minBuyAmount` of output tokens were bought.
|
/// `minBuyAmount` of output tokens were bought.
|
||||||
/// @param params Multi-hop sell parameters.
|
/// @param params Multi-hop sell parameters.
|
||||||
/// @param minBuyAmount The minimum amount of output tokens that
|
/// @param minBuyAmount The minimum amount of output tokens that
|
||||||
/// must be bought for this function to not revert.
|
/// must be bought for this function to not revert.
|
||||||
/// @return boughtAmount The amount of output tokens bought.
|
/// @return boughtAmount The amount of output tokens bought.
|
||||||
function _multiplexMultiHopSell(
|
function _multiplexMultiHopSellPrivate(
|
||||||
MultiHopSellParams memory params,
|
MultiHopSellParams memory params,
|
||||||
uint256 minBuyAmount
|
uint256 minBuyAmount
|
||||||
) private returns (uint256 boughtAmount) {
|
) private returns (uint256 boughtAmount) {
|
||||||
@ -387,14 +420,14 @@ contract MultiplexFeature is
|
|||||||
// amount of the multi-hop fill.
|
// amount of the multi-hop fill.
|
||||||
state.outputTokenAmount = params.sellAmount;
|
state.outputTokenAmount = params.sellAmount;
|
||||||
// The first call may expect the input tokens to be held by
|
// The first call may expect the input tokens to be held by
|
||||||
// `msg.sender`, `address(this)`, or some other address.
|
// `payer`, `address(this)`, or some other address.
|
||||||
// Compute the expected address and transfer the input tokens
|
// Compute the expected address and transfer the input tokens
|
||||||
// there if necessary.
|
// there if necessary.
|
||||||
state.from = _computeHopTarget(params, 0);
|
state.from = _computeHopTarget(params, 0);
|
||||||
// If the input tokens are currently held by `msg.sender` but
|
// If the input tokens are currently held by `payer` but
|
||||||
// the first hop expects them elsewhere, perform a `transferFrom`.
|
// the first hop expects them elsewhere, perform a `transferFrom`.
|
||||||
if (!params.useSelfBalance && state.from != msg.sender) {
|
if (!params.useSelfBalance && state.from != params.payer) {
|
||||||
_transferERC20TokensFrom(IERC20Token(params.tokens[0]), msg.sender, state.from, params.sellAmount);
|
_transferERC20TokensFrom(IERC20Token(params.tokens[0]), params.payer, state.from, params.sellAmount);
|
||||||
}
|
}
|
||||||
// If the input tokens are currently held by `address(this)` but
|
// If the input tokens are currently held by `address(this)` but
|
||||||
// the first hop expects them elsewhere, perform a `transfer`.
|
// the first hop expects them elsewhere, perform a `transfer`.
|
||||||
@ -411,11 +444,13 @@ contract MultiplexFeature is
|
|||||||
if (subcall.id == MultiplexSubcall.UniswapV2) {
|
if (subcall.id == MultiplexSubcall.UniswapV2) {
|
||||||
_multiHopSellUniswapV2(state, params, subcall.data);
|
_multiHopSellUniswapV2(state, params, subcall.data);
|
||||||
} else if (subcall.id == MultiplexSubcall.UniswapV3) {
|
} else if (subcall.id == MultiplexSubcall.UniswapV3) {
|
||||||
_multiHopSellUniswapV3(state, subcall.data);
|
_multiHopSellUniswapV3(state, params, subcall.data);
|
||||||
} else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
|
} else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
|
||||||
_multiHopSellLiquidityProvider(state, params, subcall.data);
|
_multiHopSellLiquidityProvider(state, params, subcall.data);
|
||||||
} else if (subcall.id == MultiplexSubcall.BatchSell) {
|
} else if (subcall.id == MultiplexSubcall.BatchSell) {
|
||||||
_nestedBatchSell(state, params, subcall.data);
|
_nestedBatchSell(state, params, subcall.data);
|
||||||
|
} else if (subcall.id == MultiplexSubcall.OTC) {
|
||||||
|
_multiHopSellOtcOrder(state, params, subcall.data);
|
||||||
} else {
|
} else {
|
||||||
revert("MultiplexFeature::_executeMultiHopSell/INVALID_SUBCALL");
|
revert("MultiplexFeature::_executeMultiHopSell/INVALID_SUBCALL");
|
||||||
}
|
}
|
||||||
@ -443,6 +478,8 @@ contract MultiplexFeature is
|
|||||||
// Likewise, the recipient of the multi-hop sell is
|
// Likewise, the recipient of the multi-hop sell is
|
||||||
// equal to the recipient of its containing batch sell.
|
// equal to the recipient of its containing batch sell.
|
||||||
multiHopParams.recipient = params.recipient;
|
multiHopParams.recipient = params.recipient;
|
||||||
|
// The payer is the same too.
|
||||||
|
multiHopParams.payer = params.payer;
|
||||||
// Execute the nested multi-hop sell.
|
// Execute the nested multi-hop sell.
|
||||||
uint256 outputTokenAmount = _executeMultiHopSell(multiHopParams).outputTokenAmount;
|
uint256 outputTokenAmount = _executeMultiHopSell(multiHopParams).outputTokenAmount;
|
||||||
// Increment the sold and bought amounts.
|
// Increment the sold and bought amounts.
|
||||||
@ -469,7 +506,7 @@ contract MultiplexFeature is
|
|||||||
// If the nested batch sell is the first hop
|
// If the nested batch sell is the first hop
|
||||||
// and `useSelfBalance` for the containing multi-
|
// and `useSelfBalance` for the containing multi-
|
||||||
// hop sell is false, the nested batch sell should
|
// hop sell is false, the nested batch sell should
|
||||||
// pull tokens from `msg.sender` (so `batchSellParams.useSelfBalance`
|
// pull tokens from `payer` (so `batchSellParams.useSelfBalance`
|
||||||
// should be false). Otherwise `batchSellParams.useSelfBalance`
|
// should be false). Otherwise `batchSellParams.useSelfBalance`
|
||||||
// should be true.
|
// should be true.
|
||||||
batchSellParams.useSelfBalance = state.hopIndex > 0 || params.useSelfBalance;
|
batchSellParams.useSelfBalance = state.hopIndex > 0 || params.useSelfBalance;
|
||||||
@ -477,6 +514,8 @@ contract MultiplexFeature is
|
|||||||
// that should receive the output tokens of the
|
// that should receive the output tokens of the
|
||||||
// batch sell.
|
// batch sell.
|
||||||
batchSellParams.recipient = state.to;
|
batchSellParams.recipient = state.to;
|
||||||
|
// payer shound be the same too.
|
||||||
|
batchSellParams.payer = params.payer;
|
||||||
// Execute the nested batch sell.
|
// Execute the nested batch sell.
|
||||||
state.outputTokenAmount = _executeBatchSell(batchSellParams).boughtAmount;
|
state.outputTokenAmount = _executeBatchSell(batchSellParams).boughtAmount;
|
||||||
}
|
}
|
||||||
@ -505,29 +544,33 @@ contract MultiplexFeature is
|
|||||||
// is executed, so we the target is the address encoded
|
// is executed, so we the target is the address encoded
|
||||||
// in the subcall data.
|
// in the subcall data.
|
||||||
(target, ) = abi.decode(subcall.data, (address, bytes));
|
(target, ) = abi.decode(subcall.data, (address, bytes));
|
||||||
} else if (subcall.id == MultiplexSubcall.UniswapV3 || subcall.id == MultiplexSubcall.BatchSell) {
|
} else if (
|
||||||
|
subcall.id == MultiplexSubcall.UniswapV3 ||
|
||||||
|
subcall.id == MultiplexSubcall.BatchSell ||
|
||||||
|
subcall.id == MultiplexSubcall.OTC
|
||||||
|
) {
|
||||||
// UniswapV3 uses a callback to pull in the tokens being
|
// UniswapV3 uses a callback to pull in the tokens being
|
||||||
// sold to it. The callback implemented in `UniswapV3Feature`
|
// sold to it. The callback implemented in `UniswapV3Feature`
|
||||||
// can either:
|
// can either:
|
||||||
// - call `transferFrom` to move tokens from `msg.sender` to the
|
// - call `transferFrom` to move tokens from `payer` to the
|
||||||
// UniswapV3 pool, or
|
// UniswapV3 pool, or
|
||||||
// - call `transfer` to move tokens from `address(this)` to the
|
// - call `transfer` to move tokens from `address(this)` to the
|
||||||
// UniswapV3 pool.
|
// UniswapV3 pool.
|
||||||
// A nested batch sell is similar, in that it can either:
|
// A nested batch sell is similar, in that it can either:
|
||||||
// - use tokens from `msg.sender`, or
|
// - use tokens from `payer`, or
|
||||||
// - use tokens held by `address(this)`.
|
// - use tokens held by `address(this)`.
|
||||||
|
|
||||||
// Suppose UniswapV3/BatchSell is the first call in the multi-hop
|
// Suppose UniswapV3/BatchSell is the first call in the multi-hop
|
||||||
// path. The input tokens are either held by `msg.sender`,
|
// path. The input tokens are either held by `payer`,
|
||||||
// or in the case of `multiplexMultiHopSellEthForToken` WETH is
|
// or in the case of `multiplexMultiHopSellEthForToken` WETH is
|
||||||
// held by `address(this)`. The target is set accordingly.
|
// held by `address(this)`. The target is set accordingly.
|
||||||
|
|
||||||
// If this is _not_ the first call in the multi-hop path, we
|
// If this is _not_ the first call in the multi-hop path, we
|
||||||
// are dealing with an "intermediate" token in the multi-hop path,
|
// are dealing with an "intermediate" token in the multi-hop path,
|
||||||
// which `msg.sender` may not have an allowance set for. Thus
|
// which `payer` may not have an allowance set for. Thus
|
||||||
// target must be set to `address(this)` for `i > 0`.
|
// target must be set to `address(this)` for `i > 0`.
|
||||||
if (i == 0 && !params.useSelfBalance) {
|
if (i == 0 && !params.useSelfBalance) {
|
||||||
target = msg.sender;
|
target = params.payer;
|
||||||
} else {
|
} else {
|
||||||
target = address(this);
|
target = address(this);
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ abstract contract MultiplexLiquidityProvider is FixinCommon, FixinTokenSpender {
|
|||||||
_transferERC20Tokens(params.inputToken, provider, sellAmount);
|
_transferERC20Tokens(params.inputToken, provider, sellAmount);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, transfer the input tokens from `msg.sender`.
|
// Otherwise, transfer the input tokens from `msg.sender`.
|
||||||
_transferERC20TokensFrom(params.inputToken, msg.sender, provider, sellAmount);
|
_transferERC20TokensFrom(params.inputToken, params.payer, provider, sellAmount);
|
||||||
}
|
}
|
||||||
// Cache the recipient's balance of the output token.
|
// Cache the recipient's balance of the output token.
|
||||||
uint256 balanceBefore = params.outputToken.balanceOf(params.recipient);
|
uint256 balanceBefore = params.outputToken.balanceOf(params.recipient);
|
||||||
|
@ -55,7 +55,7 @@ abstract contract MultiplexOtc is FixinEIP712 {
|
|||||||
order,
|
order,
|
||||||
signature,
|
signature,
|
||||||
sellAmount.safeDowncastToUint128(),
|
sellAmount.safeDowncastToUint128(),
|
||||||
msg.sender,
|
params.payer,
|
||||||
params.useSelfBalance,
|
params.useSelfBalance,
|
||||||
params.recipient
|
params.recipient
|
||||||
)
|
)
|
||||||
@ -65,4 +65,34 @@ abstract contract MultiplexOtc is FixinEIP712 {
|
|||||||
state.boughtAmount = state.boughtAmount.safeAdd(makerTokenFilledAmount);
|
state.boughtAmount = state.boughtAmount.safeAdd(makerTokenFilledAmount);
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _multiHopSellOtcOrder(
|
||||||
|
IMultiplexFeature.MultiHopSellState memory state,
|
||||||
|
IMultiplexFeature.MultiHopSellParams memory params,
|
||||||
|
bytes memory wrappedCallData
|
||||||
|
) internal {
|
||||||
|
// Decode the Otc order, and signature.
|
||||||
|
(LibNativeOrder.OtcOrder memory order, LibSignature.Signature memory signature) = abi.decode(
|
||||||
|
wrappedCallData,
|
||||||
|
(LibNativeOrder.OtcOrder, LibSignature.Signature)
|
||||||
|
);
|
||||||
|
//Make sure that the otc orders maker and taker tokens match the fill sequence in params.tokens[]
|
||||||
|
require(
|
||||||
|
address(order.takerToken) == params.tokens[state.hopIndex] &&
|
||||||
|
address(order.makerToken) == params.tokens[state.hopIndex + 1],
|
||||||
|
"MultiplexOtcOrder::_multiHopSellOtcOrder/INVALID_TOKENS"
|
||||||
|
);
|
||||||
|
// Try filling the Otc order. Bubble up reverts.
|
||||||
|
(uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount) = IOtcOrdersFeature(address(this))
|
||||||
|
._fillOtcOrder(
|
||||||
|
order,
|
||||||
|
signature,
|
||||||
|
state.outputTokenAmount.safeDowncastToUint128(),
|
||||||
|
state.from,
|
||||||
|
params.useSelfBalance,
|
||||||
|
state.to
|
||||||
|
);
|
||||||
|
//store the bought amount for the next hop
|
||||||
|
state.outputTokenAmount = makerTokenFilledAmount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ abstract contract MultiplexRfq is FixinEIP712 {
|
|||||||
order,
|
order,
|
||||||
signature,
|
signature,
|
||||||
sellAmount.safeDowncastToUint128(),
|
sellAmount.safeDowncastToUint128(),
|
||||||
msg.sender,
|
params.payer,
|
||||||
params.useSelfBalance,
|
params.useSelfBalance,
|
||||||
params.recipient
|
params.recipient
|
||||||
)
|
)
|
||||||
|
@ -30,8 +30,8 @@ abstract contract MultiplexTransformERC20 {
|
|||||||
) internal {
|
) internal {
|
||||||
ITransformERC20Feature.TransformERC20Args memory args;
|
ITransformERC20Feature.TransformERC20Args memory args;
|
||||||
// We want the TransformedERC20 event to have
|
// We want the TransformedERC20 event to have
|
||||||
// `msg.sender` as the taker.
|
// `payer` as the taker.
|
||||||
args.taker = msg.sender;
|
args.taker = payable(params.payer);
|
||||||
args.inputToken = params.inputToken;
|
args.inputToken = params.inputToken;
|
||||||
args.outputToken = params.outputToken;
|
args.outputToken = params.outputToken;
|
||||||
args.inputTokenAmount = sellAmount;
|
args.inputTokenAmount = sellAmount;
|
||||||
|
@ -77,7 +77,7 @@ abstract contract MultiplexUniswapV2 is FixinCommon, FixinTokenSpender {
|
|||||||
if (params.useSelfBalance) {
|
if (params.useSelfBalance) {
|
||||||
_transferERC20Tokens(IERC20Token(tokens[0]), firstPairAddress, sellAmount);
|
_transferERC20Tokens(IERC20Token(tokens[0]), firstPairAddress, sellAmount);
|
||||||
} else {
|
} else {
|
||||||
_transferERC20TokensFrom(IERC20Token(tokens[0]), msg.sender, firstPairAddress, sellAmount);
|
_transferERC20TokensFrom(IERC20Token(tokens[0]), params.payer, firstPairAddress, sellAmount);
|
||||||
}
|
}
|
||||||
// Execute the Uniswap/Sushiswap trade.
|
// Execute the Uniswap/Sushiswap trade.
|
||||||
return _sellToUniswapV2(tokens, sellAmount, isSushi, firstPairAddress, params.recipient);
|
return _sellToUniswapV2(tokens, sellAmount, isSushi, firstPairAddress, params.recipient);
|
||||||
|
@ -45,16 +45,16 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we self-delegatecall the normal variant
|
// Otherwise, we self-call `_sellTokenForTokenToUniswapV3`,
|
||||||
// `sellTokenForTokenToUniswapV3`, which pulls the input token
|
// which pulls the input token from a specified `payer`.
|
||||||
// from `msg.sender`.
|
(success, resultData) = address(this).call(
|
||||||
(success, resultData) = address(this).delegatecall(
|
|
||||||
abi.encodeWithSelector(
|
abi.encodeWithSelector(
|
||||||
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector,
|
IUniswapV3Feature._sellTokenForTokenToUniswapV3.selector,
|
||||||
wrappedCallData,
|
wrappedCallData,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
0,
|
0,
|
||||||
params.recipient
|
params.recipient,
|
||||||
|
params.payer
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -69,6 +69,7 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
|
|||||||
|
|
||||||
function _multiHopSellUniswapV3(
|
function _multiHopSellUniswapV3(
|
||||||
IMultiplexFeature.MultiHopSellState memory state,
|
IMultiplexFeature.MultiHopSellState memory state,
|
||||||
|
IMultiplexFeature.MultiHopSellParams memory params,
|
||||||
bytes memory wrappedCallData
|
bytes memory wrappedCallData
|
||||||
) internal {
|
) internal {
|
||||||
bool success;
|
bool success;
|
||||||
@ -87,16 +88,16 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we self-delegatecall the normal variant
|
// Otherwise, we self-call `_sellTokenForTokenToUniswapV3`,
|
||||||
// `sellTokenForTokenToUniswapV3`, which pulls the input token
|
// which pulls the input token from `payer`.
|
||||||
// from `msg.sender`.
|
(success, resultData) = address(this).call(
|
||||||
(success, resultData) = address(this).delegatecall(
|
|
||||||
abi.encodeWithSelector(
|
abi.encodeWithSelector(
|
||||||
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector,
|
IUniswapV3Feature._sellTokenForTokenToUniswapV3.selector,
|
||||||
wrappedCallData,
|
wrappedCallData,
|
||||||
state.outputTokenAmount,
|
state.outputTokenAmount,
|
||||||
0,
|
0,
|
||||||
state.to
|
state.to,
|
||||||
|
params.payer
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -34,7 +34,8 @@ library LibStorage {
|
|||||||
NativeOrders,
|
NativeOrders,
|
||||||
OtcOrders,
|
OtcOrders,
|
||||||
ERC721Orders,
|
ERC721Orders,
|
||||||
ERC1155Orders
|
ERC1155Orders,
|
||||||
|
MetaTransactionsV2
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Get the storage slot given a storage ID. We assign unique, well-spaced slots to storage bucket variables
|
/// @dev Get the storage slot given a storage ID. We assign unique, well-spaced slots to storage bucket variables
|
||||||
|
@ -36,9 +36,9 @@
|
|||||||
"typechain": "typechain --target=ethers-v5 --out-dir='typechain-wrappers' './foundry-artifacts/**/*.json'"
|
"typechain": "typechain --target=ethers-v5 --out-dir='typechain-wrappers' './foundry-artifacts/**/*.json'"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature,AvalancheBridgeAdapter,BSCBridgeAdapter,CeloBridgeAdapter,EthereumBridgeAdapter,FantomBridgeAdapter,OptimismBridgeAdapter,PolygonBridgeAdapter",
|
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature,AvalancheBridgeAdapter,BSCBridgeAdapter,CeloBridgeAdapter,EthereumBridgeAdapter,FantomBridgeAdapter,OptimismBridgeAdapter,PolygonBridgeAdapter,MetaTransactionsFeatureV2",
|
||||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||||
"abis": "./test/generated-artifacts/@(AbstractBridgeAdapter|AffiliateFeeTransformer|ArbitrumBridgeAdapter|AvalancheBridgeAdapter|BSCBridgeAdapter|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeProtocols|CeloBridgeAdapter|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|EthereumBridgeAdapter|FantomBridgeAdapter|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinERC1155Spender|FixinERC721Spender|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC1155OrdersFeature|IERC1155Token|IERC165Feature|IERC20Bridge|IERC20Transformer|IERC721OrdersFeature|IERC721Token|IFeature|IFeeRecipient|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|IPropertyValidator|ISimpleFunctionRegistryFeature|IStaking|ITakerCallback|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC1155OrdersStorage|LibERC20Transformer|LibERC721OrdersStorage|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNFTOrder|LibNFTOrdersRichErrors|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2Batch|MixinBancor|MixinBancorV3|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinGMX|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinPlatypus|MixinShell|MixinSolidly|MixinSynthetix|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OptimismBridgeAdapter|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PolygonBridgeAdapter|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFeeRecipient|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC1155Token|TestMintableERC20Token|TestMintableERC721Token|TestMooniswap|TestNFTOrderPresigner|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestPropertyValidator|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
"abis": "./test/generated-artifacts/@(AbstractBridgeAdapter|AffiliateFeeTransformer|ArbitrumBridgeAdapter|AvalancheBridgeAdapter|BSCBridgeAdapter|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeProtocols|CeloBridgeAdapter|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|EthereumBridgeAdapter|FantomBridgeAdapter|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinERC1155Spender|FixinERC721Spender|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC1155OrdersFeature|IERC1155Token|IERC165Feature|IERC20Bridge|IERC20Transformer|IERC721OrdersFeature|IERC721Token|IFeature|IFeeRecipient|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMetaTransactionsFeatureV2|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|IPropertyValidator|ISimpleFunctionRegistryFeature|IStaking|ITakerCallback|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC1155OrdersStorage|LibERC20Transformer|LibERC721OrdersStorage|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNFTOrder|LibNFTOrdersRichErrors|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MetaTransactionsFeatureV2|MixinAaveV2|MixinBalancer|MixinBalancerV2Batch|MixinBancor|MixinBancorV3|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinGMX|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinPlatypus|MixinShell|MixinSolidly|MixinSynthetix|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OptimismBridgeAdapter|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PolygonBridgeAdapter|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFeeRecipient|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC1155Token|TestMintableERC20Token|TestMintableERC721Token|TestMooniswap|TestNFTOrderPresigner|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestPropertyValidator|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -32,6 +32,7 @@ import * as IZeroEx from '../generated-artifacts/IZeroEx.json';
|
|||||||
import * as LiquidityProviderFeature from '../generated-artifacts/LiquidityProviderFeature.json';
|
import * as LiquidityProviderFeature from '../generated-artifacts/LiquidityProviderFeature.json';
|
||||||
import * as LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json';
|
import * as LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json';
|
||||||
import * as MetaTransactionsFeature from '../generated-artifacts/MetaTransactionsFeature.json';
|
import * as MetaTransactionsFeature from '../generated-artifacts/MetaTransactionsFeature.json';
|
||||||
|
import * as MetaTransactionsFeatureV2 from '../generated-artifacts/MetaTransactionsFeatureV2.json';
|
||||||
import * as MultiplexFeature from '../generated-artifacts/MultiplexFeature.json';
|
import * as MultiplexFeature from '../generated-artifacts/MultiplexFeature.json';
|
||||||
import * as NativeOrdersFeature from '../generated-artifacts/NativeOrdersFeature.json';
|
import * as NativeOrdersFeature from '../generated-artifacts/NativeOrdersFeature.json';
|
||||||
import * as OptimismBridgeAdapter from '../generated-artifacts/OptimismBridgeAdapter.json';
|
import * as OptimismBridgeAdapter from '../generated-artifacts/OptimismBridgeAdapter.json';
|
||||||
@ -84,4 +85,5 @@ export const artifacts = {
|
|||||||
FantomBridgeAdapter: FantomBridgeAdapter as ContractArtifact,
|
FantomBridgeAdapter: FantomBridgeAdapter as ContractArtifact,
|
||||||
OptimismBridgeAdapter: OptimismBridgeAdapter as ContractArtifact,
|
OptimismBridgeAdapter: OptimismBridgeAdapter as ContractArtifact,
|
||||||
PolygonBridgeAdapter: PolygonBridgeAdapter as ContractArtifact,
|
PolygonBridgeAdapter: PolygonBridgeAdapter as ContractArtifact,
|
||||||
|
MetaTransactionsFeatureV2: MetaTransactionsFeatureV2 as ContractArtifact,
|
||||||
};
|
};
|
||||||
|
@ -30,6 +30,7 @@ export * from '../generated-wrappers/initial_migration';
|
|||||||
export * from '../generated-wrappers/liquidity_provider_feature';
|
export * from '../generated-wrappers/liquidity_provider_feature';
|
||||||
export * from '../generated-wrappers/log_metadata_transformer';
|
export * from '../generated-wrappers/log_metadata_transformer';
|
||||||
export * from '../generated-wrappers/meta_transactions_feature';
|
export * from '../generated-wrappers/meta_transactions_feature';
|
||||||
|
export * from '../generated-wrappers/meta_transactions_feature_v2';
|
||||||
export * from '../generated-wrappers/multiplex_feature';
|
export * from '../generated-wrappers/multiplex_feature';
|
||||||
export * from '../generated-wrappers/native_orders_feature';
|
export * from '../generated-wrappers/native_orders_feature';
|
||||||
export * from '../generated-wrappers/optimism_bridge_adapter';
|
export * from '../generated-wrappers/optimism_bridge_adapter';
|
||||||
|
@ -51,6 +51,7 @@ import * as ILiquidityProvider from '../test/generated-artifacts/ILiquidityProvi
|
|||||||
import * as ILiquidityProviderFeature from '../test/generated-artifacts/ILiquidityProviderFeature.json';
|
import * as ILiquidityProviderFeature from '../test/generated-artifacts/ILiquidityProviderFeature.json';
|
||||||
import * as ILiquidityProviderSandbox from '../test/generated-artifacts/ILiquidityProviderSandbox.json';
|
import * as ILiquidityProviderSandbox from '../test/generated-artifacts/ILiquidityProviderSandbox.json';
|
||||||
import * as IMetaTransactionsFeature from '../test/generated-artifacts/IMetaTransactionsFeature.json';
|
import * as IMetaTransactionsFeature from '../test/generated-artifacts/IMetaTransactionsFeature.json';
|
||||||
|
import * as IMetaTransactionsFeatureV2 from '../test/generated-artifacts/IMetaTransactionsFeatureV2.json';
|
||||||
import * as IMooniswapPool from '../test/generated-artifacts/IMooniswapPool.json';
|
import * as IMooniswapPool from '../test/generated-artifacts/IMooniswapPool.json';
|
||||||
import * as IMultiplexFeature from '../test/generated-artifacts/IMultiplexFeature.json';
|
import * as IMultiplexFeature from '../test/generated-artifacts/IMultiplexFeature.json';
|
||||||
import * as INativeOrdersEvents from '../test/generated-artifacts/INativeOrdersEvents.json';
|
import * as INativeOrdersEvents from '../test/generated-artifacts/INativeOrdersEvents.json';
|
||||||
@ -104,6 +105,7 @@ import * as LiquidityProviderFeature from '../test/generated-artifacts/Liquidity
|
|||||||
import * as LiquidityProviderSandbox from '../test/generated-artifacts/LiquidityProviderSandbox.json';
|
import * as LiquidityProviderSandbox from '../test/generated-artifacts/LiquidityProviderSandbox.json';
|
||||||
import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json';
|
import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json';
|
||||||
import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.json';
|
import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.json';
|
||||||
|
import * as MetaTransactionsFeatureV2 from '../test/generated-artifacts/MetaTransactionsFeatureV2.json';
|
||||||
import * as MixinAaveV2 from '../test/generated-artifacts/MixinAaveV2.json';
|
import * as MixinAaveV2 from '../test/generated-artifacts/MixinAaveV2.json';
|
||||||
import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json';
|
import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json';
|
||||||
import * as MixinBalancerV2Batch from '../test/generated-artifacts/MixinBalancerV2Batch.json';
|
import * as MixinBalancerV2Batch from '../test/generated-artifacts/MixinBalancerV2Batch.json';
|
||||||
@ -236,6 +238,7 @@ export const artifacts = {
|
|||||||
FundRecoveryFeature: FundRecoveryFeature as ContractArtifact,
|
FundRecoveryFeature: FundRecoveryFeature as ContractArtifact,
|
||||||
LiquidityProviderFeature: LiquidityProviderFeature as ContractArtifact,
|
LiquidityProviderFeature: LiquidityProviderFeature as ContractArtifact,
|
||||||
MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
|
MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
|
||||||
|
MetaTransactionsFeatureV2: MetaTransactionsFeatureV2 as ContractArtifact,
|
||||||
NativeOrdersFeature: NativeOrdersFeature as ContractArtifact,
|
NativeOrdersFeature: NativeOrdersFeature as ContractArtifact,
|
||||||
OtcOrdersFeature: OtcOrdersFeature as ContractArtifact,
|
OtcOrdersFeature: OtcOrdersFeature as ContractArtifact,
|
||||||
OwnableFeature: OwnableFeature as ContractArtifact,
|
OwnableFeature: OwnableFeature as ContractArtifact,
|
||||||
@ -253,6 +256,7 @@ export const artifacts = {
|
|||||||
IFundRecoveryFeature: IFundRecoveryFeature as ContractArtifact,
|
IFundRecoveryFeature: IFundRecoveryFeature as ContractArtifact,
|
||||||
ILiquidityProviderFeature: ILiquidityProviderFeature as ContractArtifact,
|
ILiquidityProviderFeature: ILiquidityProviderFeature as ContractArtifact,
|
||||||
IMetaTransactionsFeature: IMetaTransactionsFeature as ContractArtifact,
|
IMetaTransactionsFeature: IMetaTransactionsFeature as ContractArtifact,
|
||||||
|
IMetaTransactionsFeatureV2: IMetaTransactionsFeatureV2 as ContractArtifact,
|
||||||
IMultiplexFeature: IMultiplexFeature as ContractArtifact,
|
IMultiplexFeature: IMultiplexFeature as ContractArtifact,
|
||||||
INativeOrdersEvents: INativeOrdersEvents as ContractArtifact,
|
INativeOrdersEvents: INativeOrdersEvents as ContractArtifact,
|
||||||
INativeOrdersFeature: INativeOrdersFeature as ContractArtifact,
|
INativeOrdersFeature: INativeOrdersFeature as ContractArtifact,
|
||||||
|
@ -49,6 +49,7 @@ export * from '../test/generated-wrappers/i_liquidity_provider';
|
|||||||
export * from '../test/generated-wrappers/i_liquidity_provider_feature';
|
export * from '../test/generated-wrappers/i_liquidity_provider_feature';
|
||||||
export * from '../test/generated-wrappers/i_liquidity_provider_sandbox';
|
export * from '../test/generated-wrappers/i_liquidity_provider_sandbox';
|
||||||
export * from '../test/generated-wrappers/i_meta_transactions_feature';
|
export * from '../test/generated-wrappers/i_meta_transactions_feature';
|
||||||
|
export * from '../test/generated-wrappers/i_meta_transactions_feature_v2';
|
||||||
export * from '../test/generated-wrappers/i_mooniswap_pool';
|
export * from '../test/generated-wrappers/i_mooniswap_pool';
|
||||||
export * from '../test/generated-wrappers/i_multiplex_feature';
|
export * from '../test/generated-wrappers/i_multiplex_feature';
|
||||||
export * from '../test/generated-wrappers/i_native_orders_events';
|
export * from '../test/generated-wrappers/i_native_orders_events';
|
||||||
@ -102,6 +103,7 @@ export * from '../test/generated-wrappers/liquidity_provider_feature';
|
|||||||
export * from '../test/generated-wrappers/liquidity_provider_sandbox';
|
export * from '../test/generated-wrappers/liquidity_provider_sandbox';
|
||||||
export * from '../test/generated-wrappers/log_metadata_transformer';
|
export * from '../test/generated-wrappers/log_metadata_transformer';
|
||||||
export * from '../test/generated-wrappers/meta_transactions_feature';
|
export * from '../test/generated-wrappers/meta_transactions_feature';
|
||||||
|
export * from '../test/generated-wrappers/meta_transactions_feature_v2';
|
||||||
export * from '../test/generated-wrappers/mixin_aave_v2';
|
export * from '../test/generated-wrappers/mixin_aave_v2';
|
||||||
export * from '../test/generated-wrappers/mixin_balancer';
|
export * from '../test/generated-wrappers/mixin_balancer';
|
||||||
export * from '../test/generated-wrappers/mixin_balancer_v2_batch';
|
export * from '../test/generated-wrappers/mixin_balancer_v2_batch';
|
||||||
|
501
contracts/zero-ex/tests/MetaTransactionV2Test.t.sol
Normal file
501
contracts/zero-ex/tests/MetaTransactionV2Test.t.sol
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
407
contracts/zero-ex/tests/MultiplexMetaTransactionsV2.t.sol
Normal file
407
contracts/zero-ex/tests/MultiplexMetaTransactionsV2.t.sol
Normal 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
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
219
contracts/zero-ex/tests/forked/MultiplexRfqTest.t.sol
Normal file
219
contracts/zero-ex/tests/forked/MultiplexRfqTest.t.sol
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,7 @@ import "src/features/FundRecoveryFeature.sol";
|
|||||||
import "src/features/TransformERC20Feature.sol";
|
import "src/features/TransformERC20Feature.sol";
|
||||||
import "src/features/OtcOrdersFeature.sol";
|
import "src/features/OtcOrdersFeature.sol";
|
||||||
import "src/features/MetaTransactionsFeature.sol";
|
import "src/features/MetaTransactionsFeature.sol";
|
||||||
|
import "src/features/MetaTransactionsFeatureV2.sol";
|
||||||
import "src/features/nft_orders/ERC1155OrdersFeature.sol";
|
import "src/features/nft_orders/ERC1155OrdersFeature.sol";
|
||||||
import "src/features/nft_orders/ERC721OrdersFeature.sol";
|
import "src/features/nft_orders/ERC721OrdersFeature.sol";
|
||||||
import "src/features/UniswapFeature.sol";
|
import "src/features/UniswapFeature.sol";
|
||||||
@ -68,6 +69,7 @@ contract DeployZeroEx is Test {
|
|||||||
FundRecoveryFeature fundRecoveryFeature;
|
FundRecoveryFeature fundRecoveryFeature;
|
||||||
TransformERC20Feature transformERC20Feature;
|
TransformERC20Feature transformERC20Feature;
|
||||||
MetaTransactionsFeature metaTransactionsFeature;
|
MetaTransactionsFeature metaTransactionsFeature;
|
||||||
|
MetaTransactionsFeatureV2 metaTransactionsFeatureV2;
|
||||||
ERC1155OrdersFeature erc1155OrdersFeature;
|
ERC1155OrdersFeature erc1155OrdersFeature;
|
||||||
ERC721OrdersFeature erc721OrdersFeature;
|
ERC721OrdersFeature erc721OrdersFeature;
|
||||||
MultiplexFeature multiplexFeature;
|
MultiplexFeature multiplexFeature;
|
||||||
@ -133,6 +135,10 @@ contract DeployZeroEx is Test {
|
|||||||
emit log_named_address("UniswapV3Feature", address(ZERO_EX_DEPLOYED.features.uniswapV3Feature));
|
emit log_named_address("UniswapV3Feature", address(ZERO_EX_DEPLOYED.features.uniswapV3Feature));
|
||||||
emit log_named_address("FundRecoveryFeature", address(ZERO_EX_DEPLOYED.features.fundRecoveryFeature));
|
emit log_named_address("FundRecoveryFeature", address(ZERO_EX_DEPLOYED.features.fundRecoveryFeature));
|
||||||
emit log_named_address("MetaTransactionsFeature", address(ZERO_EX_DEPLOYED.features.metaTransactionsFeature));
|
emit log_named_address("MetaTransactionsFeature", address(ZERO_EX_DEPLOYED.features.metaTransactionsFeature));
|
||||||
|
emit log_named_address(
|
||||||
|
"MetaTransactionsFeatureV2",
|
||||||
|
address(ZERO_EX_DEPLOYED.features.metaTransactionsFeatureV2)
|
||||||
|
);
|
||||||
emit log_named_address("ERC1155OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature));
|
emit log_named_address("ERC1155OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature));
|
||||||
emit log_named_address("ERC721OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc721OrdersFeature));
|
emit log_named_address("ERC721OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc721OrdersFeature));
|
||||||
emit log_named_address("TransformERC20Feature", address(ZERO_EX_DEPLOYED.features.transformERC20Feature));
|
emit log_named_address("TransformERC20Feature", address(ZERO_EX_DEPLOYED.features.transformERC20Feature));
|
||||||
@ -201,6 +207,10 @@ contract DeployZeroEx is Test {
|
|||||||
);
|
);
|
||||||
ZERO_EX_DEPLOYED.features.fundRecoveryFeature = new FundRecoveryFeature();
|
ZERO_EX_DEPLOYED.features.fundRecoveryFeature = new FundRecoveryFeature();
|
||||||
ZERO_EX_DEPLOYED.features.metaTransactionsFeature = new MetaTransactionsFeature(address(ZERO_EX));
|
ZERO_EX_DEPLOYED.features.metaTransactionsFeature = new MetaTransactionsFeature(address(ZERO_EX));
|
||||||
|
ZERO_EX_DEPLOYED.features.metaTransactionsFeatureV2 = new MetaTransactionsFeatureV2(
|
||||||
|
address(ZERO_EX),
|
||||||
|
ZERO_EX_DEPLOYED.weth
|
||||||
|
);
|
||||||
ZERO_EX_DEPLOYED.features.erc1155OrdersFeature = new ERC1155OrdersFeature(
|
ZERO_EX_DEPLOYED.features.erc1155OrdersFeature = new ERC1155OrdersFeature(
|
||||||
address(ZERO_EX),
|
address(ZERO_EX),
|
||||||
ZERO_EX_DEPLOYED.weth
|
ZERO_EX_DEPLOYED.weth
|
||||||
@ -270,6 +280,11 @@ contract DeployZeroEx is Test {
|
|||||||
abi.encodeWithSelector(MetaTransactionsFeature.migrate.selector),
|
abi.encodeWithSelector(MetaTransactionsFeature.migrate.selector),
|
||||||
address(this)
|
address(this)
|
||||||
);
|
);
|
||||||
|
IZERO_EX.migrate(
|
||||||
|
address(ZERO_EX_DEPLOYED.features.metaTransactionsFeatureV2),
|
||||||
|
abi.encodeWithSelector(MetaTransactionsFeatureV2.migrate.selector),
|
||||||
|
address(this)
|
||||||
|
);
|
||||||
IZERO_EX.migrate(
|
IZERO_EX.migrate(
|
||||||
address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature),
|
address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature),
|
||||||
abi.encodeWithSelector(ERC1155OrdersFeature.migrate.selector),
|
abi.encodeWithSelector(ERC1155OrdersFeature.migrate.selector),
|
||||||
|
@ -54,6 +54,9 @@ contract LocalTest is Test, TestUtils {
|
|||||||
address internal signerAddress;
|
address internal signerAddress;
|
||||||
uint256 internal signerKey;
|
uint256 internal signerKey;
|
||||||
|
|
||||||
|
address internal otherSignerAddress;
|
||||||
|
uint256 internal otherSignerKey;
|
||||||
|
|
||||||
function _infiniteApprovals() private {
|
function _infiniteApprovals() private {
|
||||||
shib.approve(address(zeroExDeployed.zeroEx), type(uint256).max);
|
shib.approve(address(zeroExDeployed.zeroEx), type(uint256).max);
|
||||||
dai.approve(address(zeroExDeployed.zeroEx), type(uint256).max);
|
dai.approve(address(zeroExDeployed.zeroEx), type(uint256).max);
|
||||||
@ -92,12 +95,23 @@ contract LocalTest is Test, TestUtils {
|
|||||||
zrx = IERC20Token(address(new TestMintableERC20Token()));
|
zrx = IERC20Token(address(new TestMintableERC20Token()));
|
||||||
weth = zeroExDeployed.weth;
|
weth = zeroExDeployed.weth;
|
||||||
|
|
||||||
|
// TODO this should be somewhere else
|
||||||
|
string memory mnemonic = "conduct into noodle wreck before satisfy alarm vendor dose lunch vapor party";
|
||||||
|
otherSignerKey = vm.deriveKey(mnemonic, 0);
|
||||||
|
otherSignerAddress = vm.addr(otherSignerKey);
|
||||||
|
vm.label(otherSignerAddress, "zeroEx/OtherGuy");
|
||||||
|
|
||||||
_infiniteApprovals();
|
_infiniteApprovals();
|
||||||
|
|
||||||
vm.startPrank(signerAddress);
|
vm.startPrank(signerAddress);
|
||||||
_infiniteApprovals();
|
_infiniteApprovals();
|
||||||
vm.stopPrank();
|
vm.stopPrank();
|
||||||
|
|
||||||
vm.deal(address(this), 10e18);
|
vm.startPrank(otherSignerAddress);
|
||||||
|
_infiniteApprovals();
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
vm.deal(address(this), 20e18);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _mintTo(address token, address recipient, uint256 amount) internal {
|
function _mintTo(address token, address recipient, uint256 amount) internal {
|
||||||
|
@ -20,6 +20,8 @@ import "forge-std/Test.sol";
|
|||||||
import "src/transformers/LibERC20Transformer.sol";
|
import "src/transformers/LibERC20Transformer.sol";
|
||||||
|
|
||||||
contract TestUtils is Test {
|
contract TestUtils is Test {
|
||||||
|
address private constant ZERO_ADDRESS = 0x0000000000000000000000000000000000000000;
|
||||||
|
|
||||||
function _findTransformerNonce(address transformer, address deployer) internal pure returns (uint32) {
|
function _findTransformerNonce(address transformer, address deployer) internal pure returns (uint32) {
|
||||||
address current;
|
address current;
|
||||||
for (uint32 i = 0; i < 1024; i++) {
|
for (uint32 i = 0; i < 1024; i++) {
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
"generated-artifacts/LiquidityProviderFeature.json",
|
"generated-artifacts/LiquidityProviderFeature.json",
|
||||||
"generated-artifacts/LogMetadataTransformer.json",
|
"generated-artifacts/LogMetadataTransformer.json",
|
||||||
"generated-artifacts/MetaTransactionsFeature.json",
|
"generated-artifacts/MetaTransactionsFeature.json",
|
||||||
|
"generated-artifacts/MetaTransactionsFeatureV2.json",
|
||||||
"generated-artifacts/MultiplexFeature.json",
|
"generated-artifacts/MultiplexFeature.json",
|
||||||
"generated-artifacts/NativeOrdersFeature.json",
|
"generated-artifacts/NativeOrdersFeature.json",
|
||||||
"generated-artifacts/OptimismBridgeAdapter.json",
|
"generated-artifacts/OptimismBridgeAdapter.json",
|
||||||
@ -88,6 +89,7 @@
|
|||||||
"test/generated-artifacts/ILiquidityProviderFeature.json",
|
"test/generated-artifacts/ILiquidityProviderFeature.json",
|
||||||
"test/generated-artifacts/ILiquidityProviderSandbox.json",
|
"test/generated-artifacts/ILiquidityProviderSandbox.json",
|
||||||
"test/generated-artifacts/IMetaTransactionsFeature.json",
|
"test/generated-artifacts/IMetaTransactionsFeature.json",
|
||||||
|
"test/generated-artifacts/IMetaTransactionsFeatureV2.json",
|
||||||
"test/generated-artifacts/IMooniswapPool.json",
|
"test/generated-artifacts/IMooniswapPool.json",
|
||||||
"test/generated-artifacts/IMultiplexFeature.json",
|
"test/generated-artifacts/IMultiplexFeature.json",
|
||||||
"test/generated-artifacts/INativeOrdersEvents.json",
|
"test/generated-artifacts/INativeOrdersEvents.json",
|
||||||
@ -141,6 +143,7 @@
|
|||||||
"test/generated-artifacts/LiquidityProviderSandbox.json",
|
"test/generated-artifacts/LiquidityProviderSandbox.json",
|
||||||
"test/generated-artifacts/LogMetadataTransformer.json",
|
"test/generated-artifacts/LogMetadataTransformer.json",
|
||||||
"test/generated-artifacts/MetaTransactionsFeature.json",
|
"test/generated-artifacts/MetaTransactionsFeature.json",
|
||||||
|
"test/generated-artifacts/MetaTransactionsFeatureV2.json",
|
||||||
"test/generated-artifacts/MixinAaveV2.json",
|
"test/generated-artifacts/MixinAaveV2.json",
|
||||||
"test/generated-artifacts/MixinBalancer.json",
|
"test/generated-artifacts/MixinBalancer.json",
|
||||||
"test/generated-artifacts/MixinBalancerV2Batch.json",
|
"test/generated-artifacts/MixinBalancerV2Batch.json",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user