From 5707993995455e8ca254df1521211de2865b3265 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 2 Jun 2020 22:29:33 -0400 Subject: [PATCH 1/6] `@0x/contracts-zero-ex`: Add `AffiliateFeeTransformer`. --- .../errors/LibTransformERC20RichErrors.sol | 1 - .../transformers/AffiliateFeeTransformer.sol | 90 +++++++++++++++++++ contracts/zero-ex/test/artifacts.ts | 2 + contracts/zero-ex/test/wrappers.ts | 1 + contracts/zero-ex/tsconfig.json | 1 + 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol diff --git a/contracts/zero-ex/contracts/src/errors/LibTransformERC20RichErrors.sol b/contracts/zero-ex/contracts/src/errors/LibTransformERC20RichErrors.sol index ea6789ea15..0513a86f65 100644 --- a/contracts/zero-ex/contracts/src/errors/LibTransformERC20RichErrors.sol +++ b/contracts/zero-ex/contracts/src/errors/LibTransformERC20RichErrors.sol @@ -230,5 +230,4 @@ library LibTransformERC20RichErrors { token ); } - } diff --git a/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol new file mode 100644 index 0000000000..28b959409e --- /dev/null +++ b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol @@ -0,0 +1,90 @@ +/* + + 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 "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; +import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; +import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; +import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol"; +import "../errors/LibTransformERC20RichErrors.sol"; +import "./Transformer.sol"; +import "./LibERC20Transformer.sol"; + + +/// @dev A transformer that transfers tokens to arbitrary addresses. +contract AffiliateFeeTransformer is + Transformer +{ + // solhint-disable no-empty-blocks + using LibRichErrorsV06 for bytes; + using LibSafeMathV06 for uint256; + using LibERC20Transformer for IERC20TokenV06; + + /// @dev Transform data to ABI-encode and pass into `transform()`. + struct TransformData { + // The tokens to transfer to each respective address in `recipients`. + IERC20TokenV06[] tokens; + // Amount of each token in `tokens` to transfer to the affiliate. + uint256[] amounts; + // Recipient of each token in `tokens`. + address payable[] recipients; + } + + /// @dev Create this contract. + /// @param deploymentNonce_ The nonce of the deployer when deploying this contract. + constructor(uint256 deploymentNonce_) + public + Transformer(deploymentNonce_) + {} + + /// @dev Transfers tokens to recipients. + /// @param data_ ABI-encoded `TransformData`, indicating which tokens to transfer. + /// @return rlpDeploymentNonce RLP-encoded deployment nonce of the deployer + /// when this transformer was deployed. This is used to verify that + /// this transformer was deployed by a trusted contract. + function transform( + bytes32, // callDataHash, + address payable, // taker, + bytes calldata data_ + ) + external + override + returns (bytes memory rlpDeploymentNonce) + { + TransformData memory data = abi.decode(data_, (TransformData)); + + // All arrays must be the same length. + if (data.tokens.length != data.amounts.length || + data.tokens.length != data.recipients.length) + { + LibTransformERC20RichErrors.InvalidTransformDataError( + LibTransformERC20RichErrors.InvalidTransformDataErrorCode.INVALID_ARRAY_LENGTH, + data_ + ).rrevert(); + } + + // Transfer tokens to recipients. + for (uint256 i = 0; i < data.tokens.length; ++i) { + data.tokens[i].transformerTransfer(data.recipients[i], data.amounts[i]); + } + + return _getRLPEncodedDeploymentNonce(); + } +} diff --git a/contracts/zero-ex/test/artifacts.ts b/contracts/zero-ex/test/artifacts.ts index 0d76a254d4..e1fb0476b2 100644 --- a/contracts/zero-ex/test/artifacts.ts +++ b/contracts/zero-ex/test/artifacts.ts @@ -5,6 +5,7 @@ */ import { ContractArtifact } from 'ethereum-types'; +import * as AffiliateFeeTransformer from '../test/generated-artifacts/AffiliateFeeTransformer.json'; import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.json'; import * as Bootstrap from '../test/generated-artifacts/Bootstrap.json'; import * as FillQuoteTransformer from '../test/generated-artifacts/FillQuoteTransformer.json'; @@ -104,6 +105,7 @@ export const artifacts = { LibStorage: LibStorage as ContractArtifact, LibTokenSpenderStorage: LibTokenSpenderStorage as ContractArtifact, LibTransformERC20Storage: LibTransformERC20Storage as ContractArtifact, + AffiliateFeeTransformer: AffiliateFeeTransformer as ContractArtifact, FillQuoteTransformer: FillQuoteTransformer as ContractArtifact, IERC20Transformer: IERC20Transformer as ContractArtifact, LibERC20Transformer: LibERC20Transformer as ContractArtifact, diff --git a/contracts/zero-ex/test/wrappers.ts b/contracts/zero-ex/test/wrappers.ts index 9df36914d7..00bdcffb29 100644 --- a/contracts/zero-ex/test/wrappers.ts +++ b/contracts/zero-ex/test/wrappers.ts @@ -3,6 +3,7 @@ * Warning: This file is auto-generated by contracts-gen. Don't edit manually. * ----------------------------------------------------------------------------- */ +export * from '../test/generated-wrappers/affiliate_fee_transformer'; export * from '../test/generated-wrappers/allowance_target'; export * from '../test/generated-wrappers/bootstrap'; export * from '../test/generated-wrappers/fill_quote_transformer'; diff --git a/contracts/zero-ex/tsconfig.json b/contracts/zero-ex/tsconfig.json index 7bc3fb6e6a..9c690c0edb 100644 --- a/contracts/zero-ex/tsconfig.json +++ b/contracts/zero-ex/tsconfig.json @@ -16,6 +16,7 @@ "generated-artifacts/PayTakerTransformer.json", "generated-artifacts/WethTransformer.json", "generated-artifacts/ZeroEx.json", + "test/generated-artifacts/AffiliateFeeTransformer.json", "test/generated-artifacts/AllowanceTarget.json", "test/generated-artifacts/Bootstrap.json", "test/generated-artifacts/FillQuoteTransformer.json", From 7c5035aa392e2f7115a4c92c0746225e6bedb64c Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Sat, 6 Jun 2020 10:59:27 -0400 Subject: [PATCH 2/6] `@0x/contracts-zero-ex`: Use array of structs in `AffiliateFeeTransformer` data. --- .../transformers/AffiliateFeeTransformer.sol | 38 ++++++++----------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol index 28b959409e..5ae1b0bb59 100644 --- a/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol @@ -37,14 +37,16 @@ contract AffiliateFeeTransformer is using LibSafeMathV06 for uint256; using LibERC20Transformer for IERC20TokenV06; - /// @dev Transform data to ABI-encode and pass into `transform()`. - struct TransformData { - // The tokens to transfer to each respective address in `recipients`. - IERC20TokenV06[] tokens; - // Amount of each token in `tokens` to transfer to the affiliate. - uint256[] amounts; - // Recipient of each token in `tokens`. - address payable[] recipients; + /// @dev Information for a single fee. + struct TokenFee { + // The token to transfer to `recipient`. + IERC20TokenV06 token; + // Amount of each `token` to transfer to `recipient`. + // If `amount == uint256(-1)`, the entire balance of `token` will be + // transferred. + uint256 amount; + // Recipient of `token`. + address payable recipient; } /// @dev Create this contract. @@ -55,34 +57,24 @@ contract AffiliateFeeTransformer is {} /// @dev Transfers tokens to recipients. - /// @param data_ ABI-encoded `TransformData`, indicating which tokens to transfer. + /// @param data ABI-encoded `TokenFee[]`, indicating which tokens to transfer. /// @return rlpDeploymentNonce RLP-encoded deployment nonce of the deployer /// when this transformer was deployed. This is used to verify that /// this transformer was deployed by a trusted contract. function transform( bytes32, // callDataHash, address payable, // taker, - bytes calldata data_ + bytes calldata data ) external override returns (bytes memory rlpDeploymentNonce) { - TransformData memory data = abi.decode(data_, (TransformData)); - - // All arrays must be the same length. - if (data.tokens.length != data.amounts.length || - data.tokens.length != data.recipients.length) - { - LibTransformERC20RichErrors.InvalidTransformDataError( - LibTransformERC20RichErrors.InvalidTransformDataErrorCode.INVALID_ARRAY_LENGTH, - data_ - ).rrevert(); - } + TokenFee[] memory fees = abi.decode(data, (TokenFee[])); // Transfer tokens to recipients. - for (uint256 i = 0; i < data.tokens.length; ++i) { - data.tokens[i].transformerTransfer(data.recipients[i], data.amounts[i]); + for (uint256 i = 0; i < fees.length; ++i) { + fees[i].token.transformerTransfer(fees[i].recipient, fees[i].amount); } return _getRLPEncodedDeploymentNonce(); From 2176deae36f532e42a086e068a9d51c0a6d1a359 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Sat, 6 Jun 2020 11:05:37 -0400 Subject: [PATCH 3/6] `@0x/contracts-zero-ex`: Rebase --- .../src/transformers/AffiliateFeeTransformer.sol | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol index 5ae1b0bb59..26a7692919 100644 --- a/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol @@ -50,17 +50,14 @@ contract AffiliateFeeTransformer is } /// @dev Create this contract. - /// @param deploymentNonce_ The nonce of the deployer when deploying this contract. - constructor(uint256 deploymentNonce_) + constructor() public - Transformer(deploymentNonce_) + Transformer() {} /// @dev Transfers tokens to recipients. /// @param data ABI-encoded `TokenFee[]`, indicating which tokens to transfer. - /// @return rlpDeploymentNonce RLP-encoded deployment nonce of the deployer - /// when this transformer was deployed. This is used to verify that - /// this transformer was deployed by a trusted contract. + /// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`). function transform( bytes32, // callDataHash, address payable, // taker, @@ -68,7 +65,7 @@ contract AffiliateFeeTransformer is ) external override - returns (bytes memory rlpDeploymentNonce) + returns (bytes4 success) { TokenFee[] memory fees = abi.decode(data, (TokenFee[])); @@ -77,6 +74,6 @@ contract AffiliateFeeTransformer is fees[i].token.transformerTransfer(fees[i].recipient, fees[i].amount); } - return _getRLPEncodedDeploymentNonce(); + return LibERC20Transformer.TRANSFORMER_SUCCESS; } } From 5e9829b2d844ad74715a9f6d9d50f9ecf2936724 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Sat, 6 Jun 2020 11:46:40 -0400 Subject: [PATCH 4/6] `@0x/contracts-zero-ex`: Add `AffiliateFeeTransformer` tests. --- .../transformers/AffiliateFeeTransformer.sol | 9 +- .../zero-ex/src/transformer_data_encoders.ts | 37 +++++ .../affiliate_fee_transformer_test.ts | 157 ++++++++++++++++++ 3 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 contracts/zero-ex/test/transformers/affiliate_fee_transformer_test.ts diff --git a/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol index 26a7692919..03bbafaeae 100644 --- a/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol @@ -49,6 +49,8 @@ contract AffiliateFeeTransformer is address payable recipient; } + uint256 private constant MAX_UINT256 = uint256(-1); + /// @dev Create this contract. constructor() public @@ -71,7 +73,12 @@ contract AffiliateFeeTransformer is // Transfer tokens to recipients. for (uint256 i = 0; i < fees.length; ++i) { - fees[i].token.transformerTransfer(fees[i].recipient, fees[i].amount); + uint256 amount = fees[i].amount == MAX_UINT256 + ? LibERC20Transformer.getTokenBalanceOf(fees[i].token, address(this)) + : fees[i].amount; + if (amount != 0) { + fees[i].token.transformerTransfer(fees[i].recipient, amount); + } } return LibERC20Transformer.TRANSFORMER_SUCCESS; diff --git a/contracts/zero-ex/src/transformer_data_encoders.ts b/contracts/zero-ex/src/transformer_data_encoders.ts index d9da6a0e7c..26c29abff3 100644 --- a/contracts/zero-ex/src/transformer_data_encoders.ts +++ b/contracts/zero-ex/src/transformer_data_encoders.ts @@ -120,3 +120,40 @@ export interface PayTakerTransformerData { export function encodePayTakerTransformerData(data: PayTakerTransformerData): string { return payTakerTransformerDataEncoder.encode([data]); } + +/** + * ABI encoder for `PayTakerTransformer.TransformData` + */ +export const affiliateFeeTransformerDataEncoder = AbiEncoder.create({ + name: 'data', + type: 'tuple', + components: [ + { + name: 'fees', + type: 'tuple[]', + components: [ + { name: 'token', type: 'address' }, + { name: 'amount', type: 'uint256' }, + { name: 'recipient', type: 'address' }, + ], + }, + ], +}); + +/** + * `AffiliateFeeTransformer.TransformData` + */ +export interface AffiliateFeeTransformerData { + fees: Array<{ + token: string; + amount: BigNumber; + recipient: string; + }>; +} + +/** + * ABI-encode a `AffiliateFeeTransformer.TransformData` type. + */ +export function encodeAffiliateFeeTransformerData(data: AffiliateFeeTransformerData): string { + return affiliateFeeTransformerDataEncoder.encode(data); +} diff --git a/contracts/zero-ex/test/transformers/affiliate_fee_transformer_test.ts b/contracts/zero-ex/test/transformers/affiliate_fee_transformer_test.ts new file mode 100644 index 0000000000..a88eca5486 --- /dev/null +++ b/contracts/zero-ex/test/transformers/affiliate_fee_transformer_test.ts @@ -0,0 +1,157 @@ +import { blockchainTests, constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils'; +import { BigNumber, hexUtils } from '@0x/utils'; +import * as _ from 'lodash'; + +import { ETH_TOKEN_ADDRESS } from '../../src/constants'; +import { encodeAffiliateFeeTransformerData } from '../../src/transformer_data_encoders'; +import { artifacts } from '../artifacts'; +import { + AffiliateFeeTransformerContract, + TestMintableERC20TokenContract, + TestTransformerHostContract, +} from '../wrappers'; + +const { MAX_UINT256, ZERO_AMOUNT } = constants; + +blockchainTests.resets('AffiliateFeeTransformer', env => { + const recipients = new Array(2).fill(0).map(() => randomAddress()); + let caller: string; + let token: TestMintableERC20TokenContract; + let transformer: AffiliateFeeTransformerContract; + let host: TestTransformerHostContract; + + before(async () => { + [caller] = await env.getAccountAddressesAsync(); + token = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync( + artifacts.TestMintableERC20Token, + env.provider, + env.txDefaults, + artifacts, + ); + transformer = await AffiliateFeeTransformerContract.deployFrom0xArtifactAsync( + artifacts.AffiliateFeeTransformer, + env.provider, + env.txDefaults, + artifacts, + ); + host = await TestTransformerHostContract.deployFrom0xArtifactAsync( + artifacts.TestTransformerHost, + env.provider, + { ...env.txDefaults, from: caller }, + artifacts, + ); + }); + + interface Balances { + ethBalance: BigNumber; + tokenBalance: BigNumber; + } + + const ZERO_BALANCES = { + ethBalance: ZERO_AMOUNT, + tokenBalance: ZERO_AMOUNT, + }; + + async function getBalancesAsync(owner: string): Promise { + return { + ethBalance: await env.web3Wrapper.getBalanceInWeiAsync(owner), + tokenBalance: await token.balanceOf(owner).callAsync(), + }; + } + + async function mintHostTokensAsync(amount: BigNumber): Promise { + await token.mint(host.address, amount).awaitTransactionSuccessAsync(); + } + + async function sendEtherAsync(to: string, amount: BigNumber): Promise { + await env.web3Wrapper.awaitTransactionSuccessAsync( + await env.web3Wrapper.sendTransactionAsync({ + ...env.txDefaults, + to, + from: caller, + value: amount, + }), + ); + } + + it('can transfer a token and ETH', async () => { + const amounts = recipients.map(() => getRandomInteger(1, '1e18')); + const tokens = [token.address, ETH_TOKEN_ADDRESS]; + const data = encodeAffiliateFeeTransformerData({ + fees: recipients.map((r, i) => ({ + token: tokens[i], + amount: amounts[i], + recipient: r, + })), + }); + await mintHostTokensAsync(amounts[0]); + await sendEtherAsync(host.address, amounts[1]); + await host + .rawExecuteTransform(transformer.address, hexUtils.random(), randomAddress(), data) + .awaitTransactionSuccessAsync(); + expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES); + expect(await getBalancesAsync(recipients[0])).to.deep.eq({ + tokenBalance: amounts[0], + ethBalance: ZERO_AMOUNT, + }); + expect(await getBalancesAsync(recipients[1])).to.deep.eq({ + tokenBalance: ZERO_AMOUNT, + ethBalance: amounts[1], + }); + }); + + it('can transfer all of a token and ETH', async () => { + const amounts = recipients.map(() => getRandomInteger(1, '1e18')); + const tokens = [token.address, ETH_TOKEN_ADDRESS]; + const data = encodeAffiliateFeeTransformerData({ + fees: recipients.map((r, i) => ({ + token: tokens[i], + amount: MAX_UINT256, + recipient: r, + })), + }); + await mintHostTokensAsync(amounts[0]); + await sendEtherAsync(host.address, amounts[1]); + await host + .rawExecuteTransform(transformer.address, hexUtils.random(), randomAddress(), data) + .awaitTransactionSuccessAsync(); + expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES); + expect(await getBalancesAsync(recipients[0])).to.deep.eq({ + tokenBalance: amounts[0], + ethBalance: ZERO_AMOUNT, + }); + expect(await getBalancesAsync(recipients[1])).to.deep.eq({ + tokenBalance: ZERO_AMOUNT, + ethBalance: amounts[1], + }); + }); + + it('can transfer less than the balance of a token and ETH', async () => { + const amounts = recipients.map(() => getRandomInteger(1, '1e18')); + const tokens = [token.address, ETH_TOKEN_ADDRESS]; + const data = encodeAffiliateFeeTransformerData({ + fees: recipients.map((r, i) => ({ + token: tokens[i], + amount: amounts[i].minus(1), + recipient: r, + })), + }); + await mintHostTokensAsync(amounts[0]); + await sendEtherAsync(host.address, amounts[1]); + await host + .rawExecuteTransform(transformer.address, hexUtils.random(), randomAddress(), data) + .awaitTransactionSuccessAsync(); + expect(await getBalancesAsync(host.address)).to.deep.eq({ + tokenBalance: new BigNumber(1), + ethBalance: new BigNumber(1), + }); + expect(await getBalancesAsync(recipients[0])).to.deep.eq({ + tokenBalance: amounts[0].minus(1), + ethBalance: ZERO_AMOUNT, + }); + expect(await getBalancesAsync(recipients[1])).to.deep.eq({ + tokenBalance: ZERO_AMOUNT, + ethBalance: amounts[1].minus(1), + }); + }); +}); From d44a91d10bb6dc4b5d3be3629b7f0841a91636a9 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Mon, 8 Jun 2020 15:37:51 -0400 Subject: [PATCH 5/6] `@0x/contracts-zero-ex`: Address review feedback. --- .../contracts/src/transformers/AffiliateFeeTransformer.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol index 03bbafaeae..17025ef0b2 100644 --- a/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol @@ -73,9 +73,10 @@ contract AffiliateFeeTransformer is // Transfer tokens to recipients. for (uint256 i = 0; i < fees.length; ++i) { - uint256 amount = fees[i].amount == MAX_UINT256 - ? LibERC20Transformer.getTokenBalanceOf(fees[i].token, address(this)) - : fees[i].amount; + uint256 amount = fees[i].amount; + if (amount == MAX_UINT256) { + amount = LibERC20Transformer.getTokenBalanceOf(fees[i].token, address(this)); + } if (amount != 0) { fees[i].token.transformerTransfer(fees[i].recipient, amount); } From 18fc1d78f42bae5bb38a41498c76d6e3a32dda55 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Mon, 8 Jun 2020 21:34:09 -0400 Subject: [PATCH 6/6] `@0x/contracts-zero-ex`: Rebase --- contracts/zero-ex/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/zero-ex/package.json b/contracts/zero-ex/package.json index febccd8d1c..db3c775e19 100644 --- a/contracts/zero-ex/package.json +++ b/contracts/zero-ex/package.json @@ -40,7 +40,7 @@ "config": { "publicInterfaceContracts": "ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Transformer|IExchange|IFeature|IFlashWallet|IOwnable|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|Ownable|PayTakerTransformer|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json" + "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Transformer|IExchange|IFeature|IFlashWallet|IOwnable|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|Ownable|PayTakerTransformer|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json" }, "repository": { "type": "git",