Use seeded rng for simulations

This commit is contained in:
Michael Zhu 2019-11-26 13:46:25 -08:00
parent 6b0f3570b9
commit d11cdcd5d2
10 changed files with 69 additions and 26 deletions

View File

@ -69,6 +69,7 @@
"@types/lodash": "4.14.104", "@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7", "@types/mocha": "^5.2.7",
"@types/node": "*", "@types/node": "*",
"@types/seedrandom": "^2.4.28",
"chai": "^4.0.1", "chai": "^4.0.1",
"chai-as-promised": "^7.1.0", "chai-as-promised": "^7.1.0",
"chai-bignumber": "^3.0.0", "chai-bignumber": "^3.0.0",
@ -78,6 +79,7 @@
"mocha": "^6.2.0", "mocha": "^6.2.0",
"nock": "^10.0.6", "nock": "^10.0.6",
"npm-run-all": "^4.1.2", "npm-run-all": "^4.1.2",
"seedrandom": "^3.0.5",
"shx": "^0.2.2", "shx": "^0.2.2",
"solhint": "^1.4.1", "solhint": "^1.4.1",
"truffle": "^5.0.32", "truffle": "^5.0.32",

View File

@ -1,10 +1,10 @@
import { constants, OrderFactory } from '@0x/contracts-test-utils'; import { constants, OrderFactory } from '@0x/contracts-test-utils';
import { Order, SignedOrder } from '@0x/types'; import { Order, SignedOrder } from '@0x/types';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash';
import { AssertionResult } from '../assertions/function_assertion'; import { AssertionResult } from '../assertions/function_assertion';
import { validJoinStakingPoolAssertion } from '../assertions/joinStakingPool'; import { validJoinStakingPoolAssertion } from '../assertions/joinStakingPool';
import { Pseudorandom } from '../pseudorandom';
import { Actor, ActorConfig, Constructor } from './base'; import { Actor, ActorConfig, Constructor } from './base';
@ -88,7 +88,7 @@ export function MakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
const { stakingPools } = this.actor.simulationEnvironment!; const { stakingPools } = this.actor.simulationEnvironment!;
const assertion = validJoinStakingPoolAssertion(this.actor.deployment); const assertion = validJoinStakingPoolAssertion(this.actor.deployment);
while (true) { while (true) {
const poolId = _.sample(Object.keys(stakingPools)); const poolId = Pseudorandom.sample(Object.keys(stakingPools));
if (poolId === undefined) { if (poolId === undefined) {
yield undefined; yield undefined;
} else { } else {

View File

@ -1,5 +1,4 @@
import { constants, StakingPoolById } from '@0x/contracts-staking'; import { constants, StakingPoolById } from '@0x/contracts-staking';
import { getRandomInteger } from '@0x/contracts-test-utils';
import '@azure/core-asynciterator-polyfill'; import '@azure/core-asynciterator-polyfill';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -7,6 +6,7 @@ import * as _ from 'lodash';
import { validCreateStakingPoolAssertion } from '../assertions/createStakingPool'; import { validCreateStakingPoolAssertion } from '../assertions/createStakingPool';
import { validDecreaseStakingPoolOperatorShareAssertion } from '../assertions/decreaseStakingPoolOperatorShare'; import { validDecreaseStakingPoolOperatorShareAssertion } from '../assertions/decreaseStakingPoolOperatorShare';
import { AssertionResult } from '../assertions/function_assertion'; import { AssertionResult } from '../assertions/function_assertion';
import { Pseudorandom } from '../pseudorandom';
import { Actor, Constructor } from './base'; import { Actor, Constructor } from './base';
@ -83,7 +83,7 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
const { stakingPools } = this.actor.simulationEnvironment!; const { stakingPools } = this.actor.simulationEnvironment!;
const assertion = validCreateStakingPoolAssertion(this.actor.deployment, stakingPools); const assertion = validCreateStakingPoolAssertion(this.actor.deployment, stakingPools);
while (true) { 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 }); yield assertion.executeAsync([operatorShare, false], { from: this.actor.address });
} }
} }
@ -92,11 +92,11 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
const { stakingPools } = this.actor.simulationEnvironment!; const { stakingPools } = this.actor.simulationEnvironment!;
const assertion = validDecreaseStakingPoolOperatorShareAssertion(this.actor.deployment, stakingPools); const assertion = validDecreaseStakingPoolOperatorShareAssertion(this.actor.deployment, stakingPools);
while (true) { while (true) {
const poolId = _.sample(this._getOperatorPoolIds(stakingPools)); const poolId = Pseudorandom.sample(this._getOperatorPoolIds(stakingPools));
if (poolId === undefined) { if (poolId === undefined) {
yield undefined; yield undefined;
} else { } 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 }); yield assertion.executeAsync([poolId, operatorShare], { from: this.actor.address });
} }
} }

View File

