protocol/contracts/zero-ex/tests/MetaTransactionV2Test.t.sol
Andy 20961ef42e
Fix usage of expectEmit in MetaTransactionsFeatureV2 tests (#727)
* fix MetaTransactionsFeatureV2 tests

* pretty
2023-06-04 19:32:43 -04:00

497 lines
22 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/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);
LibSignature.Signature memory sig = _mtxSignature(mtxData);
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, sig);
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);
LibSignature.Signature memory sig = _mtxSignature(mtxData);
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, sig);
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);
LibSignature.Signature memory sig = _mtxSignature(mtxData);
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, sig);
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);
}
}