moveStake assertion; use SimulationEnvironment to track global stake and staking pools

This commit is contained in:
Michael Zhu
2019-11-06 19:03:51 -08:00
parent c2919bcdb0
commit f33a9d162a
13 changed files with 373 additions and 88 deletions

View File

@@ -7,16 +7,12 @@ import {
validCreateStakingPoolAssertion,
validDecreaseStakingPoolOperatorShareAssertion,
} from '../function-assertions';
import { SimulationEnvironment } from '../simulation/simulation';
import { AssertionResult } from '../utils/function_assertions';
import { Actor, Constructor } from './base';
interface OperatorShareByPoolId {
[poolId: string]: number;
}
export interface PoolOperatorInterface {
operatorShares: OperatorShareByPoolId;
createStakingPoolAsync: (operatorShare: number, addOperatorAsMaker?: boolean) => Promise<string>;
decreaseOperatorShareAsync: (
poolId: string,
@@ -30,7 +26,6 @@ export interface PoolOperatorInterface {
*/
export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<PoolOperatorInterface> {
return class extends Base {
public readonly operatorShares: OperatorShareByPoolId = {};
public readonly actor: Actor;
/**
@@ -47,8 +42,10 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
if (this.actor.simulationEnvironment !== undefined) {
this.actor.simulationActions = {
...this.actor.simulationActions,
validCreateStakingPool: this._validCreateStakingPool(),
validDecreaseStakingPoolOperatorShare: this._validDecreaseStakingPoolOperatorShare(),
validCreateStakingPool: this._validCreateStakingPool(this.actor.simulationEnvironment),
validDecreaseStakingPoolOperatorShare: this._validDecreaseStakingPoolOperatorShare(
this.actor.simulationEnvironment,
),
};
}
}
@@ -69,7 +66,6 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
const createStakingPoolLog = txReceipt.logs[0];
const poolId = (createStakingPoolLog as any).args.poolId;
this.operatorShares[poolId] = operatorShare;
return poolId;
}
@@ -81,7 +77,6 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
newOperatorShare: number,
): Promise<TransactionReceiptWithDecodedLogs> {
const stakingContract = this.actor.deployment.staking.stakingWrapper;
this.operatorShares[poolId] = newOperatorShare;
return stakingContract.decreaseStakingPoolOperatorShare.awaitTransactionSuccessAsync(
poolId,
newOperatorShare,
@@ -89,22 +84,40 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
);
}
private async *_validCreateStakingPool(): AsyncIterableIterator<AssertionResult> {
const assertion = validCreateStakingPoolAssertion(this.actor.deployment, this);
private _getOperatorPoolIds(simulationEnvironment: SimulationEnvironment): string[] {
const operatorPools = _.pickBy(
simulationEnvironment.stakingPools,
pool => pool.operator === this.actor.address,
);
return Object.keys(operatorPools);
}
private async *_validCreateStakingPool(
simulationEnvironment: SimulationEnvironment,
): AsyncIterableIterator<AssertionResult> {
const assertion = validCreateStakingPoolAssertion(
this.actor.deployment,
simulationEnvironment.stakingPools,
);
while (true) {
const operatorShare = getRandomInteger(0, constants.PPM);
yield assertion.executeAsync(operatorShare, false, { from: this.actor.address });
}
}
private async *_validDecreaseStakingPoolOperatorShare(): AsyncIterableIterator<AssertionResult | void> {
const assertion = validDecreaseStakingPoolOperatorShareAssertion(this.actor.deployment, this);
private async *_validDecreaseStakingPoolOperatorShare(
simulationEnvironment: SimulationEnvironment,
): AsyncIterableIterator<AssertionResult | void> {
const assertion = validDecreaseStakingPoolOperatorShareAssertion(
this.actor.deployment,
simulationEnvironment.stakingPools,
);
while (true) {
const poolId = _.sample(Object.keys(this.operatorShares));
const poolId = _.sample(this._getOperatorPoolIds(simulationEnvironment));
if (poolId === undefined) {
yield undefined;
} else {
const operatorShare = getRandomInteger(0, this.operatorShares[poolId]);
const operatorShare = getRandomInteger(0, simulationEnvironment.stakingPools[poolId].operatorShare);
yield assertion.executeAsync(poolId, operatorShare, { from: this.actor.address });
}
}

View File

@@ -1,9 +1,10 @@
import { BlockchainBalanceStore } from '@0x/contracts-exchange';
import { StakeInfo, StakeStatus } from '@0x/contracts-staking';
import { OwnerStakeByStatus, StakeInfo, StakeStatus, StoredBalance } from '@0x/contracts-staking';
import { getRandomInteger } from '@0x/contracts-test-utils';
import { BigNumber, logUtils } from '@0x/utils';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { validStakeAssertion, validUnstakeAssertion } from '../function-assertions';
import { validMoveStakeAssertion, validStakeAssertion, validUnstakeAssertion } from '../function-assertions';
import { SimulationEnvironment } from '../simulation/simulation';
import { AssertionResult } from '../utils/function_assertions';
import { Actor, Constructor } from './base';
@@ -18,6 +19,7 @@ export interface StakerInterface {
*/
export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<StakerInterface> {
return class extends Base {
public stake: OwnerStakeByStatus;
public readonly actor: Actor;
/**
@@ -29,14 +31,18 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
// tslint:disable-next-line:no-inferred-empty-object-type
super(...args);
this.actor = (this as any) as Actor;
this.stake = {
[StakeStatus.Undelegated]: new StoredBalance(),
[StakeStatus.Delegated]: { total: new StoredBalance() },
};
// Register this mixin's assertion generators
if (this.actor.simulationEnvironment !== undefined) {
const { balanceStore } = this.actor.simulationEnvironment;
this.actor.simulationActions = {
...this.actor.simulationActions,
validStake: this._validStake(balanceStore),
validUnstake: this._validUnstake(balanceStore),
validStake: this._validStake(this.actor.simulationEnvironment),
validUnstake: this._validUnstake(this.actor.simulationEnvironment),
validMoveStake: this._validMoveStake(this.actor.simulationEnvironment),
};
}
}
@@ -60,25 +66,30 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
}
}
private async *_validStake(balanceStore: BlockchainBalanceStore): AsyncIterableIterator<AssertionResult> {
private async *_validStake(
simulationEnvironment: SimulationEnvironment,
): AsyncIterableIterator<AssertionResult> {
const { zrx } = this.actor.deployment.tokens;
const assertion = validStakeAssertion(this.actor.deployment, balanceStore);
const { deployment, balanceStore, globalStake } = simulationEnvironment;
const assertion = validStakeAssertion(deployment, balanceStore, globalStake, this.stake);
while (true) {
await balanceStore.updateErc20BalancesAsync();
const zrxBalance = balanceStore.balances.erc20[this.actor.address][zrx.address];
await simulationEnvironment.balanceStore.updateErc20BalancesAsync();
const zrxBalance = simulationEnvironment.balanceStore.balances.erc20[this.actor.address][zrx.address];
const amount = getRandomInteger(0, zrxBalance);
logUtils.log(`stake(${amount})`);
yield assertion.executeAsync(amount, { from: this.actor.address });
}
}
private async *_validUnstake(balanceStore: BlockchainBalanceStore): AsyncIterableIterator<AssertionResult> {
private async *_validUnstake(
simulationEnvironment: SimulationEnvironment,
): AsyncIterableIterator<AssertionResult> {
const { stakingWrapper } = this.actor.deployment.staking;
const assertion = validUnstakeAssertion(this.actor.deployment, balanceStore);
const { deployment, balanceStore, globalStake } = simulationEnvironment;
const assertion = validUnstakeAssertion(deployment, balanceStore, globalStake, this.stake);
while (true) {
await balanceStore.updateErc20BalancesAsync();
await simulationEnvironment.balanceStore.updateErc20BalancesAsync();
const undelegatedStake = await stakingWrapper.getOwnerStakeByStatus.callAsync(
this.actor.address,
StakeStatus.Undelegated,
@@ -88,10 +99,45 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
undelegatedStake.nextEpochBalance,
);
const amount = getRandomInteger(0, withdrawableStake);
logUtils.log(`unstake(${amount})`);
yield assertion.executeAsync(amount, { from: this.actor.address });
}
}
private async *_validMoveStake(
simulationEnvironment: SimulationEnvironment,
): AsyncIterableIterator<AssertionResult> {
const { deployment, globalStake } = simulationEnvironment;
const assertion = validMoveStakeAssertion(
deployment,
globalStake,
this.stake,
simulationEnvironment.stakingPools,
);
while (true) {
const fromPoolId = _.sample(Object.keys(_.omit(this.stake[StakeStatus.Delegated], ['total'])));
const fromStatus =
fromPoolId === undefined
? StakeStatus.Undelegated
: (_.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
const from = new StakeInfo(fromStatus, fromPoolId);
const toPoolId = _.sample(Object.keys(simulationEnvironment.stakingPools));
const toStatus =
toPoolId === undefined
? StakeStatus.Undelegated
: (_.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);
yield assertion.executeAsync(from, to, amount, { from: this.actor.address });
}
}
};
}

View File

@@ -1,5 +1,7 @@
import { StakingPoolById, StoredBalance } from '@0x/contracts-staking';
import { expect } from '@0x/contracts-test-utils';
import { BigNumber, logUtils } from '@0x/utils';
import { TxData } from 'ethereum-types';
import { DeploymentManager } from '../utils/deployment_manager';
import { FunctionAssertion, FunctionResult } from '../utils/function_assertions';
@@ -12,7 +14,7 @@ import { FunctionAssertion, FunctionResult } from '../utils/function_assertions'
*/
export function validCreateStakingPoolAssertion(
deployment: DeploymentManager,
context?: any,
pools: StakingPoolById,
): FunctionAssertion<string> {
const { stakingWrapper } = deployment.staking;
@@ -29,14 +31,18 @@ export function validCreateStakingPoolAssertion(
result: FunctionResult,
operatorShare: number,
addOperatorAsMaker: boolean,
txData: Partial<TxData>,
) => {
logUtils.log(`createStakingPool(${operatorShare}, ${addOperatorAsMaker}) => ${expectedPoolId}`);
const log = result.receipt!.logs[0]; // tslint:disable-line:no-non-null-assertion
const actualPoolId = (log as any).args.poolId;
expect(actualPoolId).to.equal(expectedPoolId);
logUtils.log(`createStakingPool(${operatorShare}, ${addOperatorAsMaker}) => ${actualPoolId}`);
if (context !== undefined) {
context.operatorShares[actualPoolId] = operatorShare;
}
pools[actualPoolId] = {
operator: txData.from as string,
operatorShare,
delegatedStake: new StoredBalance(),
};
},
});
}

View File

@@ -1,3 +1,4 @@
import { StakingPoolById } from '@0x/contracts-staking';
import { expect } from '@0x/contracts-test-utils';
import { logUtils } from '@0x/utils';
@@ -10,18 +11,17 @@ import { FunctionAssertion, FunctionResult } from '../utils/function_assertions'
*/
export function validDecreaseStakingPoolOperatorShareAssertion(
deployment: DeploymentManager,
context?: any,
pools: StakingPoolById,
): FunctionAssertion<{}> {
const { stakingWrapper } = deployment.staking;
return new FunctionAssertion<{}>(stakingWrapper.decreaseStakingPoolOperatorShare, {
after: async (_beforeInfo, _result: FunctionResult, poolId: string, expectedOperatorShare: number) => {
logUtils.log(`decreaseStakingPoolOperatorShare(${poolId}, ${expectedOperatorShare})`);
const { operatorShare } = await stakingWrapper.getStakingPool.callAsync(poolId);
expect(operatorShare).to.bignumber.equal(expectedOperatorShare);
logUtils.log(`decreaseStakingPoolOperatorShare(${poolId}, ${expectedOperatorShare})`);
if (context !== undefined) {
context.operatorShares[poolId] = operatorShare;
}
pools[poolId].operatorShare = operatorShare;
},
});
}

View File

@@ -2,3 +2,4 @@ export * from './stake';
export * from './unstake';
export * from './createStakingPool';
export * from './decreaseStakingPoolOperatorShare';
export * from './moveStake';

View File

@@ -0,0 +1,111 @@
import {
GlobalStakeByStatus,
OwnerStakeByStatus,
StakeInfo,
StakeStatus,
StakingPoolById,
StoredBalance,
} from '@0x/contracts-staking';
import { constants, expect } from '@0x/contracts-test-utils';
import { BigNumber, logUtils } from '@0x/utils';
import { TxData } from 'ethereum-types';
import * as _ from 'lodash';
import { DeploymentManager } from '../utils/deployment_manager';
import { FunctionAssertion } from '../utils/function_assertions';
function incrementNextEpochBalance(stakeBalance: StoredBalance, amount: BigNumber): void {
_.update(stakeBalance, ['nextEpochBalance'], balance => (balance || constants.ZERO_AMOUNT).plus(amount));
}
function decrementNextEpochBalance(stakeBalance: StoredBalance, amount: BigNumber): void {
_.update(stakeBalance, ['nextEpochBalance'], balance => (balance || constants.ZERO_AMOUNT).minus(amount));
}
/**
* Returns a FunctionAssertion for `moveStake` which assumes valid input is provided. The
* FunctionAssertion checks that the staker's
*/
export function validMoveStakeAssertion(
deployment: DeploymentManager,
globalStake: GlobalStakeByStatus,
ownerStake: OwnerStakeByStatus,
pools: StakingPoolById,
): FunctionAssertion<{}> {
const { stakingWrapper } = deployment.staking;
return new FunctionAssertion<{}>(stakingWrapper.moveStake, {
after: async (
_beforeInfo,
_result,
from: StakeInfo,
to: StakeInfo,
amount: BigNumber,
txData: Partial<TxData>,
) => {
logUtils.log(
`moveStake({status: ${StakeStatus[from.status]}, poolId: ${from.poolId} }, { status: ${
StakeStatus[to.status]
}, poolId: ${to.poolId} }, ${amount})`,
);
const owner = txData.from as string;
const updatedPools = [];
if (from.status === StakeStatus.Undelegated) {
decrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount);
decrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount);
} else if (from.status === StakeStatus.Delegated) {
_.defaults(ownerStake[StakeStatus.Delegated], {
[from.poolId]: new StoredBalance(),
});
decrementNextEpochBalance(ownerStake[StakeStatus.Delegated][from.poolId], amount);
decrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount);
decrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount);
decrementNextEpochBalance(pools[from.poolId].delegatedStake, amount);
updatedPools.push(from.poolId);
}
if (to.status === StakeStatus.Undelegated) {
incrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount);
incrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount);
} else if (to.status === StakeStatus.Delegated) {
_.defaults(ownerStake[StakeStatus.Delegated], {
[to.poolId]: new StoredBalance(),
});
incrementNextEpochBalance(ownerStake[StakeStatus.Delegated][to.poolId], amount);
incrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount);
incrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount);
incrementNextEpochBalance(pools[to.poolId].delegatedStake, amount);
updatedPools.push(to.poolId);
}
const ownerUndelegatedStake = {
...new StoredBalance(),
...(await stakingWrapper.getOwnerStakeByStatus.callAsync(owner, StakeStatus.Undelegated)),
};
const ownerDelegatedStake = {
...new StoredBalance(),
...(await stakingWrapper.getOwnerStakeByStatus.callAsync(owner, StakeStatus.Delegated)),
};
expect(ownerUndelegatedStake).to.deep.equal(ownerStake[StakeStatus.Undelegated]);
expect(ownerDelegatedStake).to.deep.equal(ownerStake[StakeStatus.Delegated].total);
const globalUndelegatedStake = await stakingWrapper.getGlobalStakeByStatus.callAsync(
StakeStatus.Undelegated,
);
const globalDelegatedStake = await stakingWrapper.getGlobalStakeByStatus.callAsync(StakeStatus.Delegated);
expect(globalUndelegatedStake).to.deep.equal(globalStake[StakeStatus.Undelegated]);
expect(globalDelegatedStake).to.deep.equal(globalStake[StakeStatus.Delegated]);
for (const poolId of updatedPools) {
const stakeDelegatedByOwner = await stakingWrapper.getStakeDelegatedToPoolByOwner.callAsync(
owner,
poolId,
);
const totalStakeDelegated = await stakingWrapper.getTotalStakeDelegatedToPool.callAsync(poolId);
expect(stakeDelegatedByOwner).to.deep.equal(ownerStake[StakeStatus.Delegated][poolId]);
expect(totalStakeDelegated).to.deep.equal(pools[poolId].delegatedStake);
}
},
});
}

