From c09ac58ac048439f8726dbd9e08a5937afa5629a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 10 Jan 2020 17:09:00 -0800 Subject: [PATCH] Fuzz testing for matchOrders and matchOrdersWithMaximalFill. --- .../test/framework/assertions/fillOrder.ts | 98 ++---------- .../test/framework/assertions/matchOrders.ts | 79 +++++++++ .../assertions/matchOrdersWithMaximalFill.ts | 27 ++++ .../framework/utils/assert_protocol_fee.ts | 118 ++++++++++++++ .../framework/utils/verify_match_events.ts | 151 ++++++++++++++++++ .../test/fuzz_tests/match_orders_test.ts | 83 ++++++++++ 6 files changed, 471 insertions(+), 85 deletions(-) create mode 100644 contracts/integrations/test/framework/assertions/matchOrders.ts create mode 100644 contracts/integrations/test/framework/assertions/matchOrdersWithMaximalFill.ts create mode 100644 contracts/integrations/test/framework/utils/assert_protocol_fee.ts create mode 100644 contracts/integrations/test/framework/utils/verify_match_events.ts create mode 100644 contracts/integrations/test/fuzz_tests/match_orders_test.ts diff --git a/contracts/integrations/test/framework/assertions/fillOrder.ts b/contracts/integrations/test/framework/assertions/fillOrder.ts index 135f168149..24c974649b 100644 --- a/contracts/integrations/test/framework/assertions/fillOrder.ts +++ b/contracts/integrations/test/framework/assertions/fillOrder.ts @@ -1,13 +1,7 @@ import { ERC20TokenEvents, ERC20TokenTransferEventArgs } from '@0x/contracts-erc20'; import { ExchangeEvents, ExchangeFillEventArgs } from '@0x/contracts-exchange'; import { ReferenceFunctions } from '@0x/contracts-exchange-libs'; -import { - AggregatedStats, - constants as stakingConstants, - PoolStats, - StakingEvents, - StakingStakingPoolEarnedRewardsInEpochEventArgs, -} from '@0x/contracts-staking'; +import { AggregatedStats, PoolStats } from '@0x/contracts-staking'; import { expect, orderHashUtils, verifyEvents } from '@0x/contracts-test-utils'; import { FillResults, Order } from '@0x/types'; import { BigNumber } from '@0x/utils'; @@ -18,6 +12,7 @@ import { Maker } from '../actors/maker'; import { filterActorsByRole } from '../actors/utils'; import { DeploymentManager } from '../deployment_manager'; import { SimulationEnvironment } from '../simulation'; +import { assertProtocolFeePaidAsync, getPoolInfoAsync } from '../utils/assert_protocol_fee'; import { FunctionAssertion, FunctionResult } from './function_assertion'; @@ -109,8 +104,8 @@ export function validFillOrderAssertion( deployment: DeploymentManager, simulationEnvironment: SimulationEnvironment, ): FunctionAssertion<[Order, BigNumber, string], FillOrderBeforeInfo | void, FillResults> { - const { stakingWrapper } = deployment.staking; const { actors } = simulationEnvironment; + const expectedProtocolFee = DeploymentManager.protocolFee; return new FunctionAssertion<[Order, BigNumber, string], FillOrderBeforeInfo | void, FillResults>( deployment.exchange, @@ -118,27 +113,9 @@ export function validFillOrderAssertion( { before: async (args: [Order, BigNumber, string]) => { const [order] = args; - const { currentEpoch } = simulationEnvironment; const maker = filterActorsByRole(actors, Maker).find(actor => actor.address === order.makerAddress); - - const poolId = maker!.makerPoolId; - if (poolId === undefined) { - return; - } else { - const poolStats = PoolStats.fromArray( - await stakingWrapper.poolStatsByEpoch(poolId, currentEpoch).callAsync(), - ); - const aggregatedStats = AggregatedStats.fromArray( - await stakingWrapper.aggregatedStatsByEpoch(currentEpoch).callAsync(), - ); - const { currentEpochBalance: poolStake } = await stakingWrapper - .getTotalStakeDelegatedToPool(poolId) - .callAsync(); - const { currentEpochBalance: operatorStake } = await stakingWrapper - .getStakeDelegatedToPoolByOwner(simulationEnvironment.stakingPools[poolId].operator, poolId) - .callAsync(); - return { poolStats, aggregatedStats, poolStake, poolId, operatorStake }; - } + const poolInfo = getPoolInfoAsync(maker!, simulationEnvironment, deployment); + return poolInfo; }, after: async ( beforeInfo: FillOrderBeforeInfo | void, @@ -150,69 +127,20 @@ export function validFillOrderAssertion( expect(result.success, `Error: ${result.data}`).to.be.true(); const [order, fillAmount] = args; - const { currentEpoch } = simulationEnvironment; // Ensure that the correct events were emitted. verifyFillEvents(txData, order, result.receipt!, deployment, fillAmount); - // If the maker is not in a staking pool, there's nothing to check - if (beforeInfo === undefined) { - return; - } - - const expectedPoolStats = { ...beforeInfo.poolStats }; - const expectedAggregatedStats = { ...beforeInfo.aggregatedStats }; - const expectedEvents = []; - - // Refer to `payProtocolFee` - if (beforeInfo.poolStake.isGreaterThanOrEqualTo(stakingConstants.DEFAULT_PARAMS.minimumPoolStake)) { - if (beforeInfo.poolStats.feesCollected.isZero()) { - const membersStakeInPool = beforeInfo.poolStake.minus(beforeInfo.operatorStake); - const weightedStakeInPool = beforeInfo.operatorStake.plus( - ReferenceFunctions.getPartialAmountFloor( - stakingConstants.DEFAULT_PARAMS.rewardDelegatedStakeWeight, - new BigNumber(stakingConstants.PPM), - membersStakeInPool, - ), - ); - expectedPoolStats.membersStake = membersStakeInPool; - expectedPoolStats.weightedStake = weightedStakeInPool; - expectedAggregatedStats.totalWeightedStake = beforeInfo.aggregatedStats.totalWeightedStake.plus( - weightedStakeInPool, - ); - expectedAggregatedStats.numPoolsToFinalize = beforeInfo.aggregatedStats.numPoolsToFinalize.plus( - 1, - ); - // StakingPoolEarnedRewardsInEpoch event emitted - expectedEvents.push({ - epoch: currentEpoch, - poolId: beforeInfo.poolId, - }); - } - // Credit a protocol fee to the maker's staking pool - expectedPoolStats.feesCollected = beforeInfo.poolStats.feesCollected.plus( - DeploymentManager.protocolFee, - ); - // Update aggregated stats - expectedAggregatedStats.totalFeesCollected = beforeInfo.aggregatedStats.totalFeesCollected.plus( - DeploymentManager.protocolFee, + // If the maker is in a staking pool then validate the protocol fee. + if (beforeInfo !== undefined) { + await assertProtocolFeePaidAsync( + beforeInfo, + result, + simulationEnvironment, + deployment, + expectedProtocolFee, ); } - - // Check for updated stats and event - const poolStats = PoolStats.fromArray( - await stakingWrapper.poolStatsByEpoch(beforeInfo.poolId, currentEpoch).callAsync(), - ); - const aggregatedStats = AggregatedStats.fromArray( - await stakingWrapper.aggregatedStatsByEpoch(currentEpoch).callAsync(), - ); - expect(poolStats).to.deep.equal(expectedPoolStats); - expect(aggregatedStats).to.deep.equal(expectedAggregatedStats); - verifyEvents( - result.receipt!, - expectedEvents, - StakingEvents.StakingPoolEarnedRewardsInEpoch, - ); }, }, ); diff --git a/contracts/integrations/test/framework/assertions/matchOrders.ts b/contracts/integrations/test/framework/assertions/matchOrders.ts new file mode 100644 index 0000000000..2b08700f82 --- /dev/null +++ b/contracts/integrations/test/framework/assertions/matchOrders.ts @@ -0,0 +1,79 @@ +import { MatchedFillResults, Order } from '@0x/types'; +import { TxData } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { Maker } from '../actors/maker'; +import { filterActorsByRole } from '../actors/utils'; +import { DeploymentManager } from '../deployment_manager'; +import { SimulationEnvironment } from '../simulation'; +import { assertProtocolFeePaidAsync, getPoolInfoAsync, PoolInfo } from '../utils/assert_protocol_fee'; +import { verifyMatchEvents } from '../utils/verify_match_events'; + +import { FunctionAssertion, FunctionResult } from './function_assertion'; + +export const matchOrderRuntimeAssertion = ( + deployment: DeploymentManager, + simulationEnvironment: SimulationEnvironment, + withMaximalFill: boolean, +) => { + const { actors } = simulationEnvironment; + const expectedProtocolFee = DeploymentManager.protocolFee.times(2); + + return { + before: async (args: [Order, Order, string, string]) => { + const [order] = args; + const maker = filterActorsByRole(actors, Maker).find(actor => actor.address === order.makerAddress); + // tslint:disable-next-line no-non-null-assertion + const poolInfo = getPoolInfoAsync(maker!, simulationEnvironment, deployment); + return poolInfo; + }, + after: async ( + beforeInfo: PoolInfo | void, + result: FunctionResult, + args: [Order, Order, string, string], + txData: Partial, + ) => { + // Ensure that the correct events were emitted. + const [leftOrder, rightOrder] = args; + + verifyMatchEvents( + txData, + leftOrder, + rightOrder, + // tslint:disable-next-line no-non-null-assertion no-unnecessary-type-assertion + result.receipt!, + deployment, + withMaximalFill, + ); + + // If the maker is in a staking pool then validate the protocol fee. + if (beforeInfo !== undefined) { + await assertProtocolFeePaidAsync( + beforeInfo, + result, + simulationEnvironment, + deployment, + expectedProtocolFee, + ); + } + }, + }; +}; + +/** + * A function assertion that verifies that a complete and valid `matchOrders` succeeded and emitted the correct logs. + */ +/* tslint:disable:no-unnecessary-type-assertion */ +/* tslint:disable:no-non-null-assertion */ +export function validMatchOrdersAssertion( + deployment: DeploymentManager, + simulationEnvironment: SimulationEnvironment, +): FunctionAssertion<[Order, Order, string, string], PoolInfo | void, MatchedFillResults> { + return new FunctionAssertion<[Order, Order, string, string], PoolInfo | void, MatchedFillResults>( + deployment.exchange, + 'matchOrders', + matchOrderRuntimeAssertion(deployment, simulationEnvironment, false), + ); +} +/* tslint:enable:no-non-null-assertion */ +/* tslint:enable:no-unnecessary-type-assertion */ diff --git a/contracts/integrations/test/framework/assertions/matchOrdersWithMaximalFill.ts b/contracts/integrations/test/framework/assertions/matchOrdersWithMaximalFill.ts new file mode 100644 index 0000000000..e8f5257507 --- /dev/null +++ b/contracts/integrations/test/framework/assertions/matchOrdersWithMaximalFill.ts @@ -0,0 +1,27 @@ +import { MatchedFillResults, Order } from '@0x/types'; +import * as _ from 'lodash'; + +import { DeploymentManager } from '../deployment_manager'; +import { SimulationEnvironment } from '../simulation'; +import { PoolInfo } from '../utils/assert_protocol_fee'; + +import { FunctionAssertion } from './function_assertion'; +import { matchOrderRuntimeAssertion } from './matchOrders'; + +/** + * A function assertion that verifies that a complete and valid `matchOrdersWithMaximalFill` succeeded and emitted the correct logs. + */ +/* tslint:disable:no-unnecessary-type-assertion */ +/* tslint:disable:no-non-null-assertion */ +export function validMatchOrdersWithMaximalFillAssertion( + deployment: DeploymentManager, + simulationEnvironment: SimulationEnvironment, +): FunctionAssertion<[Order, Order, string, string], PoolInfo | void, MatchedFillResults> { + return new FunctionAssertion<[Order, Order, string, string], PoolInfo | void, MatchedFillResults>( + deployment.exchange, + 'matchOrdersWithMaximalFill', + matchOrderRuntimeAssertion(deployment, simulationEnvironment, true), + ); +} +/* tslint:enable:no-non-null-assertion */ +/* tslint:enable:no-unnecessary-type-assertion */ diff --git a/contracts/integrations/test/framework/utils/assert_protocol_fee.ts b/contracts/integrations/test/framework/utils/assert_protocol_fee.ts new file mode 100644 index 0000000000..edefea6263 --- /dev/null +++ b/contracts/integrations/test/framework/utils/assert_protocol_fee.ts @@ -0,0 +1,118 @@ +import { ReferenceFunctions } from '@0x/contracts-exchange-libs'; +import { + AggregatedStats, + constants as stakingConstants, + PoolStats, + StakingEvents, + StakingStakingPoolEarnedRewardsInEpochEventArgs, +} from '@0x/contracts-staking'; +import { expect, verifyEvents } from '@0x/contracts-test-utils'; +import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; + +import { Maker } from '../actors/maker'; +import { DeploymentManager } from '../deployment_manager'; +import { SimulationEnvironment } from '../simulation'; + +import { FunctionResult } from '../assertions/function_assertion'; + +export interface PoolInfo { + poolStats: PoolStats; + aggregatedStats: AggregatedStats; + poolStake: BigNumber; + operatorStake: BigNumber; + poolId: string; +} + +/** + * Gets info for a given maker's pool. + */ +export async function getPoolInfoAsync( + maker: Maker, + simulationEnvironment: SimulationEnvironment, + deployment: DeploymentManager, +): Promise { + const { stakingWrapper } = deployment.staking; + // tslint:disable-next-line no-non-null-assertion no-unnecessary-type-assertion + const poolId = maker!.makerPoolId; + const { currentEpoch } = simulationEnvironment; + if (poolId === undefined) { + return; + } else { + const poolStats = PoolStats.fromArray(await stakingWrapper.poolStatsByEpoch(poolId, currentEpoch).callAsync()); + const aggregatedStats = AggregatedStats.fromArray( + await stakingWrapper.aggregatedStatsByEpoch(currentEpoch).callAsync(), + ); + const { currentEpochBalance: poolStake } = await stakingWrapper + .getTotalStakeDelegatedToPool(poolId) + .callAsync(); + const { currentEpochBalance: operatorStake } = await stakingWrapper + .getStakeDelegatedToPoolByOwner(simulationEnvironment.stakingPools[poolId].operator, poolId) + .callAsync(); + return { poolStats, aggregatedStats, poolStake, poolId, operatorStake }; + } +} + +/** + * Asserts that a protocol fee was paid. + */ +export async function assertProtocolFeePaidAsync( + poolInfo: PoolInfo, + result: FunctionResult, + simulationEnvironment: SimulationEnvironment, + deployment: DeploymentManager, + expectedProtocolFee: BigNumber, +): Promise { + const { currentEpoch } = simulationEnvironment; + const { stakingWrapper } = deployment.staking; + const expectedPoolStats = { ...poolInfo.poolStats }; + const expectedAggregatedStats = { ...poolInfo.aggregatedStats }; + const expectedEvents = []; + + // Refer to `payProtocolFee` + if (poolInfo.poolStake.isGreaterThanOrEqualTo(stakingConstants.DEFAULT_PARAMS.minimumPoolStake)) { + if (poolInfo.poolStats.feesCollected.isZero()) { + const membersStakeInPool = poolInfo.poolStake.minus(poolInfo.operatorStake); + const weightedStakeInPool = poolInfo.operatorStake.plus( + ReferenceFunctions.getPartialAmountFloor( + stakingConstants.DEFAULT_PARAMS.rewardDelegatedStakeWeight, + new BigNumber(stakingConstants.PPM), + membersStakeInPool, + ), + ); + expectedPoolStats.membersStake = membersStakeInPool; + expectedPoolStats.weightedStake = weightedStakeInPool; + expectedAggregatedStats.totalWeightedStake = poolInfo.aggregatedStats.totalWeightedStake.plus( + weightedStakeInPool, + ); + expectedAggregatedStats.numPoolsToFinalize = poolInfo.aggregatedStats.numPoolsToFinalize.plus(1); + // StakingPoolEarnedRewardsInEpoch event emitted + expectedEvents.push({ + epoch: currentEpoch, + poolId: poolInfo.poolId, + }); + } + // Credit a protocol fee to the maker's staking pool + expectedPoolStats.feesCollected = poolInfo.poolStats.feesCollected.plus(expectedProtocolFee); + // Update aggregated stats + expectedAggregatedStats.totalFeesCollected = poolInfo.aggregatedStats.totalFeesCollected.plus( + expectedProtocolFee, + ); + } + + // Check for updated stats and event + const poolStats = PoolStats.fromArray( + await stakingWrapper.poolStatsByEpoch(poolInfo.poolId, currentEpoch).callAsync(), + ); + const aggregatedStats = AggregatedStats.fromArray( + await stakingWrapper.aggregatedStatsByEpoch(currentEpoch).callAsync(), + ); + expect(poolStats).to.deep.equal(expectedPoolStats); + expect(aggregatedStats).to.deep.equal(expectedAggregatedStats); + verifyEvents( + // tslint:disable-next-line no-non-null-assertion no-unnecessary-type-assertion + result.receipt!, + expectedEvents, + StakingEvents.StakingPoolEarnedRewardsInEpoch, + ); +} diff --git a/contracts/integrations/test/framework/utils/verify_match_events.ts b/contracts/integrations/test/framework/utils/verify_match_events.ts new file mode 100644 index 0000000000..98e0d305ea --- /dev/null +++ b/contracts/integrations/test/framework/utils/verify_match_events.ts @@ -0,0 +1,151 @@ +import { ERC20TokenEvents, ERC20TokenTransferEventArgs } from '@0x/contracts-erc20'; +import { ExchangeEvents, ExchangeFillEventArgs } from '@0x/contracts-exchange'; +import { ReferenceFunctions } from '@0x/contracts-exchange-libs'; +import { orderHashUtils, verifyEvents } from '@0x/contracts-test-utils'; +import { MatchedFillResults, Order } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { DeploymentManager } from '../deployment_manager'; + +/** + * Verifies `Fill` and `Transfer` events emitted by `matchOrders` or `matchOrdersWithMaximalFill`. + */ +export function verifyMatchEvents( + txData: Partial, + leftOrder: Order, + rightOrder: Order, + receipt: TransactionReceiptWithDecodedLogs, + deployment: DeploymentManager, + withMaximalFill: boolean, +): void { + const matchResults = ReferenceFunctions.calculateMatchResults( + leftOrder, + rightOrder, + DeploymentManager.protocolFeeMultiplier, + DeploymentManager.gasPrice, + withMaximalFill, + ); + const takerAddress = txData.from as string; + const value = new BigNumber(txData.value || 0); + + verifyMatchFilledEvents(leftOrder, rightOrder, receipt, matchResults, takerAddress); + verifyMatchTransferEvents(leftOrder, rightOrder, receipt, matchResults, takerAddress, value, deployment); +} + +/** + * Verifies `Fill` events emitted by `matchOrders` or `matchOrdersWithMaximalFill`. + */ +const verifyMatchFilledEvents = ( + leftOrder: Order, + rightOrder: Order, + receipt: TransactionReceiptWithDecodedLogs, + matchResults: MatchedFillResults, + takerAddress: string, +) => { + const expectedFillEvents = [ + { + makerAddress: leftOrder.makerAddress, + feeRecipientAddress: leftOrder.feeRecipientAddress, + makerAssetData: leftOrder.makerAssetData, + takerAssetData: leftOrder.takerAssetData, + makerFeeAssetData: leftOrder.makerFeeAssetData, + takerFeeAssetData: leftOrder.takerFeeAssetData, + orderHash: orderHashUtils.getOrderHashHex(leftOrder), + takerAddress, + senderAddress: takerAddress, + ...matchResults.left, + }, + { + makerAddress: rightOrder.makerAddress, + feeRecipientAddress: rightOrder.feeRecipientAddress, + makerAssetData: rightOrder.makerAssetData, + takerAssetData: rightOrder.takerAssetData, + makerFeeAssetData: rightOrder.makerFeeAssetData, + takerFeeAssetData: rightOrder.takerFeeAssetData, + orderHash: orderHashUtils.getOrderHashHex(rightOrder), + takerAddress, + senderAddress: takerAddress, + ...matchResults.right, + }, + ]; + + verifyEvents(receipt, expectedFillEvents, ExchangeEvents.Fill); +}; + +/** + * Verifies `Transfer` events emitted by `matchOrders` or `matchOrdersWithMaximalFill`. + */ +const verifyMatchTransferEvents = ( + leftOrder: Order, + rightOrder: Order, + receipt: TransactionReceiptWithDecodedLogs, + matchResults: MatchedFillResults, + takerAddress: string, + value: BigNumber, + deployment: DeploymentManager, +) => { + const expectedTransferEvents = [ + { + _from: rightOrder.makerAddress, + _to: leftOrder.makerAddress, + _value: matchResults.left.takerAssetFilledAmount, + }, + { + _from: leftOrder.makerAddress, + _to: rightOrder.makerAddress, + _value: matchResults.right.takerAssetFilledAmount, + }, + { + _from: rightOrder.makerAddress, + _to: rightOrder.feeRecipientAddress, + _value: matchResults.right.makerFeePaid, + }, + { + _from: leftOrder.makerAddress, + _to: leftOrder.feeRecipientAddress, + _value: matchResults.left.makerFeePaid, + }, + { + _from: leftOrder.makerAddress, + _to: takerAddress, + _value: matchResults.left.makerAssetFilledAmount.minus(matchResults.right.takerAssetFilledAmount), + }, + { + _from: rightOrder.makerAddress, + _to: takerAddress, + _value: matchResults.right.makerAssetFilledAmount.minus(matchResults.left.takerAssetFilledAmount), + }, + { + _from: takerAddress, + _to: deployment.staking.stakingProxy.address, + _value: value.isLessThan(DeploymentManager.protocolFee.times(2)) + ? DeploymentManager.protocolFee + : new BigNumber(0), + }, + { + _from: takerAddress, + _to: deployment.staking.stakingProxy.address, + _value: value.isLessThan(DeploymentManager.protocolFee) ? DeploymentManager.protocolFee : new BigNumber(0), + }, + { + _from: takerAddress, + _to: rightOrder.feeRecipientAddress, + _value: + leftOrder.feeRecipientAddress === rightOrder.feeRecipientAddress + ? new BigNumber(0) + : matchResults.right.takerFeePaid, + }, + { + _from: takerAddress, + _to: leftOrder.feeRecipientAddress, + _value: + leftOrder.feeRecipientAddress === rightOrder.feeRecipientAddress + ? matchResults.left.takerFeePaid.plus(matchResults.right.takerFeePaid) + : matchResults.left.takerFeePaid, + }, + ].filter(event => event._value.isGreaterThan(0)); + + verifyEvents(receipt, expectedTransferEvents, ERC20TokenEvents.Transfer); +}; diff --git a/contracts/integrations/test/fuzz_tests/match_orders_test.ts b/contracts/integrations/test/fuzz_tests/match_orders_test.ts new file mode 100644 index 0000000000..ac95ec8ebf --- /dev/null +++ b/contracts/integrations/test/fuzz_tests/match_orders_test.ts @@ -0,0 +1,83 @@ +import { blockchainTests } from '@0x/contracts-test-utils'; +import * as _ from 'lodash'; + +import { Actor } from '../framework/actors/base'; +import { Maker } from '../framework/actors/maker'; +import { PoolOperator } from '../framework/actors/pool_operator'; +import { Taker } from '../framework/actors/taker'; +import { filterActorsByRole } from '../framework/actors/utils'; +import { AssertionResult } from '../framework/assertions/function_assertion'; +import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store'; +import { DeploymentManager } from '../framework/deployment_manager'; +import { Simulation, SimulationEnvironment } from '../framework/simulation'; +import { Pseudorandom } from '../framework/utils/pseudorandom'; + +import { PoolManagementSimulation } from './pool_management_test'; + +export class MatchOrdersSimulation extends Simulation { + protected async *_assertionGenerator(): AsyncIterableIterator { + const { actors } = this.environment; + const makers = filterActorsByRole(actors, Maker); + const takers = filterActorsByRole(actors, Taker); + + const poolManagement = new PoolManagementSimulation(this.environment); + + const [actions, weights] = _.unzip([ + // 20% chance of executing validJoinStakingPool for a random maker + ...makers.map(maker => [maker.simulationActions.validJoinStakingPool, 0.2 / makers.length]), + // 30% chance of executing matchOrders for a random taker + ...takers.map(taker => [taker.simulationActions.validMatchOrders, 0.3]), + // 30% chance of executing matchOrders for a random taker + ...takers.map(taker => [taker.simulationActions.validMatchOrdersWithMaximalFill, 0.3]), + // 20% chance of executing an assertion generated from the pool management simulation + [poolManagement.generator, 0.2], + ]) as [Array>, number[]]; + while (true) { + const action = Pseudorandom.sample(actions, weights); + yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion + } + } +} + +blockchainTests('Match Orders fuzz test', env => { + before(function(): void { + if (process.env.FUZZ_TEST !== 'match_orders') { + this.skip(); + } + }); + after(async () => { + Actor.reset(); + }); + + it('fuzz', async () => { + // Deploy contracts + const deployment = await DeploymentManager.deployAsync(env, { + numErc20TokensToDeploy: 4, + numErc721TokensToDeploy: 0, + numErc1155TokensToDeploy: 0, + }); + + // Set up balance store + const balanceStore = new BlockchainBalanceStore({}, {}); + + // Spin up actors + const actors = [ + new Maker({ deployment, name: 'Maker 1' }), + new Taker({ deployment, name: 'Taker 1' }), + new PoolOperator({ deployment, name: 'PoolOperator 1' }), + ]; + + // Set up simulation environment + const simulationEnvironment = new SimulationEnvironment(deployment, balanceStore, actors); + + // Takers need to set a WETH allowance for the staking proxy in case they pay the protocol fee in WETH + const takers = filterActorsByRole(actors, Taker); + for (const taker of takers) { + await taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address); + } + + // Run simulation + const simulation = new MatchOrdersSimulation(simulationEnvironment); + return simulation.fuzzAsync(); + }); +});