@ -1,5 +1,4 @@
import { OwnerStakeByStatus, StakeInfo, StakeStatus, StoredBalance } from '@0x/contracts-staking'; import { OwnerStakeByStatus, StakeInfo, StakeStatus, StoredBalance } from '@0x/contracts-staking';
import { getRandomInteger } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import '@azure/core-asynciterator-polyfill'; import '@azure/core-asynciterator-polyfill';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -8,6 +7,7 @@ import { AssertionResult } from '../assertions/function_assertion';
import { validMoveStakeAssertion } from '../assertions/moveStake'; import { validMoveStakeAssertion } from '../assertions/moveStake';
import { validStakeAssertion } from '../assertions/stake'; import { validStakeAssertion } from '../assertions/stake';
import { validUnstakeAssertion } from '../assertions/unstake'; import { validUnstakeAssertion } from '../assertions/unstake';
import { Pseudorandom } from '../pseudorandom';
import { Actor, Constructor } from './base'; import { Actor, Constructor } from './base';
@ -75,7 +75,7 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
while (true) { while (true) {
await balanceStore.updateErc20BalancesAsync(); await balanceStore.updateErc20BalancesAsync();
const zrxBalance = balanceStore.balances.erc20[this.actor.address][zrx.address]; 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 }); yield assertion.executeAsync([amount], { from: this.actor.address });
} }
} }
@ -94,7 +94,7 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
undelegatedStake.currentEpochBalance, undelegatedStake.currentEpochBalance,
undelegatedStake.nextEpochBalance, undelegatedStake.nextEpochBalance,
); );
const amount = getRandomInteger(0, withdrawableStake); const amount = Pseudorandom.integer(withdrawableStake);
yield assertion.executeAsync([amount], { from: this.actor.address }); yield assertion.executeAsync([amount], { from: this.actor.address });
} }
} }
@ -104,25 +104,27 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
const assertion = validMoveStakeAssertion(deployment, globalStake, this.stake, stakingPools); const assertion = validMoveStakeAssertion(deployment, globalStake, this.stake, stakingPools);
while (true) { 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 = const fromStatus =
fromPoolId === undefined fromPoolId === undefined
? StakeStatus.Undelegated ? StakeStatus.Undelegated
: (_.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus); : (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
const from = new StakeInfo(fromStatus, fromPoolId); const from = new StakeInfo(fromStatus, fromPoolId);
const toPoolId = _.sample(Object.keys(stakingPools)); const toPoolId = Pseudorandom.sample(Object.keys(stakingPools));
const toStatus = const toStatus =
toPoolId === undefined toPoolId === undefined
? StakeStatus.Undelegated ? StakeStatus.Undelegated
: (_.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus); : (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
const to = new StakeInfo(toStatus, toPoolId); const to = new StakeInfo(toStatus, toPoolId);
const moveableStake = const moveableStake =
from.status === StakeStatus.Undelegated from.status === StakeStatus.Undelegated
? this.stake[StakeStatus.Undelegated].nextEpochBalance ? this.stake[StakeStatus.Undelegated].nextEpochBalance
: this.stake[StakeStatus.Delegated][from.poolId].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 }); yield assertion.executeAsync([from, to, amount], { from: this.actor.address });
} }

View File

