From 1e44a9c9423c9e3ae183b63b13c4760cfb6d543c Mon Sep 17 00:00:00 2001 From: Alex Towle Date: Fri, 22 Nov 2019 14:14:45 -0600 Subject: [PATCH] Made function assertions work with the new wrappers --- .../test/framework/actors/pool_member.ts | 66 +++++++++++++++++++ .../test/framework/actors/pool_operator.ts | 16 +++-- .../test/framework/actors/staker.ts | 6 +- .../framework/assertions/createStakingPool.ts | 24 ++++--- .../decreaseStakingPoolOperatorShare.ts | 9 ++- .../assertions/function_assertion.ts | 49 +++++++++----- .../framework/assertions/joinStakingPool.ts | 45 +++++++++++++ .../test/framework/assertions/moveStake.ts | 19 +++--- .../test/framework/assertions/stake.ts | 17 +++-- .../test/framework/assertions/unstake.ts | 17 +++-- .../tests/function_assertion_test.ts | 66 ++++++++++--------- .../test/fuzz_tests/pool_membership_test.ts | 53 +++++++++++++++ 12 files changed, 298 insertions(+), 89 deletions(-) create mode 100644 contracts/integrations/test/framework/actors/pool_member.ts create mode 100644 contracts/integrations/test/framework/assertions/joinStakingPool.ts create mode 100644 contracts/integrations/test/fuzz_tests/pool_membership_test.ts diff --git a/contracts/integrations/test/framework/actors/pool_member.ts b/contracts/integrations/test/framework/actors/pool_member.ts new file mode 100644 index 0000000000..5588137f7d --- /dev/null +++ b/contracts/integrations/test/framework/actors/pool_member.ts @@ -0,0 +1,66 @@ +import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { AssertionResult } from '../assertions/function_assertion'; +import { validJoinStakingPoolAssertion } from '../assertions/joinStakingPool'; + +import { Actor, Constructor } from './base'; +import { PoolOperatorMixin } from './pool_operator'; + +interface PoolMemberInterface { + joinStakingPoolAsync: (poolId: string) => Promise; +} + +/** + * This mixin encapsulates functionaltiy associated with pool operators within the 0x ecosystem. + * This includes creating staking pools and decreasing the operator share of a pool. + */ +export function PoolMemberMixin(Base: TBase): TBase & Constructor { + return class extends Base { + public readonly actor: Actor; + + /** + * The mixin pattern requires that this constructor uses `...args: any[]`, but this class + * really expects a single `ActorConfig` parameter (assuming `Actor` is used as the + * base class). + */ + constructor(...args: any[]) { + // tslint:disable-next-line:no-inferred-empty-object-type + super(...args); + this.actor = (this as any) as Actor; + + // Register this mixin's assertion generators + this.actor.simulationActions = { + ...this.actor.simulationActions, + validJoinStakingPool: this._validJoinStakingPool(), + }; + } + + /** + * Joins a new staking pool. + */ + public async joinStakingPoolAsync(poolId: string): Promise { + const stakingContract = this.actor.deployment.staking.stakingWrapper; + return stakingContract + .joinStakingPoolAsMaker(poolId) + .awaitTransactionSuccessAsync({ from: this.actor.address }); + } + + // FIXME(jalextowle): I need to make sure that this is being sent from the actor's address + private async *_validJoinStakingPool(): AsyncIterableIterator { + const { stakingPools } = this.actor.simulationEnvironment!; + const assertion = validJoinStakingPoolAssertion(this.actor.deployment); + while (true) { + const poolId = _.sample(Object.keys(stakingPools)); + if (poolId === undefined) { + yield undefined; + } else { + console.log('Attempting to join pool'); + yield assertion.executeAsync({ args: [poolId], txData: {} }); + } + } + } + }; +} + +export class PoolMember extends PoolOperatorMixin(PoolMemberMixin(Actor)) {} diff --git a/contracts/integrations/test/framework/actors/pool_operator.ts b/contracts/integrations/test/framework/actors/pool_operator.ts index 19d9e06d12..31b7dc7e6b 100644 --- a/contracts/integrations/test/framework/actors/pool_operator.ts +++ b/contracts/integrations/test/framework/actors/pool_operator.ts @@ -80,11 +80,16 @@ export function PoolOperatorMixin(Base: TBase): TBase } private async *_validCreateStakingPool(): AsyncIterableIterator { + console.log(10); const { stakingPools } = this.actor.simulationEnvironment!; + console.log(11); const assertion = validCreateStakingPoolAssertion(this.actor.deployment, stakingPools); + console.log(12); while (true) { - const operatorShare = getRandomInteger(0, constants.PPM); - yield assertion.executeAsync(operatorShare, false, { from: this.actor.address }); + const operatorShare = getRandomInteger(0, constants.PPM).toNumber(); + console.log(13); + yield assertion.executeAsync({ args: [operatorShare, false], txData: { from: this.actor.address } }); + console.log(14); } } @@ -96,8 +101,11 @@ export function PoolOperatorMixin(Base: TBase): TBase if (poolId === undefined) { yield undefined; } else { - const operatorShare = getRandomInteger(0, stakingPools[poolId].operatorShare); - yield assertion.executeAsync(poolId, operatorShare, { from: this.actor.address }); + const operatorShare = getRandomInteger(0, stakingPools[poolId].operatorShare).toNumber(); + yield assertion.executeAsync({ + args: [poolId, operatorShare], + txData: { from: this.actor.address }, + }); } } } diff --git a/contracts/integrations/test/framework/actors/staker.ts b/contracts/integrations/test/framework/actors/staker.ts index 1261933b85..4898a4804e 100644 --- a/contracts/integrations/test/framework/actors/staker.ts +++ b/contracts/integrations/test/framework/actors/staker.ts @@ -76,7 +76,7 @@ export function StakerMixin(Base: TBase): TBase & Con await balanceStore.updateErc20BalancesAsync(); const zrxBalance = balanceStore.balances.erc20[this.actor.address][zrx.address]; const amount = getRandomInteger(0, zrxBalance); - yield assertion.executeAsync(amount, { from: this.actor.address }); + yield assertion.executeAsync({ args: [amount], txData: { from: this.actor.address } }); } } @@ -95,7 +95,7 @@ export function StakerMixin(Base: TBase): TBase & Con undelegatedStake.nextEpochBalance, ); const amount = getRandomInteger(0, withdrawableStake); - yield assertion.executeAsync(amount, { from: this.actor.address }); + yield assertion.executeAsync({ args: [amount], txData: { from: this.actor.address } }); } } @@ -124,7 +124,7 @@ export function StakerMixin(Base: TBase): TBase & Con : this.stake[StakeStatus.Delegated][from.poolId].nextEpochBalance; const amount = getRandomInteger(0, moveableStake); - yield assertion.executeAsync(from, to, amount, { from: this.actor.address }); + yield assertion.executeAsync({ args: [from, to, amount], txData: { from: this.actor.address } }); } } }; diff --git a/contracts/integrations/test/framework/assertions/createStakingPool.ts b/contracts/integrations/test/framework/assertions/createStakingPool.ts index a1829a0e62..d3bfebf2cd 100644 --- a/contracts/integrations/test/framework/assertions/createStakingPool.ts +++ b/contracts/integrations/test/framework/assertions/createStakingPool.ts @@ -16,10 +16,10 @@ import { FunctionAssertion, FunctionResult } from './function_assertion'; export function validCreateStakingPoolAssertion( deployment: DeploymentManager, pools: StakingPoolById, -): FunctionAssertion { +): FunctionAssertion<[number, boolean], string, string> { const { stakingWrapper } = deployment.staking; - return new FunctionAssertion(stakingWrapper.createStakingPool, { + return new FunctionAssertion<[number, boolean], string, string>(stakingWrapper.createStakingPool, { // Returns the expected ID of th created pool before: async () => { const lastPoolId = await stakingWrapper.lastPoolId().callAsync(); @@ -32,23 +32,31 @@ export function validCreateStakingPoolAssertion( after: async ( expectedPoolId: string, result: FunctionResult, - operatorShare: number, - addOperatorAsMaker: boolean, - txData: Partial, + args: { + args: [number, boolean]; + txData: Partial; + }, ) => { - logUtils.log(`createStakingPool(${operatorShare}, ${addOperatorAsMaker}) => ${expectedPoolId}`); + console.log(100); + logUtils.log(`createStakingPool(${args.args[0]}, ${args.args[1]}) => ${expectedPoolId}`); + console.log(101); // Checks the logs for the new poolId, verifies that it is as expected + console.log(result.receipt); const log = result.receipt!.logs[0]; // tslint:disable-line:no-non-null-assertion + console.log(102); const actualPoolId = (log as any).args.poolId; + console.log(103); expect(actualPoolId).to.equal(expectedPoolId); + console.log(104); // Adds the new pool to local state pools[actualPoolId] = { - operator: txData.from as string, - operatorShare, + operator: args.txData.from as string, + operatorShare: args.args[0], delegatedStake: new StoredBalance(), }; + console.log(105); }, }); } diff --git a/contracts/integrations/test/framework/assertions/decreaseStakingPoolOperatorShare.ts b/contracts/integrations/test/framework/assertions/decreaseStakingPoolOperatorShare.ts index dfcee98ad2..5633524e81 100644 --- a/contracts/integrations/test/framework/assertions/decreaseStakingPoolOperatorShare.ts +++ b/contracts/integrations/test/framework/assertions/decreaseStakingPoolOperatorShare.ts @@ -13,11 +13,14 @@ import { FunctionAssertion, FunctionResult } from './function_assertion'; export function validDecreaseStakingPoolOperatorShareAssertion( deployment: DeploymentManager, pools: StakingPoolById, -): FunctionAssertion<{}, void> { +): FunctionAssertion<[string, number], {}, void> { const { stakingWrapper } = deployment.staking; - return new FunctionAssertion<{}, void>(stakingWrapper.decreaseStakingPoolOperatorShare, { - after: async (_beforeInfo, _result: FunctionResult, poolId: string, expectedOperatorShare: number) => { + return new FunctionAssertion<[string, number], {}, void>(stakingWrapper.decreaseStakingPoolOperatorShare, { + after: async (_beforeInfo, _result: FunctionResult, args: { args: [string, number] }) => { + const poolId = args.args[0]; + const expectedOperatorShare = args.args[1]; + logUtils.log(`decreaseStakingPoolOperatorShare(${poolId}, ${expectedOperatorShare})`); // Checks that the on-chain pool's operator share has been updated. diff --git a/contracts/integrations/test/framework/assertions/function_assertion.ts b/contracts/integrations/test/framework/assertions/function_assertion.ts index 541e57ce12..6bdd7141ec 100644 --- a/contracts/integrations/test/framework/assertions/function_assertion.ts +++ b/contracts/integrations/test/framework/assertions/function_assertion.ts @@ -1,11 +1,16 @@ import { ContractFunctionObj, ContractTxFunctionObj } from '@0x/base-contract'; -import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; +import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types'; import * as _ from 'lodash'; // tslint:disable:max-classes-per-file export type GenericContractFunction = (...args: any[]) => ContractFunctionObj; +export interface FunctionArguments { + args: TArgs; + txData: Partial; +} + export interface FunctionResult { data?: any; success: boolean; @@ -22,9 +27,9 @@ export interface FunctionResult { * @param after A function that will be run after a call to the contract wrapper * function. */ -export interface Condition { - before: (...args: any[]) => Promise; - after: (beforeInfo: TBefore, result: FunctionResult, ...args: any[]) => Promise; +export interface Condition { + before: (args: FunctionArguments) => Promise; + after: (beforeInfo: TBefore, result: FunctionResult, args: FunctionArguments) => Promise; } /** @@ -34,8 +39,8 @@ export interface Condition { * our `Assertion` implementations will do in practice). * @param runAsync The function to execute for the assertion. */ -export interface Assertion { - executeAsync: (...args: any[]) => Promise; +export interface Assertion { + executeAsync: (args: FunctionArguments) => Promise; } export interface AssertionResult { @@ -47,9 +52,9 @@ export interface AssertionResult { * This class implements `Assertion` and represents a "Hoare Triple" that can be * executed. */ -export class FunctionAssertion implements Assertion { +export class FunctionAssertion implements Assertion { // A condition that will be applied to `wrapperFunction`. - public condition: Condition; + public condition: Condition; // The wrapper function that will be wrapped in assertions. public wrapperFunction: ( @@ -60,11 +65,15 @@ export class FunctionAssertion implements Assertion { wrapperFunction: ( ...args: any[] // tslint:disable-line:trailing-comma ) => ContractTxFunctionObj | ContractFunctionObj, - condition: Partial> = {}, + condition: Partial> = {}, ) { this.condition = { - before: _.noop.bind(this), - after: _.noop.bind(this), + before: async (args: FunctionArguments) => { + return ({} as any) as TBefore; + }, + after: async (beforeInfo: TBefore, result: FunctionResult, args: FunctionArguments) => { + return ({} as any) as TBefore; + }, ...condition, }; this.wrapperFunction = wrapperFunction; @@ -74,9 +83,9 @@ export class FunctionAssertion implements Assertion { * Runs the wrapped function and fails if the before or after assertions fail. * @param ...args The args to the contract wrapper function. */ - public async executeAsync(...args: any[]): Promise> { + public async executeAsync(args: FunctionArguments): Promise> { // Call the before condition. - const beforeInfo = await this.condition.before(...args); + const beforeInfo = await this.condition.before(args); // Initialize the callResult so that the default success value is true. const callResult: FunctionResult = { success: true }; @@ -84,21 +93,27 @@ export class FunctionAssertion implements Assertion { // Try to make the call to the function. If it is successful, pass the // result and receipt to the after condition. try { - const functionWithArgs = this.wrapperFunction(...args) as ContractTxFunctionObj; - callResult.data = await functionWithArgs.callAsync(); + const functionWithArgs = this.wrapperFunction(...args.args) as ContractTxFunctionObj; + callResult.data = await functionWithArgs.callAsync(args.txData); + + console.log(functionWithArgs); + callResult.receipt = functionWithArgs.awaitTransactionSuccessAsync !== undefined - ? await functionWithArgs.awaitTransactionSuccessAsync() // tslint:disable-line:await-promise + ? await functionWithArgs.awaitTransactionSuccessAsync(args.txData) // tslint:disable-line:await-promise : undefined; // tslint:enable:await-promise } catch (error) { + console.log('got here'); + console.log(error); + callResult.data = error; callResult.success = false; callResult.receipt = undefined; } // Call the after condition. - const afterInfo = await this.condition.after(beforeInfo, callResult, ...args); + const afterInfo = await this.condition.after(beforeInfo, callResult, args); return { beforeInfo, diff --git a/contracts/integrations/test/framework/assertions/joinStakingPool.ts b/contracts/integrations/test/framework/assertions/joinStakingPool.ts new file mode 100644 index 0000000000..6263ea1222 --- /dev/null +++ b/contracts/integrations/test/framework/assertions/joinStakingPool.ts @@ -0,0 +1,45 @@ +import { StakingEvents, StakingMakerStakingPoolSetEventArgs, StakingPoolById } from '@0x/contracts-staking'; +import { constants, expect, filterLogsToArguments } from '@0x/contracts-test-utils'; +import { logUtils } from '@0x/utils'; + +import { DeploymentManager } from '../deployment_manager'; + +import { FunctionArguments, FunctionAssertion, FunctionResult } from './function_assertion'; + +export function validJoinStakingPoolAssertion(deployment: DeploymentManager): FunctionAssertion<[string], {}, void> { + return new FunctionAssertion<[string], {}, void>(deployment.staking.stakingWrapper.joinStakingPoolAsMaker, { + after: async (_beforeInfo, _result: FunctionResult, args: FunctionArguments<[string]>) => { + const poolId = args.args[0]; + + if (args.txData === undefined) { + throw new Error('Undefined transaction data'); + } + + if (args.txData.from === undefined) { + throw new Error('Undefined from address'); + } + + if (_result.receipt === undefined) { + throw new Error('Undefined transaction receipt'); + } + + expect(_result.success).to.be.true(); + + const logs = _result.receipt.logs; + const logArgs = filterLogsToArguments( + logs, + StakingEvents.MakerStakingPoolSet, + ); + expect(logArgs).to.be.deep.eq([ + { + maker: args.txData.from, + poolId, + }, + ]); + const joinedPoolId = deployment.staking.stakingWrapper.poolIdByMaker(args.txData.from); + expect(joinedPoolId).to.be.eq(poolId); + + console.log(`Pool ${poolId} joined by ${args.txData.from}`); /* tslint:disable-line:no-console */ + }, + }); +} diff --git a/contracts/integrations/test/framework/assertions/moveStake.ts b/contracts/integrations/test/framework/assertions/moveStake.ts index 08d27627fe..e640a554fc 100644 --- a/contracts/integrations/test/framework/assertions/moveStake.ts +++ b/contracts/integrations/test/framework/assertions/moveStake.ts @@ -13,7 +13,7 @@ import * as _ from 'lodash'; import { DeploymentManager } from '../deployment_manager'; -import { FunctionAssertion } from './function_assertion'; +import { FunctionArguments, FunctionAssertion, FunctionResult } from './function_assertion'; function incrementNextEpochBalance(stakeBalance: StoredBalance, amount: BigNumber): void { _.update(stakeBalance, ['nextEpochBalance'], balance => (balance || constants.ZERO_AMOUNT).plus(amount)); @@ -82,25 +82,24 @@ export function validMoveStakeAssertion( globalStake: GlobalStakeByStatus, ownerStake: OwnerStakeByStatus, pools: StakingPoolById, -): FunctionAssertion<{}, void> { +): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void> { const { stakingWrapper } = deployment.staking; - return new FunctionAssertion<{}, void>(stakingWrapper.moveStake, { + return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void>(stakingWrapper.moveStake, { after: async ( - _beforeInfo, - _result, - from: StakeInfo, - to: StakeInfo, - amount: BigNumber, - txData: Partial, + _beforeInfo: {}, + _result: FunctionResult, + args: FunctionArguments<[StakeInfo, StakeInfo, BigNumber]>, ) => { + const [from, to, amount] = args.args; + logUtils.log( `moveStake({status: ${StakeStatus[from.status]}, poolId: ${from.poolId} }, { status: ${ StakeStatus[to.status] }, poolId: ${to.poolId} }, ${amount})`, ); - const owner = txData.from as string; + const owner = args.txData.from as string; // Update local balances to match the expected result of this `moveStake` operation const updatedPools = updateNextEpochBalances(globalStake, ownerStake, pools, from, to, amount); diff --git a/contracts/integrations/test/framework/assertions/stake.ts b/contracts/integrations/test/framework/assertions/stake.ts index 2affd8a194..5b03302eb8 100644 --- a/contracts/integrations/test/framework/assertions/stake.ts +++ b/contracts/integrations/test/framework/assertions/stake.ts @@ -7,7 +7,7 @@ import { BlockchainBalanceStore } from '../balances/blockchain_balance_store'; import { LocalBalanceStore } from '../balances/local_balance_store'; import { DeploymentManager } from '../deployment_manager'; -import { FunctionAssertion, FunctionResult } from './function_assertion'; +import { FunctionArguments, FunctionAssertion, FunctionResult } from './function_assertion'; function expectedUndelegatedStake( initStake: OwnerStakeByStatus | GlobalStakeByStatus, @@ -30,15 +30,17 @@ export function validStakeAssertion( balanceStore: BlockchainBalanceStore, globalStake: GlobalStakeByStatus, ownerStake: OwnerStakeByStatus, -): FunctionAssertion { +): FunctionAssertion<[BigNumber], LocalBalanceStore, void> { const { stakingWrapper, zrxVault } = deployment.staking; return new FunctionAssertion(stakingWrapper.stake, { - before: async (amount: BigNumber, txData: Partial) => { + before: async (args: FunctionArguments<[BigNumber]>) => { + const [amount] = args.args; + // Simulates the transfer of ZRX from staker to vault const expectedBalances = LocalBalanceStore.create(balanceStore); expectedBalances.transferAsset( - txData.from as string, + args.txData.from as string, zrxVault.address, amount, deployment.assetDataEncoder.ERC20Token(deployment.tokens.zrx.address).getABIEncodedTransactionData(), @@ -48,9 +50,10 @@ export function validStakeAssertion( after: async ( expectedBalances: LocalBalanceStore, _result: FunctionResult, - amount: BigNumber, - txData: Partial, + args: FunctionArguments<[BigNumber]>, ) => { + const [amount] = args.args; + logUtils.log(`stake(${amount})`); // Checks that the ZRX transfer updated balances as expected. @@ -59,7 +62,7 @@ export function validStakeAssertion( // Checks that the owner's undelegated stake has increased by the stake amount const ownerUndelegatedStake = await stakingWrapper - .getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated) + .getOwnerStakeByStatus(args.txData.from as string, StakeStatus.Undelegated) .callAsync(); const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount); expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake); diff --git a/contracts/integrations/test/framework/assertions/unstake.ts b/contracts/integrations/test/framework/assertions/unstake.ts index 5644ba5e39..9473124bf2 100644 --- a/contracts/integrations/test/framework/assertions/unstake.ts +++ b/contracts/integrations/test/framework/assertions/unstake.ts @@ -7,7 +7,7 @@ import { BlockchainBalanceStore } from '../balances/blockchain_balance_store'; import { LocalBalanceStore } from '../balances/local_balance_store'; import { DeploymentManager } from '../deployment_manager'; -import { FunctionAssertion, FunctionResult } from './function_assertion'; +import { FunctionArguments, FunctionAssertion, FunctionResult } from './function_assertion'; function expectedUndelegatedStake( initStake: OwnerStakeByStatus | GlobalStakeByStatus, @@ -30,16 +30,18 @@ export function validUnstakeAssertion( balanceStore: BlockchainBalanceStore, globalStake: GlobalStakeByStatus, ownerStake: OwnerStakeByStatus, -): FunctionAssertion { +): FunctionAssertion<[BigNumber], LocalBalanceStore, void> { const { stakingWrapper, zrxVault } = deployment.staking; return new FunctionAssertion(stakingWrapper.unstake, { - before: async (amount: BigNumber, txData: Partial) => { + before: async (args: FunctionArguments<[BigNumber]>) => { + const [amount] = args.args; + // Simulates the transfer of ZRX from vault to staker const expectedBalances = LocalBalanceStore.create(balanceStore); expectedBalances.transferAsset( zrxVault.address, - txData.from as string, + args.txData.from as string, amount, deployment.assetDataEncoder.ERC20Token(deployment.tokens.zrx.address).getABIEncodedTransactionData(), ); @@ -48,9 +50,10 @@ export function validUnstakeAssertion( after: async ( expectedBalances: LocalBalanceStore, _result: FunctionResult, - amount: BigNumber, - txData: Partial, + args: FunctionArguments<[BigNumber]>, ) => { + const [amount] = args.args; + logUtils.log(`unstake(${amount})`); // Checks that the ZRX transfer updated balances as expected. @@ -59,7 +62,7 @@ export function validUnstakeAssertion( // Checks that the owner's undelegated stake has decreased by the stake amount const ownerUndelegatedStake = await stakingWrapper - .getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated) + .getOwnerStakeByStatus(args.txData.from as string, StakeStatus.Undelegated) .callAsync(); const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount); expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake); diff --git a/contracts/integrations/test/framework/tests/function_assertion_test.ts b/contracts/integrations/test/framework/tests/function_assertion_test.ts index e585d17d1c..0f2e5deb46 100644 --- a/contracts/integrations/test/framework/tests/function_assertion_test.ts +++ b/contracts/integrations/test/framework/tests/function_assertion_test.ts @@ -4,11 +4,11 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { artifacts } from '../../artifacts'; import { TestFrameworkContract, TestFrameworkEventEventArgs, TestFrameworkEvents } from '../../wrappers'; -import { FunctionAssertion, FunctionResult } from '../assertions/function_assertion'; +import { FunctionArguments, FunctionAssertion, FunctionResult } from '../assertions/function_assertion'; const { ZERO_AMOUNT, MAX_UINT256 } = constants; -blockchainTests.resets('FunctionAssertion Unit Tests', env => { +blockchainTests.resets.only('FunctionAssertion Unit Tests', env => { let exampleContract: TestFrameworkContract; before(async () => { @@ -23,84 +23,87 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => { describe('executeAsync', () => { it('should call the before function with the provided arguments', async () => { let sideEffectTarget = ZERO_AMOUNT; - const assertion = new FunctionAssertion( + const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>( exampleContract.returnInteger.bind(exampleContract), { - before: async (_input: BigNumber) => { + before: async (args: { args: [BigNumber] }) => { sideEffectTarget = randomInput; }, }, ); const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256); - await assertion.executeAsync(randomInput); + await assertion.executeAsync({ args: [randomInput], txData: {} }); expect(sideEffectTarget).bignumber.to.be.eq(randomInput); }); it('should call the after function with the provided arguments', async () => { let sideEffectTarget = ZERO_AMOUNT; - const assertion = new FunctionAssertion( + const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>( exampleContract.returnInteger.bind(exampleContract), { - after: async (_beforeInfo: any, _result: FunctionResult, input: BigNumber) => { - sideEffectTarget = input; + after: async (_beforeInfo: any, _result: FunctionResult, args: FunctionArguments<[BigNumber]>) => { + sideEffectTarget = args.args[0]; }, }, ); const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256); - await assertion.executeAsync(randomInput); + await assertion.executeAsync({ args: [randomInput], txData: {} }); expect(sideEffectTarget).bignumber.to.be.eq(randomInput); }); it('should not fail immediately if the wrapped function fails', async () => { - const assertion = new FunctionAssertion<{}, void>(exampleContract.emptyRevert.bind(exampleContract)); - await assertion.executeAsync(); + const assertion = new FunctionAssertion<[], {}, void>(exampleContract.emptyRevert.bind(exampleContract)); + await assertion.executeAsync({ args: [], txData: {} }); }); it('should pass the return value of "before" to "after"', async () => { const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256); let sideEffectTarget = ZERO_AMOUNT; - const assertion = new FunctionAssertion( + const assertion = new FunctionAssertion<[BigNumber], BigNumber, BigNumber>( exampleContract.returnInteger.bind(exampleContract), { - before: async (_input: BigNumber) => { + before: async (_args: FunctionArguments<[BigNumber]>) => { return randomInput; }, - after: async (beforeInfo: any, _result: FunctionResult, _input: BigNumber) => { + after: async (beforeInfo: any, _result: FunctionResult, _args: FunctionArguments<[BigNumber]>) => { sideEffectTarget = beforeInfo; }, }, ); - await assertion.executeAsync(randomInput); + await assertion.executeAsync({ args: [randomInput], txData: {} }); expect(sideEffectTarget).bignumber.to.be.eq(randomInput); }); it('should pass the result from the function call to "after"', async () => { let sideEffectTarget = ZERO_AMOUNT; - const assertion = new FunctionAssertion( + const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>( exampleContract.returnInteger.bind(exampleContract), { - after: async (_beforeInfo: any, result: FunctionResult, _input: BigNumber) => { + after: async (_beforeInfo: any, result: FunctionResult, _args: FunctionArguments<[BigNumber]>) => { sideEffectTarget = result.data; }, }, ); const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256); - await assertion.executeAsync(randomInput); + await assertion.executeAsync({ args: [randomInput], txData: {} }); expect(sideEffectTarget).bignumber.to.be.eq(randomInput); }); it('should pass the receipt from the function call to "after"', async () => { let sideEffectTarget: TransactionReceiptWithDecodedLogs; - const assertion = new FunctionAssertion(exampleContract.emitEvent.bind(exampleContract), { - after: async (_beforeInfo: any, result: FunctionResult, _input: string) => { - if (result.receipt) { - sideEffectTarget = result.receipt; - } + const assertion = new FunctionAssertion<[string], void, void>( + exampleContract.emitEvent.bind(exampleContract), + { + after: async (_beforeInfo: any, result: FunctionResult, _args: FunctionArguments<[string]>) => { + if (result.receipt) { + sideEffectTarget = result.receipt; + } + }, }, - }); + ); const input = 'emitted data'; - await assertion.executeAsync(input); + await assertion.executeAsync({ args: [input], txData: {} }); // Ensure that the correct events were emitted. const [event] = filterLogsToArguments( @@ -112,13 +115,16 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => { it('should pass the error to "after" if the function call fails', async () => { let sideEffectTarget: Error; - const assertion = new FunctionAssertion(exampleContract.stringRevert.bind(exampleContract), { - after: async (_beforeInfo: any, result: FunctionResult, _input: string) => { - sideEffectTarget = result.data; + const assertion = new FunctionAssertion<[string], void, void>( + exampleContract.stringRevert.bind(exampleContract), + { + after: async (_beforeInfo: any, result: FunctionResult, _args: FunctionArguments<[string]>) => { + sideEffectTarget = result.data; + }, }, - }); + ); const message = 'error message'; - await assertion.executeAsync(message); + await assertion.executeAsync({ args: [message], txData: {} }); const expectedError = new StringRevertError(message); return expect(Promise.reject(sideEffectTarget!)).to.revertWith(expectedError); // tslint:disable-line diff --git a/contracts/integrations/test/fuzz_tests/pool_membership_test.ts b/contracts/integrations/test/fuzz_tests/pool_membership_test.ts new file mode 100644 index 0000000000..73930a6f1d --- /dev/null +++ b/contracts/integrations/test/fuzz_tests/pool_membership_test.ts @@ -0,0 +1,53 @@ +import { blockchainTests } from '@0x/contracts-test-utils'; +import * as _ from 'lodash'; + +import { PoolMember } from '../framework/actors/pool_member'; +import { PoolOperator } from '../framework/actors/pool_operator'; +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'; + +class PoolMembershipSimulation extends Simulation { + protected async *_assertionGenerator(): AsyncIterableIterator { + const { deployment } = this.environment; + + const operator = new PoolOperator({ + name: 'operator', + deployment, + simulationEnvironment: this.environment, + }); + + const member = new PoolMember({ + name: 'member', + deployment, + simulationEnvironment: this.environment, + }); + + const actions = [ + operator.simulationActions.validCreateStakingPool, + member.simulationActions.validJoinStakingPool, + ]; + + while (true) { + const action = _.sample(actions); + yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion + } + } +} + +blockchainTests('pool membership fuzz test', env => { + it('fuzz', async () => { + const deployment = await DeploymentManager.deployAsync(env, { + numErc20TokensToDeploy: 0, + numErc721TokensToDeploy: 0, + numErc1155TokensToDeploy: 0, + }); + + const balanceStore = new BlockchainBalanceStore({}, {}); + + const simulationEnv = new SimulationEnvironment(deployment, balanceStore); + const simulation = new PoolMembershipSimulation(simulationEnv); + return simulation.fuzzAsync(); + }); +});