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,