@ -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 { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types'; import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
import * as _ from 'lodash';
import { validFillOrderCompleteFillAssertion } from '../assertions/fillOrder'; import { validFillOrderCompleteFillAssertion } from '../assertions/fillOrder';
import { AssertionResult } from '../assertions/function_assertion'; import { AssertionResult } from '../assertions/function_assertion';
import { DeploymentManager } from '../deployment_manager'; import { DeploymentManager } from '../deployment_manager';
import { Pseudorandom } from '../pseudorandom';
import { Actor, Constructor } from './base'; import { Actor, Constructor } from './base';
@ -65,7 +65,7 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
const { marketMakers } = this.actor.simulationEnvironment!; const { marketMakers } = this.actor.simulationEnvironment!;
const assertion = validFillOrderCompleteFillAssertion(this.actor.deployment); const assertion = validFillOrderCompleteFillAssertion(this.actor.deployment);
while (true) { while (true) {
const maker = _.sample(marketMakers); const maker = Pseudorandom.sample(marketMakers);
if (maker === undefined) { if (maker === undefined) {
yield undefined; yield undefined;
} else { } else {
@ -82,8 +82,8 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
]); ]);
const order = await maker.signOrderAsync({ const order = await maker.signOrderAsync({
makerAssetAmount: getRandomInteger(constants.ZERO_AMOUNT, constants.INITIAL_ERC20_BALANCE), makerAssetAmount: Pseudorandom.integer(constants.INITIAL_ERC20_BALANCE),
takerAssetAmount: getRandomInteger(constants.ZERO_AMOUNT, constants.INITIAL_ERC20_BALANCE), takerAssetAmount: Pseudorandom.integer(constants.INITIAL_ERC20_BALANCE),
}); });
yield assertion.executeAsync([order, order.takerAssetAmount, order.signature], { yield assertion.executeAsync([order, order.takerAssetAmount, order.signature], {
from: this.actor.address, from: this.actor.address,

View File

@ -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<T>(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();

View File

@ -1,11 +1,11 @@
import { blockchainTests } from '@0x/contracts-test-utils'; import { blockchainTests } from '@0x/contracts-test-utils';
import * as _ from 'lodash';
import { Actor } from '../framework/actors/base'; import { Actor } from '../framework/actors/base';
import { PoolOperator } from '../framework/actors/pool_operator'; import { PoolOperator } from '../framework/actors/pool_operator';
import { AssertionResult } from '../framework/assertions/function_assertion'; import { AssertionResult } from '../framework/assertions/function_assertion';
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store'; import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
import { DeploymentManager } from '../framework/deployment_manager'; import { DeploymentManager } from '../framework/deployment_manager';
import { Pseudorandom } from '../framework/pseudorandom';
import { Simulation, SimulationEnvironment } from '../framework/simulation'; import { Simulation, SimulationEnvironment } from '../framework/simulation';
export class PoolManagementSimulation extends Simulation { export class PoolManagementSimulation extends Simulation {
@ -22,7 +22,7 @@ export class PoolManagementSimulation extends Simulation {
operator.simulationActions.validDecreaseStakingPoolOperatorShare, operator.simulationActions.validDecreaseStakingPoolOperatorShare,
]; ];
while (true) { while (true) {
const action = _.sample(actions); const action = Pseudorandom.sample(actions);
yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion
} }
} }

View File

@ -1,11 +1,11 @@
import { blockchainTests, constants } from '@0x/contracts-test-utils'; import { blockchainTests, constants } from '@0x/contracts-test-utils';
import * as _ from 'lodash';
import { MakerTaker } from '../framework/actors/hybrids'; import { MakerTaker } from '../framework/actors/hybrids';
import { Maker } from '../framework/actors/maker'; import { Maker } from '../framework/actors/maker';
import { AssertionResult } from '../framework/assertions/function_assertion'; import { AssertionResult } from '../framework/assertions/function_assertion';
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store'; import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
import { DeploymentManager } from '../framework/deployment_manager'; import { DeploymentManager } from '../framework/deployment_manager';
import { Pseudorandom } from '../framework/pseudorandom';
import { Simulation, SimulationEnvironment } from '../framework/simulation'; import { Simulation, SimulationEnvironment } from '../framework/simulation';
import { PoolManagementSimulation } from './pool_management_test'; import { PoolManagementSimulation } from './pool_management_test';
@ -29,7 +29,7 @@ class PoolMembershipSimulation extends Simulation {
]; ];
while (true) { while (true) {
const action = _.sample(actions); const action = Pseudorandom.sample(actions);
yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion
} }
} }

View File

@ -1,11 +1,11 @@
import { blockchainTests } from '@0x/contracts-test-utils'; import { blockchainTests } from '@0x/contracts-test-utils';
import * as _ from 'lodash';
import { Actor } from '../framework/actors/base'; import { Actor } from '../framework/actors/base';
import { Staker } from '../framework/actors/staker'; import { Staker } from '../framework/actors/staker';
import { AssertionResult } from '../framework/assertions/function_assertion'; import { AssertionResult } from '../framework/assertions/function_assertion';
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store'; import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
import { DeploymentManager } from '../framework/deployment_manager'; import { DeploymentManager } from '../framework/deployment_manager';
import { Pseudorandom } from '../framework/pseudorandom';
import { Simulation, SimulationEnvironment } from '../framework/simulation'; import { Simulation, SimulationEnvironment } from '../framework/simulation';
import { PoolManagementSimulation } from './pool_management_test'; import { PoolManagementSimulation } from './pool_management_test';
@ -26,7 +26,7 @@ export class StakeManagementSimulation extends Simulation {
poolManagement.generator, poolManagement.generator,
]; ];
while (true) { while (true) {
const action = _.sample(actions); const action = Pseudorandom.sample(actions);
yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion
} }
} }

View File

@ -50,7 +50,7 @@ export {
export { blockchainTests, BlockchainTestsEnvironment, describe } from './mocha_blockchain'; export { blockchainTests, BlockchainTestsEnvironment, describe } from './mocha_blockchain';
export { chaiSetup, expect } from './chai_setup'; export { chaiSetup, expect } from './chai_setup';
export { getCodesizeFromArtifact } from './codesize'; export { getCodesizeFromArtifact } from './codesize';
export { shortZip } from './lang_utils'; export { replaceKeysDeep, shortZip } from './lang_utils';
export { export {
assertIntegerRoughlyEquals, assertIntegerRoughlyEquals,
assertRoughlyEquals, assertRoughlyEquals,