limit order metatransactions (#44)

This commit is contained in:
Steve Marx 2020-11-24 13:39:40 -05:00 committed by GitHub
parent 5306cc03e9
commit e2ee3414ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 290 additions and 10 deletions

View File

@ -25,6 +25,10 @@
{
"note": "Convert metatransactions to use `LibSignature`",
"pr": 31
},
{
"note": "Add metatransaction support for limit orders",
"pr": 44
}
]
},

View File

@ -35,6 +35,7 @@ import "./ITransformERC20Feature.sol";
import "./libs/LibSignature.sol";
import "./ISignatureValidatorFeature.sol";
import "./IFeature.sol";
import "./INativeOrdersFeature.sol";
/// @dev MetaTransactions feature.
contract MetaTransactionsFeature is
@ -48,7 +49,6 @@ contract MetaTransactionsFeature is
using LibBytesV06 for bytes;
using LibRichErrorsV06 for bytes;
/// @dev Describes the state of a meta transaction.
struct ExecuteState {
// Sender of the meta-transaction.
@ -292,6 +292,10 @@ contract MetaTransactionsFeature is
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 {
LibMetaTransactionsRichErrors
.MetaTransactionUnsupportedFunctionError(state.hash, state.selector)
@ -453,6 +457,88 @@ contract MetaTransactionsFeature is
);
}
/// @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
),
state.mtx.value
);
}
/// @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 overrideunimpleme
/// 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
),
state.mtx.value
);
}
/// @dev Make an arbitrary internal, meta-transaction call.
/// Warning: Do not let unadulterated `callData` into this function.
function _callSelf(bytes32 hash, bytes memory callData, uint256 value)

View File

@ -319,6 +319,7 @@ contract NativeOrdersFeature is
address sender
)
public
virtual
override
payable
onlySelf
@ -355,6 +356,7 @@ contract NativeOrdersFeature is
address taker
)
public
virtual
override
payable
onlySelf

View File

