address comments

This commit is contained in:
Michael Zhu 2019-11-07 17:42:25 -08:00
parent 1f5a0987cb
commit 48ecd32d5d
10 changed files with 126 additions and 99 deletions

View File

@ -1,5 +1,5 @@
import { BaseContract } from '@0x/base-contract';
import { expect, TokenBalances } from '@0x/contracts-test-utils';
import { constants, expect, TokenBalances } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
@ -37,7 +37,7 @@ export class BalanceStore {
}
/**
* Registers the given token owner in this balance store. The token owner's balane will be
* Registers the given token owner in this balance store. The token owner's balance will be
* tracked in subsequent operations.
* @param address Address of the token owner
* @param name Name of the token owner
@ -83,8 +83,8 @@ export class BalanceStore {
*/
private _assertEthBalancesEqual(rhs: BalanceStore): void {
for (const ownerAddress of [...this._ownerAddresses, ...rhs._ownerAddresses]) {
const thisBalance = _.get(this.balances.eth, [ownerAddress], new BigNumber(0));
const rhsBalance = _.get(rhs.balances.eth, [ownerAddress], new BigNumber(0));
const thisBalance = _.get(this.balances.eth, [ownerAddress], constants.ZERO_AMOUNT);
const rhsBalance = _.get(rhs.balances.eth, [ownerAddress], constants.ZERO_AMOUNT);
expect(thisBalance, `${this._readableAddressName(ownerAddress)} ETH balance`).to.bignumber.equal(
rhsBalance,
);
@ -98,8 +98,8 @@ export class BalanceStore {
private _assertErc20BalancesEqual(rhs: BalanceStore): void {
for (const ownerAddress of [...this._ownerAddresses, ...rhs._ownerAddresses]) {
for (const tokenAddress of [...this._tokenAddresses.erc20, ...rhs._tokenAddresses.erc20]) {
const thisBalance = _.get(this.balances.erc20, [ownerAddress, tokenAddress], new BigNumber(0));
const rhsBalance = _.get(rhs.balances.erc20, [ownerAddress, tokenAddress], new BigNumber(0));
const thisBalance = _.get(this.balances.erc20, [ownerAddress, tokenAddress], constants.ZERO_AMOUNT);
const rhsBalance = _.get(rhs.balances.erc20, [ownerAddress, tokenAddress], constants.ZERO_AMOUNT);
expect(
thisBalance,
`${this._readableAddressName(ownerAddress)} ${this._readableAddressName(tokenAddress)} balance`,

View File

@ -1,4 +1,4 @@
import { constants } from '@0x/contracts-staking';
import { constants, StakingPoolById } from '@0x/contracts-staking';
import { getRandomInteger } from '@0x/contracts-test-utils';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash';
@ -7,7 +7,6 @@ import {
validCreateStakingPoolAssertion,
validDecreaseStakingPoolOperatorShareAssertion,
} from '../function-assertions';
import { SimulationEnvironment } from '../simulation/simulation';
import { AssertionResult } from '../utils/function_assertions';
import { Actor, Constructor } from './base';
@ -39,16 +38,12 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
this.actor = (this as any) as Actor;
// Register this mixin's assertion generators
if (this.actor.simulationEnvironment !== undefined) {
this.actor.simulationActions = {
...this.actor.simulationActions,
validCreateStakingPool: this._validCreateStakingPool(this.actor.simulationEnvironment),
validDecreaseStakingPoolOperatorShare: this._validDecreaseStakingPoolOperatorShare(
this.actor.simulationEnvironment,
),
validCreateStakingPool: this._validCreateStakingPool(),
validDecreaseStakingPoolOperatorShare: this._validDecreaseStakingPoolOperatorShare(),
};
}
}
/**
* Creates a staking pool and returns the ID of the new pool.
@ -84,40 +79,29 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
);
}
private _getOperatorPoolIds(simulationEnvironment: SimulationEnvironment): string[] {
const operatorPools = _.pickBy(
simulationEnvironment.stakingPools,
pool => pool.operator === this.actor.address,
);
private _getOperatorPoolIds(stakingPools: StakingPoolById): string[] {
const operatorPools = _.pickBy(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,
);
private async *_validCreateStakingPool(): AsyncIterableIterator<AssertionResult> {
const { stakingPools } = this.actor.simulationEnvironment!;
const assertion = validCreateStakingPoolAssertion(this.actor.deployment, stakingPools);
while (true) {
const operatorShare = getRandomInteger(0, constants.PPM);
yield assertion.executeAsync(operatorShare, false, { from: this.actor.address });
}
}
private async *_validDecreaseStakingPoolOperatorShare(
simulationEnvironment: SimulationEnvironment,
): AsyncIterableIterator<AssertionResult | void> {
const assertion = validDecreaseStakingPoolOperatorShareAssertion(
this.actor.deployment,
simulationEnvironment.stakingPools,
);
private async *_validDecreaseStakingPoolOperatorShare(): AsyncIterableIterator<AssertionResult | void> {
const { stakingPools } = this.actor.simulationEnvironment!;
const assertion = validDecreaseStakingPoolOperatorShareAssertion(this.actor.deployment, stakingPools);
while (true) {
const poolId = _.sample(this._getOperatorPoolIds(simulationEnvironment));
const poolId = _.sample(this._getOperatorPoolIds(stakingPools));
if (poolId === undefined) {
yield undefined;
} else {
const operatorShare = getRandomInteger(0, simulationEnvironment.stakingPools[poolId].operatorShare);
const operatorShare = getRandomInteger(0, stakingPools[poolId].operatorShare);
yield assertion.executeAsync(poolId, operatorShare, { from: this.actor.address });
}
}

View File

@ -4,7 +4,6 @@ import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { validMoveStakeAssertion, validStakeAssertion, validUnstakeAssertion } from '../function-assertions';
import { SimulationEnvironment } from '../simulation/simulation';
import { AssertionResult } from '../utils/function_assertions';
import { Actor, Constructor } from './base';
@ -37,15 +36,13 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
};
// Register this mixin's assertion generators
if (this.actor.simulationEnvironment !== undefined) {
this.actor.simulationActions = {
...this.actor.simulationActions,
validStake: this._validStake(this.actor.simulationEnvironment),
validUnstake: this._validUnstake(this.actor.simulationEnvironment),
validMoveStake: this._validMoveStake(this.actor.simulationEnvironment),
validStake: this._validStake(),
validUnstake: this._validUnstake(),
validMoveStake: this._validMoveStake(),
};
}
}
/**
* Stakes the given amount of ZRX. If `poolId` is provided, subsequently delegates the newly
@ -66,30 +63,26 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
}
}
private async *_validStake(
simulationEnvironment: SimulationEnvironment,
): AsyncIterableIterator<AssertionResult> {
private async *_validStake(): AsyncIterableIterator<AssertionResult> {
const { zrx } = this.actor.deployment.tokens;
const { deployment, balanceStore, globalStake } = simulationEnvironment;
const { deployment, balanceStore, globalStake } = this.actor.simulationEnvironment!;
const assertion = validStakeAssertion(deployment, balanceStore, globalStake, this.stake);
while (true) {
await simulationEnvironment.balanceStore.updateErc20BalancesAsync();
const zrxBalance = simulationEnvironment.balanceStore.balances.erc20[this.actor.address][zrx.address];
await balanceStore.updateErc20BalancesAsync();
const zrxBalance = balanceStore.balances.erc20[this.actor.address][zrx.address];
const amount = getRandomInteger(0, zrxBalance);
yield assertion.executeAsync(amount, { from: this.actor.address });
}
}
private async *_validUnstake(
simulationEnvironment: SimulationEnvironment,
): AsyncIterableIterator<AssertionResult> {
private async *_validUnstake(): AsyncIterableIterator<AssertionResult> {
const { stakingWrapper } = this.actor.deployment.staking;
const { deployment, balanceStore, globalStake } = simulationEnvironment;
const { deployment, balanceStore, globalStake } = this.actor.simulationEnvironment!;
const assertion = validUnstakeAssertion(deployment, balanceStore, globalStake, this.stake);
while (true) {
await simulationEnvironment.balanceStore.updateErc20BalancesAsync();
await balanceStore.updateErc20BalancesAsync();
const undelegatedStake = await stakingWrapper.getOwnerStakeByStatus.callAsync(
this.actor.address,
StakeStatus.Undelegated,
@ -103,16 +96,9 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
}
}
private async *_validMoveStake(
simulationEnvironment: SimulationEnvironment,
): AsyncIterableIterator<AssertionResult> {
const { deployment, globalStake } = simulationEnvironment;
const assertion = validMoveStakeAssertion(
deployment,
globalStake,
this.stake,
simulationEnvironment.stakingPools,
);
private async *_validMoveStake(): AsyncIterableIterator<AssertionResult> {
const { deployment, globalStake, stakingPools } = this.actor.simulationEnvironment!;
const assertion = validMoveStakeAssertion(deployment, globalStake, this.stake, stakingPools);
while (true) {
const fromPoolId = _.sample(Object.keys(_.omit(this.stake[StakeStatus.Delegated], ['total'])));
@ -122,7 +108,7 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
: (_.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
const from = new StakeInfo(fromStatus, fromPoolId);
const toPoolId = _.sample(Object.keys(simulationEnvironment.stakingPools));
const toPoolId = _.sample(Object.keys(stakingPools));
const toStatus =
toPoolId === undefined
? StakeStatus.Undelegated

View File

@ -1,6 +1,7 @@
{
"extends": ["@0x/tslint-config"],
"rules": {
"max-classes-per-file": false
"max-classes-per-file": false,
"no-non-null-assertion": false
}
}

View File

@ -19,8 +19,10 @@ export function validCreateStakingPoolAssertion(
const { stakingWrapper } = deployment.staking;
return new FunctionAssertion(stakingWrapper.createStakingPool, {
// Returns the expected ID of th created pool
before: async () => {
const lastPoolId = await stakingWrapper.lastPoolId.callAsync();
// Effectively the last poolId + 1, but as a bytestring
return `0x${new BigNumber(lastPoolId)
.plus(1)
.toString(16)
@ -35,9 +37,12 @@ export function validCreateStakingPoolAssertion(
) => {
logUtils.log(`createStakingPool(${operatorShare}, ${addOperatorAsMaker}) => ${expectedPoolId}`);
// Checks the logs for the new poolId, verifies that it is as expected
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);
// Adds the new pool to local state
pools[actualPoolId] = {
operator: txData.from as string,
operatorShare,

View File

@ -19,8 +19,10 @@ export function validDecreaseStakingPoolOperatorShareAssertion(
after: async (_beforeInfo, _result: FunctionResult, poolId: string, expectedOperatorShare: number) => {
logUtils.log(`decreaseStakingPoolOperatorShare(${poolId}, ${expectedOperatorShare})`);
// Checks that the on-chain pool's operator share has been updated.
const { operatorShare } = await stakingWrapper.getStakingPool.callAsync(poolId);
expect(operatorShare).to.bignumber.equal(expectedOperatorShare);
// Updates the pool in local state.
pools[poolId].operatorShare = operatorShare;
},
});

View File

@ -22,6 +22,56 @@ function decrementNextEpochBalance(stakeBalance: StoredBalance, amount: BigNumbe
_.update(stakeBalance, ['nextEpochBalance'], balance => (balance || constants.ZERO_AMOUNT).minus(amount));
}
function updateNextEpochBalances(
globalStake: GlobalStakeByStatus,
ownerStake: OwnerStakeByStatus,
pools: StakingPoolById,
from: StakeInfo,
to: StakeInfo,
amount: BigNumber,
): string[] {
// The on-chain state of these updated pools will be verified in the `after` of the assertion.
const updatedPools = [];
// Decrement next epoch balances associated with the `from` stake
if (from.status === StakeStatus.Undelegated) {
// Decrement owner undelegated stake
decrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount);
// Decrement global undelegated stake
decrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount);
} else if (from.status === StakeStatus.Delegated) {
// Decrement owner's delegated stake to this pool
decrementNextEpochBalance(ownerStake[StakeStatus.Delegated][from.poolId], amount);
// Decrement owner's total delegated stake
decrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount);
// Decrement global delegated stake
decrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount);
// Decrement pool's delegated stake
decrementNextEpochBalance(pools[from.poolId].delegatedStake, amount);
updatedPools.push(from.poolId);
}
// Increment next epoch balances associated with the `to` stake
if (to.status === StakeStatus.Undelegated) {
incrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount);
incrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount);
} else if (to.status === StakeStatus.Delegated) {
// Initializes the balance for this pool if the user has not previously delegated to it
_.defaults(ownerStake[StakeStatus.Delegated], {
[to.poolId]: new StoredBalance(),
});
// Increment owner's delegated stake to this pool
incrementNextEpochBalance(ownerStake[StakeStatus.Delegated][to.poolId], amount);
// Increment owner's total delegated stake
incrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount);
// Increment global delegated stake
incrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount);
// Increment pool's delegated stake
incrementNextEpochBalance(pools[to.poolId].delegatedStake, amount);
updatedPools.push(to.poolId);
}
return updatedPools;
}
/**
* Returns a FunctionAssertion for `moveStake` which assumes valid input is provided. The
* FunctionAssertion checks that the staker's
@ -50,35 +100,11 @@ export function validMoveStakeAssertion(
);
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);
}
// Update local balances to match the expected result of this `moveStake` operation
const updatedPools = updateNextEpochBalances(globalStake, ownerStake, pools, from, to, amount);
// Fetches on-chain owner stake balances and checks against local balances
const ownerUndelegatedStake = {
...new StoredBalance(),
...(await stakingWrapper.getOwnerStakeByStatus.callAsync(owner, StakeStatus.Undelegated)),
@ -90,6 +116,7 @@ export function validMoveStakeAssertion(
expect(ownerUndelegatedStake).to.deep.equal(ownerStake[StakeStatus.Undelegated]);
expect(ownerDelegatedStake).to.deep.equal(ownerStake[StakeStatus.Delegated].total);
// Fetches on-chain global stake balances and checks against local balances
const globalUndelegatedStake = await stakingWrapper.getGlobalStakeByStatus.callAsync(
StakeStatus.Undelegated,
);
@ -97,6 +124,7 @@ export function validMoveStakeAssertion(
expect(globalUndelegatedStake).to.deep.equal(globalStake[StakeStatus.Undelegated]);
expect(globalDelegatedStake).to.deep.equal(globalStake[StakeStatus.Delegated]);
// Fetches on-chain pool stake balances and checks against local balances
for (const poolId of updatedPools) {
const stakeDelegatedByOwner = await stakingWrapper.getStakeDelegatedToPoolByOwner.callAsync(
owner,

View File

@ -35,6 +35,7 @@ export function validStakeAssertion(
return new FunctionAssertion(stakingWrapper.stake, {
before: async (amount: BigNumber, txData: Partial<TxData>) => {
// Simulates the transfer of ZRX from staker to vault
const expectedBalances = LocalBalanceStore.create(balanceStore);
expectedBalances.transferAsset(
txData.from as string,
@ -52,22 +53,27 @@ export function validStakeAssertion(
) => {
logUtils.log(`stake(${amount})`);
// Checks that the ZRX transfer updated balances as expected.
await balanceStore.updateErc20BalancesAsync();
balanceStore.assertEquals(expectedBalances);
// Checks that the owner's undelegated stake has increased by the stake amount
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);
// Updates local state accordingly
ownerStake[StakeStatus.Undelegated] = expectedOwnerUndelegatedStake;
// Checks that the global undelegated stake has also increased by the stake amount
const globalUndelegatedStake = await stakingWrapper.getGlobalStakeByStatus.callAsync(
StakeStatus.Undelegated,
);
const expectedGlobalUndelegatedStake = expectedUndelegatedStake(globalStake, amount);
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(expectedGlobalUndelegatedStake);
// Updates local state accordingly
globalStake[StakeStatus.Undelegated] = expectedGlobalUndelegatedStake;
},
});

View File

@ -35,6 +35,7 @@ export function validUnstakeAssertion(
return new FunctionAssertion(stakingWrapper.unstake, {
before: async (amount: BigNumber, txData: Partial<TxData>) => {
// Simulates the transfer of ZRX from vault to staker
const expectedBalances = LocalBalanceStore.create(balanceStore);
expectedBalances.transferAsset(
zrxVault.address,
@ -52,22 +53,27 @@ export function validUnstakeAssertion(
) => {
logUtils.log(`unstake(${amount})`);
// Checks that the ZRX transfer updated balances as expected.
await balanceStore.updateErc20BalancesAsync();
balanceStore.assertEquals(expectedBalances);
// Checks that the owner's undelegated stake has decreased by the stake amount
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);
// Updates local state accordingly
ownerStake[StakeStatus.Undelegated] = expectedOwnerUndelegatedStake;
// Checks that the global undelegated stake has also decreased by the stake amount
const globalUndelegatedStake = await stakingWrapper.getGlobalStakeByStatus.callAsync(
StakeStatus.Undelegated,
);
const expectedGlobalUndelegatedStake = expectedUndelegatedStake(globalStake, amount);
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(expectedGlobalUndelegatedStake);
// Updates local state accordingly
globalStake[StakeStatus.Undelegated] = expectedGlobalUndelegatedStake;
},
});

View File

@ -6,6 +6,7 @@ import {
ExchangeFillEventArgs,
LocalBalanceStore,
} from '@0x/contracts-exchange';
import { ReferenceFunctions } from '@0x/contracts-exchange-libs';
import {
constants as stakingConstants,
IStakingEventsEpochEndedEventArgs,
@ -284,7 +285,11 @@ 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(operatorShare).dividedToIntegerBy(constants.PPM_DENOMINATOR);
const operatorReward = ReferenceFunctions.getPartialAmountFloor(
new BigNumber(operatorShare),
new BigNumber(constants.PPM_DENOMINATOR),
rewardsAvailable,
);
const delegatorReward = rewardsAvailable.minus(operatorReward);
// Finalize the pool. This should automatically pay the operator in WETH.
@ -363,7 +368,11 @@ blockchainTests.resets('fillOrder integration tests', env => {
balanceStore.assertEquals(expectedBalances);
// The rewards are split between the operator and delegator based on the pool's operatorShare
const operatorReward = rewardsAvailable.times(operatorShare).dividedToIntegerBy(constants.PPM_DENOMINATOR);
const operatorReward = ReferenceFunctions.getPartialAmountFloor(
new BigNumber(operatorShare),
new BigNumber(constants.PPM_DENOMINATOR),
rewardsAvailable,
);
// Finalize the pool. This should automatically pay the operator in WETH.
const [finalizePoolReceipt] = await delegator.finalizePoolsAsync([poolId]);