From 5a79ec28d1b4cdb8a7b39c73a9483e0746b535fc Mon Sep 17 00:00:00 2001 From: Michael Zhu Date: Thu, 21 Nov 2019 14:11:51 -0800 Subject: [PATCH] transaction protocol fee integration tests --- .../test/coordinator/coordinator_test.ts | 102 ++--- .../test/exchange/exchange_wrapper_test.ts | 28 +- .../test/exchange/fill_order_wrapper.ts | 14 +- .../test/exchange/fillorder_test.ts | 58 +-- .../test/exchange/match_order_tester.ts | 28 +- .../exchange/transaction_protocol_fee_test.ts | 414 ++++++++++++++++++ .../test/exchange/transaction_test.ts | 28 +- .../test/forwarder/forwarder_test.ts | 32 +- .../test/forwarder/forwarder_test_factory.ts | 38 +- .../test/framework/assertions/stake.ts | 2 +- .../test/framework/assertions/unstake.ts | 2 +- .../framework/balances/local_balance_store.ts | 82 +++- 12 files changed, 582 insertions(+), 246 deletions(-) create mode 100644 contracts/integrations/test/exchange/transaction_protocol_fee_test.ts diff --git a/contracts/integrations/test/coordinator/coordinator_test.ts b/contracts/integrations/test/coordinator/coordinator_test.ts index 2a723dbd0b..9667523a4f 100644 --- a/contracts/integrations/test/coordinator/coordinator_test.ts +++ b/contracts/integrations/test/coordinator/coordinator_test.ts @@ -19,7 +19,6 @@ import { } from '@0x/contracts-test-utils'; import { SignedOrder, SignedZeroExTransaction } from '@0x/types'; import { BigNumber } from '@0x/utils'; -import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { Actor } from '../framework/actors/base'; import { FeeRecipient } from '../framework/actors/fee_recipient'; @@ -99,69 +98,6 @@ blockchainTests.resets('Coordinator integration tests', env => { Actor.count = 0; }); - async function simulateFillsAsync( - orders: SignedOrder[], - txReceipt: TransactionReceiptWithDecodedLogs, - msgValue?: BigNumber, - ): Promise { - let remainingValue = msgValue || constants.ZERO_AMOUNT; - const localBalanceStore = LocalBalanceStore.create(balanceStore); - // Transaction gas cost - localBalanceStore.burnGas(txReceipt.from, DeploymentManager.gasPrice.times(txReceipt.gasUsed)); - - for (const order of orders) { - // Taker -> Maker - await localBalanceStore.transferAssetAsync( - taker.address, - maker.address, - order.takerAssetAmount, - order.takerAssetData, - ); - // Maker -> Taker - await localBalanceStore.transferAssetAsync( - maker.address, - taker.address, - order.makerAssetAmount, - order.makerAssetData, - ); - // Taker -> Fee Recipient - await localBalanceStore.transferAssetAsync( - taker.address, - feeRecipient.address, - order.takerFee, - order.takerFeeAssetData, - ); - // Maker -> Fee Recipient - await localBalanceStore.transferAssetAsync( - maker.address, - feeRecipient.address, - order.makerFee, - order.makerFeeAssetData, - ); - - // Protocol fee - if (remainingValue.isGreaterThanOrEqualTo(DeploymentManager.protocolFee)) { - localBalanceStore.sendEth( - txReceipt.from, - deployment.staking.stakingProxy.address, - DeploymentManager.protocolFee, - ); - remainingValue = remainingValue.minus(DeploymentManager.protocolFee); - } else { - await localBalanceStore.transferAssetAsync( - taker.address, - deployment.staking.stakingProxy.address, - DeploymentManager.protocolFee, - deployment.assetDataEncoder - .ERC20Token(deployment.tokens.weth.address) - .getABIEncodedTransactionData(), - ); - } - } - - return localBalanceStore; - } - function expectedFillEvent(order: SignedOrder): ExchangeFillEventArgs { return { makerAddress: order.makerAddress, @@ -201,7 +137,14 @@ blockchainTests.resets('Coordinator integration tests', env => { .executeTransaction(transaction, taker.address, transaction.signature, [approval.signature]) .awaitTransactionSuccessAsync({ from: taker.address, value: DeploymentManager.protocolFee }); - const expectedBalances = await simulateFillsAsync([order], txReceipt, DeploymentManager.protocolFee); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills( + [order], + taker.address, + txReceipt, + deployment, + DeploymentManager.protocolFee, + ); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill); @@ -212,7 +155,14 @@ blockchainTests.resets('Coordinator integration tests', env => { .executeTransaction(transaction, feeRecipient.address, transaction.signature, []) .awaitTransactionSuccessAsync({ from: feeRecipient.address, value: DeploymentManager.protocolFee }); - const expectedBalances = await simulateFillsAsync([order], txReceipt, DeploymentManager.protocolFee); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills( + [order], + taker.address, + txReceipt, + deployment, + DeploymentManager.protocolFee, + ); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill); @@ -226,9 +176,12 @@ blockchainTests.resets('Coordinator integration tests', env => { value: DeploymentManager.protocolFee.plus(1), }); - const expectedBalances = await simulateFillsAsync( + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills( [order], + taker.address, txReceipt, + deployment, DeploymentManager.protocolFee.plus(1), ); await balanceStore.updateBalancesAsync(); @@ -241,7 +194,8 @@ blockchainTests.resets('Coordinator integration tests', env => { .executeTransaction(transaction, feeRecipient.address, transaction.signature, []) .awaitTransactionSuccessAsync({ from: feeRecipient.address }); - const expectedBalances = await simulateFillsAsync([order], txReceipt); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills([order], taker.address, txReceipt, deployment); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill); @@ -252,7 +206,8 @@ blockchainTests.resets('Coordinator integration tests', env => { .executeTransaction(transaction, feeRecipient.address, transaction.signature, []) .awaitTransactionSuccessAsync({ from: feeRecipient.address, value: new BigNumber(1) }); - const expectedBalances = await simulateFillsAsync([order], txReceipt, new BigNumber(1)); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills([order], taker.address, txReceipt, deployment, new BigNumber(1)); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill); @@ -317,7 +272,8 @@ blockchainTests.resets('Coordinator integration tests', env => { .executeTransaction(transaction, taker.address, transaction.signature, [approval.signature]) .awaitTransactionSuccessAsync({ from: taker.address, value }); - const expectedBalances = await simulateFillsAsync(orders, txReceipt, value); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills(orders, taker.address, txReceipt, deployment, value); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill); @@ -329,7 +285,8 @@ blockchainTests.resets('Coordinator integration tests', env => { .executeTransaction(transaction, feeRecipient.address, transaction.signature, []) .awaitTransactionSuccessAsync({ from: feeRecipient.address, value }); - const expectedBalances = await simulateFillsAsync(orders, txReceipt, value); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills(orders, taker.address, txReceipt, deployment, value); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill); @@ -341,7 +298,8 @@ blockchainTests.resets('Coordinator integration tests', env => { .executeTransaction(transaction, feeRecipient.address, transaction.signature, []) .awaitTransactionSuccessAsync({ from: feeRecipient.address, value }); - const expectedBalances = await simulateFillsAsync(orders, txReceipt, value); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills(orders, taker.address, txReceipt, deployment, value); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill); diff --git a/contracts/integrations/test/exchange/exchange_wrapper_test.ts b/contracts/integrations/test/exchange/exchange_wrapper_test.ts index ef057c6539..c97221aba0 100644 --- a/contracts/integrations/test/exchange/exchange_wrapper_test.ts +++ b/contracts/integrations/test/exchange/exchange_wrapper_test.ts @@ -132,13 +132,9 @@ blockchainTests.resets('Exchange wrappers', env => { isValid: boolean; } - async function simulateFillAsync( - signedOrder: SignedOrder, - expectedFillResults: FillResults, - shouldUseWeth: boolean, - ): Promise { + function simulateFill(signedOrder: SignedOrder, expectedFillResults: FillResults, shouldUseWeth: boolean): void { // taker -> maker - await localBalances.transferAssetAsync( + localBalances.transferAsset( taker.address, maker.address, expectedFillResults.takerAssetFilledAmount, @@ -146,7 +142,7 @@ blockchainTests.resets('Exchange wrappers', env => { ); // maker -> taker - await localBalances.transferAssetAsync( + localBalances.transferAsset( maker.address, taker.address, expectedFillResults.makerAssetFilledAmount, @@ -154,7 +150,7 @@ blockchainTests.resets('Exchange wrappers', env => { ); // maker -> feeRecipient - await localBalances.transferAssetAsync( + localBalances.transferAsset( maker.address, feeRecipient, expectedFillResults.makerFeePaid, @@ -162,7 +158,7 @@ blockchainTests.resets('Exchange wrappers', env => { ); // taker -> feeRecipient - await localBalances.transferAssetAsync( + localBalances.transferAsset( taker.address, feeRecipient, expectedFillResults.takerFeePaid, @@ -171,7 +167,7 @@ blockchainTests.resets('Exchange wrappers', env => { // taker -> protocol fees if (shouldUseWeth) { - await localBalances.transferAssetAsync( + localBalances.transferAsset( taker.address, deployment.staking.stakingProxy.address, expectedFillResults.protocolFeePaid, @@ -343,7 +339,7 @@ blockchainTests.resets('Exchange wrappers', env => { const shouldPayWethFees = DeploymentManager.protocolFee.gt(value); // Simulate filling the order - await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees); + simulateFill(signedOrder, expectedFillResults, shouldPayWethFees); // Ensure that the correct logs were emitted and that the balances are accurate. await assertResultsAsync(receipt, [{ signedOrder, expectedFillResults, shouldPayWethFees }]); @@ -444,7 +440,7 @@ blockchainTests.resets('Exchange wrappers', env => { } fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees }); - await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees); + simulateFill(signedOrder, expectedFillResults, shouldPayWethFees); } const contractFn = deployment.exchange.batchFillOrders( @@ -506,7 +502,7 @@ blockchainTests.resets('Exchange wrappers', env => { } fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees }); - await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees); + simulateFill(signedOrder, expectedFillResults, shouldPayWethFees); } const contractFn = deployment.exchange.batchFillOrKillOrders( @@ -600,7 +596,7 @@ blockchainTests.resets('Exchange wrappers', env => { } fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees }); - await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees); + simulateFill(signedOrder, expectedFillResults, shouldPayWethFees); } else { totalFillResults.push(nullFillResults); } @@ -714,7 +710,7 @@ blockchainTests.resets('Exchange wrappers', env => { takerFillAmount, ); - await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees); + simulateFill(signedOrder, expectedFillResults, shouldPayWethFees); fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees }); totalFillResults = addFillResults(totalFillResults, expectedFillResults); @@ -912,7 +908,7 @@ blockchainTests.resets('Exchange wrappers', env => { makerAssetBought, ); - await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees); + simulateFill(signedOrder, expectedFillResults, shouldPayWethFees); fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees }); totalFillResults = addFillResults(totalFillResults, expectedFillResults); diff --git a/contracts/integrations/test/exchange/fill_order_wrapper.ts b/contracts/integrations/test/exchange/fill_order_wrapper.ts index d8d996aac2..9f2b85cf98 100644 --- a/contracts/integrations/test/exchange/fill_order_wrapper.ts +++ b/contracts/integrations/test/exchange/fill_order_wrapper.ts @@ -99,7 +99,7 @@ export class FillOrderWrapper { await this._assertOrderStateAsync(signedOrder, initTakerAssetFilledAmount); // Simulate and execute fill then assert outputs const [fillResults, fillEvent, txReceipt] = await this._fillOrderAsync(signedOrder, from, opts); - const [simulatedFillResults, simulatedFillEvent, simulatedFinalBalanceStore] = await simulateFillOrderAsync( + const [simulatedFillResults, simulatedFillEvent, simulatedFinalBalanceStore] = simulateFillOrder( txReceipt, signedOrder, from, @@ -169,13 +169,13 @@ export class FillOrderWrapper { * @param initBalanceStore Account balances prior to the fill. * @return The expected account balances, fill results, and fill events. */ -async function simulateFillOrderAsync( +function simulateFillOrder( txReceipt: TransactionReceiptWithDecodedLogs, signedOrder: SignedOrder, takerAddress: string, initBalanceStore: BalanceStore, opts: { takerAssetFillAmount?: BigNumber } = {}, -): Promise<[FillResults, FillEventArgs, BalanceStore]> { +): [FillResults, FillEventArgs, BalanceStore] { const balanceStore = LocalBalanceStore.create(initBalanceStore); const takerAssetFillAmount = opts.takerAssetFillAmount !== undefined ? opts.takerAssetFillAmount : signedOrder.takerAssetAmount; @@ -188,28 +188,28 @@ async function simulateFillOrderAsync( ); const fillEvent = FillOrderWrapper.simulateFillEvent(signedOrder, takerAddress, fillResults); // Taker -> Maker - await balanceStore.transferAssetAsync( + balanceStore.transferAsset( takerAddress, signedOrder.makerAddress, fillResults.takerAssetFilledAmount, signedOrder.takerAssetData, ); // Maker -> Taker - await balanceStore.transferAssetAsync( + balanceStore.transferAsset( signedOrder.makerAddress, takerAddress, fillResults.makerAssetFilledAmount, signedOrder.makerAssetData, ); // Taker -> Fee Recipient - await balanceStore.transferAssetAsync( + balanceStore.transferAsset( takerAddress, signedOrder.feeRecipientAddress, fillResults.takerFeePaid, signedOrder.takerFeeAssetData, ); // Maker -> Fee Recipient - await balanceStore.transferAssetAsync( + balanceStore.transferAsset( signedOrder.makerAddress, signedOrder.feeRecipientAddress, fillResults.makerFeePaid, diff --git a/contracts/integrations/test/exchange/fillorder_test.ts b/contracts/integrations/test/exchange/fillorder_test.ts index 7e8bf72457..3c9b44ed48 100644 --- a/contracts/integrations/test/exchange/fillorder_test.ts +++ b/contracts/integrations/test/exchange/fillorder_test.ts @@ -112,51 +112,6 @@ blockchainTests.resets('fillOrder integration tests', env => { Actor.count = 0; }); - async function simulateFillAsync( - order: SignedOrder, - txReceipt: TransactionReceiptWithDecodedLogs, - msgValue?: BigNumber, - ): Promise { - let remainingValue = msgValue !== undefined ? msgValue : DeploymentManager.protocolFee; - const localBalanceStore = LocalBalanceStore.create(balanceStore); - // Transaction gas cost - localBalanceStore.burnGas(txReceipt.from, DeploymentManager.gasPrice.times(txReceipt.gasUsed)); - - // Taker -> Maker - await localBalanceStore.transferAssetAsync( - taker.address, - maker.address, - order.takerAssetAmount, - order.takerAssetData, - ); - // Maker -> Taker - await localBalanceStore.transferAssetAsync( - maker.address, - taker.address, - order.makerAssetAmount, - order.makerAssetData, - ); - - // Protocol fee - if (remainingValue.isGreaterThanOrEqualTo(DeploymentManager.protocolFee)) { - localBalanceStore.sendEth( - txReceipt.from, - deployment.staking.stakingProxy.address, - DeploymentManager.protocolFee, - ); - remainingValue = remainingValue.minus(DeploymentManager.protocolFee); - } else { - await localBalanceStore.transferAssetAsync( - taker.address, - deployment.staking.stakingProxy.address, - DeploymentManager.protocolFee, - deployment.assetDataEncoder.ERC20Token(deployment.tokens.weth.address).getABIEncodedTransactionData(), - ); - } - - return localBalanceStore; - } - function verifyFillEvents(order: SignedOrder, receipt: TransactionReceiptWithDecodedLogs): void { // Ensure that the fill event was correct. verifyEvents( @@ -207,7 +162,8 @@ blockchainTests.resets('fillOrder integration tests', env => { const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount); // Check balances - const expectedBalances = await simulateFillAsync(order, receipt); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills([order], taker.address, receipt, deployment, DeploymentManager.protocolFee); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); @@ -233,7 +189,8 @@ blockchainTests.resets('fillOrder integration tests', env => { const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount); // Check balances - const expectedBalances = await simulateFillAsync(order, receipt); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills([order], taker.address, receipt, deployment, DeploymentManager.protocolFee); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); @@ -310,7 +267,7 @@ blockchainTests.resets('fillOrder integration tests', env => { const [finalizePoolReceipt] = await delegator.finalizePoolsAsync([poolId]); // Check balances - await expectedBalances.transferAssetAsync( + expectedBalances.transferAsset( deployment.staking.stakingProxy.address, operator.address, operatorReward, @@ -371,7 +328,8 @@ blockchainTests.resets('fillOrder integration tests', env => { const order = await maker.signOrderAsync(); const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount, { value: constants.ZERO_AMOUNT }); const rewardsAvailable = DeploymentManager.protocolFee; - const expectedBalances = await simulateFillAsync(order, receipt, constants.ZERO_AMOUNT); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills([order], taker.address, receipt, deployment); // End the epoch. This should wrap the staking proxy's ETH balance. const endEpochReceipt = await delegator.endEpochAsync(); @@ -392,7 +350,7 @@ blockchainTests.resets('fillOrder integration tests', env => { const [finalizePoolReceipt] = await delegator.finalizePoolsAsync([poolId]); // Check balances - await expectedBalances.transferAssetAsync( + expectedBalances.transferAsset( deployment.staking.stakingProxy.address, operator.address, operatorReward, diff --git a/contracts/integrations/test/exchange/match_order_tester.ts b/contracts/integrations/test/exchange/match_order_tester.ts index b3decd724d..58768cfa81 100644 --- a/contracts/integrations/test/exchange/match_order_tester.ts +++ b/contracts/integrations/test/exchange/match_order_tester.ts @@ -296,7 +296,7 @@ export class MatchOrderTester { localBalanceStore.burnGas(takerAddress, DeploymentManager.gasPrice.times(transactionReceipt.gasUsed)); // Simulate the fill. - const expectedMatchResults = await this._simulateMatchOrdersAsync( + const expectedMatchResults = this._simulateMatchOrders( orders, takerAddress, toFullMatchTransferAmounts(expectedTransferAmounts), @@ -319,12 +319,12 @@ export class MatchOrderTester { * @param localBalanceStore The balance store to use for the simulation. * @return The new account balances and fill events that occurred during the match. */ - protected async _simulateMatchOrdersAsync( + protected _simulateMatchOrders( orders: MatchedOrders, takerAddress: string, transferAmounts: MatchTransferAmounts, localBalanceStore: LocalBalanceStore, - ): Promise { + ): MatchResults { // prettier-ignore const matchResults = { orders: { @@ -343,7 +343,7 @@ export class MatchOrderTester { }; // Right maker asset -> left maker - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( orders.rightOrder.makerAddress, orders.leftOrder.makerAddress, transferAmounts.rightMakerAssetBoughtByLeftMakerAmount, @@ -352,7 +352,7 @@ export class MatchOrderTester { if (orders.leftOrder.makerAddress !== orders.leftOrder.feeRecipientAddress) { // Left maker fees - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( orders.leftOrder.makerAddress, orders.leftOrder.feeRecipientAddress, transferAmounts.leftMakerFeeAssetPaidByLeftMakerAmount, @@ -361,7 +361,7 @@ export class MatchOrderTester { } // Left maker asset -> right maker - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( orders.leftOrder.makerAddress, orders.rightOrder.makerAddress, transferAmounts.leftMakerAssetBoughtByRightMakerAmount, @@ -370,7 +370,7 @@ export class MatchOrderTester { if (orders.rightOrder.makerAddress !== orders.rightOrder.feeRecipientAddress) { // Right maker fees - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( orders.rightOrder.makerAddress, orders.rightOrder.feeRecipientAddress, transferAmounts.rightMakerFeeAssetPaidByRightMakerAmount, @@ -379,7 +379,7 @@ export class MatchOrderTester { } // Left taker profit - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( orders.leftOrder.makerAddress, takerAddress, transferAmounts.leftMakerAssetReceivedByTakerAmount, @@ -387,7 +387,7 @@ export class MatchOrderTester { ); // Right taker profit - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( orders.rightOrder.makerAddress, takerAddress, transferAmounts.rightMakerAssetReceivedByTakerAmount, @@ -395,7 +395,7 @@ export class MatchOrderTester { ); // Left taker fees - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( takerAddress, orders.leftOrder.feeRecipientAddress, transferAmounts.leftTakerFeeAssetPaidByTakerAmount, @@ -403,7 +403,7 @@ export class MatchOrderTester { ); // Right taker fees - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( takerAddress, orders.rightOrder.feeRecipientAddress, transferAmounts.rightTakerFeeAssetPaidByTakerAmount, @@ -424,13 +424,13 @@ export class MatchOrderTester { this._deployment.staking.stakingProxy.address, transferAmounts.rightProtocolFeePaidByTakerInEthAmount, ); - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( takerAddress, this._deployment.staking.stakingProxy.address, transferAmounts.leftProtocolFeePaidByTakerInWethAmount, wethAssetData, ); - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( takerAddress, this._deployment.staking.stakingProxy.address, transferAmounts.rightProtocolFeePaidByTakerInWethAmount, @@ -513,7 +513,7 @@ export class MatchOrderTester { // Add the latest match to the batch match results batchMatchResults.matches.push( - await this._simulateMatchOrdersAsync( + this._simulateMatchOrders( matchedOrders, takerAddress, toFullMatchTransferAmounts(transferAmounts[i]), diff --git a/contracts/integrations/test/exchange/transaction_protocol_fee_test.ts b/contracts/integrations/test/exchange/transaction_protocol_fee_test.ts new file mode 100644 index 0000000000..280cbeabc4 --- /dev/null +++ b/contracts/integrations/test/exchange/transaction_protocol_fee_test.ts @@ -0,0 +1,414 @@ +// 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 { SignedOrder, SignedZeroExTransaction } from '@0x/types'; +import { BigNumber } from '@0x/utils'; + +import { Actor } from '../framework/actors/base'; +import { FeeRecipient } from '../framework/actors/fee_recipient'; +import { Maker } from '../framework/actors/maker'; +import { Taker } from '../framework/actors/taker'; +import { actorAddressesByName } from '../framework/actors/utils'; +import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store'; +import { LocalBalanceStore } from '../framework/balances/local_balance_store'; +import { DeploymentManager } from '../framework/deployment_manager'; + +// tslint:disable:no-unnecessary-type-assertion +blockchainTests.resets('Transaction <> protocol fee integration tests', env => { + let deployment: DeploymentManager; + let balanceStore: BlockchainBalanceStore; + + let maker: Maker; + let feeRecipient: FeeRecipient; + let alice: Taker; + let bob: Taker; + let charlie: Taker; + + let order: SignedOrder; // All orders will have the same fields, modulo salt and expiration time + let transactionA: SignedZeroExTransaction; // fillOrder transaction signed by Alice + let transactionB: SignedZeroExTransaction; // fillOrder transaction signed by Bob + let transactionC: SignedZeroExTransaction; // fillOrder transaction signed by Charlie + + before(async () => { + deployment = await DeploymentManager.deployAsync(env, { + numErc20TokensToDeploy: 4, + numErc721TokensToDeploy: 0, + numErc1155TokensToDeploy: 0, + }); + const assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider); + const [makerToken, takerToken, makerFeeToken, takerFeeToken] = deployment.tokens.erc20; + + alice = new Taker({ name: 'Alice', deployment }); + bob = new Taker({ name: 'Bob', deployment }); + charlie = new Taker({ name: 'Charlie', deployment }); + feeRecipient = new FeeRecipient({ + name: 'Fee recipient', + deployment, + }); + maker = new Maker({ + name: 'Maker', + deployment, + orderConfig: { + feeRecipientAddress: feeRecipient.address, + makerAssetData: assetDataEncoder.ERC20Token(makerToken.address).getABIEncodedTransactionData(), + takerAssetData: assetDataEncoder.ERC20Token(takerToken.address).getABIEncodedTransactionData(), + makerFeeAssetData: assetDataEncoder.ERC20Token(makerFeeToken.address).getABIEncodedTransactionData(), + takerFeeAssetData: assetDataEncoder.ERC20Token(takerFeeToken.address).getABIEncodedTransactionData(), + }, + }); + + for (const taker of [alice, bob, charlie]) { + await taker.configureERC20TokenAsync(takerToken); + await taker.configureERC20TokenAsync(takerFeeToken); + await taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address); + } + await maker.configureERC20TokenAsync(makerToken); + await maker.configureERC20TokenAsync(makerFeeToken); + + balanceStore = new BlockchainBalanceStore( + { + ...actorAddressesByName([alice, bob, charlie, maker, feeRecipient]), + StakingProxy: deployment.staking.stakingProxy.address, + }, + { erc20: { makerToken, takerToken, makerFeeToken, takerFeeToken, wETH: deployment.tokens.weth } }, + {}, + ); + await balanceStore.updateBalancesAsync(); + + order = await maker.signOrderAsync(); + let data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + transactionA = await alice.signTransactionAsync({ data }); + + order = await maker.signOrderAsync(); + data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + transactionB = await bob.signTransactionAsync({ data }); + + order = await maker.signOrderAsync(); + data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + transactionC = await charlie.signTransactionAsync({ data }); + }); + + after(async () => { + Actor.count = 0; + }); + + const REFUND_AMOUNT = new BigNumber(1); + + describe('executeTransaction', () => { + const ETH_FEE_WITH_REFUND = DeploymentManager.protocolFee.plus(REFUND_AMOUNT); + + let expectedBalances: LocalBalanceStore; + beforeEach(async () => { + await balanceStore.updateBalancesAsync(); + expectedBalances = LocalBalanceStore.create(balanceStore); + }); + afterEach(async () => { + await balanceStore.updateBalancesAsync(); + balanceStore.assertEquals(expectedBalances); + }); + + describe('Simple', () => { + it('Alice executeTransaction => Alice fillOrder; protocol fee in ETH', async () => { + const txReceipt = await deployment.exchange + .executeTransaction(transactionA, transactionA.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND }); + expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, ETH_FEE_WITH_REFUND); + }); + it('Alice executeTransaction => Bob fillOrder; protocol fee in ETH', async () => { + const txReceipt = await deployment.exchange + .executeTransaction(transactionB, transactionB.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND }); + expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, ETH_FEE_WITH_REFUND); + }); + it('Alice executeTransaction => Alice fillOrder; protocol fee in wETH', async () => { + const txReceipt = await deployment.exchange + .executeTransaction(transactionA, transactionA.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, REFUND_AMOUNT); + }); + it('Alice executeTransaction => Bob fillOrder; protocol fee in wETH', async () => { + const txReceipt = await deployment.exchange + .executeTransaction(transactionB, transactionB.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, REFUND_AMOUNT); + }); + it('Alice executeTransaction => Alice batchFillOrders; mixed protocol fees', async () => { + const orders = [order, await maker.signOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.BatchFillOrders, orders); + const batchFillTransaction = await alice.signTransactionAsync({ data }); + const txReceipt = await deployment.exchange + .executeTransaction(batchFillTransaction, batchFillTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND }); + expectedBalances.simulateFills(orders, alice.address, txReceipt, deployment, ETH_FEE_WITH_REFUND); + }); + it('Alice executeTransaction => Bob batchFillOrders; mixed protocol fees', async () => { + const orders = [order, await maker.signOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.BatchFillOrders, orders); + const batchFillTransaction = await bob.signTransactionAsync({ data }); + const txReceipt = await deployment.exchange + .executeTransaction(batchFillTransaction, batchFillTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND }); + expectedBalances.simulateFills(orders, bob.address, txReceipt, deployment, ETH_FEE_WITH_REFUND); + }); + }); + describe('Nested', () => { + it('Alice executeTransaction => Alice executeTransaction => Alice fillOrder; protocol fee in ETH', async () => { + const recursiveData = deployment.exchange + .executeTransaction(transactionA, transactionA.signature) + .getABIEncodedTransactionData(); + const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData }); + const txReceipt = await deployment.exchange + .executeTransaction(recursiveTransaction, recursiveTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND }); + expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, ETH_FEE_WITH_REFUND); + }); + it('Alice executeTransaction => Alice executeTransaction => Bob fillOrder; protocol fee in ETH', async () => { + const recursiveData = deployment.exchange + .executeTransaction(transactionB, transactionB.signature) + .getABIEncodedTransactionData(); + const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData }); + const txReceipt = await deployment.exchange + .executeTransaction(recursiveTransaction, recursiveTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND }); + expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, ETH_FEE_WITH_REFUND); + }); + it('Alice executeTransaction => Alice executeTransaction => Alice fillOrder; protocol fee in wETH', async () => { + const recursiveData = deployment.exchange + .executeTransaction(transactionA, transactionA.signature) + .getABIEncodedTransactionData(); + const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData }); + const txReceipt = await deployment.exchange + .executeTransaction(recursiveTransaction, recursiveTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, REFUND_AMOUNT); + }); + it('Alice executeTransaction => Alice executeTransaction => Bob fillOrder; protocol fee in wETH', async () => { + const recursiveData = deployment.exchange + .executeTransaction(transactionB, transactionB.signature) + .getABIEncodedTransactionData(); + const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData }); + const txReceipt = await deployment.exchange + .executeTransaction(recursiveTransaction, recursiveTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, REFUND_AMOUNT); + }); + }); + }); + describe('batchExecuteTransactions', () => { + let expectedBalances: LocalBalanceStore; + beforeEach(async () => { + await balanceStore.updateBalancesAsync(); + expectedBalances = LocalBalanceStore.create(balanceStore); + }); + afterEach(async () => { + await balanceStore.updateBalancesAsync(); + balanceStore.assertEquals(expectedBalances); + }); + + describe('Simple', () => { + // All orders' protocol fees paid in ETH by sender + const ETH_FEES_WITH_REFUND = DeploymentManager.protocolFee.times(3).plus(REFUND_AMOUNT); + // First order's protocol fee paid in ETH by sender, the other two paid in WETH by their respective takers + const MIXED_FEES_WITH_REFUND = DeploymentManager.protocolFee.times(1).plus(REFUND_AMOUNT); + + it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder; protocol fees in ETH', async () => { + const transactions = [transactionA, transactionB, transactionC]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order], + [alice.address, bob.address, charlie.address], + txReceipt, + deployment, + ETH_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, Alice fillOrder, Charlie fillOrder; protocol fees in ETH', async () => { + const transactions = [transactionB, transactionA, transactionC]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order], + [bob.address, alice.address, charlie.address], + txReceipt, + deployment, + ETH_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, Alice fillOrder; protocol fees in ETH', async () => { + const transactions = [transactionB, transactionC, transactionA]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order], + [bob.address, charlie.address, alice.address], + txReceipt, + deployment, + ETH_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder; protocol fees in wETH', async () => { + const transactions = [transactionA, transactionB, transactionC]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + expectedBalances.simulateFills( + [order, order, order], + [alice.address, bob.address, charlie.address], + txReceipt, + deployment, + REFUND_AMOUNT, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, Alice fillOrder, Charlie fillOrder; protocol fees in wETH', async () => { + const transactions = [transactionB, transactionA, transactionC]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + expectedBalances.simulateFills( + [order, order, order], + [bob.address, alice.address, charlie.address], + txReceipt, + deployment, + REFUND_AMOUNT, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, Alice fillOrder; protocol fees in wETH', async () => { + const transactions = [transactionB, transactionC, transactionA]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + expectedBalances.simulateFills( + [order, order, order], + [bob.address, charlie.address, alice.address], + txReceipt, + deployment, + REFUND_AMOUNT, + ); + }); + it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder; mixed protocol fees', async () => { + const transactions = [transactionA, transactionB, transactionC]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .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 => Bob fillOrder, Alice fillOrder, Charlie fillOrder; mixed protocol fees', async () => { + const transactions = [transactionB, transactionA, transactionC]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order], + [bob.address, alice.address, charlie.address], + txReceipt, + deployment, + MIXED_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, Alice fillOrder; mixed protocol fees', async () => { + const transactions = [transactionB, transactionC, transactionA]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order], + [bob.address, charlie.address, alice.address], + txReceipt, + deployment, + MIXED_FEES_WITH_REFUND, + ); + }); + }); + describe('Nested', () => { + // First two orders' protocol fees paid in ETH by sender, the other three 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 + let nestedTransaction: SignedZeroExTransaction; + // Second fillOrder transaction signed by Bob + let transactionB2: SignedZeroExTransaction; + // Second fillOrder transaction signed by Charlie + let transactionC2: SignedZeroExTransaction; + + before(async () => { + let newOrder = await maker.signOrderAsync(); + let data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [newOrder]); + transactionB2 = await bob.signTransactionAsync({ data }); + + newOrder = await maker.signOrderAsync(); + data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [newOrder]); + transactionC2 = await charlie.signTransactionAsync({ data }); + + const transactions = [transactionA, transactionB, transactionC]; + const signatures = transactions.map(tx => tx.signature); + const recursiveData = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .getABIEncodedTransactionData(); + nestedTransaction = await alice.signTransactionAsync({ data: recursiveData }); + }); + + 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); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order, order, order], + [alice.address, bob.address, charlie.address, bob.address, charlie.address], + txReceipt, + deployment, + MIXED_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, nested batchExecuteTransactions, Charlie fillOrder; mixed protocol fees', async () => { + const transactions = [transactionB2, nestedTransaction, transactionC2]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order, order, order], + [bob.address, alice.address, bob.address, charlie.address, charlie.address], + txReceipt, + deployment, + MIXED_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, nested batchExecuteTransactions; mixed protocol fees', async () => { + const transactions = [transactionB2, transactionC2, nestedTransaction]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order, order, order], + [bob.address, charlie.address, alice.address, bob.address, charlie.address], + txReceipt, + deployment, + MIXED_FEES_WITH_REFUND, + ); + }); + }); + }); +}); diff --git a/contracts/integrations/test/exchange/transaction_test.ts b/contracts/integrations/test/exchange/transaction_test.ts index 8bbce8ba1e..2be1b04d30 100644 --- a/contracts/integrations/test/exchange/transaction_test.ts +++ b/contracts/integrations/test/exchange/transaction_test.ts @@ -637,10 +637,7 @@ blockchainTests.resets('Transaction integration tests', env => { const transaction1 = await takers[0].signTransactionAsync({ data: data1 }); const transaction2 = await takers[1].signTransactionAsync({ data: data2 }); const transactionReceipt = await deployment.exchange - .batchExecuteTransactions( - [transaction1, transaction2], - [transaction1.signature, constants.NULL_BYTES], - ) + .batchExecuteTransactions([transaction1, transaction2], [transaction1.signature, constants.NULL_BYTES]) .awaitTransactionSuccessAsync({ from: takers[1].address }); verifyEventsFromLogs( @@ -677,14 +674,8 @@ blockchainTests.resets('Transaction integration tests', env => { [transaction1.signature, transaction2.signature], ) .callAsync({ from: sender.address }); - const fillResults1: FillResults = deployment.exchange.getABIDecodedReturnData( - 'fillOrder', - returnData[0], - ); - const fillResults2: FillResults = deployment.exchange.getABIDecodedReturnData( - 'fillOrder', - returnData[1], - ); + const fillResults1: FillResults = deployment.exchange.getABIDecodedReturnData('fillOrder', returnData[0]); + const fillResults2: FillResults = deployment.exchange.getABIDecodedReturnData('fillOrder', returnData[1]); expect(fillResults1).to.deep.equal( ReferenceFunctions.calculateFillResults( order1, @@ -706,9 +697,7 @@ blockchainTests.resets('Transaction integration tests', env => { const order1 = await maker.signOrderAsync(); const order2 = await maker.signOrderAsync(); const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); - const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [ - order2, - ]); + const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order2]); const transaction1 = await takers[0].signTransactionAsync({ data: data1 }); const transaction2 = await maker.signTransactionAsync({ data: data2 }); const transactionReceipt = await deployment.exchange @@ -750,9 +739,7 @@ blockchainTests.resets('Transaction integration tests', env => { const order1 = await maker.signOrderAsync(); const order2 = await maker.signOrderAsync(); const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); - const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [ - order2, - ]); + const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order2]); const transaction1 = await takers[0].signTransactionAsync({ data: data1 }); const transaction2 = await maker.signTransactionAsync({ data: data2 }); const returnData = await deployment.exchange @@ -761,10 +748,7 @@ blockchainTests.resets('Transaction integration tests', env => { [transaction1.signature, transaction2.signature], ) .callAsync({ from: sender.address }); - const fillResults: FillResults = deployment.exchange.getABIDecodedReturnData( - 'fillOrder', - returnData[0], - ); + const fillResults: FillResults = deployment.exchange.getABIDecodedReturnData('fillOrder', returnData[0]); expect(fillResults).to.deep.equal( ReferenceFunctions.calculateFillResults( order1, diff --git a/contracts/integrations/test/forwarder/forwarder_test.ts b/contracts/integrations/test/forwarder/forwarder_test.ts index 3c414da4a3..077267e9ab 100644 --- a/contracts/integrations/test/forwarder/forwarder_test.ts +++ b/contracts/integrations/test/forwarder/forwarder_test.ts @@ -511,20 +511,10 @@ blockchainTests('Forwarder integration tests', env => { // Compute expected balances const expectedBalances = LocalBalanceStore.create(balanceStore); - await expectedBalances.transferAssetAsync( - maker.address, - taker.address, - makerAssetFillAmount, - makerAssetData, - ); + expectedBalances.transferAsset(maker.address, taker.address, makerAssetFillAmount, makerAssetData); expectedBalances.wrapEth(taker.address, deployment.tokens.weth.address, ethValue); - await expectedBalances.transferAssetAsync( - taker.address, - maker.address, - primaryTakerAssetFillAmount, - wethAssetData, - ); - await expectedBalances.transferAssetAsync( + expectedBalances.transferAsset(taker.address, maker.address, primaryTakerAssetFillAmount, wethAssetData); + expectedBalances.transferAsset( taker.address, deployment.staking.stakingProxy.address, DeploymentManager.protocolFee, @@ -568,24 +558,14 @@ blockchainTests('Forwarder integration tests', env => { // Compute expected balances const expectedBalances = LocalBalanceStore.create(balanceStore); - await expectedBalances.transferAssetAsync( - maker.address, - taker.address, - makerAssetFillAmount, - makerAssetData, - ); + expectedBalances.transferAsset(maker.address, taker.address, makerAssetFillAmount, makerAssetData); expectedBalances.wrapEth( taker.address, deployment.tokens.weth.address, takerAssetFillAmount.plus(DeploymentManager.protocolFee), ); - await expectedBalances.transferAssetAsync( - taker.address, - maker.address, - takerAssetFillAmount, - wethAssetData, - ); - await expectedBalances.transferAssetAsync( + expectedBalances.transferAsset(taker.address, maker.address, takerAssetFillAmount, wethAssetData); + expectedBalances.transferAsset( taker.address, deployment.staking.stakingProxy.address, DeploymentManager.protocolFee, diff --git a/contracts/integrations/test/forwarder/forwarder_test_factory.ts b/contracts/integrations/test/forwarder/forwarder_test_factory.ts index 7d77641d4f..259c745400 100644 --- a/contracts/integrations/test/forwarder/forwarder_test_factory.ts +++ b/contracts/integrations/test/forwarder/forwarder_test_factory.ts @@ -207,7 +207,7 @@ export class ForwarderTestFactory { continue; } - const { wethSpentAmount, makerAssetAcquiredAmount } = await this._simulateSingleFillAsync( + const { wethSpentAmount, makerAssetAcquiredAmount } = this._simulateSingleFill( balances, order, ordersInfoBefore[i].orderTakerAssetFilledAmount, @@ -232,13 +232,13 @@ export class ForwarderTestFactory { return { ...currentTotal, balances }; } - private async _simulateSingleFillAsync( + private _simulateSingleFill( balances: LocalBalanceStore, order: SignedOrder, takerAssetFilled: BigNumber, fillFraction: number, bridgeExcessBuyAmount: BigNumber, - ): Promise { + ): ForwarderFillState { let { makerAssetAmount, takerAssetAmount, makerFee, takerFee } = order; makerAssetAmount = makerAssetAmount.times(fillFraction).integerValue(BigNumber.ROUND_CEIL); takerAssetAmount = takerAssetAmount.times(fillFraction).integerValue(BigNumber.ROUND_CEIL); @@ -273,42 +273,22 @@ export class ForwarderTestFactory { // (In reality this is done all at once, but we simulate it order by order) // Maker -> Forwarder - await balances.transferAssetAsync( - order.makerAddress, - this._forwarder.address, - makerAssetAmount, - order.makerAssetData, - ); + balances.transferAsset(order.makerAddress, this._forwarder.address, makerAssetAmount, order.makerAssetData); // Maker -> Order fee recipient - await balances.transferAssetAsync( - order.makerAddress, - order.feeRecipientAddress, - makerFee, - order.makerFeeAssetData, - ); + balances.transferAsset(order.makerAddress, order.feeRecipientAddress, makerFee, order.makerFeeAssetData); // Forwarder -> Maker - await balances.transferAssetAsync( - this._forwarder.address, - order.makerAddress, - takerAssetAmount, - order.takerAssetData, - ); + balances.transferAsset(this._forwarder.address, order.makerAddress, takerAssetAmount, order.takerAssetData); // Forwarder -> Order fee recipient - await balances.transferAssetAsync( - this._forwarder.address, - order.feeRecipientAddress, - takerFee, - order.takerFeeAssetData, - ); + balances.transferAsset(this._forwarder.address, order.feeRecipientAddress, takerFee, order.takerFeeAssetData); // Forwarder pays the protocol fee in WETH - await balances.transferAssetAsync( + balances.transferAsset( this._forwarder.address, this._deployment.staking.stakingProxy.address, DeploymentManager.protocolFee, order.takerAssetData, ); // Forwarder gives acquired maker asset to taker - await balances.transferAssetAsync( + balances.transferAsset( this._forwarder.address, this._taker.address, makerAssetAcquiredAmount, diff --git a/contracts/integrations/test/framework/assertions/stake.ts b/contracts/integrations/test/framework/assertions/stake.ts index 9783a97092..2affd8a194 100644 --- a/contracts/integrations/test/framework/assertions/stake.ts +++ b/contracts/integrations/test/framework/assertions/stake.ts @@ -37,7 +37,7 @@ export function validStakeAssertion( before: async (amount: BigNumber, txData: Partial) => { // Simulates the transfer of ZRX from staker to vault const expectedBalances = LocalBalanceStore.create(balanceStore); - await expectedBalances.transferAssetAsync( + expectedBalances.transferAsset( txData.from as string, zrxVault.address, amount, diff --git a/contracts/integrations/test/framework/assertions/unstake.ts b/contracts/integrations/test/framework/assertions/unstake.ts index 5c3e26c193..5644ba5e39 100644 --- a/contracts/integrations/test/framework/assertions/unstake.ts +++ b/contracts/integrations/test/framework/assertions/unstake.ts @@ -37,7 +37,7 @@ export function validUnstakeAssertion( before: async (amount: BigNumber, txData: Partial) => { // Simulates the transfer of ZRX from vault to staker const expectedBalances = LocalBalanceStore.create(balanceStore); - await expectedBalances.transferAssetAsync( + expectedBalances.transferAsset( zrxVault.address, txData.from as string, amount, diff --git a/contracts/integrations/test/framework/balances/local_balance_store.ts b/contracts/integrations/test/framework/balances/local_balance_store.ts index 235e418f72..2828408cce 100644 --- a/contracts/integrations/test/framework/balances/local_balance_store.ts +++ b/contracts/integrations/test/framework/balances/local_balance_store.ts @@ -1,9 +1,13 @@ import { IAssetDataContract } from '@0x/contracts-asset-proxy'; +import { ReferenceFunctions } from '@0x/contracts-exchange-libs'; import { constants, hexSlice, Numberish, provider } from '@0x/contracts-test-utils'; -import { AssetProxyId } from '@0x/types'; +import { AssetProxyId, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; +import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import * as _ from 'lodash'; +import { DeploymentManager } from '../deployment_manager'; + import { BalanceStore } from './balance_store'; import { TokenContractsByName, TokenOwnersByName } from './types'; @@ -74,12 +78,7 @@ export class LocalBalanceStore extends BalanceStore { * @param amount Amount of asset(s) to transfer * @param assetData Asset data of assets being transferred. */ - public async transferAssetAsync( - fromAddress: string, - toAddress: string, - amount: BigNumber, - assetData: string, - ): Promise { + public transferAsset(fromAddress: string, toAddress: string, amount: BigNumber, assetData: string): void { if (fromAddress === toAddress || amount.isZero()) { return; } @@ -174,7 +173,7 @@ export class LocalBalanceStore extends BalanceStore { >('MultiAsset', assetData); for (const [i, amt] of amounts.entries()) { const nestedAmount = amount.times(amt); - await this.transferAssetAsync(fromAddress, toAddress, nestedAmount, nestedAssetData[i]); + this.transferAsset(fromAddress, toAddress, nestedAmount, nestedAssetData[i]); } break; } @@ -185,4 +184,71 @@ export class LocalBalanceStore extends BalanceStore { throw new Error(`Unhandled asset proxy ID: ${assetProxyId}`); } } + + public simulateFills( + orders: SignedOrder[], + takerAddresses: string[] | string, + txReceipt: TransactionReceiptWithDecodedLogs, + deployment: DeploymentManager, + msgValue: BigNumber = constants.ZERO_AMOUNT, + takerAssetFillAmounts?: BigNumber[], + ): void { + let remainingValue = msgValue; + // Transaction gas cost + this.burnGas(txReceipt.from, DeploymentManager.gasPrice.times(txReceipt.gasUsed)); + + for (const [index, order] of orders.entries()) { + const takerAddress = Array.isArray(takerAddresses) ? takerAddresses[index] : takerAddresses; + const fillResults = ReferenceFunctions.calculateFillResults( + order, + takerAssetFillAmounts ? takerAssetFillAmounts[index] : order.takerAssetAmount, + DeploymentManager.protocolFeeMultiplier, + DeploymentManager.gasPrice, + ); + + // Taker -> Maker + this.transferAsset( + takerAddress, + order.makerAddress, + fillResults.takerAssetFilledAmount, + order.takerAssetData, + ); + // Maker -> Taker + this.transferAsset( + order.makerAddress, + takerAddress, + fillResults.makerAssetFilledAmount, + order.makerAssetData, + ); + // Taker -> Fee Recipient + this.transferAsset( + takerAddress, + order.feeRecipientAddress, + fillResults.takerFeePaid, + order.takerFeeAssetData, + ); + // Maker -> Fee Recipient + this.transferAsset( + order.makerAddress, + order.feeRecipientAddress, + fillResults.makerFeePaid, + order.makerFeeAssetData, + ); + + // Protocol fee + if (remainingValue.isGreaterThanOrEqualTo(fillResults.protocolFeePaid)) { + this.sendEth(txReceipt.from, deployment.staking.stakingProxy.address, fillResults.protocolFeePaid); + remainingValue = remainingValue.minus(fillResults.protocolFeePaid); + } else { + this.transferAsset( + takerAddress, + deployment.staking.stakingProxy.address, + fillResults.protocolFeePaid, + deployment.assetDataEncoder + .ERC20Token(deployment.tokens.weth.address) + .getABIEncodedTransactionData(), + ); + } + } + } }