Initial MTX Test Migration to forge
This commit is contained in:
@@ -31,6 +31,8 @@ import "./interfaces/INativeOrdersFeature.sol";
|
||||
import "./interfaces/ITransformERC20Feature.sol";
|
||||
import "./libs/LibSignature.sol";
|
||||
|
||||
import "forge-std/console.sol";
|
||||
|
||||
/// @dev MetaTransactions feature.
|
||||
contract MetaTransactionsFeature is
|
||||
IFeature,
|
||||
@@ -253,24 +255,28 @@ contract MetaTransactionsFeature is
|
||||
|
||||
/// @dev Validate that a meta-transaction is executable.
|
||||
function _validateMetaTransaction(ExecuteState memory state) private view {
|
||||
console.log("Must be from required sender, if set");
|
||||
// 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();
|
||||
}
|
||||
console.log("Must not be expired");
|
||||
// Must not be expired.
|
||||
if (state.mtx.expirationTimeSeconds <= block.timestamp) {
|
||||
LibMetaTransactionsRichErrors
|
||||
.MetaTransactionExpiredError(state.hash, block.timestamp, state.mtx.expirationTimeSeconds)
|
||||
.rrevert();
|
||||
}
|
||||
console.log("Must have a valid gas price");
|
||||
// Must have a valid gas price.
|
||||
if (state.mtx.minGasPrice > tx.gasprice || state.mtx.maxGasPrice < tx.gasprice) {
|
||||
LibMetaTransactionsRichErrors
|
||||
.MetaTransactionGasPriceError(state.hash, tx.gasprice, state.mtx.minGasPrice, state.mtx.maxGasPrice)
|
||||
.rrevert();
|
||||
}
|
||||
console.log("Must have enough ETH");
|
||||
// Must have enough ETH.
|
||||
state.selfBalance = address(this).balance;
|
||||
if (state.mtx.value > state.selfBalance) {
|
||||
@@ -291,6 +297,7 @@ contract MetaTransactionsFeature is
|
||||
)
|
||||
.rrevert();
|
||||
}
|
||||
console.log("Must not have already been executed");
|
||||
// Transaction must not have been already executed.
|
||||
state.executedBlockNumber = LibMetaTransactionsStorage.getStorage().mtxHashToExecutedBlockNumber[state.hash];
|
||||
if (state.executedBlockNumber != 0) {
|
||||
|
||||
321
contracts/zero-ex/tests/MetaTransactionTest.t.sol
Normal file
321
contracts/zero-ex/tests/MetaTransactionTest.t.sol
Normal file
@@ -0,0 +1,321 @@
|
||||
// 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/DeployZeroEx.sol";
|
||||
import "../contracts/src/features/MetaTransactionsFeature.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-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||
|
||||
contract MetaTransactionTest is BaseTest {
|
||||
DeployZeroEx.ZeroExDeployed zeroExDeployed;
|
||||
address private constant ZERO_ADDRESS = 0x0000000000000000000000000000000000000000;
|
||||
address private constant USER_ADDRESS = 0x6dc3a54FeAE57B65d185A7B159c5d3FA7fD7FD0F;
|
||||
uint256 private constant USER_KEY = 0x1fc1630343b31e60b7a197a53149ca571ed9d9791e2833337bbd8110c30710ec;
|
||||
IEtherTokenV06 private wethToken;
|
||||
IERC20TokenV06 private usdcToken;
|
||||
IERC20TokenV06 private zrxToken;
|
||||
uint256 private constant oneEth = 1e18;
|
||||
address private signerAddress;
|
||||
uint256 private signerKey;
|
||||
uint256 private transformerNonce;
|
||||
|
||||
|
||||
function setUp() public {
|
||||
(signerAddress, signerKey) = getSigner();
|
||||
zeroExDeployed = new DeployZeroEx().deployZeroEx();
|
||||
wethToken = zeroExDeployed.weth;
|
||||
usdcToken = IERC20TokenV06(address(new TestMintableERC20Token()));
|
||||
zrxToken = IERC20TokenV06(address(new TestMintableERC20Token()));
|
||||
|
||||
transformerNonce = zeroExDeployed.transformerDeployer.nonce();
|
||||
vm.prank(zeroExDeployed.transformerDeployer.authorities(0));
|
||||
zeroExDeployed.transformerDeployer.deploy(type(TestMintTokenERC20Transformer).creationCode);
|
||||
|
||||
vm.deal(address(this), 10e18);
|
||||
vm.deal(USER_ADDRESS, 10e18);
|
||||
vm.deal(signerAddress, 10e18);
|
||||
}
|
||||
|
||||
function getSigner() public returns (address, uint) {
|
||||
string memory mnemonic = "test test test test test test test test test test test junk";
|
||||
uint256 privateKey = vm.deriveKey(mnemonic, 0);
|
||||
vm.label(vm.addr(privateKey), "zeroEx/MarketMaker");
|
||||
return (vm.addr(privateKey), privateKey);
|
||||
}
|
||||
|
||||
function makeTestRfqOrder() private returns (bytes memory callData) {
|
||||
LibNativeOrder.RfqOrder memory order = LibNativeOrder.RfqOrder({
|
||||
makerToken: wethToken,
|
||||
takerToken: usdcToken,
|
||||
makerAmount: 1e18,
|
||||
takerAmount: 1e18,
|
||||
maker: signerAddress,
|
||||
taker: ZERO_ADDRESS,
|
||||
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), USER_ADDRESS, order.takerAmount);
|
||||
vm.prank(USER_ADDRESS);
|
||||
order.takerToken.approve(address(zeroExDeployed.zeroEx), order.takerAmount);
|
||||
|
||||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signerKey, zeroExDeployed.features.nativeOrdersFeature.getRfqOrderHash(order));
|
||||
LibSignature.Signature memory sig = LibSignature.Signature(LibSignature.SignatureType.EIP712, v, r, s);
|
||||
|
||||
|
||||
return abi.encodeWithSelector(
|
||||
INativeOrdersFeature.fillRfqOrder.selector, // ??
|
||||
order, // RFQOrder
|
||||
sig, // Order Signature
|
||||
1e18 // Fill Amount
|
||||
);
|
||||
}
|
||||
|
||||
function mintTo(address token, address recipient, uint256 amount) private {
|
||||
if (token == address(wethToken)) {
|
||||
//vm.prank(recipient);
|
||||
IEtherTokenV06(token).deposit{value: amount}();
|
||||
WETH9V06(payable(token)).transfer(recipient, amount);
|
||||
} else {
|
||||
TestMintableERC20Token(token).mint(recipient, amount);
|
||||
}
|
||||
}
|
||||
|
||||
function getRandomMetaTransaction(bytes memory callData) private view returns (IMetaTransactionsFeature.MetaTransactionData memory) {
|
||||
IMetaTransactionsFeature.MetaTransactionData memory mtx = IMetaTransactionsFeature.MetaTransactionData({
|
||||
signer: payable(USER_ADDRESS),
|
||||
sender: address(this),
|
||||
minGasPrice: 0,
|
||||
maxGasPrice: 100000000000,
|
||||
expirationTimeSeconds: block.timestamp + 60,
|
||||
salt: 123,
|
||||
callData: callData,
|
||||
value: 0,
|
||||
feeToken: wethToken,
|
||||
feeAmount: 1
|
||||
});
|
||||
return mtx;
|
||||
}
|
||||
|
||||
function transformERC20Call() private returns (bytes memory) {
|
||||
ITransformERC20Feature.Transformation[] memory transformations = new ITransformERC20Feature.Transformation[](1);
|
||||
transformations[0] = ITransformERC20Feature.Transformation(
|
||||
uint32(transformerNonce),
|
||||
abi.encode(address(usdcToken), address(zrxToken), 0, oneEth, 0)
|
||||
);
|
||||
|
||||
mintTo(address(usdcToken), USER_ADDRESS, oneEth);
|
||||
vm.prank(USER_ADDRESS);
|
||||
usdcToken.approve(address(zeroExDeployed.zeroEx), oneEth);
|
||||
|
||||
return abi.encodeWithSelector(
|
||||
zeroExDeployed.zeroEx.transformERC20.selector, // 0x415565b0
|
||||
usdcToken,
|
||||
zrxToken,
|
||||
oneEth,
|
||||
oneEth,
|
||||
transformations
|
||||
);
|
||||
}
|
||||
|
||||
function mtxCall(IMetaTransactionsFeature.MetaTransactionData memory mtx) private returns (bytes memory) {
|
||||
// Mint fee to signer and approve
|
||||
if (mtx.feeAmount > 0) {
|
||||
mintTo(address(mtx.feeToken), mtx.signer, mtx.feeAmount);
|
||||
vm.prank(mtx.signer);
|
||||
mtx.feeToken.approve(address(zeroExDeployed.zeroEx), oneEth);
|
||||
}
|
||||
|
||||
bytes32 mtxHash = zeroExDeployed.features.metaTransactionsFeature.getMetaTransactionHash(mtx);
|
||||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(USER_KEY, mtxHash);
|
||||
LibSignature.Signature memory sig = LibSignature.Signature(LibSignature.SignatureType.EIP712, v, r, s);
|
||||
|
||||
return abi.encodeWithSelector(
|
||||
zeroExDeployed.zeroEx.executeMetaTransaction.selector, // 0x3d61ed3e
|
||||
mtx,
|
||||
sig
|
||||
);
|
||||
}
|
||||
|
||||
function test_createHash() public {
|
||||
bytes memory transformCallData = transformERC20Call();
|
||||
IMetaTransactionsFeature.MetaTransactionData memory mtxData = getRandomMetaTransaction(transformCallData);
|
||||
|
||||
//mtxData.signer = address(0);
|
||||
bytes32 mtxHash = zeroExDeployed.features.metaTransactionsFeature.getMetaTransactionHash(mtxData);
|
||||
assertTrue(mtxHash != bytes32(0));
|
||||
}
|
||||
|
||||
function test_transformERC20() public {
|
||||
bytes memory transformCallData = transformERC20Call();
|
||||
IMetaTransactionsFeature.MetaTransactionData memory mtxData = getRandomMetaTransaction(transformCallData);
|
||||
|
||||
bytes memory theCallData = mtxCall(mtxData);
|
||||
|
||||
assertEq(usdcToken.balanceOf(USER_ADDRESS), oneEth);
|
||||
|
||||
(bool success, ) = address(zeroExDeployed.zeroEx).call{ value: 0}(theCallData);
|
||||
assertTrue(success);
|
||||
assertEq(zrxToken.balanceOf(USER_ADDRESS), oneEth);
|
||||
assertEq(usdcToken.balanceOf(USER_ADDRESS), 0);
|
||||
assertEq(wethToken.balanceOf(address(this)), 1);
|
||||
}
|
||||
|
||||
function test_rfqOrder() public {
|
||||
bytes memory callData = makeTestRfqOrder();
|
||||
IMetaTransactionsFeature.MetaTransactionData memory mtxData = getRandomMetaTransaction(callData);
|
||||
|
||||
bytes memory rfqCallData = mtxCall(mtxData);
|
||||
|
||||
(bool success, ) = address(zeroExDeployed.zeroEx).call{ value: 0 }(rfqCallData);
|
||||
|
||||
assertTrue(success);
|
||||
assertEq(wethToken.balanceOf(signerAddress), 0);
|
||||
assertEq(wethToken.balanceOf(USER_ADDRESS), 1e18);
|
||||
assertEq(usdcToken.balanceOf(USER_ADDRESS), 0);
|
||||
assertEq(usdcToken.balanceOf(signerAddress), 1e18);
|
||||
assertEq(wethToken.balanceOf(address(this)), 1);
|
||||
|
||||
// TODO: check event log for TestMetaTransactionsNativeOrdersFeatureEvents.FillLimitOrderCalled?
|
||||
}
|
||||
|
||||
function test_fillLimitOrder() public {
|
||||
|
||||
}
|
||||
|
||||
function test_transformERC20WithAnySender() public {
|
||||
bytes memory transformCallData = transformERC20Call();
|
||||
|
||||
IMetaTransactionsFeature.MetaTransactionData memory mtxData = getRandomMetaTransaction(transformCallData);
|
||||
mtxData.sender = ZERO_ADDRESS;
|
||||
|
||||
bytes memory theCallData = mtxCall(mtxData);
|
||||
|
||||
assertEq(usdcToken.balanceOf(USER_ADDRESS), oneEth);
|
||||
|
||||
(bool success, ) = address(zeroExDeployed.zeroEx).call{ value: 0}(theCallData);
|
||||
assertTrue(success);
|
||||
assertEq(zrxToken.balanceOf(USER_ADDRESS), oneEth);
|
||||
assertEq(usdcToken.balanceOf(USER_ADDRESS), 0);
|
||||
assertEq(wethToken.balanceOf(address(this)), 1);
|
||||
}
|
||||
|
||||
function test_transformERC20WithoutFee() public {
|
||||
bytes memory transformCallData = transformERC20Call();
|
||||
|
||||
IMetaTransactionsFeature.MetaTransactionData memory mtxData = getRandomMetaTransaction(transformCallData);
|
||||
mtxData.feeAmount = 0;
|
||||
|
||||
bytes memory theCallData = mtxCall(mtxData);
|
||||
|
||||
assertEq(usdcToken.balanceOf(USER_ADDRESS), oneEth);
|
||||
|
||||
(bool success, ) = address(zeroExDeployed.zeroEx).call{ value: 0}(theCallData);
|
||||
assertTrue(success);
|
||||
assertEq(zrxToken.balanceOf(USER_ADDRESS), oneEth);
|
||||
assertEq(usdcToken.balanceOf(USER_ADDRESS), 0);
|
||||
assertEq(wethToken.balanceOf(address(this)), 0); // no fee paid out
|
||||
}
|
||||
|
||||
function test_transformERC20TranslatedCallFail() public {
|
||||
|
||||
}
|
||||
|
||||
function test_transformERC20UnsupportedFunction() public {
|
||||
|
||||
}
|
||||
|
||||
function test_transformERC20CantExecuteTwice() public {
|
||||
|
||||
}
|
||||
|
||||
function test_metaTxnFailsNotEnoughEth() public {
|
||||
bytes memory callData = makeTestRfqOrder();
|
||||
|
||||
IMetaTransactionsFeature.MetaTransactionData memory mtxData = getRandomMetaTransaction(callData);
|
||||
mtxData.value = 1;
|
||||
|
||||
bytes memory theCallData = mtxCall(mtxData);
|
||||
|
||||
(bool success, ) = address(zeroExDeployed.zeroEx).call{ value: 0}(theCallData);
|
||||
assertFalse(success);
|
||||
}
|
||||
/*
|
||||
function test_metaTxnFailsGasPriceTooLow() public {
|
||||
|
||||
}
|
||||
|
||||
function test_metaTxnFailsGasPriceTooHigh() public {
|
||||
|
||||
}*/
|
||||
|
||||
function test_metaTxnFailsIfExpired() public {
|
||||
bytes memory callData = makeTestRfqOrder();
|
||||
|
||||
IMetaTransactionsFeature.MetaTransactionData memory mtxData = getRandomMetaTransaction(callData);
|
||||
mtxData.expirationTimeSeconds = block.timestamp;
|
||||
|
||||
bytes memory theCallData = mtxCall(mtxData);
|
||||
|
||||
(bool success, ) = address(zeroExDeployed.zeroEx).call{ value: 0}(theCallData);
|
||||
assertFalse(success);
|
||||
}
|
||||
|
||||
function test_metaTxnFailsIfWrongSender() public {
|
||||
bytes memory transformCallData = transformERC20Call();
|
||||
IMetaTransactionsFeature.MetaTransactionData memory mtxData = getRandomMetaTransaction(transformCallData);
|
||||
mtxData.sender = USER_ADDRESS;
|
||||
|
||||
bytes memory theCallData = mtxCall(mtxData);
|
||||
|
||||
assertEq(usdcToken.balanceOf(USER_ADDRESS), oneEth);
|
||||
|
||||
(bool success, ) = address(zeroExDeployed.zeroEx).call{ value: 0}(theCallData);
|
||||
assertFalse(success);
|
||||
}
|
||||
|
||||
function test_metaTxnFailsWrongSignature() public {
|
||||
|
||||
}
|
||||
|
||||
function test_metaTxnCantReenterExecuteMetaTransaction() public {
|
||||
|
||||
}
|
||||
|
||||
function test_metaTxnCantReenterBatchExecuteMetaTransaction() public {
|
||||
|
||||
}
|
||||
|
||||
function test_metaTxnCantReduceInitialEthBalance() public {
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user