b89dc44889
Co-authored-by: duncancmt <1207590+duncancmt@users.noreply.github.com>
447 lines
17 KiB
Solidity
447 lines
17 KiB
Solidity
// 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() private 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 _makeTestLimitOrder() private returns (bytes memory callData) {
|
|
|
|
LibNativeOrder.LimitOrder memory order = LibNativeOrder.LimitOrder({
|
|
makerToken: wethToken,
|
|
takerToken: usdcToken,
|
|
makerAmount: 1e18,
|
|
takerAmount: 1e18,
|
|
maker: signerAddress,
|
|
taker: ZERO_ADDRESS,
|
|
sender: ZERO_ADDRESS,
|
|
takerTokenFeeAmount: 0,
|
|
feeRecipient: address(this),
|
|
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.getLimitOrderHash(order));
|
|
LibSignature.Signature memory sig = LibSignature.Signature(LibSignature.SignatureType.EIP712, v, r, s);
|
|
|
|
|
|
return abi.encodeWithSelector(
|
|
INativeOrdersFeature.fillLimitOrder.selector, // ??
|
|
order, // LimitOrder
|
|
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 _getMetaTransaction(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 _badSelectorTransformERC20Call() private 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(usdcToken), address(wethToken), 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,
|
|
wethToken,
|
|
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() external {
|
|
|
|
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() external {
|
|
|
|
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() external {
|
|
|
|
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() external {
|
|
|
|
bytes memory callData = makeTestLimitOrder();
|
|
IMetaTransactionsFeature.MetaTransactionData memory mtxData = getRandomMetaTransaction(callData);
|
|
|
|
bytes memory limitCallData = mtxCall(mtxData);
|
|
|
|
(bool success, ) = address(zeroExDeployed.zeroEx).call{ value: 0 }(limitCallData);
|
|
|
|
assertTrue(success);
|
|
assertEq(wethToken.balanceOf(signerAddress), 0);
|
|
assertEq(wethToken.balanceOf(USER_ADDRESS), 1e18);
|
|
assertEq(usdcToken.balanceOf(USER_ADDRESS), 0);
|
|
assertEq(usdcToken.balanceOf(signerAddress), 1e18);
|
|
}
|
|
|
|
function test_transformERC20WithAnySender() external {
|
|
|
|
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() external {
|
|
|
|
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() external {
|
|
|
|
bytes memory transformCallData = badTokenTransformERC20Call();
|
|
|
|
IMetaTransactionsFeature.MetaTransactionData memory mtxData = getRandomMetaTransaction(transformCallData);
|
|
|
|
bytes memory theCallData = mtxCall(mtxData);
|
|
|
|
(bool success, ) = address(zeroExDeployed.zeroEx).call{ value: 0}(theCallData);
|
|
assertFalse(success);
|
|
}
|
|
|
|
function test_transformERC20UnsupportedFunction() external {
|
|
|
|
bytes memory transformCallData = badSelectorTransformERC20Call();
|
|
|
|
IMetaTransactionsFeature.MetaTransactionData memory mtxData = getRandomMetaTransaction(transformCallData);
|
|
|
|
bytes memory theCallData = mtxCall(mtxData);
|
|
|
|
(bool success, ) = address(zeroExDeployed.zeroEx).call{ value: 0}(theCallData);
|
|
assertFalse(success);
|
|
}
|
|
|
|
function test_transformERC20CantExecuteTwice() external {
|
|
|
|
bytes memory callData = makeTestRfqOrder();
|
|
|
|
IMetaTransactionsFeature.MetaTransactionData memory mtxData = getRandomMetaTransaction(callData);
|
|
|
|
bytes memory theCallData1 = mtxCall(mtxData);
|
|
|
|
(bool success, ) = address(zeroExDeployed.zeroEx).call{ value: 0}(theCallData1);
|
|
assertTrue(success);
|
|
|
|
bytes memory theCallData2 = mtxCall(mtxData);
|
|
|
|
(success, ) = address(zeroExDeployed.zeroEx).call{ value: 0}(theCallData2);
|
|
assertFalse(success);
|
|
}
|
|
|
|
function test_metaTxnFailsNotEnoughEth() external {
|
|
|
|
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);
|
|
}
|
|
/*******
|
|
* Unclear whether we need to port these, as MetaTransactionDataV2 might deprecate these fields
|
|
*******
|
|
function test_metaTxnFailsGasPriceTooLow() public {
|
|
|
|
}
|
|
|
|
function test_metaTxnFailsGasPriceTooHigh() public {
|
|
|
|
}
|
|
*******/
|
|
|
|
function test_metaTxnFailsIfExpired() external {
|
|
|
|
bytes memory callData = makeTestRfqOrder();
|
|
|
|
IMetaTransactionsFeature.MetaTransactionData memory mtxData = getRandomMetaTransaction(callData);
|
|
mtxData.expirationTimeSeconds = block.timestamp - 1;
|
|
|
|
bytes memory theCallData = mtxCall(mtxData);
|
|
|
|
(bool success, ) = address(zeroExDeployed.zeroEx).call{ value: 0}(theCallData);
|
|
assertFalse(success);
|
|
}
|
|
|
|
function test_metaTxnFailsIfWrongSender() external {
|
|
|
|
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 {
|
|
|
|
}
|
|
/***********
|
|
* These functions require TestMetaTransactionsTransformERC20Feature.sol
|
|
***********
|
|
|
|
function test_metaTxnCantReenterExecuteMetaTransaction() public {
|
|
|
|
}
|
|
|
|
function test_metaTxnCantReenterBatchExecuteMetaTransaction() public {
|
|
|
|
}
|
|
|
|
function test_metaTxnCantReduceInitialEthBalance() public {
|
|
|
|
}
|
|
*********/
|
|
} |