moveStake assertion; use SimulationEnvironment to track global stake and staking pools
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from './stake';
|
||||
export * from './unstake';
|
||||
export * from './createStakingPool';
|
||||
export * from './decreaseStakingPoolOperatorShare';
|
||||
export * from './moveStake';
|
||||
|
||||
111
contracts/integrations/test/function-assertions/moveStake.ts
Normal file
111
contracts/integrations/test/function-assertions/moveStake.ts
Normal 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);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user