Compare commits
22 Commits
developmen
...
pdowell/de
Author | SHA1 | Date | |
---|---|---|---|
|
ac563f5164 | ||
|
ca7393641f | ||
|
6e926af41c | ||
|
172822044b | ||
|
a0485ca3e0 | ||
|
f32e834e11 | ||
|
b5c18c2a9e | ||
|
217348f31b | ||
|
65a2024285 | ||
|
b942551e33 | ||
|
4c837110f2 | ||
|
88c96659a1 | ||
|
152accf9c1 | ||
|
0f5d832daf | ||
|
13dd95688d | ||
|
15f79feb81 | ||
|
3b55a88fc5 | ||
|
cdb94d1780 | ||
|
865c1f05db | ||
|
2bbf3956f3 | ||
|
5cf4cbe4b5 | ||
|
f402c96053 |
@ -1 +1 @@
|
||||
Subproject commit a2edd39db95df7e9dd3f9ef9edc8c55fefddb6df
|
||||
Subproject commit 058d2004ac10cc8f194625fb107fb7a87c4e702d
|
@ -20,6 +20,7 @@ import "./features/interfaces/ISimpleFunctionRegistryFeature.sol";
|
||||
import "./features/interfaces/ITokenSpenderFeature.sol";
|
||||
import "./features/interfaces/ITransformERC20Feature.sol";
|
||||
import "./features/interfaces/IMetaTransactionsFeature.sol";
|
||||
import "./features/interfaces/IMetaTransactionsFeatureV2.sol";
|
||||
import "./features/interfaces/IUniswapFeature.sol";
|
||||
import "./features/interfaces/IUniswapV3Feature.sol";
|
||||
import "./features/interfaces/IPancakeSwapFeature.sol";
|
||||
@ -39,6 +40,7 @@ interface IZeroEx is
|
||||
ISimpleFunctionRegistryFeature,
|
||||
ITransformERC20Feature,
|
||||
IMetaTransactionsFeature,
|
||||
IMetaTransactionsFeatureV2,
|
||||
IUniswapFeature,
|
||||
IUniswapV3Feature,
|
||||
IPancakeSwapFeature,
|
||||
|
@ -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.sellTokenForEthToUniswapV3.selector);
|
||||
_registerFeatureFunction(this.sellTokenForTokenToUniswapV3.selector);
|
||||
_registerFeatureFunction(this._sellTokenForTokenToUniswapV3.selector);
|
||||
_registerFeatureFunction(this._sellHeldTokenForTokenToUniswapV3.selector);
|
||||
_registerFeatureFunction(this.uniswapV3SwapCallback.selector);
|
||||
return LibMigrate.MIGRATE_SUCCESS;
|
||||
@ -139,6 +140,23 @@ contract UniswapV3Feature is IFeature, IUniswapV3Feature, FixinCommon, FixinToke
|
||||
buyAmount = _swap(encodedPath, sellAmount, minBuyAmount, msg.sender, _normalizeRecipient(recipient));
|
||||
}
|
||||
|
||||
/// @dev Sell a token for another token directly against uniswap v3. Internal variant.
|
||||
/// @param encodedPath Uniswap-encoded path.
|
||||
/// @param sellAmount amount of the first token in the path to sell.
|
||||
/// @param minBuyAmount Minimum amount of the last token in the path to buy.
|
||||
/// @param recipient The recipient of the bought tokens. Can be zero for payer.
|
||||
/// @param payer The address to pull the sold tokens from.
|
||||
/// @return buyAmount Amount of the last token in the path bought.
|
||||
function _sellTokenForTokenToUniswapV3(
|
||||
bytes memory encodedPath,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount,
|
||||
address recipient,
|
||||
address payer
|
||||
) public override onlySelf returns (uint256 buyAmount) {
|
||||
buyAmount = _swap(encodedPath, sellAmount, minBuyAmount, payer, _normalizeRecipient(recipient, payer));
|
||||
}
|
||||
|
||||
/// @dev Sell a token for another token directly against uniswap v3.
|
||||
/// Private variant, uses tokens held by `address(this)`.
|
||||
/// @param encodedPath Uniswap-encoded path.
|
||||
@ -337,8 +355,16 @@ contract UniswapV3Feature is IFeature, IUniswapV3Feature, FixinCommon, FixinToke
|
||||
}
|
||||
}
|
||||
|
||||
// Convert null address values to alternative address.
|
||||
function _normalizeRecipient(
|
||||
address recipient,
|
||||
address alternative
|
||||
) private pure returns (address payable normalizedRecipient) {
|
||||
return recipient == address(0) ? payable(alternative) : payable(recipient);
|
||||
}
|
||||
|
||||
// Convert null address values to msg.sender.
|
||||
function _normalizeRecipient(address recipient) private view returns (address payable normalizedRecipient) {
|
||||
return recipient == address(0) ? msg.sender : payable(recipient);
|
||||
return _normalizeRecipient(recipient, msg.sender);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
// The recipient of the bought output tokens.
|
||||
address recipient;
|
||||
// The sender of the input tokens.
|
||||
address payer;
|
||||
}
|
||||
|
||||
// Represents a constituent call of a batch sell.
|
||||
@ -75,6 +77,8 @@ interface IMultiplexFeature {
|
||||
bool useSelfBalance;
|
||||
// The recipient of the bought output tokens.
|
||||
address recipient;
|
||||
// The sender of the input tokens.
|
||||
address payer;
|
||||
}
|
||||
|
||||
// Represents a constituent call of a multi-hop sell.
|
||||
@ -153,6 +157,17 @@ interface IMultiplexFeature {
|
||||
uint256 minBuyAmount
|
||||
) external returns (uint256 boughtAmount);
|
||||
|
||||
/// @dev Executes a multiplex BatchSell using the given
|
||||
/// parameters. Internal only.
|
||||
/// @param params The parameters for the BatchSell.
|
||||
/// @param minBuyAmount The minimum amount of `params.outputToken`
|
||||
/// that must be bought for this function to not revert.
|
||||
/// @return boughtAmount The amount of `params.outputToken` bought.
|
||||
function _multiplexBatchSell(
|
||||
BatchSellParams memory params,
|
||||
uint256 minBuyAmount
|
||||
) external returns (uint256 boughtAmount);
|
||||
|
||||
/// @dev Sells attached ETH via the given sequence of tokens
|
||||
/// and calls. `tokens[0]` must be WETH.
|
||||
/// The last token in `tokens` is the output token that
|
||||
@ -204,4 +219,15 @@ interface IMultiplexFeature {
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount
|
||||
) external returns (uint256 boughtAmount);
|
||||
|
||||
/// @dev Executes a multiplex MultiHopSell using the given
|
||||
/// parameters. Internal only.
|
||||
/// @param params The parameters for the MultiHopSell.
|
||||
/// @param minBuyAmount The minimum amount of the output token
|
||||
/// that must be bought for this function to not revert.
|
||||
/// @return boughtAmount The amount of the output token bought.
|
||||
function _multiplexMultiHopSell(
|
||||
MultiHopSellParams memory params,
|
||||
uint256 minBuyAmount
|
||||
) external returns (uint256 boughtAmount);
|
||||
}
|
||||
|
@ -54,6 +54,21 @@ interface IUniswapV3Feature {
|
||||
address recipient
|
||||
) external returns (uint256 buyAmount);
|
||||
|
||||
/// @dev Sell a token for another token directly against uniswap v3. Internal variant.
|
||||
/// @param encodedPath Uniswap-encoded path.
|
||||
/// @param sellAmount amount of the first token in the path to sell.
|
||||
/// @param minBuyAmount Minimum amount of the last token in the path to buy.
|
||||
/// @param recipient The recipient of the bought tokens. Can be zero for payer.
|
||||
/// @param payer The address to pull the sold tokens from.
|
||||
/// @return buyAmount Amount of the last token in the path bought.
|
||||
function _sellTokenForTokenToUniswapV3(
|
||||
bytes memory encodedPath,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount,
|
||||
address recipient,
|
||||
address payer
|
||||
) external returns (uint256 buyAmount);
|
||||
|
||||
/// @dev Sell a token for another token directly against uniswap v3.
|
||||
/// Private variant, uses tokens held by `address(this)`.
|
||||
/// @param encodedPath Uniswap-encoded path.
|
||||
|
@ -80,9 +80,11 @@ contract MultiplexFeature is
|
||||
_registerFeatureFunction(this.multiplexBatchSellEthForToken.selector);
|
||||
_registerFeatureFunction(this.multiplexBatchSellTokenForEth.selector);
|
||||
_registerFeatureFunction(this.multiplexBatchSellTokenForToken.selector);
|
||||
_registerFeatureFunction(this._multiplexBatchSell.selector);
|
||||
_registerFeatureFunction(this.multiplexMultiHopSellEthForToken.selector);
|
||||
_registerFeatureFunction(this.multiplexMultiHopSellTokenForEth.selector);
|
||||
_registerFeatureFunction(this.multiplexMultiHopSellTokenForToken.selector);
|
||||
_registerFeatureFunction(this._multiplexMultiHopSell.selector);
|
||||
return LibMigrate.MIGRATE_SUCCESS;
|
||||
}
|
||||
|
||||
@ -103,14 +105,15 @@ contract MultiplexFeature is
|
||||
// WETH is now held by this contract,
|
||||
// so `useSelfBalance` is true.
|
||||
return
|
||||
_multiplexBatchSell(
|
||||
_multiplexBatchSellPrivate(
|
||||
BatchSellParams({
|
||||
inputToken: WETH,
|
||||
outputToken: outputToken,
|
||||
sellAmount: msg.value,
|
||||
calls: calls,
|
||||
useSelfBalance: true,
|
||||
recipient: msg.sender
|
||||
recipient: msg.sender,
|
||||
payer: msg.sender
|
||||
}),
|
||||
minBuyAmount
|
||||
);
|
||||
@ -133,14 +136,15 @@ contract MultiplexFeature is
|
||||
// The outputToken is implicitly WETH. The `recipient`
|
||||
// of the WETH is set to this contract, since we
|
||||
// must unwrap the WETH and transfer the resulting ETH.
|
||||
boughtAmount = _multiplexBatchSell(
|
||||
boughtAmount = _multiplexBatchSellPrivate(
|
||||
BatchSellParams({
|
||||
inputToken: inputToken,
|
||||
outputToken: WETH,
|
||||
sellAmount: sellAmount,
|
||||
calls: calls,
|
||||
useSelfBalance: false,
|
||||
recipient: address(this)
|
||||
recipient: address(this),
|
||||
payer: msg.sender
|
||||
}),
|
||||
minBuyAmount
|
||||
);
|
||||
@ -167,26 +171,40 @@ contract MultiplexFeature is
|
||||
uint256 minBuyAmount
|
||||
) public override returns (uint256 boughtAmount) {
|
||||
return
|
||||
_multiplexBatchSell(
|
||||
_multiplexBatchSellPrivate(
|
||||
BatchSellParams({
|
||||
inputToken: inputToken,
|
||||
outputToken: outputToken,
|
||||
sellAmount: sellAmount,
|
||||
calls: calls,
|
||||
useSelfBalance: false,
|
||||
recipient: msg.sender
|
||||
recipient: msg.sender,
|
||||
payer: msg.sender
|
||||
}),
|
||||
minBuyAmount
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Executes a batch sell and checks that at least
|
||||
/// `minBuyAmount` of `outputToken` was bought. Internal variant.
|
||||
/// @param params Batch sell parameters.
|
||||
/// @param minBuyAmount The minimum amount of `outputToken` that
|
||||
/// must be bought for this function to not revert.
|
||||
/// @return boughtAmount The amount of `outputToken` bought.
|
||||
function _multiplexBatchSell(
|
||||
BatchSellParams memory params,
|
||||
uint256 minBuyAmount
|
||||
) public override onlySelf returns (uint256 boughtAmount) {
|
||||
return _multiplexBatchSellPrivate(params, minBuyAmount);
|
||||
}
|
||||
|
||||
/// @dev Executes a batch sell and checks that at least
|
||||
/// `minBuyAmount` of `outputToken` was bought.
|
||||
/// @param params Batch sell parameters.
|
||||
/// @param minBuyAmount The minimum amount of `outputToken` that
|
||||
/// must be bought for this function to not revert.
|
||||
/// @return boughtAmount The amount of `outputToken` bought.
|
||||
function _multiplexBatchSell(
|
||||
function _multiplexBatchSellPrivate(
|
||||
BatchSellParams memory params,
|
||||
uint256 minBuyAmount
|
||||
) private returns (uint256 boughtAmount) {
|
||||
@ -226,13 +244,14 @@ contract MultiplexFeature is
|
||||
// WETH is now held by this contract,
|
||||
// so `useSelfBalance` is true.
|
||||
return
|
||||
_multiplexMultiHopSell(
|
||||
_multiplexMultiHopSellPrivate(
|
||||
MultiHopSellParams({
|
||||
tokens: tokens,
|
||||
sellAmount: msg.value,
|
||||
calls: calls,
|
||||
useSelfBalance: true,
|
||||
recipient: msg.sender
|
||||
recipient: msg.sender,
|
||||
payer: msg.sender
|
||||
}),
|
||||
minBuyAmount
|
||||
);
|
||||
@ -262,13 +281,14 @@ contract MultiplexFeature is
|
||||
);
|
||||
// The `recipient of the WETH is set to this contract, since
|
||||
// we must unwrap the WETH and transfer the resulting ETH.
|
||||
boughtAmount = _multiplexMultiHopSell(
|
||||
boughtAmount = _multiplexMultiHopSellPrivate(
|
||||
MultiHopSellParams({
|
||||
tokens: tokens,
|
||||
sellAmount: sellAmount,
|
||||
calls: calls,
|
||||
useSelfBalance: false,
|
||||
recipient: address(this)
|
||||
recipient: address(this),
|
||||
payer: msg.sender
|
||||
}),
|
||||
minBuyAmount
|
||||
);
|
||||
@ -297,25 +317,38 @@ contract MultiplexFeature is
|
||||
uint256 minBuyAmount
|
||||
) public override returns (uint256 boughtAmount) {
|
||||
return
|
||||
_multiplexMultiHopSell(
|
||||
_multiplexMultiHopSellPrivate(
|
||||
MultiHopSellParams({
|
||||
tokens: tokens,
|
||||
sellAmount: sellAmount,
|
||||
calls: calls,
|
||||
useSelfBalance: false,
|
||||
recipient: msg.sender
|
||||
recipient: msg.sender,
|
||||
payer: msg.sender
|
||||
}),
|
||||
minBuyAmount
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Executes a multi-hop sell. Internal variant.
|
||||
/// @param params Multi-hop sell parameters.
|
||||
/// @param minBuyAmount The minimum amount of output tokens that
|
||||
/// must be bought for this function to not revert.
|
||||
/// @return boughtAmount The amount of output tokens bought.
|
||||
function _multiplexMultiHopSell(
|
||||
MultiHopSellParams memory params,
|
||||
uint256 minBuyAmount
|
||||
) public override onlySelf returns (uint256 boughtAmount) {
|
||||
return _multiplexMultiHopSellPrivate(params, minBuyAmount);
|
||||
}
|
||||
|
||||
/// @dev Executes a multi-hop sell and checks that at least
|
||||
/// `minBuyAmount` of output tokens were bought.
|
||||
/// @param params Multi-hop sell parameters.
|
||||
/// @param minBuyAmount The minimum amount of output tokens that
|
||||
/// must be bought for this function to not revert.
|
||||
/// @return boughtAmount The amount of output tokens bought.
|
||||
function _multiplexMultiHopSell(
|
||||
function _multiplexMultiHopSellPrivate(
|
||||
MultiHopSellParams memory params,
|
||||
uint256 minBuyAmount
|
||||
) private returns (uint256 boughtAmount) {
|
||||
@ -387,14 +420,14 @@ contract MultiplexFeature is
|
||||
// amount of the multi-hop fill.
|
||||
state.outputTokenAmount = params.sellAmount;
|
||||
// The first call may expect the input tokens to be held by
|
||||
// `msg.sender`, `address(this)`, or some other address.
|
||||
// `payer`, `address(this)`, or some other address.
|
||||
// Compute the expected address and transfer the input tokens
|
||||
// there if necessary.
|
||||
state.from = _computeHopTarget(params, 0);
|
||||
// If the input tokens are currently held by `msg.sender` but
|
||||
// If the input tokens are currently held by `payer` but
|
||||
// the first hop expects them elsewhere, perform a `transferFrom`.
|
||||
if (!params.useSelfBalance && state.from != msg.sender) {
|
||||
_transferERC20TokensFrom(IERC20Token(params.tokens[0]), msg.sender, state.from, params.sellAmount);
|
||||
if (!params.useSelfBalance && state.from != params.payer) {
|
||||
_transferERC20TokensFrom(IERC20Token(params.tokens[0]), params.payer, state.from, params.sellAmount);
|
||||
}
|
||||
// If the input tokens are currently held by `address(this)` but
|
||||
// the first hop expects them elsewhere, perform a `transfer`.
|
||||
@ -411,11 +444,13 @@ contract MultiplexFeature is
|
||||
if (subcall.id == MultiplexSubcall.UniswapV2) {
|
||||
_multiHopSellUniswapV2(state, params, subcall.data);
|
||||
} else if (subcall.id == MultiplexSubcall.UniswapV3) {
|
||||
_multiHopSellUniswapV3(state, subcall.data);
|
||||
_multiHopSellUniswapV3(state, params, subcall.data);
|
||||
} else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
|
||||
_multiHopSellLiquidityProvider(state, params, subcall.data);
|
||||
} else if (subcall.id == MultiplexSubcall.BatchSell) {
|
||||
_nestedBatchSell(state, params, subcall.data);
|
||||
} else if (subcall.id == MultiplexSubcall.OTC) {
|
||||
_multiHopSellOtcOrder(state, params, subcall.data);
|
||||
} else {
|
||||
revert("MultiplexFeature::_executeMultiHopSell/INVALID_SUBCALL");
|
||||
}
|
||||
@ -443,6 +478,8 @@ contract MultiplexFeature is
|
||||
// Likewise, the recipient of the multi-hop sell is
|
||||
// equal to the recipient of its containing batch sell.
|
||||
multiHopParams.recipient = params.recipient;
|
||||
// The payer is the same too.
|
||||
multiHopParams.payer = params.payer;
|
||||
// Execute the nested multi-hop sell.
|
||||
uint256 outputTokenAmount = _executeMultiHopSell(multiHopParams).outputTokenAmount;
|
||||
// Increment the sold and bought amounts.
|
||||
@ -469,7 +506,7 @@ contract MultiplexFeature is
|
||||
// If the nested batch sell is the first hop
|
||||
// and `useSelfBalance` for the containing multi-
|
||||
// hop sell is false, the nested batch sell should
|
||||
// pull tokens from `msg.sender` (so `batchSellParams.useSelfBalance`
|
||||
// pull tokens from `payer` (so `batchSellParams.useSelfBalance`
|
||||
// should be false). Otherwise `batchSellParams.useSelfBalance`
|
||||
// should be true.
|
||||
batchSellParams.useSelfBalance = state.hopIndex > 0 || params.useSelfBalance;
|
||||
@ -477,6 +514,8 @@ contract MultiplexFeature is
|
||||
// that should receive the output tokens of the
|
||||
// batch sell.
|
||||
batchSellParams.recipient = state.to;
|
||||
// payer shound be the same too.
|
||||
batchSellParams.payer = params.payer;
|
||||
// Execute the nested batch sell.
|
||||
state.outputTokenAmount = _executeBatchSell(batchSellParams).boughtAmount;
|
||||
}
|
||||
@ -505,29 +544,33 @@ contract MultiplexFeature is
|
||||
// is executed, so we the target is the address encoded
|
||||
// in the subcall data.
|
||||
(target, ) = abi.decode(subcall.data, (address, bytes));
|
||||
} else if (subcall.id == MultiplexSubcall.UniswapV3 || subcall.id == MultiplexSubcall.BatchSell) {
|
||||
} else if (
|
||||
subcall.id == MultiplexSubcall.UniswapV3 ||
|
||||
subcall.id == MultiplexSubcall.BatchSell ||
|
||||
subcall.id == MultiplexSubcall.OTC
|
||||
) {
|
||||
// UniswapV3 uses a callback to pull in the tokens being
|
||||
// sold to it. The callback implemented in `UniswapV3Feature`
|
||||
// can either:
|
||||
// - call `transferFrom` to move tokens from `msg.sender` to the
|
||||
// - call `transferFrom` to move tokens from `payer` to the
|
||||
// UniswapV3 pool, or
|
||||
// - call `transfer` to move tokens from `address(this)` to the
|
||||
// UniswapV3 pool.
|
||||
// A nested batch sell is similar, in that it can either:
|
||||
// - use tokens from `msg.sender`, or
|
||||
// - use tokens from `payer`, or
|
||||
// - use tokens held by `address(this)`.
|
||||
|
||||
// Suppose UniswapV3/BatchSell is the first call in the multi-hop
|
||||
// path. The input tokens are either held by `msg.sender`,
|
||||
// path. The input tokens are either held by `payer`,
|
||||
// or in the case of `multiplexMultiHopSellEthForToken` WETH is
|
||||
// held by `address(this)`. The target is set accordingly.
|
||||
|
||||
// If this is _not_ the first call in the multi-hop path, we
|
||||
// are dealing with an "intermediate" token in the multi-hop path,
|
||||
// which `msg.sender` may not have an allowance set for. Thus
|
||||
// which `payer` may not have an allowance set for. Thus
|
||||
// target must be set to `address(this)` for `i > 0`.
|
||||
if (i == 0 && !params.useSelfBalance) {
|
||||
target = msg.sender;
|
||||
target = params.payer;
|
||||
} else {
|
||||
target = address(this);
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ abstract contract MultiplexLiquidityProvider is FixinCommon, FixinTokenSpender {
|
||||
_transferERC20Tokens(params.inputToken, provider, sellAmount);
|
||||
} else {
|
||||
// Otherwise, transfer the input tokens from `msg.sender`.
|
||||
_transferERC20TokensFrom(params.inputToken, msg.sender, provider, sellAmount);
|
||||
_transferERC20TokensFrom(params.inputToken, params.payer, provider, sellAmount);
|
||||
}
|
||||
// Cache the recipient's balance of the output token.
|
||||
uint256 balanceBefore = params.outputToken.balanceOf(params.recipient);
|
||||
|
@ -55,7 +55,7 @@ abstract contract MultiplexOtc is FixinEIP712 {
|
||||
order,
|
||||
signature,
|
||||
sellAmount.safeDowncastToUint128(),
|
||||
msg.sender,
|
||||
params.payer,
|
||||
params.useSelfBalance,
|
||||
params.recipient
|
||||
)
|
||||
@ -65,4 +65,34 @@ abstract contract MultiplexOtc is FixinEIP712 {
|
||||
state.boughtAmount = state.boughtAmount.safeAdd(makerTokenFilledAmount);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function _multiHopSellOtcOrder(
|
||||
IMultiplexFeature.MultiHopSellState memory state,
|
||||
IMultiplexFeature.MultiHopSellParams memory params,
|
||||
bytes memory wrappedCallData
|
||||
) internal {
|
||||
// Decode the Otc order, and signature.
|
||||
(LibNativeOrder.OtcOrder memory order, LibSignature.Signature memory signature) = abi.decode(
|
||||
wrappedCallData,
|
||||
(LibNativeOrder.OtcOrder, LibSignature.Signature)
|
||||
);
|
||||
//Make sure that the otc orders maker and taker tokens match the fill sequence in params.tokens[]
|
||||
require(
|
||||
address(order.takerToken) == params.tokens[state.hopIndex] &&
|
||||
address(order.makerToken) == params.tokens[state.hopIndex + 1],
|
||||
"MultiplexOtcOrder::_multiHopSellOtcOrder/INVALID_TOKENS"
|
||||
);
|
||||
// Try filling the Otc order. Bubble up reverts.
|
||||
(uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount) = IOtcOrdersFeature(address(this))
|
||||
._fillOtcOrder(
|
||||
order,
|
||||
signature,
|
||||
state.outputTokenAmount.safeDowncastToUint128(),
|
||||
state.from,
|
||||
params.useSelfBalance,
|
||||
state.to
|
||||
);
|
||||
//store the bought amount for the next hop
|
||||
state.outputTokenAmount = makerTokenFilledAmount;
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ abstract contract MultiplexRfq is FixinEIP712 {
|
||||
order,
|
||||
signature,
|
||||
sellAmount.safeDowncastToUint128(),
|
||||
msg.sender,
|
||||
params.payer,
|
||||
params.useSelfBalance,
|
||||
params.recipient
|
||||
)
|
||||
|
@ -30,8 +30,8 @@ abstract contract MultiplexTransformERC20 {
|
||||
) internal {
|
||||
ITransformERC20Feature.TransformERC20Args memory args;
|
||||
// We want the TransformedERC20 event to have
|
||||
// `msg.sender` as the taker.
|
||||
args.taker = msg.sender;
|
||||
// `payer` as the taker.
|
||||
args.taker = payable(params.payer);
|
||||
args.inputToken = params.inputToken;
|
||||
args.outputToken = params.outputToken;
|
||||
args.inputTokenAmount = sellAmount;
|
||||
|
@ -77,7 +77,7 @@ abstract contract MultiplexUniswapV2 is FixinCommon, FixinTokenSpender {
|
||||
if (params.useSelfBalance) {
|
||||
_transferERC20Tokens(IERC20Token(tokens[0]), firstPairAddress, sellAmount);
|
||||
} else {
|
||||
_transferERC20TokensFrom(IERC20Token(tokens[0]), msg.sender, firstPairAddress, sellAmount);
|
||||
_transferERC20TokensFrom(IERC20Token(tokens[0]), params.payer, firstPairAddress, sellAmount);
|
||||
}
|
||||
// Execute the Uniswap/Sushiswap trade.
|
||||
return _sellToUniswapV2(tokens, sellAmount, isSushi, firstPairAddress, params.recipient);
|
||||
|
@ -45,16 +45,16 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Otherwise, we self-delegatecall the normal variant
|
||||
// `sellTokenForTokenToUniswapV3`, which pulls the input token
|
||||
// from `msg.sender`.
|
||||
(success, resultData) = address(this).delegatecall(
|
||||
// Otherwise, we self-call `_sellTokenForTokenToUniswapV3`,
|
||||
// which pulls the input token from a specified `payer`.
|
||||
(success, resultData) = address(this).call(
|
||||
abi.encodeWithSelector(
|
||||
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector,
|
||||
IUniswapV3Feature._sellTokenForTokenToUniswapV3.selector,
|
||||
wrappedCallData,
|
||||
sellAmount,
|
||||
0,
|
||||
params.recipient
|
||||
params.recipient,
|
||||
params.payer
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -69,6 +69,7 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
|
||||
|
||||
function _multiHopSellUniswapV3(
|
||||
IMultiplexFeature.MultiHopSellState memory state,
|
||||
IMultiplexFeature.MultiHopSellParams memory params,
|
||||
bytes memory wrappedCallData
|
||||
) internal {
|
||||
bool success;
|
||||
@ -87,16 +88,16 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Otherwise, we self-delegatecall the normal variant
|
||||
// `sellTokenForTokenToUniswapV3`, which pulls the input token
|
||||
// from `msg.sender`.
|
||||
(success, resultData) = address(this).delegatecall(
|
||||
// Otherwise, we self-call `_sellTokenForTokenToUniswapV3`,
|
||||
// which pulls the input token from `payer`.
|
||||
(success, resultData) = address(this).call(
|
||||
abi.encodeWithSelector(
|
||||
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector,
|
||||
IUniswapV3Feature._sellTokenForTokenToUniswapV3.selector,
|
||||
wrappedCallData,
|
||||
state.outputTokenAmount,
|
||||
0,
|
||||
state.to
|
||||
state.to,
|
||||
params.payer
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
OtcOrders,
|
||||
ERC721Orders,
|
||||
ERC1155Orders
|
||||
ERC1155Orders,
|
||||
MetaTransactionsV2
|
||||
}
|
||||
|
||||
/// @dev Get the storage slot given a storage ID. We assign unique, well-spaced slots to storage bucket variables
|
||||
|
96
contracts/zero-ex/script/mtxv2_deploy.s.sol
Normal file
96
contracts/zero-ex/script/mtxv2_deploy.s.sol
Normal file
@ -0,0 +1,96 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.6.5;
|
||||
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "forge-std/Script.sol";
|
||||
import "forge-std/console.sol";
|
||||
|
||||
import "@0x/contracts-erc20/src/IEtherToken.sol";
|
||||
import "../contracts/src/external/ILiquidityProviderSandbox.sol";
|
||||
import '../contracts/src/features/interfaces/IMetaTransactionsFeatureV2.sol';
|
||||
import '../contracts/src/features/interfaces/IMultiplexFeature.sol';
|
||||
import '../contracts/src/features/interfaces/IUniswapV3Feature.sol';
|
||||
import '../contracts/src/features/MetaTransactionsFeatureV2.sol';
|
||||
import '../contracts/src/features/UniswapV3Feature.sol';
|
||||
import '../contracts/src/features/multiplex/MultiplexFeature.sol';
|
||||
|
||||
contract ContractScript is Script {
|
||||
struct ChainConfig {
|
||||
address zeroExAddress;
|
||||
address wethAddress;
|
||||
address liquidityProviderSandbox;
|
||||
address uniswapV3Factory;
|
||||
address uniswapV2Factory;
|
||||
address sushiswapFactory;
|
||||
bytes32 uniswapV3PoolInitCodeHash;
|
||||
bytes32 uniswapV2PairInitCodeHash;
|
||||
bytes32 sushiswapPairInitCodeHash;
|
||||
}
|
||||
|
||||
function _getChainConfig() internal pure returns (ChainConfig memory) {
|
||||
uint256 chainId;
|
||||
assembly {
|
||||
chainId := chainid()
|
||||
}
|
||||
if (chainId == 1) { // Ethereum
|
||||
ChainConfig memory chainConfig = ChainConfig(
|
||||
0xDef1C0ded9bec7F1a1670819833240f027b25EfF, // 0x EP
|
||||
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, // weth address
|
||||
0x407B4128E9eCaD8769B2332312a9F655cB9F5F3A, // LP sandbox
|
||||
0x1F98431c8aD98523631AE4a59f267346ea31F984, // uniswapv3
|
||||
0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f, // uniswapv2
|
||||
0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac, // sushiswap
|
||||
bytes32(0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54),
|
||||
bytes32(0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f),
|
||||
bytes32(0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303)
|
||||
);
|
||||
return chainConfig;
|
||||
}
|
||||
if (chainId == 137) { // polygon
|
||||
ChainConfig memory chainConfig = ChainConfig(
|
||||
0xDef1C0ded9bec7F1a1670819833240f027b25EfF, // 0x EP
|
||||
0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270, // wmatic address
|
||||
0x4Dd97080aDf36103bD3db822f9d3c0e44890fd69, // LP sandbox
|
||||
0x1F98431c8aD98523631AE4a59f267346ea31F984, // uniswapv3
|
||||
address(0), // there is no uniswapv2 on polygon
|
||||
0xc35DADB65012eC5796536bD9864eD8773aBc74C4, // sushiswap
|
||||
bytes32(0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54),
|
||||
bytes32(0), // there is no uniswapv2 on polygon
|
||||
bytes32(0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303)
|
||||
);
|
||||
return chainConfig;
|
||||
}
|
||||
|
||||
revert("unsupported chain");
|
||||
}
|
||||
|
||||
function run() public {
|
||||
ChainConfig memory chainConfig = _getChainConfig();
|
||||
|
||||
vm.startBroadcast();
|
||||
|
||||
MetaTransactionsFeatureV2 mtx = new MetaTransactionsFeatureV2(chainConfig.zeroExAddress, IEtherToken(chainConfig.wethAddress));
|
||||
console.log("MetaTransactionsFeatureV2 address:", address(mtx));
|
||||
|
||||
UniswapV3Feature uni = new UniswapV3Feature(
|
||||
IEtherToken(chainConfig.wethAddress),
|
||||
chainConfig.uniswapV3Factory,
|
||||
chainConfig.uniswapV3PoolInitCodeHash
|
||||
);
|
||||
console.log("UniswapV3Feature address:", address(uni));
|
||||
|
||||
MultiplexFeature multi = new MultiplexFeature(
|
||||
chainConfig.zeroExAddress,
|
||||
IEtherToken(chainConfig.wethAddress),
|
||||
ILiquidityProviderSandbox(chainConfig.liquidityProviderSandbox),
|
||||
chainConfig.uniswapV2Factory,
|
||||
chainConfig.sushiswapFactory,
|
||||
chainConfig.uniswapV2PairInitCodeHash,
|
||||
chainConfig.sushiswapPairInitCodeHash
|
||||
);
|
||||
console.log("MultiplexFeature address:", address(multi));
|
||||
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
}
|
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/OtcOrdersFeature.sol";
|
||||
import "src/features/MetaTransactionsFeature.sol";
|
||||
import "src/features/MetaTransactionsFeatureV2.sol";
|
||||
import "src/features/nft_orders/ERC1155OrdersFeature.sol";
|
||||
import "src/features/nft_orders/ERC721OrdersFeature.sol";
|
||||
import "src/features/UniswapFeature.sol";
|
||||
@ -68,6 +69,7 @@ contract DeployZeroEx is Test {
|
||||
FundRecoveryFeature fundRecoveryFeature;
|
||||
TransformERC20Feature transformERC20Feature;
|
||||
MetaTransactionsFeature metaTransactionsFeature;
|
||||
MetaTransactionsFeatureV2 metaTransactionsFeatureV2;
|
||||
ERC1155OrdersFeature erc1155OrdersFeature;
|
||||
ERC721OrdersFeature erc721OrdersFeature;
|
||||
MultiplexFeature multiplexFeature;
|
||||
@ -133,6 +135,10 @@ contract DeployZeroEx is Test {
|
||||
emit log_named_address("UniswapV3Feature", address(ZERO_EX_DEPLOYED.features.uniswapV3Feature));
|
||||
emit log_named_address("FundRecoveryFeature", address(ZERO_EX_DEPLOYED.features.fundRecoveryFeature));
|
||||
emit log_named_address("MetaTransactionsFeature", address(ZERO_EX_DEPLOYED.features.metaTransactionsFeature));
|
||||
emit log_named_address(
|
||||
"MetaTransactionsFeatureV2",
|
||||
address(ZERO_EX_DEPLOYED.features.metaTransactionsFeatureV2)
|
||||
);
|
||||
emit log_named_address("ERC1155OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature));
|
||||
emit log_named_address("ERC721OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc721OrdersFeature));
|
||||
emit log_named_address("TransformERC20Feature", address(ZERO_EX_DEPLOYED.features.transformERC20Feature));
|
||||
@ -201,6 +207,10 @@ contract DeployZeroEx is Test {
|
||||
);
|
||||
ZERO_EX_DEPLOYED.features.fundRecoveryFeature = new FundRecoveryFeature();
|
||||
ZERO_EX_DEPLOYED.features.metaTransactionsFeature = new MetaTransactionsFeature(address(ZERO_EX));
|
||||
ZERO_EX_DEPLOYED.features.metaTransactionsFeatureV2 = new MetaTransactionsFeatureV2(
|
||||
address(ZERO_EX),
|
||||
ZERO_EX_DEPLOYED.weth
|
||||
);
|
||||
ZERO_EX_DEPLOYED.features.erc1155OrdersFeature = new ERC1155OrdersFeature(
|
||||
address(ZERO_EX),
|
||||
ZERO_EX_DEPLOYED.weth
|
||||
@ -270,6 +280,11 @@ contract DeployZeroEx is Test {
|
||||
abi.encodeWithSelector(MetaTransactionsFeature.migrate.selector),
|
||||
address(this)
|
||||
);
|
||||
IZERO_EX.migrate(
|
||||
address(ZERO_EX_DEPLOYED.features.metaTransactionsFeatureV2),
|
||||
abi.encodeWithSelector(MetaTransactionsFeatureV2.migrate.selector),
|
||||
address(this)
|
||||
);
|
||||
IZERO_EX.migrate(
|
||||
address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature),
|
||||
abi.encodeWithSelector(ERC1155OrdersFeature.migrate.selector),
|
||||
|
@ -54,6 +54,9 @@ contract LocalTest is Test, TestUtils {
|
||||
address internal signerAddress;
|
||||
uint256 internal signerKey;
|
||||
|
||||
address internal otherSignerAddress;
|
||||
uint256 internal otherSignerKey;
|
||||
|
||||
function _infiniteApprovals() private {
|
||||
shib.approve(address(zeroExDeployed.zeroEx), type(uint256).max);
|
||||
dai.approve(address(zeroExDeployed.zeroEx), type(uint256).max);
|
||||
@ -92,12 +95,23 @@ contract LocalTest is Test, TestUtils {
|
||||
zrx = IERC20Token(address(new TestMintableERC20Token()));
|
||||
weth = zeroExDeployed.weth;
|
||||
|
||||
// TODO this should be somewhere else
|
||||
string memory mnemonic = "conduct into noodle wreck before satisfy alarm vendor dose lunch vapor party";
|
||||
otherSignerKey = vm.deriveKey(mnemonic, 0);
|
||||
otherSignerAddress = vm.addr(otherSignerKey);
|
||||
vm.label(otherSignerAddress, "zeroEx/OtherGuy");
|
||||
|
||||
_infiniteApprovals();
|
||||
|
||||
vm.startPrank(signerAddress);
|
||||
_infiniteApprovals();
|
||||
vm.stopPrank();
|
||||
|
||||
vm.deal(address(this), 10e18);
|
||||
vm.startPrank(otherSignerAddress);
|
||||
_infiniteApprovals();
|
||||
vm.stopPrank();
|
||||
|
||||
vm.deal(address(this), 20e18);
|
||||
}
|
||||
|
||||
function _mintTo(address token, address recipient, uint256 amount) internal {
|
||||
|
@ -20,6 +20,8 @@ import "forge-std/Test.sol";
|
||||
import "src/transformers/LibERC20Transformer.sol";
|
||||
|
||||
contract TestUtils is Test {
|
||||
address private constant ZERO_ADDRESS = 0x0000000000000000000000000000000000000000;
|
||||
|
||||
function _findTransformerNonce(address transformer, address deployer) internal pure returns (uint32) {
|
||||
address current;
|
||||
for (uint32 i = 0; i < 1024; i++) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user