Address spot check feedback (#251)
* UniswapV3 VIP (#237) * `@0x/contracts-zero-ex`: Add UniswapV3Feature * `@0x/contracts-zero-ex`: Add UniswapV3 VIP `@0x/contract-artifacts`: Regenerate. `@0x/contract-wrappers`: Regenerate. `@0x/asset-swapper`: Add UniswapV3 VIP support. * address review comments and appease linter * `@0x/contracts-zero-ex`: Add UniswapV3Feature tests * Multiplex UniswapV3 (#241) * Add UniswapV3 support to Multiplex batchFill * Add AssetSwapper support for Multiplex UniswapV3 * fix repo scripts that use PKG= env var (#242) Co-authored-by: Lawrence Forman <me@merklejerk.com> * `@0x/asset-swapper`: Adjust uniswap gas overhead Co-authored-by: Lawrence Forman <me@merklejerk.com> Co-authored-by: mzhu25 <mchl.zhu.96@gmail.com> * OTC orders feature (#244) * Add OTC orders feature contracts * Address PR feedback * Remove partial fills for takerSigned variant * Add function to query the min valid nonce * Add ETH support * Tightly pack expiry, nonceBucket, and nonce * Address PR feedback * OTC orders unit tests * Bump prettier version * Skip unnecessary math if takerTokenFillAmount == order.takerAmount * appease CI * Update contract-artifacts and contract-wrappers and CHANGELOGs * `@0x/contracts-zero-ex`: Address spot check feedback * `regen wrappers * prettier * `@0x/asset-swapper`: prettier and tweak gas schedule slightly for uni3 Co-authored-by: Lawrence Forman <me@merklejerk.com> Co-authored-by: mzhu25 <mchl.zhu.96@gmail.com>
This commit is contained in:
@@ -37,6 +37,7 @@ import * as IMultiplexFeature from '../test/generated-artifacts/IMultiplexFeatur
|
||||
import * as INativeOrdersEvents from '../test/generated-artifacts/INativeOrdersEvents.json';
|
||||
import * as INativeOrdersFeature from '../test/generated-artifacts/INativeOrdersFeature.json';
|
||||
import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json';
|
||||
import * as IOtcOrdersFeature from '../test/generated-artifacts/IOtcOrdersFeature.json';
|
||||
import * as IOwnableFeature from '../test/generated-artifacts/IOwnableFeature.json';
|
||||
import * as IPancakeSwapFeature from '../test/generated-artifacts/IPancakeSwapFeature.json';
|
||||
import * as ISimpleFunctionRegistryFeature from '../test/generated-artifacts/ISimpleFunctionRegistryFeature.json';
|
||||
@@ -46,6 +47,8 @@ import * as ITokenSpenderFeature from '../test/generated-artifacts/ITokenSpender
|
||||
import * as ITransformERC20Feature from '../test/generated-artifacts/ITransformERC20Feature.json';
|
||||
import * as IUniswapFeature from '../test/generated-artifacts/IUniswapFeature.json';
|
||||
import * as IUniswapV2Pair from '../test/generated-artifacts/IUniswapV2Pair.json';
|
||||
import * as IUniswapV3Feature from '../test/generated-artifacts/IUniswapV3Feature.json';
|
||||
import * as IUniswapV3Pool from '../test/generated-artifacts/IUniswapV3Pool.json';
|
||||
import * as IZeroEx from '../test/generated-artifacts/IZeroEx.json';
|
||||
import * as LibBootstrap from '../test/generated-artifacts/LibBootstrap.json';
|
||||
import * as LibCommonRichErrors from '../test/generated-artifacts/LibCommonRichErrors.json';
|
||||
@@ -58,6 +61,7 @@ import * as LibMigrate from '../test/generated-artifacts/LibMigrate.json';
|
||||
import * as LibNativeOrder from '../test/generated-artifacts/LibNativeOrder.json';
|
||||
import * as LibNativeOrdersRichErrors from '../test/generated-artifacts/LibNativeOrdersRichErrors.json';
|
||||
import * as LibNativeOrdersStorage from '../test/generated-artifacts/LibNativeOrdersStorage.json';
|
||||
import * as LibOtcOrdersStorage from '../test/generated-artifacts/LibOtcOrdersStorage.json';
|
||||
import * as LibOwnableRichErrors from '../test/generated-artifacts/LibOwnableRichErrors.json';
|
||||
import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorage.json';
|
||||
import * as LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErrors.json';
|
||||
@@ -102,6 +106,7 @@ import * as NativeOrdersFeature from '../test/generated-artifacts/NativeOrdersFe
|
||||
import * as NativeOrdersInfo from '../test/generated-artifacts/NativeOrdersInfo.json';
|
||||
import * as NativeOrdersProtocolFees from '../test/generated-artifacts/NativeOrdersProtocolFees.json';
|
||||
import * as NativeOrdersSettlement from '../test/generated-artifacts/NativeOrdersSettlement.json';
|
||||
import * as OtcOrdersFeature from '../test/generated-artifacts/OtcOrdersFeature.json';
|
||||
import * as OwnableFeature from '../test/generated-artifacts/OwnableFeature.json';
|
||||
import * as PancakeSwapFeature from '../test/generated-artifacts/PancakeSwapFeature.json';
|
||||
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
|
||||
@@ -130,6 +135,7 @@ import * as TestMintableERC20Token from '../test/generated-artifacts/TestMintabl
|
||||
import * as TestMintTokenERC20Transformer from '../test/generated-artifacts/TestMintTokenERC20Transformer.json';
|
||||
import * as TestMooniswap from '../test/generated-artifacts/TestMooniswap.json';
|
||||
import * as TestNativeOrdersFeature from '../test/generated-artifacts/TestNativeOrdersFeature.json';
|
||||
import * as TestNoEthRecipient from '../test/generated-artifacts/TestNoEthRecipient.json';
|
||||
import * as TestOrderSignerRegistryWithContractWallet from '../test/generated-artifacts/TestOrderSignerRegistryWithContractWallet.json';
|
||||
import * as TestPermissionlessTransformerDeployerSuicidal from '../test/generated-artifacts/TestPermissionlessTransformerDeployerSuicidal.json';
|
||||
import * as TestPermissionlessTransformerDeployerTransformer from '../test/generated-artifacts/TestPermissionlessTransformerDeployerTransformer.json';
|
||||
@@ -142,6 +148,9 @@ import * as TestTransformerBase from '../test/generated-artifacts/TestTransforme
|
||||
import * as TestTransformERC20 from '../test/generated-artifacts/TestTransformERC20.json';
|
||||
import * as TestTransformerDeployerTransformer from '../test/generated-artifacts/TestTransformerDeployerTransformer.json';
|
||||
import * as TestTransformerHost from '../test/generated-artifacts/TestTransformerHost.json';
|
||||
import * as TestUniswapV3Factory from '../test/generated-artifacts/TestUniswapV3Factory.json';
|
||||
import * as TestUniswapV3Feature from '../test/generated-artifacts/TestUniswapV3Feature.json';
|
||||
import * as TestUniswapV3Pool from '../test/generated-artifacts/TestUniswapV3Pool.json';
|
||||
import * as TestWeth from '../test/generated-artifacts/TestWeth.json';
|
||||
import * as TestWethTransformerHost from '../test/generated-artifacts/TestWethTransformerHost.json';
|
||||
import * as TestZeroExFeature from '../test/generated-artifacts/TestZeroExFeature.json';
|
||||
@@ -149,6 +158,7 @@ import * as Transformer from '../test/generated-artifacts/Transformer.json';
|
||||
import * as TransformERC20Feature from '../test/generated-artifacts/TransformERC20Feature.json';
|
||||
import * as TransformerDeployer from '../test/generated-artifacts/TransformerDeployer.json';
|
||||
import * as UniswapFeature from '../test/generated-artifacts/UniswapFeature.json';
|
||||
import * as UniswapV3Feature from '../test/generated-artifacts/UniswapV3Feature.json';
|
||||
import * as WethTransformer from '../test/generated-artifacts/WethTransformer.json';
|
||||
import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
|
||||
import * as ZeroExOptimized from '../test/generated-artifacts/ZeroExOptimized.json';
|
||||
@@ -181,11 +191,13 @@ export const artifacts = {
|
||||
MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
|
||||
MultiplexFeature: MultiplexFeature as ContractArtifact,
|
||||
NativeOrdersFeature: NativeOrdersFeature as ContractArtifact,
|
||||
OtcOrdersFeature: OtcOrdersFeature as ContractArtifact,
|
||||
OwnableFeature: OwnableFeature as ContractArtifact,
|
||||
PancakeSwapFeature: PancakeSwapFeature as ContractArtifact,
|
||||
SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact,
|
||||
TransformERC20Feature: TransformERC20Feature as ContractArtifact,
|
||||
UniswapFeature: UniswapFeature as ContractArtifact,
|
||||
UniswapV3Feature: UniswapV3Feature as ContractArtifact,
|
||||
IBatchFillNativeOrdersFeature: IBatchFillNativeOrdersFeature as ContractArtifact,
|
||||
IBootstrapFeature: IBootstrapFeature as ContractArtifact,
|
||||
IFeature: IFeature as ContractArtifact,
|
||||
@@ -194,12 +206,14 @@ export const artifacts = {
|
||||
IMultiplexFeature: IMultiplexFeature as ContractArtifact,
|
||||
INativeOrdersEvents: INativeOrdersEvents as ContractArtifact,
|
||||
INativeOrdersFeature: INativeOrdersFeature as ContractArtifact,
|
||||
IOtcOrdersFeature: IOtcOrdersFeature as ContractArtifact,
|
||||
IOwnableFeature: IOwnableFeature as ContractArtifact,
|
||||
IPancakeSwapFeature: IPancakeSwapFeature as ContractArtifact,
|
||||
ISimpleFunctionRegistryFeature: ISimpleFunctionRegistryFeature as ContractArtifact,
|
||||
ITokenSpenderFeature: ITokenSpenderFeature as ContractArtifact,
|
||||
ITransformERC20Feature: ITransformERC20Feature as ContractArtifact,
|
||||
IUniswapFeature: IUniswapFeature as ContractArtifact,
|
||||
IUniswapV3Feature: IUniswapV3Feature as ContractArtifact,
|
||||
LibNativeOrder: LibNativeOrder as ContractArtifact,
|
||||
LibSignature: LibSignature as ContractArtifact,
|
||||
NativeOrdersCancellation: NativeOrdersCancellation as ContractArtifact,
|
||||
@@ -219,6 +233,7 @@ export const artifacts = {
|
||||
LibMigrate: LibMigrate as ContractArtifact,
|
||||
LibMetaTransactionsStorage: LibMetaTransactionsStorage as ContractArtifact,
|
||||
LibNativeOrdersStorage: LibNativeOrdersStorage as ContractArtifact,
|
||||
LibOtcOrdersStorage: LibOtcOrdersStorage as ContractArtifact,
|
||||
LibOwnableStorage: LibOwnableStorage as ContractArtifact,
|
||||
LibProxyStorage: LibProxyStorage as ContractArtifact,
|
||||
LibReentrancyGuardStorage: LibReentrancyGuardStorage as ContractArtifact,
|
||||
@@ -260,6 +275,7 @@ export const artifacts = {
|
||||
ILiquidityProvider: ILiquidityProvider as ContractArtifact,
|
||||
IMooniswapPool: IMooniswapPool as ContractArtifact,
|
||||
IUniswapV2Pair: IUniswapV2Pair as ContractArtifact,
|
||||
IUniswapV3Pool: IUniswapV3Pool as ContractArtifact,
|
||||
IERC20Bridge: IERC20Bridge as ContractArtifact,
|
||||
IStaking: IStaking as ContractArtifact,
|
||||
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
|
||||
@@ -285,6 +301,7 @@ export const artifacts = {
|
||||
TestMintableERC20Token: TestMintableERC20Token as ContractArtifact,
|
||||
TestMooniswap: TestMooniswap as ContractArtifact,
|
||||
TestNativeOrdersFeature: TestNativeOrdersFeature as ContractArtifact,
|
||||
TestNoEthRecipient: TestNoEthRecipient as ContractArtifact,
|
||||
TestOrderSignerRegistryWithContractWallet: TestOrderSignerRegistryWithContractWallet as ContractArtifact,
|
||||
TestPermissionlessTransformerDeployerSuicidal: TestPermissionlessTransformerDeployerSuicidal as ContractArtifact,
|
||||
TestPermissionlessTransformerDeployerTransformer: TestPermissionlessTransformerDeployerTransformer as ContractArtifact,
|
||||
@@ -297,6 +314,9 @@ export const artifacts = {
|
||||
TestTransformerBase: TestTransformerBase as ContractArtifact,
|
||||
TestTransformerDeployerTransformer: TestTransformerDeployerTransformer as ContractArtifact,
|
||||
TestTransformerHost: TestTransformerHost as ContractArtifact,
|
||||
TestUniswapV3Factory: TestUniswapV3Factory as ContractArtifact,
|
||||
TestUniswapV3Feature: TestUniswapV3Feature as ContractArtifact,
|
||||
TestUniswapV3Pool: TestUniswapV3Pool as ContractArtifact,
|
||||
TestWeth: TestWeth as ContractArtifact,
|
||||
TestWethTransformerHost: TestWethTransformerHost as ContractArtifact,
|
||||
TestZeroExFeature: TestZeroExFeature as ContractArtifact,
|
||||
|
@@ -141,7 +141,12 @@ blockchainTests.resets('BatchFillNativeOrdersFeature', env => {
|
||||
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||
const value = testUtils.protocolFee.times(orders.length);
|
||||
const tx = await feature
|
||||
.batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), false)
|
||||
.batchFillLimitOrders(
|
||||
orders,
|
||||
signatures,
|
||||
orders.map(order => order.takerAmount),
|
||||
false,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: taker, value });
|
||||
const [orderInfos] = await zeroEx.batchGetLimitOrderRelevantStates(orders, signatures).callAsync();
|
||||
orderInfos.map((orderInfo, i) =>
|
||||
@@ -192,7 +197,12 @@ blockchainTests.resets('BatchFillNativeOrdersFeature', env => {
|
||||
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||
const value = testUtils.protocolFee.times(orders.length).plus(420);
|
||||
const tx = await feature
|
||||
.batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), false)
|
||||
.batchFillLimitOrders(
|
||||
orders,
|
||||
signatures,
|
||||
orders.map(order => order.takerAmount),
|
||||
false,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: taker, value });
|
||||
const [orderInfos] = await zeroEx.batchGetLimitOrderRelevantStates(orders, signatures).callAsync();
|
||||
orderInfos.map((orderInfo, i) =>
|
||||
@@ -219,7 +229,12 @@ blockchainTests.resets('BatchFillNativeOrdersFeature', env => {
|
||||
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||
const value = testUtils.protocolFee.times(orders.length);
|
||||
const tx = await feature
|
||||
.batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), false)
|
||||
.batchFillLimitOrders(
|
||||
orders,
|
||||
signatures,
|
||||
orders.map(order => order.takerAmount),
|
||||
false,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: taker, value });
|
||||
const [orderInfos] = await zeroEx.batchGetLimitOrderRelevantStates(orders, signatures).callAsync();
|
||||
const [expiredOrderInfo, ...filledOrderInfos] = orderInfos;
|
||||
@@ -250,7 +265,12 @@ blockchainTests.resets('BatchFillNativeOrdersFeature', env => {
|
||||
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||
const value = testUtils.protocolFee.times(orders.length);
|
||||
const tx = await feature
|
||||
.batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), true)
|
||||
.batchFillLimitOrders(
|
||||
orders,
|
||||
signatures,
|
||||
orders.map(order => order.takerAmount),
|
||||
true,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: taker, value });
|
||||
const [orderInfos] = await zeroEx.batchGetLimitOrderRelevantStates(orders, signatures).callAsync();
|
||||
orderInfos.map((orderInfo, i) =>
|
||||
@@ -277,7 +297,12 @@ blockchainTests.resets('BatchFillNativeOrdersFeature', env => {
|
||||
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||
const value = testUtils.protocolFee.times(orders.length);
|
||||
const tx = feature
|
||||
.batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), true)
|
||||
.batchFillLimitOrders(
|
||||
orders,
|
||||
signatures,
|
||||
orders.map(order => order.takerAmount),
|
||||
true,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: taker, value });
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.BatchFillIncompleteError(
|
||||
@@ -299,7 +324,12 @@ blockchainTests.resets('BatchFillNativeOrdersFeature', env => {
|
||||
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||
const value = testUtils.protocolFee.times(orders.length);
|
||||
const tx = feature
|
||||
.batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), true)
|
||||
.batchFillLimitOrders(
|
||||
orders,
|
||||
signatures,
|
||||
orders.map(order => order.takerAmount),
|
||||
true,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: taker, value });
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.BatchFillIncompleteError(
|
||||
@@ -337,7 +367,12 @@ blockchainTests.resets('BatchFillNativeOrdersFeature', env => {
|
||||
);
|
||||
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||
const tx = await feature
|
||||
.batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), false)
|
||||
.batchFillRfqOrders(
|
||||
orders,
|
||||
signatures,
|
||||
orders.map(order => order.takerAmount),
|
||||
false,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
const [orderInfos] = await zeroEx.batchGetRfqOrderRelevantStates(orders, signatures).callAsync();
|
||||
orderInfos.map((orderInfo, i) =>
|
||||
@@ -388,7 +423,12 @@ blockchainTests.resets('BatchFillNativeOrdersFeature', env => {
|
||||
);
|
||||
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||
const tx = await feature
|
||||
.batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), false)
|
||||
.batchFillRfqOrders(
|
||||
orders,
|
||||
signatures,
|
||||
orders.map(order => order.takerAmount),
|
||||
false,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
const [orderInfos] = await zeroEx.batchGetRfqOrderRelevantStates(orders, signatures).callAsync();
|
||||
const [expiredOrderInfo, ...filledOrderInfos] = orderInfos;
|
||||
@@ -418,7 +458,12 @@ blockchainTests.resets('BatchFillNativeOrdersFeature', env => {
|
||||
);
|
||||
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||
const tx = await feature
|
||||
.batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), true)
|
||||
.batchFillRfqOrders(
|
||||
orders,
|
||||
signatures,
|
||||
orders.map(order => order.takerAmount),
|
||||
true,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
const [orderInfos] = await zeroEx.batchGetRfqOrderRelevantStates(orders, signatures).callAsync();
|
||||
orderInfos.map((orderInfo, i) =>
|
||||
@@ -444,7 +489,12 @@ blockchainTests.resets('BatchFillNativeOrdersFeature', env => {
|
||||
);
|
||||
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||
const tx = feature
|
||||
.batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), true)
|
||||
.batchFillRfqOrders(
|
||||
orders,
|
||||
signatures,
|
||||
orders.map(order => order.takerAmount),
|
||||
true,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.BatchFillIncompleteError(
|
||||
@@ -465,7 +515,12 @@ blockchainTests.resets('BatchFillNativeOrdersFeature', env => {
|
||||
);
|
||||
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||
const tx = feature
|
||||
.batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), true)
|
||||
.batchFillRfqOrders(
|
||||
orders,
|
||||
signatures,
|
||||
orders.map(order => order.takerAmount),
|
||||
true,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.BatchFillIncompleteError(
|
||||
|
@@ -304,7 +304,10 @@ blockchainTests.fork.skip('Multiplex feature', env => {
|
||||
{
|
||||
name: 'transformations',
|
||||
type: 'tuple[]',
|
||||
components: [{ name: 'deploymentNonce', type: 'uint32' }, { name: 'data', type: 'bytes' }],
|
||||
components: [
|
||||
{ name: 'deploymentNonce', type: 'uint32' },
|
||||
{ name: 'data', type: 'bytes' },
|
||||
],
|
||||
},
|
||||
{ name: 'ethValue', type: 'uint256' },
|
||||
]);
|
||||
@@ -439,7 +442,10 @@ blockchainTests.fork.skip('Multiplex feature', env => {
|
||||
{
|
||||
name: 'transformations',
|
||||
type: 'tuple[]',
|
||||
components: [{ name: 'deploymentNonce', type: 'uint32' }, { name: 'data', type: 'bytes' }],
|
||||
components: [
|
||||
{ name: 'deploymentNonce', type: 'uint32' },
|
||||
{ name: 'data', type: 'bytes' },
|
||||
],
|
||||
},
|
||||
{ name: 'ethValue', type: 'uint256' },
|
||||
]);
|
||||
@@ -460,7 +466,10 @@ blockchainTests.fork.skip('Multiplex feature', env => {
|
||||
{
|
||||
name: 'calls',
|
||||
type: 'tuple[]',
|
||||
components: [{ name: 'selector', type: 'bytes4' }, { name: 'data', type: 'bytes' }],
|
||||
components: [
|
||||
{ name: 'selector', type: 'bytes4' },
|
||||
{ name: 'data', type: 'bytes' },
|
||||
],
|
||||
},
|
||||
{ name: 'ethValue', type: 'uint256' },
|
||||
]);
|
||||
|
753
contracts/zero-ex/test/features/otc_orders_test.ts
Normal file
753
contracts/zero-ex/test/features/otc_orders_test.ts
Normal file
@@ -0,0 +1,753 @@
|
||||
import { blockchainTests, constants, describe, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
||||
import { OrderStatus, OtcOrder, RevertErrors, SignatureType } from '@0x/protocol-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { IOwnableFeatureContract, IZeroExContract, IZeroExEvents } from '../../src/wrappers';
|
||||
import { artifacts } from '../artifacts';
|
||||
import { abis } from '../utils/abis';
|
||||
import { fullMigrateAsync } from '../utils/migration';
|
||||
import {
|
||||
computeOtcOrderFilledAmounts,
|
||||
createExpiry,
|
||||
getRandomOtcOrder,
|
||||
NativeOrdersTestEnvironment,
|
||||
} from '../utils/orders';
|
||||
import {
|
||||
OtcOrdersFeatureContract,
|
||||
TestMintableERC20TokenContract,
|
||||
TestOrderSignerRegistryWithContractWalletContract,
|
||||
TestWethContract,
|
||||
} from '../wrappers';
|
||||
|
||||
blockchainTests.resets('OtcOrdersFeature', env => {
|
||||
const { NULL_ADDRESS, MAX_UINT256, ZERO_AMOUNT: ZERO } = constants;
|
||||
let maker: string;
|
||||
let taker: string;
|
||||
let notMaker: string;
|
||||
let notTaker: string;
|
||||
let contractWalletOwner: string;
|
||||
let contractWalletSigner: string;
|
||||
let txOrigin: string;
|
||||
let notTxOrigin: string;
|
||||
let zeroEx: IZeroExContract;
|
||||
let verifyingContract: string;
|
||||
let makerToken: TestMintableERC20TokenContract;
|
||||
let takerToken: TestMintableERC20TokenContract;
|
||||
let wethToken: TestWethContract;
|
||||
let contractWallet: TestOrderSignerRegistryWithContractWalletContract;
|
||||
let testUtils: NativeOrdersTestEnvironment;
|
||||
|
||||
before(async () => {
|
||||
// Useful for ETH balance accounting
|
||||
const txDefaults = { ...env.txDefaults, gasPrice: 0 };
|
||||
let owner;
|
||||
[
|
||||
owner,
|
||||
maker,
|
||||
taker,
|
||||
notMaker,
|
||||
notTaker,
|
||||
contractWalletOwner,
|
||||
contractWalletSigner,
|
||||
txOrigin,
|
||||
notTxOrigin,
|
||||
] = await env.getAccountAddressesAsync();
|
||||
[makerToken, takerToken] = await Promise.all(
|
||||
[...new Array(2)].map(async () =>
|
||||
TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestMintableERC20Token,
|
||||
env.provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
),
|
||||
),
|
||||
);
|
||||
wethToken = await TestWethContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestWeth,
|
||||
env.provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
zeroEx = await fullMigrateAsync(owner, env.provider, txDefaults, {}, { wethAddress: wethToken.address });
|
||||
const otcFeatureImpl = await OtcOrdersFeatureContract.deployFrom0xArtifactAsync(
|
||||
artifacts.OtcOrdersFeature,
|
||||
env.provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
zeroEx.address,
|
||||
wethToken.address,
|
||||
);
|
||||
await new IOwnableFeatureContract(zeroEx.address, env.provider, txDefaults, abis)
|
||||
.migrate(otcFeatureImpl.address, otcFeatureImpl.migrate().getABIEncodedTransactionData(), owner)
|
||||
.awaitTransactionSuccessAsync();
|
||||
verifyingContract = zeroEx.address;
|
||||
|
||||
await Promise.all([
|
||||
makerToken.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({ from: maker }),
|
||||
makerToken.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({ from: notMaker }),
|
||||
takerToken.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({ from: taker }),
|
||||
takerToken.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({ from: notTaker }),
|
||||
wethToken.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({ from: maker }),
|
||||
wethToken.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({ from: notMaker }),
|
||||
wethToken.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({ from: taker }),
|
||||
wethToken.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({ from: notTaker }),
|
||||
]);
|
||||
|
||||
// contract wallet for signer delegation
|
||||
contractWallet = await TestOrderSignerRegistryWithContractWalletContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestOrderSignerRegistryWithContractWallet,
|
||||
env.provider,
|
||||
{
|
||||
from: contractWalletOwner,
|
||||
},
|
||||
artifacts,
|
||||
zeroEx.address,
|
||||
);
|
||||
|
||||
await contractWallet
|
||||
.approveERC20(makerToken.address, zeroEx.address, MAX_UINT256)
|
||||
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
|
||||
await contractWallet
|
||||
.approveERC20(takerToken.address, zeroEx.address, MAX_UINT256)
|
||||
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
|
||||
|
||||
testUtils = new NativeOrdersTestEnvironment(maker, taker, makerToken, takerToken, zeroEx, ZERO, ZERO, env);
|
||||
});
|
||||
|
||||
function getTestOtcOrder(fields: Partial<OtcOrder> = {}): OtcOrder {
|
||||
return getRandomOtcOrder({
|
||||
maker,
|
||||
verifyingContract,
|
||||
chainId: 1337,
|
||||
takerToken: takerToken.address,
|
||||
makerToken: makerToken.address,
|
||||
taker: NULL_ADDRESS,
|
||||
txOrigin: taker,
|
||||
...fields,
|
||||
});
|
||||
}
|
||||
|
||||
describe('getOtcOrderHash()', () => {
|
||||
it('returns the correct hash', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
const hash = await zeroEx.getOtcOrderHash(order).callAsync();
|
||||
expect(hash).to.eq(order.getHash());
|
||||
});
|
||||
});
|
||||
|
||||
describe('lastOtcTxOriginNonce()', () => {
|
||||
it('returns 0 if bucket is unused', async () => {
|
||||
const nonce = await zeroEx.lastOtcTxOriginNonce(taker, ZERO).callAsync();
|
||||
expect(nonce).to.bignumber.eq(0);
|
||||
});
|
||||
it('returns the last nonce used in a bucket', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
await testUtils.fillOtcOrderAsync(order);
|
||||
const nonce = await zeroEx.lastOtcTxOriginNonce(taker, order.nonceBucket).callAsync();
|
||||
expect(nonce).to.bignumber.eq(order.nonce);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOtcOrderInfo()', () => {
|
||||
it('unfilled order', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
const info = await zeroEx.getOtcOrderInfo(order).callAsync();
|
||||
expect(info).to.deep.equal({
|
||||
status: OrderStatus.Fillable,
|
||||
orderHash: order.getHash(),
|
||||
});
|
||||
});
|
||||
|
||||
it('unfilled expired order', async () => {
|
||||
const expiry = createExpiry(-60);
|
||||
const order = getTestOtcOrder({ expiry });
|
||||
const info = await zeroEx.getOtcOrderInfo(order).callAsync();
|
||||
expect(info).to.deep.equal({
|
||||
status: OrderStatus.Expired,
|
||||
orderHash: order.getHash(),
|
||||
});
|
||||
});
|
||||
|
||||
it('filled then expired order', async () => {
|
||||
const expiry = createExpiry(60);
|
||||
const order = getTestOtcOrder({ expiry });
|
||||
await testUtils.fillOtcOrderAsync(order);
|
||||
// Advance time to expire the order.
|
||||
await env.web3Wrapper.increaseTimeAsync(61);
|
||||
const info = await zeroEx.getOtcOrderInfo(order).callAsync();
|
||||
expect(info).to.deep.equal({
|
||||
status: OrderStatus.Invalid,
|
||||
orderHash: order.getHash(),
|
||||
});
|
||||
});
|
||||
|
||||
it('filled order', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
// Fill the order first.
|
||||
await testUtils.fillOtcOrderAsync(order);
|
||||
const info = await zeroEx.getOtcOrderInfo(order).callAsync();
|
||||
expect(info).to.deep.equal({
|
||||
status: OrderStatus.Invalid,
|
||||
orderHash: order.getHash(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function assertExpectedFinalBalancesFromOtcOrderFillAsync(
|
||||
order: OtcOrder,
|
||||
takerTokenFillAmount: BigNumber = order.takerAmount,
|
||||
): Promise<void> {
|
||||
const { makerTokenFilledAmount, takerTokenFilledAmount } = computeOtcOrderFilledAmounts(
|
||||
order,
|
||||
takerTokenFillAmount,
|
||||
);
|
||||
const makerBalance = await new TestMintableERC20TokenContract(order.takerToken, env.provider)
|
||||
.balanceOf(order.maker)
|
||||
.callAsync();
|
||||
const takerBalance = await new TestMintableERC20TokenContract(order.makerToken, env.provider)
|
||||
.balanceOf(order.taker !== NULL_ADDRESS ? order.taker : taker)
|
||||
.callAsync();
|
||||
expect(makerBalance, 'maker balance').to.bignumber.eq(takerTokenFilledAmount);
|
||||
expect(takerBalance, 'taker balance').to.bignumber.eq(makerTokenFilledAmount);
|
||||
}
|
||||
|
||||
describe('fillOtcOrder()', () => {
|
||||
it('can fully fill an order', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
const receipt = await testUtils.fillOtcOrderAsync(order);
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order);
|
||||
});
|
||||
|
||||
it('can partially fill an order', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
const fillAmount = order.takerAmount.minus(1);
|
||||
const receipt = await testUtils.fillOtcOrderAsync(order, fillAmount);
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order, fillAmount)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order, fillAmount);
|
||||
});
|
||||
|
||||
it('clamps fill amount to remaining available', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
const fillAmount = order.takerAmount.plus(1);
|
||||
const receipt = await testUtils.fillOtcOrderAsync(order, fillAmount);
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order, fillAmount)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order, fillAmount);
|
||||
});
|
||||
|
||||
it('cannot fill an order with wrong tx.origin', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
const tx = testUtils.fillOtcOrderAsync(order, order.takerAmount, notTaker);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableByOriginError(order.getHash(), notTaker, taker),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot fill an order with wrong taker', async () => {
|
||||
const order = getTestOtcOrder({ taker: notTaker });
|
||||
const tx = testUtils.fillOtcOrderAsync(order);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableByTakerError(order.getHash(), taker, notTaker),
|
||||
);
|
||||
});
|
||||
|
||||
it('can fill an order from a different tx.origin if registered', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
await zeroEx.registerAllowedRfqOrigins([notTaker], true).awaitTransactionSuccessAsync({ from: taker });
|
||||
return testUtils.fillOtcOrderAsync(order, order.takerAmount, notTaker);
|
||||
});
|
||||
|
||||
it('cannot fill an order with registered then unregistered tx.origin', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
await zeroEx.registerAllowedRfqOrigins([notTaker], true).awaitTransactionSuccessAsync({ from: taker });
|
||||
await zeroEx.registerAllowedRfqOrigins([notTaker], false).awaitTransactionSuccessAsync({ from: taker });
|
||||
const tx = testUtils.fillOtcOrderAsync(order, order.takerAmount, notTaker);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableByOriginError(order.getHash(), notTaker, taker),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot fill an order with a zero tx.origin', async () => {
|
||||
const order = getTestOtcOrder({ txOrigin: NULL_ADDRESS });
|
||||
const tx = testUtils.fillOtcOrderAsync(order);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableByOriginError(order.getHash(), taker, NULL_ADDRESS),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot fill an expired order', async () => {
|
||||
const order = getTestOtcOrder({ expiry: createExpiry(-60) });
|
||||
const tx = testUtils.fillOtcOrderAsync(order);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Expired),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot fill order with bad signature', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
// Overwrite chainId to result in a different hash and therefore different
|
||||
// signature.
|
||||
const tx = testUtils.fillOtcOrderAsync(order.clone({ chainId: 1234 }));
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotSignedByMakerError(order.getHash(), undefined, order.maker),
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if ETH is attached', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
await testUtils.prepareBalancesForOrdersAsync([order], taker);
|
||||
const tx = zeroEx
|
||||
.fillOtcOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount, false)
|
||||
.awaitTransactionSuccessAsync({ from: taker, value: 1 });
|
||||
// This will revert at the language level because the fill function is not payable.
|
||||
return expect(tx).to.be.rejectedWith('revert');
|
||||
});
|
||||
|
||||
it('cannot fill the same order twice', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
await testUtils.fillOtcOrderAsync(order);
|
||||
const tx = testUtils.fillOtcOrderAsync(order);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Invalid),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot fill two orders with the same nonceBucket and nonce', async () => {
|
||||
const order1 = getTestOtcOrder();
|
||||
await testUtils.fillOtcOrderAsync(order1);
|
||||
const order2 = getTestOtcOrder({ nonceBucket: order1.nonceBucket, nonce: order1.nonce });
|
||||
const tx = testUtils.fillOtcOrderAsync(order2);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableError(order2.getHash(), OrderStatus.Invalid),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot fill an order whose nonce is less than the nonce last used in that bucket', async () => {
|
||||
const order1 = getTestOtcOrder();
|
||||
await testUtils.fillOtcOrderAsync(order1);
|
||||
const order2 = getTestOtcOrder({ nonceBucket: order1.nonceBucket, nonce: order1.nonce.minus(1) });
|
||||
const tx = testUtils.fillOtcOrderAsync(order2);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableError(order2.getHash(), OrderStatus.Invalid),
|
||||
);
|
||||
});
|
||||
|
||||
it('can fill two orders that use the same nonce bucket and increasing nonces', async () => {
|
||||
const order1 = getTestOtcOrder();
|
||||
const tx1 = await testUtils.fillOtcOrderAsync(order1);
|
||||
verifyEventsFromLogs(
|
||||
tx1.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order1)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
const order2 = getTestOtcOrder({ nonceBucket: order1.nonceBucket, nonce: order1.nonce.plus(1) });
|
||||
const tx2 = await testUtils.fillOtcOrderAsync(order2);
|
||||
verifyEventsFromLogs(
|
||||
tx2.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order2)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
});
|
||||
|
||||
it('can fill two orders that use the same nonce but different nonce buckets', async () => {
|
||||
const order1 = getTestOtcOrder();
|
||||
const tx1 = await testUtils.fillOtcOrderAsync(order1);
|
||||
verifyEventsFromLogs(
|
||||
tx1.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order1)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
const order2 = getTestOtcOrder({ nonce: order1.nonce });
|
||||
const tx2 = await testUtils.fillOtcOrderAsync(order2);
|
||||
verifyEventsFromLogs(
|
||||
tx2.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order2)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
});
|
||||
|
||||
it('can fill a WETH buy order and receive ETH', async () => {
|
||||
const takerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
|
||||
const order = getTestOtcOrder({ makerToken: wethToken.address, makerAmount: new BigNumber('1e18') });
|
||||
await wethToken.deposit().awaitTransactionSuccessAsync({ from: maker, value: order.makerAmount });
|
||||
const receipt = await testUtils.fillOtcOrderAsync(order, order.takerAmount, taker, true);
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
const takerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(taker);
|
||||
expect(takerEthBalanceAfter.minus(takerEthBalanceBefore)).to.bignumber.equal(order.makerAmount);
|
||||
});
|
||||
|
||||
it('reverts if `unwrapWeth` is true but maker token is not WETH', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
const tx = testUtils.fillOtcOrderAsync(order, order.takerAmount, taker, true);
|
||||
return expect(tx).to.revertWith('OtcOrdersFeature/INVALID_UNWRAP_WETH');
|
||||
});
|
||||
|
||||
it('allows for fills on orders signed by a approved signer', async () => {
|
||||
const order = getTestOtcOrder({ maker: contractWallet.address });
|
||||
const sig = await order.getSignatureWithProviderAsync(
|
||||
env.provider,
|
||||
SignatureType.EthSign,
|
||||
contractWalletSigner,
|
||||
);
|
||||
// covers taker
|
||||
await testUtils.prepareBalancesForOrdersAsync([order]);
|
||||
// need to provide contract wallet with a balance
|
||||
await makerToken.mint(contractWallet.address, order.makerAmount).awaitTransactionSuccessAsync();
|
||||
// allow signer
|
||||
await contractWallet
|
||||
.registerAllowedOrderSigner(contractWalletSigner, true)
|
||||
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
|
||||
// fill should succeed
|
||||
const receipt = await zeroEx
|
||||
.fillOtcOrder(order, sig, order.takerAmount, false)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order);
|
||||
});
|
||||
|
||||
it('disallows fills if the signer is revoked', async () => {
|
||||
const order = getTestOtcOrder({ maker: contractWallet.address });
|
||||
const sig = await order.getSignatureWithProviderAsync(
|
||||
env.provider,
|
||||
SignatureType.EthSign,
|
||||
contractWalletSigner,
|
||||
);
|
||||
// covers taker
|
||||
await testUtils.prepareBalancesForOrdersAsync([order]);
|
||||
// need to provide contract wallet with a balance
|
||||
await makerToken.mint(contractWallet.address, order.makerAmount).awaitTransactionSuccessAsync();
|
||||
// first allow signer
|
||||
await contractWallet
|
||||
.registerAllowedOrderSigner(contractWalletSigner, true)
|
||||
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
|
||||
// then disallow signer
|
||||
await contractWallet
|
||||
.registerAllowedOrderSigner(contractWalletSigner, false)
|
||||
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
|
||||
// fill should revert
|
||||
const tx = zeroEx
|
||||
.fillOtcOrder(order, sig, order.takerAmount, false)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotSignedByMakerError(
|
||||
order.getHash(),
|
||||
contractWalletSigner,
|
||||
order.maker,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it(`doesn't allow fills with an unapproved signer`, async () => {
|
||||
const order = getTestOtcOrder({ maker: contractWallet.address });
|
||||
const sig = await order.getSignatureWithProviderAsync(env.provider, SignatureType.EthSign, maker);
|
||||
// covers taker
|
||||
await testUtils.prepareBalancesForOrdersAsync([order]);
|
||||
// need to provide contract wallet with a balance
|
||||
await makerToken.mint(contractWallet.address, order.makerAmount).awaitTransactionSuccessAsync();
|
||||
// fill should revert
|
||||
const tx = zeroEx
|
||||
.fillOtcOrder(order, sig, order.takerAmount, false)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotSignedByMakerError(order.getHash(), maker, order.maker),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('fillOtcOrderWithEth()', () => {
|
||||
it('Can fill an order with ETH', async () => {
|
||||
const order = getTestOtcOrder({ takerToken: wethToken.address });
|
||||
const receipt = await testUtils.fillOtcOrderWithEthAsync(order);
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order);
|
||||
});
|
||||
it('Can partially fill an order with ETH', async () => {
|
||||
const order = getTestOtcOrder({ takerToken: wethToken.address });
|
||||
const fillAmount = order.takerAmount.minus(1);
|
||||
const receipt = await testUtils.fillOtcOrderWithEthAsync(order, fillAmount);
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order, fillAmount)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order, fillAmount);
|
||||
});
|
||||
it('Can refund excess ETH is msg.value > order.takerAmount', async () => {
|
||||
const order = getTestOtcOrder({ takerToken: wethToken.address });
|
||||
const fillAmount = order.takerAmount.plus(420);
|
||||
const takerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
|
||||
const receipt = await testUtils.fillOtcOrderWithEthAsync(order, fillAmount);
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
const takerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(taker);
|
||||
expect(takerEthBalanceBefore.minus(takerEthBalanceAfter)).to.bignumber.equal(order.takerAmount);
|
||||
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order);
|
||||
});
|
||||
it('Cannot fill an order if taker token is not WETH', async () => {
|
||||
const order = getTestOtcOrder();
|
||||
const tx = testUtils.fillOtcOrderWithEthAsync(order);
|
||||
return expect(tx).to.revertWith('OtcOrdersFeature/INVALID_WRAP_ETH');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fillTakerSignedOtcOrder()', () => {
|
||||
it('can fully fill an order', async () => {
|
||||
const order = getTestOtcOrder({ taker, txOrigin });
|
||||
const receipt = await testUtils.fillTakerSignedOtcOrderAsync(order);
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order);
|
||||
});
|
||||
|
||||
it('cannot fill an order with wrong tx.origin', async () => {
|
||||
const order = getTestOtcOrder({ taker, txOrigin });
|
||||
const tx = testUtils.fillTakerSignedOtcOrderAsync(order, notTxOrigin);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableByOriginError(order.getHash(), notTxOrigin, txOrigin),
|
||||
);
|
||||
});
|
||||
|
||||
it('can fill an order from a different tx.origin if registered', async () => {
|
||||
const order = getTestOtcOrder({ taker, txOrigin });
|
||||
await zeroEx
|
||||
.registerAllowedRfqOrigins([notTxOrigin], true)
|
||||
.awaitTransactionSuccessAsync({ from: txOrigin });
|
||||
return testUtils.fillTakerSignedOtcOrderAsync(order, notTxOrigin);
|
||||
});
|
||||
|
||||
it('cannot fill an order with registered then unregistered tx.origin', async () => {
|
||||
const order = getTestOtcOrder({ taker, txOrigin });
|
||||
await zeroEx
|
||||
.registerAllowedRfqOrigins([notTxOrigin], true)
|
||||
.awaitTransactionSuccessAsync({ from: txOrigin });
|
||||
await zeroEx
|
||||
.registerAllowedRfqOrigins([notTxOrigin], false)
|
||||
.awaitTransactionSuccessAsync({ from: txOrigin });
|
||||
const tx = testUtils.fillTakerSignedOtcOrderAsync(order, notTxOrigin);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableByOriginError(order.getHash(), notTxOrigin, txOrigin),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot fill an order with a zero tx.origin', async () => {
|
||||
const order = getTestOtcOrder({ taker, txOrigin: NULL_ADDRESS });
|
||||
const tx = testUtils.fillTakerSignedOtcOrderAsync(order, txOrigin);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableByOriginError(order.getHash(), txOrigin, NULL_ADDRESS),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot fill an expired order', async () => {
|
||||
const order = getTestOtcOrder({ taker, txOrigin, expiry: createExpiry(-60) });
|
||||
const tx = testUtils.fillTakerSignedOtcOrderAsync(order);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Expired),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot fill an order with bad taker signature', async () => {
|
||||
const order = getTestOtcOrder({ taker, txOrigin });
|
||||
const tx = testUtils.fillTakerSignedOtcOrderAsync(order, txOrigin, notTaker);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotSignedByTakerError(order.getHash(), notTaker, taker),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot fill order with bad maker signature', async () => {
|
||||
const order = getTestOtcOrder({ taker, txOrigin });
|
||||
// Overwrite chainId to result in a different hash and therefore different
|
||||
// signature.
|
||||
const tx = testUtils.fillTakerSignedOtcOrderAsync(order.clone({ chainId: 1234 }));
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotSignedByMakerError(order.getHash(), undefined, order.maker),
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if ETH is attached', async () => {
|
||||
const order = getTestOtcOrder({ taker, txOrigin });
|
||||
await testUtils.prepareBalancesForOrdersAsync([order], taker);
|
||||
const tx = zeroEx
|
||||
.fillTakerSignedOtcOrder(
|
||||
order,
|
||||
await order.getSignatureWithProviderAsync(env.provider),
|
||||
await order.getSignatureWithProviderAsync(env.provider, SignatureType.EthSign, taker),
|
||||
false,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: txOrigin, value: 1 });
|
||||
// This will revert at the language level because the fill function is not payable.
|
||||
return expect(tx).to.be.rejectedWith('revert');
|
||||
});
|
||||
|
||||
it('cannot fill the same order twice', async () => {
|
||||
const order = getTestOtcOrder({ taker, txOrigin });
|
||||
await testUtils.fillTakerSignedOtcOrderAsync(order);
|
||||
const tx = testUtils.fillTakerSignedOtcOrderAsync(order);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Invalid),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot fill two orders with the same nonceBucket and nonce', async () => {
|
||||
const order1 = getTestOtcOrder({ taker, txOrigin });
|
||||
await testUtils.fillTakerSignedOtcOrderAsync(order1);
|
||||
const order2 = getTestOtcOrder({ taker, txOrigin, nonceBucket: order1.nonceBucket, nonce: order1.nonce });
|
||||
const tx = testUtils.fillTakerSignedOtcOrderAsync(order2);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableError(order2.getHash(), OrderStatus.Invalid),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot fill an order whose nonce is less than the nonce last used in that bucket', async () => {
|
||||
const order1 = getTestOtcOrder({ taker, txOrigin });
|
||||
await testUtils.fillTakerSignedOtcOrderAsync(order1);
|
||||
const order2 = getTestOtcOrder({
|
||||
taker,
|
||||
txOrigin,
|
||||
nonceBucket: order1.nonceBucket,
|
||||
nonce: order1.nonce.minus(1),
|
||||
});
|
||||
const tx = testUtils.fillTakerSignedOtcOrderAsync(order2);
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotFillableError(order2.getHash(), OrderStatus.Invalid),
|
||||
);
|
||||
});
|
||||
|
||||
it('can fill two orders that use the same nonce bucket and increasing nonces', async () => {
|
||||
const order1 = getTestOtcOrder({ taker, txOrigin });
|
||||
const tx1 = await testUtils.fillTakerSignedOtcOrderAsync(order1);
|
||||
verifyEventsFromLogs(
|
||||
tx1.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order1)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
const order2 = getTestOtcOrder({
|
||||
taker,
|
||||
txOrigin,
|
||||
nonceBucket: order1.nonceBucket,
|
||||
nonce: order1.nonce.plus(1),
|
||||
});
|
||||
const tx2 = await testUtils.fillTakerSignedOtcOrderAsync(order2);
|
||||
verifyEventsFromLogs(
|
||||
tx2.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order2)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
});
|
||||
|
||||
it('can fill two orders that use the same nonce but different nonce buckets', async () => {
|
||||
const order1 = getTestOtcOrder({ taker, txOrigin });
|
||||
const tx1 = await testUtils.fillTakerSignedOtcOrderAsync(order1);
|
||||
verifyEventsFromLogs(
|
||||
tx1.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order1)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
const order2 = getTestOtcOrder({ taker, txOrigin, nonce: order1.nonce });
|
||||
const tx2 = await testUtils.fillTakerSignedOtcOrderAsync(order2);
|
||||
verifyEventsFromLogs(
|
||||
tx2.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order2)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
});
|
||||
|
||||
it('can fill a WETH buy order and receive ETH', async () => {
|
||||
const takerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
|
||||
const order = getTestOtcOrder({
|
||||
taker,
|
||||
txOrigin,
|
||||
makerToken: wethToken.address,
|
||||
makerAmount: new BigNumber('1e18'),
|
||||
});
|
||||
await wethToken.deposit().awaitTransactionSuccessAsync({ from: maker, value: order.makerAmount });
|
||||
const receipt = await testUtils.fillTakerSignedOtcOrderAsync(order, txOrigin, taker, true);
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
const takerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(taker);
|
||||
expect(takerEthBalanceAfter.minus(takerEthBalanceBefore)).to.bignumber.equal(order.makerAmount);
|
||||
});
|
||||
|
||||
it('reverts if `unwrapWeth` is true but maker token is not WETH', async () => {
|
||||
const order = getTestOtcOrder({ taker, txOrigin });
|
||||
const tx = testUtils.fillTakerSignedOtcOrderAsync(order, txOrigin, taker, true);
|
||||
return expect(tx).to.revertWith('OtcOrdersFeature/INVALID_UNWRAP_WETH');
|
||||
});
|
||||
|
||||
it('allows for fills on orders signed by a approved signer (taker)', async () => {
|
||||
const order = getTestOtcOrder({ txOrigin, taker: contractWallet.address });
|
||||
await testUtils.prepareBalancesForOrdersAsync([order], contractWallet.address);
|
||||
// allow signer
|
||||
await contractWallet
|
||||
.registerAllowedOrderSigner(contractWalletSigner, true)
|
||||
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
|
||||
// fill should succeed
|
||||
const receipt = await zeroEx
|
||||
.fillTakerSignedOtcOrder(
|
||||
order,
|
||||
await order.getSignatureWithProviderAsync(env.provider),
|
||||
await order.getSignatureWithProviderAsync(
|
||||
env.provider,
|
||||
SignatureType.EthSign,
|
||||
contractWalletSigner,
|
||||
),
|
||||
false,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: txOrigin });
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[testUtils.createOtcOrderFilledEventArgs(order)],
|
||||
IZeroExEvents.OtcOrderFilled,
|
||||
);
|
||||
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order);
|
||||
});
|
||||
|
||||
it(`doesn't allow fills with an unapproved signer (taker)`, async () => {
|
||||
const order = getTestOtcOrder({ txOrigin, taker: contractWallet.address });
|
||||
await testUtils.prepareBalancesForOrdersAsync([order], contractWallet.address);
|
||||
// fill should succeed
|
||||
const tx = zeroEx
|
||||
.fillTakerSignedOtcOrder(
|
||||
order,
|
||||
await order.getSignatureWithProviderAsync(env.provider),
|
||||
await order.getSignatureWithProviderAsync(env.provider, SignatureType.EthSign, notTaker),
|
||||
false,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: txOrigin });
|
||||
return expect(tx).to.revertWith(
|
||||
new RevertErrors.NativeOrders.OrderNotSignedByTakerError(order.getHash(), notTaker, order.taker),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@@ -43,12 +43,14 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
{
|
||||
transformERC20: (await TestTransformERC20Contract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestTransformERC20,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
)).address,
|
||||
transformERC20: (
|
||||
await TestTransformERC20Contract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestTransformERC20,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
)
|
||||
).address,
|
||||
},
|
||||
{ transformerDeployer },
|
||||
);
|
||||
|
258
contracts/zero-ex/test/features/uniswapv3_test.ts
Normal file
258
contracts/zero-ex/test/features/uniswapv3_test.ts
Normal file
@@ -0,0 +1,258 @@
|
||||
import {
|
||||
blockchainTests,
|
||||
constants,
|
||||
describe,
|
||||
expect,
|
||||
getRandomPortion,
|
||||
randomAddress,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import { LogWithDecodedArgs } from 'ethereum-types';
|
||||
|
||||
import { artifacts } from '../artifacts';
|
||||
import {
|
||||
TestMintableERC20TokenContract,
|
||||
TestNoEthRecipientContract,
|
||||
TestUniswapV3FactoryContract,
|
||||
TestUniswapV3FactoryPoolCreatedEventArgs,
|
||||
TestUniswapV3PoolContract,
|
||||
TestWethContract,
|
||||
UniswapV3FeatureContract,
|
||||
} from '../wrappers';
|
||||
|
||||
blockchainTests.resets('UniswapV3Feature', env => {
|
||||
const { MAX_UINT256, NULL_ADDRESS, ZERO_AMOUNT } = constants;
|
||||
const POOL_FEE = 1234;
|
||||
const MAX_SUPPLY = new BigNumber('10e18');
|
||||
let uniFactory: TestUniswapV3FactoryContract;
|
||||
let feature: UniswapV3FeatureContract;
|
||||
let weth: TestWethContract;
|
||||
let tokens: TestMintableERC20TokenContract[];
|
||||
const sellAmount = getRandomPortion(MAX_SUPPLY);
|
||||
const buyAmount = getRandomPortion(MAX_SUPPLY);
|
||||
let taker: string;
|
||||
const recipient = randomAddress();
|
||||
let noEthRecipient: TestNoEthRecipientContract;
|
||||
|
||||
before(async () => {
|
||||
[, taker] = await env.getAccountAddressesAsync();
|
||||
weth = await TestWethContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestWeth,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
tokens = await Promise.all(
|
||||
[...new Array(3)].map(async () =>
|
||||
TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestMintableERC20Token,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
),
|
||||
),
|
||||
);
|
||||
noEthRecipient = await TestNoEthRecipientContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestNoEthRecipient,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
uniFactory = await TestUniswapV3FactoryContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestUniswapV3Factory,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
feature = await UniswapV3FeatureContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestUniswapV3Feature,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
weth.address,
|
||||
uniFactory.address,
|
||||
await uniFactory.POOL_INIT_CODE_HASH().callAsync(),
|
||||
);
|
||||
await Promise.all(
|
||||
[...tokens, weth].map(t =>
|
||||
t.approve(feature.address, MAX_UINT256).awaitTransactionSuccessAsync({ from: taker }),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
function isWethContract(t: TestMintableERC20TokenContract | TestWethContract): t is TestWethContract {
|
||||
return !!(t as any).deposit;
|
||||
}
|
||||
|
||||
async function mintToAsync(
|
||||
token: TestMintableERC20TokenContract | TestWethContract,
|
||||
owner: string,
|
||||
amount: BigNumber,
|
||||
): Promise<void> {
|
||||
if (isWethContract(token)) {
|
||||
await token.depositTo(owner).awaitTransactionSuccessAsync({ value: amount });
|
||||
} else {
|
||||
await token.mint(owner, amount).awaitTransactionSuccessAsync();
|
||||
}
|
||||
}
|
||||
|
||||
async function createPoolAsync(
|
||||
token0: TestMintableERC20TokenContract | TestWethContract,
|
||||
token1: TestMintableERC20TokenContract | TestWethContract,
|
||||
balance0: BigNumber,
|
||||
balance1: BigNumber,
|
||||
): Promise<TestUniswapV3PoolContract> {
|
||||
const r = await uniFactory
|
||||
.createPool(token0.address, token1.address, new BigNumber(POOL_FEE))
|
||||
.awaitTransactionSuccessAsync();
|
||||
const pool = new TestUniswapV3PoolContract(
|
||||
(r.logs[0] as LogWithDecodedArgs<TestUniswapV3FactoryPoolCreatedEventArgs>).args.pool,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
);
|
||||
await mintToAsync(token0, pool.address, balance0);
|
||||
await mintToAsync(token1, pool.address, balance1);
|
||||
return pool;
|
||||
}
|
||||
|
||||
function encodePath(tokens_: Array<TestMintableERC20TokenContract | TestWethContract>): string {
|
||||
const elems: string[] = [];
|
||||
tokens_.forEach((t, i) => {
|
||||
if (i) {
|
||||
elems.push(hexUtils.leftPad(POOL_FEE, 3));
|
||||
}
|
||||
elems.push(hexUtils.leftPad(t.address, 20));
|
||||
});
|
||||
return hexUtils.concat(...elems);
|
||||
}
|
||||
|
||||
describe('sellTokenForTokenToUniswapV3()', () => {
|
||||
it('1-hop swap', async () => {
|
||||
const [sellToken, buyToken] = tokens;
|
||||
const pool = await createPoolAsync(sellToken, buyToken, ZERO_AMOUNT, buyAmount);
|
||||
await mintToAsync(sellToken, taker, sellAmount);
|
||||
await feature
|
||||
.sellTokenForTokenToUniswapV3(encodePath([sellToken, buyToken]), sellAmount, buyAmount, recipient)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
// Test pools always ask for full sell amount and pay entire balance.
|
||||
expect(await sellToken.balanceOf(taker).callAsync()).to.bignumber.eq(0);
|
||||
expect(await buyToken.balanceOf(recipient).callAsync()).to.bignumber.eq(buyAmount);
|
||||
expect(await sellToken.balanceOf(pool.address).callAsync()).to.bignumber.eq(sellAmount);
|
||||
});
|
||||
|
||||
it('2-hop swap', async () => {
|
||||
const pools = [
|
||||
await createPoolAsync(tokens[0], tokens[1], ZERO_AMOUNT, buyAmount),
|
||||
await createPoolAsync(tokens[1], tokens[2], ZERO_AMOUNT, buyAmount),
|
||||
];
|
||||
await mintToAsync(tokens[0], taker, sellAmount);
|
||||
await feature
|
||||
.sellTokenForTokenToUniswapV3(encodePath(tokens), sellAmount, buyAmount, recipient)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
// Test pools always ask for full sell amount and pay entire balance.
|
||||
expect(await tokens[0].balanceOf(taker).callAsync()).to.bignumber.eq(0);
|
||||
expect(await tokens[2].balanceOf(recipient).callAsync()).to.bignumber.eq(buyAmount);
|
||||
expect(await tokens[0].balanceOf(pools[0].address).callAsync()).to.bignumber.eq(sellAmount);
|
||||
expect(await tokens[1].balanceOf(pools[1].address).callAsync()).to.bignumber.eq(buyAmount);
|
||||
});
|
||||
|
||||
it('1-hop underbuy fails', async () => {
|
||||
const [sellToken, buyToken] = tokens;
|
||||
await createPoolAsync(sellToken, buyToken, ZERO_AMOUNT, buyAmount.minus(1));
|
||||
await mintToAsync(sellToken, taker, sellAmount);
|
||||
const tx = feature
|
||||
.sellTokenForTokenToUniswapV3(encodePath([sellToken, buyToken]), sellAmount, buyAmount, recipient)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
return expect(tx).to.revertWith('UniswapV3Feature/UNDERBOUGHT');
|
||||
});
|
||||
|
||||
it('2-hop underbuy fails', async () => {
|
||||
await createPoolAsync(tokens[0], tokens[1], ZERO_AMOUNT, buyAmount);
|
||||
await createPoolAsync(tokens[1], tokens[2], ZERO_AMOUNT, buyAmount.minus(1));
|
||||
await mintToAsync(tokens[0], taker, sellAmount);
|
||||
const tx = feature
|
||||
.sellTokenForTokenToUniswapV3(encodePath(tokens), sellAmount, buyAmount, recipient)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
return expect(tx).to.revertWith('UniswapV3Feature/UNDERBOUGHT');
|
||||
});
|
||||
|
||||
it('null recipient is sender', async () => {
|
||||
const [sellToken, buyToken] = tokens;
|
||||
await createPoolAsync(sellToken, buyToken, ZERO_AMOUNT, buyAmount);
|
||||
await mintToAsync(sellToken, taker, sellAmount);
|
||||
await feature
|
||||
.sellTokenForTokenToUniswapV3(encodePath([sellToken, buyToken]), sellAmount, buyAmount, NULL_ADDRESS)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
// Test pools always ask for full sell amount and pay entire balance.
|
||||
expect(await buyToken.balanceOf(taker).callAsync()).to.bignumber.eq(buyAmount);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sellEthForTokenToUniswapV3()', () => {
|
||||
it('1-hop swap', async () => {
|
||||
const [buyToken] = tokens;
|
||||
const pool = await createPoolAsync(weth, buyToken, ZERO_AMOUNT, buyAmount);
|
||||
await feature
|
||||
.sellEthForTokenToUniswapV3(encodePath([weth, buyToken]), buyAmount, recipient)
|
||||
.awaitTransactionSuccessAsync({ from: taker, value: sellAmount });
|
||||
// Test pools always ask for full sell amount and pay entire balance.
|
||||
expect(await buyToken.balanceOf(recipient).callAsync()).to.bignumber.eq(buyAmount);
|
||||
expect(await weth.balanceOf(pool.address).callAsync()).to.bignumber.eq(sellAmount);
|
||||
});
|
||||
|
||||
it('null recipient is sender', async () => {
|
||||
const [buyToken] = tokens;
|
||||
const pool = await createPoolAsync(weth, buyToken, ZERO_AMOUNT, buyAmount);
|
||||
await feature
|
||||
.sellEthForTokenToUniswapV3(encodePath([weth, buyToken]), buyAmount, NULL_ADDRESS)
|
||||
.awaitTransactionSuccessAsync({ from: taker, value: sellAmount });
|
||||
// Test pools always ask for full sell amount and pay entire balance.
|
||||
expect(await buyToken.balanceOf(taker).callAsync()).to.bignumber.eq(buyAmount);
|
||||
expect(await weth.balanceOf(pool.address).callAsync()).to.bignumber.eq(sellAmount);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sellTokenForEthToUniswapV3()', () => {
|
||||
it('1-hop swap', async () => {
|
||||
const [sellToken] = tokens;
|
||||
const pool = await createPoolAsync(sellToken, weth, ZERO_AMOUNT, buyAmount);
|
||||
await mintToAsync(sellToken, taker, sellAmount);
|
||||
await feature
|
||||
.sellTokenForEthToUniswapV3(encodePath([sellToken, weth]), sellAmount, buyAmount, recipient)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
// Test pools always ask for full sell amount and pay entire balance.
|
||||
expect(await sellToken.balanceOf(taker).callAsync()).to.bignumber.eq(0);
|
||||
expect(await env.web3Wrapper.getBalanceInWeiAsync(recipient)).to.bignumber.eq(buyAmount);
|
||||
expect(await sellToken.balanceOf(pool.address).callAsync()).to.bignumber.eq(sellAmount);
|
||||
});
|
||||
|
||||
it('null recipient is sender', async () => {
|
||||
const [sellToken] = tokens;
|
||||
const pool = await createPoolAsync(sellToken, weth, ZERO_AMOUNT, buyAmount);
|
||||
await mintToAsync(sellToken, taker, sellAmount);
|
||||
const takerBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
|
||||
await feature
|
||||
.sellTokenForEthToUniswapV3(encodePath([sellToken, weth]), sellAmount, buyAmount, NULL_ADDRESS)
|
||||
.awaitTransactionSuccessAsync({ from: taker, gasPrice: ZERO_AMOUNT });
|
||||
// Test pools always ask for full sell amount and pay entire balance.
|
||||
expect((await env.web3Wrapper.getBalanceInWeiAsync(taker)).minus(takerBalanceBefore)).to.bignumber.eq(
|
||||
buyAmount,
|
||||
);
|
||||
expect(await sellToken.balanceOf(pool.address).callAsync()).to.bignumber.eq(sellAmount);
|
||||
});
|
||||
|
||||
it('fails if receipient cannot receive ETH', async () => {
|
||||
const [sellToken] = tokens;
|
||||
await mintToAsync(sellToken, taker, sellAmount);
|
||||
const tx = feature
|
||||
.sellTokenForEthToUniswapV3(
|
||||
encodePath([sellToken, weth]),
|
||||
sellAmount,
|
||||
buyAmount,
|
||||
noEthRecipient.address,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
return expect(tx).to.be.rejectedWith('revert');
|
||||
});
|
||||
});
|
||||
});
|
@@ -42,19 +42,19 @@ blockchainTests.resets('FixinTokenSpender', env => {
|
||||
);
|
||||
});
|
||||
|
||||
describe('transferERC20Tokens()', () => {
|
||||
describe('transferERC20TokensFrom()', () => {
|
||||
const EMPTY_RETURN_AMOUNT = 1337;
|
||||
const FALSE_RETURN_AMOUNT = 1338;
|
||||
const REVERT_RETURN_AMOUNT = 1339;
|
||||
const EXTRA_RETURN_TRUE_AMOUNT = 1341;
|
||||
const EXTRA_RETURN_FALSE_AMOUNT = 1342;
|
||||
|
||||
it('transferERC20Tokens() successfully calls compliant ERC20 token', async () => {
|
||||
it('transferERC20TokensFrom() successfully calls compliant ERC20 token', async () => {
|
||||
const tokenFrom = randomAddress();
|
||||
const tokenTo = randomAddress();
|
||||
const tokenAmount = new BigNumber(123456);
|
||||
const receipt = await tokenSpender
|
||||
.transferERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||
.transferERC20TokensFrom(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||
.awaitTransactionSuccessAsync();
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
@@ -70,12 +70,12 @@ blockchainTests.resets('FixinTokenSpender', env => {
|
||||
);
|
||||
});
|
||||
|
||||
it('transferERC20Tokens() successfully calls non-compliant ERC20 token', async () => {
|
||||
it('transferERC20TokensFrom() successfully calls non-compliant ERC20 token', async () => {
|
||||
const tokenFrom = randomAddress();
|
||||
const tokenTo = randomAddress();
|
||||
const tokenAmount = new BigNumber(EMPTY_RETURN_AMOUNT);
|
||||
const receipt = await tokenSpender
|
||||
.transferERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||
.transferERC20TokensFrom(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||
.awaitTransactionSuccessAsync();
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
@@ -91,34 +91,34 @@ blockchainTests.resets('FixinTokenSpender', env => {
|
||||
);
|
||||
});
|
||||
|
||||
it('transferERC20Tokens() reverts if ERC20 token reverts', async () => {
|
||||
it('transferERC20TokensFrom() reverts if ERC20 token reverts', async () => {
|
||||
const tokenFrom = randomAddress();
|
||||
const tokenTo = randomAddress();
|
||||
const tokenAmount = new BigNumber(REVERT_RETURN_AMOUNT);
|
||||
const tx = tokenSpender
|
||||
.transferERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||
.transferERC20TokensFrom(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||
.awaitTransactionSuccessAsync();
|
||||
const expectedError = new StringRevertError('TestTokenSpenderERC20Token/Revert');
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('transferERC20Tokens() reverts if ERC20 token returns false', async () => {
|
||||
it('transferERC20TokensFrom() reverts if ERC20 token returns false', async () => {
|
||||
const tokenFrom = randomAddress();
|
||||
const tokenTo = randomAddress();
|
||||
const tokenAmount = new BigNumber(FALSE_RETURN_AMOUNT);
|
||||
const tx = tokenSpender
|
||||
.transferERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||
.transferERC20TokensFrom(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||
.awaitTransactionSuccessAsync();
|
||||
return expect(tx).to.revertWith(new RawRevertError(hexUtils.leftPad(0)));
|
||||
});
|
||||
|
||||
it('transferERC20Tokens() allows extra data after true', async () => {
|
||||
it('transferERC20TokensFrom() allows extra data after true', async () => {
|
||||
const tokenFrom = randomAddress();
|
||||
const tokenTo = randomAddress();
|
||||
const tokenAmount = new BigNumber(EXTRA_RETURN_TRUE_AMOUNT);
|
||||
|
||||
const receipt = await tokenSpender
|
||||
.transferERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||
.transferERC20TokensFrom(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||
.awaitTransactionSuccessAsync();
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
@@ -134,24 +134,24 @@ blockchainTests.resets('FixinTokenSpender', env => {
|
||||
);
|
||||
});
|
||||
|
||||
it("transferERC20Tokens() reverts when there's extra data after false", async () => {
|
||||
it("transferERC20TokensFrom() reverts when there's extra data after false", async () => {
|
||||
const tokenFrom = randomAddress();
|
||||
const tokenTo = randomAddress();
|
||||
const tokenAmount = new BigNumber(EXTRA_RETURN_FALSE_AMOUNT);
|
||||
|
||||
const tx = tokenSpender
|
||||
.transferERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||
.transferERC20TokensFrom(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||
.awaitTransactionSuccessAsync();
|
||||
return expect(tx).to.revertWith(new RawRevertError(hexUtils.leftPad(EXTRA_RETURN_FALSE_AMOUNT, 64)));
|
||||
});
|
||||
|
||||
it('transferERC20Tokens() cannot call self', async () => {
|
||||
it('transferERC20TokensFrom() cannot call self', async () => {
|
||||
const tokenFrom = randomAddress();
|
||||
const tokenTo = randomAddress();
|
||||
const tokenAmount = new BigNumber(123456);
|
||||
|
||||
const tx = tokenSpender
|
||||
.transferERC20Tokens(tokenSpender.address, tokenFrom, tokenTo, tokenAmount)
|
||||
.transferERC20TokensFrom(tokenSpender.address, tokenFrom, tokenTo, tokenAmount)
|
||||
.awaitTransactionSuccessAsync();
|
||||
return expect(tx).to.revertWith('FixinTokenSpender/CANNOT_INVOKE_SELF');
|
||||
});
|
||||
|
@@ -5,11 +5,25 @@ import {
|
||||
getRandomInteger,
|
||||
randomAddress,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { LimitOrder, LimitOrderFields, OrderBase, OrderInfo, RfqOrder, RfqOrderFields } from '@0x/protocol-utils';
|
||||
import {
|
||||
LimitOrder,
|
||||
LimitOrderFields,
|
||||
OrderBase,
|
||||
OrderInfo,
|
||||
OtcOrder,
|
||||
RfqOrder,
|
||||
RfqOrderFields,
|
||||
SignatureType,
|
||||
} from '@0x/protocol-utils';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
|
||||
import { IZeroExContract, IZeroExLimitOrderFilledEventArgs, IZeroExRfqOrderFilledEventArgs } from '../../src/wrappers';
|
||||
import {
|
||||
IZeroExContract,
|
||||
IZeroExLimitOrderFilledEventArgs,
|
||||
IZeroExOtcOrderFilledEventArgs,
|
||||
IZeroExRfqOrderFilledEventArgs,
|
||||
} from '../../src/wrappers';
|
||||
import { artifacts } from '../artifacts';
|
||||
import { fullMigrateAsync } from '../utils/migration';
|
||||
import { TestMintableERC20TokenContract } from '../wrappers';
|
||||
@@ -20,6 +34,7 @@ interface RfqOrderFilledAmounts {
|
||||
makerTokenFilledAmount: BigNumber;
|
||||
takerTokenFilledAmount: BigNumber;
|
||||
}
|
||||
interface OtcOrderFilledAmounts extends RfqOrderFilledAmounts {}
|
||||
|
||||
interface LimitOrderFilledAmounts {
|
||||
makerTokenFilledAmount: BigNumber;
|
||||
@@ -27,6 +42,12 @@ interface LimitOrderFilledAmounts {
|
||||
takerTokenFeeFilledAmount: BigNumber;
|
||||
}
|
||||
|
||||
export enum OtcOrderWethOptions {
|
||||
LeaveAsWeth,
|
||||
WrapEth,
|
||||
UnwrapWeth,
|
||||
}
|
||||
|
||||
export class NativeOrdersTestEnvironment {
|
||||
public static async createAsync(
|
||||
env: BlockchainTestsEnvironment,
|
||||
@@ -71,7 +92,7 @@ export class NativeOrdersTestEnvironment {
|
||||
) {}
|
||||
|
||||
public async prepareBalancesForOrdersAsync(
|
||||
orders: LimitOrder[] | RfqOrder[],
|
||||
orders: LimitOrder[] | RfqOrder[] | OtcOrder[],
|
||||
taker: string = this.taker,
|
||||
): Promise<void> {
|
||||
await this.makerToken
|
||||
@@ -128,6 +149,51 @@ export class NativeOrdersTestEnvironment {
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
}
|
||||
|
||||
public async fillOtcOrderAsync(
|
||||
order: OtcOrder,
|
||||
fillAmount: BigNumber | number = order.takerAmount,
|
||||
taker: string = this.taker,
|
||||
unwrapWeth: boolean = false,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
await this.prepareBalancesForOrdersAsync([order], taker);
|
||||
return this.zeroEx
|
||||
.fillOtcOrder(
|
||||
order,
|
||||
await order.getSignatureWithProviderAsync(this._env.provider),
|
||||
new BigNumber(fillAmount),
|
||||
unwrapWeth,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: taker });
|
||||
}
|
||||
|
||||
public async fillTakerSignedOtcOrderAsync(
|
||||
order: OtcOrder,
|
||||
origin: string = order.txOrigin,
|
||||
taker: string = order.taker,
|
||||
unwrapWeth: boolean = false,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
await this.prepareBalancesForOrdersAsync([order], taker);
|
||||
return this.zeroEx
|
||||
.fillTakerSignedOtcOrder(
|
||||
order,
|
||||
await order.getSignatureWithProviderAsync(this._env.provider),
|
||||
await order.getSignatureWithProviderAsync(this._env.provider, SignatureType.EthSign, taker),
|
||||
unwrapWeth,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: origin });
|
||||
}
|
||||
|
||||
public async fillOtcOrderWithEthAsync(
|
||||
order: OtcOrder,
|
||||
fillAmount: BigNumber | number = order.takerAmount,
|
||||
taker: string = this.taker,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
await this.prepareBalancesForOrdersAsync([order], taker);
|
||||
return this.zeroEx
|
||||
.fillOtcOrderWithEth(order, await order.getSignatureWithProviderAsync(this._env.provider))
|
||||
.awaitTransactionSuccessAsync({ from: taker, value: fillAmount });
|
||||
}
|
||||
|
||||
public createLimitOrderFilledEventArgs(
|
||||
order: LimitOrder,
|
||||
takerTokenFillAmount: BigNumber = order.takerAmount,
|
||||
@@ -175,6 +241,25 @@ export class NativeOrdersTestEnvironment {
|
||||
pool: order.pool,
|
||||
};
|
||||
}
|
||||
|
||||
public createOtcOrderFilledEventArgs(
|
||||
order: OtcOrder,
|
||||
takerTokenFillAmount: BigNumber = order.takerAmount,
|
||||
): IZeroExOtcOrderFilledEventArgs {
|
||||
const { makerTokenFilledAmount, takerTokenFilledAmount } = computeOtcOrderFilledAmounts(
|
||||
order,
|
||||
takerTokenFillAmount,
|
||||
);
|
||||
return {
|
||||
takerTokenFilledAmount,
|
||||
makerTokenFilledAmount,
|
||||
orderHash: order.getHash(),
|
||||
maker: order.maker,
|
||||
taker: order.taker !== NULL_ADDRESS ? order.taker : this.taker,
|
||||
makerToken: order.makerToken,
|
||||
takerToken: order.takerToken,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,6 +301,27 @@ export function getRandomRfqOrder(fields: Partial<RfqOrderFields> = {}): RfqOrde
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random OTC Order
|
||||
*/
|
||||
export function getRandomOtcOrder(fields: Partial<OtcOrder> = {}): OtcOrder {
|
||||
return new OtcOrder({
|
||||
makerToken: randomAddress(),
|
||||
takerToken: randomAddress(),
|
||||
makerAmount: getRandomInteger('1e18', '100e18'),
|
||||
takerAmount: getRandomInteger('1e6', '100e6'),
|
||||
maker: randomAddress(),
|
||||
taker: randomAddress(),
|
||||
txOrigin: randomAddress(),
|
||||
expiryAndNonce: OtcOrder.encodeExpiryAndNonce(
|
||||
fields.expiry ?? new BigNumber(Math.floor(Date.now() / 1000 + 60)), // expiry
|
||||
fields.nonceBucket ?? getRandomInteger(0, OtcOrder.MAX_NONCE_BUCKET), // nonceBucket
|
||||
fields.nonce ?? getRandomInteger(0, OtcOrder.MAX_NONCE_VALUE), // nonce
|
||||
),
|
||||
...fields,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the fields of an OrderInfo object.
|
||||
*/
|
||||
@@ -286,6 +392,24 @@ export function computeRfqOrderFilledAmounts(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the maker and taker amounts filled for the given OTC order.
|
||||
*/
|
||||
export function computeOtcOrderFilledAmounts(
|
||||
order: OtcOrder,
|
||||
takerTokenFillAmount: BigNumber = order.takerAmount,
|
||||
): OtcOrderFilledAmounts {
|
||||
const fillAmount = BigNumber.min(order.takerAmount, takerTokenFillAmount, order.takerAmount);
|
||||
const makerTokenFilledAmount = fillAmount
|
||||
.times(order.makerAmount)
|
||||
.div(order.takerAmount)
|
||||
.integerValue(BigNumber.ROUND_DOWN);
|
||||
return {
|
||||
makerTokenFilledAmount,
|
||||
takerTokenFilledAmount: fillAmount,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the remaining fillable amount in maker token for
|
||||
* the given order.
|
||||
|
@@ -34,6 +34,7 @@ export * from '../test/generated-wrappers/i_mooniswap_pool';
|
||||
export * from '../test/generated-wrappers/i_multiplex_feature';
|
||||
export * from '../test/generated-wrappers/i_native_orders_events';
|
||||
export * from '../test/generated-wrappers/i_native_orders_feature';
|
||||
export * from '../test/generated-wrappers/i_otc_orders_feature';
|
||||
export * from '../test/generated-wrappers/i_ownable_feature';
|
||||
export * from '../test/generated-wrappers/i_pancake_swap_feature';
|
||||
export * from '../test/generated-wrappers/i_simple_function_registry_feature';
|
||||
@@ -43,6 +44,8 @@ export * from '../test/generated-wrappers/i_token_spender_feature';
|
||||
export * from '../test/generated-wrappers/i_transform_erc20_feature';
|
||||
export * from '../test/generated-wrappers/i_uniswap_feature';
|
||||
export * from '../test/generated-wrappers/i_uniswap_v2_pair';
|
||||
export * from '../test/generated-wrappers/i_uniswap_v3_feature';
|
||||
export * from '../test/generated-wrappers/i_uniswap_v3_pool';
|
||||
export * from '../test/generated-wrappers/i_zero_ex';
|
||||
export * from '../test/generated-wrappers/initial_migration';
|
||||
export * from '../test/generated-wrappers/lib_bootstrap';
|
||||
@@ -56,6 +59,7 @@ export * from '../test/generated-wrappers/lib_migrate';
|
||||
export * from '../test/generated-wrappers/lib_native_order';
|
||||
export * from '../test/generated-wrappers/lib_native_orders_rich_errors';
|
||||
export * from '../test/generated-wrappers/lib_native_orders_storage';
|
||||
export * from '../test/generated-wrappers/lib_otc_orders_storage';
|
||||
export * from '../test/generated-wrappers/lib_ownable_rich_errors';
|
||||
export * from '../test/generated-wrappers/lib_ownable_storage';
|
||||
export * from '../test/generated-wrappers/lib_proxy_rich_errors';
|
||||
@@ -100,6 +104,7 @@ export * from '../test/generated-wrappers/native_orders_feature';
|
||||
export * from '../test/generated-wrappers/native_orders_info';
|
||||
export * from '../test/generated-wrappers/native_orders_protocol_fees';
|
||||
export * from '../test/generated-wrappers/native_orders_settlement';
|
||||
export * from '../test/generated-wrappers/otc_orders_feature';
|
||||
export * from '../test/generated-wrappers/ownable_feature';
|
||||
export * from '../test/generated-wrappers/pancake_swap_feature';
|
||||
export * from '../test/generated-wrappers/pay_taker_transformer';
|
||||
@@ -128,6 +133,7 @@ export * from '../test/generated-wrappers/test_mint_token_erc20_transformer';
|
||||
export * from '../test/generated-wrappers/test_mintable_erc20_token';
|
||||
export * from '../test/generated-wrappers/test_mooniswap';
|
||||
export * from '../test/generated-wrappers/test_native_orders_feature';
|
||||
export * from '../test/generated-wrappers/test_no_eth_recipient';
|
||||
export * from '../test/generated-wrappers/test_order_signer_registry_with_contract_wallet';
|
||||
export * from '../test/generated-wrappers/test_permissionless_transformer_deployer_suicidal';
|
||||
export * from '../test/generated-wrappers/test_permissionless_transformer_deployer_transformer';
|
||||
@@ -140,6 +146,9 @@ export * from '../test/generated-wrappers/test_transform_erc20';
|
||||
export * from '../test/generated-wrappers/test_transformer_base';
|
||||
export * from '../test/generated-wrappers/test_transformer_deployer_transformer';
|
||||
export * from '../test/generated-wrappers/test_transformer_host';
|
||||
export * from '../test/generated-wrappers/test_uniswap_v3_factory';
|
||||
export * from '../test/generated-wrappers/test_uniswap_v3_feature';
|
||||
export * from '../test/generated-wrappers/test_uniswap_v3_pool';
|
||||
export * from '../test/generated-wrappers/test_weth';
|
||||
export * from '../test/generated-wrappers/test_weth_transformer_host';
|
||||
export * from '../test/generated-wrappers/test_zero_ex_feature';
|
||||
@@ -147,6 +156,7 @@ export * from '../test/generated-wrappers/transform_erc20_feature';
|
||||
export * from '../test/generated-wrappers/transformer';
|
||||
export * from '../test/generated-wrappers/transformer_deployer';
|
||||
export * from '../test/generated-wrappers/uniswap_feature';
|
||||
export * from '../test/generated-wrappers/uniswap_v3_feature';
|
||||
export * from '../test/generated-wrappers/weth_transformer';
|
||||
export * from '../test/generated-wrappers/zero_ex';
|
||||
export * from '../test/generated-wrappers/zero_ex_optimized';
|
||||
|
Reference in New Issue
Block a user