From d11cdcd5d2ee0011f197ee54768bbf9dd2e755d9 Mon Sep 17 00:00:00 2001 From: Michael Zhu Date: Tue, 26 Nov 2019 13:46:25 -0800 Subject: [PATCH 1/6] Use seeded rng for simulations --- contracts/integrations/package.json | 2 + .../test/framework/actors/maker.ts | 4 +- .../test/framework/actors/pool_operator.ts | 8 ++-- .../test/framework/actors/staker.ts | 18 +++++---- .../test/framework/actors/taker.ts | 10 ++--- .../test/framework/pseudorandom.ts | 39 +++++++++++++++++++ .../test/fuzz_tests/pool_management_test.ts | 4 +- .../test/fuzz_tests/pool_membership_test.ts | 4 +- .../test/fuzz_tests/stake_management_test.ts | 4 +- contracts/test-utils/src/index.ts | 2 +- 10 files changed, 69 insertions(+), 26 deletions(-) create mode 100644 contracts/integrations/test/framework/pseudorandom.ts diff --git a/contracts/integrations/package.json b/contracts/integrations/package.json index b011b769dd..e44b0875c9 100644 --- a/contracts/integrations/package.json +++ b/contracts/integrations/package.json @@ -69,6 +69,7 @@ "@types/lodash": "4.14.104", "@types/mocha": "^5.2.7", "@types/node": "*", + "@types/seedrandom": "^2.4.28", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", "chai-bignumber": "^3.0.0", @@ -78,6 +79,7 @@ "mocha": "^6.2.0", "nock": "^10.0.6", "npm-run-all": "^4.1.2", + "seedrandom": "^3.0.5", "shx": "^0.2.2", "solhint": "^1.4.1", "truffle": "^5.0.32", diff --git a/contracts/integrations/test/framework/actors/maker.ts b/contracts/integrations/test/framework/actors/maker.ts index b0dbdf9077..576f8098ba 100644 --- a/contracts/integrations/test/framework/actors/maker.ts +++ b/contracts/integrations/test/framework/actors/maker.ts @@ -1,10 +1,10 @@ import { constants, OrderFactory } from '@0x/contracts-test-utils'; import { Order, SignedOrder } from '@0x/types'; import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; -import * as _ from 'lodash'; import { AssertionResult } from '../assertions/function_assertion'; import { validJoinStakingPoolAssertion } from '../assertions/joinStakingPool'; +import { Pseudorandom } from '../pseudorandom'; import { Actor, ActorConfig, Constructor } from './base'; @@ -88,7 +88,7 @@ export function MakerMixin(Base: TBase): TBase & Cons const { stakingPools } = this.actor.simulationEnvironment!; const assertion = validJoinStakingPoolAssertion(this.actor.deployment); while (true) { - const poolId = _.sample(Object.keys(stakingPools)); + const poolId = Pseudorandom.sample(Object.keys(stakingPools)); if (poolId === undefined) { yield undefined; } else { diff --git a/contracts/integrations/test/framework/actors/pool_operator.ts b/contracts/integrations/test/framework/actors/pool_operator.ts index 176e26fd06..361576cb67 100644 --- a/contracts/integrations/test/framework/actors/pool_operator.ts +++ b/contracts/integrations/test/framework/actors/pool_operator.ts @@ -1,5 +1,4 @@ import { constants, StakingPoolById } from '@0x/contracts-staking'; -import { getRandomInteger } from '@0x/contracts-test-utils'; import '@azure/core-asynciterator-polyfill'; import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import * as _ from 'lodash'; @@ -7,6 +6,7 @@ import * as _ from 'lodash'; import { validCreateStakingPoolAssertion } from '../assertions/createStakingPool'; import { validDecreaseStakingPoolOperatorShareAssertion } from '../assertions/decreaseStakingPoolOperatorShare'; import { AssertionResult } from '../assertions/function_assertion'; +import { Pseudorandom } from '../pseudorandom'; import { Actor, Constructor } from './base'; @@ -83,7 +83,7 @@ export function PoolOperatorMixin(Base: TBase): TBase const { stakingPools } = this.actor.simulationEnvironment!; const assertion = validCreateStakingPoolAssertion(this.actor.deployment, stakingPools); while (true) { - const operatorShare = getRandomInteger(0, constants.PPM).toNumber(); + const operatorShare = Pseudorandom.integer(constants.PPM).toNumber(); yield assertion.executeAsync([operatorShare, false], { from: this.actor.address }); } } @@ -92,11 +92,11 @@ export function PoolOperatorMixin(Base: TBase): TBase const { stakingPools } = this.actor.simulationEnvironment!; const assertion = validDecreaseStakingPoolOperatorShareAssertion(this.actor.deployment, stakingPools); while (true) { - const poolId = _.sample(this._getOperatorPoolIds(stakingPools)); + const poolId = Pseudorandom.sample(this._getOperatorPoolIds(stakingPools)); if (poolId === undefined) { yield undefined; } else { - const operatorShare = getRandomInteger(0, stakingPools[poolId].operatorShare).toNumber(); + const operatorShare = Pseudorandom.integer(stakingPools[poolId].operatorShare).toNumber(); yield assertion.executeAsync([poolId, operatorShare], { from: this.actor.address }); } } diff --git a/contracts/integrations/test/framework/actors/staker.ts b/contracts/integrations/test/framework/actors/staker.ts index b92f98475d..d1cfe84f20 100644 --- a/contracts/integrations/test/framework/actors/staker.ts +++ b/contracts/integrations/test/framework/actors/staker.ts @@ -1,5 +1,4 @@ import { OwnerStakeByStatus, StakeInfo, StakeStatus, StoredBalance } from '@0x/contracts-staking'; -import { getRandomInteger } from '@0x/contracts-test-utils'; import { BigNumber } from '@0x/utils'; import '@azure/core-asynciterator-polyfill'; import * as _ from 'lodash'; @@ -8,6 +7,7 @@ import { AssertionResult } from '../assertions/function_assertion'; import { validMoveStakeAssertion } from '../assertions/moveStake'; import { validStakeAssertion } from '../assertions/stake'; import { validUnstakeAssertion } from '../assertions/unstake'; +import { Pseudorandom } from '../pseudorandom'; import { Actor, Constructor } from './base'; @@ -75,7 +75,7 @@ export function StakerMixin(Base: TBase): TBase & Con while (true) { await balanceStore.updateErc20BalancesAsync(); const zrxBalance = balanceStore.balances.erc20[this.actor.address][zrx.address]; - const amount = getRandomInteger(0, zrxBalance); + const amount = Pseudorandom.integer(zrxBalance); yield assertion.executeAsync([amount], { from: this.actor.address }); } } @@ -94,7 +94,7 @@ export function StakerMixin(Base: TBase): TBase & Con undelegatedStake.currentEpochBalance, undelegatedStake.nextEpochBalance, ); - const amount = getRandomInteger(0, withdrawableStake); + const amount = Pseudorandom.integer(withdrawableStake); yield assertion.executeAsync([amount], { from: this.actor.address }); } } @@ -104,25 +104,27 @@ export function StakerMixin(Base: TBase): TBase & Con const assertion = validMoveStakeAssertion(deployment, globalStake, this.stake, stakingPools); while (true) { - const fromPoolId = _.sample(Object.keys(_.omit(this.stake[StakeStatus.Delegated], ['total']))); + const fromPoolId = Pseudorandom.sample( + Object.keys(_.omit(this.stake[StakeStatus.Delegated], ['total'])), + ); const fromStatus = fromPoolId === undefined ? StakeStatus.Undelegated - : (_.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus); + : (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus); const from = new StakeInfo(fromStatus, fromPoolId); - const toPoolId = _.sample(Object.keys(stakingPools)); + const toPoolId = Pseudorandom.sample(Object.keys(stakingPools)); const toStatus = toPoolId === undefined ? StakeStatus.Undelegated - : (_.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus); + : (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus); const to = new StakeInfo(toStatus, toPoolId); const moveableStake = from.status === StakeStatus.Undelegated ? this.stake[StakeStatus.Undelegated].nextEpochBalance : this.stake[StakeStatus.Delegated][from.poolId].nextEpochBalance; - const amount = getRandomInteger(0, moveableStake); + const amount = Pseudorandom.integer(moveableStake); yield assertion.executeAsync([from, to, amount], { from: this.actor.address }); } diff --git a/contracts/integrations/test/framework/actors/taker.ts b/contracts/integrations/test/framework/actors/taker.ts index ca545966cd..491a6d13ff 100644 --- a/contracts/integrations/test/framework/actors/taker.ts +++ b/contracts/integrations/test/framework/actors/taker.ts @@ -1,12 +1,12 @@ -import { constants, getRandomInteger } from '@0x/contracts-test-utils'; +import { constants } from '@0x/contracts-test-utils'; import { SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types'; -import * as _ from 'lodash'; import { validFillOrderCompleteFillAssertion } from '../assertions/fillOrder'; import { AssertionResult } from '../assertions/function_assertion'; import { DeploymentManager } from '../deployment_manager'; +import { Pseudorandom } from '../pseudorandom'; import { Actor, Constructor } from './base'; @@ -65,7 +65,7 @@ export function TakerMixin(Base: TBase): TBase & Cons const { marketMakers } = this.actor.simulationEnvironment!; const assertion = validFillOrderCompleteFillAssertion(this.actor.deployment); while (true) { - const maker = _.sample(marketMakers); + const maker = Pseudorandom.sample(marketMakers); if (maker === undefined) { yield undefined; } else { @@ -82,8 +82,8 @@ export function TakerMixin(Base: TBase): TBase & Cons ]); const order = await maker.signOrderAsync({ - makerAssetAmount: getRandomInteger(constants.ZERO_AMOUNT, constants.INITIAL_ERC20_BALANCE), - takerAssetAmount: getRandomInteger(constants.ZERO_AMOUNT, constants.INITIAL_ERC20_BALANCE), + makerAssetAmount: Pseudorandom.integer(constants.INITIAL_ERC20_BALANCE), + takerAssetAmount: Pseudorandom.integer(constants.INITIAL_ERC20_BALANCE), }); yield assertion.executeAsync([order, order.takerAssetAmount, order.signature], { from: this.actor.address, diff --git a/contracts/integrations/test/framework/pseudorandom.ts b/contracts/integrations/test/framework/pseudorandom.ts new file mode 100644 index 0000000000..b41dd1a3a4 --- /dev/null +++ b/contracts/integrations/test/framework/pseudorandom.ts @@ -0,0 +1,39 @@ +import { Numberish } from '@0x/contracts-test-utils'; +import { BigNumber } from '@0x/utils'; +import * as seedrandom from 'seedrandom'; + +class PRNGWrapper { + public readonly seed = process.env.UUID || Math.random().toString(); + private readonly _rng = seedrandom(this.seed); + + /* + * Pseudorandom version of _.sample. Picks an element of the given array with uniform probability. + * Return undefined if the array is empty. + */ + public sample(arr: T[]): T | undefined { + if (arr.length === 0) { + return undefined; + } + const index = Math.abs(this._rng.int32()) % arr.length; + return arr[index]; + } + + // tslint:disable:unified-signatures + /* + * Pseudorandom version of getRandomPortion/getRandomInteger. If two arguments are provided, + * treats those arguments as the min and max (inclusive) of the desired range. If only one + * argument is provided, picks an integer between 0 and the argument. + */ + public integer(max: Numberish): BigNumber; + public integer(min: Numberish, max: Numberish): BigNumber; + public integer(a: Numberish, b?: Numberish): BigNumber { + if (b === undefined) { + return new BigNumber(this._rng()).times(a).integerValue(BigNumber.ROUND_HALF_UP); + } else { + const range = new BigNumber(b).minus(a); + return this.integer(range).plus(a); + } + } +} + +export const Pseudorandom = new PRNGWrapper(); diff --git a/contracts/integrations/test/fuzz_tests/pool_management_test.ts b/contracts/integrations/test/fuzz_tests/pool_management_test.ts index 69b69eb912..7df97f74e7 100644 --- a/contracts/integrations/test/fuzz_tests/pool_management_test.ts +++ b/contracts/integrations/test/fuzz_tests/pool_management_test.ts @@ -1,11 +1,11 @@ import { blockchainTests } from '@0x/contracts-test-utils'; -import * as _ from 'lodash'; import { Actor } from '../framework/actors/base'; 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 { Pseudorandom } from '../framework/pseudorandom'; import { Simulation, SimulationEnvironment } from '../framework/simulation'; export class PoolManagementSimulation extends Simulation { @@ -22,7 +22,7 @@ export class PoolManagementSimulation extends Simulation { operator.simulationActions.validDecreaseStakingPoolOperatorShare, ]; while (true) { - const action = _.sample(actions); + const action = Pseudorandom.sample(actions); yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion } } diff --git a/contracts/integrations/test/fuzz_tests/pool_membership_test.ts b/contracts/integrations/test/fuzz_tests/pool_membership_test.ts index 00670f18d9..3be7053cb3 100644 --- a/contracts/integrations/test/fuzz_tests/pool_membership_test.ts +++ b/contracts/integrations/test/fuzz_tests/pool_membership_test.ts @@ -1,11 +1,11 @@ import { blockchainTests, constants } from '@0x/contracts-test-utils'; -import * as _ from 'lodash'; import { MakerTaker } from '../framework/actors/hybrids'; import { Maker } from '../framework/actors/maker'; import { AssertionResult } from '../framework/assertions/function_assertion'; import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store'; import { DeploymentManager } from '../framework/deployment_manager'; +import { Pseudorandom } from '../framework/pseudorandom'; import { Simulation, SimulationEnvironment } from '../framework/simulation'; import { PoolManagementSimulation } from './pool_management_test'; @@ -29,7 +29,7 @@ class PoolMembershipSimulation extends Simulation { ]; while (true) { - const action = _.sample(actions); + const action = Pseudorandom.sample(actions); yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion } } diff --git a/contracts/integrations/test/fuzz_tests/stake_management_test.ts b/contracts/integrations/test/fuzz_tests/stake_management_test.ts index 7bf770dc8d..fba7d8abc1 100644 --- a/contracts/integrations/test/fuzz_tests/stake_management_test.ts +++ b/contracts/integrations/test/fuzz_tests/stake_management_test.ts @@ -1,11 +1,11 @@ import { blockchainTests } from '@0x/contracts-test-utils'; -import * as _ from 'lodash'; import { Actor } from '../framework/actors/base'; import { Staker } from '../framework/actors/staker'; import { AssertionResult } from '../framework/assertions/function_assertion'; import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store'; import { DeploymentManager } from '../framework/deployment_manager'; +import { Pseudorandom } from '../framework/pseudorandom'; import { Simulation, SimulationEnvironment } from '../framework/simulation'; import { PoolManagementSimulation } from './pool_management_test'; @@ -26,7 +26,7 @@ export class StakeManagementSimulation extends Simulation { poolManagement.generator, ]; while (true) { - const action = _.sample(actions); + const action = Pseudorandom.sample(actions); yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion } } diff --git a/contracts/test-utils/src/index.ts b/contracts/test-utils/src/index.ts index fcdddf484e..ea29c322b3 100644 --- a/contracts/test-utils/src/index.ts +++ b/contracts/test-utils/src/index.ts @@ -50,7 +50,7 @@ export { export { blockchainTests, BlockchainTestsEnvironment, describe } from './mocha_blockchain'; export { chaiSetup, expect } from './chai_setup'; export { getCodesizeFromArtifact } from './codesize'; -export { shortZip } from './lang_utils'; +export { replaceKeysDeep, shortZip } from './lang_utils'; export { assertIntegerRoughlyEquals, assertRoughlyEquals, From faf306ad23a2ce4f41e8a3d26b95075eeb88054a Mon Sep 17 00:00:00 2001 From: Michael Zhu Date: Tue, 26 Nov 2019 13:47:11 -0800 Subject: [PATCH 2/6] Simulation logging, hopefully address function assertion lifetime issue --- .../framework/assertions/createStakingPool.ts | 67 ++++++------- .../decreaseStakingPoolOperatorShare.ts | 24 ++--- .../test/framework/assertions/fillOrder.ts | 6 +- .../assertions/function_assertion.ts | 26 +++-- .../framework/assertions/joinStakingPool.ts | 5 +- .../test/framework/assertions/moveStake.ts | 89 ++++++++---------- .../test/framework/assertions/stake.ts | 6 +- .../test/framework/assertions/unstake.ts | 6 +- .../test/framework/balances/balance_store.ts | 10 +- .../integrations/test/framework/logger.ts | 55 +++++++++++ .../integrations/test/framework/simulation.ts | 23 ++++- .../tests/function_assertion_test.ts | 94 +++++++------------ contracts/test-utils/src/lang_utils.ts | 10 ++ 13 files changed, 227 insertions(+), 194 deletions(-) create mode 100644 contracts/integrations/test/framework/logger.ts diff --git a/contracts/integrations/test/framework/assertions/createStakingPool.ts b/contracts/integrations/test/framework/assertions/createStakingPool.ts index 6748b44271..c3ad30fe8d 100644 --- a/contracts/integrations/test/framework/assertions/createStakingPool.ts +++ b/contracts/integrations/test/framework/assertions/createStakingPool.ts @@ -1,6 +1,6 @@ import { StakingPoolById, StoredBalance } from '@0x/contracts-staking'; import { expect } from '@0x/contracts-test-utils'; -import { BigNumber, logUtils } from '@0x/utils'; +import { BigNumber } from '@0x/utils'; import { TxData } from 'ethereum-types'; import { DeploymentManager } from '../deployment_manager'; @@ -20,41 +20,36 @@ export function validCreateStakingPoolAssertion( ): FunctionAssertion<[number, boolean], string, string> { const { stakingWrapper } = deployment.staking; - return new FunctionAssertion<[number, boolean], string, string>( - stakingWrapper.createStakingPool.bind(stakingWrapper), - { - // Returns the expected ID of th created pool - before: async () => { - const lastPoolId = await stakingWrapper.lastPoolId().callAsync(); - // Effectively the last poolId + 1, but as a bytestring - return `0x${new BigNumber(lastPoolId) - .plus(1) - .toString(16) - .padStart(64, '0')}`; - }, - after: async ( - expectedPoolId: string, - result: FunctionResult, - args: [number, boolean], - txData: Partial, - ) => { - const [operatorShare, shouldAddMakerAsOperator] = args; - - logUtils.log(`createStakingPool(${operatorShare}, ${shouldAddMakerAsOperator}) => ${expectedPoolId}`); - - // Checks the logs for the new poolId, verifies that it is as expected - const log = result.receipt!.logs[0]; - const actualPoolId = (log as any).args.poolId; - expect(actualPoolId).to.equal(expectedPoolId); - - // Adds the new pool to local state - pools[actualPoolId] = { - operator: txData.from!, - operatorShare, - delegatedStake: new StoredBalance(), - }; - }, + 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(); + // Effectively the last poolId + 1, but as a bytestring + return `0x${new BigNumber(lastPoolId) + .plus(1) + .toString(16) + .padStart(64, '0')}`; }, - ); + after: async ( + expectedPoolId: string, + result: FunctionResult, + args: [number, boolean], + txData: Partial, + ) => { + const [operatorShare] = args; + + // Checks the logs for the new poolId, verifies that it is as expected + const log = result.receipt!.logs[0]; + const actualPoolId = (log as any).args.poolId; + expect(actualPoolId).to.equal(expectedPoolId); + + // Adds the new pool to local state + pools[actualPoolId] = { + operator: txData.from!, + operatorShare, + delegatedStake: new StoredBalance(), + }; + }, + }); } /* tslint:enable:no-non-null-assertion*/ diff --git a/contracts/integrations/test/framework/assertions/decreaseStakingPoolOperatorShare.ts b/contracts/integrations/test/framework/assertions/decreaseStakingPoolOperatorShare.ts index 9a6ff1a0c4..2eb15675a3 100644 --- a/contracts/integrations/test/framework/assertions/decreaseStakingPoolOperatorShare.ts +++ b/contracts/integrations/test/framework/assertions/decreaseStakingPoolOperatorShare.ts @@ -1,6 +1,5 @@ import { StakingPoolById } from '@0x/contracts-staking'; import { expect } from '@0x/contracts-test-utils'; -import { logUtils } from '@0x/utils'; import { TxData } from 'ethereum-types'; import { DeploymentManager } from '../deployment_manager'; @@ -17,21 +16,16 @@ export function validDecreaseStakingPoolOperatorShareAssertion( ): FunctionAssertion<[string, number], {}, void> { const { stakingWrapper } = deployment.staking; - return new FunctionAssertion<[string, number], {}, void>( - stakingWrapper.decreaseStakingPoolOperatorShare.bind(stakingWrapper), - { - after: async (_beforeInfo, _result: FunctionResult, args: [string, number], txData: Partial) => { - const [poolId, expectedOperatorShare] = args; + return new FunctionAssertion<[string, number], {}, void>(stakingWrapper, 'decreaseStakingPoolOperatorShare', { + after: async (_beforeInfo, _result: FunctionResult, args: [string, number], _txData: Partial) => { + const [poolId, expectedOperatorShare] = args; - logUtils.log(`decreaseStakingPoolOperatorShare(${poolId}, ${expectedOperatorShare})`); + // Checks that the on-chain pool's operator share has been updated. + const { operatorShare } = await stakingWrapper.getStakingPool(poolId).callAsync(); + expect(operatorShare).to.bignumber.equal(expectedOperatorShare); - // Checks that the on-chain pool's operator share has been updated. - const { operatorShare } = await stakingWrapper.getStakingPool(poolId).callAsync(); - expect(operatorShare).to.bignumber.equal(expectedOperatorShare); - - // Updates the pool in local state. - pools[poolId].operatorShare = operatorShare; - }, + // Updates the pool in local state. + pools[poolId].operatorShare = operatorShare; }, - ); + }); } diff --git a/contracts/integrations/test/framework/assertions/fillOrder.ts b/contracts/integrations/test/framework/assertions/fillOrder.ts index a2db06e2c0..fd8dcc7fac 100644 --- a/contracts/integrations/test/framework/assertions/fillOrder.ts +++ b/contracts/integrations/test/framework/assertions/fillOrder.ts @@ -2,7 +2,7 @@ import { ERC20TokenEvents, ERC20TokenTransferEventArgs } from '@0x/contracts-erc import { ExchangeEvents, ExchangeFillEventArgs } from '@0x/contracts-exchange'; import { constants, expect, orderHashUtils, verifyEvents } from '@0x/contracts-test-utils'; import { FillResults, Order } from '@0x/types'; -import { BigNumber, logUtils } from '@0x/utils'; +import { BigNumber } from '@0x/utils'; import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types'; import * as _ from 'lodash'; @@ -74,7 +74,7 @@ export function validFillOrderCompleteFillAssertion( ): FunctionAssertion<[Order, BigNumber, string], {}, FillResults> { const exchange = deployment.exchange; - return new FunctionAssertion<[Order, BigNumber, string], {}, FillResults>(exchange.fillOrder.bind(exchange), { + return new FunctionAssertion<[Order, BigNumber, string], {}, FillResults>(exchange, 'fillOrder', { after: async ( _beforeInfo, result: FunctionResult, @@ -89,8 +89,6 @@ export function validFillOrderCompleteFillAssertion( // Ensure that the correct events were emitted. verifyFillEvents(txData.from!, order, result.receipt!, deployment); - logUtils.log(`Order filled by ${txData.from}`); - // TODO: Add validation for on-chain state (like balances) }, }); diff --git a/contracts/integrations/test/framework/assertions/function_assertion.ts b/contracts/integrations/test/framework/assertions/function_assertion.ts index ff810da3f5..678e11752f 100644 --- a/contracts/integrations/test/framework/assertions/function_assertion.ts +++ b/contracts/integrations/test/framework/assertions/function_assertion.ts @@ -1,7 +1,9 @@ -import { ContractFunctionObj, ContractTxFunctionObj } from '@0x/base-contract'; +import { BaseContract, ContractFunctionObj, ContractTxFunctionObj } from '@0x/base-contract'; import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types'; import * as _ from 'lodash'; +import { logger } from '../logger'; + // tslint:disable:max-classes-per-file export type GenericContractFunction = (...args: any[]) => ContractFunctionObj; @@ -48,29 +50,22 @@ export interface AssertionResult { */ export class FunctionAssertion implements Assertion { // A condition that will be applied to `wrapperFunction`. - public condition: Condition; - - // The wrapper function that will be wrapped in assertions. - public wrapperFunction: ( - ...args: TArgs // tslint:disable-line:trailing-comma - ) => ContractTxFunctionObj | ContractFunctionObj; + public readonly condition: Condition; constructor( - wrapperFunction: ( - ...args: TArgs // tslint:disable-line:trailing-comma - ) => ContractTxFunctionObj | ContractFunctionObj, + private readonly _contractWrapper: BaseContract, + private readonly _functionName: string, condition: Partial> = {}, ) { this.condition = { - before: async (args: TArgs, txData: Partial) => { + before: async (_args: TArgs, _txData: Partial) => { return ({} as any) as TBefore; }, - after: async (beforeInfo: TBefore, result: FunctionResult, args: TArgs, txData: Partial) => { + after: async (_beforeInfo: TBefore, _result: FunctionResult, _args: TArgs, _txData: Partial) => { return ({} as any) as TBefore; }, ...condition, }; - this.wrapperFunction = wrapperFunction; } /** @@ -87,7 +82,10 @@ export class FunctionAssertion imp // 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; + const functionWithArgs = (this._contractWrapper as any)[this._functionName]( + ...args, + ) as ContractTxFunctionObj; + logger.logFunctionAssertion(this._functionName, args, txData); callResult.data = await functionWithArgs.callAsync(txData); callResult.receipt = functionWithArgs.awaitTransactionSuccessAsync !== undefined diff --git a/contracts/integrations/test/framework/assertions/joinStakingPool.ts b/contracts/integrations/test/framework/assertions/joinStakingPool.ts index 478268ca63..88795c469a 100644 --- a/contracts/integrations/test/framework/assertions/joinStakingPool.ts +++ b/contracts/integrations/test/framework/assertions/joinStakingPool.ts @@ -1,6 +1,5 @@ import { StakingEvents, StakingMakerStakingPoolSetEventArgs } from '@0x/contracts-staking'; import { expect, filterLogsToArguments } from '@0x/contracts-test-utils'; -import { logUtils } from '@0x/utils'; import { TxData } from 'ethereum-types'; import { DeploymentManager } from '../deployment_manager'; @@ -15,7 +14,7 @@ import { FunctionAssertion, FunctionResult } from './function_assertion'; export function validJoinStakingPoolAssertion(deployment: DeploymentManager): FunctionAssertion<[string], {}, void> { const { stakingWrapper } = deployment.staking; - return new FunctionAssertion<[string], {}, void>(stakingWrapper.joinStakingPoolAsMaker.bind(stakingWrapper), { + return new FunctionAssertion<[string], {}, void>(stakingWrapper, 'joinStakingPoolAsMaker', { after: async (_beforeInfo, _result: FunctionResult, args: [string], txData: Partial) => { const [poolId] = args; @@ -34,8 +33,6 @@ export function validJoinStakingPoolAssertion(deployment: DeploymentManager): Fu ]); const joinedPoolId = await deployment.staking.stakingWrapper.poolIdByMaker(txData.from!).callAsync(); expect(joinedPoolId).to.be.eq(poolId); - - logUtils.log(`Pool ${poolId} joined by ${txData.from}`); }, }); } diff --git a/contracts/integrations/test/framework/assertions/moveStake.ts b/contracts/integrations/test/framework/assertions/moveStake.ts index f65d98ab41..8b478ffa6e 100644 --- a/contracts/integrations/test/framework/assertions/moveStake.ts +++ b/contracts/integrations/test/framework/assertions/moveStake.ts @@ -7,7 +7,7 @@ import { StoredBalance, } from '@0x/contracts-staking'; import { constants, expect } from '@0x/contracts-test-utils'; -import { BigNumber, logUtils } from '@0x/utils'; +import { BigNumber } from '@0x/utils'; import { TxData } from 'ethereum-types'; import * as _ from 'lodash'; @@ -86,61 +86,50 @@ export function validMoveStakeAssertion( ): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void> { const { stakingWrapper } = deployment.staking; - return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void>( - stakingWrapper.moveStake.bind(stakingWrapper), - { - after: async ( - _beforeInfo: {}, - _result: FunctionResult, - args: [StakeInfo, StakeInfo, BigNumber], - txData: Partial, - ) => { - const [from, to, amount] = args; + return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void>(stakingWrapper, 'moveStake', { + after: async ( + _beforeInfo: {}, + _result: FunctionResult, + args: [StakeInfo, StakeInfo, BigNumber], + txData: Partial, + ) => { + const [from, to, amount] = args; - logUtils.log( - `moveStake({status: ${StakeStatus[from.status]}, poolId: ${from.poolId} }, { status: ${ - StakeStatus[to.status] - }, poolId: ${to.poolId} }, ${amount})`, - ); + const owner = txData.from!; // tslint:disable-line:no-non-null-assertion - const owner = txData.from!; // tslint:disable-line:no-non-null-assertion + // Update local balances to match the expected result of this `moveStake` operation + const updatedPools = updateNextEpochBalances(globalStake, ownerStake, pools, from, to, amount); - // Update local balances to match the expected result of this `moveStake` operation - const updatedPools = updateNextEpochBalances(globalStake, ownerStake, pools, from, to, amount); + // Fetches on-chain owner stake balances and checks against local balances + const ownerUndelegatedStake = { + ...new StoredBalance(), + ...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Undelegated).callAsync()), + }; + const ownerDelegatedStake = { + ...new StoredBalance(), + ...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Delegated).callAsync()), + }; + expect(ownerUndelegatedStake).to.deep.equal(ownerStake[StakeStatus.Undelegated]); + expect(ownerDelegatedStake).to.deep.equal(ownerStake[StakeStatus.Delegated].total); - // Fetches on-chain owner stake balances and checks against local balances - const ownerUndelegatedStake = { - ...new StoredBalance(), - ...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Undelegated).callAsync()), - }; - const ownerDelegatedStake = { - ...new StoredBalance(), - ...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Delegated).callAsync()), - }; - expect(ownerUndelegatedStake).to.deep.equal(ownerStake[StakeStatus.Undelegated]); - expect(ownerDelegatedStake).to.deep.equal(ownerStake[StakeStatus.Delegated].total); + // Fetches on-chain global stake balances and checks against local balances + const globalUndelegatedStake = await stakingWrapper + .getGlobalStakeByStatus(StakeStatus.Undelegated) + .callAsync(); + const globalDelegatedStake = await stakingWrapper.getGlobalStakeByStatus(StakeStatus.Delegated).callAsync(); + expect(globalUndelegatedStake).to.deep.equal(globalStake[StakeStatus.Undelegated]); + expect(globalDelegatedStake).to.deep.equal(globalStake[StakeStatus.Delegated]); - // Fetches on-chain global stake balances and checks against local balances - const globalUndelegatedStake = await stakingWrapper - .getGlobalStakeByStatus(StakeStatus.Undelegated) + // Fetches on-chain pool stake balances and checks against local balances + for (const poolId of updatedPools) { + const stakeDelegatedByOwner = await stakingWrapper + .getStakeDelegatedToPoolByOwner(owner, poolId) .callAsync(); - const globalDelegatedStake = await stakingWrapper - .getGlobalStakeByStatus(StakeStatus.Delegated) - .callAsync(); - expect(globalUndelegatedStake).to.deep.equal(globalStake[StakeStatus.Undelegated]); - expect(globalDelegatedStake).to.deep.equal(globalStake[StakeStatus.Delegated]); - - // Fetches on-chain pool stake balances and checks against local balances - for (const poolId of updatedPools) { - const stakeDelegatedByOwner = await stakingWrapper - .getStakeDelegatedToPoolByOwner(owner, poolId) - .callAsync(); - const totalStakeDelegated = await stakingWrapper.getTotalStakeDelegatedToPool(poolId).callAsync(); - expect(stakeDelegatedByOwner).to.deep.equal(ownerStake[StakeStatus.Delegated][poolId]); - expect(totalStakeDelegated).to.deep.equal(pools[poolId].delegatedStake); - } - }, + const totalStakeDelegated = await stakingWrapper.getTotalStakeDelegatedToPool(poolId).callAsync(); + expect(stakeDelegatedByOwner).to.deep.equal(ownerStake[StakeStatus.Delegated][poolId]); + expect(totalStakeDelegated).to.deep.equal(pools[poolId].delegatedStake); + } }, - ); + }); } /* tslint:enable:no-unnecessary-type-assertion */ diff --git a/contracts/integrations/test/framework/assertions/stake.ts b/contracts/integrations/test/framework/assertions/stake.ts index ac88003f0c..f2ef1f190a 100644 --- a/contracts/integrations/test/framework/assertions/stake.ts +++ b/contracts/integrations/test/framework/assertions/stake.ts @@ -1,6 +1,6 @@ import { GlobalStakeByStatus, OwnerStakeByStatus, StakeStatus, StoredBalance } from '@0x/contracts-staking'; import { expect } from '@0x/contracts-test-utils'; -import { BigNumber, logUtils } from '@0x/utils'; +import { BigNumber } from '@0x/utils'; import { TxData } from 'ethereum-types'; import { BlockchainBalanceStore } from '../balances/blockchain_balance_store'; @@ -34,7 +34,7 @@ export function validStakeAssertion( ): FunctionAssertion<[BigNumber], LocalBalanceStore, void> { const { stakingWrapper, zrxVault } = deployment.staking; - return new FunctionAssertion(stakingWrapper.stake.bind(stakingWrapper), { + return new FunctionAssertion(stakingWrapper, 'stake', { before: async (args: [BigNumber], txData: Partial) => { const [amount] = args; @@ -56,8 +56,6 @@ export function validStakeAssertion( ) => { const [amount] = args; - logUtils.log(`stake(${amount})`); - // Checks that the ZRX transfer updated balances as expected. await balanceStore.updateErc20BalancesAsync(); balanceStore.assertEquals(expectedBalances); diff --git a/contracts/integrations/test/framework/assertions/unstake.ts b/contracts/integrations/test/framework/assertions/unstake.ts index 6bfc6363fe..fea2384ee1 100644 --- a/contracts/integrations/test/framework/assertions/unstake.ts +++ b/contracts/integrations/test/framework/assertions/unstake.ts @@ -1,6 +1,6 @@ import { GlobalStakeByStatus, OwnerStakeByStatus, StakeStatus, StoredBalance } from '@0x/contracts-staking'; import { expect } from '@0x/contracts-test-utils'; -import { BigNumber, logUtils } from '@0x/utils'; +import { BigNumber } from '@0x/utils'; import { TxData } from 'ethereum-types'; import { BlockchainBalanceStore } from '../balances/blockchain_balance_store'; @@ -35,7 +35,7 @@ export function validUnstakeAssertion( ): FunctionAssertion<[BigNumber], LocalBalanceStore, void> { const { stakingWrapper, zrxVault } = deployment.staking; - return new FunctionAssertion(stakingWrapper.unstake.bind(stakingWrapper), { + return new FunctionAssertion(stakingWrapper, 'unstake', { before: async (args: [BigNumber], txData: Partial) => { const [amount] = args; @@ -57,8 +57,6 @@ export function validUnstakeAssertion( ) => { const [amount] = args; - logUtils.log(`unstake(${amount})`); - // Checks that the ZRX transfer updated balances as expected. await balanceStore.updateErc20BalancesAsync(); balanceStore.assertEquals(expectedBalances); diff --git a/contracts/integrations/test/framework/balances/balance_store.ts b/contracts/integrations/test/framework/balances/balance_store.ts index c0ecb2de3d..ea9b5d6eaa 100644 --- a/contracts/integrations/test/framework/balances/balance_store.ts +++ b/contracts/integrations/test/framework/balances/balance_store.ts @@ -1,5 +1,5 @@ import { BaseContract } from '@0x/base-contract'; -import { constants, expect, TokenBalances } from '@0x/contracts-test-utils'; +import { constants, expect, replaceKeysDeep, TokenBalances } from '@0x/contracts-test-utils'; import * as _ from 'lodash'; import { TokenAddresses, TokenContractsByName, TokenOwnersByName } from './types'; @@ -68,6 +68,14 @@ export class BalanceStore { this._addressNames = _.cloneDeep(balanceStore._addressNames); } + /** + * Returns a version of balances where keys are replaced with their readable counterparts, if + * they exist. + */ + public toReadable(): _.Dictionary<{}> { + return replaceKeysDeep(this.balances, this._readableAddressName.bind(this)); + } + /** * Returns the human-readable name for the given address, if it exists. * @param address The address to get the name for. diff --git a/contracts/integrations/test/framework/logger.ts b/contracts/integrations/test/framework/logger.ts new file mode 100644 index 0000000000..ec60b3aa88 --- /dev/null +++ b/contracts/integrations/test/framework/logger.ts @@ -0,0 +1,55 @@ +import { TxData } from 'ethereum-types'; + +import { Pseudorandom} from './pseudorandom'; + +// tslint:disable:no-console + +class Logger { + private _step = 0; + + constructor() { + console.warn( + JSON.stringify({ + level: 'info', + time: new Date(), + msg: `Pseudorandom seed: ${Pseudorandom.seed}`, + }), + ); + } + + /* + * Logs the name of the function executed, the arguments and transaction data it was + * called with, and the current step of the simulation. + */ + public logFunctionAssertion(functionName: string, functionArgs: any[], txData: Partial): void { + console.warn( + JSON.stringify({ + level: 'info', + time: new Date(), + msg: `Function called: ${functionName}(${functionArgs + .map(arg => JSON.stringify(arg).replace(/"/g, "'")) + .join(', ')})`, + step: this._step++, + txData, + }), + ); + } + + /* + * Logs information about a assertion failure. Dumps the error thrown and arbitrary data from + * the calling context. + */ + public logFailure(error: Error, data: string): void { + console.warn( + JSON.stringify({ + level: 'error', + time: new Date(), + step: this._step, + error, + data, + }), + ); + } +} + +export const logger = new Logger(); diff --git a/contracts/integrations/test/framework/simulation.ts b/contracts/integrations/test/framework/simulation.ts index 1ae337cb7f..4aca8a96db 100644 --- a/contracts/integrations/test/framework/simulation.ts +++ b/contracts/integrations/test/framework/simulation.ts @@ -1,10 +1,10 @@ import { GlobalStakeByStatus, StakeStatus, StakingPoolById, StoredBalance } from '@0x/contracts-staking'; -import * as _ from 'lodash'; import { Maker } from './actors/maker'; import { AssertionResult } from './assertions/function_assertion'; import { BlockchainBalanceStore } from './balances/blockchain_balance_store'; import { DeploymentManager } from './deployment_manager'; +import { logger } from './logger'; // tslint:disable:max-classes-per-file @@ -20,6 +20,14 @@ export class SimulationEnvironment { public balanceStore: BlockchainBalanceStore, public marketMakers: Maker[] = [], ) {} + + public state(): any { + return { + globalStake: this.globalStake, + stakingPools: this.stakingPools, + balanceStore: this.balanceStore.toReadable(), + }; + } } export abstract class Simulation { @@ -30,14 +38,23 @@ export abstract class Simulation { public async fuzzAsync(steps?: number): Promise { if (steps !== undefined) { for (let i = 0; i < steps; i++) { - await this.generator.next(); + await this._stepAsync(); } } else { while (true) { - await this.generator.next(); + await this._stepAsync(); } } } protected abstract _assertionGenerator(): AsyncIterableIterator; + + private async _stepAsync(): Promise { + try { + await this.generator.next(); + } catch (error) { + logger.logFailure(error, this.environment.state()); + throw error; + } + } } diff --git a/contracts/integrations/test/framework/tests/function_assertion_test.ts b/contracts/integrations/test/framework/tests/function_assertion_test.ts index 9e9e746b46..75b25947ec 100644 --- a/contracts/integrations/test/framework/tests/function_assertion_test.ts +++ b/contracts/integrations/test/framework/tests/function_assertion_test.ts @@ -23,14 +23,11 @@ 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<[BigNumber], void, BigNumber>( - exampleContract.returnInteger.bind(exampleContract), - { - before: async (args: [BigNumber], txData: Partial) => { - sideEffectTarget = randomInput; - }, + const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(exampleContract, 'returnInteger', { + before: async (args: [BigNumber], txData: Partial) => { + sideEffectTarget = randomInput; }, - ); + }); const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256); await assertion.executeAsync([randomInput], {}); expect(sideEffectTarget).bignumber.to.be.eq(randomInput); @@ -38,26 +35,23 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => { it('should call the after function with the provided arguments', async () => { let sideEffectTarget = ZERO_AMOUNT; - const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>( - exampleContract.returnInteger.bind(exampleContract), - { - after: async ( - _beforeInfo: any, - _result: FunctionResult, - args: [BigNumber], - txData: Partial, - ) => { - [sideEffectTarget] = args; - }, + const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(exampleContract, 'returnInteger', { + after: async ( + _beforeInfo: any, + _result: FunctionResult, + args: [BigNumber], + txData: Partial, + ) => { + [sideEffectTarget] = args; }, - ); + }); const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256); await assertion.executeAsync([randomInput], {}); 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)); + const assertion = new FunctionAssertion<[], {}, void>(exampleContract, 'emptyRevert'); await assertion.executeAsync([], {}); }); @@ -65,7 +59,8 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => { const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256); let sideEffectTarget = ZERO_AMOUNT; const assertion = new FunctionAssertion<[BigNumber], BigNumber, BigNumber>( - exampleContract.returnInteger.bind(exampleContract), + exampleContract, + 'returnInteger', { before: async (_args: [BigNumber], _txData: Partial) => { return randomInput; @@ -86,19 +81,16 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => { it('should pass the result from the function call to "after"', async () => { let sideEffectTarget = ZERO_AMOUNT; - const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>( - exampleContract.returnInteger.bind(exampleContract), - { - after: async ( - _beforeInfo: any, - result: FunctionResult, - _args: [BigNumber], - _txData: Partial, - ) => { - sideEffectTarget = result.data; - }, + const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(exampleContract, 'returnInteger', { + after: async ( + _beforeInfo: any, + result: FunctionResult, + _args: [BigNumber], + _txData: Partial, + ) => { + sideEffectTarget = result.data; }, - ); + }); const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256); await assertion.executeAsync([randomInput], {}); expect(sideEffectTarget).bignumber.to.be.eq(randomInput); @@ -106,21 +98,13 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => { it('should pass the receipt from the function call to "after"', async () => { let sideEffectTarget: TransactionReceiptWithDecodedLogs; - const assertion = new FunctionAssertion<[string], void, void>( - exampleContract.emitEvent.bind(exampleContract), - { - after: async ( - _beforeInfo: any, - result: FunctionResult, - _args: [string], - _txData: Partial, - ) => { - if (result.receipt) { - sideEffectTarget = result.receipt; - } - }, + const assertion = new FunctionAssertion<[string], void, void>(exampleContract, 'emitEvent', { + after: async (_beforeInfo: any, result: FunctionResult, _args: [string], _txData: Partial) => { + if (result.receipt) { + sideEffectTarget = result.receipt; + } }, - ); + }); const input = 'emitted data'; await assertion.executeAsync([input], {}); @@ -135,19 +119,11 @@ 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<[string], void, void>( - exampleContract.stringRevert.bind(exampleContract), - { - after: async ( - _beforeInfo: any, - result: FunctionResult, - _args: [string], - _txData: Partial, - ) => { - sideEffectTarget = result.data; - }, + const assertion = new FunctionAssertion<[string], void, void>(exampleContract, 'stringRevert', { + after: async (_beforeInfo: any, result: FunctionResult, _args: [string], _txData: Partial) => { + sideEffectTarget = result.data; }, - ); + }); const message = 'error message'; await assertion.executeAsync([message], {}); diff --git a/contracts/test-utils/src/lang_utils.ts b/contracts/test-utils/src/lang_utils.ts index 0a3d923260..660c52ed23 100644 --- a/contracts/test-utils/src/lang_utils.ts +++ b/contracts/test-utils/src/lang_utils.ts @@ -7,3 +7,13 @@ export function shortZip(a: T1[], b: T2[]): Array<[T1, T2]> { const minLength = Math.min(a.length, b.length); return _.zip(a.slice(0, minLength), b.slice(0, minLength)) as Array<[T1, T2]>; } + +/** + * Replaces the keys in a deeply nested object. Adapted from https://stackoverflow.com/a/39126851 + */ +export function replaceKeysDeep(obj: {}, mapKeys: (key: string) => string | void) { + return _.transform(obj, function(result, value, key) { + var currentKey = mapKeys(key) || key; + result[currentKey] = _.isObject(value) ? replaceKeysDeep(value, mapKeys) : value; + }); +} From 1dcbebd1309198e3735bbb6e9fc5a141733e87f8 Mon Sep 17 00:00:00 2001 From: Michael Zhu Date: Tue, 26 Nov 2019 14:44:39 -0800 Subject: [PATCH 3/6] lint --- contracts/test-utils/src/lang_utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/test-utils/src/lang_utils.ts b/contracts/test-utils/src/lang_utils.ts index 660c52ed23..484ccd555f 100644 --- a/contracts/test-utils/src/lang_utils.ts +++ b/contracts/test-utils/src/lang_utils.ts @@ -11,9 +11,9 @@ export function shortZip(a: T1[], b: T2[]): Array<[T1, T2]> { /** * Replaces the keys in a deeply nested object. Adapted from https://stackoverflow.com/a/39126851 */ -export function replaceKeysDeep(obj: {}, mapKeys: (key: string) => string | void) { - return _.transform(obj, function(result, value, key) { - var currentKey = mapKeys(key) || key; +export function replaceKeysDeep(obj: {}, mapKeys: (key: string) => string | void): _.Dictionary<{}> { + return _.transform(obj, (result, value, key) => { + const currentKey = mapKeys(key) || key; result[currentKey] = _.isObject(value) ? replaceKeysDeep(value, mapKeys) : value; }); } From 130653a1aab841b4ca15b086f19df17f6d65c668 Mon Sep 17 00:00:00 2001 From: Michael Zhu Date: Tue, 26 Nov 2019 15:07:11 -0800 Subject: [PATCH 4/6] move logger, pseudorandom, wrapper_interfaces to framework/utils/ --- contracts/integrations/test/deployment_test.ts | 2 +- contracts/integrations/test/framework/actors/maker.ts | 2 +- contracts/integrations/test/framework/actors/pool_operator.ts | 2 +- contracts/integrations/test/framework/actors/staker.ts | 2 +- contracts/integrations/test/framework/actors/taker.ts | 2 +- .../test/framework/assertions/function_assertion.ts | 2 +- contracts/integrations/test/framework/deployment_manager.ts | 2 +- contracts/integrations/test/framework/simulation.ts | 2 +- .../test/framework/tests/deployment_manager_test.ts | 2 +- contracts/integrations/test/framework/{ => utils}/logger.ts | 2 +- .../integrations/test/framework/{ => utils}/pseudorandom.ts | 2 +- .../test/framework/{ => utils}/wrapper_interfaces.ts | 0 contracts/integrations/test/fuzz_tests/pool_management_test.ts | 2 +- contracts/integrations/test/fuzz_tests/pool_membership_test.ts | 2 +- contracts/integrations/test/fuzz_tests/stake_management_test.ts | 2 +- 15 files changed, 14 insertions(+), 14 deletions(-) rename contracts/integrations/test/framework/{ => utils}/logger.ts (96%) rename contracts/integrations/test/framework/{ => utils}/pseudorandom.ts (99%) rename contracts/integrations/test/framework/{ => utils}/wrapper_interfaces.ts (100%) diff --git a/contracts/integrations/test/deployment_test.ts b/contracts/integrations/test/deployment_test.ts index 5cc02e5f40..497f49ec3c 100644 --- a/contracts/integrations/test/deployment_test.ts +++ b/contracts/integrations/test/deployment_test.ts @@ -33,7 +33,7 @@ import { AssetProxyId } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { TxData } from 'ethereum-types'; -import { AssetProxyDispatcher, Authorizable, Ownable } from './framework/wrapper_interfaces'; +import { AssetProxyDispatcher, Authorizable, Ownable } from './framework/utils/wrapper_interfaces'; // tslint:disable:no-unnecessary-type-assertion blockchainTests('Deployment and Configuration End to End Tests', env => { diff --git a/contracts/integrations/test/framework/actors/maker.ts b/contracts/integrations/test/framework/actors/maker.ts index 576f8098ba..a6e9452a69 100644 --- a/contracts/integrations/test/framework/actors/maker.ts +++ b/contracts/integrations/test/framework/actors/maker.ts @@ -4,7 +4,7 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { AssertionResult } from '../assertions/function_assertion'; import { validJoinStakingPoolAssertion } from '../assertions/joinStakingPool'; -import { Pseudorandom } from '../pseudorandom'; +import { Pseudorandom } from '../utils/pseudorandom'; import { Actor, ActorConfig, Constructor } from './base'; diff --git a/contracts/integrations/test/framework/actors/pool_operator.ts b/contracts/integrations/test/framework/actors/pool_operator.ts index 361576cb67..494f082838 100644 --- a/contracts/integrations/test/framework/actors/pool_operator.ts +++ b/contracts/integrations/test/framework/actors/pool_operator.ts @@ -6,7 +6,7 @@ import * as _ from 'lodash'; import { validCreateStakingPoolAssertion } from '../assertions/createStakingPool'; import { validDecreaseStakingPoolOperatorShareAssertion } from '../assertions/decreaseStakingPoolOperatorShare'; import { AssertionResult } from '../assertions/function_assertion'; -import { Pseudorandom } from '../pseudorandom'; +import { Pseudorandom } from '../utils/pseudorandom'; import { Actor, Constructor } from './base'; diff --git a/contracts/integrations/test/framework/actors/staker.ts b/contracts/integrations/test/framework/actors/staker.ts index d1cfe84f20..c75be0c36d 100644 --- a/contracts/integrations/test/framework/actors/staker.ts +++ b/contracts/integrations/test/framework/actors/staker.ts @@ -7,7 +7,7 @@ import { AssertionResult } from '../assertions/function_assertion'; import { validMoveStakeAssertion } from '../assertions/moveStake'; import { validStakeAssertion } from '../assertions/stake'; import { validUnstakeAssertion } from '../assertions/unstake'; -import { Pseudorandom } from '../pseudorandom'; +import { Pseudorandom } from '../utils/pseudorandom'; import { Actor, Constructor } from './base'; diff --git a/contracts/integrations/test/framework/actors/taker.ts b/contracts/integrations/test/framework/actors/taker.ts index 491a6d13ff..7a617e2dad 100644 --- a/contracts/integrations/test/framework/actors/taker.ts +++ b/contracts/integrations/test/framework/actors/taker.ts @@ -6,7 +6,7 @@ import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types'; import { validFillOrderCompleteFillAssertion } from '../assertions/fillOrder'; import { AssertionResult } from '../assertions/function_assertion'; import { DeploymentManager } from '../deployment_manager'; -import { Pseudorandom } from '../pseudorandom'; +import { Pseudorandom } from '../utils/pseudorandom'; import { Actor, Constructor } from './base'; diff --git a/contracts/integrations/test/framework/assertions/function_assertion.ts b/contracts/integrations/test/framework/assertions/function_assertion.ts index 678e11752f..44ecd8afa5 100644 --- a/contracts/integrations/test/framework/assertions/function_assertion.ts +++ b/contracts/integrations/test/framework/assertions/function_assertion.ts @@ -2,7 +2,7 @@ import { BaseContract, ContractFunctionObj, ContractTxFunctionObj } from '@0x/ba import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types'; import * as _ from 'lodash'; -import { logger } from '../logger'; +import { logger } from '../utils/logger'; // tslint:disable:max-classes-per-file export type GenericContractFunction = (...args: any[]) => ContractFunctionObj; diff --git a/contracts/integrations/test/framework/deployment_manager.ts b/contracts/integrations/test/framework/deployment_manager.ts index f1d3a2b87a..6cf0f4305c 100644 --- a/contracts/integrations/test/framework/deployment_manager.ts +++ b/contracts/integrations/test/framework/deployment_manager.ts @@ -25,7 +25,7 @@ import { BigNumber } from '@0x/utils'; import { TxData } from 'ethereum-types'; import * as _ from 'lodash'; -import { AssetProxyDispatcher, Authorizable, Ownable } from './wrapper_interfaces'; +import { AssetProxyDispatcher, Authorizable, Ownable } from './utils/wrapper_interfaces'; /** * Adds a batch of authorities to a list of authorizable contracts. diff --git a/contracts/integrations/test/framework/simulation.ts b/contracts/integrations/test/framework/simulation.ts index 4aca8a96db..3bbd778ba1 100644 --- a/contracts/integrations/test/framework/simulation.ts +++ b/contracts/integrations/test/framework/simulation.ts @@ -4,7 +4,7 @@ import { Maker } from './actors/maker'; import { AssertionResult } from './assertions/function_assertion'; import { BlockchainBalanceStore } from './balances/blockchain_balance_store'; import { DeploymentManager } from './deployment_manager'; -import { logger } from './logger'; +import { logger } from './utils/logger'; // tslint:disable:max-classes-per-file diff --git a/contracts/integrations/test/framework/tests/deployment_manager_test.ts b/contracts/integrations/test/framework/tests/deployment_manager_test.ts index 0b0746f412..2a08e150c0 100644 --- a/contracts/integrations/test/framework/tests/deployment_manager_test.ts +++ b/contracts/integrations/test/framework/tests/deployment_manager_test.ts @@ -2,7 +2,7 @@ import { constants as stakingConstants } from '@0x/contracts-staking'; import { blockchainTests, expect } from '@0x/contracts-test-utils'; import { DeploymentManager } from '../deployment_manager'; -import { Authorizable, Ownable } from '../wrapper_interfaces'; +import { Authorizable, Ownable } from '../utils/wrapper_interfaces'; blockchainTests('Deployment Manager', env => { let owner: string; diff --git a/contracts/integrations/test/framework/logger.ts b/contracts/integrations/test/framework/utils/logger.ts similarity index 96% rename from contracts/integrations/test/framework/logger.ts rename to contracts/integrations/test/framework/utils/logger.ts index ec60b3aa88..35bb16a0ab 100644 --- a/contracts/integrations/test/framework/logger.ts +++ b/contracts/integrations/test/framework/utils/logger.ts @@ -1,6 +1,6 @@ import { TxData } from 'ethereum-types'; -import { Pseudorandom} from './pseudorandom'; +import { Pseudorandom } from '../utils/pseudorandom'; // tslint:disable:no-console diff --git a/contracts/integrations/test/framework/pseudorandom.ts b/contracts/integrations/test/framework/utils/pseudorandom.ts similarity index 99% rename from contracts/integrations/test/framework/pseudorandom.ts rename to contracts/integrations/test/framework/utils/pseudorandom.ts index b41dd1a3a4..874338a5b0 100644 --- a/contracts/integrations/test/framework/pseudorandom.ts +++ b/contracts/integrations/test/framework/utils/pseudorandom.ts @@ -22,7 +22,7 @@ class PRNGWrapper { /* * Pseudorandom version of getRandomPortion/getRandomInteger. If two arguments are provided, * treats those arguments as the min and max (inclusive) of the desired range. If only one - * argument is provided, picks an integer between 0 and the argument. + * argument is provided, picks an integer between 0 and the argument. */ public integer(max: Numberish): BigNumber; public integer(min: Numberish, max: Numberish): BigNumber; diff --git a/contracts/integrations/test/framework/wrapper_interfaces.ts b/contracts/integrations/test/framework/utils/wrapper_interfaces.ts similarity index 100% rename from contracts/integrations/test/framework/wrapper_interfaces.ts rename to contracts/integrations/test/framework/utils/wrapper_interfaces.ts diff --git a/contracts/integrations/test/fuzz_tests/pool_management_test.ts b/contracts/integrations/test/fuzz_tests/pool_management_test.ts index 7df97f74e7..68147bfe0e 100644 --- a/contracts/integrations/test/fuzz_tests/pool_management_test.ts +++ b/contracts/integrations/test/fuzz_tests/pool_management_test.ts @@ -5,8 +5,8 @@ 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 { Pseudorandom } from '../framework/pseudorandom'; import { Simulation, SimulationEnvironment } from '../framework/simulation'; +import { Pseudorandom } from '../framework/utils/pseudorandom'; export class PoolManagementSimulation extends Simulation { protected async *_assertionGenerator(): AsyncIterableIterator { diff --git a/contracts/integrations/test/fuzz_tests/pool_membership_test.ts b/contracts/integrations/test/fuzz_tests/pool_membership_test.ts index 3be7053cb3..4b24274cdd 100644 --- a/contracts/integrations/test/fuzz_tests/pool_membership_test.ts +++ b/contracts/integrations/test/fuzz_tests/pool_membership_test.ts @@ -5,8 +5,8 @@ import { Maker } from '../framework/actors/maker'; import { AssertionResult } from '../framework/assertions/function_assertion'; import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store'; import { DeploymentManager } from '../framework/deployment_manager'; -import { Pseudorandom } from '../framework/pseudorandom'; import { Simulation, SimulationEnvironment } from '../framework/simulation'; +import { Pseudorandom } from '../framework/utils/pseudorandom'; import { PoolManagementSimulation } from './pool_management_test'; diff --git a/contracts/integrations/test/fuzz_tests/stake_management_test.ts b/contracts/integrations/test/fuzz_tests/stake_management_test.ts index fba7d8abc1..b34978191e 100644 --- a/contracts/integrations/test/fuzz_tests/stake_management_test.ts +++ b/contracts/integrations/test/fuzz_tests/stake_management_test.ts @@ -5,8 +5,8 @@ import { Staker } from '../framework/actors/staker'; import { AssertionResult } from '../framework/assertions/function_assertion'; import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store'; import { DeploymentManager } from '../framework/deployment_manager'; -import { Pseudorandom } from '../framework/pseudorandom'; import { Simulation, SimulationEnvironment } from '../framework/simulation'; +import { Pseudorandom } from '../framework/utils/pseudorandom'; import { PoolManagementSimulation } from './pool_management_test'; From 8cc35a60e604374f9cdb90a1dbf48ae3b4012f52 Mon Sep 17 00:00:00 2001 From: Michael Zhu Date: Mon, 2 Dec 2019 14:56:09 -0800 Subject: [PATCH 5/6] Add yarn command to run a specific fuzz test --- contracts/integrations/package.json | 1 + .../integrations/test/fuzz_tests/pool_management_test.ts | 7 ++++++- .../integrations/test/fuzz_tests/pool_membership_test.ts | 8 ++++++-- .../integrations/test/fuzz_tests/stake_management_test.ts | 8 +++++++- contracts/integrations/test/fuzz_tests/tslint.json | 6 ++++++ 5 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 contracts/integrations/test/fuzz_tests/tslint.json diff --git a/contracts/integrations/package.json b/contracts/integrations/package.json index e44b0875c9..3fe0ecc814 100644 --- a/contracts/integrations/package.json +++ b/contracts/integrations/package.json @@ -19,6 +19,7 @@ "test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html", "test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha", "run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit", + "test:fuzz": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/fuzz_tests/*.js' --timeout 0 --bail --exit", "compile": "sol-compiler", "watch": "sol-compiler -w", "clean": "shx rm -rf lib test/generated-artifacts test/generated-wrappers generated-artifacts generated-wrappers", diff --git a/contracts/integrations/test/fuzz_tests/pool_management_test.ts b/contracts/integrations/test/fuzz_tests/pool_management_test.ts index 68147bfe0e..e8b415b1c1 100644 --- a/contracts/integrations/test/fuzz_tests/pool_management_test.ts +++ b/contracts/integrations/test/fuzz_tests/pool_management_test.ts @@ -28,7 +28,12 @@ export class PoolManagementSimulation extends Simulation { } } -blockchainTests.skip('Pool management fuzz test', env => { +blockchainTests('Pool management fuzz test', env => { + before(function(): void { + if (process.env.FUZZ_TEST !== 'pool_management') { + this.skip(); + } + }); after(async () => { Actor.reset(); }); diff --git a/contracts/integrations/test/fuzz_tests/pool_membership_test.ts b/contracts/integrations/test/fuzz_tests/pool_membership_test.ts index 4b24274cdd..d17382c8aa 100644 --- a/contracts/integrations/test/fuzz_tests/pool_membership_test.ts +++ b/contracts/integrations/test/fuzz_tests/pool_membership_test.ts @@ -35,11 +35,15 @@ class PoolMembershipSimulation extends Simulation { } } -blockchainTests.skip('pool membership fuzz test', env => { +blockchainTests('pool membership fuzz test', env => { let deployment: DeploymentManager; let maker: Maker; - before(async () => { + before(async function(): Promise { + if (process.env.FUZZ_TEST !== 'pool_membership') { + this.skip(); + } + deployment = await DeploymentManager.deployAsync(env, { numErc20TokensToDeploy: 2, numErc721TokensToDeploy: 0, diff --git a/contracts/integrations/test/fuzz_tests/stake_management_test.ts b/contracts/integrations/test/fuzz_tests/stake_management_test.ts index b34978191e..a6f67bdc34 100644 --- a/contracts/integrations/test/fuzz_tests/stake_management_test.ts +++ b/contracts/integrations/test/fuzz_tests/stake_management_test.ts @@ -32,7 +32,13 @@ export class StakeManagementSimulation extends Simulation { } } -blockchainTests.skip('Stake management fuzz test', env => { +blockchainTests('Stake management fuzz test', env => { + before(function(): void { + if (process.env.FUZZ_TEST !== 'stake_management') { + this.skip(); + } + }); + after(async () => { Actor.reset(); }); diff --git a/contracts/integrations/test/fuzz_tests/tslint.json b/contracts/integrations/test/fuzz_tests/tslint.json new file mode 100644 index 0000000000..d427db8710 --- /dev/null +++ b/contracts/integrations/test/fuzz_tests/tslint.json @@ -0,0 +1,6 @@ +{ + "extends": ["@0x/tslint-config"], + "rules": { + "no-invalid-this": false + } +} From 4b7434d1e8c297406a316b8bd080e3acadab4d25 Mon Sep 17 00:00:00 2001 From: Michael Zhu Date: Tue, 3 Dec 2019 15:45:24 -0800 Subject: [PATCH 6/6] post-rebase lockfile update --- yarn.lock | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/yarn.lock b/yarn.lock index 311249b2dc..1a804c2352 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2347,6 +2347,11 @@ "@types/glob" "*" "@types/node" "*" +"@types/seedrandom@^2.4.28": + version "2.4.28" + resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-2.4.28.tgz#9ce8fa048c1e8c85cb71d7fe4d704e000226036f" + integrity sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA== + "@types/semver@5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" @@ -14672,6 +14677,11 @@ seedrandom@2.4.4: version "2.4.4" resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-2.4.4.tgz#b25ea98632c73e45f58b77cfaa931678df01f9ba" +seedrandom@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" + integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== + seek-bzip@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"