diff --git a/contracts/integrations/test/coordinator/coordinator_test.ts b/contracts/integrations/test/coordinator/coordinator_test.ts index 9667523a4f..ec5848b474 100644 --- a/contracts/integrations/test/coordinator/coordinator_test.ts +++ b/contracts/integrations/test/coordinator/coordinator_test.ts @@ -95,7 +95,7 @@ blockchainTests.resets('Coordinator integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); function expectedFillEvent(order: SignedOrder): ExchangeFillEventArgs { diff --git a/contracts/integrations/test/exchange/batch_match_orders_test.ts b/contracts/integrations/test/exchange/batch_match_orders_test.ts index 8907d33d58..29f368c9db 100644 --- a/contracts/integrations/test/exchange/batch_match_orders_test.ts +++ b/contracts/integrations/test/exchange/batch_match_orders_test.ts @@ -146,7 +146,7 @@ blockchainTests.resets('matchOrders integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); describe('batchMatchOrders and batchMatchOrdersWithMaximalFill rich errors', async () => { diff --git a/contracts/integrations/test/exchange/exchange_wrapper_test.ts b/contracts/integrations/test/exchange/exchange_wrapper_test.ts index c97221aba0..ed8afb5403 100644 --- a/contracts/integrations/test/exchange/exchange_wrapper_test.ts +++ b/contracts/integrations/test/exchange/exchange_wrapper_test.ts @@ -124,7 +124,7 @@ blockchainTests.resets('Exchange wrappers', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); interface SignedOrderWithValidity { diff --git a/contracts/integrations/test/exchange/fillorder_test.ts b/contracts/integrations/test/exchange/fillorder_test.ts index 3c9b44ed48..c32ce49270 100644 --- a/contracts/integrations/test/exchange/fillorder_test.ts +++ b/contracts/integrations/test/exchange/fillorder_test.ts @@ -109,7 +109,7 @@ blockchainTests.resets('fillOrder integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); function verifyFillEvents(order: SignedOrder, receipt: TransactionReceiptWithDecodedLogs): void { diff --git a/contracts/integrations/test/exchange/match_orders_maximal_fill_test.ts b/contracts/integrations/test/exchange/match_orders_maximal_fill_test.ts index 06497d4ebf..9fa037c208 100644 --- a/contracts/integrations/test/exchange/match_orders_maximal_fill_test.ts +++ b/contracts/integrations/test/exchange/match_orders_maximal_fill_test.ts @@ -163,7 +163,7 @@ blockchainTests.resets('matchOrdersWithMaximalFill integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); describe('matchOrdersWithMaximalFill', () => { diff --git a/contracts/integrations/test/exchange/match_orders_test.ts b/contracts/integrations/test/exchange/match_orders_test.ts index 6f7b920875..ed2d3785b2 100644 --- a/contracts/integrations/test/exchange/match_orders_test.ts +++ b/contracts/integrations/test/exchange/match_orders_test.ts @@ -163,7 +163,7 @@ blockchainTests.resets('matchOrders integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); describe('matchOrders', () => { diff --git a/contracts/integrations/test/exchange/transaction_protocol_fee_test.ts b/contracts/integrations/test/exchange/transaction_protocol_fee_test.ts index 2b156b3f20..5525b545a2 100644 --- a/contracts/integrations/test/exchange/transaction_protocol_fee_test.ts +++ b/contracts/integrations/test/exchange/transaction_protocol_fee_test.ts @@ -1,7 +1,15 @@ // tslint:disable: max-file-line-count import { IAssetDataContract } from '@0x/contracts-asset-proxy'; -import { exchangeDataEncoder } from '@0x/contracts-exchange'; -import { blockchainTests, constants, describe, ExchangeFunctionName } from '@0x/contracts-test-utils'; +import { exchangeDataEncoder, ExchangeRevertErrors } from '@0x/contracts-exchange'; +import { + blockchainTests, + constants, + describe, + ExchangeFunctionName, + expect, + orderHashUtils, + transactionHashUtils, +} from '@0x/contracts-test-utils'; import { SignedOrder, SignedZeroExTransaction } from '@0x/types'; import { BigNumber } from '@0x/utils'; @@ -24,6 +32,7 @@ blockchainTests.resets('Transaction <> protocol fee integration tests', env => { let alice: Taker; let bob: Taker; let charlie: Taker; + let wethless: Taker; // Used to test revert scenarios let order: SignedOrder; // All orders will have the same fields, modulo salt and expiration time let transactionA: SignedZeroExTransaction; // fillOrder transaction signed by Alice @@ -42,6 +51,7 @@ blockchainTests.resets('Transaction <> protocol fee integration tests', env => { alice = new Taker({ name: 'Alice', deployment }); bob = new Taker({ name: 'Bob', deployment }); charlie = new Taker({ name: 'Charlie', deployment }); + wethless = new Taker({ name: 'wethless', deployment }); feeRecipient = new FeeRecipient({ name: 'Fee recipient', deployment, @@ -63,6 +73,13 @@ blockchainTests.resets('Transaction <> protocol fee integration tests', env => { await taker.configureERC20TokenAsync(takerFeeToken); await taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address); } + await wethless.configureERC20TokenAsync(takerToken); + await wethless.configureERC20TokenAsync(takerFeeToken); + await wethless.configureERC20TokenAsync( + deployment.tokens.weth, + deployment.staking.stakingProxy.address, + constants.ZERO_AMOUNT, // wethless taker has approved the proxy, but has no weth + ); await maker.configureERC20TokenAsync(makerToken); await maker.configureERC20TokenAsync(makerFeeToken); @@ -90,11 +107,28 @@ blockchainTests.resets('Transaction <> protocol fee integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); const REFUND_AMOUNT = new BigNumber(1); + function protocolFeeError( + failedOrder: SignedOrder, + failedTransaction: SignedZeroExTransaction, + ): ExchangeRevertErrors.TransactionExecutionError { + const nestedError = new ExchangeRevertErrors.PayProtocolFeeError( + orderHashUtils.getOrderHashHex(failedOrder), + DeploymentManager.protocolFee, + maker.address, + wethless.address, + '0x', + ).encode(); + return new ExchangeRevertErrors.TransactionExecutionError( + transactionHashUtils.getTransactionHashHex(failedTransaction), + nestedError, + ); + } + describe('executeTransaction', () => { const ETH_FEE_WITH_REFUND = DeploymentManager.protocolFee.plus(REFUND_AMOUNT); @@ -133,6 +167,14 @@ blockchainTests.resets('Transaction <> protocol fee integration tests', env => { .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, REFUND_AMOUNT); }); + it('Alice executeTransaction => wETH-less taker fillOrder; reverts because protocol fee cannot be paid', async () => { + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + const transaction = await wethless.signTransactionAsync({ data }); + const tx = deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + return expect(tx).to.revertWith(protocolFeeError(order, transaction)); + }); it('Alice executeTransaction => Alice batchFillOrders; mixed protocol fees', async () => { const orders = [order, await maker.signOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData( @@ -199,6 +241,22 @@ blockchainTests.resets('Transaction <> protocol fee integration tests', env => { .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, REFUND_AMOUNT); }); + it('Alice executeTransaction => Alice executeTransaction => wETH-less taker fillOrder; reverts because protocol fee cannot be paid', async () => { + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + const transaction = await wethless.signTransactionAsync({ data }); + const recursiveData = deployment.exchange + .executeTransaction(transaction, transaction.signature) + .getABIEncodedTransactionData(); + const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData }); + const tx = deployment.exchange + .executeTransaction(recursiveTransaction, recursiveTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + const expectedError = new ExchangeRevertErrors.TransactionExecutionError( + transactionHashUtils.getTransactionHashHex(recursiveTransaction), + protocolFeeError(order, transaction).encode(), + ); + return expect(tx).to.revertWith(expectedError); + }); }); }); describe('batchExecuteTransactions', () => { @@ -344,9 +402,19 @@ blockchainTests.resets('Transaction <> protocol fee integration tests', env => { MIXED_FEES_WITH_REFUND, ); }); + it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, wETH-less taker fillOrder; reverts because protocol fee cannot be paid', async () => { + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + const failTransaction = await wethless.signTransactionAsync({ data }); + const transactions = [transactionA, transactionB, failTransaction]; + const signatures = transactions.map(transaction => transaction.signature); + const tx = deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + expect(tx).to.revertWith(protocolFeeError(order, failTransaction)); + }); }); describe('Nested', () => { - // First two orders' protocol fees paid in ETH by sender, the other three paid in WETH by their respective takers + // First two orders' protocol fees paid in ETH by sender, the others paid in WETH by their respective takers const MIXED_FEES_WITH_REFUND = DeploymentManager.protocolFee.times(2.5); // Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder @@ -373,6 +441,18 @@ blockchainTests.resets('Transaction <> protocol fee integration tests', env => { nestedTransaction = await alice.signTransactionAsync({ data: recursiveData }); }); + it('Alice executeTransaction => nested batchExecuteTransactions; mixed protocol fees', async () => { + const txReceipt = await deployment.exchange + .executeTransaction(nestedTransaction, nestedTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order], + [alice.address, bob.address, charlie.address], + txReceipt, + deployment, + MIXED_FEES_WITH_REFUND, + ); + }); it('Alice batchExecuteTransactions => nested batchExecuteTransactions, Bob fillOrder, Charlie fillOrder; mixed protocol fees', async () => { const transactions = [nestedTransaction, transactionB2, transactionC2]; const signatures = transactions.map(tx => tx.signature); diff --git a/contracts/integrations/test/exchange/transaction_test.ts b/contracts/integrations/test/exchange/transaction_test.ts index 2be1b04d30..fd65e85d51 100644 --- a/contracts/integrations/test/exchange/transaction_test.ts +++ b/contracts/integrations/test/exchange/transaction_test.ts @@ -81,7 +81,7 @@ blockchainTests.resets('Transaction integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); function defaultFillEvent(order: SignedOrder): ExchangeFillEventArgs { diff --git a/contracts/integrations/test/forwarder/bridge_test.ts b/contracts/integrations/test/forwarder/bridge_test.ts index f5ac2ce510..d3216d27e7 100644 --- a/contracts/integrations/test/forwarder/bridge_test.ts +++ b/contracts/integrations/test/forwarder/bridge_test.ts @@ -156,7 +156,7 @@ blockchainTests.resets('Forwarder <> ERC20Bridge integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); describe('marketSellOrdersWithEth', () => { diff --git a/contracts/integrations/test/forwarder/forwarder_test.ts b/contracts/integrations/test/forwarder/forwarder_test.ts index 077267e9ab..a6b556d04a 100644 --- a/contracts/integrations/test/forwarder/forwarder_test.ts +++ b/contracts/integrations/test/forwarder/forwarder_test.ts @@ -106,7 +106,7 @@ blockchainTests('Forwarder integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); blockchainTests.resets('constructor', () => { diff --git a/contracts/integrations/test/framework/actors/base.ts b/contracts/integrations/test/framework/actors/base.ts index 8281d73d1e..c73d15ec1c 100644 --- a/contracts/integrations/test/framework/actors/base.ts +++ b/contracts/integrations/test/framework/actors/base.ts @@ -31,6 +31,10 @@ export class Actor { } = {}; protected readonly _transactionFactory: TransactionFactory; + public static reset(): void { + Actor.count = 0; + } + constructor(config: ActorConfig) { Actor.count++; diff --git a/contracts/integrations/test/fuzz_tests/pool_management_test.ts b/contracts/integrations/test/fuzz_tests/pool_management_test.ts index 51dd018257..69b69eb912 100644 --- a/contracts/integrations/test/fuzz_tests/pool_management_test.ts +++ b/contracts/integrations/test/fuzz_tests/pool_management_test.ts @@ -30,7 +30,7 @@ export class PoolManagementSimulation extends Simulation { blockchainTests.skip('Pool management fuzz test', env => { after(async () => { - Actor.count = 0; + Actor.reset(); }); it('fuzz', async () => { diff --git a/contracts/integrations/test/fuzz_tests/stake_management_test.ts b/contracts/integrations/test/fuzz_tests/stake_management_test.ts index 2bcebe9171..7bf770dc8d 100644 --- a/contracts/integrations/test/fuzz_tests/stake_management_test.ts +++ b/contracts/integrations/test/fuzz_tests/stake_management_test.ts @@ -34,7 +34,7 @@ export class StakeManagementSimulation extends Simulation { blockchainTests.skip('Stake management fuzz test', env => { after(async () => { - Actor.count = 0; + Actor.reset(); }); it('fuzz', async () => {