View File

@@ -1,10 +1,23 @@
import { BlockchainBalanceStore, LocalBalanceStore } from '@0x/contracts-exchange';
import { GlobalStakeByStatus, OwnerStakeByStatus, StakeStatus, StoredBalance } from '@0x/contracts-staking';
import { expect } from '@0x/contracts-test-utils';
import { assetDataUtils } from '@0x/order-utils';
import { BigNumber } from '@0x/utils';
import { BigNumber, logUtils } from '@0x/utils';
import { TxData } from 'ethereum-types';
import { DeploymentManager } from '../utils/deployment_manager';
import { FunctionAssertion } from '../utils/function_assertions';
import { FunctionAssertion, FunctionResult } from '../utils/function_assertions';
function expectedUndelegatedStake(
initStake: OwnerStakeByStatus | GlobalStakeByStatus,
amount: BigNumber,
): StoredBalance {
return {
currentEpoch: initStake[StakeStatus.Undelegated].currentEpoch,
currentEpochBalance: initStake[StakeStatus.Undelegated].currentEpochBalance.plus(amount),
nextEpochBalance: initStake[StakeStatus.Undelegated].nextEpochBalance.plus(amount),
};
}
/**
* Returns a FunctionAssertion for `stake` which assumes valid input is provided. The
@@ -14,6 +27,8 @@ import { FunctionAssertion } from '../utils/function_assertions';
export function validStakeAssertion(
deployment: DeploymentManager,
balanceStore: BlockchainBalanceStore,
globalStake: GlobalStakeByStatus,
ownerStake: OwnerStakeByStatus,
): FunctionAssertion<LocalBalanceStore> {
const { stakingWrapper, zrxVault } = deployment.staking;
const { zrx } = deployment.tokens;
@@ -29,9 +44,31 @@ export function validStakeAssertion(
);
return expectedBalances;
},
after: async (expectedBalances: LocalBalanceStore) => {
after: async (
expectedBalances: LocalBalanceStore,
_result: FunctionResult,
amount: BigNumber,
txData: Partial<TxData>,
) => {
logUtils.log(`stake(${amount})`);
await balanceStore.updateErc20BalancesAsync();
balanceStore.assertEquals(expectedBalances);
const ownerUndelegatedStake = await stakingWrapper.getOwnerStakeByStatus.callAsync(
txData.from as string,
StakeStatus.Undelegated,
);
const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount);
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake);
ownerStake[StakeStatus.Undelegated] = expectedOwnerUndelegatedStake;
const globalUndelegatedStake = await stakingWrapper.getGlobalStakeByStatus.callAsync(
StakeStatus.Undelegated,
);
const expectedGlobalUndelegatedStake = expectedUndelegatedStake(globalStake, amount);
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(expectedGlobalUndelegatedStake);
globalStake[StakeStatus.Undelegated] = expectedGlobalUndelegatedStake;
},
});
}

View File

@@ -1,10 +1,23 @@
import { BlockchainBalanceStore, LocalBalanceStore } from '@0x/contracts-exchange';
import { GlobalStakeByStatus, OwnerStakeByStatus, StakeStatus, StoredBalance } from '@0x/contracts-staking';
import { expect } from '@0x/contracts-test-utils';
import { assetDataUtils } from '@0x/order-utils';
import { BigNumber } from '@0x/utils';
import { BigNumber, logUtils } from '@0x/utils';
import { TxData } from 'ethereum-types';
import { DeploymentManager } from '../utils/deployment_manager';
import { FunctionAssertion } from '../utils/function_assertions';
import { FunctionAssertion, FunctionResult } from '../utils/function_assertions';
function expectedUndelegatedStake(
initStake: OwnerStakeByStatus | GlobalStakeByStatus,
amount: BigNumber,
): StoredBalance {
return {
currentEpoch: initStake[StakeStatus.Undelegated].currentEpoch,
currentEpochBalance: initStake[StakeStatus.Undelegated].currentEpochBalance.minus(amount),
nextEpochBalance: initStake[StakeStatus.Undelegated].nextEpochBalance.minus(amount),
};
}
/**
* Returns a FunctionAssertion for `unstake` which assumes valid input is provided. The
@@ -14,6 +27,8 @@ import { FunctionAssertion } from '../utils/function_assertions';
export function validUnstakeAssertion(
deployment: DeploymentManager,
balanceStore: BlockchainBalanceStore,
globalStake: GlobalStakeByStatus,
ownerStake: OwnerStakeByStatus,
): FunctionAssertion<LocalBalanceStore> {
const { stakingWrapper, zrxVault } = deployment.staking;
const { zrx } = deployment.tokens;
@@ -29,9 +44,31 @@ export function validUnstakeAssertion(
);
return expectedBalances;
},
after: async (expectedBalances: LocalBalanceStore) => {
after: async (
expectedBalances: LocalBalanceStore,
_result: FunctionResult,
amount: BigNumber,
txData: Partial<TxData>,
) => {
logUtils.log(`unstake(${amount})`);
await balanceStore.updateErc20BalancesAsync();
balanceStore.assertEquals(expectedBalances);
const ownerUndelegatedStake = await stakingWrapper.getOwnerStakeByStatus.callAsync(
txData.from as string,
StakeStatus.Undelegated,
);
const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount);
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake);
ownerStake[StakeStatus.Undelegated] = expectedOwnerUndelegatedStake;
const globalUndelegatedStake = await stakingWrapper.getGlobalStakeByStatus.callAsync(
StakeStatus.Undelegated,
);
const expectedGlobalUndelegatedStake = expectedUndelegatedStake(globalStake, amount);
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(expectedGlobalUndelegatedStake);
globalStake[StakeStatus.Undelegated] = expectedGlobalUndelegatedStake;
},
});
}

View File

@@ -35,6 +35,7 @@ blockchainTests.resets('fillOrder integration tests', env => {
let delegator: StakerKeeper;
let poolId: string;
let operatorShare: number;
before(async () => {
deployment = await DeploymentManager.deployAsync(env, {
@@ -79,7 +80,8 @@ blockchainTests.resets('fillOrder integration tests', env => {
await delegator.configureERC20TokenAsync(deployment.tokens.zrx);
// Create a staking pool with the operator as a maker.
poolId = await operator.createStakingPoolAsync(stakingConstants.PPM * 0.95, true);
operatorShare = stakingConstants.PPM * 0.95;
poolId = await operator.createStakingPoolAsync(operatorShare, true);
// A vanilla maker joins the pool as well.
await maker.joinStakingPoolAsync(poolId);
@@ -283,7 +285,7 @@ blockchainTests.resets('fillOrder integration tests', env => {
// The rewards are split between the operator and delegator based on the pool's operatorShare
const operatorReward = rewardsAvailable
.times(operator.operatorShares[poolId])
.times(operatorShare)
.dividedToIntegerBy(constants.PPM_DENOMINATOR);
const delegatorReward = rewardsAvailable.minus(operatorReward);
@@ -364,7 +366,7 @@ blockchainTests.resets('fillOrder integration tests', env => {
// The rewards are split between the operator and delegator based on the pool's operatorShare
const operatorReward = rewardsAvailable
.times(operator.operatorShares[poolId])
.times(operatorShare)
.dividedToIntegerBy(constants.PPM_DENOMINATOR);
// Finalize the pool. This should automatically pay the operator in WETH.

View File

@@ -9,10 +9,6 @@ import { AssertionResult } from '../utils/function_assertions';
import { Simulation, SimulationEnvironment } from './simulation';
export class PoolManagementSimulation extends Simulation {
constructor(environment: SimulationEnvironment) {
super(environment);
}
protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
const { deployment } = this.environment;
const operator = new PoolOperator({
@@ -41,7 +37,8 @@ blockchainTests.skip('Pool management fuzz test', env => {
});
const balanceStore = new BlockchainBalanceStore({}, {});
const sim = new PoolManagementSimulation({ balanceStore, deployment });
return sim.fuzzAsync();
const simulationEnv = new SimulationEnvironment(deployment, balanceStore);
const simulation = new PoolManagementSimulation(simulationEnv);
return simulation.fuzzAsync();
});
});

View File

@@ -1,31 +1,35 @@
import { BlockchainBalanceStore } from '@0x/contracts-exchange';
import { GlobalStakeByStatus, StakeStatus, StakingPoolById, StoredBalance } from '@0x/contracts-staking';
import * as _ from 'lodash';
import { DeploymentManager } from '../utils/deployment_manager';
import { AssertionResult } from '../utils/function_assertions';
export interface SimulationEnvironment {
balanceStore: BlockchainBalanceStore;
deployment: DeploymentManager;
// tslint:disable:max-classes-per-file
export class SimulationEnvironment {
public globalStake: GlobalStakeByStatus = {
[StakeStatus.Undelegated]: new StoredBalance(),
[StakeStatus.Delegated]: new StoredBalance(),
};
public stakingPools: StakingPoolById = {};
public constructor(public readonly deployment: DeploymentManager, public balanceStore: BlockchainBalanceStore) {}
}
export abstract class Simulation {
private readonly _generator = this._assertionGenerator();
public readonly generator = this._assertionGenerator();
protected constructor(public readonly environment: SimulationEnvironment) {}
public async stepAsync(): Promise<void> {
await this._generator.next();
}
constructor(public environment: SimulationEnvironment) {}
public async fuzzAsync(steps?: number): Promise<void> {
if (steps !== undefined) {
for (let i = 0; i < steps; i++) {
await this.stepAsync();
await this.generator.next();
}
} else {
while (true) {
await this.stepAsync();
await this.generator.next();
}
}
}

View File

@@ -6,20 +6,24 @@ import { Staker } from '../actors';
import { DeploymentManager } from '../utils/deployment_manager';
import { AssertionResult } from '../utils/function_assertions';
import { PoolManagementSimulation } from './pool_management_test';
import { Simulation, SimulationEnvironment } from './simulation';
export class StakeManagementSimulation extends Simulation {
constructor(environment: SimulationEnvironment) {
super(environment);
}
protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
const { deployment, balanceStore } = this.environment;
const poolManagement = new PoolManagementSimulation(this.environment);
const staker = new Staker({ name: 'Staker', deployment, simulationEnvironment: this.environment });
await staker.configureERC20TokenAsync(deployment.tokens.zrx);
balanceStore.registerTokenOwner(staker.address, staker.name);
const actions = [staker.simulationActions.validStake, staker.simulationActions.validUnstake];
const actions = [
staker.simulationActions.validStake,
staker.simulationActions.validUnstake,
staker.simulationActions.validMoveStake,
poolManagement.generator,
];
while (true) {
const action = _.sample(actions);
yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion
@@ -42,7 +46,8 @@ blockchainTests.skip('Stake management fuzz test', env => {
{ erc20: { ZRX: deployment.tokens.zrx } },
);
const sim = new StakeManagementSimulation({ balanceStore, deployment });
return sim.fuzzAsync();
const simulationEnv = new SimulationEnvironment(deployment, balanceStore);
const simulation = new StakeManagementSimulation(simulationEnv);
return simulation.fuzzAsync();
});
});