@ -0,0 +1,104 @@
/*
Copyright 2020 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 "../src/features/NativeOrdersFeature.sol";
import "../src/features/IMetaTransactionsFeature.sol";
contract TestMetaTransactionsNativeOrdersFeature is
NativeOrdersFeature
{
constructor(
)
public
NativeOrdersFeature(address(0), IEtherTokenV06(0), IStaking(0), 0, bytes32(0))
{
}
event FillLimitOrderCalled(
LibNativeOrder.LimitOrder order,
LibSignature.SignatureType signatureType,
uint8 v,
bytes32 r,
bytes32 s,
uint128 takerTokenFillAmount,
address taker,
address sender
);
function _fillLimitOrder(
LibNativeOrder.LimitOrder memory order,
LibSignature.Signature memory signature,
uint128 takerTokenFillAmount,
address taker,
address sender
)
public
override
payable
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
emit FillLimitOrderCalled(
order,
signature.signatureType,
signature.v,
signature.r,
signature.s,
takerTokenFillAmount,
taker,
sender
);
return (0, 1337);
}
event FillRfqOrderCalled(
LibNativeOrder.RfqOrder order,
LibSignature.SignatureType signatureType,
uint8 v,
bytes32 r,
bytes32 s,
uint128 takerTokenFillAmount,
address taker
);
function _fillRfqOrder(
LibNativeOrder.RfqOrder memory order,
LibSignature.Signature memory signature,
uint128 takerTokenFillAmount,
address taker
)
public
override
payable
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
emit FillRfqOrderCalled(
order,
signature.signatureType,
signature.v,
signature.r,
signature.s,
takerTokenFillAmount,
taker
);
return (0, 1337);
}
}

View File

@ -42,7 +42,7 @@
"config": {
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,SignatureValidatorFeature,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FeeCollector|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|INativeOrdersFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOrderHash|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinDodo|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestNativeOrdersFeature|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json"
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FeeCollector|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|INativeOrdersFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOrderHash|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinDodo|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestNativeOrdersFeature|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json"
},
"repository": {
"type": "git",

View File

@ -104,6 +104,7 @@ import * as TestInitialMigration from '../test/generated-artifacts/TestInitialMi
import * as TestLibNativeOrder from '../test/generated-artifacts/TestLibNativeOrder.json';
import * as TestLibSignature from '../test/generated-artifacts/TestLibSignature.json';
import * as TestLiquidityProvider from '../test/generated-artifacts/TestLiquidityProvider.json';
import * as TestMetaTransactionsNativeOrdersFeature from '../test/generated-artifacts/TestMetaTransactionsNativeOrdersFeature.json';
import * as TestMetaTransactionsTransformERC20Feature from '../test/generated-artifacts/TestMetaTransactionsTransformERC20Feature.json';
import * as TestMigrator from '../test/generated-artifacts/TestMigrator.json';
import * as TestMintableERC20Token from '../test/generated-artifacts/TestMintableERC20Token.json';
@ -237,6 +238,7 @@ export const artifacts = {
TestLibNativeOrder: TestLibNativeOrder as ContractArtifact,
TestLibSignature: TestLibSignature as ContractArtifact,
TestLiquidityProvider: TestLiquidityProvider as ContractArtifact,
TestMetaTransactionsNativeOrdersFeature: TestMetaTransactionsNativeOrdersFeature as ContractArtifact,
TestMetaTransactionsTransformERC20Feature: TestMetaTransactionsTransformERC20Feature as ContractArtifact,
TestMigrator: TestMigrator as ContractArtifact,
TestMintTokenERC20Transformer: TestMintTokenERC20Transformer as ContractArtifact,

View File

@ -17,7 +17,10 @@ import { IZeroExContract, MetaTransactionsFeatureContract } from '../../src/wrap
import { artifacts } from '../artifacts';
import { abis } from '../utils/abis';
import { fullMigrateAsync } from '../utils/migration';
import { getRandomLimitOrder, getRandomRfqOrder } from '../utils/orders';
import {
TestMetaTransactionsNativeOrdersFeatureContract,
TestMetaTransactionsNativeOrdersFeatureEvents,
TestMetaTransactionsTransformERC20FeatureContract,
TestMetaTransactionsTransformERC20FeatureEvents,
TestMintableERC20TokenContract,
@ -27,12 +30,14 @@ const { NULL_ADDRESS, NULL_BYTES, ZERO_AMOUNT } = constants;
blockchainTests.resets('MetaTransactions feature', env => {
let owner: string;
let maker: string;
let sender: string;
let signers: string[];
let zeroEx: IZeroExContract;
let feature: MetaTransactionsFeatureContract;
let feeToken: TestMintableERC20TokenContract;
let transformERC20Feature: TestMetaTransactionsTransformERC20FeatureContract;
let nativeOrdersFeature: TestMetaTransactionsNativeOrdersFeatureContract;
const MAX_FEE_AMOUNT = new BigNumber('1e18');
const TRANSFORM_ERC20_FAILING_VALUE = new BigNumber(666);
@ -41,15 +46,22 @@ blockchainTests.resets('MetaTransactions feature', env => {
const REENTRANCY_FLAG_MTX = 0x1;
before(async () => {
[owner, sender, ...signers] = await env.getAccountAddressesAsync();
[owner, maker, sender, ...signers] = await env.getAccountAddressesAsync();
transformERC20Feature = await TestMetaTransactionsTransformERC20FeatureContract.deployFrom0xArtifactAsync(
artifacts.TestMetaTransactionsTransformERC20Feature,
env.provider,
env.txDefaults,
{},
);
nativeOrdersFeature = await TestMetaTransactionsNativeOrdersFeatureContract.deployFrom0xArtifactAsync(
artifacts.TestMetaTransactionsNativeOrdersFeature,
env.provider,
env.txDefaults,
{},
);
zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, {
transformERC20: transformERC20Feature.address,
nativeOrders: nativeOrdersFeature.address,
});
feature = new MetaTransactionsFeatureContract(
zeroEx.address,
@ -141,9 +153,77 @@ blockchainTests.resets('MetaTransactions feature', env => {
};
}
const RAW_SUCCESS_RESULT = hexUtils.leftPad(1337);
const RAW_TRANSFORM_SUCCESS_RESULT = hexUtils.leftPad(1337);
const RAW_ORDER_SUCCESS_RESULT = hexUtils.leftPad(1337, 64);
describe('executeMetaTransaction()', () => {
it('can call NativeOrders.fillLimitOrder()', async () => {
const order = getRandomLimitOrder({ maker });
const fillAmount = new BigNumber(23456);
const sig = await order.getSignatureWithProviderAsync(env.provider);
const mtx = getRandomMetaTransaction({
callData: nativeOrdersFeature.fillLimitOrder(order, sig, fillAmount).getABIEncodedTransactionData(),
});
const signature = await signMetaTransactionAsync(mtx);
const callOpts = {
gasPrice: mtx.minGasPrice,
value: mtx.value,
};
const rawResult = await feature.executeMetaTransaction(mtx, signature).callAsync(callOpts);
expect(rawResult).to.eq(RAW_ORDER_SUCCESS_RESULT);
const receipt = await feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
verifyEventsFromLogs(
receipt.logs,
[
{
order: _.omit(order, ['verifyingContract', 'chainId']),
sender: mtx.sender,
taker: mtx.signer,
takerTokenFillAmount: fillAmount,
signatureType: sig.signatureType,
v: sig.v,
r: sig.r,
s: sig.s,
},
],
TestMetaTransactionsNativeOrdersFeatureEvents.FillLimitOrderCalled,
);
});
it('can call NativeOrders.fillRfqOrder()', async () => {
const order = getRandomRfqOrder({ maker });
const sig = await order.getSignatureWithProviderAsync(env.provider);
const fillAmount = new BigNumber(23456);
const mtx = getRandomMetaTransaction({
callData: nativeOrdersFeature.fillRfqOrder(order, sig, fillAmount).getABIEncodedTransactionData(),
});
const signature = await signMetaTransactionAsync(mtx);
const callOpts = {
gasPrice: mtx.minGasPrice,
value: mtx.value,
};
const rawResult = await feature.executeMetaTransaction(mtx, signature).callAsync(callOpts);
expect(rawResult).to.eq(RAW_ORDER_SUCCESS_RESULT);
const receipt = await feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
verifyEventsFromLogs(
receipt.logs,
[
{
order: _.omit(order, ['verifyingContract', 'chainId']),
taker: mtx.signer,
takerTokenFillAmount: fillAmount,
signatureType: sig.signatureType,
v: sig.v,
r: sig.r,
s: sig.s,
},
],
TestMetaTransactionsNativeOrdersFeatureEvents.FillRfqOrderCalled,
);
});
it('can call `TransformERC20.transformERC20()`', async () => {
const args = getRandomTransformERC20Args();
const mtx = getRandomMetaTransaction({
@ -163,7 +243,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
value: mtx.value,
};
const rawResult = await feature.executeMetaTransaction(mtx, signature).callAsync(callOpts);
expect(rawResult).to.eq(RAW_SUCCESS_RESULT);
expect(rawResult).to.eq(RAW_TRANSFORM_SUCCESS_RESULT);
const receipt = await feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
verifyEventsFromLogs(
receipt.logs,
@ -206,7 +286,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
value: mtx.value,
};
const rawResult = await feature.executeMetaTransaction(mtx, signature).callAsync(callOpts);
expect(rawResult).to.eq(RAW_SUCCESS_RESULT);
expect(rawResult).to.eq(RAW_TRANSFORM_SUCCESS_RESULT);
const receipt = await feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
verifyEventsFromLogs(
receipt.logs,
@ -249,7 +329,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
from: randomAddress(),
};
const rawResult = await feature.executeMetaTransaction(mtx, signature).callAsync(callOpts);
expect(rawResult).to.eq(RAW_SUCCESS_RESULT);
expect(rawResult).to.eq(RAW_TRANSFORM_SUCCESS_RESULT);
});
it('works without fee', async () => {
@ -273,7 +353,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
value: mtx.value,
};
const rawResult = await feature.executeMetaTransaction(mtx, signature).callAsync(callOpts);
expect(rawResult).to.eq(RAW_SUCCESS_RESULT);
expect(rawResult).to.eq(RAW_TRANSFORM_SUCCESS_RESULT);
});
it('fails if the translated call fails', async () => {
@ -638,7 +718,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
value: BigNumber.sum(...mtxs.map(mtx => mtx.value)),
};
const rawResults = await feature.batchExecuteMetaTransactions(mtxs, signatures).callAsync(callOpts);
expect(rawResults).to.eql(mtxs.map(() => RAW_SUCCESS_RESULT));
expect(rawResults).to.eql(mtxs.map(() => RAW_TRANSFORM_SUCCESS_RESULT));
});
it('cannot execute the same transaction twice', async () => {

View File

@ -102,6 +102,7 @@ export * from '../test/generated-wrappers/test_initial_migration';
export * from '../test/generated-wrappers/test_lib_native_order';
export * from '../test/generated-wrappers/test_lib_signature';
export * from '../test/generated-wrappers/test_liquidity_provider';
export * from '../test/generated-wrappers/test_meta_transactions_native_orders_feature';
export * from '../test/generated-wrappers/test_meta_transactions_transform_erc20_feature';
export * from '../test/generated-wrappers/test_migrator';
export * from '../test/generated-wrappers/test_mint_token_erc20_transformer';

View File

@ -129,6 +129,7 @@
"test/generated-artifacts/TestLibNativeOrder.json",
"test/generated-artifacts/TestLibSignature.json",
"test/generated-artifacts/TestLiquidityProvider.json",
"test/generated-artifacts/TestMetaTransactionsNativeOrdersFeature.json",
"test/generated-artifacts/TestMetaTransactionsTransformERC20Feature.json",
"test/generated-artifacts/TestMigrator.json",
"test/generated-artifacts/TestMintTokenERC20Transformer.json",